1/** 2 * @license 3 * Video.js 7.5.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.5.5"; 22 23 function _inheritsLoose(subClass, superClass) { 24 subClass.prototype = Object.create(superClass.prototype); 25 subClass.prototype.constructor = subClass; 26 subClass.__proto__ = superClass; 27 } 28 29 function _setPrototypeOf(o, p) { 30 _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { 31 o.__proto__ = p; 32 return o; 33 }; 34 35 return _setPrototypeOf(o, p); 36 } 37 38 function isNativeReflectConstruct() { 39 if (typeof Reflect === "undefined" || !Reflect.construct) return false; 40 if (Reflect.construct.sham) return false; 41 if (typeof Proxy === "function") return true; 42 43 try { 44 Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); 45 return true; 46 } catch (e) { 47 return false; 48 } 49 } 50 51 function _construct(Parent, args, Class) { 52 if (isNativeReflectConstruct()) { 53 _construct = Reflect.construct; 54 } else { 55 _construct = function _construct(Parent, args, Class) { 56 var a = [null]; 57 a.push.apply(a, args); 58 var Constructor = Function.bind.apply(Parent, a); 59 var instance = new Constructor(); 60 if (Class) _setPrototypeOf(instance, Class.prototype); 61 return instance; 62 }; 63 } 64 65 return _construct.apply(null, arguments); 66 } 67 68 function _assertThisInitialized(self) { 69 if (self === void 0) { 70 throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); 71 } 72 73 return self; 74 } 75 76 function _taggedTemplateLiteralLoose(strings, raw) { 77 if (!raw) { 78 raw = strings.slice(0); 79 } 80 81 strings.raw = raw; 82 return strings; 83 } 84 85 /** 86 * @file create-logger.js 87 * @module create-logger 88 */ 89 90 var history = []; 91 /** 92 * Log messages to the console and history based on the type of message 93 * 94 * @private 95 * @param {string} type 96 * The name of the console method to use. 97 * 98 * @param {Array} args 99 * The arguments to be passed to the matching console method. 100 */ 101 102 var LogByTypeFactory = function LogByTypeFactory(name, log) { 103 return function (type, level, args) { 104 var lvl = log.levels[level]; 105 var lvlRegExp = new RegExp("^(" + lvl + ")$"); 106 107 if (type !== 'log') { 108 // Add the type to the front of the message when it's not "log". 109 args.unshift(type.toUpperCase() + ':'); 110 } // Add console prefix after adding to history. 111 112 113 args.unshift(name + ':'); // Add a clone of the args at this point to history. 114 115 if (history) { 116 history.push([].concat(args)); 117 } // If there's no console then don't try to output messages, but they will 118 // still be stored in history. 119 120 121 if (!window$1.console) { 122 return; 123 } // Was setting these once outside of this function, but containing them 124 // in the function makes it easier to test cases where console doesn't exist 125 // when the module is executed. 126 127 128 var fn = window$1.console[type]; 129 130 if (!fn && type === 'debug') { 131 // Certain browsers don't have support for console.debug. For those, we 132 // should default to the closest comparable log. 133 fn = window$1.console.info || window$1.console.log; 134 } // Bail out if there's no console or if this type is not allowed by the 135 // current logging level. 136 137 138 if (!fn || !lvl || !lvlRegExp.test(type)) { 139 return; 140 } 141 142 fn[Array.isArray(args) ? 'apply' : 'call'](window$1.console, args); 143 }; 144 }; 145 146 function createLogger(name) { 147 // This is the private tracking variable for logging level. 148 var level = 'info'; // the curried logByType bound to the specific log and history 149 150 var logByType; 151 /** 152 * Logs plain debug messages. Similar to `console.log`. 153 * 154 * Due to [limitations](https://github.com/jsdoc3/jsdoc/issues/955#issuecomment-313829149) 155 * of our JSDoc template, we cannot properly document this as both a function 156 * and a namespace, so its function signature is documented here. 157 * 158 * #### Arguments 159 * ##### *args 160 * Mixed[] 161 * 162 * Any combination of values that could be passed to `console.log()`. 163 * 164 * #### Return Value 165 * 166 * `undefined` 167 * 168 * @namespace 169 * @param {Mixed[]} args 170 * One or more messages or objects that should be logged. 171 */ 172 173 var log = function log() { 174 for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { 175 args[_key] = arguments[_key]; 176 } 177
vendor: 9,004 bytes, lines 178-522
178 logByType('log', level, args); 179 }; // This is the logByType helper that the logging methods below use 180 181 182 logByType = LogByTypeFactory(name, log); 183 /** 184 * Create a new sublogger which chains the old name to the new name. 185 * 186 * For example, doing `videojs.log.createLogger('player')` and then using that logger will log the following: 187 * ```js 188 * mylogger('foo'); 189 * // > VIDEOJS: player: foo 190 * ``` 191 * 192 * @param {string} name 193 * The name to add call the new logger 194 * @return {Object} 195 */ 196 197 log.createLogger = function (subname) { 198 return createLogger(name + ': ' + subname); 199 }; 200 /** 201 * Enumeration of available logging levels, where the keys are the level names 202 * and the values are `|`-separated strings containing logging methods allowed 203 * in that logging level. These strings are used to create a regular expression 204 * matching the function name being called. 205 * 206 * Levels provided by Video.js are: 207 * 208 * - `off`: Matches no calls. Any value that can be cast to `false` will have 209 * this effect. The most restrictive. 210 * - `all`: Matches only Video.js-provided functions (`debug`, `log`, 211 * `log.warn`, and `log.error`). 212 * - `debug`: Matches `log.debug`, `log`, `log.warn`, and `log.error` calls. 213 * - `info` (default): Matches `log`, `log.warn`, and `log.error` calls. 214 * - `warn`: Matches `log.warn` and `log.error` calls. 215 * - `error`: Matches only `log.error` calls. 216 * 217 * @type {Object} 218 */ 219 220 221 log.levels = { 222 all: 'debug|log|warn|error', 223 off: '', 224 debug: 'debug|log|warn|error', 225 info: 'log|warn|error', 226 warn: 'warn|error', 227 error: 'error', 228 DEFAULT: level 229 }; 230 /** 231 * Get or set the current logging level. 232 * 233 * If a string matching a key from {@link module:log.levels} is provided, acts 234 * as a setter. 235 * 236 * @param {string} [lvl] 237 * Pass a valid level to set a new logging level. 238 * 239 * @return {string} 240 * The current logging level. 241 */ 242 243 log.level = function (lvl) { 244 if (typeof lvl === 'string') { 245 if (!log.levels.hasOwnProperty(lvl)) { 246 throw new Error("\"" + lvl + "\" in not a valid log level"); 247 } 248 249 level = lvl; 250 } 251 252 return level; 253 }; 254 /** 255 * Returns an array containing everything that has been logged to the history. 256 * 257 * This array is a shallow clone of the internal history record. However, its 258 * contents are _not_ cloned; so, mutating objects inside this array will 259 * mutate them in history. 260 * 261 * @return {Array} 262 */ 263 264 265 log.history = function () { 266 return history ? [].concat(history) : []; 267 }; 268 /** 269 * Allows you to filter the history by the given logger name 270 * 271 * @param {string} fname 272 * The name to filter by 273 * 274 * @return {Array} 275 * The filtered list to return 276 */ 277 278 279 log.history.filter = function (fname) { 280 return (history || []).filter(function (historyItem) { 281 // if the first item in each historyItem includes `fname`, then it's a match 282 return new RegExp(".*" + fname + ".*").test(historyItem[0]); 283 }); 284 }; 285 /** 286 * Clears the internal history tracking, but does not prevent further history 287 * tracking. 288 */ 289 290 291 log.history.clear = function () { 292 if (history) { 293 history.length = 0; 294 } 295 }; 296 /** 297 * Disable history tracking if it is currently enabled. 298 */ 299 300 301 log.history.disable = function () { 302 if (history !== null) { 303 history.length = 0; 304 history = null; 305 } 306 }; 307 /** 308 * Enable history tracking if it is currently disabled. 309 */ 310 311 312 log.history.enable = function () { 313 if (history === null) { 314 history = []; 315 } 316 }; 317 /** 318 * Logs error messages. Similar to `console.error`. 319 * 320 * @param {Mixed[]} args 321 * One or more messages or objects that should be logged as an error 322 */ 323 324 325 log.error = function () { 326 for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { 327 args[_key2] = arguments[_key2]; 328 } 329 330 return logByType('error', level, args); 331 }; 332 /** 333 * Logs warning messages. Similar to `console.warn`. 334 * 335 * @param {Mixed[]} args 336 * One or more messages or objects that should be logged as a warning. 337 */ 338 339 340 log.warn = function () { 341 for (var _len3 = arguments.length, args = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) { 342 args[_key3] = arguments[_key3]; 343 } 344 345 return logByType('warn', level, args); 346 }; 347 /** 348 * Logs debug messages. Similar to `console.debug`, but may also act as a comparable 349 * log if `console.debug` is not available 350 * 351 * @param {Mixed[]} args 352 * One or more messages or objects that should be logged as debug. 353 */ 354 355 356 log.debug = function () { 357 for (var _len4 = arguments.length, args = new Array(_len4), _key4 = 0; _key4 < _len4; _key4++) { 358 args[_key4] = arguments[_key4]; 359 } 360 361 return logByType('debug', level, args); 362 }; 363 364 return log; 365 } 366 367 /** 368 * @file log.js 369 * @module log 370 */ 371 var log = createLogger('VIDEOJS'); 372 var createLogger$1 = log.createLogger; 373 374 function clean(s) { 375 return s.replace(/\n\r?\s*/g, ''); 376 } 377 378 var tsml = function tsml(sa) { 379 var s = '', 380 i = 0; 381 382 for (; i < arguments.length; i++) { 383 s += clean(sa[i]) + (arguments[i + 1] || ''); 384 } 385 386 return s; 387 }; 388 389 /** 390 * @file obj.js 391 * @module obj 392 */ 393 394 /** 395 * @callback obj:EachCallback 396 * 397 * @param {Mixed} value 398 * The current key for the object that is being iterated over. 399 * 400 * @param {string} key 401 * The current key-value for object that is being iterated over 402 */ 403 404 /** 405 * @callback obj:ReduceCallback 406 * 407 * @param {Mixed} accum 408 * The value that is accumulating over the reduce loop. 409 * 410 * @param {Mixed} value 411 * The current key for the object that is being iterated over. 412 * 413 * @param {string} key 414 * The current key-value for object that is being iterated over 415 * 416 * @return {Mixed} 417 * The new accumulated value. 418 */ 419 var toString = Object.prototype.toString; 420 /** 421 * Get the keys of an Object 422 * 423 * @param {Object} 424 * The Object to get the keys from 425 * 426 * @return {string[]} 427 * An array of the keys from the object. Returns an empty array if the 428 * object passed in was invalid or had no keys. 429 * 430 * @private 431 */ 432 433 var keys = function keys(object) { 434 return isObject(object) ? Object.keys(object) : []; 435 }; 436 /** 437 * Array-like iteration for objects. 438 * 439 * @param {Object} object 440 * The object to iterate over 441 * 442 * @param {obj:EachCallback} fn 443 * The callback function which is called for each key in the object. 444 */ 445 446 447 function each(object, fn) { 448 keys(object).forEach(function (key) { 449 return fn(object[key], key); 450 }); 451 } 452 /** 453 * Array-like reduce for objects. 454 * 455 * @param {Object} object 456 * The Object that you want to reduce. 457 * 458 * @param {Function} fn 459 * A callback function which is called for each key in the object. It 460 * receives the accumulated value and the per-iteration value and key 461 * as arguments. 462 * 463 * @param {Mixed} [initial = 0] 464 * Starting value 465 * 466 * @return {Mixed} 467 * The final accumulated value. 468 */ 469 470 function reduce(object, fn, initial) { 471 if (initial === void 0) { 472 initial = 0; 473 } 474 475 return keys(object).reduce(function (accum, key) { 476 return fn(accum, object[key], key); 477 }, initial); 478 } 479 /** 480 * Object.assign-style object shallow merge/extend. 481 * 482 * @param {Object} target 483 * @param {Object} ...sources 484 * @return {Object} 485 */ 486 487 function assign(target) { 488 for (var _len = arguments.length, sources = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { 489 sources[_key - 1] = arguments[_key]; 490 } 491 492 if (Object.assign) { 493 return Object.assign.apply(Object, [target].concat(sources)); 494 } 495 496 sources.forEach(function (source) { 497 if (!source) { 498 return; 499 } 500 501 each(source, function (value, key) { 502 target[key] = value; 503 }); 504 }); 505 return target; 506 } 507 /** 508 * Returns whether a value is an object of any kind - including DOM nodes, 509 * arrays, regular expressions, etc. Not functions, though. 510 * 511 * This avoids the gotcha where using `typeof` on a `null` value 512 * results in `'object'`. 513 * 514 * @param {Object} value 515 * @return {boolean} 516 */ 517 518 function isObject(value) { 519 return !!value && typeof value === 'object'; 520 } 521 /** 522 * Returns whether an object appears to be a "plain" object - that is, a
523 * direct instance of `Object`. 524 * 525 * @param {Object} value 526 * @return {boolean} 527 */ 528 529 function isPlain(value) { 530 return isObject(value) && toString.call(value) === '[object Object]' && value.constructor === Object; 531 } 532 533 /** 534 * @file computed-style.js 535 * @module computed-style 536 */ 537 /** 538 * A safe getComputedStyle. 539 * 540 * This is needed because in Firefox, if the player is loaded in an iframe with 541 * `display:none`, then `getComputedStyle` returns `null`, so, we do a 542 * null-check to make sure that the player doesn't break in these cases. 543 * 544 * @function 545 * @param {Element} el 546 * The element you want the computed style of 547 * 548 * @param {string} prop 549 * The property name you want 550 * 551 * @see https://bugzilla.mozilla.org/show_bug.cgi?id=548397 552 */ 553 554 function computedStyle(el, prop) { 555 if (!el || !prop) { 556 return ''; 557 } 558 559 if (typeof window$1.getComputedStyle === 'function') { 560 var cs = window$1.getComputedStyle(el); 561 return cs ? cs[prop] : ''; 562 } 563 564 return ''; 565 } 566 567 function _templateObject() { 568 var data = _taggedTemplateLiteralLoose(["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 ", " to ", "."]); 569 570 _templateObject = function _templateObject() { 571 return data; 572 }; 573 574 return data; 575 } 576 /** 577 * Detect if a value is a string with any non-whitespace characters. 578 * 579 * @private 580 * @param {string} str 581 * The string to check 582 * 583 * @return {boolean} 584 * Will be `true` if the string is non-blank, `false` otherwise. 585 * 586 */ 587 588 function isNonBlankString(str) { 589 return typeof str === 'string' && /\S/.test(str); 590 } 591 /** 592 * Throws an error if the passed string has whitespace. This is used by 593 * class methods to be relatively consistent with the classList API. 594 * 595 * @private 596 * @param {string} str 597 * The string to check for whitespace. 598 * 599 * @throws {Error} 600 * Throws an error if there is whitespace in the string. 601 */ 602 603 604 function throwIfWhitespace(str) { 605 if (/\s/.test(str)) { 606 throw new Error('class has illegal whitespace characters'); 607 } 608 } 609 /** 610 * Produce a regular expression for matching a className within an elements className. 611 * 612 * @private 613 * @param {string} className 614 * The className to generate the RegExp for. 615 * 616 * @return {RegExp} 617 * The RegExp that will check for a specific `className` in an elements 618 * className. 619 */ 620 621 622 function classRegExp(className) { 623 return new RegExp('(^|\\s)' + className + '($|\\s)'); 624 } 625 /** 626 * Whether the current DOM interface appears to be real (i.e. not simulated). 627 * 628 * @return {boolean} 629 * Will be `true` if the DOM appears to be real, `false` otherwise. 630 */ 631 632 633 function isReal() { 634 // Both document and window will never be undefined thanks to `global`. 635 return document === window$1.document; 636 } 637 /** 638 * Determines, via duck typing, whether or not a value is a DOM element. 639 * 640 * @param {Mixed} value 641 * The value to check. 642 * 643 * @return {boolean} 644 * Will be `true` if the value is a DOM element, `false` otherwise. 645 */ 646 647 function isEl(value) { 648 return isObject(value) && value.nodeType === 1; 649 } 650 /** 651 * Determines if the current DOM is embedded in an iframe. 652 * 653 * @return {boolean} 654 * Will be `true` if the DOM is embedded in an iframe, `false` 655 * otherwise. 656 */ 657 658 function isInFrame() { 659 // We need a try/catch here because Safari will throw errors when attempting 660 // to get either `parent` or `self` 661 try { 662 return window$1.parent !== window$1.self; 663 } catch (x) { 664 return true; 665 } 666 } 667 /** 668 * Creates functions to query the DOM using a given method. 669 * 670 * @private 671 * @param {string} method 672 * The method to create the query with. 673 * 674 * @return {Function} 675 * The query method 676 */ 677 678 function createQuerier(method) { 679 return function (selector, context) { 680 if (!isNonBlankString(selector)) { 681 return document[method](null); 682 } 683 684 if (isNonBlankString(context)) { 685 context = document.querySelector(context); 686 } 687 688 var ctx = isEl(context) ? context : document; 689 return ctx[method] && ctx[method](selector); 690 }; 691 } 692 /** 693 * Creates an element and applies properties, attributes, and inserts content. 694 * 695 * @param {string} [tagName='div'] 696 * Name of tag to be created. 697 * 698 * @param {Object} [properties={}] 699 * Element properties to be applied. 700 * 701 * @param {Object} [attributes={}] 702 * Element attributes to be applied. 703 * 704 * @param {module:dom~ContentDescriptor} content 705 * A content descriptor object. 706 * 707 * @return {Element} 708 * The element that was created. 709 */ 710 711 712 function createEl(tagName, properties, attributes, content) { 713 if (tagName === void 0) { 714 tagName = 'div'; 715 } 716 717 if (properties === void 0) { 718 properties = {}; 719 } 720 721 if (attributes === void 0) { 722 attributes = {}; 723 } 724 725 var el = document.createElement(tagName);
vendor: 4,990 bytes, lines 726-897
726 Object.getOwnPropertyNames(properties).forEach(function (propName) { 727 var val = properties[propName]; // See #2176 728 // We originally were accepting both properties and attributes in the 729 // same object, but that doesn't work so well. 730 731 if (propName.indexOf('aria-') !== -1 || propName === 'role' || propName === 'type') { 732 log.warn(tsml(_templateObject(), propName, val)); 733 el.setAttribute(propName, val); // Handle textContent since it's not supported everywhere and we have a 734 // method for it. 735 } else if (propName === 'textContent') { 736 textContent(el, val); 737 } else { 738 el[propName] = val; 739 } 740 }); 741 Object.getOwnPropertyNames(attributes).forEach(function (attrName) { 742 el.setAttribute(attrName, attributes[attrName]); 743 }); 744 745 if (content) { 746 appendContent(el, content); 747 } 748 749 return el; 750 } 751 /** 752 * Injects text into an element, replacing any existing contents entirely. 753 * 754 * @param {Element} el 755 * The element to add text content into 756 * 757 * @param {string} text 758 * The text content to add. 759 * 760 * @return {Element} 761 * The element with added text content. 762 */ 763 764 function textContent(el, text) { 765 if (typeof el.textContent === 'undefined') { 766 el.innerText = text; 767 } else { 768 el.textContent = text; 769 } 770 771 return el; 772 } 773 /** 774 * Insert an element as the first child node of another 775 * 776 * @param {Element} child 777 * Element to insert 778 * 779 * @param {Element} parent 780 * Element to insert child into 781 */ 782 783 function prependTo(child, parent) { 784 if (parent.firstChild) { 785 parent.insertBefore(child, parent.firstChild); 786 } else { 787 parent.appendChild(child); 788 } 789 } 790 /** 791 * Check if an element has a class name. 792 * 793 * @param {Element} element 794 * Element to check 795 * 796 * @param {string} classToCheck 797 * Class name to check for 798 * 799 * @return {boolean} 800 * Will be `true` if the element has a class, `false` otherwise. 801 * 802 * @throws {Error} 803 * Throws an error if `classToCheck` has white space. 804 */ 805 806 function hasClass(element, classToCheck) { 807 throwIfWhitespace(classToCheck); 808 809 if (element.classList) { 810 return element.classList.contains(classToCheck); 811 } 812 813 return classRegExp(classToCheck).test(element.className); 814 } 815 /** 816 * Add a class name to an element. 817 * 818 * @param {Element} element 819 * Element to add class name to. 820 * 821 * @param {string} classToAdd 822 * Class name to add. 823 * 824 * @return {Element} 825 * The DOM element with the added class name. 826 */ 827 828 function addClass(element, classToAdd) { 829 if (element.classList) { 830 element.classList.add(classToAdd); // Don't need to `throwIfWhitespace` here because `hasElClass` will do it 831 // in the case of classList not being supported. 832 } else if (!hasClass(element, classToAdd)) { 833 element.className = (element.className + ' ' + classToAdd).trim(); 834 } 835 836 return element; 837 } 838 /** 839 * Remove a class name from an element. 840 * 841 * @param {Element} element 842 * Element to remove a class name from. 843 * 844 * @param {string} classToRemove 845 * Class name to remove 846 * 847 * @return {Element} 848 * The DOM element with class name removed. 849 */ 850 851 function removeClass(element, classToRemove) { 852 if (element.classList) { 853 element.classList.remove(classToRemove); 854 } else { 855 throwIfWhitespace(classToRemove); 856 element.className = element.className.split(/\s+/).filter(function (c) { 857 return c !== classToRemove; 858 }).join(' '); 859 } 860 861 return element; 862 } 863 /** 864 * The callback definition for toggleClass. 865 * 866 * @callback module:dom~PredicateCallback 867 * @param {Element} element 868 * The DOM element of the Component. 869 * 870 * @param {string} classToToggle 871 * The `className` that wants to be toggled 872 * 873 * @return {boolean|undefined} 874 * If `true` is returned, the `classToToggle` will be added to the 875 * `element`. If `false`, the `classToToggle` will be removed from 876 * the `element`. If `undefined`, the callback will be ignored. 877 */ 878 879 /** 880 * Adds or removes a class name to/from an element depending on an optional 881 * condition or the presence/absence of the class name. 882 * 883 * @param {Element} element 884 * The element to toggle a class name on. 885 * 886 * @param {string} classToToggle 887 * The class that should be toggled. 888 * 889 * @param {boolean|module:dom~PredicateCallback} [predicate] 890 * See the return value for {@link module:dom~PredicateCallback} 891 * 892 * @return {Element} 893 * The element with a class that has been toggled. 894 */ 895 896 function toggleClass(element, classToToggle, predicate) { 897 // This CANNOT use `classList` internally because IE11 does not support the
vendor: 4,324 bytes, lines 898-1054
898 // second parameter to the `classList.toggle()` method! Which is fine because 899 // `classList` will be used by the add/remove functions. 900 var has = hasClass(element, classToToggle); 901 902 if (typeof predicate === 'function') { 903 predicate = predicate(element, classToToggle); 904 } 905 906 if (typeof predicate !== 'boolean') { 907 predicate = !has; 908 } // If the necessary class operation matches the current state of the 909 // element, no action is required. 910 911 912 if (predicate === has) { 913 return; 914 } 915 916 if (predicate) { 917 addClass(element, classToToggle); 918 } else { 919 removeClass(element, classToToggle); 920 } 921 922 return element; 923 } 924 /** 925 * Apply attributes to an HTML element. 926 * 927 * @param {Element} el 928 * Element to add attributes to. 929 * 930 * @param {Object} [attributes] 931 * Attributes to be applied. 932 */ 933 934 function setAttributes(el, attributes) { 935 Object.getOwnPropertyNames(attributes).forEach(function (attrName) { 936 var attrValue = attributes[attrName]; 937 938 if (attrValue === null || typeof attrValue === 'undefined' || attrValue === false) { 939 el.removeAttribute(attrName); 940 } else { 941 el.setAttribute(attrName, attrValue === true ? '' : attrValue); 942 } 943 }); 944 } 945 /** 946 * Get an element's attribute values, as defined on the HTML tag. 947 * 948 * Attributes are not the same as properties. They're defined on the tag 949 * or with setAttribute. 950 * 951 * @param {Element} tag 952 * Element from which to get tag attributes. 953 * 954 * @return {Object} 955 * All attributes of the element. Boolean attributes will be `true` or 956 * `false`, others will be strings. 957 */ 958 959 function getAttributes(tag) { 960 var obj = {}; // known boolean attributes 961 // we can check for matching boolean properties, but not all browsers 962 // and not all tags know about these attributes, so, we still want to check them manually 963 964 var knownBooleans = ',' + 'autoplay,controls,playsinline,loop,muted,default,defaultMuted' + ','; 965 966 if (tag && tag.attributes && tag.attributes.length > 0) { 967 var attrs = tag.attributes; 968 969 for (var i = attrs.length - 1; i >= 0; i--) { 970 var attrName = attrs[i].name; 971 var attrVal = attrs[i].value; // check for known booleans 972 // the matching element property will return a value for typeof 973 974 if (typeof tag[attrName] === 'boolean' || knownBooleans.indexOf(',' + attrName + ',') !== -1) { 975 // the value of an included boolean attribute is typically an empty 976 // string ('') which would equal false if we just check for a false value. 977 // we also don't want support bad code like autoplay='false' 978 attrVal = attrVal !== null ? true : false; 979 } 980 981 obj[attrName] = attrVal; 982 } 983 } 984 985 return obj; 986 } 987 /** 988 * Get the value of an element's attribute. 989 * 990 * @param {Element} el 991 * A DOM element. 992 * 993 * @param {string} attribute 994 * Attribute to get the value of. 995 * 996 * @return {string} 997 * The value of the attribute. 998 */ 999 1000 function getAttribute(el, attribute) { 1001 return el.getAttribute(attribute); 1002 } 1003 /** 1004 * Set the value of an element's attribute. 1005 * 1006 * @param {Element} el 1007 * A DOM element. 1008 * 1009 * @param {string} attribute 1010 * Attribute to set. 1011 * 1012 * @param {string} value 1013 * Value to set the attribute to. 1014 */ 1015 1016 function setAttribute(el, attribute, value) { 1017 el.setAttribute(attribute, value); 1018 } 1019 /** 1020 * Remove an element's attribute. 1021 * 1022 * @param {Element} el 1023 * A DOM element. 1024 * 1025 * @param {string} attribute 1026 * Attribute to remove. 1027 */ 1028 1029 function removeAttribute(el, attribute) { 1030 el.removeAttribute(attribute); 1031 } 1032 /** 1033 * Attempt to block the ability to select text. 1034 */ 1035 1036 function blockTextSelection() { 1037 document.body.focus(); 1038 1039 document.onselectstart = function () { 1040 return false; 1041 }; 1042 } 1043 /** 1044 * Turn off text selection blocking. 1045 */ 1046 1047 function unblockTextSelection() { 1048 document.onselectstart = function () { 1049 return true; 1050 }; 1051 } 1052 /** 1053 * Identical to the native `getBoundingClientRect` function, but ensures that 1054 * the method is supported at all (it is in all browsers we claim to supp
1054ort) 1055 * and that the element is in the DOM before continuing. 1056 * 1057 * This wrapper function also shims properties which are not provided by some 1058 * older browsers (namely, IE8). 1059 * 1060 * Additionally, some browsers do not support adding properties to a 1061 * `ClientRect`/`DOMRect` object; so, we shallow-copy it with the standard 1062 * properties (except `x` and `y` which are not widely supported). This helps 1063 * avoid implementations where keys are non-enumerable. 1064 * 1065 * @param {Element} el 1066 * Element whose `ClientRect` we want to calculate. 1067 * 1068 * @return {Object|undefined} 1069 * Always returns a plain object - or `undefined` if it cannot. 1070 */ 1071 1072 function getBoundingClientRect(el) { 1073 if (el && el.getBoundingClientRect && el.parentNode) { 1074 var rect = el.getBoundingClientRect(); 1075 var result = {}; 1076 ['bottom', 'height', 'left', 'right', 'top', 'width'].forEach(function (k) { 1077 if (rect[k] !== undefined) { 1078 result[k] = rect[k]; 1079 } 1080 }); 1081 1082 if (!result.height) { 1083 result.height = parseFloat(computedStyle(el, 'height')); 1084 } 1085 1086 if (!result.width) { 1087 result.width = parseFloat(computedStyle(el, 'width')); 1088 } 1089 1090 return result; 1091 } 1092 } 1093 /** 1094 * Represents the position of a DOM element on the page. 1095 * 1096 * @typedef {Object} module:dom~Position 1097 * 1098 * @property {number} left 1099 * Pixels to the left. 1100 * 1101 * @property {number} top 1102 * Pixels from the top. 1103 */ 1104 1105 /** 1106 * Get the position of an element in the DOM. 1107 * 1108 * Uses `getBoundingClientRect` technique from John Resig. 1109 * 1110 * @see http://ejohn.org/blog/getboundingclientrect-is-awesome/ 1111 * 1112 * @param {Element} el 1113 * Element from which to get offset. 1114 * 1115 * @return {module:dom~Position} 1116 * The position of the element that was passed in. 1117 */ 1118 1119 function findPosition(el) { 1120 var box; 1121 1122 if (el.getBoundingClientRect && el.parentNode) { 1123 box = el.getBoundingClientRect(); 1124 } 1125 1126 if (!box) { 1127 return { 1128 left: 0, 1129 top: 0 1130 }; 1131 } 1132 1133 var docEl = document.documentElement; 1134 var body = document.body; 1135 var clientLeft = docEl.clientLeft || body.clientLeft || 0; 1136 var scrollLeft = window$1.pageXOffset || body.scrollLeft; 1137 var left = box.left + scrollLeft - clientLeft; 1138 var clientTop = docEl.clientTop || body.clientTop || 0; 1139 var scrollTop = window$1.pageYOffset || body.scrollTop; 1140 var top = box.top + scrollTop - clientTop; // Android sometimes returns slightly off decimal values, so need to round 1141 1142 return { 1143 left: Math.round(left), 1144 top: Math.round(top) 1145 }; 1146 } 1147 /** 1148 * Represents x and y coordinates for a DOM element or mouse pointer. 1149 * 1150 * @typedef {Object} module:dom~Coordinates 1151 * 1152 * @property {number} x 1153 * x coordinate in pixels 1154 * 1155 * @property {number} y 1156 * y coordinate in pixels 1157 */ 1158 1159 /** 1160 * Get the pointer position within an element. 1161 * 1162 * The base on the coordinates are the bottom left of the element. 1163 * 1164 * @param {Element} el 1165 * Element on which to get the pointer position on. 1166 * 1167 * @param {EventTarget~Event} event 1168 * Event object. 1169 * 1170 * @return {module:dom~Coordinates} 1171 * A coordinates object corresponding to the mouse position. 1172 * 1173 */ 1174 1175 function getPointerPosition(el, event) { 1176 var position = {}; 1177 var box = findPosition(el); 1178 var boxW = el.offsetWidth; 1179 var boxH = el.offsetHeight; 1180 var boxY = box.top; 1181 var boxX = box.left; 1182 var pageY = event.pageY; 1183 var pageX = event.pageX; 1184 1185 if (event.changedTouches) { 1186 pageX = event.changedTouches[0].pageX; 1187 pageY = event.changedTouches[0].pageY; 1188 } 1189 1190 position.y = Math.max(0, Math.min(1, (boxY - pageY + boxH) / boxH)); 1191 position.x = Math.max(0, Math.min(1, (pageX - boxX) / boxW)); 1192 return position; 1193 } 1194 /** 1195 * Determines, via duck typing, whether or not a value is a text node. 1196 * 1197 * @param {Mixed} value 1198 * Check if this value is a text node. 1199 * 1200 * @return {boolean} 1201 * Will be `true` if the value is a text node, `false` otherwise. 1202 */ 1203 1204 function isTextNode(value) { 1205 return isObject(value) && value.nodeType === 3; 1206 } 1207 /** 1208 * Empties the contents of an element. 1209 * 1210 * @param {Element} el 1211 * The element to empty children from 1212 * 1213 * @return {Element} 1214 * The element with no children 1215 */ 1216 1217 function emptyEl(el) { 1218 while (el.firstChild) { 1219 el.removeChild(el.firstChild); 1220 } 1221 1222 return el; 1223 } 1224 /** 1225 * This is a mixed value that describes content to be injected into the DOM 1226 * via some method. It can be of the following types: 1227 * 1228 * Type | Description 1229 * -----------|------------- 1230 * `string` | The value will be normalized into a text node. 1231 * `Element` | The value will be accepted as-is. 1232 * `TextNode` | The value will be accepted as-is.
1233 * `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). 1234 * `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. 1235 * 1236 * @typedef {string|Element|TextNode|Array|Function} module:dom~ContentDescriptor 1237 */ 1238 1239 /** 1240 * Normalizes content for eventual insertion into the DOM. 1241 * 1242 * This allows a wide range of content definition methods, but helps protect 1243 * from falling into the trap of simply writing to `innerHTML`, which could 1244 * be an XSS concern. 1245 * 1246 * The content for an element can be passed in multiple types and 1247 * combinations, whose behavior is as follows: 1248 * 1249 * @param {module:dom~ContentDescriptor} content 1250 * A content descriptor value. 1251 * 1252 * @return {Array} 1253 * All of the content that was passed in, normalized to an array of 1254 * elements or text nodes. 1255 */ 1256 1257 function normalizeContent(content) { 1258 // First, invoke content if it is a function. If it produces an array, 1259 // that needs to happen before normalization. 1260 if (typeof content === 'function') { 1261 content = content(); 1262 } // Next up, normalize to an array, so one or many items can be normalized, 1263 // filtered, and returned. 1264 1265 1266 return (Array.isArray(content) ? content : [content]).map(function (value) { 1267 // First, invoke value if it is a function to produce a new value, 1268 // which will be subsequently normalized to a Node of some kind. 1269 if (typeof value === 'function') { 1270 value = value(); 1271 } 1272 1273 if (isEl(value) || isTextNode(value)) { 1274 return value; 1275 } 1276 1277 if (typeof value === 'string' && /\S/.test(value)) { 1278 return document.createTextNode(value); 1279 } 1280 }).filter(function (value) { 1281 return value; 1282 }); 1283 } 1284 /** 1285 * Normalizes and appends content to an element. 1286 * 1287 * @param {Element} el 1288 * Element to append normalized content to. 1289 * 1290 * @param {module:dom~ContentDescriptor} content 1291 * A content descriptor value. 1292 * 1293 * @return {Element} 1294 * The element with appended normalized content. 1295 */ 1296 1297 function appendContent(el, content) { 1298 normalizeContent(content).forEach(function (node) { 1299 return el.appendChild(node); 1300 }); 1301 return el; 1302 } 1303 /** 1304 * Normalizes and inserts content into an element; this is identical to 1305 * `appendContent()`, except it empties the element first. 1306 * 1307 * @param {Element} el 1308 * Element to insert normalized content into. 1309 * 1310 * @param {module:dom~ContentDescriptor} content 1311 * A content descriptor value. 1312 * 1313 * @return {Element} 1314 * The element with inserted normalized content. 1315 */ 1316 1317 function insertContent(el, content) { 1318 return appendContent(emptyEl(el), content); 1319 } 1320 /** 1321 * Check if an event was a single left click. 1322 * 1323 * @param {EventTarget~Event} event 1324 * Event object. 1325 * 1326 * @return {boolean} 1327 * Will be `true` if a single left click, `false` otherwise. 1328 */ 1329 1330 function isSingleLeftClick(event) { 1331 // Note: if you create something draggable, be sure to 1332 // call it on both `mousedown` and `mousemove` event, 1333 // otherwise `mousedown` should be enough for a button 1334 if (event.button === undefined && event.buttons === undefined) { 1335 // Why do we need `buttons` ? 1336 // Because, middle mouse sometimes have this: 1337 // e.button === 0 and e.buttons === 4 1338 // Furthermore, we want to prevent combination click, something like 1339 // HOLD middlemouse then left click, that would be 1340 // e.button === 0, e.buttons === 5 1341 // just `button` is not gonna work 1342 // Alright, then what this block does ? 1343 // this is for chrome `simulate mobile devices`
vendor: 8,747 bytes, lines 1344-1655
1344 // I want to support this as well 1345 return true; 1346 } 1347 1348 if (event.button === 0 && event.buttons === undefined) { 1349 // Touch screen, sometimes on some specific device, `buttons` 1350 // doesn't have anything (safari on ios, blackberry...) 1351 return true; 1352 } 1353 1354 if (event.button !== 0 || event.buttons !== 1) { 1355 // This is the reason we have those if else block above 1356 // if any special case we can catch and let it slide 1357 // we do it above, when get to here, this definitely 1358 // is-not-left-click 1359 return false; 1360 } 1361 1362 return true; 1363 } 1364 /** 1365 * Finds a single DOM element matching `selector` within the optional 1366 * `context` of another DOM element (defaulting to `document`). 1367 * 1368 * @param {string} selector 1369 * A valid CSS selector, which will be passed to `querySelector`. 1370 * 1371 * @param {Element|String} [context=document] 1372 * A DOM element within which to query. Can also be a selector 1373 * string in which case the first matching element will be used 1374 * as context. If missing (or no element matches selector), falls 1375 * back to `document`. 1376 * 1377 * @return {Element|null} 1378 * The element that was found or null. 1379 */ 1380 1381 var $ = createQuerier('querySelector'); 1382 /** 1383 * Finds a all DOM elements matching `selector` within the optional 1384 * `context` of another DOM element (defaulting to `document`). 1385 * 1386 * @param {string} selector 1387 * A valid CSS selector, which will be passed to `querySelectorAll`. 1388 * 1389 * @param {Element|String} [context=document] 1390 * A DOM element within which to query. Can also be a selector 1391 * string in which case the first matching element will be used 1392 * as context. If missing (or no element matches selector), falls 1393 * back to `document`. 1394 * 1395 * @return {NodeList} 1396 * A element list of elements that were found. Will be empty if none 1397 * were found. 1398 * 1399 */ 1400 1401 var $$ = createQuerier('querySelectorAll'); 1402 1403 var Dom = /*#__PURE__*/Object.freeze({ 1404 isReal: isReal, 1405 isEl: isEl, 1406 isInFrame: isInFrame, 1407 createEl: createEl, 1408 textContent: textContent, 1409 prependTo: prependTo, 1410 hasClass: hasClass, 1411 addClass: addClass, 1412 removeClass: removeClass, 1413 toggleClass: toggleClass, 1414 setAttributes: setAttributes, 1415 getAttributes: getAttributes, 1416 getAttribute: getAttribute, 1417 setAttribute: setAttribute, 1418 removeAttribute: removeAttribute, 1419 blockTextSelection: blockTextSelection, 1420 unblockTextSelection: unblockTextSelection, 1421 getBoundingClientRect: getBoundingClientRect, 1422 findPosition: findPosition, 1423 getPointerPosition: getPointerPosition, 1424 isTextNode: isTextNode, 1425 emptyEl: emptyEl, 1426 normalizeContent: normalizeContent, 1427 appendContent: appendContent, 1428 insertContent: insertContent, 1429 isSingleLeftClick: isSingleLeftClick, 1430 $: $, 1431 $$: $$ 1432 }); 1433 1434 /** 1435 * @file guid.js 1436 * @module guid 1437 */ 1438 1439 /** 1440 * Unique ID for an element or function 1441 * @type {Number} 1442 */ 1443 var _guid = 1; 1444 /** 1445 * Get a unique auto-incrementing ID by number that has not been returned before. 1446 * 1447 * @return {number} 1448 * A new unique ID. 1449 */ 1450 1451 function newGUID() { 1452 return _guid++; 1453 } 1454 1455 /** 1456 * @file dom-data.js 1457 * @module dom-data 1458 */ 1459 /** 1460 * Element Data Store. 1461 * 1462 * Allows for binding data to an element without putting it directly on the 1463 * element. Ex. Event listeners are stored here. 1464 * (also from jsninja.com, slightly modified and updated for closure compiler) 1465 * 1466 * @type {Object} 1467 * @private 1468 */ 1469 1470 var elData = {}; 1471 /* 1472 * Unique attribute name to store an element's guid in 1473 * 1474 * @type {String} 1475 * @constant 1476 * @private 1477 */ 1478 1479 var elIdAttr = 'vdata' + new Date().getTime(); 1480 /** 1481 * Returns the cache object where data for an element is stored 1482 * 1483 * @param {Element} el 1484 * Element to store data for. 1485 * 1486 * @return {Object} 1487 * The cache object for that el that was passed in. 1488 */ 1489 1490 function getData(el) { 1491 var id = el[elIdAttr]; 1492 1493 if (!id) { 1494 id = el[elIdAttr] = newGUID(); 1495 } 1496 1497 if (!elData[id]) { 1498 elData[id] = {}; 1499 } 1500 1501 return elData[id]; 1502 } 1503 /** 1504 * Returns whether or not an element has cached data 1505 * 1506 * @param {Element} el 1507 * Check if this element has cached data. 1508 * 1509 * @return {boolean} 1510 * - True if the DOM element has cached data. 1511 * - False otherwise. 1512 */ 1513 1514 function hasData(el) { 1515 var id = el[elIdAttr]; 1516 1517 if (!id) { 1518 return false; 1519 } 1520 1521 return !!Object.getOwnPropertyNames(elData[id]).length; 1522 } 1523 /** 1524 * Delete data for the element from the cache and the guid attr from getElementById 1525 * 1526 * @param {Element} el 1527 * Remove cached data for this element. 1528 */ 1529 1530 function removeData(el) { 1531 var id = el[elIdAttr]; 1532 1533 if (!id) { 1534 return; 1535 } // Remove all stored data 1536 1537 1538 delete elData[id]; // Remove the elIdAttr property from the DOM node 1539 1540 try { 1541 delete el[elIdAttr]; 1542 } catch (e) { 1543 if (el.removeAttribute) { 1544 el.removeAttribute(elIdAttr); 1545 } else { 1546 // IE doesn't appear to support removeAttribute on the document element 1547 el[elIdAttr] = null; 1548 } 1549 } 1550 } 1551 1552 /** 1553 * @file events.js. An Event System (John Resig - Secrets of a JS Ninja http://jsninja.com/) 1554 * (Original book version wasn't completely usable, so fixed some things and made Closure Compiler compatible) 1555 * This should work very similarly to jQuery's events, however it's based off the book version which isn't as 1556 * robust as jquery's, so there's probably some differences. 1557 * 1558 * @file events.js 1559 * @module events 1560 */ 1561 /** 1562 * Clean up the listener cache and dispatchers 1563 * 1564 * @param {Element|Object} elem 1565 * Element to clean up 1566 * 1567 * @param {string} type 1568 * Type of event to clean up 1569 */ 1570 1571 function _cleanUpEvents(elem, type) { 1572 var data = getData(elem); // Remove the events of a particular type if there are none left 1573 1574 if (data.handlers[type].length === 0) { 1575 delete data.handlers[type]; // data.handlers[type] = null; 1576 // Setting to null was causing an error with data.handlers 1577 // Remove the meta-handler from the element 1578 1579 if (elem.removeEventListener) { 1580 elem.removeEventListener(type, data.dispatcher, false); 1581 } else if (elem.detachEvent) { 1582 elem.detachEvent('on' + type, data.dispatcher); 1583 } 1584 } // Remove the events object if there are no types left 1585 1586 1587 if (Object.getOwnPropertyNames(data.handlers).length <= 0) { 1588 delete data.handlers; 1589 delete data.dispatcher; 1590 delete data.disabled; 1591 } // Finally remove the element data if there is no data left 1592 1593 1594 if (Object.getOwnPropertyNames(data).length === 0) { 1595 removeData(elem); 1596 } 1597 } 1598 /** 1599 * Loops through an array of event types and calls the requested method for each type. 1600 * 1601 * @param {Function} fn 1602 * The event method we want to use. 1603 * 1604 * @param {Element|Object} elem 1605 * Element or object to bind listeners to 1606 * 1607 * @param {string} type 1608 * Type of event to bind to. 1609 * 1610 * @param {EventTarget~EventListener} callback 1611 * Event listener. 1612 */ 1613 1614 1615 function _handleMultipleEvents(fn, elem, types, callback) { 1616 types.forEach(function (type) { 1617 // Call the event method for each one of the types 1618 fn(elem, type, callback); 1619 }); 1620 } 1621 /** 1622 * Fix a native event to have standard property values 1623 * 1624 * @param {Object} event 1625 * Event object to fix. 1626 * 1627 * @return {Object} 1628 * Fixed event object. 1629 */ 1630 1631 1632 function fixEvent(event) { 1633 function returnTrue() { 1634 return true; 1635 } 1636 1637 function returnFalse() { 1638 return false; 1639 } // Test if fixing up is needed 1640 // Used to check if !event.stopPropagation instead of isPropagationStopped 1641 // But native events return true for stopPropagation, but don't have 1642 // other expected methods like isPropagationStopped. Seems to be a problem 1643 // with the Javascript Ninja code. So we're just overriding all events now. 1644 1645 1646 if (!event || !event.isPropagationStopped) { 1647 var old = event || window$1.event; 1648 event = {}; // Clone the old object so that we can modify the values event = {}; 1649 // IE8 Doesn't like when you mess with native event properties 1650 // Firefox returns false for event.hasOwnProperty('type') and other props 1651 // which makes copying more difficult. 1652 // TODO: Probably best to create a whitelist of event props 1653 1654 for (var key in old) { 1655 // Safari 6.0.3 warns you if you try to copy deprecated layerX/Y
vendor: 4,373 bytes, lines 1656-1799
1656 // Chrome warns you if you try to copy deprecated keyboardEvent.keyLocation 1657 // and webkitMovementX/Y 1658 if (key !== 'layerX' && key !== 'layerY' && key !== 'keyLocation' && key !== 'webkitMovementX' && key !== 'webkitMovementY') { 1659 // Chrome 32+ warns if you try to copy deprecated returnValue, but 1660 // we still want to if preventDefault isn't supported (IE8). 1661 if (!(key === 'returnValue' && old.preventDefault)) { 1662 event[key] = old[key]; 1663 } 1664 } 1665 } // The event occurred on this element 1666 1667 1668 if (!event.target) { 1669 event.target = event.srcElement || document; 1670 } // Handle which other element the event is related to 1671 1672 1673 if (!event.relatedTarget) { 1674 event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement; 1675 } // Stop the default browser action 1676 1677 1678 event.preventDefault = function () { 1679 if (old.preventDefault) { 1680 old.preventDefault(); 1681 } 1682 1683 event.returnValue = false; 1684 old.returnValue = false; 1685 event.defaultPrevented = true; 1686 }; 1687 1688 event.defaultPrevented = false; // Stop the event from bubbling 1689 1690 event.stopPropagation = function () { 1691 if (old.stopPropagation) { 1692 old.stopPropagation(); 1693 } 1694 1695 event.cancelBubble = true; 1696 old.cancelBubble = true; 1697 event.isPropagationStopped = returnTrue; 1698 }; 1699 1700 event.isPropagationStopped = returnFalse; // Stop the event from bubbling and executing other handlers 1701 1702 event.stopImmediatePropagation = function () { 1703 if (old.stopImmediatePropagation) { 1704 old.stopImmediatePropagation(); 1705 } 1706 1707 event.isImmediatePropagationStopped = returnTrue; 1708 event.stopPropagation(); 1709 }; 1710 1711 event.isImmediatePropagationStopped = returnFalse; // Handle mouse position 1712 1713 if (event.clientX !== null && event.clientX !== undefined) { 1714 var doc = document.documentElement; 1715 var body = document.body; 1716 event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0); 1717 event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0); 1718 } // Handle key presses 1719 1720 1721 event.which = event.charCode || event.keyCode; // Fix button for mouse clicks: 1722 // 0 == left; 1 == middle; 2 == right 1723 1724 if (event.button !== null && event.button !== undefined) { 1725 // The following is disabled because it does not pass videojs-standard 1726 // and... yikes. 1727 1728 /* eslint-disable */ 1729 event.button = event.button & 1 ? 0 : event.button & 4 ? 1 : event.button & 2 ? 2 : 0; 1730 /* eslint-enable */ 1731 } 1732 } // Returns fixed-up instance 1733 1734 1735 return event; 1736 } 1737 /** 1738 * Whether passive event listeners are supported 1739 */ 1740 1741 var _supportsPassive = false; 1742 1743 (function () { 1744 try { 1745 var opts = Object.defineProperty({}, 'passive', { 1746 get: function get() { 1747 _supportsPassive = true; 1748 } 1749 }); 1750 window$1.addEventListener('test', null, opts); 1751 window$1.removeEventListener('test', null, opts); 1752 } catch (e) {// disregard 1753 } 1754 })(); 1755 /** 1756 * Touch events Chrome expects to be passive 1757 */ 1758 1759 1760 var passiveEvents = ['touchstart', 'touchmove']; 1761 /** 1762 * Add an event listener to element 1763 * It stores the handler function in a separate cache object 1764 * and adds a generic handler to the element's event, 1765 * along with a unique id (guid) to the element. 1766 * 1767 * @param {Element|Object} elem 1768 * Element or object to bind listeners to 1769 * 1770 * @param {string|string[]} type 1771 * Type of event to bind to. 1772 * 1773 * @param {EventTarget~EventListener} fn 1774 * Event listener. 1775 */ 1776 1777 function on(elem, type, fn) { 1778 if (Array.isArray(type)) { 1779 return _handleMultipleEvents(on, elem, type, fn); 1780 } 1781 1782 var data = getData(elem); // We need a place to store all our handler data 1783 1784 if (!data.handlers) { 1785 data.handlers = {}; 1786 } 1787 1788 if (!data.handlers[type]) { 1789 data.handlers[type] = []; 1790 } 1791 1792 if (!fn.guid) { 1793 fn.guid = newGUID(); 1794 } 1795 1796 data.handlers[type].push(fn); 1797 1798 if (!data.dispatcher) { 1799 data.disabled = false;
vendor: 9,558 bytes, lines 1800-2139
1800 1801 data.dispatcher = function (event, hash) { 1802 if (data.disabled) { 1803 return; 1804 } 1805 1806 event = fixEvent(event); 1807 var handlers = data.handlers[event.type]; 1808 1809 if (handlers) { 1810 // Copy handlers so if handlers are added/removed during the process it doesn't throw everything off. 1811 var handlersCopy = handlers.slice(0); 1812 1813 for (var m = 0, n = handlersCopy.length; m < n; m++) { 1814 if (event.isImmediatePropagationStopped()) { 1815 break; 1816 } else { 1817 try { 1818 handlersCopy[m].call(elem, event, hash); 1819 } catch (e) { 1820 log.error(e); 1821 } 1822 } 1823 } 1824 } 1825 }; 1826 } 1827 1828 if (data.handlers[type].length === 1) { 1829 if (elem.addEventListener) { 1830 var options = false; 1831 1832 if (_supportsPassive && passiveEvents.indexOf(type) > -1) { 1833 options = { 1834 passive: true 1835 }; 1836 } 1837 1838 elem.addEventListener(type, data.dispatcher, options); 1839 } else if (elem.attachEvent) { 1840 elem.attachEvent('on' + type, data.dispatcher); 1841 } 1842 } 1843 } 1844 /** 1845 * Removes event listeners from an element 1846 * 1847 * @param {Element|Object} elem 1848 * Object to remove listeners from. 1849 * 1850 * @param {string|string[]} [type] 1851 * Type of listener to remove. Don't include to remove all events from element. 1852 * 1853 * @param {EventTarget~EventListener} [fn] 1854 * Specific listener to remove. Don't include to remove listeners for an event 1855 * type. 1856 */ 1857 1858 function off(elem, type, fn) { 1859 // Don't want to add a cache object through getElData if not needed 1860 if (!hasData(elem)) { 1861 return; 1862 } 1863 1864 var data = getData(elem); // If no events exist, nothing to unbind 1865 1866 if (!data.handlers) { 1867 return; 1868 } 1869 1870 if (Array.isArray(type)) { 1871 return _handleMultipleEvents(off, elem, type, fn); 1872 } // Utility function 1873 1874 1875 var removeType = function removeType(el, t) { 1876 data.handlers[t] = []; 1877 1878 _cleanUpEvents(el, t); 1879 }; // Are we removing all bound events? 1880 1881 1882 if (type === undefined) { 1883 for (var t in data.handlers) { 1884 if (Object.prototype.hasOwnProperty.call(data.handlers || {}, t)) { 1885 removeType(elem, t); 1886 } 1887 } 1888 1889 return; 1890 } 1891 1892 var handlers = data.handlers[type]; // If no handlers exist, nothing to unbind 1893 1894 if (!handlers) { 1895 return; 1896 } // If no listener was provided, remove all listeners for type 1897 1898 1899 if (!fn) { 1900 removeType(elem, type); 1901 return; 1902 } // We're only removing a single handler 1903 1904 1905 if (fn.guid) { 1906 for (var n = 0; n < handlers.length; n++) { 1907 if (handlers[n].guid === fn.guid) { 1908 handlers.splice(n--, 1); 1909 } 1910 } 1911 } 1912 1913 _cleanUpEvents(elem, type); 1914 } 1915 /** 1916 * Trigger an event for an element 1917 * 1918 * @param {Element|Object} elem 1919 * Element to trigger an event on 1920 * 1921 * @param {EventTarget~Event|string} event 1922 * A string (the type) or an event object with a type attribute 1923 * 1924 * @param {Object} [hash] 1925 * data hash to pass along with the event 1926 * 1927 * @return {boolean|undefined} 1928 * Returns the opposite of `defaultPrevented` if default was 1929 * prevented. Otherwise, returns `undefined` 1930 */ 1931 1932 function trigger(elem, event, hash) { 1933 // Fetches element data and a reference to the parent (for bubbling). 1934 // Don't want to add a data object to cache for every parent, 1935 // so checking hasElData first. 1936 var elemData = hasData(elem) ? getData(elem) : {}; 1937 var parent = elem.parentNode || elem.ownerDocument; // type = event.type || event, 1938 // handler; 1939 // If an event name was passed as a string, creates an event out of it 1940 1941 if (typeof event === 'string') { 1942 event = { 1943 type: event, 1944 target: elem 1945 }; 1946 } else if (!event.target) { 1947 event.target = elem; 1948 } // Normalizes the event properties. 1949 1950 1951 event = fixEvent(event); // If the passed element has a dispatcher, executes the established handlers. 1952 1953 if (elemData.dispatcher) { 1954 elemData.dispatcher.call(elem, event, hash); 1955 } // Unless explicitly stopped or the event does not bubble (e.g. media events) 1956 // recursively calls this function to bubble the event up the DOM. 1957 1958 1959 if (parent && !event.isPropagationStopped() && event.bubbles === true) { 1960 trigger.call(null, parent, event, hash); // If at the top of the DOM, triggers the default action unless disabled. 1961 } else if (!parent && !event.defaultPrevented && event.target && event.target[event.type]) { 1962 var targetData = getData(event.target); // Checks if the target has a default action for this event. 1963 1964 if (event.target[event.type]) { 1965 // Temporarily disables event dispatching on the target as we have already executed the handler. 1966 targetData.disabled = true; // Executes the default action. 1967 1968 if (typeof event.target[event.type] === 'function') { 1969 event.target[event.type](); 1970 } // Re-enables event dispatching. 1971 1972 1973 targetData.disabled = false; 1974 } 1975 } // Inform the triggerer if the default was prevented by returning false 1976 1977 1978 return !event.defaultPrevented; 1979 } 1980 /** 1981 * Trigger a listener only once for an event. 1982 * 1983 * @param {Element|Object} elem 1984 * Element or object to bind to. 1985 * 1986 * @param {string|string[]} type 1987 * Name/type of event 1988 * 1989 * @param {Event~EventListener} fn 1990 * Event listener function 1991 */ 1992 1993 function one(elem, type, fn) { 1994 if (Array.isArray(type)) { 1995 return _handleMultipleEvents(one, elem, type, fn); 1996 } 1997 1998 var func = function func() { 1999 off(elem, type, func); 2000 fn.apply(this, arguments); 2001 }; // copy the guid to the new function so it can removed using the original function's ID 2002 2003 2004 func.guid = fn.guid = fn.guid || newGUID(); 2005 on(elem, type, func); 2006 } 2007 2008 var Events = /*#__PURE__*/Object.freeze({ 2009 fixEvent: fixEvent, 2010 on: on, 2011 off: off, 2012 trigger: trigger, 2013 one: one 2014 }); 2015 2016 /** 2017 * @file setup.js - Functions for setting up a player without 2018 * user interaction based on the data-setup `attribute` of the video tag. 2019 * 2020 * @module setup 2021 */ 2022 var _windowLoaded = false; 2023 var videojs; 2024 /** 2025 * Set up any tags that have a data-setup `attribute` when the player is started. 2026 */ 2027 2028 var autoSetup = function autoSetup() { 2029 // Protect against breakage in non-browser environments and check global autoSetup option. 2030 if (!isReal() || videojs.options.autoSetup === false) { 2031 return; 2032 } 2033 2034 var vids = Array.prototype.slice.call(document.getElementsByTagName('video')); 2035 var audios = Array.prototype.slice.call(document.getElementsByTagName('audio')); 2036 var divs = Array.prototype.slice.call(document.getElementsByTagName('video-js')); 2037 var mediaEls = vids.concat(audios, divs); // Check if any media elements exist 2038 2039 if (mediaEls && mediaEls.length > 0) { 2040 for (var i = 0, e = mediaEls.length; i < e; i++) { 2041 var mediaEl = mediaEls[i]; // Check if element exists, has getAttribute func. 2042 2043 if (mediaEl && mediaEl.getAttribute) { 2044 // Make sure this player hasn't already been set up. 2045 if (mediaEl.player === undefined) { 2046 var options = mediaEl.getAttribute('data-setup'); // Check if data-setup attr exists. 2047 // We only auto-setup if they've added the data-setup attr. 2048 2049 if (options !== null) { 2050 // Create new video.js instance. 2051 videojs(mediaEl); 2052 } 2053 } // If getAttribute isn't defined, we need to wait for the DOM. 2054 2055 } else { 2056 autoSetupTimeout(1); 2057 break; 2058 } 2059 } // No videos were found, so keep looping unless page is finished loading. 2060 2061 } else if (!_windowLoaded) { 2062 autoSetupTimeout(1); 2063 } 2064 }; 2065 /** 2066 * Wait until the page is loaded before running autoSetup. This will be called in 2067 * autoSetup if `hasLoaded` returns false. 2068 * 2069 * @param {number} wait 2070 * How long to wait in ms 2071 * 2072 * @param {module:videojs} [vjs] 2073 * The videojs library function 2074 */ 2075 2076 2077 function autoSetupTimeout(wait, vjs) { 2078 if (vjs) { 2079 videojs = vjs; 2080 } 2081 2082 window$1.setTimeout(autoSetup, wait); 2083 } 2084 2085 if (isReal() && document.readyState === 'complete') { 2086 _windowLoaded = true; 2087 } else { 2088 /** 2089 * Listen for the load event on window, and set _windowLoaded to true. 2090 * 2091 * @listens load 2092 */ 2093 one(window$1, 'load', function () { 2094 _windowLoaded = true; 2095 }); 2096 } 2097 2098 /** 2099 * @file stylesheet.js 2100 * @module stylesheet 2101 */ 2102 /** 2103 * Create a DOM syle element given a className for it. 2104 * 2105 * @param {string} className 2106 * The className to add to the created style element. 2107 * 2108 * @return {Element} 2109 * The element that was created. 2110 */ 2111 2112 var createStyleElement = function createStyleElement(className) { 2113 var style = document.createElement('style'); 2114 style.className = className; 2115 return style; 2116 }; 2117 /** 2118 * Add text to a DOM element. 2119 * 2120 * @param {Element} el 2121 * The Element to add text content to. 2122 * 2123 * @param {string} content 2124 * The text to add to the element. 2125 */ 2126 2127 var setTextContent = function setTextContent(el, content) { 2128 if (el.styleSheet) { 2129 el.styleSheet.cssText = content; 2130 } else { 2131 el.textContent = content; 2132 } 2133 }; 2134 2135 /** 2136 * @file fn.js 2137 * @module fn 2138 */ 2139 /**
2140 * Bind (a.k.a proxy or context). A simple method for changing the context of 2141 * a function. 2142 * 2143 * It also stores a unique id on the function so it can be easily removed from 2144 * events. 2145 * 2146 * @function 2147 * @param {Mixed} context 2148 * The object to bind as scope. 2149 * 2150 * @param {Function} fn 2151 * The function to be bound to a scope. 2152 * 2153 * @param {number} [uid] 2154 * An optional unique ID for the function to be set 2155 * 2156 * @return {Function} 2157 * The new function that will be bound into the context given 2158 */ 2159 2160 var bind = function bind(context, fn, uid) { 2161 // Make sure the function has a unique ID 2162 if (!fn.guid) { 2163 fn.guid = newGUID(); 2164 } // Create the new function that changes the context 2165 2166 2167 var bound = function bound() { 2168 return fn.apply(context, arguments); 2169 }; // Allow for the ability to individualize this function 2170 // Needed in the case where multiple objects might share the same prototype 2171 // IF both items add an event listener with the same function, then you try to remove just one 2172 // it will remove both because they both have the same guid. 2173 // when using this, you need to use the bind method when you remove the listener as well. 2174 // currently used in text tracks 2175 2176 2177 bound.guid = uid ? uid + '_' + fn.guid : fn.guid; 2178 return bound; 2179 }; 2180 /** 2181 * Wraps the given function, `fn`, with a new function that only invokes `fn` 2182 * at most once per every `wait` milliseconds. 2183 * 2184 * @function 2185 * @param {Function} fn 2186 * The function to be throttled. 2187 * 2188 * @param {number} wait 2189 * The number of milliseconds by which to throttle. 2190 * 2191 * @return {Function} 2192 */ 2193 2194 var throttle = function throttle(fn, wait) { 2195 var last = Date.now(); 2196 2197 var throttled = function throttled() { 2198 var now = Date.now(); 2199 2200 if (now - last >= wait) { 2201 fn.apply(void 0, arguments); 2202 last = now; 2203 } 2204 }; 2205 2206 return throttled; 2207 }; 2208 /** 2209 * Creates a debounced function that delays invoking `func` until after `wait` 2210 * milliseconds have elapsed since the last time the debounced function was 2211 * invoked. 2212 * 2213 * Inspired by lodash and underscore implementations. 2214 * 2215 * @function 2216 * @param {Function} func 2217 * The function to wrap with debounce behavior. 2218 * 2219 * @param {number} wait 2220 * The number of milliseconds to wait after the last invocation. 2221 * 2222 * @param {boolean} [immediate] 2223 * Whether or not to invoke the function immediately upon creation. 2224 * 2225 * @param {Object} [context=window] 2226 * The "context" in which the debounced function should debounce. For 2227 * example, if this function should be tied to a Video.js player, 2228 * the player can be passed here. Alternatively, defaults to the 2229 * global `window` object. 2230 * 2231 * @return {Function} 2232 * A debounced function. 2233 */ 2234 2235 var debounce = function debounce(func, wait, immediate, context) { 2236 if (context === void 0) { 2237 context = window$1; 2238 } 2239 2240 var timeout; 2241 2242 var cancel = function cancel() { 2243 context.clearTimeout(timeout); 2244 timeout = null; 2245 }; 2246 /* eslint-disable consistent-this */ 2247 2248 2249 var debounced = function debounced() { 2250 var self = this; 2251 var args = arguments; 2252 2253 var _later = function later() { 2254 timeout = null; 2255 _later = null; 2256 2257 if (!immediate) { 2258 func.apply(self, args); 2259 } 2260 }; 2261 2262 if (!timeout && immediate) { 2263 func.apply(self, args); 2264 } 2265 2266 context.clearTimeout(timeout); 2267 timeout = context.setTimeout(_later, wait); 2268 }; 2269 /* eslint-enable consistent-this */ 2270 2271 2272 debounced.cancel = cancel; 2273 return debounced; 2274 }; 2275 2276 /** 2277 * @file src/js/event-target.js 2278 */ 2279 /** 2280 * `EventTarget` is a class that can have the same API as the DOM `EventTarget`. It 2281 * adds shorthand functions that wrap around lengthy functions. For example: 2282 * the `on` function is a wrapper around `addEventListener`. 2283 * 2284 * @see [EventTarget Spec]{@link https://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-EventTarget} 2285 * @class EventTarget 2286 */ 2287 2288 var EventTarget = function EventTarget() {}; 2289 /**
vendor: 7,375 bytes, lines 2290-2550
2290 * A Custom DOM event. 2291 * 2292 * @typedef {Object} EventTarget~Event 2293 * @see [Properties]{@link https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent} 2294 */ 2295 2296 /** 2297 * All event listeners should follow the following format. 2298 * 2299 * @callback EventTarget~EventListener 2300 * @this {EventTarget} 2301 * 2302 * @param {EventTarget~Event} event 2303 * the event that triggered this function 2304 * 2305 * @param {Object} [hash] 2306 * hash of data sent during the event 2307 */ 2308 2309 /** 2310 * An object containing event names as keys and booleans as values. 2311 * 2312 * > NOTE: If an event name is set to a true value here {@link EventTarget#trigger} 2313 * will have extra functionality. See that function for more information. 2314 * 2315 * @property EventTarget.prototype.allowedEvents_ 2316 * @private 2317 */ 2318 2319 2320 EventTarget.prototype.allowedEvents_ = {}; 2321 /** 2322 * Adds an `event listener` to an instance of an `EventTarget`. An `event listener` is a 2323 * function that will get called when an event with a certain name gets triggered. 2324 * 2325 * @param {string|string[]} type 2326 * An event name or an array of event names. 2327 * 2328 * @param {EventTarget~EventListener} fn 2329 * The function to call with `EventTarget`s 2330 */ 2331 2332 EventTarget.prototype.on = function (type, fn) { 2333 // Remove the addEventListener alias before calling Events.on 2334 // so we don't get into an infinite type loop 2335 var ael = this.addEventListener; 2336 2337 this.addEventListener = function () {}; 2338 2339 on(this, type, fn); 2340 this.addEventListener = ael; 2341 }; 2342 /** 2343 * An alias of {@link EventTarget#on}. Allows `EventTarget` to mimic 2344 * the standard DOM API. 2345 * 2346 * @function 2347 * @see {@link EventTarget#on} 2348 */ 2349 2350 2351 EventTarget.prototype.addEventListener = EventTarget.prototype.on; 2352 /** 2353 * Removes an `event listener` for a specific event from an instance of `EventTarget`. 2354 * This makes it so that the `event listener` will no longer get called when the 2355 * named event happens. 2356 * 2357 * @param {string|string[]} type 2358 * An event name or an array of event names. 2359 * 2360 * @param {EventTarget~EventListener} fn 2361 * The function to remove. 2362 */ 2363 2364 EventTarget.prototype.off = function (type, fn) { 2365 off(this, type, fn); 2366 }; 2367 /** 2368 * An alias of {@link EventTarget#off}. Allows `EventTarget` to mimic 2369 * the standard DOM API. 2370 * 2371 * @function 2372 * @see {@link EventTarget#off} 2373 */ 2374 2375 2376 EventTarget.prototype.removeEventListener = EventTarget.prototype.off; 2377 /** 2378 * This function will add an `event listener` that gets triggered only once. After the 2379 * first trigger it will get removed. This is like adding an `event listener` 2380 * with {@link EventTarget#on} that calls {@link EventTarget#off} on itself. 2381 * 2382 * @param {string|string[]} type 2383 * An event name or an array of event names. 2384 * 2385 * @param {EventTarget~EventListener} fn 2386 * The function to be called once for each event name. 2387 */ 2388 2389 EventTarget.prototype.one = function (type, fn) { 2390 // Remove the addEventListener alialing Events.on 2391 // so we don't get into an infinite type loop 2392 var ael = this.addEventListener; 2393 2394 this.addEventListener = function () {}; 2395 2396 one(this, type, fn); 2397 this.addEventListener = ael; 2398 }; 2399 /** 2400 * This function causes an event to happen. This will then cause any `event listeners` 2401 * that are waiting for that event, to get called. If there are no `event listeners` 2402 * for an event then nothing will happen. 2403 * 2404 * If the name of the `Event` that is being triggered is in `EventTarget.allowedEvents_`. 2405 * Trigger will also call the `on` + `uppercaseEventName` function. 2406 * 2407 * Example: 2408 * 'click' is in `EventTarget.allowedEvents_`, so, trigger will attempt to call 2409 * `onClick` if it exists. 2410 * 2411 * @param {string|EventTarget~Event|Object} event 2412 * The name of the event, an `Event`, or an object with a key of type set to 2413 * an event name. 2414 */ 2415 2416 2417 EventTarget.prototype.trigger = function (event) { 2418 var type = event.type || event; // deprecation 2419 // In a future version we should default target to `this` 2420 // similar to how we default the target to `elem` in 2421 // `Events.trigger`. Right now the default `target` will be 2422 // `document` due to the `Event.fixEvent` call. 2423 2424 if (typeof event === 'string') { 2425 event = { 2426 type: type 2427 }; 2428 } 2429 2430 event = fixEvent(event); 2431 2432 if (this.allowedEvents_[type] && this['on' + type]) { 2433 this['on' + type](event); 2434 } 2435 2436 trigger(this, event); 2437 }; 2438 /** 2439 * An alias of {@link EventTarget#trigger}. Allows `EventTarget` to mimic 2440 * the standard DOM API. 2441 * 2442 * @function 2443 * @see {@link EventTarget#trigger} 2444 */ 2445 2446 2447 EventTarget.prototype.dispatchEvent = EventTarget.prototype.trigger; 2448 var EVENT_MAP; 2449 2450 EventTarget.prototype.queueTrigger = function (event) { 2451 var _this = this; 2452 2453 // only set up EVENT_MAP if it'll be used 2454 if (!EVENT_MAP) { 2455 EVENT_MAP = new Map(); 2456 } 2457 2458 var type = event.type || event; 2459 var map = EVENT_MAP.get(this); 2460 2461 if (!map) { 2462 map = new Map(); 2463 EVENT_MAP.set(this, map); 2464 } 2465 2466 var oldTimeout = map.get(type); 2467 map.delete(type); 2468 window$1.clearTimeout(oldTimeout); 2469 var timeout = window$1.setTimeout(function () { 2470 // if we cleared out all timeouts for the current target, delete its map 2471 if (map.size === 0) { 2472 map = null; 2473 EVENT_MAP.delete(_this); 2474 } 2475 2476 _this.trigger(event); 2477 }, 0); 2478 map.set(type, timeout); 2479 }; 2480 2481 /** 2482 * @file mixins/evented.js 2483 * @module evented 2484 */ 2485 /** 2486 * Returns whether or not an object has had the evented mixin applied. 2487 * 2488 * @param {Object} object 2489 * An object to test. 2490 * 2491 * @return {boolean} 2492 * Whether or not the object appears to be evented. 2493 */ 2494 2495 var isEvented = function isEvented(object) { 2496 return object instanceof EventTarget || !!object.eventBusEl_ && ['on', 'one', 'off', 'trigger'].every(function (k) { 2497 return typeof object[k] === 'function'; 2498 }); 2499 }; 2500 /** 2501 * Adds a callback to run after the evented mixin applied. 2502 * 2503 * @param {Object} object 2504 * An object to Add 2505 * @param {Function} callback 2506 * The callback to run. 2507 */ 2508 2509 2510 var addEventedCallback = function addEventedCallback(target, callback) { 2511 if (isEvented(target)) { 2512 callback(); 2513 } else { 2514 if (!target.eventedCallbacks) { 2515 target.eventedCallbacks = []; 2516 } 2517 2518 target.eventedCallbacks.push(callback); 2519 } 2520 }; 2521 /** 2522 * Whether a value is a valid event type - non-empty string or array. 2523 * 2524 * @private 2525 * @param {string|Array} type 2526 * The type value to test. 2527 * 2528 * @return {boolean} 2529 * Whether or not the type is a valid event type. 2530 */ 2531 2532 2533 var isValidEventType = function isValidEventType(type) { 2534 return (// The regex here verifies that the `type` contains at least one non- 2535 // whitespace character. 2536 typeof type === 'string' && /\S/.test(type) || Array.isArray(type) && !!type.length 2537 ); 2538 }; 2539 /** 2540 * Validates a value to determine if it is a valid event target. Throws if not. 2541 * 2542 * @private 2543 * @throws {Error} 2544 * If the target does not appear to be a valid event target. 2545 * 2546 * @param {Object} target 2547 * The object to test. 2548 */ 2549 2550
vendor: 6,293 bytes, lines 2551-2744
2551 var validateTarget = function validateTarget(target) { 2552 if (!target.nodeName && !isEvented(target)) { 2553 throw new Error('Invalid target; must be a DOM node or evented object.'); 2554 } 2555 }; 2556 /** 2557 * Validates a value to determine if it is a valid event target. Throws if not. 2558 * 2559 * @private 2560 * @throws {Error} 2561 * If the type does not appear to be a valid event type. 2562 * 2563 * @param {string|Array} type 2564 * The type to test. 2565 */ 2566 2567 2568 var validateEventType = function validateEventType(type) { 2569 if (!isValidEventType(type)) { 2570 throw new Error('Invalid event type; must be a non-empty string or array.'); 2571 } 2572 }; 2573 /** 2574 * Validates a value to determine if it is a valid listener. Throws if not. 2575 * 2576 * @private 2577 * @throws {Error} 2578 * If the listener is not a function. 2579 * 2580 * @param {Function} listener 2581 * The listener to test. 2582 */ 2583 2584 2585 var validateListener = function validateListener(listener) { 2586 if (typeof listener !== 'function') { 2587 throw new Error('Invalid listener; must be a function.'); 2588 } 2589 }; 2590 /** 2591 * Takes an array of arguments given to `on()` or `one()`, validates them, and 2592 * normalizes them into an object. 2593 * 2594 * @private 2595 * @param {Object} self 2596 * The evented object on which `on()` or `one()` was called. This 2597 * object will be bound as the `this` value for the listener. 2598 * 2599 * @param {Array} args 2600 * An array of arguments passed to `on()` or `one()`. 2601 * 2602 * @return {Object} 2603 * An object containing useful values for `on()` or `one()` calls. 2604 */ 2605 2606 2607 var normalizeListenArgs = function normalizeListenArgs(self, args) { 2608 // If the number of arguments is less than 3, the target is always the 2609 // evented object itself. 2610 var isTargetingSelf = args.length < 3 || args[0] === self || args[0] === self.eventBusEl_; 2611 var target; 2612 var type; 2613 var listener; 2614 2615 if (isTargetingSelf) { 2616 target = self.eventBusEl_; // Deal with cases where we got 3 arguments, but we are still listening to 2617 // the evented object itself. 2618 2619 if (args.length >= 3) { 2620 args.shift(); 2621 } 2622 2623 type = args[0]; 2624 listener = args[1]; 2625 } else { 2626 target = args[0]; 2627 type = args[1]; 2628 listener = args[2]; 2629 } 2630 2631 validateTarget(target); 2632 validateEventType(type); 2633 validateListener(listener); 2634 listener = bind(self, listener); 2635 return { 2636 isTargetingSelf: isTargetingSelf, 2637 target: target, 2638 type: type, 2639 listener: listener 2640 }; 2641 }; 2642 /** 2643 * Adds the listener to the event type(s) on the target, normalizing for 2644 * the type of target. 2645 * 2646 * @private 2647 * @param {Element|Object} target 2648 * A DOM node or evented object. 2649 * 2650 * @param {string} method 2651 * The event binding method to use ("on" or "one"). 2652 * 2653 * @param {string|Array} type 2654 * One or more event type(s). 2655 * 2656 * @param {Function} listener 2657 * A listener function. 2658 */ 2659 2660 2661 var listen = function listen(target, method, type, listener) { 2662 validateTarget(target); 2663 2664 if (target.nodeName) { 2665 Events[method](target, type, listener); 2666 } else { 2667 target[method](type, listener); 2668 } 2669 }; 2670 /** 2671 * Contains methods that provide event capabilities to an object which is passed 2672 * to {@link module:evented|evented}. 2673 * 2674 * @mixin EventedMixin 2675 */ 2676 2677 2678 var EventedMixin = { 2679 /** 2680 * Add a listener to an event (or events) on this object or another evented 2681 * object. 2682 * 2683 * @param {string|Array|Element|Object} targetOrType 2684 * If this is a string or array, it represents the event type(s) 2685 * that will trigger the listener. 2686 * 2687 * Another evented object can be passed here instead, which will 2688 * cause the listener to listen for events on _that_ object. 2689 * 2690 * In either case, the listener's `this` value will be bound to 2691 * this object. 2692 * 2693 * @param {string|Array|Function} typeOrListener 2694 * If the first argument was a string or array, this should be the 2695 * listener function. Otherwise, this is a string or array of event 2696 * type(s). 2697 * 2698 * @param {Function} [listener] 2699 * If the first argument was another evented object, this will be 2700 * the listener function. 2701 */ 2702 on: function on$$1() { 2703 var _this = this; 2704 2705 for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { 2706 args[_key] = arguments[_key]; 2707 } 2708 2709 var _normalizeListenArgs = normalizeListenArgs(this, args), 2710 isTargetingSelf = _normalizeListenArgs.isTargetingSelf, 2711 target = _normalizeListenArgs.target, 2712 type = _normalizeListenArgs.type, 2713 listener = _normalizeListenArgs.listener; 2714 2715 listen(target, 'on', type, listener); // If this object is listening to another evented object. 2716 2717 if (!isTargetingSelf) { 2718 // If this object is disposed, remove the listener. 2719 var removeListenerOnDispose = function removeListenerOnDispose() { 2720 return _this.off(target, type, listener); 2721 }; // Use the same function ID as the listener so we can remove it later it 2722 // using the ID of the original listener. 2723 2724 2725 removeListenerOnDispose.guid = listener.guid; // Add a listener to the target's dispose event as well. This ensures 2726 // that if the target is disposed BEFORE this object, we remove the 2727 // removal listener that was just added. Otherwise, we create a memory leak. 2728 2729 var removeRemoverOnTargetDispose = function removeRemoverOnTargetDispose() { 2730 return _this.off('dispose', removeListenerOnDispose); 2731 }; // Use the same function ID as the listener so we can remove it later 2732 // it using the ID of the original listener. 2733 2734 2735 removeRemoverOnTargetDispose.guid = listener.guid; 2736 listen(this, 'on', 'dispose', removeListenerOnDispose); 2737 listen(target, 'on', 'dispose', removeRemoverOnTargetDispose); 2738 } 2739 }, 2740 2741 /** 2742 * Add a listener to an event (or events) on this object or another evented 2743 * object. The listener will only be called once and then removed. 2744 *
2745 * @param {string|Array|Element|Object} targetOrType 2746 * If this is a string or array, it represents the event type(s) 2747 * that will trigger the listener. 2748 * 2749 * Another evented object can be passed here instead, which will 2750 * cause the listener to listen for events on _that_ object. 2751 * 2752 * In either case, the listener's `this` value will be bound to 2753 * this object. 2754 * 2755 * @param {string|Array|Function} typeOrListener 2756 * If the first argument was a string or array, this should be the 2757 * listener function. Otherwise, this is a string or array of event 2758 * type(s). 2759 * 2760 * @param {Function} [listener] 2761 * If the first argument was another evented object, this will be 2762 * the listener function. 2763 */ 2764 one: function one$$1() { 2765 var _this2 = this; 2766 2767 for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { 2768 args[_key2] = arguments[_key2]; 2769 } 2770 2771 var _normalizeListenArgs2 = normalizeListenArgs(this, args), 2772 isTargetingSelf = _normalizeListenArgs2.isTargetingSelf, 2773 target = _normalizeListenArgs2.target, 2774 type = _normalizeListenArgs2.type, 2775 listener = _normalizeListenArgs2.listener; // Targeting this evented object. 2776 2777 2778 if (isTargetingSelf) { 2779 listen(target, 'one', type, listener); // Targeting another evented object. 2780 } else { 2781 var wrapper = function wrapper() { 2782 _this2.off(target, type, wrapper); 2783 2784 for (var _len3 = arguments.length, largs = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) { 2785 largs[_key3] = arguments[_key3]; 2786 } 2787 2788 listener.apply(null, largs); 2789 }; // Use the same function ID as the listener so we can remove it later 2790 // it using the ID of the original listener. 2791 2792 2793 wrapper.guid = listener.guid; 2794 listen(target, 'one', type, wrapper); 2795 } 2796 }, 2797 2798 /** 2799 * Removes listener(s) from event(s) on an evented object. 2800 * 2801 * @param {string|Array|Element|Object} [targetOrType] 2802 * If this is a string or array, it represents the event type(s). 2803 * 2804 * Another evented object can be passed here instead, in which case 2805 * ALL 3 arguments are _required_. 2806 * 2807 * @param {string|Array|Function} [typeOrListener] 2808 * If the first argument was a string or array, this may be the 2809 * listener function. Otherwise, this is a string or array of event 2810 * type(s). 2811 * 2812 * @param {Function} [listener] 2813 * If the first argument was another evented object, this will be 2814 * the listener function; otherwise, _all_ listeners bound to the 2815 * event type(s) will be removed. 2816 */ 2817 off: function off$$1(targetOrType, typeOrListener, listener) { 2818 // Targeting this evented object. 2819 if (!targetOrType || isValidEventType(targetOrType)) { 2820 off(this.eventBusEl_, targetOrType, typeOrListener); // Targeting another evented object. 2821 } else { 2822 var target = targetOrType; 2823 var type = typeOrListener; // Fail fast and in a meaningful way! 2824 2825 validateTarget(target); 2826 validateEventType(type); 2827 validateListener(listener); // Ensure there's at least a guid, even if the function hasn't been used 2828 2829 listener = bind(this, listener); // Remove the dispose listener on this evented object, which was given 2830 // the same guid as the event listener in on(). 2831 2832 this.off('dispose', listener); 2833 2834 if (target.nodeName) { 2835 off(target, type, listener); 2836 off(target, 'dispose', listener); 2837 } else if (isEvented(target)) { 2838 target.off(type, listener); 2839 target.off('dispose', listener); 2840 } 2841 } 2842 }, 2843 2844 /** 2845 * Fire an event on this evented object, causing its listeners to be called. 2846 * 2847 * @param {string|Object} event 2848 * An event type or an object with a type property. 2849 * 2850 * @param {Object} [hash] 2851 * An additional object to pass along to listeners. 2852 * 2853 * @return {boolean} 2854 * Whether or not the default behavior was prevented. 2855 */ 2856 trigger: function trigger$$1(event, hash) { 2857 return trigger(this.eventBusEl_, event, hash); 2858 } 2859 }; 2860 /** 2861 * Applies {@link module:evented~EventedMixin|EventedMixin} to a target object. 2862 * 2863 * @param {Object} target 2864 * The object to which to add event methods. 2865 * 2866 * @param {Object} [options={}] 2867 * Options for customizing the mixin behavior. 2868 * 2869 * @param {string} [options.eventBusKey] 2870 * By default, adds a `eventBusEl_` DOM element to the target object, 2871 * which is used as an event bus. If the target object already has a 2872 * DOM element that should be used, pass its key here. 2873 * 2874 * @return {Object} 2875 * The target object. 2876 */ 2877 2878 function evented(target, options) { 2879 if (options === void 0) { 2880 options = {}; 2881 } 2882 2883 var _options = options, 2884 eventBusKey = _options.eventBusKey; // Set or create the eventBusEl_. 2885
vendor: 5,588 bytes, lines 2886-3080
2886 if (eventBusKey) { 2887 if (!target[eventBusKey].nodeName) { 2888 throw new Error("The eventBusKey \"" + eventBusKey + "\" does not refer to an element."); 2889 } 2890 2891 target.eventBusEl_ = target[eventBusKey]; 2892 } else { 2893 target.eventBusEl_ = createEl('span', { 2894 className: 'vjs-event-bus' 2895 }); 2896 } 2897 2898 assign(target, EventedMixin); 2899 2900 if (target.eventedCallbacks) { 2901 target.eventedCallbacks.forEach(function (callback) { 2902 callback(); 2903 }); 2904 } // When any evented object is disposed, it removes all its listeners. 2905 2906 2907 target.on('dispose', function () { 2908 target.off(); 2909 window$1.setTimeout(function () { 2910 target.eventBusEl_ = null; 2911 }, 0); 2912 }); 2913 return target; 2914 } 2915 2916 /** 2917 * @file mixins/stateful.js 2918 * @module stateful 2919 */ 2920 /** 2921 * Contains methods that provide statefulness to an object which is passed 2922 * to {@link module:stateful}. 2923 * 2924 * @mixin StatefulMixin 2925 */ 2926 2927 var StatefulMixin = { 2928 /** 2929 * A hash containing arbitrary keys and values representing the state of 2930 * the object. 2931 * 2932 * @type {Object} 2933 */ 2934 state: {}, 2935 2936 /** 2937 * Set the state of an object by mutating its 2938 * {@link module:stateful~StatefulMixin.state|state} object in place. 2939 * 2940 * @fires module:stateful~StatefulMixin#statechanged 2941 * @param {Object|Function} stateUpdates 2942 * A new set of properties to shallow-merge into the plugin state. 2943 * Can be a plain object or a function returning a plain object. 2944 * 2945 * @return {Object|undefined} 2946 * An object containing changes that occurred. If no changes 2947 * occurred, returns `undefined`. 2948 */ 2949 setState: function setState(stateUpdates) { 2950 var _this = this; 2951 2952 // Support providing the `stateUpdates` state as a function. 2953 if (typeof stateUpdates === 'function') { 2954 stateUpdates = stateUpdates(); 2955 } 2956 2957 var changes; 2958 each(stateUpdates, function (value, key) { 2959 // Record the change if the value is different from what's in the 2960 // current state. 2961 if (_this.state[key] !== value) { 2962 changes = changes || {}; 2963 changes[key] = { 2964 from: _this.state[key], 2965 to: value 2966 }; 2967 } 2968 2969 _this.state[key] = value; 2970 }); // Only trigger "statechange" if there were changes AND we have a trigger 2971 // function. This allows us to not require that the target object be an 2972 // evented object. 2973 2974 if (changes && isEvented(this)) { 2975 /** 2976 * An event triggered on an object that is both 2977 * {@link module:stateful|stateful} and {@link module:evented|evented} 2978 * indicating that its state has changed. 2979 * 2980 * @event module:stateful~StatefulMixin#statechanged 2981 * @type {Object} 2982 * @property {Object} changes 2983 * A hash containing the properties that were changed and 2984 * the values they were changed `from` and `to`. 2985 */ 2986 this.trigger({ 2987 changes: changes, 2988 type: 'statechanged' 2989 }); 2990 } 2991 2992 return changes; 2993 } 2994 }; 2995 /** 2996 * Applies {@link module:stateful~StatefulMixin|StatefulMixin} to a target 2997 * object. 2998 * 2999 * If the target object is {@link module:evented|evented} and has a 3000 * `handleStateChanged` method, that method will be automatically bound to the 3001 * `statechanged` event on itself. 3002 * 3003 * @param {Object} target 3004 * The object to be made stateful. 3005 * 3006 * @param {Object} [defaultState] 3007 * A default set of properties to populate the newly-stateful object's 3008 * `state` property. 3009 * 3010 * @return {Object} 3011 * Returns the `target`. 3012 */ 3013 3014 function stateful(target, defaultState) { 3015 assign(target, StatefulMixin); // This happens after the mixing-in because we need to replace the `state` 3016 // added in that step. 3017 3018 target.state = assign({}, target.state, defaultState); // Auto-bind the `handleStateChanged` method of the target object if it exists. 3019 3020 if (typeof target.handleStateChanged === 'function' && isEvented(target)) { 3021 target.on('statechanged', target.handleStateChanged); 3022 } 3023 3024 return target; 3025 } 3026 3027 /** 3028 * @file to-title-case.js 3029 * @module to-title-case 3030 */ 3031 3032 /** 3033 * Uppercase the first letter of a string. 3034 * 3035 * @param {string} string 3036 * String to be uppercased 3037 * 3038 * @return {string} 3039 * The string with an uppercased first letter 3040 */ 3041 function toTitleCase(string) { 3042 if (typeof string !== 'string') { 3043 return string; 3044 } 3045 3046 return string.charAt(0).toUpperCase() + string.slice(1); 3047 } 3048 /** 3049 * Compares the TitleCase versions of the two strings for equality. 3050 * 3051 * @param {string} str1 3052 * The first string to compare 3053 * 3054 * @param {string} str2 3055 * The second string to compare 3056 * 3057 * @return {boolean} 3058 * Whether the TitleCase versions of the strings are equal 3059 */ 3060 3061 function titleCaseEquals(str1, str2) { 3062 return toTitleCase(str1) === toTitleCase(str2); 3063 } 3064 3065 /** 3066 * @file merge-options.js 3067 * @module merge-options 3068 */ 3069 /** 3070 * Merge two objects recursively. 3071 * 3072 * Performs a deep merge like 3073 * {@link https://lodash.com/docs/4.17.10#merge|lodash.merge}, but only merges 3074 * plain objects (not arrays, elements, or anything else). 3075 * 3076 * Non-plain object values will be copied directly from the right-most 3077 * argument. 3078 * 3079 * @static 3080 * @param {Object[]} sources
vendor: 10,100 bytes, lines 3081-3406
3081 * One or more objects to merge into a new object. 3082 * 3083 * @return {Object} 3084 * A new object that is the merged result of all sources. 3085 */ 3086 3087 function mergeOptions() { 3088 var result = {}; 3089 3090 for (var _len = arguments.length, sources = new Array(_len), _key = 0; _key < _len; _key++) { 3091 sources[_key] = arguments[_key]; 3092 } 3093 3094 sources.forEach(function (source) { 3095 if (!source) { 3096 return; 3097 } 3098 3099 each(source, function (value, key) { 3100 if (!isPlain(value)) { 3101 result[key] = value; 3102 return; 3103 } 3104 3105 if (!isPlain(result[key])) { 3106 result[key] = {}; 3107 } 3108 3109 result[key] = mergeOptions(result[key], value); 3110 }); 3111 }); 3112 return result; 3113 } 3114 3115 /** 3116 * Player Component - Base class for all UI objects 3117 * 3118 * @file component.js 3119 */ 3120 /** 3121 * Base class for all UI Components. 3122 * Components are UI objects which represent both a javascript object and an element 3123 * in the DOM. They can be children of other components, and can have 3124 * children themselves. 3125 * 3126 * Components can also use methods from {@link EventTarget} 3127 */ 3128 3129 var Component = 3130 /*#__PURE__*/ 3131 function () { 3132 /** 3133 * A callback that is called when a component is ready. Does not have any 3134 * paramters and any callback value will be ignored. 3135 * 3136 * @callback Component~ReadyCallback 3137 * @this Component 3138 */ 3139 3140 /** 3141 * Creates an instance of this class. 3142 * 3143 * @param {Player} player 3144 * The `Player` that this class should be attached to. 3145 * 3146 * @param {Object} [options] 3147 * The key/value store of player options. 3148 * 3149 * @param {Object[]} [options.children] 3150 * An array of children objects to intialize this component with. Children objects have 3151 * a name property that will be used if more than one component of the same type needs to be 3152 * added. 3153 * 3154 * @param {Component~ReadyCallback} [ready] 3155 * Function that gets called when the `Component` is ready. 3156 */ 3157 function Component(player, options, ready) { 3158 // The component might be the player itself and we can't pass `this` to super 3159 if (!player && this.play) { 3160 this.player_ = player = this; // eslint-disable-line 3161 } else { 3162 this.player_ = player; 3163 } // Hold the reference to the parent component via `addChild` method 3164 3165 3166 this.parentComponent_ = null; // Make a copy of prototype.options_ to protect against overriding defaults 3167 3168 this.options_ = mergeOptions({}, this.options_); // Updated options with supplied options 3169 3170 options = this.options_ = mergeOptions(this.options_, options); // Get ID from options or options element if one is supplied 3171 3172 this.id_ = options.id || options.el && options.el.id; // If there was no ID from the options, generate one 3173 3174 if (!this.id_) { 3175 // Don't require the player ID function in the case of mock players 3176 var id = player && player.id && player.id() || 'no_player'; 3177 this.id_ = id + "_component_" + newGUID(); 3178 } 3179 3180 this.name_ = options.name || null; // Create element if one wasn't provided in options 3181 3182 if (options.el) { 3183 this.el_ = options.el; 3184 } else if (options.createEl !== false) { 3185 this.el_ = this.createEl(); 3186 } // if evented is anything except false, we want to mixin in evented 3187 3188 3189 if (options.evented !== false) { 3190 // Make this an evented object and use `el_`, if available, as its event bus 3191 evented(this, { 3192 eventBusKey: this.el_ ? 'el_' : null 3193 }); 3194 } 3195 3196 stateful(this, this.constructor.defaultState); 3197 this.children_ = []; 3198 this.childIndex_ = {}; 3199 this.childNameIndex_ = {}; // 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 removeData(this.el_); 3258 this.el_ = null; 3259 } // remove reference to the player after disposing of the element 3260 3261 3262 this.player_ = null; 3263 } 3264 /** 3265 * Return the {@link Player} that the `Component` has attached to. 3266 * 3267 * @return {Player} 3268 * The player that this `Component` has attached to. 3269 */ 3270 ; 3271 3272 _proto.player = function player() { 3273 return this.player_; 3274 } 3275 /** 3276 * Deep merge of options objects with new options. 3277 * > Note: When both `obj` and `options` contain properties whose values are objects. 3278 * The two properties get merged using {@link module:mergeOptions} 3279 * 3280 * @param {Object} obj 3281 * The object that contains new options. 3282 * 3283 * @return {Object} 3284 * A new object of `this.options_` and `obj` merged together. 3285 * 3286 * @deprecated since version 5 3287 */ 3288 ; 3289 3290 _proto.options = function options(obj) { 3291 log.warn('this.options() has been deprecated and will be moved to the constructor in 6.0'); 3292 3293 if (!obj) { 3294 return this.options_; 3295 } 3296 3297 this.options_ = mergeOptions(this.options_, obj); 3298 return this.options_; 3299 } 3300 /** 3301 * Get the `Component`s DOM element 3302 * 3303 * @return {Element} 3304 * The DOM element for this `Component`. 3305 */ 3306 ; 3307 3308 _proto.el = function el() { 3309 return this.el_; 3310 } 3311 /** 3312 * Create the `Component`s DOM element. 3313 * 3314 * @param {string} [tagName] 3315 * Element's DOM node type. e.g. 'div' 3316 * 3317 * @param {Object} [properties] 3318 * An object of properties that should be set. 3319 * 3320 * @param {Object} [attributes] 3321 * An object of attributes that should be set. 3322 * 3323 * @return {Element} 3324 * The element that gets created. 3325 */ 3326 ; 3327 3328 _proto.createEl = function createEl$$1(tagName, properties, attributes) { 3329 return createEl(tagName, properties, attributes); 3330 } 3331 /** 3332 * Localize a string given the string in english. 3333 * 3334 * If tokens are provided, it'll try and run a simple token replacement on the provided string. 3335 * The tokens it looks for look like `{1}` with the index being 1-indexed into the tokens array. 3336 * 3337 * If a `defaultValue` is provided, it'll use that over `string`, 3338 * if a value isn't found in provided language files. 3339 * This is useful if you want to have a descriptive key for token replacement 3340 * but have a succinct localized string and not require `en.json` to be included. 3341 * 3342 * Currently, it is used for the progress bar timing. 3343 * ```js 3344 * { 3345 * "progress bar timing: currentTime={1} duration={2}": "{1} of {2}" 3346 * } 3347 * ``` 3348 * It is then used like so: 3349 * ```js 3350 * this.localize('progress bar timing: currentTime={1} duration{2}', 3351 * [this.player_.currentTime(), this.player_.duration()], 3352 * '{1} of {2}'); 3353 * ``` 3354 * 3355 * Which outputs something like: `01:23 of 24:56`. 3356 * 3357 * 3358 * @param {string} string 3359 * The string to localize and the key to lookup in the language files. 3360 * @param {string[]} [tokens] 3361 * If the current item has token replacements, provide the tokens here. 3362 * @param {string} [defaultValue] 3363 * Defaults to `string`. Can be a default value to use for token replacement 3364 * if the lookup key is needed to be separate. 3365 * 3366 * @return {string} 3367 * The localized string or if no localization exists the english string. 3368 */ 3369 ; 3370 3371 _proto.localize = function localize(string, tokens, defaultValue) { 3372 if (defaultValue === void 0) { 3373 defaultValue = string; 3374 } 3375 3376 var code = this.player_.language && this.player_.language(); 3377 var languages = this.player_.languages && this.player_.languages(); 3378 var language = languages && languages[code]; 3379 var primaryCode = code && code.split('-')[0]; 3380 var primaryLang = languages && languages[primaryCode]; 3381 var localizedString = defaultValue; 3382 3383 if (language && language[string]) { 3384 localizedString = language[string]; 3385 } else if (primaryLang && primaryLang[string]) { 3386 localizedString = primaryLang[string]; 3387 } 3388 3389 if (tokens) { 3390 localizedString = localizedString.replace(/\{(\d+)\}/g, function (match, index) { 3391 var value = tokens[index - 1]; 3392 var ret = value; 3393 3394 if (typeof value === 'undefined') { 3395 ret = match; 3396 } 3397 3398 return ret; 3399 }); 3400 } 3401 3402 return localizedString; 3403 } 3404 /** 3405 * Return the `Component`s DOM element. This is where children get inserted. 3406 * This will usually be the the same as the element returned in {@link Component#el}.
vendor: 19,707 bytes, lines 3407-4038
3407 * 3408 * @return {Element} 3409 * The content element for this `Component`. 3410 */ 3411 ; 3412 3413 _proto.contentEl = function contentEl() { 3414 return this.contentEl_ || this.el_; 3415 } 3416 /** 3417 * Get this `Component`s ID 3418 * 3419 * @return {string} 3420 * The id of this `Component` 3421 */ 3422 ; 3423 3424 _proto.id = function id() { 3425 return this.id_; 3426 } 3427 /** 3428 * Get the `Component`s name. The name gets used to reference the `Component` 3429 * and is set during registration. 3430 * 3431 * @return {string} 3432 * The name of this `Component`. 3433 */ 3434 ; 3435 3436 _proto.name = function name() { 3437 return this.name_; 3438 } 3439 /** 3440 * Get an array of all child components 3441 * 3442 * @return {Array} 3443 * The children 3444 */ 3445 ; 3446 3447 _proto.children = function children() { 3448 return this.children_; 3449 } 3450 /** 3451 * Returns the child `Component` with the given `id`. 3452 * 3453 * @param {string} id 3454 * The id of the child `Component` to get. 3455 * 3456 * @return {Component|undefined} 3457 * The child `Component` with the given `id` or undefined. 3458 */ 3459 ; 3460 3461 _proto.getChildById = function getChildById(id) { 3462 return this.childIndex_[id]; 3463 } 3464 /** 3465 * Returns the child `Component` with the given `name`. 3466 * 3467 * @param {string} name 3468 * The name of the child `Component` to get. 3469 * 3470 * @return {Component|undefined} 3471 * The child `Component` with the given `name` or undefined. 3472 */ 3473 ; 3474 3475 _proto.getChild = function getChild(name) { 3476 if (!name) { 3477 return; 3478 } 3479 3480 name = toTitleCase(name); 3481 return this.childNameIndex_[name]; 3482 } 3483 /** 3484 * Add a child `Component` inside the current `Component`. 3485 * 3486 * 3487 * @param {string|Component} child 3488 * The name or instance of a child to add. 3489 * 3490 * @param {Object} [options={}] 3491 * The key/value store of options that will get passed to children of 3492 * the child. 3493 * 3494 * @param {number} [index=this.children_.length] 3495 * The index to attempt to add a child into. 3496 * 3497 * @return {Component} 3498 * The `Component` that gets added as a child. When using a string the 3499 * `Component` will get created by this process. 3500 */ 3501 ; 3502 3503 _proto.addChild = function addChild(child, options, index) { 3504 if (options === void 0) { 3505 options = {}; 3506 } 3507 3508 if (index === void 0) { 3509 index = this.children_.length; 3510 } 3511 3512 var component; 3513 var componentName; // If child is a string, create component with options 3514 3515 if (typeof child === 'string') { 3516 componentName = toTitleCase(child); 3517 var componentClassName = options.componentClass || componentName; // Set name through options 3518 3519 options.name = componentName; // Create a new object & element for this controls set 3520 // If there's no .player_, this is a player 3521 3522 var ComponentClass = Component.getComponent(componentClassName); 3523 3524 if (!ComponentClass) { 3525 throw new Error("Component " + componentClassName + " does not exist"); 3526 } // data stored directly on the videojs object may be 3527 // misidentified as a component to retain 3528 // backwards-compatibility with 4.x. check to make sure the 3529 // component class can be instantiated. 3530 3531 3532 if (typeof ComponentClass !== 'function') { 3533 return null; 3534 } 3535 3536 component = new ComponentClass(this.player_ || this, options); // child is a component instance 3537 } else { 3538 component = child; 3539 } 3540 3541 if (component.parentComponent_) { 3542 component.parentComponent_.removeChild(component); 3543 } 3544 3545 this.children_.splice(index, 0, component); 3546 component.parentComponent_ = this; 3547 3548 if (typeof component.id === 'function') { 3549 this.childIndex_[component.id()] = component; 3550 } // If a name wasn't used to create the component, check if we can use the 3551 // name function of the component 3552 3553 3554 componentName = componentName || component.name && toTitleCase(component.name()); 3555 3556 if (componentName) { 3557 this.childNameIndex_[componentName] = component; 3558 } // Add the UI object's element to the container div (box) 3559 // Having an element is not required 3560 3561 3562 if (typeof component.el === 'function' && component.el()) { 3563 var childNodes = this.contentEl().children; 3564 var refNode = childNodes[index] || null; 3565 this.contentEl().insertBefore(component.el(), refNode); 3566 } // Return so it can stored on parent object if desired. 3567 3568 3569 return component; 3570 } 3571 /** 3572 * Remove a child `Component` from this `Component`s list of children. Also removes 3573 * the child `Component`s element from this `Component`s element. 3574 * 3575 * @param {Component} component 3576 * The child `Component` to remove. 3577 */ 3578 ; 3579 3580 _proto.removeChild = function removeChild(component) { 3581 if (typeof component === 'string') { 3582 component = this.getChild(component); 3583 } 3584 3585 if (!component || !this.children_) { 3586 return; 3587 } 3588 3589 var childFound = false; 3590 3591 for (var i = this.children_.length - 1; i >= 0; i--) { 3592 if (this.children_[i] === component) { 3593 childFound = true; 3594 this.children_.splice(i, 1); 3595 break; 3596 } 3597 } 3598 3599 if (!childFound) { 3600 return; 3601 } 3602 3603 component.parentComponent_ = null; 3604 this.childIndex_[component.id()] = null; 3605 this.childNameIndex_[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-u
4038sing-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 if (typeof window$1.getComputedStyle === 'function') { 4131 var computedStyle = window$1.getComputedStyle(this.el_); 4132 computedWidthOrHeight = computedStyle.getPropertyValue(widthOrHeight) || computedStyle[widthOrHeight]; 4133 } // remove 'px' from variable and parse as integer 4134 4135 4136 computedWidthOrHeight = parseFloat(computedWidthOrHeight); // if the computed value is still 0, it's possible that the browser is lying 4137 // and we want to check the offset values. 4138 // This code also runs wherever getComputedStyle doesn't exist. 4139 4140 if (computedWidthOrHeight === 0) { 4141 var rule = "offset" + toTitleCase(widthOrHeight); 4142 computedWidthOrHeight = this.el_[rule]; 4143 } 4144 4145 return computedWidthOrHeight; 4146 } 4147 /** 4148 * An object that contains width and height values of the `Component`s 4149 * computed style. Uses `window.getComputedStyle`. 4150 * 4151 * @typedef {Object} Component~DimensionObject 4152 * 4153 * @property {number} width 4154 * The width of the `Component`s computed style. 4155 * 4156 * @property {number} height 4157 * The height of the `Component`s computed style. 4158 */ 4159 4160 /** 4161 * Get an object that contains computed width and height values of the 4162 * component's element. 4163 * 4164 * Uses `window.getComputedStyle`. 4165 * 4166 * @return {Component~DimensionObject} 4167 * The computed dimensions of the component's element. 4168 */ 4169 ; 4170 4171 _proto.currentDimensions = function currentDimensions() { 4172 return { 4173 width: this.currentDimension('width'), 4174 height: this.currentDimension('height') 4175 }; 4176 } 4177 /** 4178 * Get the computed width of the component's element. 4179 * 4180 * Uses `window.getComputedStyle`. 4181 * 4182 * @return {number} 4183 * The computed width of the component's element. 4184 */ 4185 ; 4186 4187 _proto.currentWidth = function currentWidth() { 4188 return this.currentDimension('width'); 4189 } 4190 /** 4191 * Get the computed height of the component's element. 4192 * 4193 * Uses `window.getComputedStyle`. 4194 * 4195 * @return {number} 4196 * The computed height of the component's element. 4197 */ 4198 ; 4199 4200 _proto.currentHeight = function currentHeight() { 4201 return this.currentDimension('height'); 4202 } 4203 /** 4204 * Set the focus to this component 4205 */ 4206 ; 4207 4208 _proto.focus = function focus() { 4209 this.el_.focus(); 4210 } 4211 /** 4212 * Remove the focus from this component 4213 */ 4214 ; 4215 4216 _proto.blur = function blur() { 4217 this.el_.blur(); 4218 } 4219 /** 4220 * When this Component receives a `keydown` event which it does not process, 4221 * it passes the event to the Player for handling. 4222 * 4223 * @param {EventTarget~Event} event
vendor: 10,582 bytes, lines 4224-4489
4224 * The `keydown` event that caused this function to be called. 4225 */ 4226 ; 4227 4228 _proto.handleKeyDown = function handleKeyDown(event) { 4229 if (this.player_) { 4230 // We only stop propagation here because we want unhandled events to fall 4231 // back to the browser. 4232 event.stopPropagation(); 4233 this.player_.handleKeyDown(event); 4234 } 4235 } 4236 /** 4237 * Many components used to have a `handleKeyPress` method, which was poorly 4238 * named because it listened to a `keydown` event. This method name now 4239 * delegates to `handleKeyDown`. This means anyone calling `handleKeyPress` 4240 * will not see their method calls stop working. 4241 * 4242 * @param {EventTarget~Event} event 4243 * The event that caused this function to be called. 4244 */ 4245 ; 4246 4247 _proto.handleKeyPress = function handleKeyPress(event) { 4248 this.handleKeyDown(event); 4249 } 4250 /** 4251 * Emit a 'tap' events when touch event support gets detected. This gets used to 4252 * support toggling the controls through a tap on the video. They get enabled 4253 * because every sub-component would have extra overhead otherwise. 4254 * 4255 * @private 4256 * @fires Component#tap 4257 * @listens Component#touchstart 4258 * @listens Component#touchmove 4259 * @listens Component#touchleave 4260 * @listens Component#touchcancel 4261 * @listens Component#touchend 4262 */ 4263 ; 4264 4265 _proto.emitTapEvents = function emitTapEvents() { 4266 // Track the start time so we can determine how long the touch lasted 4267 var touchStart = 0; 4268 var firstTouch = null; // Maximum movement allowed during a touch event to still be considered a tap 4269 // Other popular libs use anywhere from 2 (hammer.js) to 15, 4270 // so 10 seems like a nice, round number. 4271 4272 var tapMovementThreshold = 10; // The maximum length a touch can be while still being considered a tap 4273 4274 var touchTimeThreshold = 200; 4275 var couldBeTap; 4276 this.on('touchstart', function (event) { 4277 // If more than one finger, don't consider treating this as a click 4278 if (event.touches.length === 1) { 4279 // Copy pageX/pageY from the object 4280 firstTouch = { 4281 pageX: event.touches[0].pageX, 4282 pageY: event.touches[0].pageY 4283 }; // Record start time so we can detect a tap vs. "touch and hold" 4284 4285 touchStart = new Date().getTime(); // Reset couldBeTap tracking 4286 4287 couldBeTap = true; 4288 } 4289 }); 4290 this.on('touchmove', function (event) { 4291 // If more than one finger, don't consider treating this as a click 4292 if (event.touches.length > 1) { 4293 couldBeTap = false; 4294 } else if (firstTouch) { 4295 // Some devices will throw touchmoves for all but the slightest of taps. 4296 // So, if we moved only a small distance, this could still be a tap 4297 var xdiff = event.touches[0].pageX - firstTouch.pageX; 4298 var ydiff = event.touches[0].pageY - firstTouch.pageY; 4299 var touchDistance = Math.sqrt(xdiff * xdiff + ydiff * ydiff); 4300 4301 if (touchDistance > tapMovementThreshold) { 4302 couldBeTap = false; 4303 } 4304 } 4305 }); 4306 4307 var noTap = function noTap() { 4308 couldBeTap = false; 4309 }; // TODO: Listen to the original target. http://youtu.be/DujfpXOKUp8?t=13m8s 4310 4311 4312 this.on('touchleave', noTap); 4313 this.on('touchcancel', noTap); // When the touch ends, measure how long it took and trigger the appropriate 4314 // event 4315 4316 this.on('touchend', function (event) { 4317 firstTouch = null; // Proceed only if the touchmove/leave/cancel event didn't happen 4318 4319 if (couldBeTap === true) { 4320 // Measure how long the touch lasted 4321 var touchTime = new Date().getTime() - touchStart; // Make sure the touch was less than the threshold to be considered a tap 4322 4323 if (touchTime < touchTimeThreshold) { 4324 // Don't let browser turn this into a click 4325 event.preventDefault(); 4326 /** 4327 * Triggered when a `Component` is tapped. 4328 * 4329 * @event Component#tap 4330 * @type {EventTarget~Event} 4331 */ 4332 4333 this.trigger('tap'); // It may be good to copy the touchend event object and change the 4334 // type to tap, if the other event properties aren't exact after 4335 // Events.fixEvent runs (e.g. event.target) 4336 } 4337 } 4338 }); 4339 } 4340 /** 4341 * This function reports user activity whenever touch events happen. This can get 4342 * turned off by any sub-components that wants touch events to act another way. 4343 * 4344 * Report user touch activity when touch events occur. User activity gets used to 4345 * determine when controls should show/hide. It is simple when it comes to mouse 4346 * events, because any mouse event should show the controls. So we capture mouse 4347 * events that bubble up to the player and report activity when that happens. 4348 * With touch events it isn't as easy as `touchstart` and `touchend` toggle player 4349 * controls. So touch events can't help us at the player level either. 4350 * 4351 * User activity gets checked asynchronously. So what could happen is a tap event 4352 * on the video turns the controls off. Then the `touchend` event bubbles up to 4353 * the player. Which, if it reported user activity, would turn the controls right 4354 * back on. We also don't want to completely block touch events from bubbling up. 4355 * Furthermore a `touchmove` event and anything other than a tap, should not turn 4356 * controls back on. 4357 * 4358 * @listens Component#touchstart 4359 * @listens Component#touchmove 4360 * @listens Component#touchend 4361 * @listens Component#touchcancel 4362 */ 4363 ; 4364 4365 _proto.enableTouchActivity = function enableTouchActivity() { 4366 // Don't continue if the root player doesn't support reporting user activity 4367 if (!this.player() || !this.player().reportUserActivity) { 4368 return; 4369 } // listener for reporting that the user is active 4370 4371 4372 var report = bind(this.player(), this.player().reportUserActivity); 4373 var touchHolding; 4374 this.on('touchstart', function () { 4375 report(); // For as long as the they are touching the device or have their mouse down, 4376 // we consider them active even if they're not moving their finger or mouse. 4377 // So we want to continue to update that they are active 4378 4379 this.clearInterval(touchHolding); // report at the same interval as activityCheck 4380 4381 touchHolding = this.setInterval(report, 250); 4382 }); 4383 4384 var touchEnd = function touchEnd(event) { 4385 report(); // stop the interval that maintains activity if the touch is holding 4386 4387 this.clearInterval(touchHolding); 4388 }; 4389 4390 this.on('touchmove', report); 4391 this.on('touchend', touchEnd); 4392 this.on('touchcancel', touchEnd); 4393 } 4394 /** 4395 * A callback that has no parameters and is bound into `Component`s context. 4396 * 4397 * @callback Component~GenericCallback 4398 * @this Component 4399 */ 4400 4401 /** 4402 * Creates a function that runs after an `x` millisecond timeout. This function is a 4403 * wrapper around `window.setTimeout`. There are a few reasons to use this one 4404 * instead though: 4405 * 1. It gets cleared via {@link Component#clearTimeout} when 4406 * {@link Component#dispose} gets called. 4407 * 2. The function callback will gets turned into a {@link Component~GenericCallback} 4408 * 4409 * > Note: You can't use `window.clearTimeout` on the id returned by this function. This 4410 * will cause its dispose listener not to get cleaned up! Please use 4411 * {@link Component#clearTimeout} or {@link Component#dispose} instead. 4412 * 4413 * @param {Component~GenericCallback} fn 4414 * The function that will be run after `timeout`. 4415 * 4416 * @param {number} timeout 4417 * Timeout in milliseconds to delay before executing the specified function. 4418 * 4419 * @return {number} 4420 * Returns a timeout ID that gets used to identify the timeout. It can also 4421 * get used in {@link Component#clearTimeout} to clear the timeout that 4422 * was set. 4423 * 4424 * @listens Component#dispose 4425 * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setTimeout} 4426 */ 4427 ; 4428 4429 _proto.setTimeout = function setTimeout(fn, timeout) { 4430 var _this2 = this; 4431 4432 // declare as variables so they are properly available in timeout function 4433 // eslint-disable-next-line 4434 var timeoutId, disposeFn; 4435 fn = bind(this, fn); 4436 timeoutId = window$1.setTimeout(function () { 4437 _this2.off('dispose', disposeFn); 4438 4439 fn(); 4440 }, timeout); 4441 4442 disposeFn = function disposeFn() { 4443 return _this2.clearTimeout(timeoutId); 4444 }; 4445 4446 disposeFn.guid = "vjs-timeout-" + timeoutId; 4447 this.on('dispose', disposeFn); 4448 return timeoutId; 4449 } 4450 /** 4451 * Clears a timeout that gets created via `window.setTimeout` or 4452 * {@link Component#setTimeout}. If you set a timeout via {@link Component#setTimeout} 4453 * use this function instead of `window.clearTimout`. If you don't your dispose 4454 * listener will not get cleaned up until {@link Component#dispose}! 4455 * 4456 * @param {number} timeoutId 4457 * The id of the timeout to clear. The return value of 4458 * {@link Component#setTimeout} or `window.setTimeout`. 4459 * 4460 * @return {number} 4461 * Returns the timeout id that was cleared. 4462 * 4463 * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/clearTimeout} 4464 */ 4465 ; 4466 4467 _proto.clearTimeout = function clearTimeout(timeoutId) { 4468 window$1.clearTimeout(timeoutId); 4469 4470 var disposeFn = function disposeFn() {}; 4471 4472 disposeFn.guid = "vjs-timeout-" + timeoutId; 4473 this.off('dispose', disposeFn); 4474 return timeoutId; 4475 } 4476 /** 4477 * Creates a function that gets run every `x` milliseconds. This function is a wrapper 4478 * around `window.setInterval`. There are a few reasons to use this one instead though. 4479 * 1. It gets cleared via {@link Component#clearInterval} when 4480 * {@link Component#dispose} gets called. 4481 * 2. The function callback will be a {@link Component~GenericCallback} 4482 * 4483 * @param {Component~GenericCallback} fn 4484 * The function to run every `x` seconds. 4485 * 4486 * @param {number} interval 4487 * Execute the specified function every `x` milliseconds. 4488 * 4489 * @return {number}
4490 * Returns an id that can be used to identify the interval. It can also be be used in 4491 * {@link Component#clearInterval} to clear the interval. 4492 * 4493 * @listens Component#dispose 4494 * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setInterval} 4495 */ 4496 ; 4497 4498 _proto.setInterval = function setInterval(fn, interval) { 4499 var _this3 = this; 4500 4501 fn = bind(this, fn); 4502 var intervalId = window$1.setInterval(fn, interval); 4503 4504 var disposeFn = function disposeFn() { 4505 return _this3.clearInterval(intervalId); 4506 }; 4507 4508 disposeFn.guid = "vjs-interval-" + intervalId; 4509 this.on('dispose', disposeFn); 4510 return intervalId; 4511 } 4512 /** 4513 * Clears an interval that gets created via `window.setInterval` or 4514 * {@link Component#setInterval}. If you set an inteval via {@link Component#setInterval} 4515 * use this function instead of `window.clearInterval`. If you don't your dispose 4516 * listener will not get cleaned up until {@link Component#dispose}! 4517 * 4518 * @param {number} intervalId 4519 * The id of the interval to clear. The return value of 4520 * {@link Component#setInterval} or `window.setInterval`. 4521 * 4522 * @return {number} 4523 * Returns the interval id that was cleared. 4524 * 4525 * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/clearInterval} 4526 */ 4527 ; 4528 4529 _proto.clearInterval = function clearInterval(intervalId) { 4530 window$1.clearInterval(intervalId); 4531 4532 var disposeFn = function disposeFn() {}; 4533 4534 disposeFn.guid = "vjs-interval-" + intervalId; 4535 this.off('dispose', disposeFn); 4536 return intervalId; 4537 } 4538 /** 4539 * Queues up a callback to be passed to requestAnimationFrame (rAF), but 4540 * with a few extra bonuses: 4541 * 4542 * - Supports browsers that do not support rAF by falling back to 4543 * {@link Component#setTimeout}. 4544 * 4545 * - The callback is turned into a {@link Component~GenericCallback} (i.e. 4546 * bound to the component). 4547 * 4548 * - Automatic cancellation of the rAF callback is handled if the component 4549 * is disposed before it is called. 4550 * 4551 * @param {Component~GenericCallback} fn 4552 * A function that will be bound to this component and executed just 4553 * before the browser's next repaint. 4554 * 4555 * @return {number} 4556 * Returns an rAF ID that gets used to identify the timeout. It can 4557 * also be used in {@link Component#cancelAnimationFrame} to cancel 4558 * the animation frame callback. 4559 * 4560 * @listens Component#dispose 4561 * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame} 4562 */ 4563 ; 4564 4565 _proto.requestAnimationFrame = function requestAnimationFrame(fn) { 4566 var _this4 = this; 4567 4568 // declare as variables so they are properly available in rAF function 4569 // eslint-disable-next-line 4570 var id, disposeFn; 4571 4572 if (this.supportsRaf_) { 4573 fn = bind(this, fn); 4574 id = window$1.requestAnimationFrame(function () { 4575 _this4.off('dispose', disposeFn); 4576 4577 fn(); 4578 }); 4579 4580 disposeFn = function disposeFn() { 4581 return _this4.cancelAnimationFrame(id); 4582 }; 4583 4584 disposeFn.guid = "vjs-raf-" + id; 4585 this.on('dispose', disposeFn); 4586 return id; 4587 } // Fall back to using a timer. 4588 4589 4590 return this.setTimeout(fn, 1000 / 60); 4591 } 4592 /** 4593 * Cancels a queued callback passed to {@link Component#requestAnimationFrame} 4594 * (rAF). 4595 * 4596 * If you queue an rAF callback via {@link Component#requestAnimationFrame}, 4597 * use this function instead of `window.cancelAnimationFrame`. If you don't, 4598 * your dispose listener will not get cleaned up until {@link Component#dispose}! 4599 * 4600 * @param {number} id 4601 * The rAF ID to clear. The return value of {@link Component#requestAnimationFrame}. 4602 * 4603 * @return {number} 4604 * Returns the rAF ID that was cleared. 4605 * 4606 * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/window/cancelAnimationFrame} 4607 */ 4608 ; 4609 4610 _proto.cancelAnimationFrame = function cancelAnimationFrame(id) { 4611 if (this.supportsRaf_) { 4612 window$1.cancelAnimationFrame(id); 4613 4614 var disposeFn = function disposeFn() {}; 4615 4616 disposeFn.guid = "vjs-raf-" + id; 4617 this.off('dispose', disposeFn); 4618 return id; 4619 } // Fall back to using a timer. 4620 4621 4622 return this.clearTimeout(id); 4623 } 4624 /** 4625 * Register a `Component` with `videojs` given the name and the component. 4626 * 4627 * > NOTE: {@link Tech}s should not be registered as a `Component`. {@link Tech}s 4628 * should be registered using {@link Tech.registerTech} or
4629 * {@link videojs:videojs.registerTech}. 4630 * 4631 * > NOTE: This function can also be seen on videojs as 4632 * {@link videojs:videojs.registerComponent}. 4633 * 4634 * @param {string} name 4635 * The name of the `Component` to register. 4636 * 4637 * @param {Component} ComponentToRegister 4638 * The `Component` class to register. 4639 * 4640 * @return {Component} 4641 * The `Component` that was registered. 4642 */ 4643 ; 4644 4645 Component.registerComponent = function registerComponent(name, ComponentToRegister) { 4646 if (typeof name !== 'string' || !name) { 4647 throw new Error("Illegal component name, \"" + name + "\"; must be a non-empty string."); 4648 } 4649 4650 var Tech = Component.getComponent('Tech'); // We need to make sure this check is only done if Tech has been registered. 4651 4652 var isTech = Tech && Tech.isTech(ComponentToRegister); 4653 var isComp = Component === ComponentToRegister || Component.prototype.isPrototypeOf(ComponentToRegister.prototype); 4654 4655 if (isTech || !isComp) { 4656 var reason; 4657 4658 if (isTech) { 4659 reason = 'techs must be registered using Tech.registerTech()'; 4660 } else { 4661 reason = 'must be a Component subclass'; 4662 } 4663 4664 throw new Error("Illegal component, \"" + name + "\"; " + reason + "."); 4665 } 4666 4667 name = toTitleCase(name); 4668 4669 if (!Component.components_) { 4670 Component.components_ = {}; 4671 } 4672 4673 var Player = Component.getComponent('Player'); 4674 4675 if (name === 'Player' && Player && Player.players) { 4676 var players = Player.players; 4677 var playerNames = Object.keys(players); // If we have players that were disposed, then their name will still be 4678 // in Players.players. So, we must loop through and verify that the value 4679 // for each item is not null. This allows registration of the Player component 4680 // after all players have been disposed or before any were created. 4681 4682 if (players && playerNames.length > 0 && playerNames.map(function (pname) { 4683 return players[pname]; 4684 }).every(Boolean)) { 4685 throw new Error('Can not register Player component after player has been created.'); 4686 } 4687 } 4688 4689 Component.components_[name] = ComponentToRegister; 4690 return ComponentToRegister; 4691 } 4692 /** 4693 * Get a `Component` based on the name it was registered with. 4694 * 4695 * @param {string} name 4696 * The Name of the component to get. 4697 * 4698 * @return {Component} 4699 * The `Component` that got registered under the given name. 4700 * 4701 * @deprecated In `videojs` 6 this will not return `Component`s that were not 4702 * registered using {@link Component.registerComponent}. Currently we 4703 * check the global `videojs` object for a `Component` name and 4704 * return that if it exists. 4705 */ 4706 ; 4707 4708 Component.getComponent = function getComponent(name) { 4709 if (!name) { 4710 return; 4711 } 4712 4713 name = toTitleCase(name); 4714 4715 if (Component.components_ && Component.components_[name]) { 4716 return Component.components_[name]; 4717 } 4718 }; 4719 4720 return Component; 4721 }(); 4722 /** 4723 * Whether or not this component supports `requestAnimationFrame`. 4724 * 4725 * This is exposed primarily for testing purposes. 4726 * 4727 * @private 4728 * @type {Boolean} 4729 */ 4730 4731 4732 Component.prototype.supportsRaf_ = typeof window$1.requestAnimationFrame === 'function' && typeof window$1.cancelAnimationFrame === 'function'; 4733 Component.registerComponent('Component', Component); 4734 4735 /** 4736 * @file browser.js 4737 * @module browser 4738 */ 4739 var USER_AGENT = window$1.navigator && window$1.navigator.userAgent || ''; 4740 var webkitVersionMap = /AppleWebKit\/([\d.]+)/i.exec(USER_AGENT); 4741 var appleWebkitVersion = webkitVersionMap ? parseFloat(webkitVersionMap.pop()) : null; 4742 /** 4743 * Whether or not this device is an iPad. 4744 * 4745 * @static 4746 * @const 4747 * @type {Boolean} 4748 */ 4749 4750 var IS_IPAD = /iPad/i.test(USER_AGENT); 4751 /** 4752 * Whether or not this device is an iPhone. 4753 * 4754 * @static 4755 * @const 4756 * @type {Boolean} 4757 */ 4758 // The Facebook app's UIWebView identifies as both an iPhone and iPad, so 4759 // to identify iPhones, we need to exclude iPads. 4760 // http://artsy.github.io/blog/2012/10/18/the-perils-of-ios-user-agent-sniffing/ 4761 4762 var IS_IPHONE = /iPhone/i.test(USER_AGENT) && !IS_IPAD; 4763 /** 4764 * Whether or not this device is an iPod. 4765 * 4766 * @static 4767 * @const 4768 * @type {Boolean} 4769 */ 4770 4771 var IS_IPOD = /iPod/i.test(USER_AGENT); 4772 /** 4773 * Whether or not this is an iOS device. 4774 * 4775 * @static 4776 * @const 4777 * @type {Boolean} 4778 */ 4779 4780 var IS_IOS = IS_IPHONE || IS_IPAD || IS_IPOD; 4781 /** 4782 * The detected iOS version - or `null`. 4783 * 4784 * @static 4785 * @const 4786 * @type {string|null} 4787 */ 4788 4789 var IOS_VERSION = function () { 4790 var match = USER_AGENT.match(/OS (\d+)_/i); 4791 4792 if (match && match[1]) { 4793 return match[1]; 4794 } 4795 4796 return null; 4797 }(); 4798 /** 4799 * Whether or not this is an Android device. 4800 * 4801 * @static 4802 * @const 4803 * @type {Boolean} 4804 */ 4805 4806 var IS_ANDROID = /Android/i.test(USER_AGENT); 4807 /** 4808 * The detected Android version - or `null`. 4809 * 4810 * @static 4811 * @const 4812 * @type {number|string|null} 4813 */ 4814 4815 var ANDROID_VERSION = function () { 4816 // This matches Android Major.Minor.Patch versions
vendor: 10,112 bytes, lines 4817-5184
4817 // ANDROID_VERSION is Major.Minor as a Number, if Minor isn't available, then only Major is returned 4818 var match = USER_AGENT.match(/Android (\d+)(?:\.(\d+))?(?:\.(\d+))*/i); 4819 4820 if (!match) { 4821 return null; 4822 } 4823 4824 var major = match[1] && parseFloat(match[1]); 4825 var minor = match[2] && parseFloat(match[2]); 4826 4827 if (major && minor) { 4828 return parseFloat(match[1] + '.' + match[2]); 4829 } else if (major) { 4830 return major; 4831 } 4832 4833 return null; 4834 }(); 4835 /** 4836 * Whether or not this is a native Android browser. 4837 * 4838 * @static 4839 * @const 4840 * @type {Boolean} 4841 */ 4842 4843 var IS_NATIVE_ANDROID = IS_ANDROID && ANDROID_VERSION < 5 && appleWebkitVersion < 537; 4844 /** 4845 * Whether or not this is Mozilla Firefox. 4846 * 4847 * @static 4848 * @const 4849 * @type {Boolean} 4850 */ 4851 4852 var IS_FIREFOX = /Firefox/i.test(USER_AGENT); 4853 /** 4854 * Whether or not this is Microsoft Edge. 4855 * 4856 * @static 4857 * @const 4858 * @type {Boolean} 4859 */ 4860 4861 var IS_EDGE = /Edge/i.test(USER_AGENT); 4862 /** 4863 * Whether or not this is Google Chrome. 4864 * 4865 * This will also be `true` for Chrome on iOS, which will have different support 4866 * as it is actually Safari under the hood. 4867 * 4868 * @static 4869 * @const 4870 * @type {Boolean} 4871 */ 4872 4873 var IS_CHROME = !IS_EDGE && (/Chrome/i.test(USER_AGENT) || /CriOS/i.test(USER_AGENT)); 4874 /** 4875 * The detected Google Chrome version - or `null`. 4876 * 4877 * @static 4878 * @const 4879 * @type {number|null} 4880 */ 4881 4882 var CHROME_VERSION = function () { 4883 var match = USER_AGENT.match(/(Chrome|CriOS)\/(\d+)/); 4884 4885 if (match && match[2]) { 4886 return parseFloat(match[2]); 4887 } 4888 4889 return null; 4890 }(); 4891 /** 4892 * The detected Internet Explorer version - or `null`. 4893 * 4894 * @static 4895 * @const 4896 * @type {number|null} 4897 */ 4898 4899 var IE_VERSION = function () { 4900 var result = /MSIE\s(\d+)\.\d/.exec(USER_AGENT); 4901 var version = result && parseFloat(result[1]); 4902 4903 if (!version && /Trident\/7.0/i.test(USER_AGENT) && /rv:11.0/.test(USER_AGENT)) { 4904 // IE 11 has a different user agent string than other IE versions 4905 version = 11.0; 4906 } 4907 4908 return version; 4909 }(); 4910 /** 4911 * Whether or not this is desktop Safari. 4912 * 4913 * @static 4914 * @const 4915 * @type {Boolean} 4916 */ 4917 4918 var IS_SAFARI = /Safari/i.test(USER_AGENT) && !IS_CHROME && !IS_ANDROID && !IS_EDGE; 4919 /** 4920 * Whether or not this is any flavor of Safari - including iOS. 4921 * 4922 * @static 4923 * @const 4924 * @type {Boolean} 4925 */ 4926 4927 var IS_ANY_SAFARI = (IS_SAFARI || IS_IOS) && !IS_CHROME; 4928 /** 4929 * Whether or not this device is touch-enabled. 4930 * 4931 * @static 4932 * @const 4933 * @type {Boolean} 4934 */ 4935 4936 var TOUCH_ENABLED = isReal() && ('ontouchstart' in window$1 || window$1.navigator.maxTouchPoints || window$1.DocumentTouch && window$1.document instanceof window$1.DocumentTouch); 4937 4938 var browser = /*#__PURE__*/Object.freeze({ 4939 IS_IPAD: IS_IPAD, 4940 IS_IPHONE: IS_IPHONE, 4941 IS_IPOD: IS_IPOD, 4942 IS_IOS: IS_IOS, 4943 IOS_VERSION: IOS_VERSION, 4944 IS_ANDROID: IS_ANDROID, 4945 ANDROID_VERSION: ANDROID_VERSION, 4946 IS_NATIVE_ANDROID: IS_NATIVE_ANDROID, 4947 IS_FIREFOX: IS_FIREFOX, 4948 IS_EDGE: IS_EDGE, 4949 IS_CHROME: IS_CHROME, 4950 CHROME_VERSION: CHROME_VERSION, 4951 IE_VERSION: IE_VERSION, 4952 IS_SAFARI: IS_SAFARI, 4953 IS_ANY_SAFARI: IS_ANY_SAFARI, 4954 TOUCH_ENABLED: TOUCH_ENABLED 4955 }); 4956 4957 /** 4958 * @file time-ranges.js 4959 * @module time-ranges 4960 */ 4961 4962 /** 4963 * Returns the time for the specified index at the start or end 4964 * of a TimeRange object. 4965 * 4966 * @typedef {Function} TimeRangeIndex 4967 * 4968 * @param {number} [index=0] 4969 * The range number to return the time for. 4970 * 4971 * @return {number} 4972 * The time offset at the specified index. 4973 * 4974 * @deprecated The index argument must be provided. 4975 * In the future, leaving it out will throw an error. 4976 */ 4977 4978 /** 4979 * An object that contains ranges of time. 4980 * 4981 * @typedef {Object} TimeRange 4982 * 4983 * @property {number} length 4984 * The number of time ranges represented by this object. 4985 * 4986 * @property {module:time-ranges~TimeRangeIndex} start 4987 * Returns the time offset at which a specified time range begins. 4988 * 4989 * @property {module:time-ranges~TimeRangeIndex} end 4990 * Returns the time offset at which a specified time range ends. 4991 * 4992 * @see https://developer.mozilla.org/en-US/docs/Web/API/TimeRanges 4993 */ 4994 4995 /** 4996 * Check if any of the time ranges are over the maximum index. 4997 * 4998 * @private 4999 * @param {string} fnName 5000 * The function name to use for logging 5001 * 5002 * @param {number} index 5003 * The index to check 5004 * 5005 * @param {number} maxIndex 5006 * The maximum possible index 5007 * 5008 * @throws {Error} if the timeRanges provided are over the maxIndex 5009 */ 5010 function rangeCheck(fnName, index, maxIndex) { 5011 if (typeof index !== 'number' || index < 0 || index > maxIndex) { 5012 throw new Error("Failed to execute '" + fnName + "' on 'TimeRanges': The index provided (" + index + ") is non-numeric or out of bounds (0-" + maxIndex + ")."); 5013 } 5014 } 5015 /** 5016 * Get the time for the specified index at the start or end 5017 * of a TimeRange object. 5018 * 5019 * @private 5020 * @param {string} fnName 5021 * The function name to use for logging 5022 * 5023 * @param {string} valueIndex 5024 * The property that should be used to get the time. should be 5025 * 'start' or 'end' 5026 * 5027 * @param {Array} ranges 5028 * An array of time ranges 5029 * 5030 * @param {Array} [rangeIndex=0] 5031 * The index to start the search at 5032 * 5033 * @return {number} 5034 * The time that offset at the specified index. 5035 * 5036 * @deprecated rangeIndex must be set to a value, in the future this will throw an error. 5037 * @throws {Error} if rangeIndex is more than the length of ranges 5038 */ 5039 5040 5041 function getRange(fnName, valueIndex, ranges, rangeIndex) { 5042 rangeCheck(fnName, rangeIndex, ranges.length - 1); 5043 return ranges[rangeIndex][valueIndex]; 5044 } 5045 /** 5046 * Create a time range object given ranges of time. 5047 * 5048 * @private 5049 * @param {Array} [ranges] 5050 * An array of time ranges. 5051 */ 5052 5053 5054 function createTimeRangesObj(ranges) { 5055 if (ranges === undefined || ranges.length === 0) { 5056 return { 5057 length: 0, 5058 start: function start() { 5059 throw new Error('This TimeRanges object is empty'); 5060 }, 5061 end: function end() { 5062 throw new Error('This TimeRanges object is empty'); 5063 } 5064 }; 5065 } 5066 5067 return { 5068 length: ranges.length, 5069 start: getRange.bind(null, 'start', 0, ranges), 5070 end: getRange.bind(null, 'end', 1, ranges) 5071 }; 5072 } 5073 /** 5074 * Create a `TimeRange` object which mimics an 5075 * {@link https://developer.mozilla.org/en-US/docs/Web/API/TimeRanges|HTML5 TimeRanges instance}. 5076 * 5077 * @param {number|Array[]} start 5078 * The start of a single range (a number) or an array of ranges (an 5079 * array of arrays of two numbers each). 5080 * 5081 * @param {number} end 5082 * The end of a single range. Cannot be used with the array form of 5083 * the `start` argument. 5084 */ 5085 5086 5087 function createTimeRanges(start, end) { 5088 if (Array.isArray(start)) { 5089 return createTimeRangesObj(start); 5090 } else if (start === undefined || end === undefined) { 5091 return createTimeRangesObj(); 5092 } 5093 5094 return createTimeRangesObj([[start, end]]); 5095 } 5096 5097 /** 5098 * @file buffer.js 5099 * @module buffer 5100 */ 5101 /** 5102 * Compute the percentage of the media that has been buffered. 5103 * 5104 * @param {TimeRange} buffered 5105 * The current `TimeRange` object representing buffered time ranges 5106 * 5107 * @param {number} duration 5108 * Total duration of the media 5109 * 5110 * @return {number} 5111 * Percent buffered of the total duration in decimal form. 5112 */ 5113 5114 function bufferedPercent(buffered, duration) { 5115 var bufferedDuration = 0; 5116 var start; 5117 var end; 5118 5119 if (!duration) { 5120 return 0; 5121 } 5122 5123 if (!buffered || !buffered.length) { 5124 buffered = createTimeRanges(0, 0); 5125 } 5126 5127 for (var i = 0; i < buffered.length; i++) { 5128 start = buffered.start(i); 5129 end = buffered.end(i); // buffered end can be bigger than duration by a very small fraction 5130 5131 if (end > duration) { 5132 end = duration; 5133 } 5134 5135 bufferedDuration += end - start; 5136 } 5137 5138 return bufferedDuration / duration; 5139 } 5140 5141 /** 5142 * @file fullscreen-api.js 5143 * @module fullscreen-api 5144 * @private 5145 */ 5146 /** 5147 * Store the browser-specific methods for the fullscreen API. 5148 * 5149 * @type {Object} 5150 * @see [Specification]{@link https://fullscreen.spec.whatwg.org} 5151 * @see [Map Approach From Screenfull.js]{@link https://github.com/sindresorhus/screenfull.js} 5152 */ 5153 5154 var FullscreenApi = {}; // browser API methods 5155 5156 var apiMap = [['requestFullscreen', 'exitFullscreen', 'fullscreenElement', 'fullscreenEnabled', 'fullscreenchange', 'fullscreenerror', 'fullscreen'], // WebKit 5157 ['webkitRequestFullscreen', 'webkitExitFullscreen', 'webkitFullscreenElement', 'webkitFullscreenEnabled', 'webkitfullscreenchange', 'webkitfullscreenerror', '-webkit-full-screen'], // Mozilla 5158 ['mozRequestFullScreen', 'mozCancelFullScreen', 'mozFullScreenElement', 'mozFullScreenEnabled', 'mozfullscreenchange', 'mozfullscreenerror', '-moz-full-screen'], // Microsoft 5159 ['msRequestFullscreen', 'msExitFullscreen', 'msFullscreenElement', 'msFullscreenEnabled', 'MSFullscreenChange', 'MSFullscreenError', '-ms-fullscreen']]; 5160 var specApi = apiMap[0]; 5161 var browserApi; 5162 var prefixedAPI = false; // determine the supported set of functions 5163 5164 for (var i = 0; i < apiMap.length; i++) { 5165 // check for exitFullscreen function 5166 if (apiMap[i][1] in document) { 5167 browserApi = apiMap[i]; 5168 break; 5169 } 5170 } // map the browser API names to the spec API names 5171 5172 5173 if (browserApi) { 5174 for (var _i = 0; _i < browserApi.length; _i++) { 5175 FullscreenApi[specApi[_i]] = browserApi[_i]; 5176 } 5177 5178 prefixedAPI = browserApi[0] === specApi[0]; 5179 } 5180 5181 /** 5182 * @file media-error.js 5183 */ 5184 /**
5185 * A Custom `MediaError` class which mimics the standard HTML5 `MediaError` class. 5186 * 5187 * @param {number|string|Object|MediaError} value 5188 * This can be of multiple types: 5189 * - number: should be a standard error code 5190 * - string: an error message (the code will be 0) 5191 * - Object: arbitrary properties 5192 * - `MediaError` (native): used to populate a video.js `MediaError` object 5193 * - `MediaError` (video.js): will return itself if it's already a 5194 * video.js `MediaError` object. 5195 * 5196 * @see [MediaError Spec]{@link https://dev.w3.org/html5/spec-author-view/video.html#mediaerror} 5197 * @see [Encrypted MediaError Spec]{@link https://www.w3.org/TR/2013/WD-encrypted-media-20130510/#error-codes} 5198 * 5199 * @class MediaError 5200 */ 5201 5202 function MediaError(value) { 5203 // Allow redundant calls to this constructor to avoid having `instanceof` 5204 // checks peppered around the code. 5205 if (value instanceof MediaError) { 5206 return value; 5207 } 5208 5209 if (typeof value === 'number') { 5210 this.code = value; 5211 } else if (typeof value === 'string') {
vendor: 4,827 bytes, lines 5212-5360
5212 // default code is zero, so this is a custom error 5213 this.message = value; 5214 } else if (isObject(value)) { 5215 // We assign the `code` property manually because native `MediaError` objects 5216 // do not expose it as an own/enumerable property of the object. 5217 if (typeof value.code === 'number') { 5218 this.code = value.code; 5219 } 5220 5221 assign(this, value); 5222 } 5223 5224 if (!this.message) { 5225 this.message = MediaError.defaultMessages[this.code] || ''; 5226 } 5227 } 5228 /** 5229 * The error code that refers two one of the defined `MediaError` types 5230 * 5231 * @type {Number} 5232 */ 5233 5234 5235 MediaError.prototype.code = 0; 5236 /** 5237 * An optional message that to show with the error. Message is not part of the HTML5 5238 * video spec but allows for more informative custom errors. 5239 * 5240 * @type {String} 5241 */ 5242 5243 MediaError.prototype.message = ''; 5244 /** 5245 * An optional status code that can be set by plugins to allow even more detail about 5246 * the error. For example a plugin might provide a specific HTTP status code and an 5247 * error message for that code. Then when the plugin gets that error this class will 5248 * know how to display an error message for it. This allows a custom message to show 5249 * up on the `Player` error overlay. 5250 * 5251 * @type {Array} 5252 */ 5253 5254 MediaError.prototype.status = null; 5255 /** 5256 * Errors indexed by the W3C standard. The order **CANNOT CHANGE**! See the 5257 * specification listed under {@link MediaError} for more information. 5258 * 5259 * @enum {array} 5260 * @readonly 5261 * @property {string} 0 - MEDIA_ERR_CUSTOM 5262 * @property {string} 1 - MEDIA_ERR_ABORTED 5263 * @property {string} 2 - MEDIA_ERR_NETWORK 5264 * @property {string} 3 - MEDIA_ERR_DECODE 5265 * @property {string} 4 - MEDIA_ERR_SRC_NOT_SUPPORTED 5266 * @property {string} 5 - MEDIA_ERR_ENCRYPTED 5267 */ 5268 5269 MediaError.errorTypes = ['MEDIA_ERR_CUSTOM', 'MEDIA_ERR_ABORTED', 'MEDIA_ERR_NETWORK', 'MEDIA_ERR_DECODE', 'MEDIA_ERR_SRC_NOT_SUPPORTED', 'MEDIA_ERR_ENCRYPTED']; 5270 /** 5271 * The default `MediaError` messages based on the {@link MediaError.errorTypes}. 5272 * 5273 * @type {Array} 5274 * @constant 5275 */ 5276 5277 MediaError.defaultMessages = { 5278 1: 'You aborted the media playback', 5279 2: 'A network error caused the media download to fail part-way.', 5280 3: 'The media playback was aborted due to a corruption problem or because the media used features your browser did not support.', 5281 4: 'The media could not be loaded, either because the server or network failed or because the format is not supported.', 5282 5: 'The media is encrypted and we do not have the keys to decrypt it.' 5283 }; // Add types as properties on MediaError 5284 // e.g. MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED = 4; 5285 5286 for (var errNum = 0; errNum < MediaError.errorTypes.length; errNum++) { 5287 MediaError[MediaError.errorTypes[errNum]] = errNum; // values should be accessible on both the class and instance 5288 5289 MediaError.prototype[MediaError.errorTypes[errNum]] = errNum; 5290 } // jsdocs for instance/static members added above 5291 5292 var tuple = SafeParseTuple; 5293 5294 function SafeParseTuple(obj, reviver) { 5295 var json; 5296 var error = null; 5297 5298 try { 5299 json = JSON.parse(obj, reviver); 5300 } catch (err) { 5301 error = err; 5302 } 5303 5304 return [error, json]; 5305 } 5306 5307 /** 5308 * Returns whether an object is `Promise`-like (i.e. has a `then` method). 5309 * 5310 * @param {Object} value 5311 * An object that may or may not be `Promise`-like. 5312 * 5313 * @return {boolean} 5314 * Whether or not the object is `Promise`-like. 5315 */ 5316 function isPromise(value) { 5317 return value !== undefined && value !== null && typeof value.then === 'function'; 5318 } 5319 /** 5320 * Silence a Promise-like object. 5321 * 5322 * This is useful for avoiding non-harmful, but potentially confusing "uncaught 5323 * play promise" rejection error messages. 5324 * 5325 * @param {Object} value 5326 * An object that may or may not be `Promise`-like. 5327 */ 5328 5329 function silencePromise(value) { 5330 if (isPromise(value)) { 5331 value.then(null, function (e) {}); 5332 } 5333 } 5334 5335 /** 5336 * @file text-track-list-converter.js Utilities for capturing text track state and 5337 * re-creating tracks based on a capture. 5338 * 5339 * @module text-track-list-converter 5340 */ 5341 5342 /** 5343 * Examine a single {@link TextTrack} and return a JSON-compatible javascript object that 5344 * represents the {@link TextTrack}'s state. 5345 * 5346 * @param {TextTrack} track 5347 * The text track to query. 5348 * 5349 * @return {Object} 5350 * A serializable javascript representation of the TextTrack. 5351 * @private 5352 */ 5353 var trackToJson_ = function trackToJson_(track) { 5354 var ret = ['kind', 'label', 'language', 'id', 'inBandMetadataTrackDispatchType', 'mode', 'src'].reduce(function (acc, prop, i) { 5355 if (track[prop]) { 5356 acc[prop] = track[prop]; 5357 } 5358 5359 return acc; 5360 }, {
5361 cues: track.cues && Array.prototype.map.call(track.cues, function (cue) { 5362 return { 5363 startTime: cue.startTime, 5364 endTime: cue.endTime, 5365 text: cue.text, 5366 id: cue.id 5367 }; 5368 }) 5369 }); 5370 return ret; 5371 }; 5372 /** 5373 * Examine a {@link Tech} and return a JSON-compatible javascript array that represents the 5374 * state of all {@link TextTrack}s currently configured. The return array is compatible with 5375 * {@link text-track-list-converter:jsonToTextTracks}. 5376 * 5377 * @param {Tech} tech 5378 * The tech object to query 5379 * 5380 * @return {Array} 5381 * A serializable javascript representation of the {@link Tech}s 5382 * {@link TextTrackList}. 5383 */ 5384 5385 5386 var textTracksToJson = function textTracksToJson(tech) { 5387 var trackEls = tech.$$('track'); 5388 var trackObjs = Array.prototype.map.call(trackEls, function (t) { 5389 return t.track; 5390 }); 5391 var tracks = Array.prototype.map.call(trackEls, function (trackEl) { 5392 var json = trackToJson_(trackEl.track); 5393 5394 if (trackEl.src) { 5395 json.src = trackEl.src; 5396 } 5397 5398 return json; 5399 }); 5400 return tracks.concat(Array.prototype.filter.call(tech.textTracks(), function (track) { 5401 return trackObjs.indexOf(track) === -1; 5402 }).map(trackToJson_)); 5403 }; 5404 /** 5405 * Create a set of remote {@link TextTrack}s on a {@link Tech} based on an array of javascript 5406 * object {@link TextTrack} representations. 5407 * 5408 * @param {Array} json 5409 * An array of `TextTrack` representation objects, like those that would be 5410 * produced by `textTracksToJson`. 5411 * 5412 * @param {Tech} tech 5413 * The `Tech` to create the `TextTrack`s on. 5414 */ 5415 5416 5417 var jsonToTextTracks = function jsonToTextTracks(json, tech) { 5418 json.forEach(function (track) { 5419 var addedTrack = tech.addRemoteTextTrack(track).track; 5420 5421 if (!track.src && track.cues) { 5422 track.cues.forEach(function (cue) { 5423 return addedTrack.addCue(cue); 5424 }); 5425 } 5426 }); 5427 return tech.textTracks(); 5428 }; 5429 5430 var textTrackConverter = { 5431 textTracksToJson: textTracksToJson, 5432 jsonToTextTracks: jsonToTextTracks, 5433 trackToJson_: trackToJson_ 5434 }; 5435 5436 var commonjsGlobal = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}; 5437 5438 function createCommonjsModule(fn, module) { 5439 return module = { exports: {} }, fn(module, module.exports), module.exports; 5440 } 5441 5442 var keycode = createCommonjsModule(function (module, exports) { 5443 // Source: http://jsfiddle.net/vWx8V/ 5444 // http://stackoverflow.com/questions/5603195/full-list-of-javascript-keycodes 5445 5446 /** 5447 * Conenience method returns corresponding value for given keyName or keyCode. 5448 * 5449 * @param {Mixed} keyCode {Number} or keyName {String} 5450 * @return {Mixed} 5451 * @api public 5452 */ 5453 function keyCode(searchInput) { 5454 // Keyboard Events 5455 if (searchInput && 'object' === typeof searchInput) { 5456 var hasKeyCode = searchInput.which || searchInput.keyCode || searchInput.charCode; 5457 if (hasKeyCode) searchInput = hasKeyCode; 5458 } // Numbers 5459 5460 5461 if ('number' === typeof searchInput) return names[searchInput]; // Everything else (cast to string) 5462 5463 var search = String(searchInput); // check codes 5464 5465 var foundNamedKey = codes[search.toLowerCase()]; 5466 if (foundNamedKey) return foundNamedKey; // check aliases 5467 5468 var foundNamedKey = aliases[search.toLowerCase()]; 5469 if (foundNamedKey) return foundNamedKey; // weird character? 5470 5471 if (search.length === 1) return search.charCodeAt(0); 5472 return undefined; 5473 } 5474 /** 5475 * Compares a keyboard event with a given keyCode or keyName. 5476 * 5477 * @param {Event} event Keyboard event that should be tested 5478 * @param {Mixed} keyCode {Number} or keyName {String} 5479 * @return {Boolean} 5480 * @api public 5481 */ 5482 5483 5484 keyCode.isEventKey = function isEventKey(event, nameOrCode) { 5485 if (event && 'object' === typeof event) { 5486 var keyCode = event.which || event.keyCode || event.charCode; 5487
5488 if (keyCode === null || keyCode === undefined) { 5489 return false; 5490 } 5491 5492 if (typeof nameOrCode === 'string') { 5493 // check codes 5494 var foundNamedKey = codes[nameOrCode.toLowerCase()]; 5495 5496 if (foundNamedKey) { 5497 return foundNamedKey === keyCode; 5498 } // check aliases 5499 5500 5501 var foundNamedKey = aliases[nameOrCode.toLowerCase()]; 5502 5503 if (foundNamedKey) { 5504 return foundNamedKey === keyCode; 5505 } 5506 } else if (typeof nameOrCode === 'number') { 5507 return nameOrCode === keyCode; 5508 } 5509 5510 return false; 5511 } 5512 }; 5513 5514 exports = module.exports = keyCode; 5515 /** 5516 * Get by name 5517 * 5518 * exports.code['enter'] // => 13 5519 */ 5520 5521 var codes = exports.code = exports.codes = { 5522 'backspace': 8, 5523 'tab': 9, 5524 'enter': 13, 5525 'shift': 16, 5526 'ctrl': 17, 5527 'alt': 18, 5528 'pause/break': 19, 5529 'caps lock': 20, 5530 'esc': 27, 5531 'space': 32, 5532 'page up': 33, 5533 'page down': 34, 5534 'end': 35, 5535 'home': 36, 5536 'left': 37, 5537 'up': 38, 5538 'right': 39, 5539 'down': 40, 5540 'insert': 45, 5541 'delete': 46, 5542 'command': 91, 5543 'left command': 91, 5544 'right command': 93, 5545 'numpad *': 106, 5546 'numpad +': 107, 5547 'numpad -': 109, 5548 'numpad .': 110, 5549 'numpad /': 111, 5550 'num lock': 144, 5551 'scroll lock': 145, 5552 'my computer': 182, 5553 'my calculator': 183, 5554 ';': 186, 5555 '=': 187, 5556 ',': 188, 5557 '-': 189, 5558 '.': 190, 5559 '/': 191, 5560 '`': 192, 5561 '[': 219, 5562 '\\': 220, 5563 ']': 221, 5564 "'": 222 // Helper aliases 5565 5566 }; 5567 var aliases = exports.aliases = { 5568 'windows': 91, 5569 'â§': 16, 5570 'â¥': 18, 5571 'â': 17, 5572 'â': 91, 5573 'ctl': 17, 5574 'control': 17, 5575 'option': 18, 5576 'pause': 19, 5577 'break': 19, 5578 'caps': 20, 5579 'return': 13, 5580 'escape': 27, 5581 'spc': 32, 5582 'spacebar': 32, 5583 'pgup': 33, 5584 'pgdn': 34, 5585 'ins': 45, 5586 'del': 46, 5587 'cmd': 91 5588 /*! 5589 * Programatically add the following 5590 */ 5591 // lower case chars 5592 5593 }; 5594 5595 for (i = 97; i < 123; i++) { 5596 codes[String.fromCharCode(i)] = i - 32; 5597 } // numbers 5598 5599 5600 for (var i = 48; i < 58; i++) { 5601 codes[i - 48] = i; 5602 } // function keys 5603 5604 5605 for (i = 1; i < 13; i++) { 5606 codes['f' + i] = i + 111; 5607 } // numpad keys 5608 5609 5610 for (i = 0; i < 10; i++) { 5611 codes['numpad ' + i] = i + 96; 5612 } 5613 /** 5614 * Get by code 5615 * 5616 * exports.name[13] // => 'Enter' 5617 */ 5618 5619 5620 var names = exports.names = exports.title = {}; // title for backward compat 5621 // Create reverse mapping 5622 5623 for (i in codes) { 5624 names[codes[i]] = i; 5625 } // Add aliases 5626 5627 5628 for (var alias in aliases) { 5629 codes[alias] = aliases[alias]; 5630 } 5631 }); 5632 var keycode_1 = keycode.code; 5633 var keycode_2 = keycode.codes; 5634 var keycode_3 = keycode.aliases; 5635 var keycode_4 = keycode.names; 5636 var keycode_5 = keycode.title; 5637 5638 var MODAL_CLASS_NAME = 'vjs-modal-dialog'; 5639 /** 5640 * The `ModalDialog` displays over the video and its controls, which blocks 5641 * interaction with the player until it is closed. 5642 * 5643 * Modal dialogs include a "Close" button and will close when that button 5644 * is activated - or when ESC is pressed anywhere. 5645 * 5646 * @extends Component 5647 */ 5648 5649 var ModalDialog = 5650 /*#__PURE__*/ 5651 function (_Component) { 5652 _inheritsLoose(ModalDialog, _Component); 5653 5654 /** 5655 * Create an instance of this class. 5656 * 5657 * @param {Player} player 5658 * The `Player` that this class should be attached to. 5659 * 5660 * @param {Object} [options] 5661 * The key/value store of player options. 5662 * 5663 * @param {Mixed} [options.content=undefined] 5664 * Provide customized content for this modal. 5665 * 5666 * @param {string} [options.description] 5667 * A text description for the modal, primarily for accessibility. 5668 * 5669 * @param {boolean} [options.fillAlways=false] 5670 * Normally, modals are automatically filled only the first time 5671 * they open. This tells the modal to refresh its content 5672 * every time it opens. 5673 * 5674 * @param {string} [options.label] 5675 * A text label for the modal, primarily for accessibility. 5676 * 5677 * @param {boolean}
5677 [options.pauseOnOpen=true] 5678 * If `true`, playback will will be paused if playing when 5679 * the modal opens, and resumed when it closes. 5680 * 5681 * @param {boolean} [options.temporary=true] 5682 * If `true`, the modal can only be opened once; it will be 5683 * disposed as soon as it's closed. 5684 * 5685 * @param {boolean} [options.uncloseable=false] 5686 * If `true`, the user will not be able to close the modal 5687 * through the UI in the normal ways. Programmatic closing is 5688 * still possible. 5689 */ 5690 function ModalDialog(player, options) { 5691 var _this; 5692 5693 _this = _Component.call(this, player, options) || this; 5694 _this.opened_ = _this.hasBeenOpened_ = _this.hasBeenFilled_ = false; 5695 5696 _this.closeable(!_this.options_.uncloseable); 5697 5698 _this.content(_this.options_.content); // Make sure the contentEl is defined AFTER any children are initialized 5699 // because we only want the contents of the modal in the contentEl 5700 // (not the UI elements like the close button). 5701 5702 5703 _this.contentEl_ = createEl('div', { 5704 className: MODAL_CLASS_NAME + "-content" 5705 }, { 5706 role: 'document' 5707 }); 5708 _this.descEl_ = createEl('p', { 5709 className: MODAL_CLASS_NAME + "-description vjs-control-text", 5710 id: _this.el().getAttribute('aria-describedby') 5711 }); 5712 textContent(_this.descEl_, _this.description()); 5713 5714 _this.el_.appendChild(_this.descEl_); 5715 5716 _this.el_.appendChild(_this.contentEl_); 5717 5718 return _this; 5719 } 5720 /** 5721 * Create the `ModalDialog`'s DOM element 5722 * 5723 * @return {Element} 5724 * The DOM element that gets created. 5725 */ 5726 5727 5728 var _proto = ModalDialog.prototype; 5729 5730 _proto.createEl = function createEl$$1() { 5731 return _Component.prototype.createEl.call(this, 'div', { 5732 className: this.buildCSSClass(), 5733 tabIndex: -1 5734 }, { 5735 'aria-describedby': this.id() + "_description", 5736 'aria-hidden': 'true', 5737 'aria-label': this.label(), 5738 'role': 'dialog' 5739 }); 5740 }; 5741 5742 _proto.dispose = function dispose() { 5743 this.contentEl_ = null; 5744 this.descEl_ = null; 5745 this.previouslyActiveEl_ = null; 5746 5747 _Component.prototype.dispose.call(this); 5748 } 5749 /** 5750 * Builds the default DOM `className`. 5751 * 5752 * @return {string} 5753 * The DOM `className` for this object. 5754 */ 5755 ; 5756 5757 _proto.buildCSSClass = function buildCSSClass() { 5758 return MODAL_CLASS_NAME + " vjs-hidden " + _Component.prototype.buildCSSClass.call(this); 5759 } 5760 /** 5761 * Returns the label string for this modal. Primarily used for accessibility. 5762 * 5763 * @return {string} 5764 * the localized or raw label of this modal. 5765 */ 5766 ; 5767 5768 _proto.label = function label() { 5769 return this.localize(this.options_.label || 'Modal Window'); 5770 } 5771 /** 5772 * Returns the description string for this modal. Primarily used for 5773 * accessibility. 5774 * 5775 * @return {string} 5776 * The localized or raw description of this modal. 5777 */ 5778 ; 5779 5780 _proto.description = function description() { 5781 var desc = this.options_.description || this.localize('This is a modal window.'); // Append a universal closeability message if the modal is closeable. 5782 5783 if (this.closeable()) { 5784 desc += ' ' + this.localize('This modal can be closed by pressing the Escape key or activating the close button.'); 5785 } 5786 5787 return desc; 5788 } 5789 /** 5790 * Opens the modal. 5791 * 5792 * @fires ModalDialog#beforemodalopen 5793 * @fires ModalDialog#modalopen 5794 */ 5795 ; 5796 5797 _proto.open = function open() { 5798 if (!this.opened_) { 5799 var player = this.player(); 5800 /** 5801 * Fired just before a `ModalDialog` is opened. 5802 * 5803 * @event ModalDialog#beforemodalopen 5804 * @type {EventTarget~Event} 5805 */ 5806 5807 this.trigger('beforemodalopen'); 5808 this.opened_ = true; // Fill content if the modal has never opened before and 5809 // never been filled. 5810 5811 if (this.options_.fillAlways || !this.hasBeenOpened_ && !this.hasBeenFilled_) { 5812 this.fill(); 5813 } // If the player was playing, pause it and take note of its previously 5814 // playing state. 5815 5816 5817 this.wasPlaying_ = !player.paused(); 5818
vendor: 33,984 bytes, lines 5819-7139
5819 if (this.options_.pauseOnOpen && this.wasPlaying_) { 5820 player.pause(); 5821 } 5822 5823 this.on('keydown', this.handleKeyDown); // Hide controls and note if they were enabled. 5824 5825 this.hadControls_ = player.controls(); 5826 player.controls(false); 5827 this.show(); 5828 this.conditionalFocus_(); 5829 this.el().setAttribute('aria-hidden', 'false'); 5830 /** 5831 * Fired just after a `ModalDialog` is opened. 5832 * 5833 * @event ModalDialog#modalopen 5834 * @type {EventTarget~Event} 5835 */ 5836 5837 this.trigger('modalopen'); 5838 this.hasBeenOpened_ = true; 5839 } 5840 } 5841 /** 5842 * If the `ModalDialog` is currently open or closed. 5843 * 5844 * @param {boolean} [value] 5845 * If given, it will open (`true`) or close (`false`) the modal. 5846 * 5847 * @return {boolean} 5848 * the current open state of the modaldialog 5849 */ 5850 ; 5851 5852 _proto.opened = function opened(value) { 5853 if (typeof value === 'boolean') { 5854 this[value ? 'open' : 'close'](); 5855 } 5856 5857 return this.opened_; 5858 } 5859 /** 5860 * Closes the modal, does nothing if the `ModalDialog` is 5861 * not open. 5862 * 5863 * @fires ModalDialog#beforemodalclose 5864 * @fires ModalDialog#modalclose 5865 */ 5866 ; 5867 5868 _proto.close = function close() { 5869 if (!this.opened_) { 5870 return; 5871 } 5872 5873 var player = this.player(); 5874 /** 5875 * Fired just before a `ModalDialog` is closed. 5876 * 5877 * @event ModalDialog#beforemodalclose 5878 * @type {EventTarget~Event} 5879 */ 5880 5881 this.trigger('beforemodalclose'); 5882 this.opened_ = false; 5883 5884 if (this.wasPlaying_ && this.options_.pauseOnOpen) { 5885 player.play(); 5886 } 5887 5888 this.off('keydown', this.handleKeyDown); 5889 5890 if (this.hadControls_) { 5891 player.controls(true); 5892 } 5893 5894 this.hide(); 5895 this.el().setAttribute('aria-hidden', 'true'); 5896 /** 5897 * Fired just after a `ModalDialog` is closed. 5898 * 5899 * @event ModalDialog#modalclose 5900 * @type {EventTarget~Event} 5901 */ 5902 5903 this.trigger('modalclose'); 5904 this.conditionalBlur_(); 5905 5906 if (this.options_.temporary) { 5907 this.dispose(); 5908 } 5909 } 5910 /** 5911 * Check to see if the `ModalDialog` is closeable via the UI. 5912 * 5913 * @param {boolean} [value] 5914 * If given as a boolean, it will set the `closeable` option. 5915 * 5916 * @return {boolean} 5917 * Returns the final value of the closable option. 5918 */ 5919 ; 5920 5921 _proto.closeable = function closeable(value) { 5922 if (typeof value === 'boolean') { 5923 var closeable = this.closeable_ = !!value; 5924 var close = this.getChild('closeButton'); // If this is being made closeable and has no close button, add one. 5925 5926 if (closeable && !close) { 5927 // The close button should be a child of the modal - not its 5928 // content element, so temporarily change the content element. 5929 var temp = this.contentEl_; 5930 this.contentEl_ = this.el_; 5931 close = this.addChild('closeButton', { 5932 controlText: 'Close Modal Dialog' 5933 }); 5934 this.contentEl_ = temp; 5935 this.on(close, 'close', this.close); 5936 } // If this is being made uncloseable and has a close button, remove it. 5937 5938 5939 if (!closeable && close) { 5940 this.off(close, 'close', this.close); 5941 this.removeChild(close); 5942 close.dispose(); 5943 } 5944 } 5945 5946 return this.closeable_; 5947 } 5948 /** 5949 * Fill the modal's content element with the modal's "content" option. 5950 * The content element will be emptied before this change takes place. 5951 */ 5952 ; 5953 5954 _proto.fill = function fill() { 5955 this.fillWith(this.content()); 5956 } 5957 /** 5958 * Fill the modal's content element with arbitrary content. 5959 * The content element will be emptied before this change takes place. 5960 * 5961 * @fires ModalDialog#beforemodalfill 5962 * @fires ModalDialog#modalfill 5963 * 5964 * @param {Mixed} [content] 5965 * The same rules apply to this as apply to the `content` option. 5966 */ 5967 ; 5968 5969 _proto.fillWith = function fillWith(content) { 5970 var contentEl = this.contentEl(); 5971 var parentEl = contentEl.parentNode; 5972 var nextSiblingEl = contentEl.nextSibling; 5973 /** 5974 * Fired just before a `ModalDialog` is filled with content. 5975 * 5976 * @event ModalDialog#beforemodalfill 5977 * @type {EventTarget~Event} 5978 */ 5979 5980 this.trigger('beforemodalfill'); 5981 this.hasBeenFilled_ = true; // Detach the content element from the DOM before performing 5982 // manipulation to avoid modifying the live DOM multiple times. 5983 5984 parentEl.removeChild(contentEl); 5985 this.empty(); 5986 insertContent(contentEl, content); 5987 /** 5988 * Fired just after a `ModalDialog` is filled with content. 5989 * 5990 * @event ModalDialog#modalfill 5991 * @type {EventTarget~Event} 5992 */ 5993 5994 this.trigger('modalfill'); // Re-inject the re-filled content element. 5995 5996 if (nextSiblingEl) { 5997 parentEl.insertBefore(contentEl, nextSiblingEl); 5998 } else { 5999 parentEl.appendChild(contentEl); 6000 } // make sure that the close button is last in the dialog DOM 6001 6002 6003 var closeButton = this.getChild('closeButton'); 6004 6005 if (closeButton) { 6006 parentEl.appendChild(closeButton.el_); 6007 } 6008 } 6009 /** 6010 * Empties the content element. This happens anytime the modal is filled. 6011 * 6012 * @fires ModalDialog#beforemodalempty 6013 * @fires ModalDialog#modalempty 6014 */ 6015 ; 6016 6017 _proto.empty = function empty() { 6018 /** 6019 * Fired just before a `ModalDialog` is emptied. 6020 * 6021 * @event ModalDialog#beforemodalempty 6022 * @type {EventTarget~Event} 6023 */ 6024 this.trigger('beforemodalempty'); 6025 emptyEl(this.contentEl()); 6026 /** 6027 * Fired just after a `ModalDialog` is emptied. 6028 * 6029 * @event ModalDialog#modalempty 6030 * @type {EventTarget~Event} 6031 */ 6032 6033 this.trigger('modalempty'); 6034 } 6035 /** 6036 * Gets or sets the modal content, which gets normalized before being 6037 * rendered into the DOM. 6038 * 6039 * This does not update the DOM or fill the modal, but it is called during 6040 * that process. 6041 * 6042 * @param {Mixed} [value] 6043 * If defined, sets the internal content value to be used on the 6044 * next call(s) to `fill`. This value is normalized before being 6045 * inserted. To "clear" the internal content value, pass `null`. 6046 * 6047 * @return {Mixed} 6048 * The current content of the modal dialog 6049 */ 6050 ; 6051 6052 _proto.content = function content(value) { 6053 if (typeof value !== 'undefined') { 6054 this.content_ = value; 6055 } 6056 6057 return this.content_; 6058 } 6059 /** 6060 * conditionally focus the modal dialog if focus was previously on the player. 6061 * 6062 * @private 6063 */ 6064 ; 6065 6066 _proto.conditionalFocus_ = function conditionalFocus_() { 6067 var activeEl = document.activeElement; 6068 var playerEl = this.player_.el_; 6069 this.previouslyActiveEl_ = null; 6070 6071 if (playerEl.contains(activeEl) || playerEl === activeEl) { 6072 this.previouslyActiveEl_ = activeEl; 6073 this.focus(); 6074 } 6075 } 6076 /** 6077 * conditionally blur the element and refocus the last focused element 6078 * 6079 * @private 6080 */ 6081 ; 6082 6083 _proto.conditionalBlur_ = function conditionalBlur_() { 6084 if (this.previouslyActiveEl_) { 6085 this.previouslyActiveEl_.focus(); 6086 this.previouslyActiveEl_ = null; 6087 } 6088 } 6089 /** 6090 * Keydown handler. Attached when modal is focused. 6091 * 6092 * @listens keydown 6093 */ 6094 ; 6095 6096 _proto.handleKeyDown = function handleKeyDown(event) { 6097 // Do not allow keydowns to reach out of the modal dialog. 6098 event.stopPropagation(); 6099 6100 if (keycode.isEventKey(event, 'Escape') && this.closeable()) { 6101 event.preventDefault(); 6102 this.close(); 6103 return; 6104 } // exit early if it isn't a tab key 6105 6106 6107 if (!keycode.isEventKey(event, 'Tab')) { 6108 return; 6109 } 6110 6111 var focusableEls = this.focusableEls_(); 6112 var activeEl = this.el_.querySelector(':focus'); 6113 var focusIndex; 6114 6115 for (var i = 0; i < focusableEls.length; i++) { 6116 if (activeEl === focusableEls[i]) { 6117 focusIndex = i; 6118 break; 6119 } 6120 } 6121 6122 if (document.activeElement === this.el_) { 6123 focusIndex = 0; 6124 } 6125 6126 if (event.shiftKey && focusIndex === 0) { 6127 focusableEls[focusableEls.length - 1].focus(); 6128 event.preventDefault(); 6129 } else if (!event.shiftKey && focusIndex === focusableEls.length - 1) { 6130 focusableEls[0].focus(); 6131 event.preventDefault(); 6132 } 6133 } 6134 /** 6135 * get all focusable elements 6136 * 6137 * @private 6138 */ 6139 ; 6140 6141 _proto.focusableEls_ = function focusableEls_() { 6142 var allChildren = this.el_.querySelectorAll('*'); 6143 return Array.prototype.filter.call(allChildren, function (child) { 6144 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'); 6145 }); 6146 }; 6147 6148 return ModalDialog; 6149 }(Component); 6150 /** 6151 * Default options for `ModalDialog` default options. 6152 * 6153 * @type {Object} 6154 * @private 6155 */ 6156 6157 6158 ModalDialog.prototype.options_ = { 6159 pauseOnOpen: true, 6160 temporary: true 6161 }; 6162 Component.registerComponent('ModalDialog', ModalDialog); 6163 6164 /** 6165 * Common functionaliy between {@link TextTrackList}, {@link AudioTrackList}, and 6166 * {@link VideoTrackList} 6167 * 6168 * @extends EventTarget 6169 */ 6170 6171 var TrackList = 6172 /*#__PURE__*/ 6173 function (_EventTarget) { 6174 _inheritsLoose(TrackList, _EventTarget); 6175 6176 /** 6177 * Create an instance of this class 6178 * 6179 * @param {Track[]} tracks 6180 * A list of tracks to initialize the list with. 6181 * 6182 * @abstract 6183 */ 6184 function TrackList(tracks) { 6185 var _this; 6186 6187 if (tracks === void 0) { 6188 tracks = []; 6189 } 6190 6191 _this = _EventTarget.call(this) || this; 6192 _this.tracks_ = []; 6193 /** 6194 * @memberof TrackList 6195 * @member {number} length 6196 * The current number of `Track`s in the this Trackist. 6197 * @instance 6198 */ 6199 6200 Object.defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), 'length', { 6201 get: function get() { 6202 return this.tracks_.length; 6203 } 6204 }); 6205 6206 for (var i = 0; i < tracks.length; i++) { 6207 _this.addTrack(tracks[i]); 6208 } 6209 6210 return _this; 6211 } 6212 /** 6213 * Add a {@link Track} to the `TrackList` 6214 * 6215 * @param {Track} track 6216 * The audio, video, or text track to add to the list. 6217 * 6218 * @fires TrackList#addtrack 6219 */ 6220 6221 6222 var _proto = TrackList.prototype; 6223 6224 _proto.addTrack = function addTrack(track) { 6225 var index = this.tracks_.length; 6226 6227 if (!('' + index in this)) { 6228 Object.defineProperty(this, index, { 6229 get: function get() { 6230 return this.tracks_[index]; 6231 } 6232 }); 6233 } // Do not add duplicate tracks 6234 6235 6236 if (this.tracks_.indexOf(track) === -1) { 6237 this.tracks_.push(track); 6238 /** 6239 * Triggered when a track is added to a track list. 6240 * 6241 * @event TrackList#addtrack 6242 * @type {EventTarget~Event} 6243 * @property {Track} track 6244 * A reference to track that was added. 6245 */ 6246 6247 this.trigger({ 6248 track: track, 6249 type: 'addtrack', 6250 target: this 6251 }); 6252 } 6253 } 6254 /** 6255 * Remove a {@link Track} from the `TrackList` 6256 * 6257 * @param {Track} rtrack 6258 * The audio, video, or text track to remove from the list. 6259 * 6260 * @fires TrackList#removetrack 6261 */ 6262 ; 6263 6264 _proto.removeTrack = function removeTrack(rtrack) { 6265 var track; 6266 6267 for (var i = 0, l = this.length; i < l; i++) { 6268 if (this[i] === rtrack) { 6269 track = this[i]; 6270 6271 if (track.off) { 6272 track.off(); 6273 } 6274 6275 this.tracks_.splice(i, 1); 6276 break; 6277 } 6278 } 6279 6280 if (!track) { 6281 return; 6282 } 6283 /** 6284 * Triggered when a track is removed from track list. 6285 * 6286 * @event TrackList#removetrack 6287 * @type {EventTarget~Event} 6288 * @property {Track} track 6289 * A reference to track that was removed. 6290 */ 6291 6292 6293 this.trigger({ 6294 track: track, 6295 type: 'removetrack', 6296 target: this 6297 }); 6298 } 6299 /** 6300 * Get a Track from the TrackList by a tracks id 6301 * 6302 * @param {string} id - the id of the track to get 6303 * @method getTrackById 6304 * @return {Track} 6305 * @private 6306 */ 6307 ; 6308 6309 _proto.getTrackById = function getTrackById(id) { 6310 var result = null; 6311 6312 for (var i = 0, l = this.length; i < l; i++) { 6313 var track = this[i]; 6314 6315 if (track.id === id) { 6316 result = track; 6317 break; 6318 } 6319 } 6320 6321 return result; 6322 }; 6323 6324 return TrackList; 6325 }(EventTarget); 6326 /** 6327 * Triggered when a different track is selected/enabled. 6328 * 6329 * @event TrackList#change 6330 * @type {EventTarget~Event} 6331 */ 6332 6333 /** 6334 * Events that can be called with on + eventName. See {@link EventHandler}. 6335 * 6336 * @property {Object} TrackList#allowedEvents_ 6337 * @private 6338 */ 6339 6340 6341 TrackList.prototype.allowedEvents_ = { 6342 change: 'change', 6343 addtrack: 'addtrack', 6344 removetrack: 'removetrack' 6345 }; // emulate attribute EventHandler support to allow for feature detection 6346 6347 for (var event in TrackList.prototype.allowedEvents_) { 6348 TrackList.prototype['on' + event] = null; 6349 } 6350 6351 /** 6352 * Anywhere we call this function we diverge from the spec 6353 * as we only support one enabled audiotrack at a time 6354 * 6355 * @param {AudioTrackList} list 6356 * list to work on 6357 * 6358 * @param {AudioTrack} track 6359 * The track to skip 6360 * 6361 * @private 6362 */ 6363 6364 var disableOthers = function disableOthers(list, track) { 6365 for (var i = 0; i < list.length; i++) { 6366 if (!Object.keys(list[i]).length || track.id === list[i].id) { 6367 continue; 6368 } // another audio track is enabled, disable it 6369 6370 6371 list[i].enabled = false; 6372 } 6373 }; 6374 /** 6375 * The current list of {@link AudioTrack} for a media file. 6376 * 6377 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotracklist} 6378 * @extends TrackList 6379 */ 6380 6381 6382 var AudioTrackList = 6383 /*#__PURE__*/ 6384 function (_TrackList) { 6385 _inheritsLoose(AudioTrackList, _TrackList); 6386 6387 /** 6388 * Create an instance of this class. 6389 * 6390 * @param {AudioTrack[]} [tracks=[]] 6391 * A list of `AudioTrack` to instantiate the list with. 6392 */ 6393 function AudioTrackList(tracks) { 6394 var _this; 6395 6396 if (tracks === void 0) { 6397 tracks = []; 6398 } 6399 6400 // make sure only 1 track is enabled 6401 // sorted from last index to first index 6402 for (var i = tracks.length - 1; i >= 0; i--) { 6403 if (tracks[i].enabled) { 6404 disableOthers(tracks, tracks[i]); 6405 break; 6406 } 6407 } 6408 6409 _this = _TrackList.call(this, tracks) || this; 6410 _this.changing_ = false; 6411 return _this; 6412 } 6413 /** 6414 * Add an {@link AudioTrack} to the `AudioTrackList`. 6415 * 6416 * @param {AudioTrack} track 6417 * The AudioTrack to add to the list 6418 * 6419 * @fires TrackList#addtrack 6420 */ 6421 6422 6423 var _proto = AudioTrackList.prototype; 6424 6425 _proto.addTrack = function addTrack(track) { 6426 var _this2 = this; 6427 6428 if (track.enabled) { 6429 disableOthers(this, track); 6430 } 6431 6432 _TrackList.prototype.addTrack.call(this, track); // native tracks don't have this 6433 6434 6435 if (!track.addEventListener) { 6436 return; 6437 } 6438 6439 track.enabledChange_ = function () { 6440 // when we are disabling other tracks (since we don't support 6441 // more than one track at a time) we will set changing_ 6442 // to true so that we don't trigger additional change events 6443 if (_this2.changing_) { 6444 return; 6445 } 6446 6447 _this2.changing_ = true; 6448 disableOthers(_this2, track); 6449 _this2.changing_ = false; 6450 6451 _this2.trigger('change'); 6452 }; 6453 /** 6454 * @listens AudioTrack#enabledchange 6455 * @fires TrackList#change 6456 */ 6457 6458 6459 track.addEventListener('enabledchange', track.enabledChange_); 6460 }; 6461 6462 _proto.removeTrack = function removeTrack(rtrack) { 6463 _TrackList.prototype.removeTrack.call(this, rtrack); 6464 6465 if (rtrack.removeEventListener && rtrack.enabledChange_) { 6466 rtrack.removeEventListener('enabledchange', rtrack.enabledChange_); 6467 rtrack.enabledChange_ = null; 6468 } 6469 }; 6470 6471 return AudioTrackList; 6472 }(TrackList); 6473 6474 /** 6475 * Un-select all other {@link VideoTrack}s that are selected. 6476 * 6477 * @param {VideoTrackList} list 6478 * list to work on 6479 * 6480 * @param {VideoTrack} track 6481 * The track to skip 6482 * 6483 * @private 6484 */ 6485 6486 var disableOthers$1 = function disableOthers(list, track) { 6487 for (var i = 0; i < list.length; i++) { 6488 if (!Object.keys(list[i]).length || track.id === list[i].id) { 6489 continue; 6490 } // another video track is enabled, disable it 6491 6492 6493 list[i].selected = false; 6494 } 6495 }; 6496 /** 6497 * The current list of {@link VideoTrack} for a video. 6498 * 6499 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#videotracklist} 6500 * @extends TrackList 6501 */ 6502 6503 6504 var VideoTrackList = 6505 /*#__PURE__*/ 6506 function (_TrackList) { 6507 _inheritsLoose(VideoTrackList, _TrackList); 6508 6509 /** 6510 * Create an instance of this class. 6511 * 6512 * @param {VideoTrack[]} [tracks=[]] 6513 * A list of `VideoTrack` to instantiate the list with. 6514 */ 6515 function VideoTrackList(tracks) { 6516 var _this; 6517 6518 if (tracks === void 0) { 6519 tracks = []; 6520 } 6521 6522 // make sure only 1 track is enabled 6523 // sorted from last index to first index 6524 for (var i = tracks.length - 1; i >= 0; i--) { 6525 if (tracks[i].selected) { 6526 disableOthers$1(tracks, tracks[i]); 6527 break; 6528 } 6529 } 6530 6531 _this = _TrackList.call(this, tracks) || this; 6532 _this.changing_ = false; 6533 /** 6534 * @member {number} VideoTrackList#selectedIndex 6535 * The current index of the selected {@link VideoTrack`}. 6536 */ 6537 6538 Object.defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), 'selectedIndex', { 6539 get: function get() { 6540 for (var _i = 0; _i < this.length; _i++) { 6541 if (this[_i].selected) { 6542 return _i; 6543 } 6544 } 6545 6546 return -1; 6547 }, 6548 set: function set() {} 6549 }); 6550 return _this; 6551 } 6552 /** 6553 * Add a {@link VideoTrack} to the `VideoTrackList`. 6554 * 6555 * @param {VideoTrack} track 6556 * The VideoTrack to add to the list 6557 * 6558 * @fires TrackList#addtrack 6559 */ 6560 6561 6562 var _proto = VideoTrackList.prototype; 6563 6564 _proto.addTrack = function addTrack(track) { 6565 var _this2 = this; 6566 6567 if (track.selected) { 6568 disableOthers$1(this, track); 6569 } 6570 6571 _TrackList.prototype.addTrack.call(this, track); // native tracks don't have this 6572 6573 6574 if (!track.addEventListener) { 6575 return; 6576 } 6577 6578 track.selectedChange_ = function () { 6579 if (_this2.changing_) { 6580 return; 6581 } 6582 6583 _this2.changing_ = true; 6584 disableOthers$1(_this2, track); 6585 _this2.changing_ = false; 6586 6587 _this2.trigger('change'); 6588 }; 6589 /** 6590 * @listens VideoTrack#selectedchange 6591 * @fires TrackList#change 6592 */ 6593 6594 6595 track.addEventListener('selectedchange', track.selectedChange_); 6596 }; 6597 6598 _proto.removeTrack = function removeTrack(rtrack) { 6599 _TrackList.prototype.removeTrack.call(this, rtrack); 6600 6601 if (rtrack.removeEventListener && rtrack.selectedChange_) { 6602 rtrack.removeEventListener('selectedchange', rtrack.selectedChange_); 6603 rtrack.selectedChange_ = null; 6604 } 6605 }; 6606 6607 return VideoTrackList; 6608 }(TrackList); 6609 6610 /** 6611 * The current list of {@link TextTrack} for a media file. 6612 * 6613 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttracklist} 6614 * @extends TrackList 6615 */ 6616 6617 var TextTrackList = 6618 /*#__PURE__*/ 6619 function (_TrackList) { 6620 _inheritsLoose(TextTrackList, _TrackList); 6621 6622 function TextTrackList() { 6623 return _TrackList.apply(this, arguments) || this; 6624 } 6625 6626 var _proto = TextTrackList.prototype; 6627 6628 /** 6629 * Add a {@link TextTrack} to the `TextTrackList` 6630 * 6631 * @param {TextTrack} track 6632 * The text track to add to the list. 6633 * 6634 * @fires TrackList#addtrack 6635 */ 6636 _proto.addTrack = function addTrack(track) { 6637 var _this = this; 6638 6639 _TrackList.prototype.addTrack.call(this, track); 6640 6641 if (!this.queueChange_) { 6642 this.queueChange_ = function () { 6643 return _this.queueTrigger('change'); 6644 }; 6645 } 6646 6647 if (!this.triggerSelectedlanguagechange) { 6648 this.triggerSelectedlanguagechange_ = function () { 6649 return _this.trigger('selectedlanguagechange'); 6650 }; 6651 } 6652 /** 6653 * @listens TextTrack#modechange 6654 * @fires TrackList#change 6655 */ 6656 6657 6658 track.addEventListener('modechange', this.queueChange_); 6659 var nonLanguageTextTrackKind = ['metadata', 'chapters']; 6660 6661 if (nonLanguageTextTrackKind.indexOf(track.kind) === -1) { 6662 track.addEventListener('modechange', this.triggerSelectedlanguagechange_); 6663 } 6664 }; 6665 6666 _proto.removeTrack = function removeTrack(rtrack) { 6667 _TrackList.prototype.removeTrack.call(this, rtrack); // manually remove the event handlers we added 6668 6669 6670 if (rtrack.removeEventListener) { 6671 if (this.queueChange_) { 6672 rtrack.removeEventListener('modechange', this.queueChange_); 6673 } 6674 6675 if (this.selectedlanguagechange_) { 6676 rtrack.removeEventListener('modechange', this.triggerSelectedlanguagechange_); 6677 } 6678 } 6679 }; 6680 6681 return TextTrackList; 6682 }(TrackList); 6683 6684 /** 6685 * @file html-track-element-list.js 6686 */ 6687 6688 /** 6689 * The current list of {@link HtmlTrackElement}s. 6690 */ 6691 var HtmlTrackElementList = 6692 /*#__PURE__*/ 6693 function () { 6694 /** 6695 * Create an instance of this class. 6696 * 6697 * @param {HtmlTrackElement[]} [tracks=[]] 6698 * A list of `HtmlTrackElement` to instantiate the list with. 6699 */ 6700 function HtmlTrackElementList(trackElements) { 6701 if (trackElements === void 0) { 6702 trackElements = []; 6703 } 6704 6705 this.trackElements_ = []; 6706 /** 6707 * @memberof HtmlTrackElementList 6708 * @member {number} length 6709 * The current number of `Track`s in the this Trackist. 6710 * @instance 6711 */ 6712 6713 Object.defineProperty(this, 'length', { 6714 get: function get() { 6715 return this.trackElements_.length; 6716 } 6717 }); 6718 6719 for (var i = 0, length = trackElements.length; i < length; i++) { 6720 this.addTrackElement_(trackElements[i]); 6721 } 6722 } 6723 /** 6724 * Add an {@link HtmlTrackElement} to the `HtmlTrackElementList` 6725 * 6726 * @param {HtmlTrackElement} trackElement 6727 * The track element to add to the list. 6728 * 6729 * @private 6730 */ 6731 6732 6733 var _proto = HtmlTrackElementList.prototype; 6734 6735 _proto.addTrackElement_ = function addTrackElement_(trackElement) { 6736 var index = this.trackElements_.length; 6737 6738 if (!('' + index in this)) { 6739 Object.defineProperty(this, index, { 6740 get: function get() { 6741 return this.trackElements_[index]; 6742 } 6743 }); 6744 } // Do not add duplicate elements 6745 6746 6747 if (this.trackElements_.indexOf(trackElement) === -1) { 6748 this.trackElements_.push(trackElement); 6749 } 6750 } 6751 /** 6752 * Get an {@link HtmlTrackElement} from the `HtmlTrackElementList` given an 6753 * {@link TextTrack}. 6754 * 6755 * @param {TextTrack} track 6756 * The track associated with a track element. 6757 * 6758 * @return {HtmlTrackElement|undefined} 6759 * The track element that was found or undefined. 6760 * 6761 * @private 6762 */ 6763 ; 6764 6765 _proto.getTrackElementByTrack_ = function getTrackElementByTrack_(track) { 6766 var trackElement_; 6767 6768 for (var i = 0, length = this.trackElements_.length; i < length; i++) { 6769 if (track === this.trackElements_[i].track) { 6770 trackElement_ = this.trackElements_[i]; 6771 break; 6772 } 6773 } 6774 6775 return trackElement_; 6776 } 6777 /** 6778 * Remove a {@link HtmlTrackElement} from the `HtmlTrackElementList` 6779 * 6780 * @param {HtmlTrackElement} trackElement 6781 * The track element to remove from the list. 6782 * 6783 * @private 6784 */ 6785 ; 6786 6787 _proto.removeTrackElement_ = function removeTrackElement_(trackElement) { 6788 for (var i = 0, length = this.trackElements_.length; i < length; i++) { 6789 if (trackElement === this.trackElements_[i]) { 6790 if (this.trackElements_[i].track && typeof this.trackElements_[i].track.off === 'function') { 6791 this.trackElements_[i].track.off(); 6792 } 6793 6794 if (typeof this.trackElements_[i].off === 'function') { 6795 this.trackElements_[i].off(); 6796 } 6797 6798 this.trackElements_.splice(i, 1); 6799 break; 6800 } 6801 } 6802 }; 6803 6804 return HtmlTrackElementList; 6805 }(); 6806 6807 /** 6808 * @file text-track-cue-list.js 6809 */ 6810 6811 /** 6812 * @typedef {Object} TextTrackCueList~TextTrackCue 6813 * 6814 * @property {string} id 6815 * The unique id for this text track cue 6816 * 6817 * @property {number} startTime 6818 * The start time for this text track cue 6819 * 6820 * @property {number} endTime 6821 * The end time for this text track cue 6822 * 6823 * @property {boolean} pauseOnExit 6824 * Pause when the end time is reached if true. 6825 * 6826 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackcue} 6827 */ 6828 6829 /** 6830 * A List of TextTrackCues. 6831 * 6832 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackcuelist} 6833 */ 6834 var TextTrackCueList = 6835 /*#__PURE__*/ 6836 function () { 6837 /** 6838 * Create an instance of this class.. 6839 * 6840 * @param {Array} cues 6841 * A list of cues to be initialized with 6842 */ 6843 function TextTrackCueList(cues) { 6844 TextTrackCueList.prototype.setCues_.call(this, cues); 6845 /** 6846 * @memberof TextTrackCueList 6847 * @member {number} length 6848 * The current number of `TextTrackCue`s in the TextTrackCueList. 6849 * @instance 6850 */ 6851 6852 Object.defineProperty(this, 'length', { 6853 get: function get() { 6854 return this.length_; 6855 } 6856 }); 6857 } 6858 /** 6859 * A setter for cues in this list. Creates getters 6860 * an an index for the cues. 6861 * 6862 * @param {Array} cues 6863 * An array of cues to set 6864 * 6865 * @private 6866 */ 6867 6868 6869 var _proto = TextTrackCueList.prototype; 6870 6871 _proto.setCues_ = function setCues_(cues) { 6872 var oldLength = this.length || 0; 6873 var i = 0; 6874 var l = cues.length; 6875 this.cues_ = cues; 6876 this.length_ = cues.length; 6877 6878 var defineProp = function defineProp(index) { 6879 if (!('' + index in this)) { 6880 Object.defineProperty(this, '' + index, { 6881 get: function get() { 6882 return this.cues_[index]; 6883 } 6884 }); 6885 } 6886 }; 6887 6888 if (oldLength < l) { 6889 i = oldLength; 6890 6891 for (; i < l; i++) { 6892 defineProp.call(this, i); 6893 } 6894 } 6895 } 6896 /** 6897 * Get a `TextTrackCue` that is currently in the `TextTrackCueList` by id. 6898 * 6899 * @param {string} id 6900 * The id of the cue that should be searched for. 6901 * 6902 * @return {TextTrackCueList~TextTrackCue|null} 6903 * A single cue or null if none was found. 6904 */ 6905 ; 6906 6907 _proto.getCueById = function getCueById(id) { 6908 var result = null; 6909 6910 for (var i = 0, l = this.length; i < l; i++) { 6911 var cue = this[i]; 6912 6913 if (cue.id === id) { 6914 result = cue; 6915 break; 6916 } 6917 } 6918 6919 return result; 6920 }; 6921 6922 return TextTrackCueList; 6923 }(); 6924 6925 /** 6926 * @file track-kinds.js 6927 */ 6928 6929 /** 6930 * All possible `VideoTrackKind`s 6931 * 6932 * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-videotrack-kind 6933 * @typedef VideoTrack~Kind 6934 * @enum 6935 */ 6936 var VideoTrackKind = { 6937 alternative: 'alternative', 6938 captions: 'captions', 6939 main: 'main', 6940 sign: 'sign', 6941 subtitles: 'subtitles', 6942 commentary: 'commentary' 6943 }; 6944 /** 6945 * All possible `AudioTrackKind`s 6946 * 6947 * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-audiotrack-kind 6948 * @typedef AudioTrack~Kind 6949 * @enum 6950 */ 6951 6952 var AudioTrackKind = { 6953 'alternative': 'alternative', 6954 'descriptions': 'descriptions', 6955 'main': 'main', 6956 'main-desc': 'main-desc', 6957 'translation': 'translation', 6958 'commentary': 'commentary' 6959 }; 6960 /** 6961 * All possible `TextTrackKind`s 6962 * 6963 * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-texttrack-kind 6964 * @typedef TextTrack~Kind 6965 * @enum 6966 */ 6967 6968 var TextTrackKind = { 6969 subtitles: 'subtitles', 6970 captions: 'captions', 6971 descriptions: 'descriptions', 6972 chapters: 'chapters', 6973 metadata: 'metadata' 6974 }; 6975 /** 6976 * All possible `TextTrackMode`s 6977 * 6978 * @see https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackmode 6979 * @typedef TextTrack~Mode 6980 * @enum 6981 */ 6982 6983 var TextTrackMode = { 6984 disabled: 'disabled', 6985 hidden: 'hidden', 6986 showing: 'showing' 6987 }; 6988 6989 /** 6990 * A Track class that contains all of the common functionality for {@link AudioTrack}, 6991 * {@link VideoTrack}, and {@link TextTrack}. 6992 * 6993 * > Note: This class should not be used directly 6994 * 6995 * @see {@link https://html.spec.whatwg.org/multipage/embedded-content.html} 6996 * @extends EventTarget 6997 * @abstract 6998 */ 6999 7000 var Track = 7001 /*#__PURE__*/ 7002 function (_EventTarget) { 7003 _inheritsLoose(Track, _EventTarget); 7004 7005 /** 7006 * Create an instance of this class. 7007 * 7008 * @param {Object} [options={}] 7009 * Object of option names and values 7010 * 7011 * @param {string} [options.kind=''] 7012 * A valid kind for the track type you are creating. 7013 * 7014 * @param {string} [options.id='vjs_track_' + Guid.newGUID()] 7015 * A unique id for this AudioTrack. 7016 * 7017 * @param {string} [options.label=''] 7018 * The menu label for this track. 7019 * 7020 * @param {string} [options.language=''] 7021 * A valid two character language code. 7022 * 7023 * @abstract 7024 */ 7025 function Track(options) { 7026 var _this; 7027 7028 if (options === void 0) { 7029 options = {}; 7030 } 7031 7032 _this = _EventTarget.call(this) || this; 7033 var trackProps = { 7034 id: options.id || 'vjs_track_' + newGUID(), 7035 kind: options.kind || '', 7036 label: options.label || '', 7037 language: options.language || '' 7038 }; 7039 /** 7040 * @memberof Track 7041 * @member {string} id 7042 * The id of this track. Cannot be changed after creation. 7043 * @instance 7044 * 7045 * @readonly 7046 */ 7047 7048 /** 7049 * @memberof Track 7050 * @member {string} kind 7051 * The kind of track that this is. Cannot be changed after creation. 7052 * @instance 7053 * 7054 * @readonly 7055 */ 7056 7057 /** 7058 * @memberof Track 7059 * @member {string} label 7060 * The label of this track. Cannot be changed after creation. 7061 * @instance 7062 * 7063 * @readonly 7064 */ 7065 7066 /** 7067 * @memberof Track 7068 * @member {string} language 7069 * The two letter language code for this track. Cannot be changed after 7070 * creation. 7071 * @instance 7072 * 7073 * @readonly 7074 */ 7075 7076 var _loop = function _loop(key) { 7077 Object.defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), key, { 7078 get: function get() { 7079 return trackProps[key]; 7080 }, 7081 set: function set() {} 7082 }); 7083 }; 7084 7085 for (var key in trackProps) { 7086 _loop(key); 7087 } 7088 7089 return _this; 7090 } 7091 7092 return Track; 7093 }(EventTarget); 7094 7095 /** 7096 * @file url.js 7097 * @module url 7098 */ 7099 /** 7100 * @typedef {Object} url:URLObject 7101 * 7102 * @property {string} protocol 7103 * The protocol of the url that was parsed. 7104 * 7105 * @property {string} hostname 7106 * The hostname of the url that was parsed. 7107 * 7108 * @property {string} port 7109 * The port of the url that was parsed. 7110 * 7111 * @property {string} pathname 7112 * The pathname of the url that was parsed. 7113 * 7114 * @property {string} search 7115 * The search query of the url that was parsed. 7116 * 7117 * @property {string} hash 7118 * The hash of the url that was parsed. 7119 * 7120 * @property {string} host 7121 * The host of the url that was parsed. 7122 */ 7123 7124 /** 7125 * Resolve and parse the elements of a URL. 7126 * 7127 * @function 7128 * @param {String} url 7129 * The url to parse 7130 * 7131 * @return {url:URLObject} 7132 * An object of url details 7133 */ 7134 7135 var parseUrl = function parseUrl(url) { 7136 var props = ['protocol', 'hostname', 'port', 'pathname', 'search', 'hash', 'host']; // add the url to an anchor and let the browser parse the URL 7137 7138 var a = document.createElement('a'); 7139 a.href = url; // IE8 (and 9?) Fix
vendor: 4,092 bytes, lines 7140-7270
7140 // ie8 doesn't parse the URL correctly until the anchor is actually 7141 // added to the body, and an innerHTML is needed to trigger the parsing 7142 7143 var addToBody = a.host === '' && a.protocol !== 'file:'; 7144 var div; 7145 7146 if (addToBody) { 7147 div = document.createElement('div'); 7148 div.innerHTML = "<a href=\"" + url + "\"></a>"; 7149 a = div.firstChild; // prevent the div from affecting layout 7150 7151 div.setAttribute('style', 'display:none; position:absolute;'); 7152 document.body.appendChild(div); 7153 } // Copy the specific URL properties to a new object 7154 // This is also needed for IE8 because the anchor loses its 7155 // properties when it's removed from the dom 7156 7157 7158 var details = {}; 7159 7160 for (var i = 0; i < props.length; i++) { 7161 details[props[i]] = a[props[i]]; 7162 } // IE9 adds the port to the host property unlike everyone else. If 7163 // a port identifier is added for standard ports, strip it. 7164 7165 7166 if (details.protocol === 'http:') { 7167 details.host = details.host.replace(/:80$/, ''); 7168 } 7169 7170 if (details.protocol === 'https:') { 7171 details.host = details.host.replace(/:443$/, ''); 7172 } 7173 7174 if (!details.protocol) { 7175 details.protocol = window$1.location.protocol; 7176 } 7177 7178 if (addToBody) { 7179 document.body.removeChild(div); 7180 } 7181 7182 return details; 7183 }; 7184 /** 7185 * Get absolute version of relative URL. Used to tell Flash the correct URL. 7186 * 7187 * @function 7188 * @param {string} url 7189 * URL to make absolute 7190 * 7191 * @return {string} 7192 * Absolute URL 7193 * 7194 * @see http://stackoverflow.com/questions/470832/getting-an-absolute-url-from-a-relative-one-ie6-issue 7195 */ 7196 7197 var getAbsoluteURL = function getAbsoluteURL(url) { 7198 // Check if absolute URL 7199 if (!url.match(/^https?:\/\//)) { 7200 // Convert to absolute URL. Flash hosted off-site needs an absolute URL. 7201 var div = document.createElement('div'); 7202 div.innerHTML = "<a href=\"" + url + "\">x</a>"; 7203 url = div.firstChild.href; 7204 } 7205 7206 return url; 7207 }; 7208 /** 7209 * Returns the extension of the passed file name. It will return an empty string 7210 * if passed an invalid path. 7211 * 7212 * @function 7213 * @param {string} path 7214 * The fileName path like '/path/to/file.mp4' 7215 * 7216 * @return {string} 7217 * The extension in lower case or an empty string if no 7218 * extension could be found. 7219 */ 7220 7221 var getFileExtension = function getFileExtension(path) { 7222 if (typeof path === 'string') { 7223 var splitPathRe = /^(\/?)([\s\S]*?)((?:\.{1,2}|[^\/]+?)(\.([^\.\/\?]+)))(?:[\/]*|[\?].*)$/i; 7224 var pathParts = splitPathRe.exec(path); 7225 7226 if (pathParts) { 7227 return pathParts.pop().toLowerCase(); 7228 } 7229 } 7230 7231 return ''; 7232 }; 7233 /** 7234 * Returns whether the url passed is a cross domain request or not. 7235 * 7236 * @function 7237 * @param {string} url 7238 * The url to check. 7239 * 7240 * @return {boolean} 7241 * Whether it is a cross domain request or not. 7242 */ 7243 7244 var isCrossOrigin = function isCrossOrigin(url) { 7245 var winLoc = window$1.location; 7246 var urlInfo = parseUrl(url); // IE8 protocol relative urls will return ':' for protocol 7247 7248 var srcProtocol = urlInfo.protocol === ':' ? winLoc.protocol : urlInfo.protocol; // Check if url is for another domain/origin 7249 // IE8 doesn't know location.origin, so we won't rely on it here 7250 7251 var crossOrigin = srcProtocol + urlInfo.host !== winLoc.protocol + winLoc.host; 7252 return crossOrigin; 7253 }; 7254 7255 var Url = /*#__PURE__*/Object.freeze({ 7256 parseUrl: parseUrl, 7257 getAbsoluteURL: getAbsoluteURL, 7258 getFileExtension: getFileExtension, 7259 isCrossOrigin: isCrossOrigin 7260 }); 7261 7262 var isFunction_1 = isFunction; 7263 var toString$1 = Object.prototype.toString; 7264 7265 function isFunction(fn) { 7266 var string = toString$1.call(fn); 7267 return string === '[object Function]' || typeof fn === 'function' && string !== '[object RegExp]' || typeof window !== 'undefined' && ( // IE8 and below 7268 fn === window.setTimeout || fn === window.alert || fn === window.confirm || fn === window.prompt); 7269 } 7270
7271 var trim_1 = createCommonjsModule(function (module, exports) { 7272 exports = module.exports = trim; 7273 7274 function trim(str) { 7275 return str.replace(/^\s*|\s*$/g, ''); 7276 } 7277 7278 exports.left = function (str) { 7279 return str.replace(/^\s*/, ''); 7280 }; 7281 7282 exports.right = function (str) { 7283 return str.replace(/\s*$/, ''); 7284 }; 7285 }); 7286 var trim_2 = trim_1.left; 7287 var trim_3 = trim_1.right; 7288 7289 var fnToStr = Function.prototype.toString; 7290 var constructorRegex = /^\s*class\b/; 7291 7292 var isES6ClassFn = function isES6ClassFunction(value) { 7293 try { 7294 var fnStr = fnToStr.call(value); 7295 return constructorRegex.test(fnStr); 7296 } catch (e) { 7297 return false; // not a function 7298 } 7299 }; 7300 7301 var tryFunctionObject = function tryFunctionToStr(value) { 7302 try { 7303 if (isES6ClassFn(value)) { 7304 return false; 7305 } 7306 7307 fnToStr.call(value); 7308 return true; 7309 } catch (e) { 7310 return false; 7311 } 7312 }; 7313 7314 var toStr = Object.prototype.toString; 7315 var fnClass = '[object Function]'; 7316 var genClass = '[object GeneratorFunction]'; 7317 var hasToStringTag = typeof Symbol === 'function' && typeof Symbol.toStringTag === 'symbol'; 7318 7319 var isCallable = function isCallable(value) { 7320 if (!value) { 7321 return false; 7322 } 7323 7324 if (typeof value !== 'function' && typeof value !== 'object') { 7325 return false; 7326 } 7327 7328 if (typeof value === 'function' && !value.prototype) { 7329 return true; 7330 } 7331 7332 if (hasToStringTag) { 7333 return tryFunctionObject(value); 7334 } 7335 7336 if (isES6ClassFn(value)) { 7337 return false; 7338 } 7339 7340 var strClass = toStr.call(value); 7341 return strClass === fnClass || strClass === genClass; 7342 }; 7343 7344 var toStr$1 = Object.prototype.toString; 7345 var hasOwnProperty = Object.prototype.hasOwnProperty; 7346 7347 var forEachArray = function forEachArray(array, iterator, receiver) { 7348 for (var i = 0, len = array.length; i < len; i++) { 7349 if (hasOwnProperty.call(array, i)) { 7350 if (receiver == null) { 7351 iterator(array[i], i, array); 7352 } else { 7353 iterator.call(receiver, array[i], i, array); 7354 } 7355 } 7356 } 7357 }; 7358 7359 var forEachString = function forEachString(string, iterator, receiver) { 7360 for (var i = 0, len = string.length; i < len; i++) { 7361 // no such thing as a sparse string. 7362 if (receiver == null) { 7363 iterator(string.charAt(i), i, string); 7364 } else { 7365 iterator.call(receiver, string.charAt(i), i, string); 7366 } 7367 } 7368 }; 7369 7370 var forEachObject = function forEachObject(object, iterator, receiver) { 7371 for (var k in object) { 7372 if (hasOwnProperty.call(object, k)) { 7373 if (receiver == null) { 7374 iterator(object[k], k, object); 7375 } else { 7376 iterator.call(receiver, object[k], k, object); 7377 } 7378 } 7379 } 7380 }; 7381 7382 var forEach = function forEach(list, iterator, thisArg) { 7383 if (!isCallable(iterator)) { 7384 throw new TypeError('iterator must be a function'); 7385 } 7386 7387 var receiver; 7388 7389 if (arguments.length >= 3) { 7390 receiver = thisArg; 7391 } 7392 7393 if (toStr$1.call(list) === '[object Array]') { 7394 forEachArray(list, iterator, receiver); 7395 } else if (typeof list === 'string') { 7396 forEachString(list, iterator, receiver); 7397 } else { 7398 forEachObject(list, iterator, receiver); 7399 } 7400 }; 7401 7402 var forEach_1 = forEach; 7403 7404 var isArray = function isArray(arg) { 7405 return Object.prototype.toString.call(arg) === '[object Array]'; 7406 }; 7407 7408 var parseHeaders = function parseHeaders(headers) { 7409 if (!headers) return {}; 7410 var result = {}; 7411 forEach_1(trim_1(headers).split('\n'), function (row) { 7412 var index = row.indexOf(':'), 7413 key = trim_1(row.slice(0, index)).toLowerCase(), 7414 value = trim_1(row.slice(index + 1)); 7415 7416 if (typeof result[key] === 'undefined') { 7417 result[key] = value; 7418 } else if (isArray(result[key])) { 7419 result[key].push(value); 7420 } else { 7421 result[key] = [result[key], value]; 7422 } 7423 }); 7424 return result; 7425 }; 7426 7427 var immutable = extend; 7428 var hasOwnProperty$1 = Object.prototype.hasOwnProperty; 7429 7430 function extend() { 7431 var target = {}; 7432 7433 for (var i = 0; i < arguments.length; i++) { 7434 var source = arguments[i]; 7435 7436 for (var key in source) { 7437 if (hasOwnProperty$1.call(source, key)) { 7438 target[key] = source[key]; 7439 } 7440 } 7441 } 7442 7443 return target; 7444 } 7445 7446 var xhr = createXHR; 7447 createXHR.XMLHttpRequest = window$1.XMLHttpRequest || noop; 7448 createXHR.XDomainRequest = "withCredentials" in new createXHR.XMLHttpRequest() ? createXHR.XMLHttpRequest : window$1.XDomainRequest; 7449 forEachArray$1(["get", "put", "post", "patch", "head", "delete"], function (method) { 7450 createXHR[method === "delete" ? "del" : method] = function (uri, options, callback) { 7451 options = initParams(uri, options, callback); 7452 options.method = method.toUpperCase(); 7453 return _createXHR(options); 7454 }; 7455 }); 7456 7457 function forEachArray$1(array, iterator) { 7458 for (var i = 0; i < array.length; i++) { 7459 iterator(array[i]); 7460 } 7461 } 7462 7463 function isEmpty(obj) { 7464 for (var i in obj) { 7465 if (obj.hasOwnProperty(i)) return false;
vendor: 5,374 bytes, lines 7466-7669
7466 } 7467 7468 return true; 7469 } 7470 7471 function initParams(uri, options, callback) { 7472 var params = uri; 7473 7474 if (isFunction_1(options)) { 7475 callback = options; 7476 7477 if (typeof uri === "string") { 7478 params = { 7479 uri: uri 7480 }; 7481 } 7482 } else { 7483 params = immutable(options, { 7484 uri: uri 7485 }); 7486 } 7487 7488 params.callback = callback; 7489 return params; 7490 } 7491 7492 function createXHR(uri, options, callback) { 7493 options = initParams(uri, options, callback); 7494 return _createXHR(options); 7495 } 7496 7497 function _createXHR(options) { 7498 if (typeof options.callback === "undefined") { 7499 throw new Error("callback argument missing"); 7500 } 7501 7502 var called = false; 7503 7504 var callback = function cbOnce(err, response, body) { 7505 if (!called) { 7506 called = true; 7507 options.callback(err, response, body); 7508 } 7509 }; 7510 7511 function readystatechange() { 7512 if (xhr.readyState === 4) { 7513 setTimeout(loadFunc, 0); 7514 } 7515 } 7516 7517 function getBody() { 7518 // Chrome with requestType=blob throws errors arround when even testing access to responseText 7519 var body = undefined; 7520 7521 if (xhr.response) { 7522 body = xhr.response; 7523 } else { 7524 body = xhr.responseText || getXml(xhr); 7525 } 7526 7527 if (isJson) { 7528 try { 7529 body = JSON.parse(body); 7530 } catch (e) {} 7531 } 7532 7533 return body; 7534 } 7535 7536 function errorFunc(evt) { 7537 clearTimeout(timeoutTimer); 7538 7539 if (!(evt instanceof Error)) { 7540 evt = new Error("" + (evt || "Unknown XMLHttpRequest Error")); 7541 } 7542 7543 evt.statusCode = 0; 7544 return callback(evt, failureResponse); 7545 } // will load the data & process the response in a special response object 7546 7547 7548 function loadFunc() { 7549 if (aborted) return; 7550 var status; 7551 clearTimeout(timeoutTimer); 7552 7553 if (options.useXDR && xhr.status === undefined) { 7554 //IE8 CORS GET successful response doesn't have a status field, but body is fine 7555 status = 200; 7556 } else { 7557 status = xhr.status === 1223 ? 204 : xhr.status; 7558 } 7559 7560 var response = failureResponse; 7561 var err = null; 7562 7563 if (status !== 0) { 7564 response = { 7565 body: getBody(), 7566 statusCode: status, 7567 method: method, 7568 headers: {}, 7569 url: uri, 7570 rawRequest: xhr 7571 }; 7572 7573 if (xhr.getAllResponseHeaders) { 7574 //remember xhr can in fact be XDR for CORS in IE 7575 response.headers = parseHeaders(xhr.getAllResponseHeaders()); 7576 } 7577 } else { 7578 err = new Error("Internal XMLHttpRequest Error"); 7579 } 7580 7581 return callback(err, response, response.body); 7582 } 7583 7584 var xhr = options.xhr || null; 7585 7586 if (!xhr) { 7587 if (options.cors || options.useXDR) { 7588 xhr = new createXHR.XDomainRequest(); 7589 } else { 7590 xhr = new createXHR.XMLHttpRequest(); 7591 } 7592 } 7593 7594 var key; 7595 var aborted; 7596 var uri = xhr.url = options.uri || options.url; 7597 var method = xhr.method = options.method || "GET"; 7598 var body = options.body || options.data; 7599 var headers = xhr.headers = options.headers || {}; 7600 var sync = !!options.sync; 7601 var isJson = false; 7602 var timeoutTimer; 7603 var failureResponse = { 7604 body: undefined, 7605 headers: {}, 7606 statusCode: 0, 7607 method: method, 7608 url: uri, 7609 rawRequest: xhr 7610 }; 7611 7612 if ("json" in options && options.json !== false) { 7613 isJson = true; 7614 headers["accept"] || headers["Accept"] || (headers["Accept"] = "application/json"); //Don't override existing accept header declared by user 7615 7616 if (method !== "GET" && method !== "HEAD") { 7617 headers["content-type"] || headers["Content-Type"] || (headers["Content-Type"] = "application/json"); //Don't override existing accept header declared by user 7618 7619 body = JSON.stringify(options.json === true ? body : options.json); 7620 } 7621 } 7622 7623 xhr.onreadystatechange = readystatechange; 7624 xhr.onload = loadFunc; 7625 xhr.onerror = errorFunc; // IE9 must have onprogress be set to a unique function. 7626 7627 xhr.onprogress = function () {// IE must die 7628 }; 7629 7630 xhr.onabort = function () { 7631 aborted = true; 7632 }; 7633 7634 xhr.ontimeout = errorFunc; 7635 xhr.open(method, uri, !sync, options.username, options.password); //has to be after open 7636 7637 if (!sync) { 7638 xhr.withCredentials = !!options.withCredentials; 7639 } // Cannot set timeout with sync request 7640 // not setting timeout on the xhr object, because of old webkits etc. not handling that correctly 7641 // both npm's request and jquery 1.x use this kind of timeout, so this is being consistent 7642 7643 7644 if (!sync && options.timeout > 0) { 7645 timeoutTimer = setTimeout(function () { 7646 if (aborted) return; 7647 aborted = true; //IE9 may still call readystatechange 7648 7649 xhr.abort("timeout"); 7650 var e = new Error("XMLHttpRequest timeout"); 7651 e.code = "ETIMEDOUT"; 7652 errorFunc(e); 7653 }, options.timeout); 7654 } 7655 7656 if (xhr.setRequestHeader) { 7657 for (key in headers) { 7658 if (headers.hasOwnProperty(key)) { 7659 xhr.setRequestHeader(key, headers[key]); 7660 } 7661 } 7662 } else if (options.headers && !isEmpty(options.headers)) { 7663 throw new Error("Headers cannot be set on an XDomainRequest object"); 7664 } 7665 7666 if ("responseType" in options) { 7667 xhr.responseType = options.responseType; 7668 } 7669
vendor: 5,015 bytes, lines 7670-7855
7670 if ("beforeSend" in options && typeof options.beforeSend === "function") { 7671 options.beforeSend(xhr); 7672 } // Microsoft Edge browser sends "undefined" when send is called with undefined value. 7673 // XMLHttpRequest spec says to pass null as body to indicate no body 7674 // See https://github.com/naugtur/xhr/issues/100. 7675 7676 7677 xhr.send(body || null); 7678 return xhr; 7679 } 7680 7681 function getXml(xhr) { 7682 if (xhr.responseType === "document") { 7683 return xhr.responseXML; 7684 } 7685 7686 var firefoxBugTakenEffect = xhr.responseXML && xhr.responseXML.documentElement.nodeName === "parsererror"; 7687 7688 if (xhr.responseType === "" && !firefoxBugTakenEffect) { 7689 return xhr.responseXML; 7690 } 7691 7692 return null; 7693 } 7694 7695 function noop() {} 7696 7697 /** 7698 * Takes a webvtt file contents and parses it into cues 7699 * 7700 * @param {string} srcContent 7701 * webVTT file contents 7702 * 7703 * @param {TextTrack} track 7704 * TextTrack to add cues to. Cues come from the srcContent. 7705 * 7706 * @private 7707 */ 7708 7709 var parseCues = function parseCues(srcContent, track) { 7710 var parser = new window$1.WebVTT.Parser(window$1, window$1.vttjs, window$1.WebVTT.StringDecoder()); 7711 var errors = []; 7712 7713 parser.oncue = function (cue) { 7714 track.addCue(cue); 7715 }; 7716 7717 parser.onparsingerror = function (error) { 7718 errors.push(error); 7719 }; 7720 7721 parser.onflush = function () { 7722 track.trigger({ 7723 type: 'loadeddata', 7724 target: track 7725 }); 7726 }; 7727 7728 parser.parse(srcContent); 7729 7730 if (errors.length > 0) { 7731 if (window$1.console && window$1.console.groupCollapsed) { 7732 window$1.console.groupCollapsed("Text Track parsing errors for " + track.src); 7733 } 7734 7735 errors.forEach(function (error) { 7736 return log.error(error); 7737 }); 7738 7739 if (window$1.console && window$1.console.groupEnd) { 7740 window$1.console.groupEnd(); 7741 } 7742 } 7743 7744 parser.flush(); 7745 }; 7746 /** 7747 * Load a `TextTrack` from a specified url. 7748 * 7749 * @param {string} src 7750 * Url to load track from. 7751 * 7752 * @param {TextTrack} track 7753 * Track to add cues to. Comes from the content at the end of `url`. 7754 * 7755 * @private 7756 */ 7757 7758 7759 var loadTrack = function loadTrack(src, track) { 7760 var opts = { 7761 uri: src 7762 }; 7763 var crossOrigin = isCrossOrigin(src); 7764 7765 if (crossOrigin) { 7766 opts.cors = crossOrigin; 7767 } 7768 7769 xhr(opts, bind(this, function (err, response, responseBody) { 7770 if (err) { 7771 return log.error(err, response); 7772 } 7773 7774 track.loaded_ = true; // Make sure that vttjs has loaded, otherwise, wait till it finished loading 7775 // NOTE: this is only used for the alt/video.novtt.js build 7776 7777 if (typeof window$1.WebVTT !== 'function') { 7778 if (track.tech_) { 7779 // to prevent use before define eslint error, we define loadHandler 7780 // as a let here 7781 var loadHandler; 7782 7783 var errorHandler = function errorHandler() { 7784 log.error("vttjs failed to load, stopping trying to process " + track.src); 7785 track.tech_.off('vttjsloaded', loadHandler); 7786 }; 7787 7788 loadHandler = function loadHandler() { 7789 track.tech_.off('vttjserror', errorHandler); 7790 return parseCues(responseBody, track); 7791 }; 7792 7793 track.tech_.one('vttjsloaded', loadHandler); 7794 track.tech_.one('vttjserror', errorHandler); 7795 } 7796 } else { 7797 parseCues(responseBody, track); 7798 } 7799 })); 7800 }; 7801 /** 7802 * A representation of a single `TextTrack`. 7803 * 7804 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrack} 7805 * @extends Track 7806 */ 7807 7808 7809 var TextTrack = 7810 /*#__PURE__*/ 7811 function (_Track) { 7812 _inheritsLoose(TextTrack, _Track); 7813 7814 /** 7815 * Create an instance of this class. 7816 * 7817 * @param {Object} options={} 7818 * Object of option names and values 7819 * 7820 * @param {Tech} options.tech 7821 * A reference to the tech that owns this TextTrack. 7822 * 7823 * @param {TextTrack~Kind} [options.kind='subtitles'] 7824 * A valid text track kind. 7825 * 7826 * @param {TextTrack~Mode} [options.mode='disabled'] 7827 * A valid text track mode. 7828 * 7829 * @param {string} [options.id='vjs_track_' + Guid.newGUID()] 7830 * A unique id for this TextTrack. 7831 * 7832 * @param {string} [options.label=''] 7833 * The menu label for this track. 7834 * 7835 * @param {string} [options.language=''] 7836 * A valid two character language code. 7837 * 7838 * @param {string} [options.srclang=''] 7839 * A valid two character language code. An alternative, but deprioritized 7840 * version of `options.language` 7841 * 7842 * @param {string} [options.src] 7843 * A url to TextTrack cues. 7844 * 7845 * @param {boolean} [options.default] 7846 * If this track should default to on or off. 7847 */ 7848 function TextTrack(options) { 7849 var _this; 7850 7851 if (options === void 0) { 7852 options = {}; 7853 } 7854 7855 if (!options.tech) {
vendor: 4,481 bytes, lines 7856-8004
7856 throw new Error('A tech was not provided.'); 7857 } 7858 7859 var settings = mergeOptions(options, { 7860 kind: TextTrackKind[options.kind] || 'subtitles', 7861 language: options.language || options.srclang || '' 7862 }); 7863 var mode = TextTrackMode[settings.mode] || 'disabled'; 7864 var default_ = settings.default; 7865 7866 if (settings.kind === 'metadata' || settings.kind === 'chapters') { 7867 mode = 'hidden'; 7868 } 7869 7870 _this = _Track.call(this, settings) || this; 7871 _this.tech_ = settings.tech; 7872 _this.cues_ = []; 7873 _this.activeCues_ = []; 7874 var cues = new TextTrackCueList(_this.cues_); 7875 var activeCues = new TextTrackCueList(_this.activeCues_); 7876 var changed = false; 7877 var timeupdateHandler = bind(_assertThisInitialized(_assertThisInitialized(_this)), function () { 7878 // Accessing this.activeCues for the side-effects of updating itself 7879 // due to its nature as a getter function. Do not remove or cues will 7880 // stop updating! 7881 // Use the setter to prevent deletion from uglify (pure_getters rule) 7882 this.activeCues = this.activeCues; 7883 7884 if (changed) { 7885 this.trigger('cuechange'); 7886 changed = false; 7887 } 7888 }); 7889 7890 if (mode !== 'disabled') { 7891 _this.tech_.ready(function () { 7892 _this.tech_.on('timeupdate', timeupdateHandler); 7893 }, true); 7894 } 7895 7896 Object.defineProperties(_assertThisInitialized(_assertThisInitialized(_this)), { 7897 /** 7898 * @memberof TextTrack 7899 * @member {boolean} default 7900 * If this track was set to be on or off by default. Cannot be changed after 7901 * creation. 7902 * @instance 7903 * 7904 * @readonly 7905 */ 7906 default: { 7907 get: function get() { 7908 return default_; 7909 }, 7910 set: function set() {} 7911 }, 7912 7913 /** 7914 * @memberof TextTrack 7915 * @member {string} mode 7916 * Set the mode of this TextTrack to a valid {@link TextTrack~Mode}. Will 7917 * not be set if setting to an invalid mode. 7918 * @instance 7919 * 7920 * @fires TextTrack#modechange 7921 */ 7922 mode: { 7923 get: function get() { 7924 return mode; 7925 }, 7926 set: function set(newMode) { 7927 var _this2 = this; 7928 7929 if (!TextTrackMode[newMode]) { 7930 return; 7931 } 7932 7933 mode = newMode; 7934 7935 if (mode !== 'disabled') { 7936 this.tech_.ready(function () { 7937 _this2.tech_.on('timeupdate', timeupdateHandler); 7938 }, true); 7939 } else { 7940 this.tech_.off('timeupdate', timeupdateHandler); 7941 } 7942 /** 7943 * An event that fires when mode changes on this track. This allows 7944 * the TextTrackList that holds this track to act accordingly. 7945 * 7946 * > Note: This is not part of the spec! 7947 * 7948 * @event TextTrack#modechange 7949 * @type {EventTarget~Event} 7950 */ 7951 7952 7953 this.trigger('modechange'); 7954 } 7955 }, 7956 7957 /** 7958 * @memberof TextTrack 7959 * @member {TextTrackCueList} cues 7960 * The text track cue list for this TextTrack. 7961 * @instance 7962 */ 7963 cues: { 7964 get: function get() { 7965 if (!this.loaded_) { 7966 return null; 7967 } 7968 7969 return cues; 7970 }, 7971 set: function set() {} 7972 }, 7973 7974 /** 7975 * @memberof TextTrack 7976 * @member {TextTrackCueList} activeCues 7977 * The list text track cues that are currently active for this TextTrack. 7978 * @instance 7979 */ 7980 activeCues: { 7981 get: function get() { 7982 if (!this.loaded_) { 7983 return null; 7984 } // nothing to do 7985 7986 7987 if (this.cues.length === 0) { 7988 return activeCues; 7989 } 7990 7991 var ct = this.tech_.currentTime(); 7992 var active = []; 7993 7994 for (var i = 0, l = this.cues.length; i < l; i++) { 7995 var cue = this.cues[i]; 7996 7997 if (cue.startTime <= ct && cue.endTime >= ct) { 7998 active.push(cue); 7999 } else if (cue.startTime === cue.endTime && cue.startTime <= ct && cue.startTime + 0.5 >= ct) { 8000 active.push(cue); 8001 } 8002 } 8003 8004 changed = false;
vendor: 12,090 bytes, lines 8005-8440
8005 8006 if (active.length !== this.activeCues_.length) { 8007 changed = true; 8008 } else { 8009 for (var _i = 0; _i < active.length; _i++) { 8010 if (this.activeCues_.indexOf(active[_i]) === -1) { 8011 changed = true; 8012 } 8013 } 8014 } 8015 8016 this.activeCues_ = active; 8017 activeCues.setCues_(this.activeCues_); 8018 return activeCues; 8019 }, 8020 // /!\ Keep this setter empty (see the timeupdate handler above) 8021 set: function set() {} 8022 } 8023 }); 8024 8025 if (settings.src) { 8026 _this.src = settings.src; 8027 loadTrack(settings.src, _assertThisInitialized(_assertThisInitialized(_this))); 8028 } else { 8029 _this.loaded_ = true; 8030 } 8031 8032 return _this; 8033 } 8034 /** 8035 * Add a cue to the internal list of cues. 8036 * 8037 * @param {TextTrack~Cue} cue 8038 * The cue to add to our internal list 8039 */ 8040 8041 8042 var _proto = TextTrack.prototype; 8043 8044 _proto.addCue = function addCue(originalCue) { 8045 var cue = originalCue; 8046 8047 if (window$1.vttjs && !(originalCue instanceof window$1.vttjs.VTTCue)) { 8048 cue = new window$1.vttjs.VTTCue(originalCue.startTime, originalCue.endTime, originalCue.text); 8049 8050 for (var prop in originalCue) { 8051 if (!(prop in cue)) { 8052 cue[prop] = originalCue[prop]; 8053 } 8054 } // make sure that `id` is copied over 8055 8056 8057 cue.id = originalCue.id; 8058 cue.originalCue_ = originalCue; 8059 } 8060 8061 var tracks = this.tech_.textTracks(); 8062 8063 for (var i = 0; i < tracks.length; i++) { 8064 if (tracks[i] !== this) { 8065 tracks[i].removeCue(cue); 8066 } 8067 } 8068 8069 this.cues_.push(cue); 8070 this.cues.setCues_(this.cues_); 8071 } 8072 /** 8073 * Remove a cue from our internal list 8074 * 8075 * @param {TextTrack~Cue} removeCue 8076 * The cue to remove from our internal list 8077 */ 8078 ; 8079 8080 _proto.removeCue = function removeCue(_removeCue) { 8081 var i = this.cues_.length; 8082 8083 while (i--) { 8084 var cue = this.cues_[i]; 8085 8086 if (cue === _removeCue || cue.originalCue_ && cue.originalCue_ === _removeCue) { 8087 this.cues_.splice(i, 1); 8088 this.cues.setCues_(this.cues_); 8089 break; 8090 } 8091 } 8092 }; 8093 8094 return TextTrack; 8095 }(Track); 8096 /** 8097 * cuechange - One or more cues in the track have become active or stopped being active. 8098 */ 8099 8100 8101 TextTrack.prototype.allowedEvents_ = { 8102 cuechange: 'cuechange' 8103 }; 8104 8105 /** 8106 * A representation of a single `AudioTrack`. If it is part of an {@link AudioTrackList} 8107 * only one `AudioTrack` in the list will be enabled at a time. 8108 * 8109 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotrack} 8110 * @extends Track 8111 */ 8112 8113 var AudioTrack = 8114 /*#__PURE__*/ 8115 function (_Track) { 8116 _inheritsLoose(AudioTrack, _Track); 8117 8118 /** 8119 * Create an instance of this class. 8120 * 8121 * @param {Object} [options={}] 8122 * Object of option names and values 8123 * 8124 * @param {AudioTrack~Kind} [options.kind=''] 8125 * A valid audio track kind 8126 * 8127 * @param {string} [options.id='vjs_track_' + Guid.newGUID()] 8128 * A unique id for this AudioTrack. 8129 * 8130 * @param {string} [options.label=''] 8131 * The menu label for this track. 8132 * 8133 * @param {string} [options.language=''] 8134 * A valid two character language code. 8135 * 8136 * @param {boolean} [options.enabled] 8137 * If this track is the one that is currently playing. If this track is part of 8138 * an {@link AudioTrackList}, only one {@link AudioTrack} will be enabled. 8139 */ 8140 function AudioTrack(options) { 8141 var _this; 8142 8143 if (options === void 0) { 8144 options = {}; 8145 } 8146 8147 var settings = mergeOptions(options, { 8148 kind: AudioTrackKind[options.kind] || '' 8149 }); 8150 _this = _Track.call(this, settings) || this; 8151 var enabled = false; 8152 /** 8153 * @memberof AudioTrack 8154 * @member {boolean} enabled 8155 * If this `AudioTrack` is enabled or not. When setting this will 8156 * fire {@link AudioTrack#enabledchange} if the state of enabled is changed. 8157 * @instance 8158 * 8159 * @fires VideoTrack#selectedchange 8160 */ 8161 8162 Object.defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), 'enabled', { 8163 get: function get() { 8164 return enabled; 8165 }, 8166 set: function set(newEnabled) { 8167 // an invalid or unchanged value 8168 if (typeof newEnabled !== 'boolean' || newEnabled === enabled) { 8169 return; 8170 } 8171 8172 enabled = newEnabled; 8173 /** 8174 * An event that fires when enabled changes on this track. This allows 8175 * the AudioTrackList that holds this track to act accordingly. 8176 * 8177 * > Note: This is not part of the spec! Native tracks will do 8178 * this internally without an event. 8179 * 8180 * @event AudioTrack#enabledchange 8181 * @type {EventTarget~Event} 8182 */ 8183 8184 this.trigger('enabledchange'); 8185 } 8186 }); // if the user sets this track to selected then 8187 // set selected to that true value otherwise 8188 // we keep it false 8189 8190 if (settings.enabled) { 8191 _this.enabled = settings.enabled; 8192 } 8193 8194 _this.loaded_ = true; 8195 return _this; 8196 } 8197 8198 return AudioTrack; 8199 }(Track); 8200 8201 /** 8202 * A representation of a single `VideoTrack`. 8203 * 8204 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#videotrack} 8205 * @extends Track 8206 */ 8207 8208 var VideoTrack = 8209 /*#__PURE__*/ 8210 function (_Track) { 8211 _inheritsLoose(VideoTrack, _Track); 8212 8213 /** 8214 * Create an instance of this class. 8215 * 8216 * @param {Object} [options={}] 8217 * Object of option names and values 8218 * 8219 * @param {string} [options.kind=''] 8220 * A valid {@link VideoTrack~Kind} 8221 * 8222 * @param {string} [options.id='vjs_track_' + Guid.newGUID()] 8223 * A unique id for this AudioTrack. 8224 * 8225 * @param {string} [options.label=''] 8226 * The menu label for this track. 8227 * 8228 * @param {string} [options.language=''] 8229 * A valid two character language code. 8230 * 8231 * @param {boolean} [options.selected] 8232 * If this track is the one that is currently playing. 8233 */ 8234 function VideoTrack(options) { 8235 var _this; 8236 8237 if (options === void 0) { 8238 options = {}; 8239 } 8240 8241 var settings = mergeOptions(options, { 8242 kind: VideoTrackKind[options.kind] || '' 8243 }); 8244 _this = _Track.call(this, settings) || this; 8245 var selected = false; 8246 /** 8247 * @memberof VideoTrack 8248 * @member {boolean} selected 8249 * If this `VideoTrack` is selected or not. When setting this will 8250 * fire {@link VideoTrack#selectedchange} if the state of selected changed. 8251 * @instance 8252 * 8253 * @fires VideoTrack#selectedchange 8254 */ 8255 8256 Object.defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), 'selected', { 8257 get: function get() { 8258 return selected; 8259 }, 8260 set: function set(newSelected) { 8261 // an invalid or unchanged value 8262 if (typeof newSelected !== 'boolean' || newSelected === selected) { 8263 return; 8264 } 8265 8266 selected = newSelected; 8267 /** 8268 * An event that fires when selected changes on this track. This allows 8269 * the VideoTrackList that holds this track to act accordingly. 8270 * 8271 * > Note: This is not part of the spec! Native tracks will do 8272 * this internally without an event. 8273 * 8274 * @event VideoTrack#selectedchange 8275 * @type {EventTarget~Event} 8276 */ 8277 8278 this.trigger('selectedchange'); 8279 } 8280 }); // if the user sets this track to selected then 8281 // set selected to that true value otherwise 8282 // we keep it false 8283 8284 if (settings.selected) { 8285 _this.selected = settings.selected; 8286 } 8287 8288 return _this; 8289 } 8290 8291 return VideoTrack; 8292 }(Track); 8293 8294 /** 8295 * @memberof HTMLTrackElement 8296 * @typedef {HTMLTrackElement~ReadyState} 8297 * @enum {number} 8298 */ 8299 8300 var NONE = 0; 8301 var LOADING = 1; 8302 var LOADED = 2; 8303 var ERROR = 3; 8304 /** 8305 * A single track represented in the DOM. 8306 * 8307 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#htmltrackelement} 8308 * @extends EventTarget 8309 */ 8310 8311 var HTMLTrackElement = 8312 /*#__PURE__*/ 8313 function (_EventTarget) { 8314 _inheritsLoose(HTMLTrackElement, _EventTarget); 8315 8316 /** 8317 * Create an instance of this class. 8318 * 8319 * @param {Object} options={} 8320 * Object of option names and values 8321 * 8322 * @param {Tech} options.tech 8323 * A reference to the tech that owns this HTMLTrackElement. 8324 * 8325 * @param {TextTrack~Kind} [options.kind='subtitles'] 8326 * A valid text track kind. 8327 * 8328 * @param {TextTrack~Mode} [options.mode='disabled'] 8329 * A valid text track mode. 8330 * 8331 * @param {string} [options.id='vjs_track_' + Guid.newGUID()] 8332 * A unique id for this TextTrack. 8333 * 8334 * @param {string} [options.label=''] 8335 * The menu label for this track. 8336 * 8337 * @param {string} [options.language=''] 8338 * A valid two character language code. 8339 * 8340 * @param {string} [options.srclang=''] 8341 * A valid two character language code. An alternative, but deprioritized 8342 * vesion of `options.language` 8343 * 8344 * @param {string} [options.src] 8345 * A url to TextTrack cues. 8346 * 8347 * @param {boolean} [options.default] 8348 * If this track should default to on or off. 8349 */ 8350 function HTMLTrackElement(options) { 8351 var _this; 8352 8353 if (options === void 0) { 8354 options = {}; 8355 } 8356 8357 _this = _EventTarget.call(this) || this; 8358 var readyState; 8359 var track = new TextTrack(options); 8360 _this.kind = track.kind; 8361 _this.src = track.src; 8362 _this.srclang = track.language; 8363 _this.label = track.label; 8364 _this.default = track.default; 8365 Object.defineProperties(_assertThisInitialized(_assertThisInitialized(_this)), { 8366 /** 8367 * @memberof HTMLTrackElement 8368 * @member {HTMLTrackElement~ReadyState} readyState 8369 * The current ready state of the track element. 8370 * @instance 8371 */ 8372 readyState: { 8373 get: function get() { 8374 return readyState; 8375 } 8376 }, 8377 8378 /** 8379 * @memberof HTMLTrackElement 8380 * @member {TextTrack} track 8381 * The underlying TextTrack object. 8382 * @instance 8383 * 8384 */ 8385 track: { 8386 get: function get() { 8387 return track; 8388 } 8389 } 8390 }); 8391 readyState = NONE; 8392 /** 8393 * @listens TextTrack#loadeddata 8394 * @fires HTMLTrackElement#load 8395 */ 8396 8397 track.addEventListener('loadeddata', function () { 8398 readyState = LOADED; 8399 8400 _this.trigger({ 8401 type: 'load', 8402 target: _assertThisInitialized(_assertThisInitialized(_this)) 8403 }); 8404 }); 8405 return _this; 8406 } 8407 8408 return HTMLTrackElement; 8409 }(EventTarget); 8410 8411 HTMLTrackElement.prototype.allowedEvents_ = { 8412 load: 'load' 8413 }; 8414 HTMLTrackElement.NONE = NONE; 8415 HTMLTrackElement.LOADING = LOADING; 8416 HTMLTrackElement.LOADED = LOADED; 8417 HTMLTrackElement.ERROR = ERROR; 8418 8419 /* 8420 * This file contains all track properties that are used in 8421 * player.js, tech.js, html5.js and possibly other techs in the future. 8422 */ 8423 8424 var NORMAL = { 8425 audio: { 8426 ListClass: AudioTrackList, 8427 TrackClass: AudioTrack, 8428 capitalName: 'Audio' 8429 }, 8430 video: { 8431 ListClass: VideoTrackList, 8432 TrackClass: VideoTrack, 8433 capitalName: 'Video' 8434 }, 8435 text: { 8436 ListClass: TextTrackList, 8437 TrackClass: TextTrack, 8438 capitalName: 'Text' 8439 } 8440 };
vendor: 14,149 bytes, lines 8441-8898
8441 Object.keys(NORMAL).forEach(function (type) { 8442 NORMAL[type].getterName = type + "Tracks"; 8443 NORMAL[type].privateName = type + "Tracks_"; 8444 }); 8445 var REMOTE = { 8446 remoteText: { 8447 ListClass: TextTrackList, 8448 TrackClass: TextTrack, 8449 capitalName: 'RemoteText', 8450 getterName: 'remoteTextTracks', 8451 privateName: 'remoteTextTracks_' 8452 }, 8453 remoteTextEl: { 8454 ListClass: HtmlTrackElementList, 8455 TrackClass: HTMLTrackElement, 8456 capitalName: 'RemoteTextTrackEls', 8457 getterName: 'remoteTextTrackEls', 8458 privateName: 'remoteTextTrackEls_' 8459 } 8460 }; 8461 var ALL = mergeOptions(NORMAL, REMOTE); 8462 REMOTE.names = Object.keys(REMOTE); 8463 NORMAL.names = Object.keys(NORMAL); 8464 ALL.names = [].concat(REMOTE.names).concat(NORMAL.names); 8465 8466 /** 8467 * Copyright 2013 vtt.js Contributors 8468 * 8469 * Licensed under the Apache License, Version 2.0 (the "License"); 8470 * you may not use this file except in compliance with the License. 8471 * You may obtain a copy of the License at 8472 * 8473 * http://www.apache.org/licenses/LICENSE-2.0 8474 * 8475 * Unless required by applicable law or agreed to in writing, software 8476 * distributed under the License is distributed on an "AS IS" BASIS, 8477 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 8478 * See the License for the specific language governing permissions and 8479 * limitations under the License. 8480 */ 8481 8482 /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 8483 8484 /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ 8485 var _objCreate = Object.create || function () { 8486 function F() {} 8487 8488 return function (o) { 8489 if (arguments.length !== 1) { 8490 throw new Error('Object.create shim only accepts one parameter.'); 8491 } 8492 8493 F.prototype = o; 8494 return new F(); 8495 }; 8496 }(); // Creates a new ParserError object from an errorData object. The errorData 8497 // object should have default code and message properties. The default message 8498 // property can be overriden by passing in a message parameter. 8499 // See ParsingError.Errors below for acceptable errors. 8500 8501 8502 function ParsingError(errorData, message) { 8503 this.name = "ParsingError"; 8504 this.code = errorData.code; 8505 this.message = message || errorData.message; 8506 } 8507 8508 ParsingError.prototype = _objCreate(Error.prototype); 8509 ParsingError.prototype.constructor = ParsingError; // ParsingError metadata for acceptable ParsingErrors. 8510 8511 ParsingError.Errors = { 8512 BadSignature: { 8513 code: 0, 8514 message: "Malformed WebVTT signature." 8515 }, 8516 BadTimeStamp: { 8517 code: 1, 8518 message: "Malformed time stamp." 8519 } 8520 }; // Try to parse input as a time stamp. 8521 8522 function parseTimeStamp(input) { 8523 function computeSeconds(h, m, s, f) { 8524 return (h | 0) * 3600 + (m | 0) * 60 + (s | 0) + (f | 0) / 1000; 8525 } 8526 8527 var m = input.match(/^(\d+):(\d{2})(:\d{2})?\.(\d{3})/); 8528 8529 if (!m) { 8530 return null; 8531 } 8532 8533 if (m[3]) { 8534 // Timestamp takes the form of [hours]:[minutes]:[seconds].[milliseconds] 8535 return computeSeconds(m[1], m[2], m[3].replace(":", ""), m[4]); 8536 } else if (m[1] > 59) { 8537 // Timestamp takes the form of [hours]:[minutes].[milliseconds] 8538 // First position is hours as it's over 59. 8539 return computeSeconds(m[1], m[2], 0, m[4]); 8540 } else { 8541 // Timestamp takes the form of [minutes]:[seconds].[milliseconds] 8542 return computeSeconds(0, m[1], m[2], m[4]); 8543 } 8544 } // A settings object holds key/value pairs and will ignore anything but the first 8545 // assignment to a specific key. 8546 8547 8548 function Settings() { 8549 this.values = _objCreate(null); 8550 } 8551 8552 Settings.prototype = { 8553 // Only accept the first assignment to any key. 8554 set: function set(k, v) { 8555 if (!this.get(k) && v !== "") { 8556 this.values[k] = v; 8557 } 8558 }, 8559 // Return the value for a key, or a default value. 8560 // If 'defaultKey' is passed then 'dflt' is assumed to be an object with 8561 // a number of possible default values as properties where 'defaultKey' is 8562 // the key of the property that will be chosen; otherwise it's assumed to be 8563 // a single value. 8564 get: function get(k, dflt, defaultKey) { 8565 if (defaultKey) { 8566 return this.has(k) ? this.values[k] : dflt[defaultKey]; 8567 } 8568 8569 return this.has(k) ? this.values[k] : dflt; 8570 }, 8571 // Check whether we have a value for a key. 8572 has: function has(k) { 8573 return k in this.values; 8574 }, 8575 // Accept a setting if its one of the given alternatives. 8576 alt: function alt(k, v, a) { 8577 for (var n = 0; n < a.length; ++n) { 8578 if (v === a[n]) { 8579 this.set(k, v); 8580 break; 8581 } 8582 } 8583 }, 8584 // Accept a setting if its a valid (signed) integer. 8585 integer: function integer(k, v) { 8586 if (/^-?\d+$/.test(v)) { 8587 // integer 8588 this.set(k, parseInt(v, 10)); 8589 } 8590 }, 8591 // Accept a setting if its a valid percentage. 8592 percent: function percent(k, v) { 8593 var m; 8594 8595 if (m = v.match(/^([\d]{1,3})(\.[\d]*)?%$/)) { 8596 v = parseFloat(v); 8597 8598 if (v >= 0 && v <= 100) { 8599 this.set(k, v); 8600 return true; 8601 } 8602 } 8603 8604 return false; 8605 } 8606 }; // Helper function to parse input into groups separated by 'groupDelim', and 8607 // interprete each group as a key/value pair separated by 'keyValueDelim'. 8608 8609 function parseOptions(input, callback, keyValueDelim, groupDelim) { 8610 var groups = groupDelim ? input.split(groupDelim) : [input]; 8611 8612 for (var i in groups) { 8613 if (typeof groups[i] !== "string") { 8614 continue; 8615 } 8616 8617 var kv = groups[i].split(keyValueDelim); 8618 8619 if (kv.length !== 2) { 8620 continue; 8621 } 8622 8623 var k = kv[0]; 8624 var v = kv[1]; 8625 callback(k, v); 8626 } 8627 } 8628 8629 function parseCue(input, cue, regionList) { 8630 // Remember the original input if we need to throw an error. 8631 var oInput = input; // 4.1 WebVTT timestamp 8632 8633 function consumeTimeStamp() { 8634 var ts = parseTimeStamp(input); 8635 8636 if (ts === null) { 8637 throw new ParsingError(ParsingError.Errors.BadTimeStamp, "Malformed timestamp: " + oInput); 8638 } // Remove time stamp from input. 8639 8640 8641 input = input.replace(/^[^\sa-zA-Z-]+/, ""); 8642 return ts; 8643 } // 4.4.2 WebVTT cue settings 8644 8645 8646 function consumeCueSettings(input, cue) { 8647 var settings = new Settings(); 8648 parseOptions(input, function (k, v) { 8649 switch (k) { 8650 case "region": 8651 // Find the last region we parsed with the same region id. 8652 for (var i = regionList.length - 1; i >= 0; i--) { 8653 if (regionList[i].id === v) { 8654 settings.set(k, regionList[i].region); 8655 break; 8656 } 8657 } 8658 8659 break; 8660 8661 case "vertical": 8662 settings.alt(k, v, ["rl", "lr"]); 8663 break; 8664 8665 case "line": 8666 var vals = v.split(","), 8667 vals0 = vals[0]; 8668 settings.integer(k, vals0); 8669 settings.percent(k, vals0) ? settings.set("snapToLines", false) : null; 8670 settings.alt(k, vals0, ["auto"]); 8671 8672 if (vals.length === 2) { 8673 settings.alt("lineAlign", vals[1], ["start", "middle", "end"]); 8674 } 8675 8676 break; 8677 8678 case "position": 8679 vals = v.split(","); 8680 settings.percent(k, vals[0]); 8681 8682 if (vals.length === 2) { 8683 settings.alt("positionAlign", vals[1], ["start", "middle", "end"]); 8684 } 8685 8686 break; 8687 8688 case "size": 8689 settings.percent(k, v); 8690 break; 8691 8692 case "align": 8693 settings.alt(k, v, ["start", "middle", "end", "left", "right"]); 8694 break; 8695 } 8696 }, /:/, /\s/); // Apply default values for any missing fields. 8697 8698 cue.region = settings.get("region", null); 8699 cue.vertical = settings.get("vertical", ""); 8700 cue.line = settings.get("line", "auto"); 8701 cue.lineAlign = settings.get("lineAlign", "start"); 8702 cue.snapToLines = settings.get("snapToLines", true); 8703 cue.size = settings.get("size", 100); 8704 cue.align = settings.get("align", "middle"); 8705 cue.position = settings.get("position", { 8706 start: 0, 8707 left: 0, 8708 middle: 50, 8709 end: 100, 8710 right: 100 8711 }, cue.align); 8712 cue.positionAlign = settings.get("positionAlign", { 8713 start: "start", 8714 left: "start", 8715 middle: "middle", 8716 end: "end", 8717 right: "end" 8718 }, cue.align); 8719 } 8720 8721 function skipWhitespace() { 8722 input = input.replace(/^\s+/, ""); 8723 } // 4.1 WebVTT cue timings. 8724 8725 8726 skipWhitespace(); 8727 cue.startTime = consumeTimeStamp(); // (1) collect cue start time 8728 8729 skipWhitespace(); 8730 8731 if (input.substr(0, 3) !== "-->") { 8732 // (3) next characters must match "-->" 8733 throw new ParsingError(ParsingError.Errors.BadTimeStamp, "Malformed time stamp (time stamps must be separated by '-->'): " + oInput); 8734 } 8735 8736 input = input.substr(3); 8737 skipWhitespace(); 8738 cue.endTime = consumeTimeStamp(); // (5) collect cue end time 8739 // 4.1 WebVTT cue settings list. 8740 8741 skipWhitespace(); 8742 consumeCueSettings(input, cue); 8743 } 8744 8745 var ESCAPE = { 8746 "&": "&", 8747 "<": "<", 8748 ">": ">", 8749 "‎": "\u200E", 8750 "‏": "\u200F", 8751 " ": "\xA0" 8752 }; 8753 var TAG_NAME = { 8754 c: "span", 8755 i: "i", 8756 b: "b", 8757 u: "u", 8758 ruby: "ruby", 8759 rt: "rt", 8760 v: "span", 8761 lang: "span" 8762 }; 8763 var TAG_ANNOTATION = { 8764 v: "title", 8765 lang: "lang" 8766 }; 8767 var NEEDS_PARENT = { 8768 rt: "ruby" 8769 }; // Parse content into a document fragment. 8770 8771 function parseContent(window, input) { 8772 function nextToken() { 8773 // Check for end-of-string. 8774 if (!input) { 8775 return null; 8776 } // Consume 'n' characters from the input. 8777 8778 8779 function consume(result) { 8780 input = input.substr(result.length); 8781 return result; 8782 } 8783 8784 var m = input.match(/^([^<]*)(<[^>]*>?)?/); // If there is some text before the next tag, return it, otherwise return 8785 // the tag. 8786 8787 return consume(m[1] ? m[1] : m[2]); 8788 } // Unescape a string 's'. 8789 8790 8791 function unescape1(e) { 8792 return ESCAPE[e]; 8793 } 8794 8795 function unescape(s) { 8796 while (m = s.match(/&(amp|lt|gt|lrm|rlm|nbsp);/)) { 8797 s = s.replace(m[0], unescape1); 8798 } 8799 8800 return s; 8801 } 8802 8803 function shouldAdd(current, element) { 8804 return !NEEDS_PARENT[element.localName] || NEEDS_PARENT[element.localName] === current.localName; 8805 } // Create an element for this tag. 8806 8807 8808 function createElement(type, annotation) { 8809 var tagName = TAG_NAME[type]; 8810 8811 if (!tagName) { 8812 return null; 8813 } 8814 8815 var element = window.document.createElement(tagName); 8816 element.localName = tagName; 8817 var name = TAG_ANNOTATION[type]; 8818 8819 if (name && annotation) { 8820 element[name] = annotation.trim(); 8821 } 8822 8823 return element; 8824 } 8825 8826 var rootDiv = window.document.createElement("div"), 8827 current = rootDiv, 8828 t, 8829 tagStack = []; 8830 8831 while ((t = nextToken()) !== null) { 8832 if (t[0] === '<') { 8833 if (t[1] === "/") { 8834 // If the closing tag matches, move back up to the parent node. 8835 if (tagStack.length && tagStack[tagStack.length - 1] === t.substr(2).replace(">", "")) { 8836 tagStack.pop(); 8837 current = current.parentNode; 8838 } // Otherwise just ignore the end tag. 8839 8840 8841 continue; 8842 } 8843 8844 var ts = parseTimeStamp(t.substr(1, t.length - 2)); 8845 var node; 8846 8847 if (ts) { 8848 // Timestamps are lead nodes as well. 8849 node = window.document.createProcessingInstruction("timestamp", ts); 8850 current.appendChild(node); 8851 continue; 8852 } 8853 8854 var m = t.match(/^<([^.\s/0-9>]+)(\.[^\s\\>]+)?([^>\\]+)?(\\?)>?$/); // If we can't parse the tag, skip to the next tag. 8855 8856 if (!m) { 8857 continue; 8858 } // Try to construct an element, and ignore the tag if we couldn't. 8859 8860 8861 node = createElement(m[1], m[3]); 8862 8863 if (!node) { 8864 continue; 8865 } // Determine if the tag should be added based on the context of where it 8866 // is placed in the cuetext. 8867 8868 8869 if (!shouldAdd(current, node)) { 8870 continue; 8871 } // Set the class list (as a list of classes, separated by space). 8872 8873 8874 if (m[2]) { 8875 node.className = m[2].substr(1).replace('.', ' '); 8876 } // Append the node to the current node, and enter the scope of the new 8877 // node. 8878 8879 8880 tagStack.push(m[1]); 8881 current.appendChild(node); 8882 current = node; 8883 continue; 8884 } // Text nodes are leaf nodes. 8885 8886 8887 current.appendChild(window.document.createTextNode(unescape(t))); 8888 } 8889 8890 return rootDiv; 8891 } // This is a list of all the Unicode characters that have a strong 8892 // right-to-left category. What this means is that these characters are 8893 // written right-to-left for sure. It was generated by pulling all the strong 8894 // right-to-left characters out of the Unicode data table. That table can 8895 // found at: http://www.unicode.org/Public/UNIDATA/UnicodeData.txt 8896 8897 8898 var strongRTLRanges = [[0x5be, 0x5be], [0x5c0, 0x5c0], [0x5c3, 0x5c3], [0x5c6, 0x5c6], [0x5d0, 0x5ea], [0x5f0, 0x5f4], [0x608, 0x608], [0x60b, 0x60b], [0x60d, 0x60d], [0x61b, 0x61b], [0x61e, 0x64a], [0x66d, 0x66f], [0x671, 0x6d5], [0x6e5, 0x6e6], [0x6ee, 0x6ef], [0x6fa, 0x70d], [0x70f, 0x710], [0x712, 0x72f], [0x74d, 0x7a5], [0x7b1, 0x7b1], [0x7c0, 0x7ea], [0x7f4, 0x7f5], [0x7fa, 0x7fa], [0x800, 0x815], [0x81a, 0x81a], [0x824, 0x824], [0x828, 0x828], [0x830, 0x83e], [0x840, 0x858], [0x85e, 0x85e], [0x8a0, 0x8a0], [0x8a2, 0x8ac], [0x200f, 0x200f], [0xfb1d, 0xfb1d], [0xfb1f, 0xfb28], [0xfb2a, 0xfb36], [0xfb38, 0xfb3c], [0xfb3e, 0xfb3e], [0xfb40, 0xfb41], [0xfb43, 0xfb44], [0xfb46, 0xfbc1], [0xfbd3, 0xfd3d], [0xfd50, 0xfd8f], [0xfd92, 0xfdc7], [0xfdf0, 0xfdfc], [0xfe70, 0xfe74], [0xfe76, 0xfefc], [0x10800, 0x10805], [0x10808, 0x10808], [0x1080a, 0x10835], [0x10837, 0x10838], [0x1083c, 0x1083c], [0x1083f, 0x10855], [0x10857, 0x1085f], [0x10900, 0x1091b], [0x10920, 0x10939], [0x1093f,
vendor: 18,632 bytes, lines 8898-9439
88980x1093f], [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]]; 8899 8900 function isStrongRTLChar(charCode) { 8901 for (var i = 0; i < strongRTLRanges.length; i++) { 8902 var currentRange = strongRTLRanges[i]; 8903 8904 if (charCode >= currentRange[0] && charCode <= currentRange[1]) { 8905 return true; 8906 } 8907 } 8908 8909 return false; 8910 } 8911 8912 function determineBidi(cueDiv) { 8913 var nodeStack = [], 8914 text = "", 8915 charCode; 8916 8917 if (!cueDiv || !cueDiv.childNodes) { 8918 return "ltr"; 8919 } 8920 8921 function pushNodes(nodeStack, node) { 8922 for (var i = node.childNodes.length - 1; i >= 0; i--) { 8923 nodeStack.push(node.childNodes[i]); 8924 } 8925 } 8926 8927 function nextTextNode(nodeStack) { 8928 if (!nodeStack || !nodeStack.length) { 8929 return null; 8930 } 8931 8932 var node = nodeStack.pop(), 8933 text = node.textContent || node.innerText; 8934 8935 if (text) { 8936 // TODO: This should match all unicode type B characters (paragraph 8937 // separator characters). See issue #115. 8938 var m = text.match(/^.*(\n|\r)/); 8939 8940 if (m) { 8941 nodeStack.length = 0; 8942 return m[0]; 8943 } 8944 8945 return text; 8946 } 8947 8948 if (node.tagName === "ruby") { 8949 return nextTextNode(nodeStack); 8950 } 8951 8952 if (node.childNodes) { 8953 pushNodes(nodeStack, node); 8954 return nextTextNode(nodeStack); 8955 } 8956 } 8957 8958 pushNodes(nodeStack, cueDiv); 8959 8960 while (text = nextTextNode(nodeStack)) { 8961 for (var i = 0; i < text.length; i++) { 8962 charCode = text.charCodeAt(i); 8963 8964 if (isStrongRTLChar(charCode)) { 8965 return "rtl"; 8966 } 8967 } 8968 } 8969 8970 return "ltr"; 8971 } 8972 8973 function computeLinePos(cue) { 8974 if (typeof cue.line === "number" && (cue.snapToLines || cue.line >= 0 && cue.line <= 100)) { 8975 return cue.line; 8976 } 8977 8978 if (!cue.track || !cue.track.textTrackList || !cue.track.textTrackList.mediaElement) { 8979 return -1; 8980 } 8981 8982 var track = cue.track, 8983 trackList = track.textTrackList, 8984 count = 0; 8985 8986 for (var i = 0; i < trackList.length && trackList[i] !== track; i++) { 8987 if (trackList[i].mode === "showing") { 8988 count++; 8989 } 8990 } 8991 8992 return ++count * -1; 8993 } 8994 8995 function StyleBox() {} // Apply styles to a div. If there is no div passed then it defaults to the 8996 // div on 'this'. 8997 8998 8999 StyleBox.prototype.applyStyles = function (styles, div) { 9000 div = div || this.div; 9001 9002 for (var prop in styles) { 9003 if (styles.hasOwnProperty(prop)) { 9004 div.style[prop] = styles[prop]; 9005 } 9006 } 9007 }; 9008 9009 StyleBox.prototype.formatStyle = function (val, unit) { 9010 return val === 0 ? 0 : val + unit; 9011 }; // Constructs the computed display state of the cue (a div). Places the div 9012 // into the overlay which should be a block level element (usually a div). 9013 9014 9015 function CueStyleBox(window, cue, styleOptions) { 9016 StyleBox.call(this); 9017 this.cue = cue; // Parse our cue's text into a DOM tree rooted at 'cueDiv'. This div will 9018 // have inline positioning and will function as the cue background box. 9019 9020 this.cueDiv = parseContent(window, cue.text); 9021 var styles = { 9022 color: "rgba(255, 255, 255, 1)", 9023 backgroundColor: "rgba(0, 0, 0, 0.8)", 9024 position: "relative", 9025 left: 0, 9026 right: 0, 9027 top: 0, 9028 bottom: 0, 9029 display: "inline", 9030 writingMode: cue.vertical === "" ? "horizontal-tb" : cue.vertical === "lr" ? "vertical-lr" : "vertical-rl", 9031 unicodeBidi: "plaintext" 9032 }; 9033 this.applyStyles(styles, this.cueDiv); // Create an absolutely positioned div that will be used to position the cue 9034 // div. Note, all WebVTT cue-setting alignments are equivalent to the CSS 9035 // mirrors of them except "middle" which is "center" in CSS. 9036 9037 this.div = window.document.createElement("div"); 9038 styles = { 9039 direction: determineBidi(this.cueDiv), 9040 writingMode: cue.vertical === "" ? "horizontal-tb" : cue.vertical === "lr" ? "vertical-lr" : "vertical-rl", 9041 unicodeBidi: "plaintext", 9042 textAlign: cue.align === "middle" ? "center" : cue.align, 9043 font: styleOptions.font, 9044 whiteSpace: "pre-line", 9045 position: "absolute" 9046 }; 9047 this.applyStyles(styles); 9048 this.div.appendChild(this.cueDiv); // Calculate the distance from the reference edge of the viewport to the text 9049 // position of the cue box. The reference edge will be resolved later when 9050 // the box orientation styles are applied. 9051 9052 var textPos = 0; 9053 9054 switch (cue.positionAlign) { 9055 case "start": 9056 textPos = cue.position; 9057 break; 9058 9059 case "middle": 9060 textPos = cue.position - cue.size / 2; 9061 break; 9062 9063 case "end": 9064 textPos = cue.position - cue.size; 9065 break; 9066 } // Horizontal box orientation; textPos is the distance from the left edge of the 9067 // area to the left edge of the box and cue.size is the distance extending to 9068 // the right from there. 9069 9070 9071 if (cue.vertical === "") { 9072 this.applyStyles({ 9073 left: this.formatStyle(textPos, "%"), 9074 width: this.formatStyle(cue.size, "%") 9075 }); // Vertical box orientation; textPos is the distance from the top edge of the 9076 // area to the top edge of the box and cue.size is the height extending 9077 // downwards from there. 9078 } else { 9079 this.applyStyles({ 9080 top: this.formatStyle(textPos, "%"), 9081 height: this.formatStyle(cue.size, "%") 9082 }); 9083 } 9084 9085 this.move = function (box) { 9086 this.applyStyles({ 9087 top: this.formatStyle(box.top, "px"), 9088 bottom: this.formatStyle(box.bottom, "px"), 9089 left: this.formatStyle(box.left, "px"), 9090 right: this.formatStyle(box.right, "px"), 9091 height: this.formatStyle(box.height, "px"), 9092 width: this.formatStyle(box.width, "px") 9093 }); 9094 }; 9095 } 9096 9097 CueStyleBox.prototype = _objCreate(StyleBox.prototype); 9098 CueStyleBox.prototype.constructor = CueStyleBox; // Represents the co-ordinates of an Element in a way that we can easily 9099 // compute things with such as if it overlaps or intersects with another Element. 9100 // Can initialize it with either a StyleBox or another BoxPosition. 9101 9102 function BoxPosition(obj) { 9103 // Either a BoxPosition was passed in and we need to copy it, or a StyleBox 9104 // was passed in and we need to copy the results of 'getBoundingClientRect' 9105 // as the object returned is readonly. All co-ordinate values are in reference 9106 // to the viewport origin (top left). 9107 var lh, height, width, top; 9108 9109 if (obj.div) { 9110 height = obj.div.offsetHeight; 9111 width = obj.div.offsetWidth; 9112 top = obj.div.offsetTop; 9113 var rects = (rects = obj.div.childNodes) && (rects = rects[0]) && rects.getClientRects && rects.getClientRects(); 9114 obj = obj.div.getBoundingClientRect(); // In certain cases the outter div will be slightly larger then the sum of 9115 // the inner div's lines. This could be due to bold text, etc, on some platforms. 9116 // In this case we should get the average line height and use that. This will 9117 // result in the desired behaviour. 9118 9119 lh = rects ? Math.max(rects[0] && rects[0].height || 0, obj.height / rects.length) : 0; 9120 } 9121 9122 this.left = obj.left; 9123 this.right = obj.right; 9124 this.top = obj.top || top; 9125 this.height = obj.height || height; 9126 this.bottom = obj.bottom || top + (obj.height || height); 9127 this.width = obj.width || width; 9128 this.lineHeight = lh !== undefined ? lh : obj.lineHeight; 9129 } // Move the box along a particular axis. Optionally pass in an amount to move 9130 // the box. If no amount is passed then the default is the line height of the 9131 // box. 9132 9133 9134 BoxPosition.prototype.move = function (axis, toMove) { 9135 toMove = toMove !== undefined ? toMove : this.lineHeight; 9136 9137 switch (axis) { 9138 case "+x": 9139 this.left += toMove; 9140 this.right += toMove; 9141 break; 9142 9143 case "-x": 9144 this.left -= toMove; 9145 this.right -= toMove; 9146 break; 9147 9148 case "+y": 9149 this.top += toMove; 9150 this.bottom += toMove; 9151 break; 9152 9153 case "-y": 9154 this.top -= toMove; 9155 this.bottom -= toMove; 9156 break; 9157 } 9158 }; // Check if this box overlaps another box, b2. 9159 9160 9161 BoxPosition.prototype.overlaps = function (b2) { 9162 return this.left < b2.right && this.right > b2.left && this.top < b2.bottom && this.bottom > b2.top; 9163 }; // Check if this box overlaps any other boxes in boxes. 9164 9165 9166 BoxPosition.prototype.overlapsAny = function (boxes) { 9167 for (var i = 0; i < boxes.length; i++) { 9168 if (this.overlaps(boxes[i])) { 9169 return true; 9170 } 9171 } 9172 9173 return false; 9174 }; // Check if this box is within another box. 9175 9176 9177 BoxPosition.prototype.within = function (container) { 9178 return this.top >= container.top && this.bottom <= container.bottom && this.left >= container.left && this.right <= container.right; 9179 }; // Check if this box is entirely within the container or it is overlapping 9180 // on the edge opposite of the axis direction passed. For example, if "+x" is 9181 // passed and the box is overlapping on the left edge of the container, then 9182 // return true. 9183 9184 9185 BoxPosition.prototype.overlapsOppositeAxis = function (container, axis) { 9186 switch (axis) { 9187 case "+x": 9188 return this.left < container.left; 9189 9190 case "-x": 9191 return this.right > container.right; 9192 9193 case "+y": 9194 return this.top < container.top; 9195 9196 case "-y": 9197 return this.bottom > container.bottom; 9198 } 9199 }; // Find the percentage of the area that this box is overlapping with another 9200 // box. 9201 9202 9203 BoxPosition.prototype.intersectPercentage = function (b2) { 9204 var x = Math.max(0, Math.min(this.right, b2.right) - Math.max(this.left, b2.left)), 9205 y = Math.max(0, Math.min(this.bottom, b2.bottom) - Math.max(this.top, b2.top)), 9206 intersectArea = x * y; 9207 return intersectArea / (this.height * this.width); 9208 }; // Convert the positions from this box to CSS compatible positions using 9209 // the reference container's positions. This has to be done because this 9210 // box's positions are in reference to the viewport origin, whereas, CSS 9211 // values are in referecne to their respective edges. 9212 9213 9214 BoxPosition.prototype.toCSSCompatValues = function (reference) { 9215 return { 9216 top: this.top - reference.top, 9217 bottom: reference.bottom - this.bottom, 9218 left: this.left - reference.left, 9219 right: reference.right - this.right, 9220 height: this.height, 9221 width: this.width 9222 }; 9223 }; // Get an object that represents the box's position without anything extra. 9224 // Can pass a StyleBox, HTMLElement, or another BoxPositon. 9225 9226 9227 BoxPosition.getSimpleBoxPosition = function (obj) { 9228 var height = obj.div ? obj.div.offsetHeight : obj.tagName ? obj.offsetHeight : 0; 9229 var width = obj.div ? obj.div.offsetWidth : obj.tagName ? obj.offsetWidth : 0; 9230 var top = obj.div ? obj.div.offsetTop : obj.tagName ? obj.offsetTop : 0; 9231 obj = obj.div ? obj.div.getBoundingClientRect() : obj.tagName ? obj.getBoundingClientRect() : obj; 9232 var ret = { 9233 left: obj.left, 9234 right: obj.right, 9235 top: obj.top || top, 9236 height: obj.height || height, 9237 bottom: obj.bottom || top + (obj.height || height), 9238 width: obj.width || width 9239 }; 9240 return ret; 9241 }; // Move a StyleBox to its specified, or next best, position. The containerBox 9242 // is the box that contains the StyleBox, such as a div. boxPositions are 9243 // a list of other boxes that the styleBox can't overlap with. 9244 9245 9246 function moveBoxToLinePosition(window, styleBox, containerBox, boxPositions) { 9247 // Find the best position for a cue box, b, on the video. The axis parameter 9248 // is a list of axis, the order of which, it will move the box along. For example: 9249 // Passing ["+x", "-x"] will move the box first along the x axis in the positive 9250 // direction. If it doesn't find a good position for it there it will then move 9251 // it along the x axis in the negative direction. 9252 function findBestPosition(b, axis) { 9253 var bestPosition, 9254 specifiedPosition = new BoxPosition(b), 9255 percentage = 1; // Highest possible so the first thing we get is better. 9256 9257 for (var i = 0; i < axis.length; i++) { 9258 while (b.overlapsOppositeAxis(containerBox, axis[i]) || b.within(containerBox) && b.overlapsAny(boxPositions)) { 9259 b.move(axis[i]); 9260 } // We found a spot where we aren't overlapping anything. This is our 9261 // best position. 9262 9263 9264 if (b.within(containerBox)) { 9265 return b; 9266 } 9267 9268 var p = b.intersectPercentage(containerBox); // If we're outside the container box less then we were on our last try 9269 // then remember this position as the best position. 9270 9271 if (percentage > p) { 9272 bestPosition = new BoxPosition(b); 9273 percentage = p; 9274 } // Reset the box position to the specified position. 9275 9276 9277 b = new BoxPosition(specifiedPosition); 9278 } 9279 9280 return bestPosition || specifiedPosition; 9281 } 9282 9283 var boxPosition = new BoxPosition(styleBox), 9284 cue = styleBox.cue, 9285 linePos = computeLinePos(cue), 9286 axis = []; // If we have a line number to align the cue to. 9287 9288 if (cue.snapToLines) { 9289 var size; 9290 9291 switch (cue.vertical) { 9292 case "": 9293 axis = ["+y", "-y"]; 9294 size = "height"; 9295 break; 9296 9297 case "rl": 9298 axis = ["+x", "-x"]; 9299 size = "width"; 9300 break; 9301 9302 case "lr": 9303 axis = ["-x", "+x"]; 9304 size = "width"; 9305 break; 9306 } 9307 9308 var step = boxPosition.lineHeight, 9309 position = step * Math.round(linePos), 9310 maxPosition = containerBox[size] + step, 9311 initialAxis = axis[0]; // If the specified intial position is greater then the max position then 9312 // clamp the box to the amount of steps it would take for the box to 9313 // reach the max position. 9314 9315 if (Math.abs(position) > maxPosition) { 9316 position = position < 0 ? -1 : 1; 9317 position *= Math.ceil(maxPosition / step) * step; 9318 } // If computed line position returns negative then line numbers are 9319 // relative to the bottom of the video instead of the top. Therefore, we 9320 // need to increase our initial position by the length or width of the 9321 // video, depending on the writing direction, and reverse our axis directions. 9322 9323 9324 if (linePos < 0) { 9325 position += cue.vertical === "" ? containerBox.height : containerBox.width; 9326 axis = axis.reverse(); 9327 } // Move the box to the specified position. This may not be its best 9328 // position. 9329 9330 9331 boxPosition.move(initialAxis, position); 9332 } else { 9333 // If we have a percentage line value for the cue. 9334 var calculatedPercentage = boxPosition.lineHeight / containerBox.height * 100; 9335 9336 switch (cue.lineAlign) { 9337 case "middle": 9338 linePos -= calculatedPercentage / 2; 9339 break; 9340 9341 case "end": 9342 linePos -= calculatedPercentage; 9343 break; 9344 } // Apply initial line position to the cue box. 9345 9346 9347 switch (cue.vertical) { 9348 case "": 9349 styleBox.applyStyles({ 9350 top: styleBox.formatStyle(linePos, "%") 9351 }); 9352 break; 9353 9354 case "rl": 9355 styleBox.applyStyles({ 9356 left: styleBox.formatStyle(linePos, "%") 9357 }); 9358 break; 9359 9360 case "lr": 9361 styleBox.applyStyles({ 9362 right: styleBox.formatStyle(linePos, "%") 9363 }); 9364 break; 9365 } 9366 9367 axis = ["+y", "-x", "+x", "-y"]; // Get the box position again after we've applied the specified positioning 9368 // to it. 9369 9370 boxPosition = new BoxPosition(styleBox); 9371 } 9372 9373 var bestPosition = findBestPosition(boxPosition, axis); 9374 styleBox.move(bestPosition.toCSSCompatValues(containerBox)); 9375 } 9376 9377 function WebVTT$1() {} // Nothing 9378 // Helper to allow strings to be decoded instead of the default binary utf8 data. 9379 9380 9381 WebVTT$1.StringDecoder = function () { 9382 return { 9383 decode: function decode(data) { 9384 if (!data) { 9385 return ""; 9386 } 9387 9388 if (typeof data !== "string") { 9389 throw new Error("Error - expected string data."); 9390 } 9391 9392 return decodeURIComponent(encodeURIComponent(data)); 9393 } 9394 }; 9395 }; 9396 9397 WebVTT$1.convertCueToDOMTree = function (window, cuetext) { 9398 if (!window || !cuetext) { 9399 return null; 9400 } 9401 9402 return parseContent(window, cuetext); 9403 }; 9404 9405 var FONT_SIZE_PERCENT = 0.05; 9406 var FONT_STYLE = "sans-serif"; 9407 var CUE_BACKGROUND_PADDING = "1.5%"; // Runs the processing model over the cues and regions passed to it. 9408 // @param overlay A block level element (usually a div) that the computed cues 9409 // and regions will be placed into. 9410 9411 WebVTT$1.processCues = function (window, cues, overlay) { 9412 if (!window || !cues || !overlay) { 9413 return null; 9414 } // Remove all previous children. 9415 9416 9417 while (overlay.firstChild) { 9418 overlay.removeChild(overlay.firstChild); 9419 } 9420 9421 var paddedOverlay = window.document.createElement("div"); 9422 paddedOverlay.style.position = "absolute"; 9423 paddedOverlay.style.left = "0"; 9424 paddedOverlay.style.right = "0"; 9425 paddedOverlay.style.top = "0"; 9426 paddedOverlay.style.bottom = "0"; 9427 paddedOverlay.style.margin = CUE_BACKGROUND_PADDING; 9428 overlay.appendChild(paddedOverlay); // Determine if we need to compute the display states of the cues. This could 9429 // be the case if a cue's state has been changed since the last computation or 9430 // if it has not been computed yet. 9431 9432 function shouldCompute(cues) { 9433 for (var i = 0; i < cues.length; i++) { 9434 if (cues[i].hasBeenReset || !cues[i].displayState) { 9435 return true; 9436 } 9437 } 9438 9439 return false;
vendor: 10,534 bytes, lines 9440-9790
9440 } // We don't need to recompute the cues' display states. Just reuse them. 9441 9442 9443 if (!shouldCompute(cues)) { 9444 for (var i = 0; i < cues.length; i++) { 9445 paddedOverlay.appendChild(cues[i].displayState); 9446 } 9447 9448 return; 9449 } 9450 9451 var boxPositions = [], 9452 containerBox = BoxPosition.getSimpleBoxPosition(paddedOverlay), 9453 fontSize = Math.round(containerBox.height * FONT_SIZE_PERCENT * 100) / 100; 9454 var styleOptions = { 9455 font: fontSize + "px " + FONT_STYLE 9456 }; 9457 9458 (function () { 9459 var styleBox, cue; 9460 9461 for (var i = 0; i < cues.length; i++) { 9462 cue = cues[i]; // Compute the intial position and styles of the cue div. 9463 9464 styleBox = new CueStyleBox(window, cue, styleOptions); 9465 paddedOverlay.appendChild(styleBox.div); // Move the cue div to it's correct line position. 9466 9467 moveBoxToLinePosition(window, styleBox, containerBox, boxPositions); // Remember the computed div so that we don't have to recompute it later 9468 // if we don't have too. 9469 9470 cue.displayState = styleBox.div; 9471 boxPositions.push(BoxPosition.getSimpleBoxPosition(styleBox)); 9472 } 9473 })(); 9474 }; 9475 9476 WebVTT$1.Parser = function (window, vttjs, decoder) { 9477 if (!decoder) { 9478 decoder = vttjs; 9479 vttjs = {}; 9480 } 9481 9482 if (!vttjs) { 9483 vttjs = {}; 9484 } 9485 9486 this.window = window; 9487 this.vttjs = vttjs; 9488 this.state = "INITIAL"; 9489 this.buffer = ""; 9490 this.decoder = decoder || new TextDecoder("utf8"); 9491 this.regionList = []; 9492 }; 9493 9494 WebVTT$1.Parser.prototype = { 9495 // If the error is a ParsingError then report it to the consumer if 9496 // possible. If it's not a ParsingError then throw it like normal. 9497 reportOrThrowError: function reportOrThrowError(e) { 9498 if (e instanceof ParsingError) { 9499 this.onparsingerror && this.onparsingerror(e); 9500 } else { 9501 throw e; 9502 } 9503 }, 9504 parse: function parse(data) { 9505 var self = this; // If there is no data then we won't decode it, but will just try to parse 9506 // whatever is in buffer already. This may occur in circumstances, for 9507 // example when flush() is called. 9508 9509 if (data) { 9510 // Try to decode the data that we received. 9511 self.buffer += self.decoder.decode(data, { 9512 stream: true 9513 }); 9514 } 9515 9516 function collectNextLine() { 9517 var buffer = self.buffer; 9518 var pos = 0; 9519 9520 while (pos < buffer.length && buffer[pos] !== '\r' && buffer[pos] !== '\n') { 9521 ++pos; 9522 } 9523 9524 var line = buffer.substr(0, pos); // Advance the buffer early in case we fail below. 9525 9526 if (buffer[pos] === '\r') { 9527 ++pos; 9528 } 9529 9530 if (buffer[pos] === '\n') { 9531 ++pos; 9532 } 9533 9534 self.buffer = buffer.substr(pos); 9535 return line; 9536 } // 3.4 WebVTT region and WebVTT region settings syntax 9537 9538 9539 function parseRegion(input) { 9540 var settings = new Settings(); 9541 parseOptions(input, function (k, v) { 9542 switch (k) { 9543 case "id": 9544 settings.set(k, v); 9545 break; 9546 9547 case "width": 9548 settings.percent(k, v); 9549 break; 9550 9551 case "lines": 9552 settings.integer(k, v); 9553 break; 9554 9555 case "regionanchor": 9556 case "viewportanchor": 9557 var xy = v.split(','); 9558 9559 if (xy.length !== 2) { 9560 break; 9561 } // We have to make sure both x and y parse, so use a temporary 9562 // settings object here. 9563 9564 9565 var anchor = new Settings(); 9566 anchor.percent("x", xy[0]); 9567 anchor.percent("y", xy[1]); 9568 9569 if (!anchor.has("x") || !anchor.has("y")) { 9570 break; 9571 } 9572 9573 settings.set(k + "X", anchor.get("x")); 9574 settings.set(k + "Y", anchor.get("y")); 9575 break; 9576 9577 case "scroll": 9578 settings.alt(k, v, ["up"]); 9579 break; 9580 } 9581 }, /=/, /\s/); // Create the region, using default values for any values that were not 9582 // specified. 9583 9584 if (settings.has("id")) { 9585 var region = new (self.vttjs.VTTRegion || self.window.VTTRegion)(); 9586 region.width = settings.get("width", 100); 9587 region.lines = settings.get("lines", 3); 9588 region.regionAnchorX = settings.get("regionanchorX", 0); 9589 region.regionAnchorY = settings.get("regionanchorY", 100); 9590 region.viewportAnchorX = settings.get("viewportanchorX", 0); 9591 region.viewportAnchorY = settings.get("viewportanchorY", 100); 9592 region.scroll = settings.get("scroll", ""); // Register the region. 9593 9594 self.onregion && self.onregion(region); // Remember the VTTRegion for later in case we parse any VTTCues that 9595 // reference it. 9596 9597 self.regionList.push({ 9598 id: settings.get("id"), 9599 region: region 9600 }); 9601 } 9602 } // draft-pantos-http-live-streaming-20 9603 // https://tools.ietf.org/html/draft-pantos-http-live-streaming-20#section-3.5 9604 // 3.5 WebVTT 9605 9606 9607 function parseTimestampMap(input) { 9608 var settings = new Settings(); 9609 parseOptions(input, function (k, v) { 9610 switch (k) { 9611 case "MPEGT": 9612 settings.integer(k + 'S', v); 9613 break; 9614 9615 case "LOCA": 9616 settings.set(k + 'L', parseTimeStamp(v)); 9617 break; 9618 } 9619 }, /[^\d]:/, /,/); 9620 self.ontimestampmap && self.ontimestampmap({ 9621 "MPEGTS": settings.get("MPEGTS"), 9622 "LOCAL": settings.get("LOCAL") 9623 }); 9624 } // 3.2 WebVTT metadata header syntax 9625 9626 9627 function parseHeader(input) { 9628 if (input.match(/X-TIMESTAMP-MAP/)) { 9629 // This line contains HLS X-TIMESTAMP-MAP metadata 9630 parseOptions(input, function (k, v) { 9631 switch (k) { 9632 case "X-TIMESTAMP-MAP": 9633 parseTimestampMap(v); 9634 break; 9635 } 9636 }, /=/); 9637 } else { 9638 parseOptions(input, function (k, v) { 9639 switch (k) { 9640 case "Region": 9641 // 3.3 WebVTT region metadata header syntax 9642 parseRegion(v); 9643 break; 9644 } 9645 }, /:/); 9646 } 9647 } // 5.1 WebVTT file parsing. 9648 9649 9650 try { 9651 var line; 9652 9653 if (self.state === "INITIAL") { 9654 // We can't start parsing until we have the first line. 9655 if (!/\r\n|\n/.test(self.buffer)) { 9656 return this; 9657 } 9658 9659 line = collectNextLine(); 9660 var m = line.match(/^WEBVTT([ \t].*)?$/); 9661 9662 if (!m || !m[0]) { 9663 throw new ParsingError(ParsingError.Errors.BadSignature); 9664 } 9665 9666 self.state = "HEADER"; 9667 } 9668 9669 var alreadyCollectedLine = false; 9670 9671 while (self.buffer) { 9672 // We can't parse a line until we have the full line. 9673 if (!/\r\n|\n/.test(self.buffer)) { 9674 return this; 9675 } 9676 9677 if (!alreadyCollectedLine) { 9678 line = collectNextLine(); 9679 } else { 9680 alreadyCollectedLine = false; 9681 } 9682 9683 switch (self.state) { 9684 case "HEADER": 9685 // 13-18 - Allow a header (metadata) under the WEBVTT line. 9686 if (/:/.test(line)) { 9687 parseHeader(line); 9688 } else if (!line) { 9689 // An empty line terminates the header and starts the body (cues). 9690 self.state = "ID"; 9691 } 9692 9693 continue; 9694 9695 case "NOTE": 9696 // Ignore NOTE blocks. 9697 if (!line) { 9698 self.state = "ID"; 9699 } 9700 9701 continue; 9702 9703 case "ID": 9704 // Check for the start of NOTE blocks. 9705 if (/^NOTE($|[ \t])/.test(line)) { 9706 self.state = "NOTE"; 9707 break; 9708 } // 19-29 - Allow any number of line terminators, then initialize new cue values. 9709 9710 9711 if (!line) { 9712 continue; 9713 } 9714 9715 self.cue = new (self.vttjs.VTTCue || self.window.VTTCue)(0, 0, ""); 9716 self.state = "CUE"; // 30-39 - Check if self line contains an optional identifier or timing data. 9717 9718 if (line.indexOf("-->") === -1) { 9719 self.cue.id = line; 9720 continue; 9721 } 9722 9723 // Process line as start of a cue. 9724 9725 /*falls through*/ 9726 9727 case "CUE": 9728 // 40 - Collect cue timings and settings. 9729 try { 9730 parseCue(line, self.cue, self.regionList); 9731 } catch (e) { 9732 self.reportOrThrowError(e); // In case of an error ignore rest of the cue. 9733 9734 self.cue = null; 9735 self.state = "BADCUE"; 9736 continue; 9737 } 9738 9739 self.state = "CUETEXT"; 9740 continue; 9741 9742 case "CUETEXT": 9743 var hasSubstring = line.indexOf("-->") !== -1; // 34 - If we have an empty line then report the cue. 9744 // 35 - If we have the special substring '-->' then report the cue, 9745 // but do not collect the line as we need to process the current 9746 // one as a new cue. 9747 9748 if (!line || hasSubstring && (alreadyCollectedLine = true)) { 9749 // We are done parsing self cue. 9750 self.oncue && self.oncue(self.cue); 9751 self.cue = null; 9752 self.state = "ID"; 9753 continue; 9754 } 9755 9756 if (self.cue.text) { 9757 self.cue.text += "\n"; 9758 } 9759 9760 self.cue.text += line; 9761 continue; 9762 9763 case "BADCUE": 9764 // BADCUE 9765 // 54-62 - Collect and discard the remaining cue. 9766 if (!line) { 9767 self.state = "ID"; 9768 } 9769 9770 continue; 9771 } 9772 } 9773 } catch (e) { 9774 self.reportOrThrowError(e); // If we are currently parsing a cue, report what we have. 9775 9776 if (self.state === "CUETEXT" && self.cue && self.oncue) { 9777 self.oncue(self.cue); 9778 } 9779 9780 self.cue = null; // Enter BADWEBVTT state if header was not parsed correctly otherwise 9781 // another exception occurred so enter BADCUE state. 9782 9783 self.state = self.state === "INITIAL" ? "BADWEBVTT" : "BADCUE"; 9784 } 9785 9786 return this; 9787 }, 9788 flush: function flush() { 9789 var self = this; 9790
vendor: 5,283 bytes, lines 9791-9989
9791 try { 9792 // Finish decoding the stream. 9793 self.buffer += self.decoder.decode(); // Synthesize the end of the current cue or region. 9794 9795 if (self.cue || self.state === "HEADER") { 9796 self.buffer += "\n\n"; 9797 self.parse(); 9798 } // If we've flushed, parsed, and we're still on the INITIAL state then 9799 // that means we don't have enough of the stream to parse the first 9800 // line. 9801 9802 9803 if (self.state === "INITIAL") { 9804 throw new ParsingError(ParsingError.Errors.BadSignature); 9805 } 9806 } catch (e) { 9807 self.reportOrThrowError(e); 9808 } 9809 9810 self.onflush && self.onflush(); 9811 return this; 9812 } 9813 }; 9814 var vtt = WebVTT$1; 9815 9816 /** 9817 * Copyright 2013 vtt.js Contributors 9818 * 9819 * Licensed under the Apache License, Version 2.0 (the "License"); 9820 * you may not use this file except in compliance with the License. 9821 * You may obtain a copy of the License at 9822 * 9823 * http://www.apache.org/licenses/LICENSE-2.0 9824 * 9825 * Unless required by applicable law or agreed to in writing, software 9826 * distributed under the License is distributed on an "AS IS" BASIS, 9827 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9828 * See the License for the specific language governing permissions and 9829 * limitations under the License. 9830 */ 9831 var autoKeyword = "auto"; 9832 var directionSetting = { 9833 "": 1, 9834 "lr": 1, 9835 "rl": 1 9836 }; 9837 var alignSetting = { 9838 "start": 1, 9839 "middle": 1, 9840 "end": 1, 9841 "left": 1, 9842 "right": 1 9843 }; 9844 9845 function findDirectionSetting(value) { 9846 if (typeof value !== "string") { 9847 return false; 9848 } 9849 9850 var dir = directionSetting[value.toLowerCase()]; 9851 return dir ? value.toLowerCase() : false; 9852 } 9853 9854 function findAlignSetting(value) { 9855 if (typeof value !== "string") { 9856 return false; 9857 } 9858 9859 var align = alignSetting[value.toLowerCase()]; 9860 return align ? value.toLowerCase() : false; 9861 } 9862 9863 function VTTCue(startTime, endTime, text) { 9864 /** 9865 * Shim implementation specific properties. These properties are not in 9866 * the spec. 9867 */ 9868 // Lets us know when the VTTCue's data has changed in such a way that we need 9869 // to recompute its display state. This lets us compute its display state 9870 // lazily. 9871 this.hasBeenReset = false; 9872 /** 9873 * VTTCue and TextTrackCue properties 9874 * http://dev.w3.org/html5/webvtt/#vttcue-interface 9875 */ 9876 9877 var _id = ""; 9878 var _pauseOnExit = false; 9879 var _startTime = startTime; 9880 var _endTime = endTime; 9881 var _text = text; 9882 var _region = null; 9883 var _vertical = ""; 9884 var _snapToLines = true; 9885 var _line = "auto"; 9886 var _lineAlign = "start"; 9887 var _position = 50; 9888 var _positionAlign = "middle"; 9889 var _size = 50; 9890 var _align = "middle"; 9891 Object.defineProperties(this, { 9892 "id": { 9893 enumerable: true, 9894 get: function get() { 9895 return _id; 9896 }, 9897 set: function set(value) { 9898 _id = "" + value; 9899 } 9900 }, 9901 "pauseOnExit": { 9902 enumerable: true, 9903 get: function get() { 9904 return _pauseOnExit; 9905 }, 9906 set: function set(value) { 9907 _pauseOnExit = !!value; 9908 } 9909 }, 9910 "startTime": { 9911 enumerable: true, 9912 get: function get() { 9913 return _startTime; 9914 }, 9915 set: function set(value) { 9916 if (typeof value !== "number") { 9917 throw new TypeError("Start time must be set to a number."); 9918 } 9919 9920 _startTime = value; 9921 this.hasBeenReset = true; 9922 } 9923 }, 9924 "endTime": { 9925 enumerable: true, 9926 get: function get() { 9927 return _endTime; 9928 }, 9929 set: function set(value) { 9930 if (typeof value !== "number") { 9931 throw new TypeError("End time must be set to a number."); 9932 } 9933 9934 _endTime = value; 9935 this.hasBeenReset = true; 9936 } 9937 }, 9938 "text": { 9939 enumerable: true, 9940 get: function get() { 9941 return _text; 9942 }, 9943 set: function set(value) { 9944 _text = "" + value; 9945 this.hasBeenReset = true; 9946 } 9947 }, 9948 "region": { 9949 enumerable: true, 9950 get: function get() { 9951 return _region; 9952 }, 9953 set: function set(value) { 9954 _region = value; 9955 this.hasBeenReset = true; 9956 } 9957 }, 9958 "vertical": { 9959 enumerable: true, 9960 get: function get() { 9961 return _vertical; 9962 }, 9963 set: function set(value) { 9964 var setting = findDirectionSetting(value); // Have to check for false because the setting an be an empty string. 9965 9966 if (setting === false) { 9967 throw new SyntaxError("An invalid or illegal string was specified."); 9968 } 9969 9970 _vertical = setting; 9971 this.hasBeenReset = true; 9972 } 9973 }, 9974 "snapToLines": { 9975 enumerable: true, 9976 get: function get() { 9977 return _snapToLines; 9978 }, 9979 set: function set(value) { 9980 _snapToLines = !!value; 9981 this.hasBeenReset = true; 9982 } 9983 }, 9984 "line": { 9985 enumerable: true, 9986 get: function get() { 9987 return _line; 9988 }, 9989 set: function set(value) {
vendor: 4,419 bytes, lines 9990-10155
9990 if (typeof value !== "number" && value !== autoKeyword) { 9991 throw new SyntaxError("An invalid number or illegal string was specified."); 9992 } 9993 9994 _line = value; 9995 this.hasBeenReset = true; 9996 } 9997 }, 9998 "lineAlign": { 9999 enumerable: true, 10000 get: function get() { 10001 return _lineAlign; 10002 }, 10003 set: function set(value) { 10004 var setting = findAlignSetting(value); 10005 10006 if (!setting) { 10007 throw new SyntaxError("An invalid or illegal string was specified."); 10008 } 10009 10010 _lineAlign = setting; 10011 this.hasBeenReset = true; 10012 } 10013 }, 10014 "position": { 10015 enumerable: true, 10016 get: function get() { 10017 return _position; 10018 }, 10019 set: function set(value) { 10020 if (value < 0 || value > 100) { 10021 throw new Error("Position must be between 0 and 100."); 10022 } 10023 10024 _position = value; 10025 this.hasBeenReset = true; 10026 } 10027 }, 10028 "positionAlign": { 10029 enumerable: true, 10030 get: function get() { 10031 return _positionAlign; 10032 }, 10033 set: function set(value) { 10034 var setting = findAlignSetting(value); 10035 10036 if (!setting) { 10037 throw new SyntaxError("An invalid or illegal string was specified."); 10038 } 10039 10040 _positionAlign = setting; 10041 this.hasBeenReset = true; 10042 } 10043 }, 10044 "size": { 10045 enumerable: true, 10046 get: function get() { 10047 return _size; 10048 }, 10049 set: function set(value) { 10050 if (value < 0 || value > 100) { 10051 throw new Error("Size must be between 0 and 100."); 10052 } 10053 10054 _size = value; 10055 this.hasBeenReset = true; 10056 } 10057 }, 10058 "align": { 10059 enumerable: true, 10060 get: function get() { 10061 return _align; 10062 }, 10063 set: function set(value) { 10064 var setting = findAlignSetting(value); 10065 10066 if (!setting) { 10067 throw new SyntaxError("An invalid or illegal string was specified."); 10068 } 10069 10070 _align = setting; 10071 this.hasBeenReset = true; 10072 } 10073 } 10074 }); 10075 /** 10076 * Other <track> spec defined properties 10077 */ 10078 // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#text-track-cue-display-state 10079 10080 this.displayState = undefined; 10081 } 10082 /** 10083 * VTTCue methods 10084 */ 10085 10086 10087 VTTCue.prototype.getCueAsHTML = function () { 10088 // Assume WebVTT.convertCueToDOMTree is on the global. 10089 return WebVTT.convertCueToDOMTree(window, this.text); 10090 }; 10091 10092 var vttcue = VTTCue; 10093 10094 /** 10095 * Copyright 2013 vtt.js Contributors 10096 * 10097 * Licensed under the Apache License, Version 2.0 (the "License"); 10098 * you may not use this file except in compliance with the License. 10099 * You may obtain a copy of the License at 10100 * 10101 * http://www.apache.org/licenses/LICENSE-2.0 10102 * 10103 * Unless required by applicable law or agreed to in writing, software 10104 * distributed under the License is distributed on an "AS IS" BASIS, 10105 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10106 * See the License for the specific language governing permissions and 10107 * limitations under the License. 10108 */ 10109 var scrollSetting = { 10110 "": true, 10111 "up": true 10112 }; 10113 10114 function findScrollSetting(value) { 10115 if (typeof value !== "string") { 10116 return false; 10117 } 10118 10119 var scroll = scrollSetting[value.toLowerCase()]; 10120 return scroll ? value.toLowerCase() : false; 10121 } 10122 10123 function isValidPercentValue(value) { 10124 return typeof value === "number" && value >= 0 && value <= 100; 10125 } // VTTRegion shim http://dev.w3.org/html5/webvtt/#vttregion-interface 10126 10127 10128 function VTTRegion() { 10129 var _width = 100; 10130 var _lines = 3; 10131 var _regionAnchorX = 0; 10132 var _regionAnchorY = 100; 10133 var _viewportAnchorX = 0; 10134 var _viewportAnchorY = 100; 10135 var _scroll = ""; 10136 Object.defineProperties(this, { 10137 "width": { 10138 enumerable: true, 10139 get: function get() { 10140 return _width; 10141 }, 10142 set: function set(value) { 10143 if (!isValidPercentValue(value)) { 10144 throw new Error("Width must be between 0 and 100."); 10145 } 10146 10147 _width = value; 10148 } 10149 }, 10150 "lines": { 10151 enumerable: true, 10152 get: function get() { 10153 return _lines; 10154 }, 10155 set: function set(value) {
10156 if (typeof value !== "number") { 10157 throw new TypeError("Lines must be set to a number."); 10158 } 10159 10160 _lines = value; 10161 } 10162 }, 10163 "regionAnchorY": { 10164 enumerable: true, 10165 get: function get() { 10166 return _regionAnchorY; 10167 }, 10168 set: function set(value) { 10169 if (!isValidPercentValue(value)) { 10170 throw new Error("RegionAnchorX must be between 0 and 100."); 10171 } 10172 10173 _regionAnchorY = value; 10174 } 10175 }, 10176 "regionAnchorX": { 10177 enumerable: true, 10178 get: function get() { 10179 return _regionAnchorX; 10180 }, 10181 set: function set(value) { 10182 if (!isValidPercentValue(value)) { 10183 throw new Error("RegionAnchorY must be between 0 and 100."); 10184 } 10185 10186 _regionAnchorX = value; 10187 } 10188 }, 10189 "viewportAnchorY": { 10190 enumerable: true, 10191 get: function get() { 10192 return _viewportAnchorY; 10193 }, 10194 set: function set(value) { 10195 if (!isValidPercentValue(value)) { 10196 throw new Error("ViewportAnchorY must be between 0 and 100."); 10197 } 10198 10199 _viewportAnchorY = value; 10200 } 10201 }, 10202 "viewportAnchorX": { 10203 enumerable: true, 10204 get: function get() { 10205 return _viewportAnchorX; 10206 }, 10207 set: function set(value) { 10208 if (!isValidPercentValue(value)) { 10209 throw new Error("ViewportAnchorX must be between 0 and 100."); 10210 } 10211 10212 _viewportAnchorX = value; 10213 } 10214 }, 10215 "scroll": { 10216 enumerable: true, 10217 get: function get() { 10218 return _scroll; 10219 }, 10220 set: function set(value) { 10221 var setting = findScrollSetting(value); // Have to check for false as an empty string is a legal value. 10222 10223 if (setting === false) { 10224 throw new SyntaxError("An invalid or illegal string was specified."); 10225 } 10226 10227 _scroll = setting; 10228 } 10229 } 10230 }); 10231 } 10232 10233 var vttregion = VTTRegion; 10234 10235 var browserIndex = createCommonjsModule(function (module) { 10236 /** 10237 * Copyright 2013 vtt.js Contributors 10238 * 10239 * Licensed under the Apache License, Version 2.0 (the "License"); 10240 * you may not use this file except in compliance with the License. 10241 * You may obtain a copy of the License at 10242 * 10243 * http://www.apache.org/licenses/LICENSE-2.0 10244 * 10245 * Unless required by applicable law or agreed to in writing, software 10246 * distributed under the License is distributed on an "AS IS" BASIS, 10247 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10248 * See the License for the specific language governing permissions and 10249 * limitations under the License. 10250 */ 10251 // Default exports for Node. Export the extended versions of VTTCue and 10252 // VTTRegion in Node since we likely want the capability to convert back and 10253 // forth between JSON. If we don't then it's not that big of a deal since we're 10254 // off browser. 10255 var vttjs = module.exports = { 10256 WebVTT: vtt, 10257 VTTCue: vttcue, 10258 VTTRegion: vttregion 10259 }; 10260 window$1.vttjs = vttjs; 10261 window$1.WebVTT = vttjs.WebVTT; 10262 var cueShim = vttjs.VTTCue; 10263 var regionShim = vttjs.VTTRegion; 10264 var nativeVTTCue = window$1.VTTCue; 10265 var nativeVTTRegion = window$1.VTTRegion; 10266 10267 vttjs.shim = function () { 10268 window$1.VTTCue = cueShim; 10269 window$1.VTTRegion = regionShim; 10270 }; 10271 10272 vttjs.restore = function () { 10273 window$1.VTTCue = nativeVTTCue; 10274 window$1.VTTRegion = nativeVTTRegion; 10275 }; 10276 10277 if (!window$1.VTTCue) { 10278 vttjs.shim(); 10279 } 10280 }); 10281 var browserIndex_1 = browserIndex.WebVTT; 10282 var browserIndex_2 = browserIndex.VTTCue; 10283 var browserIndex_3 = browserIndex.VTTRegion; 10284 10285 /** 10286 * An Object containing a structure like: `{src: 'url', type: 'mimetype'}` or string 10287 * that just contains the src url alone. 10288 * * `var SourceObject = {src: 'http://ex.com/video.mp4', type: 'video/mp4'};` 10289 * `var SourceString = 'http://example.com/some-video.mp4';` 10290 * 10291 * @typedef {Object|string} Tech~SourceObject 10292 * 10293 * @property {string} src 10294 * The url to the source 10295 * 10296 * @property {string} type 10297 * The mime type of the source 10298 */ 10299 10300 /** 10301 * A function used by {@link Tech} to create a new {@link TextTrack}. 10302 * 10303 * @private 10304 * 10305 * @param {Tech} self 10306 * An instance of the Tech class. 10307 * 10308 * @param {string} kind 10309 * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata) 10310 * 10311 * @param {string} [label] 10312 * Label to identify the text track 10313 * 10314 * @param {string} [language] 10315 * Two letter language abbreviation 10316 * 10317 * @param {Object} [options={}] 10318 * An object with additional text track options 10319 * 10320 * @return {TextTrack} 10321 * The text track that was created. 10322 */ 10323 10324 function createTrackHelper(self, kind, label, language, options) { 10325 if (options === void 0) { 10326 options = {}; 10327 } 10328 10329 var tracks = self.textTracks(); 10330 options.kind = kind; 10331 10332 if (label) { 10333 options.label = label; 10334 } 10335 10336 if (language) { 10337 options.language = language; 10338 } 10339 10340 options.tech = self; 10341 var track = new ALL.text.TrackClass(options); 10342 tracks.addTrack(track); 10343 return track; 10344 } 10345 /** 10346 * This is the base class for media playback technology controllers, such as 10347 * {@link Flash} and {@link HTML5} 10348 * 10349 * @extends Component 10350 */ 10351 10352 10353 var Tech = 10354 /*#__PURE__*/ 10355 function (_Component) { 10356 _inheritsLoose(Tech, _Component); 10357 10358 /** 10359 * Create an instance of this Tech. 10360 * 10361 * @param {Object} [options] 10362 * The key/value store of player options. 10363 * 10364 * @param {Component~ReadyCallback} ready 10365 * Callback function to call when the `HTML5` Tech is ready. 10366 */ 10367 function Tech(options, ready) { 10368 var _this; 10369 10370 if (options === void 0) { 10371 options = {}; 10372 } 10373 10374 if (ready === void 0) { 10375 ready = function ready() {}; 10376 } 10377 10378 // we don't want the tech to report user activity automatically.
vendor: 36,792 bytes, lines 10379-11651
10379 // This is done manually in addControlsListeners 10380 options.reportTouchActivity = false; 10381 _this = _Component.call(this, null, options, ready) || this; // keep track of whether the current source has played at all to 10382 // implement a very limited played() 10383 10384 _this.hasStarted_ = false; 10385 10386 _this.on('playing', function () { 10387 this.hasStarted_ = true; 10388 }); 10389 10390 _this.on('loadstart', function () { 10391 this.hasStarted_ = false; 10392 }); 10393 10394 ALL.names.forEach(function (name) { 10395 var props = ALL[name]; 10396 10397 if (options && options[props.getterName]) { 10398 _this[props.privateName] = options[props.getterName]; 10399 } 10400 }); // Manually track progress in cases where the browser/flash player doesn't report it. 10401 10402 if (!_this.featuresProgressEvents) { 10403 _this.manualProgressOn(); 10404 } // Manually track timeupdates in cases where the browser/flash player doesn't report it. 10405 10406 10407 if (!_this.featuresTimeupdateEvents) { 10408 _this.manualTimeUpdatesOn(); 10409 } 10410 10411 ['Text', 'Audio', 'Video'].forEach(function (track) { 10412 if (options["native" + track + "Tracks"] === false) { 10413 _this["featuresNative" + track + "Tracks"] = false; 10414 } 10415 }); 10416 10417 if (options.nativeCaptions === false || options.nativeTextTracks === false) { 10418 _this.featuresNativeTextTracks = false; 10419 } else if (options.nativeCaptions === true || options.nativeTextTracks === true) { 10420 _this.featuresNativeTextTracks = true; 10421 } 10422 10423 if (!_this.featuresNativeTextTracks) { 10424 _this.emulateTextTracks(); 10425 } 10426 10427 _this.autoRemoteTextTracks_ = new ALL.text.ListClass(); 10428 10429 _this.initTrackListeners(); // Turn on component tap events only if not using native controls 10430 10431 10432 if (!options.nativeControlsForTouch) { 10433 _this.emitTapEvents(); 10434 } 10435 10436 if (_this.constructor) { 10437 _this.name_ = _this.constructor.name || 'Unknown Tech'; 10438 } 10439 10440 return _this; 10441 } 10442 /** 10443 * A special function to trigger source set in a way that will allow player 10444 * to re-trigger if the player or tech are not ready yet. 10445 * 10446 * @fires Tech#sourceset 10447 * @param {string} src The source string at the time of the source changing. 10448 */ 10449 10450 10451 var _proto = Tech.prototype; 10452 10453 _proto.triggerSourceset = function triggerSourceset(src) { 10454 var _this2 = this; 10455 10456 if (!this.isReady_) { 10457 // on initial ready we have to trigger source set 10458 // 1ms after ready so that player can watch for it. 10459 this.one('ready', function () { 10460 return _this2.setTimeout(function () { 10461 return _this2.triggerSourceset(src); 10462 }, 1); 10463 }); 10464 } 10465 /** 10466 * Fired when the source is set on the tech causing the media element 10467 * to reload. 10468 * 10469 * @see {@link Player#event:sourceset} 10470 * @event Tech#sourceset 10471 * @type {EventTarget~Event} 10472 */ 10473 10474 10475 this.trigger({ 10476 src: src, 10477 type: 'sourceset' 10478 }); 10479 } 10480 /* Fallbacks for unsupported event types 10481 ================================================================================ */ 10482 10483 /** 10484 * Polyfill the `progress` event for browsers that don't support it natively. 10485 * 10486 * @see {@link Tech#trackProgress} 10487 */ 10488 ; 10489 10490 _proto.manualProgressOn = function manualProgressOn() { 10491 this.on('durationchange', this.onDurationChange); 10492 this.manualProgress = true; // Trigger progress watching when a source begins loading 10493 10494 this.one('ready', this.trackProgress); 10495 } 10496 /** 10497 * Turn off the polyfill for `progress` events that was created in 10498 * {@link Tech#manualProgressOn} 10499 */ 10500 ; 10501 10502 _proto.manualProgressOff = function manualProgressOff() { 10503 this.manualProgress = false; 10504 this.stopTrackingProgress(); 10505 this.off('durationchange', this.onDurationChange); 10506 } 10507 /** 10508 * This is used to trigger a `progress` event when the buffered percent changes. It 10509 * sets an interval function that will be called every 500 milliseconds to check if the 10510 * buffer end percent has changed. 10511 * 10512 * > This function is called by {@link Tech#manualProgressOn} 10513 * 10514 * @param {EventTarget~Event} event 10515 * The `ready` event that caused this to run. 10516 * 10517 * @listens Tech#ready 10518 * @fires Tech#progress 10519 */ 10520 ; 10521 10522 _proto.trackProgress = function trackProgress(event) { 10523 this.stopTrackingProgress(); 10524 this.progressInterval = this.setInterval(bind(this, function () { 10525 // Don't trigger unless buffered amount is greater than last time 10526 var numBufferedPercent = this.bufferedPercent(); 10527 10528 if (this.bufferedPercent_ !== numBufferedPercent) { 10529 /** 10530 * See {@link Player#progress} 10531 * 10532 * @event Tech#progress 10533 * @type {EventTarget~Event} 10534 */ 10535 this.trigger('progress'); 10536 } 10537 10538 this.bufferedPercent_ = numBufferedPercent; 10539 10540 if (numBufferedPercent === 1) { 10541 this.stopTrackingProgress(); 10542 } 10543 }), 500); 10544 } 10545 /** 10546 * Update our internal duration on a `durationchange` event by calling 10547 * {@link Tech#duration}. 10548 * 10549 * @param {EventTarget~Event} event 10550 * The `durationchange` event that caused this to run. 10551 * 10552 * @listens Tech#durationchange 10553 */ 10554 ; 10555 10556 _proto.onDurationChange = function onDurationChange(event) { 10557 this.duration_ = this.duration(); 10558 } 10559 /** 10560 * Get and create a `TimeRange` object for buffering. 10561 * 10562 * @return {TimeRange} 10563 * The time range object that was created. 10564 */ 10565 ; 10566 10567 _proto.buffered = function buffered() { 10568 return createTimeRanges(0, 0); 10569 } 10570 /** 10571 * Get the percentage of the current video that is currently buffered. 10572 * 10573 * @return {number} 10574 * A number from 0 to 1 that represents the decimal percentage of the 10575 * video that is buffered. 10576 * 10577 */ 10578 ; 10579 10580 _proto.bufferedPercent = function bufferedPercent$$1() { 10581 return bufferedPercent(this.buffered(), this.duration_); 10582 } 10583 /** 10584 * Turn off the polyfill for `progress` events that was created in 10585 * {@link Tech#manualProgressOn} 10586 * Stop manually tracking progress events by clearing the interval that was set in 10587 * {@link Tech#trackProgress}. 10588 */ 10589 ; 10590 10591 _proto.stopTrackingProgress = function stopTrackingProgress() { 10592 this.clearInterval(this.progressInterval); 10593 } 10594 /** 10595 * Polyfill the `timeupdate` event for browsers that don't support it. 10596 * 10597 * @see {@link Tech#trackCurrentTime} 10598 */ 10599 ; 10600 10601 _proto.manualTimeUpdatesOn = function manualTimeUpdatesOn() { 10602 this.manualTimeUpdates = true; 10603 this.on('play', this.trackCurrentTime); 10604 this.on('pause', this.stopTrackingCurrentTime); 10605 } 10606 /** 10607 * Turn off the polyfill for `timeupdate` events that was created in 10608 * {@link Tech#manualTimeUpdatesOn} 10609 */ 10610 ; 10611 10612 _proto.manualTimeUpdatesOff = function manualTimeUpdatesOff() { 10613 this.manualTimeUpdates = false; 10614 this.stopTrackingCurrentTime(); 10615 this.off('play', this.trackCurrentTime); 10616 this.off('pause', this.stopTrackingCurrentTime); 10617 } 10618 /** 10619 * Sets up an interval function to track current time and trigger `timeupdate` every 10620 * 250 milliseconds. 10621 * 10622 * @listens Tech#play 10623 * @triggers Tech#timeupdate 10624 */ 10625 ; 10626 10627 _proto.trackCurrentTime = function trackCurrentTime() { 10628 if (this.currentTimeInterval) { 10629 this.stopTrackingCurrentTime(); 10630 } 10631 10632 this.currentTimeInterval = this.setInterval(function () { 10633 /** 10634 * Triggered at an interval of 250ms to indicated that time is passing in the video. 10635 * 10636 * @event Tech#timeupdate 10637 * @type {EventTarget~Event} 10638 */ 10639 this.trigger({ 10640 type: 'timeupdate', 10641 target: this, 10642 manuallyTriggered: true 10643 }); // 42 = 24 fps // 250 is what Webkit uses // FF uses 15 10644 }, 250); 10645 } 10646 /** 10647 * Stop the interval function created in {@link Tech#trackCurrentTime} so that the 10648 * `timeupdate` event is no longer triggered. 10649 * 10650 * @listens {Tech#pause} 10651 */ 10652 ; 10653 10654 _proto.stopTrackingCurrentTime = function stopTrackingCurrentTime() { 10655 this.clearInterval(this.currentTimeInterval); // #1002 - if the video ends right before the next timeupdate would happen, 10656 // the progress bar won't make it all the way to the end 10657 10658 this.trigger({ 10659 type: 'timeupdate', 10660 target: this, 10661 manuallyTriggered: true 10662 }); 10663 } 10664 /** 10665 * Turn off all event polyfills, clear the `Tech`s {@link AudioTrackList}, 10666 * {@link VideoTrackList}, and {@link TextTrackList}, and dispose of this Tech. 10667 * 10668 * @fires Component#dispose 10669 */ 10670 ; 10671 10672 _proto.dispose = function dispose() { 10673 // clear out all tracks because we can't reuse them between techs 10674 this.clearTracks(NORMAL.names); // Turn off any manual progress or timeupdate tracking 10675 10676 if (this.manualProgress) { 10677 this.manualProgressOff(); 10678 } 10679 10680 if (this.manualTimeUpdates) { 10681 this.manualTimeUpdatesOff(); 10682 } 10683 10684 _Component.prototype.dispose.call(this); 10685 } 10686 /** 10687 * Clear out a single `TrackList` or an array of `TrackLists` given their names. 10688 * 10689 * > Note: Techs without source handlers should call this between sources for `video` 10690 * & `audio` tracks. You don't want to use them between tracks! 10691 * 10692 * @param {string[]|string} types 10693 * TrackList names to clear, valid names are `video`, `audio`, and 10694 * `text`. 10695 */ 10696 ; 10697 10698 _proto.clearTracks = function clearTracks(types) { 10699 var _this3 = this; 10700 10701 types = [].concat(types); // clear out all tracks because we can't reuse them between techs 10702 10703 types.forEach(function (type) { 10704 var list = _this3[type + "Tracks"]() || []; 10705 var i = list.length; 10706 10707 while (i--) { 10708 var track = list[i]; 10709 10710 if (type === 'text') { 10711 _this3.removeRemoteTextTrack(track); 10712 } 10713 10714 list.removeTrack(track); 10715 } 10716 }); 10717 } 10718 /** 10719 * Remove any TextTracks added via addRemoteTextTrack that are 10720 * flagged for automatic garbage collection 10721 */ 10722 ; 10723 10724 _proto.cleanupAutoTextTracks = function cleanupAutoTextTracks() { 10725 var list = this.autoRemoteTextTracks_ || []; 10726 var i = list.length; 10727 10728 while (i--) { 10729 var track = list[i]; 10730 this.removeRemoteTextTrack(track); 10731 } 10732 } 10733 /** 10734 * Reset the tech, which will removes all sources and reset the internal readyState. 10735 * 10736 * @abstract 10737 */ 10738 ; 10739 10740 _proto.reset = function reset() {} 10741 /** 10742 * Get or set an error on the Tech. 10743 * 10744 * @param {MediaError} [err] 10745 * Error to set on the Tech 10746 * 10747 * @return {MediaError|null} 10748 * The current error object on the tech, or null if there isn't one. 10749 */ 10750 ; 10751 10752 _proto.error = function error(err) { 10753 if (err !== undefined) { 10754 this.error_ = new MediaError(err); 10755 this.trigger('error'); 10756 } 10757 10758 return this.error_; 10759 } 10760 /** 10761 * Returns the `TimeRange`s that have been played through for the current source. 10762 * 10763 * > NOTE: This implementation is incomplete. It does not track the played `TimeRange`. 10764 * It only checks whether the source has played at all or not. 10765 * 10766 * @return {TimeRange} 10767 * - A single time range if this video has played 10768 * - An empty set of ranges if not. 10769 */ 10770 ; 10771 10772 _proto.played = function played() { 10773 if (this.hasStarted_) { 10774 return createTimeRanges(0, 0); 10775 } 10776 10777 return createTimeRanges(); 10778 } 10779 /** 10780 * Causes a manual time update to occur if {@link Tech#manualTimeUpdatesOn} was 10781 * previously called. 10782 * 10783 * @fires Tech#timeupdate 10784 */ 10785 ; 10786 10787 _proto.setCurrentTime = function setCurrentTime() { 10788 // improve the accuracy of manual timeupdates 10789 if (this.manualTimeUpdates) { 10790 /** 10791 * A manual `timeupdate` event. 10792 * 10793 * @event Tech#timeupdate 10794 * @type {EventTarget~Event} 10795 */ 10796 this.trigger({ 10797 type: 'timeupdate', 10798 target: this, 10799 manuallyTriggered: true 10800 }); 10801 } 10802 } 10803 /** 10804 * Turn on listeners for {@link VideoTrackList}, {@link {AudioTrackList}, and 10805 * {@link TextTrackList} events. 10806 * 10807 * This adds {@link EventTarget~EventListeners} for `addtrack`, and `removetrack`. 10808 * 10809 * @fires Tech#audiotrackchange 10810 * @fires Tech#videotrackchange 10811 * @fires Tech#texttrackchange 10812 */ 10813 ; 10814 10815 _proto.initTrackListeners = function initTrackListeners() { 10816 var _this4 = this; 10817 10818 /** 10819 * Triggered when tracks are added or removed on the Tech {@link AudioTrackList} 10820 * 10821 * @event Tech#audiotrackchange 10822 * @type {EventTarget~Event} 10823 */ 10824 10825 /** 10826 * Triggered when tracks are added or removed on the Tech {@link VideoTrackList} 10827 * 10828 * @event Tech#videotrackchange 10829 * @type {EventTarget~Event} 10830 */ 10831 10832 /** 10833 * Triggered when tracks are added or removed on the Tech {@link TextTrackList} 10834 * 10835 * @event Tech#texttrackchange 10836 * @type {EventTarget~Event} 10837 */ 10838 NORMAL.names.forEach(function (name) { 10839 var props = NORMAL[name]; 10840 10841 var trackListChanges = function trackListChanges() { 10842 _this4.trigger(name + "trackchange"); 10843 }; 10844 10845 var tracks = _this4[props.getterName](); 10846 10847 tracks.addEventListener('removetrack', trackListChanges); 10848 tracks.addEventListener('addtrack', trackListChanges); 10849 10850 _this4.on('dispose', function () { 10851 tracks.removeEventListener('removetrack', trackListChanges); 10852 tracks.removeEventListener('addtrack', trackListChanges); 10853 }); 10854 }); 10855 } 10856 /** 10857 * Emulate TextTracks using vtt.js if necessary 10858 * 10859 * @fires Tech#vttjsloaded 10860 * @fires Tech#vttjserror 10861 */ 10862 ; 10863 10864 _proto.addWebVttScript_ = function addWebVttScript_() { 10865 var _this5 = this; 10866 10867 if (window$1.WebVTT) { 10868 return; 10869 } // Initially, Tech.el_ is a child of a dummy-div wait until the Component system 10870 // signals that the Tech is ready at which point Tech.el_ is part of the DOM 10871 // before inserting the WebVTT script 10872 10873 10874 if (document.body.contains(this.el())) { 10875 // load via require if available and vtt.js script location was not passed in 10876 // as an option. novtt builds will turn the above require call into an empty object 10877 // which will cause this if check to always fail. 10878 if (!this.options_['vtt.js'] && isPlain(browserIndex) && Object.keys(browserIndex).length > 0) { 10879 this.trigger('vttjsloaded'); 10880 return; 10881 } // load vtt.js via the script location option or the cdn of no location was 10882 // passed in 10883 10884 10885 var script = document.createElement('script'); 10886 script.src = this.options_['vtt.js'] || 'https://vjs.zencdn.net/vttjs/0.14.1/vtt.min.js'; 10887 10888 script.onload = function () { 10889 /** 10890 * Fired when vtt.js is loaded. 10891 * 10892 * @event Tech#vttjsloaded 10893 * @type {EventTarget~Event} 10894 */ 10895 _this5.trigger('vttjsloaded'); 10896 }; 10897 10898 script.onerror = function () { 10899 /** 10900 * Fired when vtt.js was not loaded due to an error 10901 * 10902 * @event Tech#vttjsloaded 10903 * @type {EventTarget~Event} 10904 */ 10905 _this5.trigger('vttjserror'); 10906 }; 10907 10908 this.on('dispose', function () { 10909 script.onload = null; 10910 script.onerror = null; 10911 }); // but have not loaded yet and we set it to true before the inject so that 10912 // we don't overwrite the injected window.WebVTT if it loads right away 10913 10914 window$1.WebVTT = true; 10915 this.el().parentNode.appendChild(script); 10916 } else { 10917 this.ready(this.addWebVttScript_); 10918 } 10919 } 10920 /** 10921 * Emulate texttracks 10922 * 10923 */ 10924 ; 10925 10926 _proto.emulateTextTracks = function emulateTextTracks() { 10927 var _this6 = this; 10928 10929 var tracks = this.textTracks(); 10930 var remoteTracks = this.remoteTextTracks(); 10931 10932 var handleAddTrack = function handleAddTrack(e) { 10933 return tracks.addTrack(e.track); 10934 }; 10935 10936 var handleRemoveTrack = function handleRemoveTrack(e) { 10937 return tracks.removeTrack(e.track); 10938 }; 10939 10940 remoteTracks.on('addtrack', handleAddTrack); 10941 remoteTracks.on('removetrack', handleRemoveTrack); 10942 this.addWebVttScript_(); 10943 10944 var updateDisplay = function updateDisplay() { 10945 return _this6.trigger('texttrackchange'); 10946 }; 10947 10948 var textTracksChanges = function textTracksChanges() { 10949 updateDisplay(); 10950 10951 for (var i = 0; i < tracks.length; i++) { 10952 var track = tracks[i]; 10953 track.removeEventListener('cuechange', updateDisplay); 10954 10955 if (track.mode === 'showing') { 10956 track.addEventListener('cuechange', updateDisplay); 10957 } 10958 } 10959 }; 10960 10961 textTracksChanges(); 10962 tracks.addEventListener('change', textTracksChanges); 10963 tracks.addEventListener('addtrack', textTracksChanges); 10964 tracks.addEventListener('removetrack', textTracksChanges); 10965 this.on('dispose', function () { 10966 remoteTracks.off('addtrack', handleAddTrack); 10967 remoteTracks.off('removetrack', handleRemoveTrack); 10968 tracks.removeEventListener('change', textTracksChanges); 10969 tracks.removeEventListener('addtrack', textTracksChanges); 10970 tracks.removeEventListener('removetrack', textTracksChanges); 10971 10972 for (var i = 0; i < tracks.length; i++) { 10973 var track = tracks[i]; 10974 track.removeEventListener('cuechange', updateDisplay); 10975 } 10976 }); 10977 } 10978 /** 10979 * Create and returns a remote {@link TextTrack} object. 10980 * 10981 * @param {string} kind 10982 * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata) 10983 * 10984 * @param {string} [label] 10985 * Label to identify the text track 10986 * 10987 * @param {string} [language] 10988 * Two letter language abbreviation 10989 * 10990 * @return {TextTrack} 10991 * The TextTrack that gets created. 10992 */ 10993 ; 10994 10995 _proto.addTextTrack = function addTextTrack(kind, label, language) { 10996 if (!kind) { 10997 throw new Error('TextTrack kind is required but was not provided'); 10998 } 10999 11000 return createTrackHelper(this, kind, label, language); 11001 } 11002 /** 11003 * Create an emulated TextTrack for use by addRemoteTextTrack 11004 * 11005 * This is intended to be overridden by classes that inherit from 11006 * Tech in order to create native or custom TextTracks. 11007 * 11008 * @param {Object} options 11009 * The object should contain the options to initialize the TextTrack with. 11010 * 11011 * @param {string} [options.kind] 11012 * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata). 11013 * 11014 * @param {string} [options.label]. 11015 * Label to identify the text track 11016 * 11017 * @param {string} [options.language] 11018 * Two letter language abbreviation. 11019 * 11020 * @return {HTMLTrackElement} 11021 * The track element that gets created. 11022 */ 11023 ; 11024 11025 _proto.createRemoteTextTrack = function createRemoteTextTrack(options) { 11026 var track = mergeOptions(options, { 11027 tech: this 11028 }); 11029 return new REMOTE.remoteTextEl.TrackClass(track); 11030 } 11031 /** 11032 * Creates a remote text track object and returns an html track element. 11033 * 11034 * > Note: This can be an emulated {@link HTMLTrackElement} or a native one. 11035 * 11036 * @param {Object} options 11037 * See {@link Tech#createRemoteTextTrack} for more detailed properties. 11038 * 11039 * @param {boolean} [manualCleanup=true] 11040 * - When false: the TextTrack will be automatically removed from the video 11041 * element whenever the source changes 11042 * - When True: The TextTrack will have to be cleaned up manually 11043 * 11044 * @return {HTMLTrackElement} 11045 * An Html Track Element. 11046 * 11047 * @deprecated The default functionality for this function will be equivalent 11048 * to "manualCleanup=false" in the future. The manualCleanup parameter will 11049 * also be removed. 11050 */ 11051 ; 11052 11053 _proto.addRemoteTextTrack = function addRemoteTextTrack(options, manualCleanup) { 11054 var _this7 = this; 11055 11056 if (options === void 0) { 11057 options = {}; 11058 } 11059 11060 var htmlTrackElement = this.createRemoteTextTrack(options); 11061 11062 if (manualCleanup !== true && manualCleanup !== false) { 11063 // deprecation warning 11064 log.warn('Calling addRemoteTextTrack without explicitly setting the "manualCleanup" parameter to `true` is deprecated and default to `false` in future version of video.js'); 11065 manualCleanup = true; 11066 } // store HTMLTrackElement and TextTrack to remote list 11067 11068 11069 this.remoteTextTrackEls().addTrackElement_(htmlTrackElement); 11070 this.remoteTextTracks().addTrack(htmlTrackElement.track); 11071 11072 if (manualCleanup !== true) { 11073 // create the TextTrackList if it doesn't exist 11074 this.ready(function () { 11075 return _this7.autoRemoteTextTracks_.addTrack(htmlTrackElement.track); 11076 }); 11077 } 11078 11079 return htmlTrackElement; 11080 } 11081 /** 11082 * Remove a remote text track from the remote `TextTrackList`. 11083 * 11084 * @param {TextTrack} track 11085 * `TextTrack` to remove from the `TextTrackList` 11086 */ 11087 ; 11088 11089 _proto.removeRemoteTextTrack = function removeRemoteTextTrack(track) { 11090 var trackElement = this.remoteTextTrackEls().getTrackElementByTrack_(track); // remove HTMLTrackElement and TextTrack from remote list 11091 11092 this.remoteTextTrackEls().removeTrackElement_(trackElement); 11093 this.remoteTextTracks().removeTrack(track); 11094 this.autoRemoteTextTracks_.removeTrack(track); 11095 } 11096 /** 11097 * Gets available media playback quality metrics as specified by the W3C's Media 11098 * Playback Quality API. 11099 * 11100 * @see [Spec]{@link https://wicg.github.io/media-playback-quality} 11101 * 11102 * @return {Object} 11103 * An object with supported media playback quality metrics 11104 * 11105 * @abstract 11106 */ 11107 ; 11108 11109 _proto.getVideoPlaybackQuality = function getVideoPlaybackQuality() { 11110 return {}; 11111 } 11112 /** 11113 * A method to set a poster from a `Tech`. 11114 * 11115 * @abstract 11116 */ 11117 ; 11118 11119 _proto.setPoster = function setPoster() {} 11120 /** 11121 * A method to check for the presence of the 'playsinline' <video> attribute. 11122 * 11123 * @abstract 11124 */ 11125 ; 11126 11127 _proto.playsinline = function playsinline() {} 11128 /** 11129 * A method to set or unset the 'playsinline' <video> attribute. 11130 * 11131 * @abstract 11132 */ 11133 ; 11134 11135 _proto.setPlaysinline = function setPlaysinline() {} 11136 /** 11137 * Attempt to force override of native audio tracks. 11138 * 11139 * @param {boolean} override - If set to true native audio will be overridden, 11140 * otherwise native audio will potentially be used. 11141 * 11142 * @abstract 11143 */ 11144 ; 11145 11146 _proto.overrideNativeAudioTracks = function overrideNativeAudioTracks() {} 11147 /** 11148 * Attempt to force override of native video tracks. 11149 * 11150 * @param {boolean} override - If set to true native video will be overridden, 11151 * otherwise native video will potentially be used. 11152 * 11153 * @abstract 11154 */ 11155 ; 11156 11157 _proto.overrideNativeVideoTracks = function overrideNativeVideoTracks() {} 11158 /* 11159 * Check if the tech can support the given mime-type. 11160 * 11161 * The base tech does not support any type, but source handlers might 11162 * overwrite this. 11163 * 11164 * @param {string} type 11165 * The mimetype to check for support 11166 * 11167 * @return {string} 11168 * 'probably', 'maybe', or empty string 11169 * 11170 * @see [Spec]{@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/canPlayType} 11171 * 11172 * @abstract 11173 */ 11174 ; 11175 11176 _proto.canPlayType = function canPlayType() { 11177 return ''; 11178 } 11179 /** 11180 * Check if the type is supported by this tech. 11181 * 11182 * The base tech does not support any type, but source handlers might 11183 * overwrite this. 11184 * 11185 * @param {string} type 11186 * The media type to check 11187 * @return {string} Returns the native video element's response 11188 */ 11189 ; 11190 11191 Tech.canPlayType = function canPlayType() { 11192 return ''; 11193 } 11194 /** 11195 * Check if the tech can support the given source 11196 * 11197 * @param {Object} srcObj 11198 * The source object 11199 * @param {Object} options 11200 * The options passed to the tech 11201 * @return {string} 'probably', 'maybe', or '' (empty string) 11202 */ 11203 ; 11204 11205 Tech.canPlaySource = function canPlaySource(srcObj, options) { 11206 return Tech.canPlayType(srcObj.type); 11207 } 11208 /* 11209 * Return whether the argument is a Tech or not. 11210 * Can be passed either a Class like `Html5` or a instance like `player.tech_` 11211 * 11212 * @param {Object} component 11213 * The item to check 11214 * 11215 * @return {boolean} 11216 * Whether it is a tech or not 11217 * - True if it is a tech 11218 * - False if it is not 11219 */ 11220 ; 11221 11222 Tech.isTech = function isTech(component) { 11223 return component.prototype instanceof Tech || component instanceof Tech || component === Tech; 11224 } 11225 /** 11226 * Registers a `Tech` into a shared list for videojs. 11227 * 11228 * @param {string} name 11229 * Name of the `Tech` to register. 11230 * 11231 * @param {Object} tech 11232 * The `Tech` class to register. 11233 */ 11234 ; 11235 11236 Tech.registerTech = function registerTech(name, tech) { 11237 if (!Tech.techs_) { 11238 Tech.techs_ = {}; 11239 } 11240 11241 if (!Tech.isTech(tech)) { 11242 throw new Error("Tech " + name + " must be a Tech"); 11243 } 11244 11245 if (!Tech.canPlayType) { 11246 throw new Error('Techs must have a static canPlayType method on them'); 11247 } 11248 11249 if (!Tech.canPlaySource) { 11250 throw new Error('Techs must have a static canPlaySource method on them'); 11251 } 11252 11253 name = toTitleCase(name); 11254 Tech.techs_[name] = tech; 11255 11256 if (name !== 'Tech') { 11257 // camel case the techName for use in techOrder 11258 Tech.defaultTechOrder_.push(name); 11259 } 11260 11261 return tech; 11262 } 11263 /** 11264 * Get a `Tech` from the shared list by name. 11265 * 11266 * @param {string} name 11267 * `camelCase` or `TitleCase` name of the Tech to get 11268 * 11269 * @return {Tech|undefined} 11270 * The `Tech` or undefined if there was no tech with the name requested. 11271 */ 11272 ; 11273 11274 Tech.getTech = function getTech(name) { 11275 if (!name) { 11276 return; 11277 } 11278 11279 name = toTitleCase(name); 11280 11281 if (Tech.techs_ && Tech.techs_[name]) { 11282 return Tech.techs_[name]; 11283 } 11284 11285 if (window$1 && window$1.videojs && window$1.videojs[name]) { 11286 log.warn("The " + name + " tech was added to the videojs object when it should be registered using videojs.registerTech(name, tech)"); 11287 return window$1.videojs[name]; 11288 } 11289 }; 11290 11291 return Tech; 11292 }(Component); 11293 /** 11294 * Get the {@link VideoTrackList} 11295 * 11296 * @returns {VideoTrackList} 11297 * @method Tech.prototype.videoTracks 11298 */ 11299 11300 /** 11301 * Get the {@link AudioTrackList} 11302 * 11303 * @returns {AudioTrackList} 11304 * @method Tech.prototype.audioTracks 11305 */ 11306 11307 /** 11308 * Get the {@link TextTrackList} 11309 * 11310 * @returns {TextTrackList} 11311 * @method Tech.prototype.textTracks 11312 */ 11313 11314 /** 11315 * Get the remote element {@link TextTrackList} 11316 * 11317 * @returns {TextTrackList} 11318 * @method Tech.prototype.remoteTextTracks 11319 */ 11320 11321 /** 11322 * Get the remote element {@link HtmlTrackElementList} 11323 * 11324 * @returns {HtmlTrackElementList} 11325 * @method Tech.prototype.remoteTextTrackEls 11326 */ 11327 11328 11329 ALL.names.forEach(function (name) { 11330 var props = ALL[name]; 11331 11332 Tech.prototype[props.getterName] = function () { 11333 this[props.privateName] = this[props.privateName] || new props.ListClass(); 11334 return this[props.privateName]; 11335 }; 11336 }); 11337 /** 11338 * List of associated text tracks 11339 * 11340 * @type {TextTrackList} 11341 * @private 11342 * @property Tech#textTracks_ 11343 */ 11344 11345 /** 11346 * List of associated audio tracks. 11347 * 11348 * @type {AudioTrackList} 11349 * @private 11350 * @property Tech#audioTracks_ 11351 */ 11352 11353 /** 11354 * List of associated video tracks. 11355 * 11356 * @type {VideoTrackList} 11357 * @private 11358 * @property Tech#videoTracks_ 11359 */ 11360 11361 /** 11362 * Boolean indicating whether the `Tech` supports volume control. 11363 * 11364 * @type {boolean} 11365 * @default 11366 */ 11367 11368 Tech.prototype.featuresVolumeControl = true; 11369 /** 11370 * Boolean indicating whether the `Tech` supports muting volume. 11371 * 11372 * @type {bolean} 11373 * @default 11374 */ 11375 11376 Tech.prototype.featuresMuteControl = true; 11377 /** 11378 * Boolean indicating whether the `Tech` supports fullscreen resize control. 11379 * Resizing plugins using request fullscreen reloads the plugin 11380 * 11381 * @type {boolean} 11382 * @default 11383 */ 11384 11385 Tech.prototype.featuresFullscreenResize = false; 11386 /** 11387 * Boolean indicating whether the `Tech` supports changing the speed at which the video 11388 * plays. Examples: 11389 * - Set player to play 2x (twice) as fast 11390 * - Set player to play 0.5x (half) as fast 11391 * 11392 * @type {boolean} 11393 * @default 11394 */ 11395 11396 Tech.prototype.featuresPlaybackRate = false; 11397 /** 11398 * Boolean indicating whether the `Tech` supports the `progress` event. This is currently 11399 * not triggered by video-js-swf. This will be used to determine if 11400 * {@link Tech#manualProgressOn} should be called. 11401 * 11402 * @type {boolean} 11403 * @default 11404 */ 11405 11406 Tech.prototype.featuresProgressEvents = false; 11407 /** 11408 * Boolean indicating whether the `Tech` supports the `sourceset` event. 11409 * 11410 * A tech should set this to `true` and then use {@link Tech#triggerSourceset} 11411 * to trigger a {@link Tech#event:sourceset} at the earliest time after getting 11412 * a new source. 11413 * 11414 * @type {boolean} 11415 * @default 11416 */ 11417 11418 Tech.prototype.featuresSourceset = false; 11419 /** 11420 * Boolean indicating whether the `Tech` supports the `timeupdate` event. This is currently 11421 * not triggered by video-js-swf. This will be used to determine if 11422 * {@link Tech#manualTimeUpdates} should be called. 11423 * 11424 * @type {boolean} 11425 * @default 11426 */ 11427 11428 Tech.prototype.featuresTimeupdateEvents = false; 11429 /** 11430 * Boolean indicating whether the `Tech` supports the native `TextTrack`s. 11431 * This will help us integrate with native `TextTrack`s if the browser supports them. 11432 * 11433 * @type {boolean} 11434 * @default 11435 */ 11436 11437 Tech.prototype.featuresNativeTextTracks = false; 11438 /** 11439 * A functional mixin for techs that want to use the Source Handler pattern. 11440 * Source handlers are scripts for handling specific formats. 11441 * The source handler pattern is used for adaptive formats (HLS, DASH) that 11442 * manually load video data and feed it into a Source Buffer (Media Source Extensions) 11443 * Example: `Tech.withSourceHandlers.call(MyTech);` 11444 * 11445 * @param {Tech} _Tech 11446 * The tech to add source handler functions to. 11447 * 11448 * @mixes Tech~SourceHandlerAdditions 11449 */ 11450 11451 Tech.withSourceHandlers = function (_Tech) { 11452 /** 11453 * Register a source handler 11454 * 11455 * @param {Function} handler 11456 * The source handler class 11457 * 11458 * @param {number} [index] 11459 * Register it at the following index 11460 */ 11461 _Tech.registerSourceHandler = function (handler, index) { 11462 var handlers = _Tech.sourceHandlers; 11463 11464 if (!handlers) { 11465 handlers = _Tech.sourceHandlers = []; 11466 } 11467 11468 if (index === undefined) { 11469 // add to the end of the list 11470 index = handlers.length; 11471 } 11472 11473 handlers.splice(index, 0, handler); 11474 }; 11475 /** 11476 * Check if the tech can support the given type. Also checks the 11477 * Techs sourceHandlers. 11478 * 11479 * @param {string} type 11480 * The mimetype to check. 11481 * 11482 * @return {string} 11483 * 'probably', 'maybe', or '' (empty string) 11484 */ 11485 11486 11487 _Tech.canPlayType = function (type) { 11488 var handlers = _Tech.sourceHandlers || []; 11489 var can; 11490 11491 for (var i = 0; i < handlers.length; i++) { 11492 can = handlers[i].canPlayType(type); 11493 11494 if (can) { 11495 return can; 11496 } 11497 } 11498 11499 return ''; 11500 }; 11501 /** 11502 * Returns the first source handler that supports the source. 11503 * 11504 * TODO: Answer question: should 'probably' be prioritized over 'maybe' 11505 * 11506 * @param {Tech~SourceObject} source 11507 * The source object 11508 * 11509 * @param {Object} options 11510 * The options passed to the tech 11511 * 11512 * @return {SourceHandler|null} 11513 * The first source handler that supports the source or null if 11514 * no SourceHandler supports the source 11515 */ 11516 11517 11518 _Tech.selectSourceHandler = function (source, options) { 11519 var handlers = _Tech.sourceHandlers || []; 11520 var can; 11521 11522 for (var i = 0; i < handlers.length; i++) { 11523 can = handlers[i].canHandleSource(source, options); 11524 11525 if (can) { 11526 return handlers[i]; 11527 } 11528 } 11529 11530 return null; 11531 }; 11532 /** 11533 * Check if the tech can support the given source. 11534 * 11535 * @param {Tech~SourceObject} srcObj 11536 * The source object 11537 * 11538 * @param {Object} options 11539 * The options passed to the tech 11540 * 11541 * @return {string} 11542 * 'probably', 'maybe', or '' (empty string) 11543 */ 11544 11545 11546 _Tech.canPlaySource = function (srcObj, options) { 11547 var sh = _Tech.selectSourceHandler(srcObj, options); 11548 11549 if (sh) { 11550 return sh.canHandleSource(srcObj, options); 11551 } 11552 11553 return ''; 11554 }; 11555 /** 11556 * When using a source handler, prefer its implementation of 11557 * any function normally provided by the tech. 11558 */ 11559 11560 11561 var deferrable = ['seekable', 'seeking', 'duration']; 11562 /** 11563 * A wrapper around {@link Tech#seekable} that will call a `SourceHandler`s seekable 11564 * function if it exists, with a fallback to the Techs seekable function. 11565 * 11566 * @method _Tech.seekable 11567 */ 11568 11569 /** 11570 * A wrapper around {@link Tech#duration} that will call a `SourceHandler`s duration 11571 * function if it exists, otherwise it will fallback to the techs duration function. 11572 * 11573 * @method _Tech.duration 11574 */ 11575 11576 deferrable.forEach(function (fnName) { 11577 var originalFn = this[fnName]; 11578 11579 if (typeof originalFn !== 'function') { 11580 return; 11581 } 11582 11583 this[fnName] = function () { 11584 if (this.sourceHandler_ && this.sourceHandler_[fnName]) { 11585 return this.sourceHandler_[fnName].apply(this.sourceHandler_, arguments); 11586 } 11587 11588 return originalFn.apply(this, arguments); 11589 }; 11590 }, _Tech.prototype); 11591 /** 11592 * Create a function for setting the source using a source object 11593 * and source handlers. 11594 * Should never be called unless a source handler was found. 11595 * 11596 * @param {Tech~SourceObject} source 11597 * A source object with src and type keys 11598 */ 11599 11600 _Tech.prototype.setSource = function (source) { 11601 var sh = _Tech.selectSourceHandler(source, this.options_); 11602 11603 if (!sh) { 11604 // Fall back to a native source hander when unsupported sources are 11605 // deliberately set 11606 if (_Tech.nativeSourceHandler) { 11607 sh = _Tech.nativeSourceHandler; 11608 } else { 11609 log.error('No source handler found for the current source.'); 11610 } 11611 } // Dispose any existing source handler 11612 11613 11614 this.disposeSourceHandler(); 11615 this.off('dispose', this.disposeSourceHandler); 11616 11617 if (sh !== _Tech.nativeSourceHandler) { 11618 this.currentSource_ = source; 11619 } 11620 11621 this.sourceHandler_ = sh.handleSource(source, this, this.options_); 11622 this.one('dispose', this.disposeSourceHandler); 11623 }; 11624 /** 11625 * Clean up any existing SourceHandlers and listeners when the Tech is disposed. 11626 * 11627 * @listens Tech#dispose 11628 */ 11629 11630 11631 _Tech.prototype.disposeSourceHandler = function () { 11632 // if we have a source and get another one 11633 // then we are loading something new 11634 // than clear all of our current tracks 11635 if (this.currentSource_) { 11636 this.clearTracks(['audio', 'video']); 11637 this.currentSource_ = null; 11638 } // always clean up auto-text tracks 11639 11640 11641 this.cleanupAutoTextTracks(); 11642 11643 if (this.sourceHandler_) { 11644 if (this.sourceHandler_.dispose) { 11645 this.sourceHandler_.dispose(); 11646 } 11647 11648 this.sourceHandler_ = null; 11649 } 11650 }; 11651 };
vendor: 4,342 bytes, lines 11651-11801
11651 // The base Tech class needs to be registered as a Component. It is the only 11652 // Tech that can be registered as a Component. 11653 11654 11655 Component.registerComponent('Tech', Tech); 11656 Tech.registerTech('Tech', Tech); 11657 /** 11658 * A list of techs that should be added to techOrder on Players 11659 * 11660 * @private 11661 */ 11662 11663 Tech.defaultTechOrder_ = []; 11664 11665 /** 11666 * @file middleware.js 11667 * @module middleware 11668 */ 11669 var middlewares = {}; 11670 var middlewareInstances = {}; 11671 var TERMINATOR = {}; 11672 /** 11673 * A middleware object is a plain JavaScript object that has methods that 11674 * match the {@link Tech} methods found in the lists of allowed 11675 * {@link module:middleware.allowedGetters|getters}, 11676 * {@link module:middleware.allowedSetters|setters}, and 11677 * {@link module:middleware.allowedMediators|mediators}. 11678 * 11679 * @typedef {Object} MiddlewareObject 11680 */ 11681 11682 /** 11683 * A middleware factory function that should return a 11684 * {@link module:middleware~MiddlewareObject|MiddlewareObject}. 11685 * 11686 * This factory will be called for each player when needed, with the player 11687 * passed in as an argument. 11688 * 11689 * @callback MiddlewareFactory 11690 * @param {Player} player 11691 * A Video.js player. 11692 */ 11693 11694 /** 11695 * Define a middleware that the player should use by way of a factory function 11696 * that returns a middleware object. 11697 * 11698 * @param {string} type 11699 * The MIME type to match or `"*"` for all MIME types. 11700 * 11701 * @param {MiddlewareFactory} middleware 11702 * A middleware factory function that will be executed for 11703 * matching types. 11704 */ 11705 11706 function use(type, middleware) { 11707 middlewares[type] = middlewares[type] || []; 11708 middlewares[type].push(middleware); 11709 } 11710 /** 11711 * Asynchronously sets a source using middleware by recursing through any 11712 * matching middlewares and calling `setSource` on each, passing along the 11713 * previous returned value each time. 11714 * 11715 * @param {Player} player 11716 * A {@link Player} instance. 11717 * 11718 * @param {Tech~SourceObject} src 11719 * A source object. 11720 * 11721 * @param {Function} 11722 * The next middleware to run. 11723 */ 11724 11725 function setSource(player, src, next) { 11726 player.setTimeout(function () { 11727 return setSourceHelper(src, middlewares[src.type], next, player); 11728 }, 1); 11729 } 11730 /** 11731 * When the tech is set, passes the tech to each middleware's `setTech` method. 11732 * 11733 * @param {Object[]} middleware 11734 * An array of middleware instances. 11735 * 11736 * @param {Tech} tech 11737 * A Video.js tech. 11738 */ 11739 11740 function setTech(middleware, tech) { 11741 middleware.forEach(function (mw) { 11742 return mw.setTech && mw.setTech(tech); 11743 }); 11744 } 11745 /** 11746 * Calls a getter on the tech first, through each middleware 11747 * from right to left to the player. 11748 * 11749 * @param {Object[]} middleware 11750 * An array of middleware instances. 11751 * 11752 * @param {Tech} tech 11753 * The current tech. 11754 * 11755 * @param {string} method 11756 * A method name. 11757 * 11758 * @return {Mixed} 11759 * The final value from the tech after middleware has intercepted it. 11760 */ 11761 11762 function get(middleware, tech, method) { 11763 return middleware.reduceRight(middlewareIterator(method), tech[method]()); 11764 } 11765 /** 11766 * Takes the argument given to the player and calls the setter method on each 11767 * middleware from left to right to the tech. 11768 * 11769 * @param {Object[]} middleware 11770 * An array of middleware instances. 11771 * 11772 * @param {Tech} tech 11773 * The current tech. 11774 * 11775 * @param {string} method 11776 * A method name. 11777 * 11778 * @param {Mixed} arg 11779 * The value to set on the tech. 11780 * 11781 * @return {Mixed} 11782 * The return value of the `method` of the `tech`. 11783 */ 11784 11785 function set$1(middleware, tech, method, arg) { 11786 return tech[method](middleware.reduce(middlewareIterator(method), arg)); 11787 } 11788 /** 11789 * Takes the argument given to the player and calls the `call` version of the 11790 * method on each middleware from left to right. 11791 * 11792 * Then, call the passed in method on the tech and return the result unchanged 11793 * back to the player, through middleware, this time from right to left. 11794 * 11795 * @param {Object[]} middleware 11796 * An array of middleware instances. 11797 * 11798 * @param {Tech} tech 11799 * The current tech. 11800 * 11801 * @param {string} method
vendor: 44,299 bytes, lines 11802-13373
11802 * A method name. 11803 * 11804 * @param {Mixed} arg 11805 * The value to set on the tech. 11806 * 11807 * @return {Mixed} 11808 * The return value of the `method` of the `tech`, regardless of the 11809 * return values of middlewares. 11810 */ 11811 11812 function mediate(middleware, tech, method, arg) { 11813 if (arg === void 0) { 11814 arg = null; 11815 } 11816 11817 var callMethod = 'call' + toTitleCase(method); 11818 var middlewareValue = middleware.reduce(middlewareIterator(callMethod), arg); 11819 var terminated = middlewareValue === TERMINATOR; // deprecated. The `null` return value should instead return TERMINATOR to 11820 // prevent confusion if a techs method actually returns null. 11821 11822 var returnValue = terminated ? null : tech[method](middlewareValue); 11823 executeRight(middleware, method, returnValue, terminated); 11824 return returnValue; 11825 } 11826 /** 11827 * Enumeration of allowed getters where the keys are method names. 11828 * 11829 * @type {Object} 11830 */ 11831 11832 var allowedGetters = { 11833 buffered: 1, 11834 currentTime: 1, 11835 duration: 1, 11836 seekable: 1, 11837 played: 1, 11838 paused: 1 11839 }; 11840 /** 11841 * Enumeration of allowed setters where the keys are method names. 11842 * 11843 * @type {Object} 11844 */ 11845 11846 var allowedSetters = { 11847 setCurrentTime: 1 11848 }; 11849 /** 11850 * Enumeration of allowed mediators where the keys are method names. 11851 * 11852 * @type {Object} 11853 */ 11854 11855 var allowedMediators = { 11856 play: 1, 11857 pause: 1 11858 }; 11859 11860 function middlewareIterator(method) { 11861 return function (value, mw) { 11862 // if the previous middleware terminated, pass along the termination 11863 if (value === TERMINATOR) { 11864 return TERMINATOR; 11865 } 11866 11867 if (mw[method]) { 11868 return mw[method](value); 11869 } 11870 11871 return value; 11872 }; 11873 } 11874 11875 function executeRight(mws, method, value, terminated) { 11876 for (var i = mws.length - 1; i >= 0; i--) { 11877 var mw = mws[i]; 11878 11879 if (mw[method]) { 11880 mw[method](terminated, value); 11881 } 11882 } 11883 } 11884 /** 11885 * Clear the middleware cache for a player. 11886 * 11887 * @param {Player} player 11888 * A {@link Player} instance. 11889 */ 11890 11891 11892 function clearCacheForPlayer(player) { 11893 middlewareInstances[player.id()] = null; 11894 } 11895 /** 11896 * { 11897 * [playerId]: [[mwFactory, mwInstance], ...] 11898 * } 11899 * 11900 * @private 11901 */ 11902 11903 function getOrCreateFactory(player, mwFactory) { 11904 var mws = middlewareInstances[player.id()]; 11905 var mw = null; 11906 11907 if (mws === undefined || mws === null) { 11908 mw = mwFactory(player); 11909 middlewareInstances[player.id()] = [[mwFactory, mw]]; 11910 return mw; 11911 } 11912 11913 for (var i = 0; i < mws.length; i++) { 11914 var _mws$i = mws[i], 11915 mwf = _mws$i[0], 11916 mwi = _mws$i[1]; 11917 11918 if (mwf !== mwFactory) { 11919 continue; 11920 } 11921 11922 mw = mwi; 11923 } 11924 11925 if (mw === null) { 11926 mw = mwFactory(player); 11927 mws.push([mwFactory, mw]); 11928 } 11929 11930 return mw; 11931 } 11932 11933 function setSourceHelper(src, middleware, next, player, acc, lastRun) { 11934 if (src === void 0) { 11935 src = {}; 11936 } 11937 11938 if (middleware === void 0) { 11939 middleware = []; 11940 } 11941 11942 if (acc === void 0) { 11943 acc = []; 11944 } 11945 11946 if (lastRun === void 0) { 11947 lastRun = false; 11948 } 11949 11950 var _middleware = middleware, 11951 mwFactory = _middleware[0], 11952 mwrest = _middleware.slice(1); // if mwFactory is a string, then we're at a fork in the road 11953 11954 11955 if (typeof mwFactory === 'string') { 11956 setSourceHelper(src, middlewares[mwFactory], next, player, acc, lastRun); // if we have an mwFactory, call it with the player to get the mw, 11957 // then call the mw's setSource method 11958 } else if (mwFactory) { 11959 var mw = getOrCreateFactory(player, mwFactory); // if setSource isn't present, implicitly select this middleware 11960 11961 if (!mw.setSource) { 11962 acc.push(mw); 11963 return setSourceHelper(src, mwrest, next, player, acc, lastRun); 11964 } 11965 11966 mw.setSource(assign({}, src), function (err, _src) { 11967 // something happened, try the next middleware on the current level 11968 // make sure to use the old src 11969 if (err) { 11970 return setSourceHelper(src, mwrest, next, player, acc, lastRun); 11971 } // we've succeeded, now we need to go deeper 11972 11973 11974 acc.push(mw); // if it's the same type, continue down the current chain 11975 // otherwise, we want to go down the new chain 11976 11977 setSourceHelper(_src, src.type === _src.type ? mwrest : middlewares[_src.type], next, player, acc, lastRun); 11978 }); 11979 } else if (mwrest.length) { 11980 setSourceHelper(src, mwrest, next, player, acc, lastRun); 11981 } else if (lastRun) { 11982 next(src, acc); 11983 } else { 11984 setSourceHelper(src, middlewares['*'], next, player, acc, true); 11985 } 11986 } 11987 11988 /** 11989 * Mimetypes 11990 * 11991 * @see http://hul.harvard.edu/ois/////systems/wax/wax-public-help/mimetypes.htm 11992 * @typedef Mimetypes~Kind 11993 * @enum 11994 */ 11995 11996 var MimetypesKind = { 11997 opus: 'video/ogg', 11998 ogv: 'video/ogg', 11999 mp4: 'video/mp4', 12000 mov: 'video/mp4', 12001 m4v: 'video/mp4', 12002 mkv: 'video/x-matroska', 12003 mp3: 'audio/mpeg', 12004 aac: 'audio/aac', 12005 oga: 'audio/ogg', 12006 m3u8: 'application/x-mpegURL', 12007 jpg: 'image/jpeg', 12008 jpeg: 'image/jpeg', 12009 gif: 'image/gif', 12010 png: 'image/png', 12011 svg: 'image/svg+xml', 12012 webp: 'image/webp' 12013 }; 12014 /** 12015 * Get the mimetype of a given src url if possible 12016 * 12017 * @param {string} src 12018 * The url to the src 12019 * 12020 * @return {string} 12021 * return the mimetype if it was known or empty string otherwise 12022 */ 12023 12024 var getMimetype = function getMimetype(src) { 12025 if (src === void 0) { 12026 src = ''; 12027 } 12028 12029 var ext = getFileExtension(src); 12030 var mimetype = MimetypesKind[ext.toLowerCase()]; 12031 return mimetype || ''; 12032 }; 12033 /** 12034 * Find the mime type of a given source string if possible. Uses the player 12035 * source cache. 12036 * 12037 * @param {Player} player 12038 * The player object 12039 * 12040 * @param {string} src 12041 * The source string 12042 * 12043 * @return {string} 12044 * The type that was found 12045 */ 12046 12047 var findMimetype = function findMimetype(player, src) { 12048 if (!src) { 12049 return ''; 12050 } // 1. check for the type in the `source` cache 12051 12052 12053 if (player.cache_.source.src === src && player.cache_.source.type) { 12054 return player.cache_.source.type; 12055 } // 2. see if we have this source in our `currentSources` cache 12056 12057 12058 var matchingSources = player.cache_.sources.filter(function (s) { 12059 return s.src === src; 12060 }); 12061 12062 if (matchingSources.length) { 12063 return matchingSources[0].type; 12064 } // 3. look for the src url in source elements and use the type there 12065 12066 12067 var sources = player.$$('source'); 12068 12069 for (var i = 0; i < sources.length; i++) { 12070 var s = sources[i]; 12071 12072 if (s.type && s.src && s.src === src) { 12073 return s.type; 12074 } 12075 } // 4. finally fallback to our list of mime types based on src url extension 12076 12077 12078 return getMimetype(src); 12079 }; 12080 12081 /** 12082 * @module filter-source 12083 */ 12084 /** 12085 * Filter out single bad source objects or multiple source objects in an 12086 * array. Also flattens nested source object arrays into a 1 dimensional 12087 * array of source objects. 12088 * 12089 * @param {Tech~SourceObject|Tech~SourceObject[]} src 12090 * The src object to filter 12091 * 12092 * @return {Tech~SourceObject[]} 12093 * An array of sourceobjects containing only valid sources 12094 * 12095 * @private 12096 */ 12097 12098 var filterSource = function filterSource(src) { 12099 // traverse array 12100 if (Array.isArray(src)) { 12101 var newsrc = []; 12102 src.forEach(function (srcobj) { 12103 srcobj = filterSource(srcobj); 12104 12105 if (Array.isArray(srcobj)) { 12106 newsrc = newsrc.concat(srcobj); 12107 } else if (isObject(srcobj)) { 12108 newsrc.push(srcobj); 12109 } 12110 }); 12111 src = newsrc; 12112 } else if (typeof src === 'string' && src.trim()) { 12113 // convert string into object 12114 src = [fixSource({ 12115 src: src 12116 })]; 12117 } else if (isObject(src) && typeof src.src === 'string' && src.src && src.src.trim()) { 12118 // src is already valid 12119 src = [fixSource(src)]; 12120 } else { 12121 // invalid source, turn it into an empty array 12122 src = []; 12123 } 12124 12125 return src; 12126 }; 12127 /** 12128 * Checks src mimetype, adding it when possible 12129 * 12130 * @param {Tech~SourceObject} src 12131 * The src object to check 12132 * @return {Tech~SourceObject} 12133 * src Object with known type 12134 */ 12135 12136 12137 function fixSource(src) { 12138 var mimetype = getMimetype(src.src); 12139 12140 if (!src.type && mimetype) { 12141 src.type = mimetype; 12142 } 12143 12144 return src; 12145 } 12146 12147 /** 12148 * The `MediaLoader` is the `Component` that decides which playback technology to load 12149 * when a player is initialized. 12150 * 12151 * @extends Component 12152 */ 12153 12154 var MediaLoader = 12155 /*#__PURE__*/ 12156 function (_Component) { 12157 _inheritsLoose(MediaLoader, _Component); 12158 12159 /** 12160 * Create an instance of this class. 12161 * 12162 * @param {Player} player 12163 * The `Player` that this class should attach to. 12164 * 12165 * @param {Object} [options] 12166 * The key/value store of player options. 12167 * 12168 * @param {Component~ReadyCallback} [ready] 12169 * The function that is run when this component is ready. 12170 */ 12171 function MediaLoader(player, options, ready) { 12172 var _this; 12173 12174 // MediaLoader has no element 12175 var options_ = mergeOptions({ 12176 createEl: false 12177 }, options); 12178 _this = _Component.call(this, player, options_, ready) || this; // If there are no sources when the player is initialized, 12179 // load the first supported playback technology. 12180 12181 if (!options.playerOptions.sources || options.playerOptions.sources.length === 0) { 12182 for (var i = 0, j = options.playerOptions.techOrder; i < j.length; i++) { 12183 var techName = toTitleCase(j[i]); 12184 var tech = Tech.getTech(techName); // Support old behavior of techs being registered as components. 12185 // Remove once that deprecated behavior is removed. 12186 12187 if (!techName) { 12188 tech = Component.getComponent(techName); 12189 } // Check if the browser supports this technology 12190 12191 12192 if (tech && tech.isSupported()) { 12193 player.loadTech_(techName); 12194 break; 12195 } 12196 } 12197 } else { 12198 // Loop through playback technologies (HTML5, Flash) and check for support. 12199 // Then load the best source. 12200 // A few assumptions here: 12201 // All playback technologies respect preload false. 12202 player.src(options.playerOptions.sources); 12203 } 12204 12205 return _this; 12206 } 12207 12208 return MediaLoader; 12209 }(Component); 12210 12211 Component.registerComponent('MediaLoader', MediaLoader); 12212 12213 /** 12214 * Component which is clickable or keyboard actionable, but is not a 12215 * native HTML button. 12216 * 12217 * @extends Component 12218 */ 12219 12220 var ClickableComponent = 12221 /*#__PURE__*/ 12222 function (_Component) { 12223 _inheritsLoose(ClickableComponent, _Component); 12224 12225 /** 12226 * Creates an instance of this class. 12227 * 12228 * @param {Player} player 12229 * The `Player` that this class should be attached to. 12230 * 12231 * @param {Object} [options] 12232 * The key/value store of player options. 12233 */ 12234 function ClickableComponent(player, options) { 12235 var _this; 12236 12237 _this = _Component.call(this, player, options) || this; 12238 12239 _this.emitTapEvents(); 12240 12241 _this.enable(); 12242 12243 return _this; 12244 } 12245 /** 12246 * Create the `ClickableComponent`s DOM element. 12247 * 12248 * @param {string} [tag=div] 12249 * The element's node type. 12250 * 12251 * @param {Object} [props={}] 12252 * An object of properties that should be set on the element. 12253 * 12254 * @param {Object} [attributes={}] 12255 * An object of attributes that should be set on the element. 12256 * 12257 * @return {Element} 12258 * The element that gets created. 12259 */ 12260 12261 12262 var _proto = ClickableComponent.prototype; 12263 12264 _proto.createEl = function createEl$$1(tag, props, attributes) { 12265 if (tag === void 0) { 12266 tag = 'div'; 12267 } 12268 12269 if (props === void 0) { 12270 props = {}; 12271 } 12272 12273 if (attributes === void 0) { 12274 attributes = {}; 12275 } 12276 12277 props = assign({ 12278 innerHTML: '<span aria-hidden="true" class="vjs-icon-placeholder"></span>', 12279 className: this.buildCSSClass(), 12280 tabIndex: 0 12281 }, props); 12282 12283 if (tag === 'button') { 12284 log.error("Creating a ClickableComponent with an HTML element of " + tag + " is not supported; use a Button instead."); 12285 } // Add ARIA attributes for clickable element which is not a native HTML button 12286 12287 12288 attributes = assign({ 12289 role: 'button' 12290 }, attributes); 12291 this.tabIndex_ = props.tabIndex; 12292 12293 var el = _Component.prototype.createEl.call(this, tag, props, attributes); 12294 12295 this.createControlTextEl(el); 12296 return el; 12297 }; 12298 12299 _proto.dispose = function dispose() { 12300 // remove controlTextEl_ on dispose 12301 this.controlTextEl_ = null; 12302 12303 _Component.prototype.dispose.call(this); 12304 } 12305 /** 12306 * Create a control text element on this `ClickableComponent` 12307 * 12308 * @param {Element} [el] 12309 * Parent element for the control text. 12310 * 12311 * @return {Element} 12312 * The control text element that gets created. 12313 */ 12314 ; 12315 12316 _proto.createControlTextEl = function createControlTextEl(el) { 12317 this.controlTextEl_ = createEl('span', { 12318 className: 'vjs-control-text' 12319 }, { 12320 // let the screen reader user know that the text of the element may change 12321 'aria-live': 'polite' 12322 }); 12323 12324 if (el) { 12325 el.appendChild(this.controlTextEl_); 12326 } 12327 12328 this.controlText(this.controlText_, el); 12329 return this.controlTextEl_; 12330 } 12331 /** 12332 * Get or set the localize text to use for the controls on the `ClickableComponent`. 12333 * 12334 * @param {string} [text] 12335 * Control text for element. 12336 * 12337 * @param {Element} [el=this.el()] 12338 * Element to set the title on. 12339 * 12340 * @return {string} 12341 * - The control text when getting 12342 */ 12343 ; 12344 12345 _proto.controlText = function controlText(text, el) { 12346 if (el === void 0) { 12347 el = this.el(); 12348 } 12349 12350 if (text === undefined) { 12351 return this.controlText_ || 'Need Text'; 12352 } 12353 12354 var localizedText = this.localize(text); 12355 this.controlText_ = text; 12356 textContent(this.controlTextEl_, localizedText); 12357 12358 if (!this.nonIconControl) { 12359 // Set title attribute if only an icon is shown 12360 el.setAttribute('title', localizedText); 12361 } 12362 } 12363 /** 12364 * Builds the default DOM `className`. 12365 * 12366 * @return {string} 12367 * The DOM `className` for this object. 12368 */ 12369 ; 12370 12371 _proto.buildCSSClass = function buildCSSClass() { 12372 return "vjs-control vjs-button " + _Component.prototype.buildCSSClass.call(this); 12373 } 12374 /** 12375 * Enable this `ClickableComponent` 12376 */ 12377 ; 12378 12379 _proto.enable = function enable() { 12380 if (!this.enabled_) { 12381 this.enabled_ = true; 12382 this.removeClass('vjs-disabled'); 12383 this.el_.setAttribute('aria-disabled', 'false'); 12384 12385 if (typeof this.tabIndex_ !== 'undefined') { 12386 this.el_.setAttribute('tabIndex', this.tabIndex_); 12387 } 12388 12389 this.on(['tap', 'click'], this.handleClick); 12390 this.on('keydown', this.handleKeyDown); 12391 } 12392 } 12393 /** 12394 * Disable this `ClickableComponent` 12395 */ 12396 ; 12397 12398 _proto.disable = function disable() { 12399 this.enabled_ = false; 12400 this.addClass('vjs-disabled'); 12401 this.el_.setAttribute('aria-disabled', 'true'); 12402 12403 if (typeof this.tabIndex_ !== 'undefined') { 12404 this.el_.removeAttribute('tabIndex'); 12405 } 12406 12407 this.off(['tap', 'click'], this.handleClick); 12408 this.off('keydown', this.handleKeyDown); 12409 } 12410 /** 12411 * Event handler that is called when a `ClickableComponent` receives a 12412 * `click` or `tap` event. 12413 * 12414 * @param {EventTarget~Event} event 12415 * The `tap` or `click` event that caused this function to be called. 12416 * 12417 * @listens tap 12418 * @listens click 12419 * @abstract 12420 */ 12421 ; 12422 12423 _proto.handleClick = function handleClick(event) {} 12424 /** 12425 * Event handler that is called when a `ClickableComponent` receives a 12426 * `keydown` event. 12427 * 12428 * By default, if the key is Space or Enter, it will trigger a `click` event. 12429 * 12430 * @param {EventTarget~Event} event 12431 * The `keydown` event that caused this function to be called. 12432 * 12433 * @listens keydown 12434 */ 12435 ; 12436 12437 _proto.handleKeyDown = function handleKeyDown(event) { 12438 // Support Space or Enter key operation to fire a click event. Also, 12439 // prevent the event from propagating through the DOM and triggering 12440 // Player hotkeys. 12441 if (keycode.isEventKey(event, 'Space') || keycode.isEventKey(event, 'Enter')) { 12442 event.preventDefault(); 12443 event.stopPropagation(); 12444 this.trigger('click'); 12445 } else { 12446 // Pass keypress handling up for unsupported keys 12447 _Component.prototype.handleKeyDown.call(this, event); 12448 } 12449 }; 12450 12451 return ClickableComponent; 12452 }(Component); 12453 12454 Component.registerComponent('ClickableComponent', ClickableComponent); 12455 12456 /** 12457 * A `ClickableComponent` that handles showing the poster image for the player. 12458 * 12459 * @extends ClickableComponent 12460 */ 12461 12462 var PosterImage = 12463 /*#__PURE__*/ 12464 function (_ClickableComponent) { 12465 _inheritsLoose(PosterImage, _ClickableComponent); 12466 12467 /** 12468 * Create an instance of this class. 12469 * 12470 * @param {Player} player 12471 * The `Player` that this class should attach to. 12472 * 12473 * @param {Object} [options] 12474 * The key/value store of player options. 12475 */ 12476 function PosterImage(player, options) { 12477 var _this; 12478 12479 _this = _ClickableComponent.call(this, player, options) || this; 12480 12481 _this.update(); 12482 12483 player.on('posterchange', bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.update)); 12484 return _this; 12485 } 12486 /** 12487 * Clean up and dispose of the `PosterImage`. 12488 */ 12489 12490 12491 var _proto = PosterImage.prototype; 12492 12493 _proto.dispose = function dispose() { 12494 this.player().off('posterchange', this.update); 12495 12496 _ClickableComponent.prototype.dispose.call(this); 12497 } 12498 /** 12499 * Create the `PosterImage`s DOM element. 12500 * 12501 * @return {Element} 12502 * The element that gets created. 12503 */ 12504 ; 12505 12506 _proto.createEl = function createEl$$1() { 12507 var el = createEl('div', { 12508 className: 'vjs-poster', 12509 // Don't want poster to be tabbable. 12510 tabIndex: -1 12511 }); 12512 return el; 12513 } 12514 /** 12515 * An {@link EventTarget~EventListener} for {@link Player#posterchange} events. 12516 * 12517 * @listens Player#posterchange 12518 * 12519 * @param {EventTarget~Event} [event] 12520 * The `Player#posterchange` event that triggered this function. 12521 */ 12522 ; 12523 12524 _proto.update = function update(event) { 12525 var url = this.player().poster(); 12526 this.setSrc(url); // If there's no poster source we should display:none on this component 12527 // so it's not still clickable or right-clickable 12528 12529 if (url) { 12530 this.show(); 12531 } else { 12532 this.hide(); 12533 } 12534 } 12535 /** 12536 * Set the source of the `PosterImage` depending on the display method. 12537 * 12538 * @param {string} url 12539 * The URL to the source for the `PosterImage`. 12540 */ 12541 ; 12542 12543 _proto.setSrc = function setSrc(url) { 12544 var backgroundImage = ''; // Any falsy value should stay as an empty string, otherwise 12545 // this will throw an extra error 12546 12547 if (url) { 12548 backgroundImage = "url(\"" + url + "\")"; 12549 } 12550 12551 this.el_.style.backgroundImage = backgroundImage; 12552 } 12553 /** 12554 * An {@link EventTarget~EventListener} for clicks on the `PosterImage`. See 12555 * {@link ClickableComponent#handleClick} for instances where this will be triggered. 12556 * 12557 * @listens tap 12558 * @listens click 12559 * @listens keydown 12560 * 12561 * @param {EventTarget~Event} event 12562 + The `click`, `tap` or `keydown` event that caused this function to be called. 12563 */ 12564 ; 12565 12566 _proto.handleClick = function handleClick(event) { 12567 // We don't want a click to trigger playback when controls are disabled 12568 if (!this.player_.controls()) { 12569 return; 12570 } 12571 12572 this.player_.tech(true).focus(); 12573 12574 if (this.player_.paused()) { 12575 silencePromise(this.player_.play()); 12576 } else { 12577 this.player_.pause(); 12578 } 12579 }; 12580 12581 return PosterImage; 12582 }(ClickableComponent); 12583 12584 Component.registerComponent('PosterImage', PosterImage); 12585 12586 var darkGray = '#222'; 12587 var lightGray = '#ccc'; 12588 var fontMap = { 12589 monospace: 'monospace', 12590 sansSerif: 'sans-serif', 12591 serif: 'serif', 12592 monospaceSansSerif: '"Andale Mono", "Lucida Console", monospace', 12593 monospaceSerif: '"Courier New", monospace', 12594 proportionalSansSerif: 'sans-serif', 12595 proportionalSerif: 'serif', 12596 casual: '"Comic Sans MS", Impact, fantasy', 12597 script: '"Monotype Corsiva", cursive', 12598 smallcaps: '"Andale Mono", "Lucida Console", monospace, sans-serif' 12599 }; 12600 /** 12601 * Construct an rgba color from a given hex color code. 12602 * 12603 * @param {number} color 12604 * Hex number for color, like #f0e or #f604e2. 12605 * 12606 * @param {number} opacity 12607 * Value for opacity, 0.0 - 1.0. 12608 * 12609 * @return {string} 12610 * The rgba color that was created, like 'rgba(255, 0, 0, 0.3)'. 12611 */ 12612 12613 function constructColor(color, opacity) { 12614 var hex; 12615 12616 if (color.length === 4) { 12617 // color looks like "#f0e" 12618 hex = color[1] + color[1] + color[2] + color[2] + color[3] + color[3]; 12619 } else if (color.length === 7) { 12620 // color looks like "#f604e2" 12621 hex = color.slice(1); 12622 } else { 12623 throw new Error('Invalid color code provided, ' + color + '; must be formatted as e.g. #f0e or #f604e2.'); 12624 } 12625 12626 return 'rgba(' + parseInt(hex.slice(0, 2), 16) + ',' + parseInt(hex.slice(2, 4), 16) + ',' + parseInt(hex.slice(4, 6), 16) + ',' + opacity + ')'; 12627 } 12628 /** 12629 * Try to update the style of a DOM element. Some style changes will throw an error, 12630 * particularly in IE8. Those should be noops. 12631 * 12632 * @param {Element} el 12633 * The DOM element to be styled. 12634 * 12635 * @param {string} style 12636 * The CSS property on the element that should be styled. 12637 * 12638 * @param {string} rule 12639 * The style rule that should be applied to the property. 12640 * 12641 * @private 12642 */ 12643 12644 function tryUpdateStyle(el, style, rule) { 12645 try { 12646 el.style[style] = rule; 12647 } catch (e) { 12648 // Satisfies linter. 12649 return; 12650 } 12651 } 12652 /** 12653 * The component for displaying text track cues. 12654 * 12655 * @extends Component 12656 */ 12657 12658 12659 var TextTrackDisplay = 12660 /*#__PURE__*/ 12661 function (_Component) { 12662 _inheritsLoose(TextTrackDisplay, _Component); 12663 12664 /** 12665 * Creates an instance of this class. 12666 * 12667 * @param {Player} player 12668 * The `Player` that this class should be attached to. 12669 * 12670 * @param {Object} [options] 12671 * The key/value store of player options. 12672 * 12673 * @param {Component~ReadyCallback} [ready] 12674 * The function to call when `TextTrackDisplay` is ready. 12675 */ 12676 function TextTrackDisplay(player, options, ready) { 12677 var _this; 12678 12679 _this = _Component.call(this, player, options, ready) || this; 12680 var updateDisplayHandler = bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.updateDisplay); 12681 player.on('loadstart', bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.toggleDisplay)); 12682 player.on('texttrackchange', updateDisplayHandler); 12683 player.on('loadedmetadata', bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.preselectTrack)); // This used to be called during player init, but was causing an error 12684 // if a track should show by default and the display hadn't loaded yet. 12685 // Should probably be moved to an external track loader when we support 12686 // tracks that don't need a display. 12687 12688 player.ready(bind(_assertThisInitialized(_assertThisInitialized(_this)), function () { 12689 if (player.tech_ && player.tech_.featuresNativeTextTracks) { 12690 this.hide(); 12691 return; 12692 } 12693 12694 player.on('fullscreenchange', updateDisplayHandler); 12695 player.on('playerresize', updateDisplayHandler); 12696 window$1.addEventListener('orientationchange', updateDisplayHandler); 12697 player.on('dispose', function () { 12698 return window$1.removeEventListener('orientationchange', updateDisplayHandler); 12699 }); 12700 var tracks = this.options_.playerOptions.tracks || []; 12701 12702 for (var i = 0; i < tracks.length; i++) { 12703 this.player_.addRemoteTextTrack(tracks[i], true); 12704 } 12705 12706 this.preselectTrack(); 12707 })); 12708 return _this; 12709 } 12710 /** 12711 * Preselect a track following this precedence: 12712 * - matches the previously selected {@link TextTrack}'s language and kind 12713 * - matches the previously selected {@link TextTrack}'s language only 12714 * - is the first default captions track 12715 * - is the first default descriptions track 12716 * 12717 * @listens Player#loadstart 12718 */ 12719 12720 12721 var _proto = TextTrackDisplay.prototype; 12722 12723 _proto.preselectTrack = function preselectTrack() { 12724 var modes = { 12725 captions: 1, 12726 subtitles: 1 12727 }; 12728 var trackList = this.player_.textTracks(); 12729 var userPref = this.player_.cache_.selectedLanguage; 12730 var firstDesc; 12731 var firstCaptions; 12732 var preferredTrack; 12733 12734 for (var i = 0; i < trackList.length; i++) { 12735 var track = trackList[i]; 12736 12737 if (userPref && userPref.enabled && userPref.language && userPref.language === track.language && track.kind in modes) { 12738 // Always choose the track that matches both language and kind 12739 if (track.kind === userPref.kind) { 12740 preferredTrack = track; // or choose the first track that matches language 12741 } else if (!preferredTrack) { 12742 preferredTrack = track; 12743 } // clear everything if offTextTrackMenuItem was clicked 12744 12745 } else if (userPref && !userPref.enabled) { 12746 preferredTrack = null; 12747 firstDesc = null; 12748 firstCaptions = null; 12749 } else if (track.default) { 12750 if (track.kind === 'descriptions' && !firstDesc) { 12751 firstDesc = track; 12752 } else if (track.kind in modes && !firstCaptions) { 12753 firstCaptions = track; 12754 } 12755 } 12756 } // The preferredTrack matches the user preference and takes 12757 // precedence over all the other tracks. 12758 // So, display the preferredTrack before the first default track 12759 // and the subtitles/captions track before the descriptions track 12760 12761 12762 if (preferredTrack) { 12763 preferredTrack.mode = 'showing'; 12764 } else if (firstCaptions) { 12765 firstCaptions.mode = 'showing'; 12766 } else if (firstDesc) { 12767 firstDesc.mode = 'showing'; 12768 } 12769 } 12770 /** 12771 * Turn display of {@link TextTrack}'s from the current state into the other state. 12772 * There are only two states: 12773 * - 'shown' 12774 * - 'hidden' 12775 * 12776 * @listens Player#loadstart 12777 */ 12778 ; 12779 12780 _proto.toggleDisplay = function toggleDisplay() { 12781 if (this.player_.tech_ && this.player_.tech_.featuresNativeTextTracks) { 12782 this.hide(); 12783 } else { 12784 this.show(); 12785 } 12786 } 12787 /** 12788 * Create the {@link Component}'s DOM element. 12789 * 12790 * @return {Element} 12791 * The element that was created. 12792 */ 12793 ; 12794 12795 _proto.createEl = function createEl() { 12796 return _Component.prototype.createEl.call(this, 'div', { 12797 className: 'vjs-text-track-display' 12798 }, { 12799 'aria-live': 'off', 12800 'aria-atomic': 'true' 12801 }); 12802 } 12803 /** 12804 * Clear all displayed {@link TextTrack}s. 12805 */ 12806 ; 12807 12808 _proto.clearDisplay = function clearDisplay() { 12809 if (typeof window$1.WebVTT === 'function') { 12810 window$1.WebVTT.processCues(window$1, [], this.el_); 12811 } 12812 } 12813 /** 12814 * Update the displayed TextTrack when a either a {@link Player#texttrackchange} or 12815 * a {@link Player#fullscreenchange} is fired. 12816 * 12817 * @listens Player#texttrackchange 12818 * @listens Player#fullscreenchange 12819 */ 12820 ; 12821 12822 _proto.updateDisplay = function updateDisplay() { 12823 var tracks = this.player_.textTracks(); 12824 this.clearDisplay(); // Track display prioritization model: if multiple tracks are 'showing', 12825 // display the first 'subtitles' or 'captions' track which is 'showing', 12826 // otherwise display the first 'descriptions' track which is 'showing' 12827 12828 var descriptionsTrack = null; 12829 var captionsSubtitlesTrack = null; 12830 var i = tracks.length; 12831 12832 while (i--) { 12833 var track = tracks[i]; 12834 12835 if (track.mode === 'showing') { 12836 if (track.kind === 'descriptions') { 12837 descriptionsTrack = track; 12838 } else { 12839 captionsSubtitlesTrack = track; 12840 } 12841 } 12842 } 12843 12844 if (captionsSubtitlesTrack) { 12845 if (this.getAttribute('aria-live') !== 'off') { 12846 this.setAttribute('aria-live', 'off'); 12847 } 12848 12849 this.updateForTrack(captionsSubtitlesTrack); 12850 } else if (descriptionsTrack) { 12851 if (this.getAttribute('aria-live') !== 'assertive') { 12852 this.setAttribute('aria-live', 'assertive'); 12853 } 12854 12855 this.updateForTrack(descriptionsTrack); 12856 } 12857 } 12858 /** 12859 * Add an {@link TextTrack} to to the {@link Tech}s {@link TextTrackList}. 12860 * 12861 * @param {TextTrack} track 12862 * Text track object to be added to the list. 12863 */ 12864 ; 12865 12866 _proto.updateForTrack = function updateForTrack(track) { 12867 if (typeof window$1.WebVTT !== 'function' || !track.activeCues) { 12868 return; 12869 } 12870 12871 var cues = []; 12872 12873 for (var _i = 0; _i < track.activeCues.length; _i++) { 12874 cues.push(track.activeCues[_i]); 12875 } 12876 12877 window$1.WebVTT.processCues(window$1, cues, this.el_); 12878 12879 if (!this.player_.textTrackSettings) { 12880 return; 12881 } 12882 12883 var overrides = this.player_.textTrackSettings.getValues(); 12884 var i = cues.length; 12885 12886 while (i--) { 12887 var cue = cues[i]; 12888 12889 if (!cue) { 12890 continue; 12891 } 12892 12893 var cueDiv = cue.displayState; 12894 12895 if (overrides.color) { 12896 cueDiv.firstChild.style.color = overrides.color; 12897 } 12898 12899 if (overrides.textOpacity) { 12900 tryUpdateStyle(cueDiv.firstChild, 'color', constructColor(overrides.color || '#fff', overrides.textOpacity)); 12901 } 12902 12903 if (overrides.backgroundColor) { 12904 cueDiv.firstChild.style.backgroundColor = overrides.backgroundColor; 12905 } 12906 12907 if (overrides.backgroundOpacity) { 12908 tryUpdateStyle(cueDiv.firstChild, 'backgroundColor', constructColor(overrides.backgroundColor || '#000', overrides.backgroundOpacity)); 12909 } 12910 12911 if (overrides.windowColor) { 12912 if (overrides.windowOpacity) { 12913 tryUpdateStyle(cueDiv, 'backgroundColor', constructColor(overrides.windowColor, overrides.windowOpacity)); 12914 } else { 12915 cueDiv.style.backgroundColor = overrides.windowColor; 12916 } 12917 } 12918 12919 if (overrides.edgeStyle) { 12920 if (overrides.edgeStyle === 'dropshadow') { 12921 cueDiv.firstChild.style.textShadow = "2px 2px 3px " + darkGray + ", 2px 2px 4px " + darkGray + ", 2px 2px 5px " + darkGray; 12922 } else if (overrides.edgeStyle === 'raised') { 12923 cueDiv.firstChild.style.textShadow = "1px 1px " + darkGray + ", 2px 2px " + darkGray + ", 3px 3px " + darkGray; 12924 } else if (overrides.edgeStyle === 'depressed') { 12925 cueDiv.firstChild.style.textShadow = "1px 1px " + lightGray + ", 0 1px " + lightGray + ", -1px -1px " + darkGray + ", 0 -1px " + darkGray; 12926 } else if (overrides.edgeStyle === 'uniform') { 12927 cueDiv.firstChild.style.textShadow = "0 0 4px " + darkGray + ", 0 0 4px " + darkGray + ", 0 0 4px " + darkGray + ", 0 0 4px " + darkGray; 12928 } 12929 } 12930 12931 if (overrides.fontPercent && overrides.fontPercent !== 1) { 12932 var fontSize = window$1.parseFloat(cueDiv.style.fontSize); 12933 cueDiv.style.fontSize = fontSize * overrides.fontPercent + 'px'; 12934 cueDiv.style.height = 'auto'; 12935 cueDiv.style.top = 'auto'; 12936 cueDiv.style.bottom = '2px'; 12937 } 12938 12939 if (overrides.fontFamily && overrides.fontFamily !== 'default') { 12940 if (overrides.fontFamily === 'small-caps') { 12941 cueDiv.firstChild.style.fontVariant = 'small-caps'; 12942 } else { 12943 cueDiv.firstChild.style.fontFamily = fontMap[overrides.fontFamily]; 12944 } 12945 } 12946 } 12947 }; 12948 12949 return TextTrackDisplay; 12950 }(Component); 12951 12952 Component.registerComponent('TextTrackDisplay', TextTrackDisplay); 12953 12954 /** 12955 * A loading spinner for use during waiting/loading events. 12956 * 12957 * @extends Component 12958 */ 12959 12960 var LoadingSpinner = 12961 /*#__PURE__*/ 12962 function (_Component) { 12963 _inheritsLoose(LoadingSpinner, _Component); 12964 12965 function LoadingSpinner() { 12966 return _Component.apply(this, arguments) || this; 12967 } 12968 12969 var _proto = LoadingSpinner.prototype; 12970 12971 /** 12972 * Create the `LoadingSpinner`s DOM element. 12973 * 12974 * @return {Element} 12975 * The dom element that gets created. 12976 */ 12977 _proto.createEl = function createEl$$1() { 12978 var isAudio = this.player_.isAudio(); 12979 var playerType = this.localize(isAudio ? 'Audio Player' : 'Video Player'); 12980 var controlText = createEl('span', { 12981 className: 'vjs-control-text', 12982 innerHTML: this.localize('{1} is loading.', [playerType]) 12983 }); 12984 12985 var el = _Component.prototype.createEl.call(this, 'div', { 12986 className: 'vjs-loading-spinner', 12987 dir: 'ltr' 12988 }); 12989 12990 el.appendChild(controlText); 12991 return el; 12992 }; 12993 12994 return LoadingSpinner; 12995 }(Component); 12996 12997 Component.registerComponent('LoadingSpinner', LoadingSpinner); 12998 12999 /** 13000 * Base class for all buttons. 13001 * 13002 * @extends ClickableComponent 13003 */ 13004 13005 var Button = 13006 /*#__PURE__*/ 13007 function (_ClickableComponent) { 13008 _inheritsLoose(Button, _ClickableComponent); 13009 13010 function Button() { 13011 return _ClickableComponent.apply(this, arguments) || this; 13012 } 13013 13014 var _proto = Button.prototype; 13015 13016 /** 13017 * Create the `Button`s DOM element. 13018 * 13019 * @param {string} [tag="button"] 13020 * The element's node type. This argument is IGNORED: no matter what 13021 * is passed, it will always create a `button` element. 13022 * 13023 * @param {Object} [props={}] 13024 * An object of properties that should be set on the element. 13025 * 13026 * @param {Object} [attributes={}] 13027 * An object of attributes that should be set on the element. 13028 * 13029 * @return {Element} 13030 * The element that gets created. 13031 */ 13032 _proto.createEl = function createEl(tag, props, attributes) { 13033 if (props === void 0) { 13034 props = {}; 13035 } 13036 13037 if (attributes === void 0) { 13038 attributes = {}; 13039 } 13040 13041 tag = 'button'; 13042 props = assign({ 13043 innerHTML: '<span aria-hidden="true" class="vjs-icon-placeholder"></span>', 13044 className: this.buildCSSClass() 13045 }, props); // Add attributes for button element 13046 13047 attributes = assign({ 13048 // Necessary since the default button type is "submit" 13049 type: 'button' 13050 }, attributes); 13051 var el = Component.prototype.createEl.call(this, tag, props, attributes); 13052 this.createControlTextEl(el); 13053 return el; 13054 } 13055 /** 13056 * Add a child `Component` inside of this `Button`. 13057 * 13058 * @param {string|Component} child 13059 * The name or instance of a child to add. 13060 * 13061 * @param {Object} [options={}] 13062 * The key/value store of options that will get passed to children of 13063 * the child. 13064 * 13065 * @return {Component} 13066 * The `Component` that gets added as a child. When using a string the 13067 * `Component` will get created by this process. 13068 * 13069 * @deprecated since version 5 13070 */ 13071 ; 13072 13073 _proto.addChild = function addChild(child, options) { 13074 if (options === void 0) { 13075 options = {}; 13076 } 13077 13078 var className = this.constructor.name; 13079 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 13080 13081 return Component.prototype.addChild.call(this, child, options); 13082 } 13083 /** 13084 * Enable the `Button` element so that it can be activated or clicked. Use this with 13085 * {@link Button#disable}. 13086 */ 13087 ; 13088 13089 _proto.enable = function enable() { 13090 _ClickableComponent.prototype.enable.call(this); 13091 13092 this.el_.removeAttribute('disabled'); 13093 } 13094 /** 13095 * Disable the `Button` element so that it cannot be activated or clicked. Use this with 13096 * {@link Button#enable}. 13097 */ 13098 ; 13099 13100 _proto.disable = function disable() { 13101 _ClickableComponent.prototype.disable.call(this); 13102 13103 this.el_.setAttribute('disabled', 'disabled'); 13104 } 13105 /** 13106 * This gets called when a `Button` has focus and `keydown` is triggered via a key 13107 * press. 13108 * 13109 * @param {EventTarget~Event} event 13110 * The event that caused this function to get called. 13111 * 13112 * @listens keydown 13113 */ 13114 ; 13115 13116 _proto.handleKeyDown = function handleKeyDown(event) { 13117 // Ignore Space or Enter key operation, which is handled by the browser for 13118 // a button - though not for its super class, ClickableComponent. Also, 13119 // prevent the event from propagating through the DOM and triggering Player 13120 // hotkeys. We do not preventDefault here because we _want_ the browser to 13121 // handle it. 13122 if (keycode.isEventKey(event, 'Space') || keycode.isEventKey(event, 'Enter')) { 13123 event.stopPropagation(); 13124 return; 13125 } // Pass keypress handling up for unsupported keys 13126 13127 13128 _ClickableComponent.prototype.handleKeyDown.call(this, event); 13129 }; 13130 13131 return Button; 13132 }(ClickableComponent); 13133 13134 Component.registerComponent('Button', Button); 13135 13136 /** 13137 * The initial play button that shows before the video has played. The hiding of the 13138 * `BigPlayButton` get done via CSS and `Player` states. 13139 * 13140 * @extends Button 13141 */ 13142 13143 var BigPlayButton = 13144 /*#__PURE__*/ 13145 function (_Button) { 13146 _inheritsLoose(BigPlayButton, _Button); 13147 13148 function BigPlayButton(player, options) { 13149 var _this; 13150 13151 _this = _Button.call(this, player, options) || this; 13152 _this.mouseused_ = false; 13153 13154 _this.on('mousedown', _this.handleMouseDown); 13155 13156 return _this; 13157 } 13158 /** 13159 * Builds the default DOM `className`. 13160 * 13161 * @return {string} 13162 * The DOM `className` for this object. Always returns 'vjs-big-play-button'. 13163 */ 13164 13165 13166 var _proto = BigPlayButton.prototype; 13167 13168 _proto.buildCSSClass = function buildCSSClass() { 13169 return 'vjs-big-play-button'; 13170 } 13171 /** 13172 * This gets called when a `BigPlayButton` "clicked". See {@link ClickableComponent} 13173 * for more detailed information on what a click can be. 13174 * 13175 * @param {EventTarget~Event} event 13176 * The `keydown`, `tap`, or `click` event that caused this function to be 13177 * called. 13178 * 13179 * @listens tap 13180 * @listens click 13181 */ 13182 ; 13183 13184 _proto.handleClick = function handleClick(event) { 13185 var playPromise = this.player_.play(); // exit early if clicked via the mouse 13186 13187 if (this.mouseused_ && event.clientX && event.clientY) { 13188 silencePromise(playPromise); 13189 this.player_.tech(true).focus(); 13190 return; 13191 } 13192 13193 var cb = this.player_.getChild('controlBar'); 13194 var playToggle = cb && cb.getChild('playToggle'); 13195 13196 if (!playToggle) { 13197 this.player_.tech(true).focus(); 13198 return; 13199 } 13200 13201 var playFocus = function playFocus() { 13202 return playToggle.focus(); 13203 }; 13204 13205 if (isPromise(playPromise)) { 13206 playPromise.then(playFocus, function () {}); 13207 } else { 13208 this.setTimeout(playFocus, 1); 13209 } 13210 }; 13211 13212 _proto.handleKeyDown = function handleKeyDown(event) { 13213 this.mouseused_ = false; 13214 13215 _Button.prototype.handleKeyDown.call(this, event); 13216 }; 13217 13218 _proto.handleMouseDown = function handleMouseDown(event) { 13219 this.mouseused_ = true; 13220 }; 13221 13222 return BigPlayButton; 13223 }(Button); 13224 /** 13225 * The text that should display over the `BigPlayButton`s controls. Added to for localization. 13226 * 13227 * @type {string} 13228 * @private 13229 */ 13230 13231 13232 BigPlayButton.prototype.controlText_ = 'Play Video'; 13233 Component.registerComponent('BigPlayButton', BigPlayButton); 13234 13235 /** 13236 * The `CloseButton` is a `{@link Button}` that fires a `close` event when 13237 * it gets clicked. 13238 * 13239 * @extends Button 13240 */ 13241 13242 var CloseButton = 13243 /*#__PURE__*/ 13244 function (_Button) { 13245 _inheritsLoose(CloseButton, _Button); 13246 13247 /** 13248 * Creates an instance of the this class. 13249 * 13250 * @param {Player} player 13251 * The `Player` that this class should be attached to. 13252 * 13253 * @param {Object} [options] 13254 * The key/value store of player options. 13255 */ 13256 function CloseButton(player, options) { 13257 var _this; 13258 13259 _this = _Button.call(this, player, options) || this; 13260 13261 _this.controlText(options && options.controlText || _this.localize('Close')); 13262 13263 return _this; 13264 } 13265 /** 13266 * Builds the default DOM `className`. 13267 * 13268 * @return {string} 13269 * The DOM `className` for this object. 13270 */ 13271 13272 13273 var _proto = CloseButton.prototype; 13274 13275 _proto.buildCSSClass = function buildCSSClass() { 13276 return "vjs-close-button " + _Button.prototype.buildCSSClass.call(this); 13277 } 13278 /** 13279 * This gets called when a `CloseButton` gets clicked. See 13280 * {@link ClickableComponent#handleClick} for more information on when 13281 * this will be triggered 13282 * 13283 * @param {EventTarget~Event} event 13284 * The `keydown`, `tap`, or `click` event that caused this function to be 13285 * called. 13286 * 13287 * @listens tap 13288 * @listens click 13289 * @fires CloseButton#close 13290 */ 13291 ; 13292 13293 _proto.handleClick = function handleClick(event) { 13294 /** 13295 * Triggered when the a `CloseButton` is clicked. 13296 * 13297 * @event CloseButton#close 13298 * @type {EventTarget~Event} 13299 * 13300 * @property {boolean} [bubbles=false] 13301 * set to false so that the close event does not 13302 * bubble up to parents if there is no listener 13303 */ 13304 this.trigger({ 13305 type: 'close', 13306 bubbles: false 13307 }); 13308 }; 13309 13310 return CloseButton; 13311 }(Button); 13312 13313 Component.registerComponent('CloseButton', CloseButton); 13314 13315 /** 13316 * Button to toggle between play and pause. 13317 * 13318 * @extends Button 13319 */ 13320 13321 var PlayToggle = 13322 /*#__PURE__*/ 13323 function (_Button) { 13324 _inheritsLoose(PlayToggle, _Button); 13325 13326 /** 13327 * Creates an instance of this class. 13328 * 13329 * @param {Player} player 13330 * The `Player` that this class should be attached to. 13331 * 13332 * @param {Object} [options={}] 13333 * The key/value store of player options. 13334 */ 13335 function PlayToggle(player, options) { 13336 var _this; 13337 13338 if (options === void 0) { 13339 options = {}; 13340 } 13341 13342 _this = _Button.call(this, player, options) || this; // show or hide replay icon 13343 13344 options.replay = options.replay === undefined || options.replay; 13345 13346 _this.on(player, 'play', _this.handlePlay); 13347 13348 _this.on(player, 'pause', _this.handlePause); 13349 13350 if (options.replay) { 13351 _this.on(player, 'ended', _this.handleEnded); 13352 } 13353 13354 return _this; 13355 } 13356 /** 13357 * Builds the default DOM `className`. 13358 * 13359 * @return {string} 13360 * The DOM `className` for this object. 13361 */ 13362 13363 13364 var _proto = PlayToggle.prototype; 13365 13366 _proto.buildCSSClass = function buildCSSClass() { 13367 return "vjs-play-control " + _Button.prototype.buildCSSClass.call(this); 13368 } 13369 /** 13370 * This gets called when an `PlayToggle` is "clicked". See 13371 * {@link ClickableComponent} for more detailed information on what a click can be. 13372 * 13373 * @param {EventTarget~Event} [event]
vendor: 9,894 bytes, lines 13374-13723
13374 * The `keydown`, `tap`, or `click` event that caused this function to be 13375 * called. 13376 * 13377 * @listens tap 13378 * @listens click 13379 */ 13380 ; 13381 13382 _proto.handleClick = function handleClick(event) { 13383 if (this.player_.paused()) { 13384 this.player_.play(); 13385 } else { 13386 this.player_.pause(); 13387 } 13388 } 13389 /** 13390 * This gets called once after the video has ended and the user seeks so that 13391 * we can change the replay button back to a play button. 13392 * 13393 * @param {EventTarget~Event} [event] 13394 * The event that caused this function to run. 13395 * 13396 * @listens Player#seeked 13397 */ 13398 ; 13399 13400 _proto.handleSeeked = function handleSeeked(event) { 13401 this.removeClass('vjs-ended'); 13402 13403 if (this.player_.paused()) { 13404 this.handlePause(event); 13405 } else { 13406 this.handlePlay(event); 13407 } 13408 } 13409 /** 13410 * Add the vjs-playing class to the element so it can change appearance. 13411 * 13412 * @param {EventTarget~Event} [event] 13413 * The event that caused this function to run. 13414 * 13415 * @listens Player#play 13416 */ 13417 ; 13418 13419 _proto.handlePlay = function handlePlay(event) { 13420 this.removeClass('vjs-ended'); 13421 this.removeClass('vjs-paused'); 13422 this.addClass('vjs-playing'); // change the button text to "Pause" 13423 13424 this.controlText('Pause'); 13425 } 13426 /** 13427 * Add the vjs-paused class to the element so it can change appearance. 13428 * 13429 * @param {EventTarget~Event} [event] 13430 * The event that caused this function to run. 13431 * 13432 * @listens Player#pause 13433 */ 13434 ; 13435 13436 _proto.handlePause = function handlePause(event) { 13437 this.removeClass('vjs-playing'); 13438 this.addClass('vjs-paused'); // change the button text to "Play" 13439 13440 this.controlText('Play'); 13441 } 13442 /** 13443 * Add the vjs-ended class to the element so it can change appearance 13444 * 13445 * @param {EventTarget~Event} [event] 13446 * The event that caused this function to run. 13447 * 13448 * @listens Player#ended 13449 */ 13450 ; 13451 13452 _proto.handleEnded = function handleEnded(event) { 13453 this.removeClass('vjs-playing'); 13454 this.addClass('vjs-ended'); // change the button text to "Replay" 13455 13456 this.controlText('Replay'); // on the next seek remove the replay button 13457 13458 this.one(this.player_, 'seeked', this.handleSeeked); 13459 }; 13460 13461 return PlayToggle; 13462 }(Button); 13463 /** 13464 * The text that should display over the `PlayToggle`s controls. Added for localization. 13465 * 13466 * @type {string} 13467 * @private 13468 */ 13469 13470 13471 PlayToggle.prototype.controlText_ = 'Play'; 13472 Component.registerComponent('PlayToggle', PlayToggle); 13473 13474 /** 13475 * @file format-time.js 13476 * @module format-time 13477 */ 13478 13479 /** 13480 * Format seconds as a time string, H:MM:SS or M:SS. Supplying a guide (in 13481 * seconds) will force a number of leading zeros to cover the length of the 13482 * guide. 13483 * 13484 * @private 13485 * @param {number} seconds 13486 * Number of seconds to be turned into a string 13487 * 13488 * @param {number} guide 13489 * Number (in seconds) to model the string after 13490 * 13491 * @return {string} 13492 * Time formatted as H:MM:SS or M:SS 13493 */ 13494 var defaultImplementation = function defaultImplementation(seconds, guide) { 13495 seconds = seconds < 0 ? 0 : seconds; 13496 var s = Math.floor(seconds % 60); 13497 var m = Math.floor(seconds / 60 % 60); 13498 var h = Math.floor(seconds / 3600); 13499 var gm = Math.floor(guide / 60 % 60); 13500 var gh = Math.floor(guide / 3600); // handle invalid times 13501 13502 if (isNaN(seconds) || seconds === Infinity) { 13503 // '-' is false for all relational operators (e.g. <, >=) so this setting 13504 // will add the minimum number of fields specified by the guide 13505 h = m = s = '-'; 13506 } // Check if we need to show hours 13507 13508 13509 h = h > 0 || gh > 0 ? h + ':' : ''; // If hours are showing, we may need to add a leading zero. 13510 // Always show at least one digit of minutes. 13511 13512 m = ((h || gm >= 10) && m < 10 ? '0' + m : m) + ':'; // Check if leading zero is need for seconds 13513 13514 s = s < 10 ? '0' + s : s; 13515 return h + m + s; 13516 }; // Internal pointer to the current implementation. 13517 13518 13519 var implementation = defaultImplementation; 13520 /** 13521 * Replaces the default formatTime implementation with a custom implementation. 13522 * 13523 * @param {Function} customImplementation 13524 * A function which will be used in place of the default formatTime 13525 * implementation. Will receive the current time in seconds and the 13526 * guide (in seconds) as arguments. 13527 */ 13528 13529 function setFormatTime(customImplementation) { 13530 implementation = customImplementation; 13531 } 13532 /** 13533 * Resets formatTime to the default implementation. 13534 */ 13535 13536 function resetFormatTime() { 13537 implementation = defaultImplementation; 13538 } 13539 /** 13540 * Delegates to either the default time formatting function or a custom 13541 * function supplied via `setFormatTime`. 13542 * 13543 * Formats seconds as a time string (H:MM:SS or M:SS). Supplying a 13544 * guide (in seconds) will force a number of leading zeros to cover the 13545 * length of the guide. 13546 * 13547 * @static 13548 * @example formatTime(125, 600) === "02:05" 13549 * @param {number} seconds 13550 * Number of seconds to be turned into a string 13551 * 13552 * @param {number} guide 13553 * Number (in seconds) to model the string after 13554 * 13555 * @return {string} 13556 * Time formatted as H:MM:SS or M:SS 13557 */ 13558 13559 function formatTime(seconds, guide) { 13560 if (guide === void 0) { 13561 guide = seconds; 13562 } 13563 13564 return implementation(seconds, guide); 13565 } 13566 13567 /** 13568 * Displays time information about the video 13569 * 13570 * @extends Component 13571 */ 13572 13573 var TimeDisplay = 13574 /*#__PURE__*/ 13575 function (_Component) { 13576 _inheritsLoose(TimeDisplay, _Component); 13577 13578 /** 13579 * Creates an instance of this class. 13580 * 13581 * @param {Player} player 13582 * The `Player` that this class should be attached to. 13583 * 13584 * @param {Object} [options] 13585 * The key/value store of player options. 13586 */ 13587 function TimeDisplay(player, options) { 13588 var _this; 13589 13590 _this = _Component.call(this, player, options) || this; 13591 _this.throttledUpdateContent = throttle(bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.updateContent), 25); 13592 13593 _this.on(player, 'timeupdate', _this.throttledUpdateContent); 13594 13595 return _this; 13596 } 13597 /** 13598 * Create the `Component`'s DOM element 13599 * 13600 * @return {Element} 13601 * The element that was created. 13602 */ 13603 13604 13605 var _proto = TimeDisplay.prototype; 13606 13607 _proto.createEl = function createEl$$1() { 13608 var className = this.buildCSSClass(); 13609 13610 var el = _Component.prototype.createEl.call(this, 'div', { 13611 className: className + " vjs-time-control vjs-control", 13612 innerHTML: "<span class=\"vjs-control-text\" role=\"presentation\">" + this.localize(this.labelText_) + "\xA0</span>" 13613 }); 13614 13615 this.contentEl_ = createEl('span', { 13616 className: className + "-display" 13617 }, { 13618 // tell screen readers not to automatically read the time as it changes 13619 'aria-live': 'off', 13620 // span elements have no implicit role, but some screen readers (notably VoiceOver) 13621 // treat them as a break between items in the DOM when using arrow keys 13622 // (or left-to-right swipes on iOS) to read contents of a page. Using 13623 // role='presentation' causes VoiceOver to NOT treat this span as a break. 13624 'role': 'presentation' 13625 }); 13626 this.updateTextNode_(); 13627 el.appendChild(this.contentEl_); 13628 return el; 13629 }; 13630 13631 _proto.dispose = function dispose() { 13632 this.contentEl_ = null; 13633 this.textNode_ = null; 13634 13635 _Component.prototype.dispose.call(this); 13636 } 13637 /** 13638 * Updates the "remaining time" text node with new content using the 13639 * contents of the `formattedTime_` property. 13640 * 13641 * @private 13642 */ 13643 ; 13644 13645 _proto.updateTextNode_ = function updateTextNode_() { 13646 if (!this.contentEl_) { 13647 return; 13648 } 13649 13650 while (this.contentEl_.firstChild) { 13651 this.contentEl_.removeChild(this.contentEl_.firstChild); 13652 } 13653 13654 this.textNode_ = document.createTextNode(this.formattedTime_ || this.formatTime_(0)); 13655 this.contentEl_.appendChild(this.textNode_); 13656 } 13657 /** 13658 * Generates a formatted time for this component to use in display. 13659 * 13660 * @param {number} time 13661 * A numeric time, in seconds. 13662 * 13663 * @return {string} 13664 * A formatted time 13665 * 13666 * @private 13667 */ 13668 ; 13669 13670 _proto.formatTime_ = function formatTime_(time) { 13671 return formatTime(time); 13672 } 13673 /** 13674 * Updates the time display text node if it has what was passed in changed 13675 * the formatted time. 13676 * 13677 * @param {number} time 13678 * The time to update to 13679 * 13680 * @private 13681 */ 13682 ; 13683 13684 _proto.updateFormattedTime_ = function updateFormattedTime_(time) { 13685 var formattedTime = this.formatTime_(time); 13686 13687 if (formattedTime === this.formattedTime_) { 13688 return; 13689 } 13690 13691 this.formattedTime_ = formattedTime; 13692 this.requestAnimationFrame(this.updateTextNode_); 13693 } 13694 /** 13695 * To be filled out in the child class, should update the displayed time 13696 * in accordance with the fact that the current time has changed. 13697 * 13698 * @param {EventTarget~Event} [event] 13699 * The `timeupdate` event that caused this to run. 13700 * 13701 * @listens Player#timeupdate 13702 */ 13703 ; 13704 13705 _proto.updateContent = function updateContent(event) {}; 13706 13707 return TimeDisplay; 13708 }(Component); 13709 /** 13710 * The text that is added to the `TimeDisplay` for screen reader users. 13711 * 13712 * @type {string} 13713 * @private 13714 */ 13715 13716 13717 TimeDisplay.prototype.labelText_ = 'Time'; 13718 /** 13719 * The text that should display over the `TimeDisplay`s controls. Added to for localization. 13720 * 13721 * @type {string} 13722 * @private 13723 *
vendor: 5,455 bytes, lines 13724-13922
13724 * @deprecated in v7; controlText_ is not used in non-active display Components 13725 */ 13726 13727 TimeDisplay.prototype.controlText_ = 'Time'; 13728 Component.registerComponent('TimeDisplay', TimeDisplay); 13729 13730 /** 13731 * Displays the current time 13732 * 13733 * @extends Component 13734 */ 13735 13736 var CurrentTimeDisplay = 13737 /*#__PURE__*/ 13738 function (_TimeDisplay) { 13739 _inheritsLoose(CurrentTimeDisplay, _TimeDisplay); 13740 13741 /** 13742 * Creates an instance of this class. 13743 * 13744 * @param {Player} player 13745 * The `Player` that this class should be attached to. 13746 * 13747 * @param {Object} [options] 13748 * The key/value store of player options. 13749 */ 13750 function CurrentTimeDisplay(player, options) { 13751 var _this; 13752 13753 _this = _TimeDisplay.call(this, player, options) || this; 13754 13755 _this.on(player, 'ended', _this.handleEnded); 13756 13757 return _this; 13758 } 13759 /** 13760 * Builds the default DOM `className`. 13761 * 13762 * @return {string} 13763 * The DOM `className` for this object. 13764 */ 13765 13766 13767 var _proto = CurrentTimeDisplay.prototype; 13768 13769 _proto.buildCSSClass = function buildCSSClass() { 13770 return 'vjs-current-time'; 13771 } 13772 /** 13773 * Update current time display 13774 * 13775 * @param {EventTarget~Event} [event] 13776 * The `timeupdate` event that caused this function to run. 13777 * 13778 * @listens Player#timeupdate 13779 */ 13780 ; 13781 13782 _proto.updateContent = function updateContent(event) { 13783 // Allows for smooth scrubbing, when player can't keep up. 13784 var time = this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime(); 13785 this.updateFormattedTime_(time); 13786 } 13787 /** 13788 * When the player fires ended there should be no time left. Sadly 13789 * this is not always the case, lets make it seem like that is the case 13790 * for users. 13791 * 13792 * @param {EventTarget~Event} [event] 13793 * The `ended` event that caused this to run. 13794 * 13795 * @listens Player#ended 13796 */ 13797 ; 13798 13799 _proto.handleEnded = function handleEnded(event) { 13800 if (!this.player_.duration()) { 13801 return; 13802 } 13803 13804 this.updateFormattedTime_(this.player_.duration()); 13805 }; 13806 13807 return CurrentTimeDisplay; 13808 }(TimeDisplay); 13809 /** 13810 * The text that is added to the `CurrentTimeDisplay` for screen reader users. 13811 * 13812 * @type {string} 13813 * @private 13814 */ 13815 13816 13817 CurrentTimeDisplay.prototype.labelText_ = 'Current Time'; 13818 /** 13819 * The text that should display over the `CurrentTimeDisplay`s controls. Added to for localization. 13820 * 13821 * @type {string} 13822 * @private 13823 * 13824 * @deprecated in v7; controlText_ is not used in non-active display Components 13825 */ 13826 13827 CurrentTimeDisplay.prototype.controlText_ = 'Current Time'; 13828 Component.registerComponent('CurrentTimeDisplay', CurrentTimeDisplay); 13829 13830 /** 13831 * Displays the duration 13832 * 13833 * @extends Component 13834 */ 13835 13836 var DurationDisplay = 13837 /*#__PURE__*/ 13838 function (_TimeDisplay) { 13839 _inheritsLoose(DurationDisplay, _TimeDisplay); 13840 13841 /** 13842 * Creates an instance of this class. 13843 * 13844 * @param {Player} player 13845 * The `Player` that this class should be attached to. 13846 * 13847 * @param {Object} [options] 13848 * The key/value store of player options. 13849 */ 13850 function DurationDisplay(player, options) { 13851 var _this; 13852 13853 _this = _TimeDisplay.call(this, player, options) || this; // we do not want to/need to throttle duration changes, 13854 // as they should always display the changed duration as 13855 // it has changed 13856 13857 _this.on(player, 'durationchange', _this.updateContent); // Listen to loadstart because the player duration is reset when a new media element is loaded, 13858 // but the durationchange on the user agent will not fire. 13859 // @see [Spec]{@link https://www.w3.org/TR/2011/WD-html5-20110113/video.html#media-element-load-algorithm} 13860 13861 13862 _this.on(player, 'loadstart', _this.updateContent); // Also listen for timeupdate (in the parent) and loadedmetadata because removing those 13863 // listeners could have broken dependent applications/libraries. These 13864 // can likely be removed for 7.0. 13865 13866 13867 _this.on(player, 'loadedmetadata', _this.throttledUpdateContent); 13868 13869 return _this; 13870 } 13871 /** 13872 * Builds the default DOM `className`. 13873 * 13874 * @return {string} 13875 * The DOM `className` for this object. 13876 */ 13877 13878 13879 var _proto = DurationDisplay.prototype; 13880 13881 _proto.buildCSSClass = function buildCSSClass() { 13882 return 'vjs-duration'; 13883 } 13884 /** 13885 * Update duration time display. 13886 * 13887 * @param {EventTarget~Event} [event] 13888 * The `durationchange`, `timeupdate`, or `loadedmetadata` event that caused 13889 * this function to be called. 13890 * 13891 * @listens Player#durationchange 13892 * @listens Player#timeupdate 13893 * @listens Player#loadedmetadata 13894 */ 13895 ; 13896 13897 _proto.updateContent = function updateContent(event) { 13898 var duration = this.player_.duration(); 13899 13900 if (this.duration_ !== duration) { 13901 this.duration_ = duration; 13902 this.updateFormattedTime_(duration); 13903 } 13904 }; 13905 13906 return DurationDisplay; 13907 }(TimeDisplay); 13908 /** 13909 * The text that is added to the `DurationDisplay` for screen reader users. 13910 * 13911 * @type {string} 13912 * @private 13913 */ 13914 13915 13916 DurationDisplay.prototype.labelText_ = 'Duration'; 13917 /** 13918 * The text that should display over the `DurationDisplay`s controls. Added to for localization. 13919 * 13920 * @type {string} 13921 * @private 13922 *
13923 * @deprecated in v7; controlText_ is not used in non-active display Components 13924 */ 13925 13926 DurationDisplay.prototype.controlText_ = 'Duration'; 13927 Component.registerComponent('DurationDisplay', DurationDisplay); 13928 13929 /** 13930 * The separator between the current time and duration. 13931 * Can be hidden if it's not needed in the design. 13932 * 13933 * @extends Component 13934 */ 13935 13936 var TimeDivider = 13937 /*#__PURE__*/ 13938 function (_Component) { 13939 _inheritsLoose(TimeDivider, _Component); 13940 13941 function TimeDivider() { 13942 return _Component.apply(this, arguments) || this; 13943 } 13944 13945 var _proto = TimeDivider.prototype; 13946 13947 /** 13948 * Create the component's DOM element 13949 * 13950 * @return {Element} 13951 * The element that was created. 13952 */ 13953 _proto.createEl = function createEl() { 13954 return _Component.prototype.createEl.call(this, 'div', { 13955 className: 'vjs-time-control vjs-time-divider', 13956 innerHTML: '<div><span>/</span></div>' 13957 }, { 13958 // this element and its contents can be hidden from assistive techs since 13959 // it is made extraneous by the announcement of the control text 13960 // for the current time and duration displays 13961 'aria-hidden': true 13962 }); 13963 }; 13964 13965 return TimeDivider; 13966 }(Component); 13967 13968 Component.registerComponent('TimeDivider', TimeDivider); 13969 13970 /** 13971 * Displays the time left in the video 13972 * 13973 * @extends Component 13974 */ 13975 13976 var RemainingTimeDisplay = 13977 /*#__PURE__*/ 13978 function (_TimeDisplay) { 13979 _inheritsLoose(RemainingTimeDisplay, _TimeDisplay); 13980 13981 /** 13982 * Creates an instance of this class. 13983 * 13984 * @param {Player} player 13985 * The `Player` that this class should be attached to. 13986 * 13987 * @param {Object} [options] 13988 * The key/value store of player options. 13989 */ 13990 function RemainingTimeDisplay(player, options) { 13991 var _this; 13992 13993 _this = _TimeDisplay.call(this, player, options) || this; 13994 13995 _this.on(player, 'durationchange', _this.throttledUpdateContent); 13996 13997 _this.on(player, 'ended', _this.handleEnded); 13998 13999 return _this; 14000 } 14001 /** 14002 * Builds the default DOM `className`. 14003 * 14004 * @return {string} 14005 * The DOM `className` for this object. 14006 */ 14007 14008 14009 var _proto = RemainingTimeDisplay.prototype; 14010 14011 _proto.buildCSSClass = function buildCSSClass() { 14012 return 'vjs-remaining-time'; 14013 } 14014 /** 14015 * Create the `Component`'s DOM element with the "minus" characted prepend to the time 14016 * 14017 * @return {Element} 14018 * The element that was created. 14019 */ 14020 ; 14021 14022 _proto.createEl = function createEl$$1() { 14023 var el = _TimeDisplay.prototype.createEl.call(this); 14024 14025 el.insertBefore(createEl('span', {}, { 14026 'aria-hidden': true 14027 }, '-'), this.contentEl_); 14028 return el; 14029 } 14030 /** 14031 * Update remaining time display. 14032 * 14033 * @param {EventTarget~Event} [event] 14034 * The `timeupdate` or `durationchange` event that caused this to run. 14035 * 14036 * @listens Player#timeupdate 14037 * @listens Player#durationchange 14038 */ 14039 ; 14040 14041 _proto.updateContent = function updateContent(event) { 14042 if (typeof this.player_.duration() !== 'number') { 14043 return; 14044 } // @deprecated We should only use remainingTimeDisplay 14045 // as of video.js 7 14046 14047 14048 if (this.player_.remainingTimeDisplay) { 14049 this.updateFormattedTime_(this.player_.remainingTimeDisplay()); 14050 } else { 14051 this.updateFormattedTime_(this.player_.remainingTime()); 14052 } 14053 } 14054 /** 14055 * When the player fires ended there should be no time left. Sadly 14056 * this is not always the case, lets make it seem like that is the case 14057 * for users. 14058 * 14059 * @param {EventTarget~Event} [event] 14060 * The `ended` event that caused this to run. 14061 * 14062 * @listens Player#ended 14063 */ 14064 ; 14065 14066 _proto.handleEnded = function handleEnded(event) { 14067 if (!this.player_.duration()) { 14068 return; 14069 } 14070 14071 this.updateFormattedTime_(0); 14072 }; 14073 14074 return RemainingTimeDisplay; 14075 }(TimeDisplay); 14076 /** 14077 * The text that is added to the `RemainingTimeDisplay` for screen reader users. 14078 * 14079 * @type {string} 14080 * @private 14081 */ 14082 14083 14084 RemainingTimeDisplay.prototype.labelText_ = 'Remaining Time'; 14085 /** 14086 * The text that should display over the `RemainingTimeDisplay`s controls. Added to for localization. 14087 * 14088 * @type {string} 14089 * @private 14090 *
vendor: 9,955 bytes, lines 14091-14457
14091 * @deprecated in v7; controlText_ is not used in non-active display Components 14092 */ 14093 14094 RemainingTimeDisplay.prototype.controlText_ = 'Remaining Time'; 14095 Component.registerComponent('RemainingTimeDisplay', RemainingTimeDisplay); 14096 14097 /** 14098 * Displays the live indicator when duration is Infinity. 14099 * 14100 * @extends Component 14101 */ 14102 14103 var LiveDisplay = 14104 /*#__PURE__*/ 14105 function (_Component) { 14106 _inheritsLoose(LiveDisplay, _Component); 14107 14108 /** 14109 * Creates an instance of this class. 14110 * 14111 * @param {Player} player 14112 * The `Player` that this class should be attached to. 14113 * 14114 * @param {Object} [options] 14115 * The key/value store of player options. 14116 */ 14117 function LiveDisplay(player, options) { 14118 var _this; 14119 14120 _this = _Component.call(this, player, options) || this; 14121 14122 _this.updateShowing(); 14123 14124 _this.on(_this.player(), 'durationchange', _this.updateShowing); 14125 14126 return _this; 14127 } 14128 /** 14129 * Create the `Component`'s DOM element 14130 * 14131 * @return {Element} 14132 * The element that was created. 14133 */ 14134 14135 14136 var _proto = LiveDisplay.prototype; 14137 14138 _proto.createEl = function createEl$$1() { 14139 var el = _Component.prototype.createEl.call(this, 'div', { 14140 className: 'vjs-live-control vjs-control' 14141 }); 14142 14143 this.contentEl_ = createEl('div', { 14144 className: 'vjs-live-display', 14145 innerHTML: "<span class=\"vjs-control-text\">" + this.localize('Stream Type') + "\xA0</span>" + this.localize('LIVE') 14146 }, { 14147 'aria-live': 'off' 14148 }); 14149 el.appendChild(this.contentEl_); 14150 return el; 14151 }; 14152 14153 _proto.dispose = function dispose() { 14154 this.contentEl_ = null; 14155 14156 _Component.prototype.dispose.call(this); 14157 } 14158 /** 14159 * Check the duration to see if the LiveDisplay should be showing or not. Then show/hide 14160 * it accordingly 14161 * 14162 * @param {EventTarget~Event} [event] 14163 * The {@link Player#durationchange} event that caused this function to run. 14164 * 14165 * @listens Player#durationchange 14166 */ 14167 ; 14168 14169 _proto.updateShowing = function updateShowing(event) { 14170 if (this.player().duration() === Infinity) { 14171 this.show(); 14172 } else { 14173 this.hide(); 14174 } 14175 }; 14176 14177 return LiveDisplay; 14178 }(Component); 14179 14180 Component.registerComponent('LiveDisplay', LiveDisplay); 14181 14182 /** 14183 * Displays the live indicator when duration is Infinity. 14184 * 14185 * @extends Component 14186 */ 14187 14188 var SeekToLive = 14189 /*#__PURE__*/ 14190 function (_Button) { 14191 _inheritsLoose(SeekToLive, _Button); 14192 14193 /** 14194 * Creates an instance of this class. 14195 * 14196 * @param {Player} player 14197 * The `Player` that this class should be attached to. 14198 * 14199 * @param {Object} [options] 14200 * The key/value store of player options. 14201 */ 14202 function SeekToLive(player, options) { 14203 var _this; 14204 14205 _this = _Button.call(this, player, options) || this; 14206 14207 _this.updateLiveEdgeStatus(); 14208 14209 if (_this.player_.liveTracker) { 14210 _this.on(_this.player_.liveTracker, 'liveedgechange', _this.updateLiveEdgeStatus); 14211 } 14212 14213 return _this; 14214 } 14215 /** 14216 * Create the `Component`'s DOM element 14217 * 14218 * @return {Element} 14219 * The element that was created. 14220 */ 14221 14222 14223 var _proto = SeekToLive.prototype; 14224 14225 _proto.createEl = function createEl$$1() { 14226 var el = _Button.prototype.createEl.call(this, 'button', { 14227 className: 'vjs-seek-to-live-control vjs-control' 14228 }); 14229 14230 this.textEl_ = createEl('span', { 14231 className: 'vjs-seek-to-live-text', 14232 innerHTML: this.localize('LIVE') 14233 }, { 14234 'aria-hidden': 'true' 14235 }); 14236 el.appendChild(this.textEl_); 14237 return el; 14238 } 14239 /** 14240 * Update the state of this button if we are at the live edge 14241 * or not 14242 */ 14243 ; 14244 14245 _proto.updateLiveEdgeStatus = function updateLiveEdgeStatus(e) { 14246 // default to live edge 14247 if (!this.player_.liveTracker || this.player_.liveTracker.atLiveEdge()) { 14248 this.setAttribute('aria-disabled', true); 14249 this.addClass('vjs-at-live-edge'); 14250 this.controlText('Seek to live, currently playing live'); 14251 } else { 14252 this.setAttribute('aria-disabled', false); 14253 this.removeClass('vjs-at-live-edge'); 14254 this.controlText('Seek to live, currently behind live'); 14255 } 14256 } 14257 /** 14258 * On click bring us as near to the live point as possible. 14259 * This requires that we wait for the next `live-seekable-change` 14260 * event which will happen every segment length seconds. 14261 */ 14262 ; 14263 14264 _proto.handleClick = function handleClick() { 14265 this.player_.liveTracker.seekToLiveEdge(); 14266 } 14267 /** 14268 * Dispose of the element and stop tracking 14269 */ 14270 ; 14271 14272 _proto.dispose = function dispose() { 14273 if (this.player_.liveTracker) { 14274 this.off(this.player_.liveTracker, 'liveedgechange', this.updateLiveEdgeStatus); 14275 } 14276 14277 this.textEl_ = null; 14278 14279 _Button.prototype.dispose.call(this); 14280 }; 14281 14282 return SeekToLive; 14283 }(Button); 14284 14285 SeekToLive.prototype.controlText_ = 'Seek to live, currently playing live'; 14286 Component.registerComponent('SeekToLive', SeekToLive); 14287 14288 /** 14289 * The base functionality for a slider. Can be vertical or horizontal. 14290 * For instance the volume bar or the seek bar on a video is a slider. 14291 * 14292 * @extends Component 14293 */ 14294 14295 var Slider = 14296 /*#__PURE__*/ 14297 function (_Component) { 14298 _inheritsLoose(Slider, _Component); 14299 14300 /** 14301 * Create an instance of this class 14302 * 14303 * @param {Player} player 14304 * The `Player` that this class should be attached to. 14305 * 14306 * @param {Object} [options] 14307 * The key/value store of player options. 14308 */ 14309 function Slider(player, options) { 14310 var _this; 14311 14312 _this = _Component.call(this, player, options) || this; // Set property names to bar to match with the child Slider class is looking for 14313 14314 _this.bar = _this.getChild(_this.options_.barName); // Set a horizontal or vertical class on the slider depending on the slider type 14315 14316 _this.vertical(!!_this.options_.vertical); 14317 14318 _this.enable(); 14319 14320 return _this; 14321 } 14322 /** 14323 * Are controls are currently enabled for this slider or not. 14324 * 14325 * @return {boolean} 14326 * true if controls are enabled, false otherwise 14327 */ 14328 14329 14330 var _proto = Slider.prototype; 14331 14332 _proto.enabled = function enabled() { 14333 return this.enabled_; 14334 } 14335 /** 14336 * Enable controls for this slider if they are disabled 14337 */ 14338 ; 14339 14340 _proto.enable = function enable() { 14341 if (this.enabled()) { 14342 return; 14343 } 14344 14345 this.on('mousedown', this.handleMouseDown); 14346 this.on('touchstart', this.handleMouseDown); 14347 this.on('keydown', this.handleKeyDown); 14348 this.on('click', this.handleClick); 14349 this.on(this.player_, 'controlsvisible', this.update); 14350 14351 if (this.playerEvent) { 14352 this.on(this.player_, this.playerEvent, this.update); 14353 } 14354 14355 this.removeClass('disabled'); 14356 this.setAttribute('tabindex', 0); 14357 this.enabled_ = true; 14358 } 14359 /** 14360 * Disable controls for this slider if they are enabled 14361 */ 14362 ; 14363 14364 _proto.disable = function disable() { 14365 if (!this.enabled()) { 14366 return; 14367 } 14368 14369 var doc = this.bar.el_.ownerDocument; 14370 this.off('mousedown', this.handleMouseDown); 14371 this.off('touchstart', this.handleMouseDown); 14372 this.off('keydown', this.handleKeyDown); 14373 this.off('click', this.handleClick); 14374 this.off(this.player_, 'controlsvisible', this.update); 14375 this.off(doc, 'mousemove', this.handleMouseMove); 14376 this.off(doc, 'mouseup', this.handleMouseUp); 14377 this.off(doc, 'touchmove', this.handleMouseMove); 14378 this.off(doc, 'touchend', this.handleMouseUp); 14379 this.removeAttribute('tabindex'); 14380 this.addClass('disabled'); 14381 14382 if (this.playerEvent) { 14383 this.off(this.player_, this.playerEvent, this.update); 14384 } 14385 14386 this.enabled_ = false; 14387 } 14388 /** 14389 * Create the `Slider`s DOM element. 14390 * 14391 * @param {string} type 14392 * Type of element to create. 14393 * 14394 * @param {Object} [props={}] 14395 * List of properties in Object form. 14396 * 14397 * @param {Object} [attributes={}] 14398 * list of attributes in Object form. 14399 * 14400 * @return {Element} 14401 * The element that gets created. 14402 */ 14403 ; 14404 14405 _proto.createEl = function createEl$$1(type, props, attributes) { 14406 if (props === void 0) { 14407 props = {}; 14408 } 14409 14410 if (attributes === void 0) { 14411 attributes = {}; 14412 } 14413 14414 // Add the slider element class to all sub classes 14415 props.className = props.className + ' vjs-slider'; 14416 props = assign({ 14417 tabIndex: 0 14418 }, props); 14419 attributes = assign({ 14420 'role': 'slider', 14421 'aria-valuenow': 0, 14422 'aria-valuemin': 0, 14423 'aria-valuemax': 100, 14424 'tabIndex': 0 14425 }, attributes); 14426 return _Component.prototype.createEl.call(this, type, props, attributes); 14427 } 14428 /** 14429 * Handle `mousedown` or `touchstart` events on the `Slider`. 14430 * 14431 * @param {EventTarget~Event} event 14432 * `mousedown` or `touchstart` event that triggered this function 14433 * 14434 * @listens mousedown 14435 * @listens touchstart 14436 * @fires Slider#slideractive 14437 */ 14438 ; 14439 14440 _proto.handleMouseDown = function handleMouseDown(event) { 14441 var doc = this.bar.el_.ownerDocument; 14442 14443 if (event.type === 'mousedown') { 14444 event.preventDefault(); 14445 } // Do not call preventDefault() on touchstart in Chrome 14446 // to avoid console warnings. Use a 'touch-action: none' style 14447 // instead to prevent unintented scrolling. 14448 // https://developers.google.com/web/updates/2017/01/scrolling-intervention 14449 14450 14451 if (event.type === 'touchstart' && !IS_CHROME) { 14452 event.preventDefault(); 14453 } 14454 14455 blockTextSelection(); 14456 this.addClass('vjs-sliding'); 14457 /**
14458 * Triggered when the slider is in an active state 14459 * 14460 * @event Slider#slideractive 14461 * @type {EventTarget~Event} 14462 */ 14463 14464 this.trigger('slideractive'); 14465 this.on(doc, 'mousemove', this.handleMouseMove); 14466 this.on(doc, 'mouseup', this.handleMouseUp); 14467 this.on(doc, 'touchmove', this.handleMouseMove); 14468 this.on(doc, 'touchend', this.handleMouseUp); 14469 this.handleMouseMove(event); 14470 } 14471 /** 14472 * Handle the `mousemove`, `touchmove`, and `mousedown` events on this `Slider`. 14473 * The `mousemove` and `touchmove` events will only only trigger this function during 14474 * `mousedown` and `touchstart`. This is due to {@link Slider#handleMouseDown} and 14475 * {@link Slider#handleMouseUp}. 14476 * 14477 * @param {EventTarget~Event} event 14478 * `mousedown`, `mousemove`, `touchstart`, or `touchmove` event that triggered 14479 * this function 14480 * 14481 * @listens mousemove 14482 * @listens touchmove 14483 */ 14484 ; 14485 14486 _proto.handleMouseMove = function handleMouseMove(event) {} 14487 /** 14488 * Handle `mouseup` or `touchend` events on the `Slider`. 14489 * 14490 * @param {EventTarget~Event} event 14491 * `mouseup` or `touchend` event that triggered this function. 14492 * 14493 * @listens touchend 14494 * @listens mouseup 14495 * @fires Slider#sliderinactive 14496 */ 14497 ; 14498 14499 _proto.handleMouseUp = function handleMouseUp() { 14500 var doc = this.bar.el_.ownerDocument; 14501 unblockTextSelection(); 14502 this.removeClass('vjs-sliding'); 14503 /** 14504 * Triggered when the slider is no longer in an active state. 14505 * 14506 * @event Slider#sliderinactive 14507 * @type {EventTarget~Event} 14508 */ 14509 14510 this.trigger('sliderinactive'); 14511 this.off(doc, 'mousemove', this.handleMouseMove); 14512 this.off(doc, 'mouseup', this.handleMouseUp); 14513 this.off(doc, 'touchmove', this.handleMouseMove); 14514 this.off(doc, 'touchend', this.handleMouseUp); 14515 this.update(); 14516 } 14517 /** 14518 * Update the progress bar of the `Slider`. 14519 * 14520 * @return {number} 14521 * The percentage of progress the progress bar represents as a 14522 * number from 0 to 1. 14523 */ 14524 ; 14525 14526 _proto.update = function update() { 14527 // In VolumeBar init we have a setTimeout for update that pops and update 14528 // to the end of the execution stack. The player is destroyed before then 14529 // update will cause an error 14530 if (!this.el_) { 14531 return; 14532 } // If scrubbing, we could use a cached value to make the handle keep up 14533 // with the user's mouse. On HTML5 browsers scrubbing is really smooth, but 14534 // some flash players are slow, so we might want to utilize this later. 14535 // var progress = (this.player_.scrubbing()) ? this.player_.getCache().currentTime / this.player_.duration() : this.player_.currentTime() / this.player_.duration(); 14536 14537 14538 var progress = this.getPercent(); 14539 var bar = this.bar; // If there's no bar... 14540 14541 if (!bar) { 14542 return; 14543 } // Protect against no duration and other division issues 14544 14545 14546 if (typeof progress !== 'number' || progress !== progress || progress < 0 || progress === Infinity) { 14547 progress = 0; 14548 } // Convert to a percentage for setting 14549 14550 14551 var percentage = (progress * 100).toFixed(2) + '%'; 14552 var style = bar.el().style; // Set the new bar width or height 14553 14554 if (this.vertical()) { 14555 style.height = percentage; 14556 } else { 14557 style.width = percentage; 14558 } 14559 14560 return progress; 14561 } 14562 /** 14563 * Calculate distance for slider 14564 * 14565 * @param {EventTarget~Event} event 14566 * The event that caused this function to run. 14567 * 14568 * @return {number} 14569 * The current position of the Slider. 14570 * - position.x for vertical `Slider`s 14571 * - position.y for horizontal `Slider`s 14572 */ 14573 ; 14574 14575 _proto.calculateDistance = function calculateDistance(event) { 14576 var position = getPointerPosition(this.el_, event); 14577 14578 if (this.vertical()) { 14579 return position.y; 14580 } 14581 14582 return position.x; 14583 } 14584 /** 14585 * Handle a `keydown` event on the `Slider`. Watches for left, rigth, up, and down 14586 * arrow keys. This function will only be called when the slider has focus. See 14587 * {@link Slider#handleFocus} and {@link Slider#handleBlur}. 14588 * 14589 * @param {EventTarget~Event} event 14590 * the `keydown` event that caused this function to run. 14591 * 14592 * @listens keydown 14593 */ 14594 ; 14595 14596 _proto.handleKeyDown = function handleKeyDown(event) { 14597 // Left and Down Arrows 14598 if (keycode.isEventKey(event, 'Left') || keycode.isEventKey(event, 'Down')) { 14599 event.preventDefault(); 14600 event.stopPropagation(); 14601 this.stepBack(); // Up and Right Arrows 14602 } else if (keycode.isEventKey(event, 'Right') || keycode.isEventKey(event, 'Up')) { 14603 event.preventDefault(); 14604 event.stopPropagation(); 14605 this.stepForward(); 14606 } else { 14607 // Pass keydown handling up for unsupported keys 14608 _Component.prototype.handleKeyDown.call(this, event); 14609 } 14610 } 14611 /** 14612 * Listener for click events on slider, used to prevent clicks 14613 * from bubbling up to parent elements like button menus. 14614 * 14615 * @param {Object} event 14616 * Event that caused this object to run 14617 */ 14618 ; 14619 14620 _proto.handleClick = function handleClick(event) { 14621 event.stopPropagation(); 14622 event.preventDefault(); 14623 } 14624 /** 14625 * Get/set if slider is horizontal for vertical 14626 * 14627 * @param {boolean} [bool] 14628 * - true if slider is vertical, 14629 * - false is horizontal 14630 * 14631 * @return {boolean} 14632 * - true if slider is vertical, and getting 14633 * - false if the slider is horizontal, and getting 14634 */ 14635 ; 14636 14637 _proto.vertical = function vertical(bool) { 14638 if (bool === undefined) { 14639 return this.vertical_ || false; 14640 } 14641 14642 this.vertical_ = !!bool; 14643 14644 if (this.vertical_) { 14645 this.addClass('vjs-slider-vertical'); 14646 } else { 14647 this.addClass('vjs-slider-horizontal'); 14648 } 14649 }; 14650 14651 return Slider; 14652 }(Component); 14653 14654 Component.registerComponent('Slider', Slider); 14655 14656 /** 14657 * Shows loading progress 14658 * 14659 * @extends Component 14660 */ 14661 14662 var LoadProgressBar = 14663 /*#__PURE__*/ 14664 function (_Component) { 14665 _inheritsLoose(LoadProgressBar, _Component); 14666 14667 /** 14668 * Creates an instance of this class. 14669 * 14670 * @param {Player} player 14671 * The `Player` that this class should be attached to. 14672 * 14673 * @param {Object} [options] 14674 * The key/value store of player options. 14675 */ 14676 function LoadProgressBar(player, options) { 14677 var _this; 14678 14679 _this = _Component.call(this, player, options) || this; 14680 _this.partEls_ = []; 14681 14682 _this.on(player, 'progress', _this.update); 14683 14684 return _this; 14685 } 14686 /** 14687 * Create the `Component`'s DOM element 14688 * 14689 * @return {Element} 14690 * The element that was created. 14691 */ 14692 14693 14694 var _proto = LoadProgressBar.prototype; 14695 14696 _proto.createEl = function createEl$$1() { 14697 return _Component.prototype.createEl.call(this, 'div', { 14698 className: 'vjs-load-progress', 14699 innerHTML: "<span class=\"vjs-control-text\"><span>" + this.localize('Loaded') + "</span>: <span class=\"vjs-control-text-loaded-percentage\">0%</span></span>" 14700 }); 14701 }; 14702 14703 _proto.dispose = function dispose() { 14704 this.partEls_ = null; 14705 14706 _Component.prototype.dispose.call(this); 14707 } 14708 /** 14709 * Update progress bar 14710 * 14711 * @param {EventTarget~Event} [event] 14712 * The `progress` event that caused this function to run. 14713 * 14714 * @listens Player#progress 14715 */ 14716 ; 14717 14718 _proto.update = function update(event) { 14719 var liveTracker = this.player_.liveTracker; 14720 var buffered = this.player_.buffered(); 14721 var duration = liveTracker && liveTracker.isLive() ? liveTracker.seekableEnd() : this.player_.duration(); 14722 var bufferedEnd = this.player_.bufferedEnd(); 14723 var children = this.partEls_; 14724 var controlTextPercentage = this.$('.vjs-control-text-loaded-percentage'); // get the percent width of a time compared to the total end 14725 14726 var percentify = function percentify(time, end, rounded) { 14727 // no NaN 14728 var percent = time / end || 0; 14729 percent = (percent >= 1 ? 1 : percent) * 100; 14730 14731 if (rounded) { 14732 percent = percent.toFixed(2); 14733 } 14734 14735 return percent + '%'; 14736 }; // update the width of the progress bar 14737 14738 14739 this.el_.style.width = percentify(bufferedEnd, duration); // update the control-text 14740 14741 textContent(controlTextPercentage, percentify(bufferedEnd, duration, true)); // add child elements to represent the individual buffered time ranges 14742 14743 for (var i = 0; i < buffered.length; i++) { 14744 var start = buffered.start(i); 14745 var end = buffered.end(i); 14746 var part = children[i]; 14747 14748 if (!part) { 14749 part = this.el_.appendChild(createEl()); 14750 children[i] = part;
vendor: 88,552 bytes, lines 14751-17856
14751 } // set the percent based on the width of the progress bar (bufferedEnd) 14752 14753 14754 part.style.left = percentify(start, bufferedEnd); 14755 part.style.width = percentify(end - start, bufferedEnd); 14756 } // remove unused buffered range elements 14757 14758 14759 for (var _i = children.length; _i > buffered.length; _i--) { 14760 this.el_.removeChild(children[_i - 1]); 14761 } 14762 14763 children.length = buffered.length; 14764 }; 14765 14766 return LoadProgressBar; 14767 }(Component); 14768 14769 Component.registerComponent('LoadProgressBar', LoadProgressBar); 14770 14771 /** 14772 * Time tooltips display a time above the progress bar. 14773 * 14774 * @extends Component 14775 */ 14776 14777 var TimeTooltip = 14778 /*#__PURE__*/ 14779 function (_Component) { 14780 _inheritsLoose(TimeTooltip, _Component); 14781 14782 function TimeTooltip() { 14783 return _Component.apply(this, arguments) || this; 14784 } 14785 14786 var _proto = TimeTooltip.prototype; 14787 14788 /** 14789 * Create the time tooltip DOM element 14790 * 14791 * @return {Element} 14792 * The element that was created. 14793 */ 14794 _proto.createEl = function createEl$$1() { 14795 return _Component.prototype.createEl.call(this, 'div', { 14796 className: 'vjs-time-tooltip' 14797 }, { 14798 'aria-hidden': 'true' 14799 }); 14800 } 14801 /** 14802 * Updates the position of the time tooltip relative to the `SeekBar`. 14803 * 14804 * @param {Object} seekBarRect 14805 * The `ClientRect` for the {@link SeekBar} element. 14806 * 14807 * @param {number} seekBarPoint 14808 * A number from 0 to 1, representing a horizontal reference point 14809 * from the left edge of the {@link SeekBar} 14810 */ 14811 ; 14812 14813 _proto.update = function update(seekBarRect, seekBarPoint, content) { 14814 var tooltipRect = getBoundingClientRect(this.el_); 14815 var playerRect = getBoundingClientRect(this.player_.el()); 14816 var seekBarPointPx = seekBarRect.width * seekBarPoint; // do nothing if either rect isn't available 14817 // for example, if the player isn't in the DOM for testing 14818 14819 if (!playerRect || !tooltipRect) { 14820 return; 14821 } // This is the space left of the `seekBarPoint` available within the bounds 14822 // of the player. We calculate any gap between the left edge of the player 14823 // and the left edge of the `SeekBar` and add the number of pixels in the 14824 // `SeekBar` before hitting the `seekBarPoint` 14825 14826 14827 var spaceLeftOfPoint = seekBarRect.left - playerRect.left + seekBarPointPx; // This is the space right of the `seekBarPoint` available within the bounds 14828 // of the player. We calculate the number of pixels from the `seekBarPoint` 14829 // to the right edge of the `SeekBar` and add to that any gap between the 14830 // right edge of the `SeekBar` and the player. 14831 14832 var spaceRightOfPoint = seekBarRect.width - seekBarPointPx + (playerRect.right - seekBarRect.right); // This is the number of pixels by which the tooltip will need to be pulled 14833 // further to the right to center it over the `seekBarPoint`. 14834 14835 var pullTooltipBy = tooltipRect.width / 2; // Adjust the `pullTooltipBy` distance to the left or right depending on 14836 // the results of the space calculations above. 14837 14838 if (spaceLeftOfPoint < pullTooltipBy) { 14839 pullTooltipBy += pullTooltipBy - spaceLeftOfPoint; 14840 } else if (spaceRightOfPoint < pullTooltipBy) { 14841 pullTooltipBy = spaceRightOfPoint; 14842 } // Due to the imprecision of decimal/ratio based calculations and varying 14843 // rounding behaviors, there are cases where the spacing adjustment is off 14844 // by a pixel or two. This adds insurance to these calculations. 14845 14846 14847 if (pullTooltipBy < 0) { 14848 pullTooltipBy = 0; 14849 } else if (pullTooltipBy > tooltipRect.width) { 14850 pullTooltipBy = tooltipRect.width; 14851 } 14852 14853 this.el_.style.right = "-" + pullTooltipBy + "px"; 14854 textContent(this.el_, content); 14855 } 14856 /** 14857 * Updates the position of the time tooltip relative to the `SeekBar`. 14858 * 14859 * @param {Object} seekBarRect 14860 * The `ClientRect` for the {@link SeekBar} element. 14861 * 14862 * @param {number} seekBarPoint 14863 * A number from 0 to 1, representing a horizontal reference point 14864 * from the left edge of the {@link SeekBar} 14865 * 14866 * @param {number} time 14867 * The time to update the tooltip to, not used during live playback 14868 * 14869 * @param {Function} cb 14870 * A function that will be called during the request animation frame 14871 * for tooltips that need to do additional animations from the default 14872 */ 14873 ; 14874 14875 _proto.updateTime = function updateTime(seekBarRect, seekBarPoint, time, cb) { 14876 var _this = this; 14877 14878 // If there is an existing rAF ID, cancel it so we don't over-queue. 14879 if (this.rafId_) { 14880 this.cancelAnimationFrame(this.rafId_); 14881 } 14882 14883 this.rafId_ = this.requestAnimationFrame(function () { 14884 var content; 14885 14886 var duration = _this.player_.duration(); 14887 14888 if (_this.player_.liveTracker && _this.player_.liveTracker.isLive()) { 14889 var liveWindow = _this.player_.liveTracker.liveWindow(); 14890 14891 var secondsBehind = liveWindow - seekBarPoint * liveWindow; 14892 content = (secondsBehind < 1 ? '' : '-') + formatTime(secondsBehind, liveWindow); 14893 } else { 14894 content = formatTime(time, duration); 14895 } 14896 14897 _this.update(seekBarRect, seekBarPoint, content); 14898 14899 if (cb) { 14900 cb(); 14901 } 14902 }); 14903 }; 14904 14905 return TimeTooltip; 14906 }(Component); 14907 14908 Component.registerComponent('TimeTooltip', TimeTooltip); 14909 14910 /** 14911 * Used by {@link SeekBar} to display media playback progress as part of the 14912 * {@link ProgressControl}. 14913 * 14914 * @extends Component 14915 */ 14916 14917 var PlayProgressBar = 14918 /*#__PURE__*/ 14919 function (_Component) { 14920 _inheritsLoose(PlayProgressBar, _Component); 14921 14922 function PlayProgressBar() { 14923 return _Component.apply(this, arguments) || this; 14924 } 14925 14926 var _proto = PlayProgressBar.prototype; 14927 14928 /** 14929 * Create the the DOM element for this class. 14930 * 14931 * @return {Element} 14932 * The element that was created. 14933 */ 14934 _proto.createEl = function createEl() { 14935 return _Component.prototype.createEl.call(this, 'div', { 14936 className: 'vjs-play-progress vjs-slider-bar' 14937 }, { 14938 'aria-hidden': 'true' 14939 }); 14940 } 14941 /** 14942 * Enqueues updates to its own DOM as well as the DOM of its 14943 * {@link TimeTooltip} child. 14944 * 14945 * @param {Object} seekBarRect 14946 * The `ClientRect` for the {@link SeekBar} element. 14947 * 14948 * @param {number} seekBarPoint 14949 * A number from 0 to 1, representing a horizontal reference point 14950 * from the left edge of the {@link SeekBar} 14951 */ 14952 ; 14953 14954 _proto.update = function update(seekBarRect, seekBarPoint) { 14955 var timeTooltip = this.getChild('timeTooltip'); 14956 14957 if (!timeTooltip) { 14958 return; 14959 } 14960 14961 var time = this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime(); 14962 timeTooltip.updateTime(seekBarRect, seekBarPoint, time); 14963 }; 14964 14965 return PlayProgressBar; 14966 }(Component); 14967 /** 14968 * Default options for {@link PlayProgressBar}. 14969 * 14970 * @type {Object} 14971 * @private 14972 */ 14973 14974 14975 PlayProgressBar.prototype.options_ = { 14976 children: [] 14977 }; // Time tooltips should not be added to a player on mobile devices 14978 14979 if (!IS_IOS && !IS_ANDROID) { 14980 PlayProgressBar.prototype.options_.children.push('timeTooltip'); 14981 } 14982 14983 Component.registerComponent('PlayProgressBar', PlayProgressBar); 14984 14985 /** 14986 * The {@link MouseTimeDisplay} component tracks mouse movement over the 14987 * {@link ProgressControl}. It displays an indicator and a {@link TimeTooltip} 14988 * indicating the time which is represented by a given point in the 14989 * {@link ProgressControl}. 14990 * 14991 * @extends Component 14992 */ 14993 14994 var MouseTimeDisplay = 14995 /*#__PURE__*/ 14996 function (_Component) { 14997 _inheritsLoose(MouseTimeDisplay, _Component); 14998 14999 /** 15000 * Creates an instance of this class. 15001 * 15002 * @param {Player} player 15003 * The {@link Player} that this class should be attached to. 15004 * 15005 * @param {Object} [options] 15006 * The key/value store of player options. 15007 */ 15008 function MouseTimeDisplay(player, options) { 15009 var _this; 15010 15011 _this = _Component.call(this, player, options) || this; 15012 _this.update = throttle(bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.update), 25); 15013 return _this; 15014 } 15015 /** 15016 * Create the DOM element for this class. 15017 * 15018 * @return {Element} 15019 * The element that was created. 15020 */ 15021 15022 15023 var _proto = MouseTimeDisplay.prototype; 15024 15025 _proto.createEl = function createEl() { 15026 return _Component.prototype.createEl.call(this, 'div', { 15027 className: 'vjs-mouse-display' 15028 }); 15029 } 15030 /** 15031 * Enqueues updates to its own DOM as well as the DOM of its 15032 * {@link TimeTooltip} child. 15033 * 15034 * @param {Object} seekBarRect 15035 * The `ClientRect` for the {@link SeekBar} element. 15036 * 15037 * @param {number} seekBarPoint 15038 * A number from 0 to 1, representing a horizontal reference point 15039 * from the left edge of the {@link SeekBar} 15040 */ 15041 ; 15042 15043 _proto.update = function update(seekBarRect, seekBarPoint) { 15044 var _this2 = this; 15045 15046 var time = seekBarPoint * this.player_.duration(); 15047 this.getChild('timeTooltip').updateTime(seekBarRect, seekBarPoint, time, function () { 15048 _this2.el_.style.left = seekBarRect.width * seekBarPoint + "px"; 15049 }); 15050 }; 15051 15052 return MouseTimeDisplay; 15053 }(Component); 15054 /** 15055 * Default options for `MouseTimeDisplay` 15056 * 15057 * @type {Object} 15058 * @private 15059 */ 15060 15061 15062 MouseTimeDisplay.prototype.options_ = { 15063 children: ['timeTooltip'] 15064 }; 15065 Component.registerComponent('MouseTimeDisplay', MouseTimeDisplay); 15066 15067 var STEP_SECONDS = 5; // The multiplier of STEP_SECONDS that PgUp/PgDown move the timeline. 15068 15069 var PAGE_KEY_MULTIPLIER = 12; // The interval at which the bar should update as it progresses. 15070 15071 var UPDATE_REFRESH_INTERVAL = 30; 15072 /** 15073 * Seek bar and container for the progress bars. Uses {@link PlayProgressBar} 15074 * as its `bar`. 15075 * 15076 * @extends Slider 15077 */ 15078 15079 var SeekBar = 15080 /*#__PURE__*/ 15081 function (_Slider) { 15082 _inheritsLoose(SeekBar, _Slider); 15083 15084 /** 15085 * Creates an instance of this class. 15086 * 15087 * @param {Player} player 15088 * The `Player` that this class should be attached to. 15089 * 15090 * @param {Object} [options] 15091 * The key/value store of player options. 15092 */ 15093 function SeekBar(player, options) { 15094 var _this; 15095 15096 _this = _Slider.call(this, player, options) || this; 15097 15098 _this.setEventHandlers_(); 15099 15100 return _this; 15101 } 15102 /** 15103 * Sets the event handlers 15104 * 15105 * @private 15106 */ 15107 15108 15109 var _proto = SeekBar.prototype; 15110 15111 _proto.setEventHandlers_ = function setEventHandlers_() { 15112 this.update = throttle(bind(this, this.update), UPDATE_REFRESH_INTERVAL); 15113 this.on(this.player_, 'timeupdate', this.update); 15114 this.on(this.player_, 'ended', this.handleEnded); 15115 this.on(this.player_, 'durationchange', this.update); 15116 15117 if (this.player_.liveTracker) { 15118 this.on(this.player_.liveTracker, 'liveedgechange', this.update); 15119 } // when playing, let's ensure we smoothly update the play progress bar 15120 // via an interval 15121 15122 15123 this.updateInterval = null; 15124 this.on(this.player_, ['playing'], this.enableInterval_); 15125 this.on(this.player_, ['ended', 'pause', 'waiting'], this.disableInterval_); // we don't need to update the play progress if the document is hidden, 15126 // also, this causes the CPU to spike and eventually crash the page on IE11. 15127 15128 if ('hidden' in document && 'visibilityState' in document) { 15129 this.on(document, 'visibilitychange', this.toggleVisibility_); 15130 } 15131 }; 15132 15133 _proto.toggleVisibility_ = function toggleVisibility_(e) { 15134 if (document.hidden) { 15135 this.disableInterval_(e); 15136 } else { 15137 this.enableInterval_(); // we just switched back to the page and someone may be looking, so, update ASAP 15138 15139 this.requestAnimationFrame(this.update); 15140 } 15141 }; 15142 15143 _proto.enableInterval_ = function enableInterval_() { 15144 var _this2 = this; 15145 15146 this.clearInterval(this.updateInterval); 15147 this.updateInterval = this.setInterval(function () { 15148 _this2.requestAnimationFrame(_this2.update); 15149 }, UPDATE_REFRESH_INTERVAL); 15150 }; 15151 15152 _proto.disableInterval_ = function disableInterval_(e) { 15153 if (this.player_.liveTracker && this.player_.liveTracker.isLive() && e.type !== 'ended') { 15154 return; 15155 } 15156 15157 this.clearInterval(this.updateInterval); 15158 } 15159 /** 15160 * Create the `Component`'s DOM element 15161 * 15162 * @return {Element} 15163 * The element that was created. 15164 */ 15165 ; 15166 15167 _proto.createEl = function createEl$$1() { 15168 return _Slider.prototype.createEl.call(this, 'div', { 15169 className: 'vjs-progress-holder' 15170 }, { 15171 'aria-label': this.localize('Progress Bar') 15172 }); 15173 } 15174 /** 15175 * This function updates the play progress bar and accessibility 15176 * attributes to whatever is passed in. 15177 * 15178 * @param {number} currentTime 15179 * The currentTime value that should be used for accessibility 15180 * 15181 * @param {number} percent 15182 * The percentage as a decimal that the bar should be filled from 0-1. 15183 * 15184 * @private 15185 */ 15186 ; 15187 15188 _proto.update_ = function update_(currentTime, percent) { 15189 var liveTracker = this.player_.liveTracker; 15190 var duration = this.player_.duration(); 15191 15192 if (liveTracker && liveTracker.isLive()) { 15193 duration = this.player_.liveTracker.liveCurrentTime(); 15194 } // machine readable value of progress bar (percentage complete) 15195 15196 15197 this.el_.setAttribute('aria-valuenow', (percent * 100).toFixed(2)); // human readable value of progress bar (time complete) 15198 15199 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`. 15200 15201 if (this.bar) { 15202 this.bar.update(getBoundingClientRect(this.el_), percent); 15203 } 15204 } 15205 /** 15206 * Update the seek bar's UI. 15207 * 15208 * @param {EventTarget~Event} [event] 15209 * The `timeupdate` or `ended` event that caused this to run. 15210 * 15211 * @listens Player#timeupdate 15212 * 15213 * @return {number} 15214 * The current percent at a number from 0-1 15215 */ 15216 ; 15217 15218 _proto.update = function update(event) { 15219 // if the offsetParent is null, then this element is hidden, in which case 15220 // we don't need to update it. 15221 if (this.el().offsetParent === null) { 15222 return; 15223 } 15224 15225 var percent = _Slider.prototype.update.call(this); 15226 15227 this.update_(this.getCurrentTime_(), percent); 15228 return percent; 15229 } 15230 /** 15231 * Get the value of current time but allows for smooth scrubbing, 15232 * when player can't keep up. 15233 * 15234 * @return {number} 15235 * The current time value to display 15236 * 15237 * @private 15238 */ 15239 ; 15240 15241 _proto.getCurrentTime_ = function getCurrentTime_() { 15242 return this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime(); 15243 } 15244 /** 15245 * We want the seek bar to be full on ended 15246 * no matter what the actual internal values are. so we force it. 15247 * 15248 * @param {EventTarget~Event} [event] 15249 * The `timeupdate` or `ended` event that caused this to run. 15250 * 15251 * @listens Player#ended 15252 */ 15253 ; 15254 15255 _proto.handleEnded = function handleEnded(event) { 15256 this.update_(this.player_.duration(), 1); 15257 } 15258 /** 15259 * Get the percentage of media played so far. 15260 * 15261 * @return {number} 15262 * The percentage of media played so far (0 to 1). 15263 */ 15264 ; 15265 15266 _proto.getPercent = function getPercent() { 15267 var currentTime = this.getCurrentTime_(); 15268 var percent; 15269 var liveTracker = this.player_.liveTracker; 15270 15271 if (liveTracker && liveTracker.isLive()) { 15272 percent = (currentTime - liveTracker.seekableStart()) / liveTracker.liveWindow(); // prevent the percent from changing at the live edge 15273 15274 if (liveTracker.atLiveEdge()) { 15275 percent = 1; 15276 } 15277 } else { 15278 percent = currentTime / this.player_.duration(); 15279 } 15280 15281 return percent >= 1 ? 1 : percent || 0; 15282 } 15283 /** 15284 * Handle mouse down on seek bar 15285 * 15286 * @param {EventTarget~Event} event 15287 * The `mousedown` event that caused this to run. 15288 * 15289 * @listens mousedown 15290 */ 15291 ; 15292 15293 _proto.handleMouseDown = function handleMouseDown(event) { 15294 if (!isSingleLeftClick(event)) { 15295 return; 15296 } // Stop event propagation to prevent double fire in progress-control.js 15297 15298 15299 event.stopPropagation(); 15300 this.player_.scrubbing(true); 15301 this.videoWasPlaying = !this.player_.paused(); 15302 this.player_.pause(); 15303 15304 _Slider.prototype.handleMouseDown.call(this, event); 15305 } 15306 /** 15307 * Handle mouse move on seek bar 15308 * 15309 * @param {EventTarget~Event} event 15310 * The `mousemove` event that caused this to run. 15311 * 15312 * @listens mousemove 15313 */ 15314 ; 15315 15316 _proto.handleMouseMove = function handleMouseMove(event) { 15317 if (!isSingleLeftClick(event)) { 15318 return; 15319 } 15320 15321 var newTime; 15322 var distance = this.calculateDistance(event); 15323 var liveTracker = this.player_.liveTracker; 15324 15325 if (!liveTracker || !liveTracker.isLive()) { 15326 newTime = distance * this.player_.duration(); // Don't let video end while scrubbing. 15327 15328 if (newTime === this.player_.duration()) { 15329 newTime = newTime - 0.1; 15330 } 15331 } else { 15332 var seekableStart = liveTracker.seekableStart(); 15333 var seekableEnd = liveTracker.liveCurrentTime(); 15334 newTime = seekableStart + distance * liveTracker.liveWindow(); // Don't let video end while scrubbing. 15335 15336 if (newTime >= seekableEnd) { 15337 newTime = seekableEnd; 15338 } // Compensate for precision differences so that currentTime is not less 15339 // than seekable start 15340 15341 15342 if (newTime <= seekableStart) { 15343 newTime = seekableStart + 0.1; 15344 } // On android seekableEnd can be Infinity sometimes, 15345 // this will cause newTime to be Infinity, which is 15346 // not a valid currentTime. 15347 15348 15349 if (newTime === Infinity) { 15350 return; 15351 } 15352 } // Set new time (tell player to seek to new time) 15353 15354 15355 this.player_.currentTime(newTime); 15356 }; 15357 15358 _proto.enable = function enable() { 15359 _Slider.prototype.enable.call(this); 15360 15361 var mouseTimeDisplay = this.getChild('mouseTimeDisplay'); 15362 15363 if (!mouseTimeDisplay) { 15364 return; 15365 } 15366 15367 mouseTimeDisplay.show(); 15368 }; 15369 15370 _proto.disable = function disable() { 15371 _Slider.prototype.disable.call(this); 15372 15373 var mouseTimeDisplay = this.getChild('mouseTimeDisplay'); 15374 15375 if (!mouseTimeDisplay) { 15376 return; 15377 } 15378 15379 mouseTimeDisplay.hide(); 15380 } 15381 /** 15382 * Handle mouse up on seek bar 15383 * 15384 * @param {EventTarget~Event} event 15385 * The `mouseup` event that caused this to run. 15386 * 15387 * @listens mouseup 15388 */ 15389 ; 15390 15391 _proto.handleMouseUp = function handleMouseUp(event) { 15392 _Slider.prototype.handleMouseUp.call(this, event); // Stop event propagation to prevent double fire in progress-control.js 15393 15394 15395 if (event) { 15396 event.stopPropagation(); 15397 } 15398 15399 this.player_.scrubbing(false); 15400 /** 15401 * Trigger timeupdate because we're done seeking and the time has changed. 15402 * This is particularly useful for if the player is paused to time the time displays. 15403 * 15404 * @event Tech#timeupdate 15405 * @type {EventTarget~Event} 15406 */ 15407 15408 this.player_.trigger({ 15409 type: 'timeupdate', 15410 target: this, 15411 manuallyTriggered: true 15412 }); 15413 15414 if (this.videoWasPlaying) { 15415 silencePromise(this.player_.play()); 15416 } 15417 } 15418 /** 15419 * Move more quickly fast forward for keyboard-only users 15420 */ 15421 ; 15422 15423 _proto.stepForward = function stepForward() { 15424 this.player_.currentTime(this.player_.currentTime() + STEP_SECONDS); 15425 } 15426 /** 15427 * Move more quickly rewind for keyboard-only users 15428 */ 15429 ; 15430 15431 _proto.stepBack = function stepBack() { 15432 this.player_.currentTime(this.player_.currentTime() - STEP_SECONDS); 15433 } 15434 /** 15435 * Toggles the playback state of the player 15436 * This gets called when enter or space is used on the seekbar 15437 * 15438 * @param {EventTarget~Event} event 15439 * The `keydown` event that caused this function to be called 15440 * 15441 */ 15442 ; 15443 15444 _proto.handleAction = function handleAction(event) { 15445 if (this.player_.paused()) { 15446 this.player_.play(); 15447 } else { 15448 this.player_.pause(); 15449 } 15450 } 15451 /** 15452 * Called when this SeekBar has focus and a key gets pressed down. 15453 * Supports the following keys: 15454 * 15455 * Space or Enter key fire a click event 15456 * Home key moves to start of the timeline 15457 * End key moves to end of the timeline 15458 * Digit "0" through "9" keys move to 0%, 10% ... 80%, 90% of the timeline 15459 * PageDown key moves back a larger step than ArrowDown 15460 * PageUp key moves forward a large step 15461 * 15462 * @param {EventTarget~Event} event 15463 * The `keydown` event that caused this function to be called. 15464 * 15465 * @listens keydown 15466 */ 15467 ; 15468 15469 _proto.handleKeyDown = function handleKeyDown(event) { 15470 if (keycode.isEventKey(event, 'Space') || keycode.isEventKey(event, 'Enter')) { 15471 event.preventDefault(); 15472 event.stopPropagation(); 15473 this.handleAction(event); 15474 } else if (keycode.isEventKey(event, 'Home')) { 15475 event.preventDefault(); 15476 event.stopPropagation(); 15477 this.player_.currentTime(0); 15478 } else if (keycode.isEventKey(event, 'End')) { 15479 event.preventDefault(); 15480 event.stopPropagation(); 15481 this.player_.currentTime(this.player_.duration()); 15482 } else if (/^[0-9]$/.test(keycode(event))) { 15483 event.preventDefault(); 15484 event.stopPropagation(); 15485 var gotoFraction = (keycode.codes[keycode(event)] - keycode.codes['0']) * 10.0 / 100.0; 15486 this.player_.currentTime(this.player_.duration() * gotoFraction); 15487 } else if (keycode.isEventKey(event, 'PgDn')) { 15488 event.preventDefault(); 15489 event.stopPropagation(); 15490 this.player_.currentTime(this.player_.currentTime() - STEP_SECONDS * PAGE_KEY_MULTIPLIER); 15491 } else if (keycode.isEventKey(event, 'PgUp')) { 15492 event.preventDefault(); 15493 event.stopPropagation(); 15494 this.player_.currentTime(this.player_.currentTime() + STEP_SECONDS * PAGE_KEY_MULTIPLIER); 15495 } else { 15496 // Pass keydown handling up for unsupported keys 15497 _Slider.prototype.handleKeyDown.call(this, event); 15498 } 15499 }; 15500 15501 return SeekBar; 15502 }(Slider); 15503 /** 15504 * Default options for the `SeekBar` 15505 * 15506 * @type {Object} 15507 * @private 15508 */ 15509 15510 15511 SeekBar.prototype.options_ = { 15512 children: ['loadProgressBar', 'playProgressBar'], 15513 barName: 'playProgressBar' 15514 }; // MouseTimeDisplay tooltips should not be added to a player on mobile devices 15515 15516 if (!IS_IOS && !IS_ANDROID) { 15517 SeekBar.prototype.options_.children.splice(1, 0, 'mouseTimeDisplay'); 15518 } 15519 15520 Component.registerComponent('SeekBar', SeekBar); 15521 15522 /** 15523 * The Progress Control component contains the seek bar, load progress, 15524 * and play progress. 15525 * 15526 * @extends Component 15527 */ 15528 15529 var ProgressControl = 15530 /*#__PURE__*/ 15531 function (_Component) { 15532 _inheritsLoose(ProgressControl, _Component); 15533 15534 /** 15535 * Creates an instance of this class. 15536 * 15537 * @param {Player} player 15538 * The `Player` that this class should be attached to. 15539 * 15540 * @param {Object} [options] 15541 * The key/value store of player options. 15542 */ 15543 function ProgressControl(player, options) { 15544 var _this; 15545 15546 _this = _Component.call(this, player, options) || this; 15547 _this.handleMouseMove = throttle(bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.handleMouseMove), 25); 15548 _this.throttledHandleMouseSeek = throttle(bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.handleMouseSeek), 25); 15549 15550 _this.enable(); 15551 15552 return _this; 15553 } 15554 /** 15555 * Create the `Component`'s DOM element 15556 * 15557 * @return {Element} 15558 * The element that was created. 15559 */ 15560 15561 15562 var _proto = ProgressControl.prototype; 15563 15564 _proto.createEl = function createEl$$1() { 15565 return _Component.prototype.createEl.call(this, 'div', { 15566 className: 'vjs-progress-control vjs-control' 15567 }); 15568 } 15569 /** 15570 * When the mouse moves over the `ProgressControl`, the pointer position 15571 * gets passed down to the `MouseTimeDisplay` component. 15572 * 15573 * @param {EventTarget~Event} event 15574 * The `mousemove` event that caused this function to run. 15575 * 15576 * @listen mousemove 15577 */ 15578 ; 15579 15580 _proto.handleMouseMove = function handleMouseMove(event) { 15581 var seekBar = this.getChild('seekBar'); 15582 15583 if (seekBar) { 15584 var mouseTimeDisplay = seekBar.getChild('mouseTimeDisplay'); 15585 var seekBarEl = seekBar.el(); 15586 var seekBarRect = getBoundingClientRect(seekBarEl); 15587 var seekBarPoint = getPointerPosition(seekBarEl, event).x; // The default skin has a gap on either side of the `SeekBar`. This means 15588 // that it's possible to trigger this behavior outside the boundaries of 15589 // the `SeekBar`. This ensures we stay within it at all times. 15590 15591 if (seekBarPoint > 1) { 15592 seekBarPoint = 1; 15593 } else if (seekBarPoint < 0) { 15594 seekBarPoint = 0; 15595 } 15596 15597 if (mouseTimeDisplay) { 15598 mouseTimeDisplay.update(seekBarRect, seekBarPoint); 15599 } 15600 } 15601 } 15602 /** 15603 * A throttled version of the {@link ProgressControl#handleMouseSeek} listener. 15604 * 15605 * @method ProgressControl#throttledHandleMouseSeek 15606 * @param {EventTarget~Event} event 15607 * The `mousemove` event that caused this function to run. 15608 * 15609 * @listen mousemove 15610 * @listen touchmove 15611 */ 15612 15613 /** 15614 * Handle `mousemove` or `touchmove` events on the `ProgressControl`. 15615 * 15616 * @param {EventTarget~Event} event 15617 * `mousedown` or `touchstart` event that triggered this function 15618 * 15619 * @listens mousemove 15620 * @listens touchmove 15621 */ 15622 ; 15623 15624 _proto.handleMouseSeek = function handleMouseSeek(event) { 15625 var seekBar = this.getChild('seekBar'); 15626 15627 if (seekBar) { 15628 seekBar.handleMouseMove(event); 15629 } 15630 } 15631 /** 15632 * Are controls are currently enabled for this progress control. 15633 * 15634 * @return {boolean} 15635 * true if controls are enabled, false otherwise 15636 */ 15637 ; 15638 15639 _proto.enabled = function enabled() { 15640 return this.enabled_; 15641 } 15642 /** 15643 * Disable all controls on the progress control and its children 15644 */ 15645 ; 15646 15647 _proto.disable = function disable() { 15648 this.children().forEach(function (child) { 15649 return child.disable && child.disable(); 15650 }); 15651 15652 if (!this.enabled()) { 15653 return; 15654 } 15655 15656 this.off(['mousedown', 'touchstart'], this.handleMouseDown); 15657 this.off(this.el_, 'mousemove', this.handleMouseMove); 15658 this.handleMouseUp(); 15659 this.addClass('disabled'); 15660 this.enabled_ = false; 15661 } 15662 /** 15663 * Enable all controls on the progress control and its children 15664 */ 15665 ; 15666 15667 _proto.enable = function enable() { 15668 this.children().forEach(function (child) { 15669 return child.enable && child.enable(); 15670 }); 15671 15672 if (this.enabled()) { 15673 return; 15674 } 15675 15676 this.on(['mousedown', 'touchstart'], this.handleMouseDown); 15677 this.on(this.el_, 'mousemove', this.handleMouseMove); 15678 this.removeClass('disabled'); 15679 this.enabled_ = true; 15680 } 15681 /** 15682 * Handle `mousedown` or `touchstart` events on the `ProgressControl`. 15683 * 15684 * @param {EventTarget~Event} event 15685 * `mousedown` or `touchstart` event that triggered this function 15686 * 15687 * @listens mousedown 15688 * @listens touchstart 15689 */ 15690 ; 15691 15692 _proto.handleMouseDown = function handleMouseDown(event) { 15693 var doc = this.el_.ownerDocument; 15694 var seekBar = this.getChild('seekBar'); 15695 15696 if (seekBar) { 15697 seekBar.handleMouseDown(event); 15698 } 15699 15700 this.on(doc, 'mousemove', this.throttledHandleMouseSeek); 15701 this.on(doc, 'touchmove', this.throttledHandleMouseSeek); 15702 this.on(doc, 'mouseup', this.handleMouseUp); 15703 this.on(doc, 'touchend', this.handleMouseUp); 15704 } 15705 /** 15706 * Handle `mouseup` or `touchend` events on the `ProgressControl`. 15707 * 15708 * @param {EventTarget~Event} event 15709 * `mouseup` or `touchend` event that triggered this function. 15710 * 15711 * @listens touchend 15712 * @listens mouseup 15713 */ 15714 ; 15715 15716 _proto.handleMouseUp = function handleMouseUp(event) { 15717 var doc = this.el_.ownerDocument; 15718 var seekBar = this.getChild('seekBar'); 15719 15720 if (seekBar) { 15721 seekBar.handleMouseUp(event); 15722 } 15723 15724 this.off(doc, 'mousemove', this.throttledHandleMouseSeek); 15725 this.off(doc, 'touchmove', this.throttledHandleMouseSeek); 15726 this.off(doc, 'mouseup', this.handleMouseUp); 15727 this.off(doc, 'touchend', this.handleMouseUp); 15728 }; 15729 15730 return ProgressControl; 15731 }(Component); 15732 /** 15733 * Default options for `ProgressControl` 15734 * 15735 * @type {Object} 15736 * @private 15737 */ 15738 15739 15740 ProgressControl.prototype.options_ = { 15741 children: ['seekBar'] 15742 }; 15743 Component.registerComponent('ProgressControl', ProgressControl); 15744 15745 /** 15746 * Toggle fullscreen video 15747 * 15748 * @extends Button 15749 */ 15750 15751 var FullscreenToggle = 15752 /*#__PURE__*/ 15753 function (_Button) { 15754 _inheritsLoose(FullscreenToggle, _Button); 15755 15756 /** 15757 * Creates an instance of this class. 15758 * 15759 * @param {Player} player 15760 * The `Player` that this class should be attached to. 15761 * 15762 * @param {Object} [options] 15763 * The key/value store of player options. 15764 */ 15765 function FullscreenToggle(player, options) { 15766 var _this; 15767 15768 _this = _Button.call(this, player, options) || this; 15769 15770 _this.on(player, 'fullscreenchange', _this.handleFullscreenChange); 15771 15772 if (document[FullscreenApi.fullscreenEnabled] === false) { 15773 _this.disable(); 15774 } 15775 15776 return _this; 15777 } 15778 /** 15779 * Builds the default DOM `className`. 15780 * 15781 * @return {string} 15782 * The DOM `className` for this object. 15783 */ 15784 15785 15786 var _proto = FullscreenToggle.prototype; 15787 15788 _proto.buildCSSClass = function buildCSSClass() { 15789 return "vjs-fullscreen-control " + _Button.prototype.buildCSSClass.call(this); 15790 } 15791 /** 15792 * Handles fullscreenchange on the player and change control text accordingly. 15793 * 15794 * @param {EventTarget~Event} [event] 15795 * The {@link Player#fullscreenchange} event that caused this function to be 15796 * called. 15797 * 15798 * @listens Player#fullscreenchange 15799 */ 15800 ; 15801 15802 _proto.handleFullscreenChange = function handleFullscreenChange(event) { 15803 if (this.player_.isFullscreen()) { 15804 this.controlText('Non-Fullscreen'); 15805 } else { 15806 this.controlText('Fullscreen'); 15807 } 15808 } 15809 /** 15810 * This gets called when an `FullscreenToggle` is "clicked". See 15811 * {@link ClickableComponent} for more detailed information on what a click can be. 15812 * 15813 * @param {EventTarget~Event} [event] 15814 * The `keydown`, `tap`, or `click` event that caused this function to be 15815 * called. 15816 * 15817 * @listens tap 15818 * @listens click 15819 */ 15820 ; 15821 15822 _proto.handleClick = function handleClick(event) { 15823 if (!this.player_.isFullscreen()) { 15824 this.player_.requestFullscreen(); 15825 } else { 15826 this.player_.exitFullscreen(); 15827 } 15828 }; 15829 15830 return FullscreenToggle; 15831 }(Button); 15832 /** 15833 * The text that should display over the `FullscreenToggle`s controls. Added for localization. 15834 * 15835 * @type {string} 15836 * @private 15837 */ 15838 15839 15840 FullscreenToggle.prototype.controlText_ = 'Fullscreen'; 15841 Component.registerComponent('FullscreenToggle', FullscreenToggle); 15842 15843 /** 15844 * Check if volume control is supported and if it isn't hide the 15845 * `Component` that was passed using the `vjs-hidden` class. 15846 * 15847 * @param {Component} self 15848 * The component that should be hidden if volume is unsupported 15849 * 15850 * @param {Player} player 15851 * A reference to the player 15852 * 15853 * @private 15854 */ 15855 var checkVolumeSupport = function checkVolumeSupport(self, player) { 15856 // hide volume controls when they're not supported by the current tech 15857 if (player.tech_ && !player.tech_.featuresVolumeControl) { 15858 self.addClass('vjs-hidden'); 15859 } 15860 15861 self.on(player, 'loadstart', function () { 15862 if (!player.tech_.featuresVolumeControl) { 15863 self.addClass('vjs-hidden'); 15864 } else { 15865 self.removeClass('vjs-hidden'); 15866 } 15867 }); 15868 }; 15869 15870 /** 15871 * Shows volume level 15872 * 15873 * @extends Component 15874 */ 15875 15876 var VolumeLevel = 15877 /*#__PURE__*/ 15878 function (_Component) { 15879 _inheritsLoose(VolumeLevel, _Component); 15880 15881 function VolumeLevel() { 15882 return _Component.apply(this, arguments) || this; 15883 } 15884 15885 var _proto = VolumeLevel.prototype; 15886 15887 /** 15888 * Create the `Component`'s DOM element 15889 * 15890 * @return {Element} 15891 * The element that was created. 15892 */ 15893 _proto.createEl = function createEl() { 15894 return _Component.prototype.createEl.call(this, 'div', { 15895 className: 'vjs-volume-level', 15896 innerHTML: '<span class="vjs-control-text"></span>' 15897 }); 15898 }; 15899 15900 return VolumeLevel; 15901 }(Component); 15902 15903 Component.registerComponent('VolumeLevel', VolumeLevel); 15904 15905 /** 15906 * The bar that contains the volume level and can be clicked on to adjust the level 15907 * 15908 * @extends Slider 15909 */ 15910 15911 var VolumeBar = 15912 /*#__PURE__*/ 15913 function (_Slider) { 15914 _inheritsLoose(VolumeBar, _Slider); 15915 15916 /** 15917 * Creates an instance of this class. 15918 * 15919 * @param {Player} player 15920 * The `Player` that this class should be attached to. 15921 * 15922 * @param {Object} [options] 15923 * The key/value store of player options. 15924 */ 15925 function VolumeBar(player, options) { 15926 var _this; 15927 15928 _this = _Slider.call(this, player, options) || this; 15929 15930 _this.on('slideractive', _this.updateLastVolume_); 15931 15932 _this.on(player, 'volumechange', _this.updateARIAAttributes); 15933 15934 player.ready(function () { 15935 return _this.updateARIAAttributes(); 15936 }); 15937 return _this; 15938 } 15939 /** 15940 * Create the `Component`'s DOM element 15941 * 15942 * @return {Element} 15943 * The element that was created. 15944 */ 15945 15946 15947 var _proto = VolumeBar.prototype; 15948 15949 _proto.createEl = function createEl$$1() { 15950 return _Slider.prototype.createEl.call(this, 'div', { 15951 className: 'vjs-volume-bar vjs-slider-bar' 15952 }, { 15953 'aria-label': this.localize('Volume Level'), 15954 'aria-live': 'polite' 15955 }); 15956 } 15957 /** 15958 * Handle mouse down on volume bar 15959 * 15960 * @param {EventTarget~Event} event 15961 * The `mousedown` event that caused this to run. 15962 * 15963 * @listens mousedown 15964 */ 15965 ; 15966 15967 _proto.handleMouseDown = function handleMouseDown(event) { 15968 if (!isSingleLeftClick(event)) { 15969 return; 15970 } 15971 15972 _Slider.prototype.handleMouseDown.call(this, event); 15973 } 15974 /** 15975 * Handle movement events on the {@link VolumeMenuButton}. 15976 * 15977 * @param {EventTarget~Event} event 15978 * The event that caused this function to run. 15979 * 15980 * @listens mousemove 15981 */ 15982 ; 15983 15984 _proto.handleMouseMove = function handleMouseMove(event) { 15985 if (!isSingleLeftClick(event)) { 15986 return; 15987 } 15988 15989 this.checkMuted(); 15990 this.player_.volume(this.calculateDistance(event)); 15991 } 15992 /** 15993 * If the player is muted unmute it. 15994 */ 15995 ; 15996 15997 _proto.checkMuted = function checkMuted() { 15998 if (this.player_.muted()) { 15999 this.player_.muted(false); 16000 } 16001 } 16002 /** 16003 * Get percent of volume level 16004 * 16005 * @return {number} 16006 * Volume level percent as a decimal number. 16007 */ 16008 ; 16009 16010 _proto.getPercent = function getPercent() { 16011 if (this.player_.muted()) { 16012 return 0; 16013 } 16014 16015 return this.player_.volume(); 16016 } 16017 /** 16018 * Increase volume level for keyboard users 16019 */ 16020 ; 16021 16022 _proto.stepForward = function stepForward() { 16023 this.checkMuted(); 16024 this.player_.volume(this.player_.volume() + 0.1); 16025 } 16026 /** 16027 * Decrease volume level for keyboard users 16028 */ 16029 ; 16030 16031 _proto.stepBack = function stepBack() { 16032 this.checkMuted(); 16033 this.player_.volume(this.player_.volume() - 0.1); 16034 } 16035 /** 16036 * Update ARIA accessibility attributes 16037 * 16038 * @param {EventTarget~Event} [event] 16039 * The `volumechange` event that caused this function to run. 16040 * 16041 * @listens Player#volumechange 16042 */ 16043 ; 16044 16045 _proto.updateARIAAttributes = function updateARIAAttributes(event) { 16046 var ariaValue = this.player_.muted() ? 0 : this.volumeAsPercentage_(); 16047 this.el_.setAttribute('aria-valuenow', ariaValue); 16048 this.el_.setAttribute('aria-valuetext', ariaValue + '%'); 16049 } 16050 /** 16051 * Returns the current value of the player volume as a percentage 16052 * 16053 * @private 16054 */ 16055 ; 16056 16057 _proto.volumeAsPercentage_ = function volumeAsPercentage_() { 16058 return Math.round(this.player_.volume() * 100); 16059 } 16060 /** 16061 * When user starts dragging the VolumeBar, store the volume and listen for 16062 * the end of the drag. When the drag ends, if the volume was set to zero, 16063 * set lastVolume to the stored volume. 16064 * 16065 * @listens slideractive 16066 * @private 16067 */ 16068 ; 16069 16070 _proto.updateLastVolume_ = function updateLastVolume_() { 16071 var _this2 = this; 16072 16073 var volumeBeforeDrag = this.player_.volume(); 16074 this.one('sliderinactive', function () { 16075 if (_this2.player_.volume() === 0) { 16076 _this2.player_.lastVolume_(volumeBeforeDrag); 16077 } 16078 }); 16079 }; 16080 16081 return VolumeBar; 16082 }(Slider); 16083 /** 16084 * Default options for the `VolumeBar` 16085 * 16086 * @type {Object} 16087 * @private 16088 */ 16089 16090 16091 VolumeBar.prototype.options_ = { 16092 children: ['volumeLevel'], 16093 barName: 'volumeLevel' 16094 }; 16095 /** 16096 * Call the update event for this Slider when this event happens on the player. 16097 * 16098 * @type {string} 16099 */ 16100 16101 VolumeBar.prototype.playerEvent = 'volumechange'; 16102 Component.registerComponent('VolumeBar', VolumeBar); 16103 16104 /** 16105 * The component for controlling the volume level 16106 * 16107 * @extends Component 16108 */ 16109 16110 var VolumeControl = 16111 /*#__PURE__*/ 16112 function (_Component) { 16113 _inheritsLoose(VolumeControl, _Component); 16114 16115 /** 16116 * Creates an instance of this class. 16117 * 16118 * @param {Player} player 16119 * The `Player` that this class should be attached to. 16120 * 16121 * @param {Object} [options={}] 16122 * The key/value store of player options. 16123 */ 16124 function VolumeControl(player, options) { 16125 var _this; 16126 16127 if (options === void 0) { 16128 options = {}; 16129 } 16130 16131 options.vertical = options.vertical || false; // Pass the vertical option down to the VolumeBar if 16132 // the VolumeBar is turned on. 16133 16134 if (typeof options.volumeBar === 'undefined' || isPlain(options.volumeBar)) { 16135 options.volumeBar = options.volumeBar || {}; 16136 options.volumeBar.vertical = options.vertical; 16137 } 16138 16139 _this = _Component.call(this, player, options) || this; // hide this control if volume support is missing 16140 16141 checkVolumeSupport(_assertThisInitialized(_assertThisInitialized(_this)), player); 16142 _this.throttledHandleMouseMove = throttle(bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.handleMouseMove), 25); 16143 16144 _this.on('mousedown', _this.handleMouseDown); 16145 16146 _this.on('touchstart', _this.handleMouseDown); // while the slider is active (the mouse has been pressed down and 16147 // is dragging) or in focus we do not want to hide the VolumeBar 16148 16149 16150 _this.on(_this.volumeBar, ['focus', 'slideractive'], function () { 16151 _this.volumeBar.addClass('vjs-slider-active'); 16152 16153 _this.addClass('vjs-slider-active'); 16154 16155 _this.trigger('slideractive'); 16156 }); 16157 16158 _this.on(_this.volumeBar, ['blur', 'sliderinactive'], function () { 16159 _this.volumeBar.removeClass('vjs-slider-active'); 16160 16161 _this.removeClass('vjs-slider-active'); 16162 16163 _this.trigger('sliderinactive'); 16164 }); 16165 16166 return _this; 16167 } 16168 /** 16169 * Create the `Component`'s DOM element 16170 * 16171 * @return {Element} 16172 * The element that was created. 16173 */ 16174 16175 16176 var _proto = VolumeControl.prototype; 16177 16178 _proto.createEl = function createEl() { 16179 var orientationClass = 'vjs-volume-horizontal'; 16180 16181 if (this.options_.vertical) { 16182 orientationClass = 'vjs-volume-vertical'; 16183 } 16184 16185 return _Component.prototype.createEl.call(this, 'div', { 16186 className: "vjs-volume-control vjs-control " + orientationClass 16187 }); 16188 } 16189 /** 16190 * Handle `mousedown` or `touchstart` events on the `VolumeControl`. 16191 * 16192 * @param {EventTarget~Event} event 16193 * `mousedown` or `touchstart` event that triggered this function 16194 * 16195 * @listens mousedown 16196 * @listens touchstart 16197 */ 16198 ; 16199 16200 _proto.handleMouseDown = function handleMouseDown(event) { 16201 var doc = this.el_.ownerDocument; 16202 this.on(doc, 'mousemove', this.throttledHandleMouseMove); 16203 this.on(doc, 'touchmove', this.throttledHandleMouseMove); 16204 this.on(doc, 'mouseup', this.handleMouseUp); 16205 this.on(doc, 'touchend', this.handleMouseUp); 16206 } 16207 /** 16208 * Handle `mouseup` or `touchend` events on the `VolumeControl`. 16209 * 16210 * @param {EventTarget~Event} event 16211 * `mouseup` or `touchend` event that triggered this function. 16212 * 16213 * @listens touchend 16214 * @listens mouseup 16215 */ 16216 ; 16217 16218 _proto.handleMouseUp = function handleMouseUp(event) { 16219 var doc = this.el_.ownerDocument; 16220 this.off(doc, 'mousemove', this.throttledHandleMouseMove); 16221 this.off(doc, 'touchmove', this.throttledHandleMouseMove); 16222 this.off(doc, 'mouseup', this.handleMouseUp); 16223 this.off(doc, 'touchend', this.handleMouseUp); 16224 } 16225 /** 16226 * Handle `mousedown` or `touchstart` events on the `VolumeControl`. 16227 * 16228 * @param {EventTarget~Event} event 16229 * `mousedown` or `touchstart` event that triggered this function 16230 * 16231 * @listens mousedown 16232 * @listens touchstart 16233 */ 16234 ; 16235 16236 _proto.handleMouseMove = function handleMouseMove(event) { 16237 this.volumeBar.handleMouseMove(event); 16238 }; 16239 16240 return VolumeControl; 16241 }(Component); 16242 /** 16243 * Default options for the `VolumeControl` 16244 * 16245 * @type {Object} 16246 * @private 16247 */ 16248 16249 16250 VolumeControl.prototype.options_ = { 16251 children: ['volumeBar'] 16252 }; 16253 Component.registerComponent('VolumeControl', VolumeControl); 16254 16255 /** 16256 * Check if muting volume is supported and if it isn't hide the mute toggle 16257 * button. 16258 * 16259 * @param {Component} self 16260 * A reference to the mute toggle button 16261 * 16262 * @param {Player} player 16263 * A reference to the player 16264 * 16265 * @private 16266 */ 16267 var checkMuteSupport = function checkMuteSupport(self, player) { 16268 // hide mute toggle button if it's not supported by the current tech 16269 if (player.tech_ && !player.tech_.featuresMuteControl) { 16270 self.addClass('vjs-hidden'); 16271 } 16272 16273 self.on(player, 'loadstart', function () { 16274 if (!player.tech_.featuresMuteControl) { 16275 self.addClass('vjs-hidden'); 16276 } else { 16277 self.removeClass('vjs-hidden'); 16278 } 16279 }); 16280 }; 16281 16282 /** 16283 * A button component for muting the audio. 16284 * 16285 * @extends Button 16286 */ 16287 16288 var MuteToggle = 16289 /*#__PURE__*/ 16290 function (_Button) { 16291 _inheritsLoose(MuteToggle, _Button); 16292 16293 /** 16294 * Creates an instance of this class. 16295 * 16296 * @param {Player} player 16297 * The `Player` that this class should be attached to. 16298 * 16299 * @param {Object} [options] 16300 * The key/value store of player options. 16301 */ 16302 function MuteToggle(player, options) { 16303 var _this; 16304 16305 _this = _Button.call(this, player, options) || this; // hide this control if volume support is missing 16306 16307 checkMuteSupport(_assertThisInitialized(_assertThisInitialized(_this)), player); 16308 16309 _this.on(player, ['loadstart', 'volumechange'], _this.update); 16310 16311 return _this; 16312 } 16313 /** 16314 * Builds the default DOM `className`. 16315 * 16316 * @return {string} 16317 * The DOM `className` for this object. 16318 */ 16319 16320 16321 var _proto = MuteToggle.prototype; 16322 16323 _proto.buildCSSClass = function buildCSSClass() { 16324 return "vjs-mute-control " + _Button.prototype.buildCSSClass.call(this); 16325 } 16326 /** 16327 * This gets called when an `MuteToggle` is "clicked". See 16328 * {@link ClickableComponent} for more detailed information on what a click can be. 16329 * 16330 * @param {EventTarget~Event} [event] 16331 * The `keydown`, `tap`, or `click` event that caused this function to be 16332 * called. 16333 * 16334 * @listens tap 16335 * @listens click 16336 */ 16337 ; 16338 16339 _proto.handleClick = function handleClick(event) { 16340 var vol = this.player_.volume(); 16341 var lastVolume = this.player_.lastVolume_(); 16342 16343 if (vol === 0) { 16344 var volumeToSet = lastVolume < 0.1 ? 0.1 : lastVolume; 16345 this.player_.volume(volumeToSet); 16346 this.player_.muted(false); 16347 } else { 16348 this.player_.muted(this.player_.muted() ? false : true); 16349 } 16350 } 16351 /** 16352 * Update the `MuteToggle` button based on the state of `volume` and `muted` 16353 * on the player. 16354 * 16355 * @param {EventTarget~Event} [event] 16356 * The {@link Player#loadstart} event if this function was called 16357 * through an event. 16358 * 16359 * @listens Player#loadstart 16360 * @listens Player#volumechange 16361 */ 16362 ; 16363 16364 _proto.update = function update(event) { 16365 this.updateIcon_(); 16366 this.updateControlText_(); 16367 } 16368 /** 16369 * Update the appearance of the `MuteToggle` icon. 16370 * 16371 * Possible states (given `level` variable below): 16372 * - 0: crossed out 16373 * - 1: zero bars of volume 16374 * - 2: one bar of volume 16375 * - 3: two bars of volume 16376 * 16377 * @private 16378 */ 16379 ; 16380 16381 _proto.updateIcon_ = function updateIcon_() { 16382 var vol = this.player_.volume(); 16383 var level = 3; // in iOS when a player is loaded with muted attribute 16384 // and volume is changed with a native mute button 16385 // we want to make sure muted state is updated 16386 16387 if (IS_IOS && this.player_.tech_ && this.player_.tech_.el_) { 16388 this.player_.muted(this.player_.tech_.el_.muted); 16389 } 16390 16391 if (vol === 0 || this.player_.muted()) { 16392 level = 0; 16393 } else if (vol < 0.33) { 16394 level = 1; 16395 } else if (vol < 0.67) { 16396 level = 2; 16397 } // TODO improve muted icon classes 16398 16399 16400 for (var i = 0; i < 4; i++) { 16401 removeClass(this.el_, "vjs-vol-" + i); 16402 } 16403 16404 addClass(this.el_, "vjs-vol-" + level); 16405 } 16406 /** 16407 * If `muted` has changed on the player, update the control text 16408 * (`title` attribute on `vjs-mute-control` element and content of 16409 * `vjs-control-text` element). 16410 * 16411 * @private 16412 */ 16413 ; 16414 16415 _proto.updateControlText_ = function updateControlText_() { 16416 var soundOff = this.player_.muted() || this.player_.volume() === 0; 16417 var text = soundOff ? 'Unmute' : 'Mute'; 16418 16419 if (this.controlText() !== text) { 16420 this.controlText(text); 16421 } 16422 }; 16423 16424 return MuteToggle; 16425 }(Button); 16426 /** 16427 * The text that should display over the `MuteToggle`s controls. Added for localization. 16428 * 16429 * @type {string} 16430 * @private 16431 */ 16432 16433 16434 MuteToggle.prototype.controlText_ = 'Mute'; 16435 Component.registerComponent('MuteToggle', MuteToggle); 16436 16437 /** 16438 * A Component to contain the MuteToggle and VolumeControl so that 16439 * they can work together. 16440 * 16441 * @extends Component 16442 */ 16443 16444 var VolumePanel = 16445 /*#__PURE__*/ 16446 function (_Component) { 16447 _inheritsLoose(VolumePanel, _Component); 16448 16449 /** 16450 * Creates an instance of this class. 16451 * 16452 * @param {Player} player 16453 * The `Player` that this class should be attached to. 16454 * 16455 * @param {Object} [options={}] 16456 * The key/value store of player options. 16457 */ 16458 function VolumePanel(player, options) { 16459 var _this; 16460 16461 if (options === void 0) { 16462 options = {}; 16463 } 16464 16465 if (typeof options.inline !== 'undefined') { 16466 options.inline = options.inline; 16467 } else { 16468 options.inline = true; 16469 } // pass the inline option down to the VolumeControl as vertical if 16470 // the VolumeControl is on. 16471 16472 16473 if (typeof options.volumeControl === 'undefined' || isPlain(options.volumeControl)) { 16474 options.volumeControl = options.volumeControl || {}; 16475 options.volumeControl.vertical = !options.inline; 16476 } 16477 16478 _this = _Component.call(this, player, options) || this; 16479 16480 _this.on(player, ['loadstart'], _this.volumePanelState_); // while the slider is active (the mouse has been pressed down and 16481 // is dragging) we do not want to hide the VolumeBar 16482 16483 16484 _this.on(_this.volumeControl, ['slideractive'], _this.sliderActive_); 16485 16486 _this.on(_this.volumeControl, ['sliderinactive'], _this.sliderInactive_); 16487 16488 return _this; 16489 } 16490 /** 16491 * Add vjs-slider-active class to the VolumePanel 16492 * 16493 * @listens VolumeControl#slideractive 16494 * @private 16495 */ 16496 16497 16498 var _proto = VolumePanel.prototype; 16499 16500 _proto.sliderActive_ = function sliderActive_() { 16501 this.addClass('vjs-slider-active'); 16502 } 16503 /** 16504 * Removes vjs-slider-active class to the VolumePanel 16505 * 16506 * @listens VolumeControl#sliderinactive 16507 * @private 16508 */ 16509 ; 16510 16511 _proto.sliderInactive_ = function sliderInactive_() { 16512 this.removeClass('vjs-slider-active'); 16513 } 16514 /** 16515 * Adds vjs-hidden or vjs-mute-toggle-only to the VolumePanel 16516 * depending on MuteToggle and VolumeControl state 16517 * 16518 * @listens Player#loadstart 16519 * @private 16520 */ 16521 ; 16522 16523 _proto.volumePanelState_ = function volumePanelState_() { 16524 // hide volume panel if neither volume control or mute toggle 16525 // are displayed 16526 if (this.volumeControl.hasClass('vjs-hidden') && this.muteToggle.hasClass('vjs-hidden')) { 16527 this.addClass('vjs-hidden'); 16528 } // if only mute toggle is visible we don't want 16529 // volume panel expanding when hovered or active 16530 16531 16532 if (this.volumeControl.hasClass('vjs-hidden') && !this.muteToggle.hasClass('vjs-hidden')) { 16533 this.addClass('vjs-mute-toggle-only'); 16534 } 16535 } 16536 /** 16537 * Create the `Component`'s DOM element 16538 * 16539 * @return {Element} 16540 * The element that was created. 16541 */ 16542 ; 16543 16544 _proto.createEl = function createEl() { 16545 var orientationClass = 'vjs-volume-panel-horizontal'; 16546 16547 if (!this.options_.inline) { 16548 orientationClass = 'vjs-volume-panel-vertical'; 16549 } 16550 16551 return _Component.prototype.createEl.call(this, 'div', { 16552 className: "vjs-volume-panel vjs-control " + orientationClass 16553 }); 16554 }; 16555 16556 return VolumePanel; 16557 }(Component); 16558 /** 16559 * Default options for the `VolumeControl` 16560 * 16561 * @type {Object} 16562 * @private 16563 */ 16564 16565 16566 VolumePanel.prototype.options_ = { 16567 children: ['muteToggle', 'volumeControl'] 16568 }; 16569 Component.registerComponent('VolumePanel', VolumePanel); 16570 16571 /** 16572 * The Menu component is used to build popup menus, including subtitle and 16573 * captions selection menus. 16574 * 16575 * @extends Component 16576 */ 16577 16578 var Menu = 16579 /*#__PURE__*/ 16580 function (_Component) { 16581 _inheritsLoose(Menu, _Component); 16582 16583 /** 16584 * Create an instance of this class. 16585 * 16586 * @param {Player} player 16587 * the player that this component should attach to 16588 * 16589 * @param {Object} [options] 16590 * Object of option names and values 16591 * 16592 */ 16593 function Menu(player, options) { 16594 var _this; 16595 16596 _this = _Component.call(this, player, options) || this; 16597 16598 if (options) { 16599 _this.menuButton_ = options.menuButton; 16600 } 16601 16602 _this.focusedChild_ = -1; 16603 16604 _this.on('keydown', _this.handleKeyDown); // All the menu item instances share the same blur handler provided by the menu container. 16605 16606 16607 _this.boundHandleBlur_ = bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.handleBlur); 16608 _this.boundHandleTapClick_ = bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.handleTapClick); 16609 return _this; 16610 } 16611 /** 16612 * Add event listeners to the {@link MenuItem}. 16613 * 16614 * @param {Object} component 16615 * The instance of the `MenuItem` to add listeners to. 16616 * 16617 */ 16618 16619 16620 var _proto = Menu.prototype; 16621 16622 _proto.addEventListenerForItem = function addEventListenerForItem(component) { 16623 if (!(component instanceof Component)) { 16624 return; 16625 } 16626 16627 this.on(component, 'blur', this.boundHandleBlur_); 16628 this.on(component, ['tap', 'click'], this.boundHandleTapClick_); 16629 } 16630 /** 16631 * Remove event listeners from the {@link MenuItem}. 16632 * 16633 * @param {Object} component 16634 * The instance of the `MenuItem` to remove listeners. 16635 * 16636 */ 16637 ; 16638 16639 _proto.removeEventListenerForItem = function removeEventListenerForItem(component) { 16640 if (!(component instanceof Component)) { 16641 return; 16642 } 16643 16644 this.off(component, 'blur', this.boundHandleBlur_); 16645 this.off(component, ['tap', 'click'], this.boundHandleTapClick_); 16646 } 16647 /** 16648 * This method will be called indirectly when the component has been added 16649 * before the component adds to the new menu instance by `addItem`. 16650 * In this case, the original menu instance will remove the component 16651 * by calling `removeChild`. 16652 * 16653 * @param {Object} component 16654 * The instance of the `MenuItem` 16655 */ 16656 ; 16657 16658 _proto.removeChild = function removeChild(component) { 16659 if (typeof component === 'string') { 16660 component = this.getChild(component); 16661 } 16662 16663 this.removeEventListenerForItem(component); 16664 16665 _Component.prototype.removeChild.call(this, component); 16666 } 16667 /** 16668 * Add a {@link MenuItem} to the menu. 16669 * 16670 * @param {Object|string} component 16671 * The name or instance of the `MenuItem` to add. 16672 * 16673 */ 16674 ; 16675 16676 _proto.addItem = function addItem(component) { 16677 var childComponent = this.addChild(component); 16678 16679 if (childComponent) { 16680 this.addEventListenerForItem(childComponent); 16681 } 16682 } 16683 /** 16684 * Create the `Menu`s DOM element. 16685 * 16686 * @return {Element} 16687 * the element that was created 16688 */ 16689 ; 16690 16691 _proto.createEl = function createEl$$1() { 16692 var contentElType = this.options_.contentElType || 'ul'; 16693 this.contentEl_ = createEl(contentElType, { 16694 className: 'vjs-menu-content' 16695 }); 16696 this.contentEl_.setAttribute('role', 'menu'); 16697 16698 var el = _Component.prototype.createEl.call(this, 'div', { 16699 append: this.contentEl_, 16700 className: 'vjs-menu' 16701 }); 16702 16703 el.appendChild(this.contentEl_); // Prevent clicks from bubbling up. Needed for Menu Buttons, 16704 // where a click on the parent is significant 16705 16706 on(el, 'click', function (event) { 16707 event.preventDefault(); 16708 event.stopImmediatePropagation(); 16709 }); 16710 return el; 16711 }; 16712 16713 _proto.dispose = function dispose() { 16714 this.contentEl_ = null; 16715 this.boundHandleBlur_ = null; 16716 this.boundHandleTapClick_ = null; 16717 16718 _Component.prototype.dispose.call(this); 16719 } 16720 /** 16721 * Called when a `MenuItem` loses focus. 16722 * 16723 * @param {EventTarget~Event} event 16724 * The `blur` event that caused this function to be called. 16725 * 16726 * @listens blur 16727 */ 16728 ; 16729 16730 _proto.handleBlur = function handleBlur(event) { 16731 var relatedTarget = event.relatedTarget || document.activeElement; // Close menu popup when a user clicks outside the menu 16732 16733 if (!this.children().some(function (element) { 16734 return element.el() === relatedTarget; 16735 })) { 16736 var btn = this.menuButton_; 16737 16738 if (btn && btn.buttonPressed_ && relatedTarget !== btn.el().firstChild) { 16739 btn.unpressButton(); 16740 } 16741 } 16742 } 16743 /** 16744 * Called when a `MenuItem` gets clicked or tapped. 16745 * 16746 * @param {EventTarget~Event} event 16747 * The `click` or `tap` event that caused this function to be called. 16748 * 16749 * @listens click,tap 16750 */ 16751 ; 16752 16753 _proto.handleTapClick = function handleTapClick(event) { 16754 // Unpress the associated MenuButton, and move focus back to it 16755 if (this.menuButton_) { 16756 this.menuButton_.unpressButton(); 16757 var childComponents = this.children(); 16758 16759 if (!Array.isArray(childComponents)) { 16760 return; 16761 } 16762 16763 var foundComponent = childComponents.filter(function (component) { 16764 return component.el() === event.target; 16765 })[0]; 16766 16767 if (!foundComponent) { 16768 return; 16769 } // don't focus menu button if item is a caption settings item 16770 // because focus will move elsewhere 16771 16772 16773 if (foundComponent.name() !== 'CaptionSettingsMenuItem') { 16774 this.menuButton_.focus(); 16775 } 16776 } 16777 } 16778 /** 16779 * Handle a `keydown` event on this menu. This listener is added in the constructor. 16780 * 16781 * @param {EventTarget~Event} event 16782 * A `keydown` event that happened on the menu. 16783 * 16784 * @listens keydown 16785 */ 16786 ; 16787 16788 _proto.handleKeyDown = function handleKeyDown(event) { 16789 // Left and Down Arrows 16790 if (keycode.isEventKey(event, 'Left') || keycode.isEventKey(event, 'Down')) { 16791 event.preventDefault(); 16792 event.stopPropagation(); 16793 this.stepForward(); // Up and Right Arrows 16794 } else if (keycode.isEventKey(event, 'Right') || keycode.isEventKey(event, 'Up')) { 16795 event.preventDefault(); 16796 event.stopPropagation(); 16797 this.stepBack(); 16798 } 16799 } 16800 /** 16801 * Move to next (lower) menu item for keyboard users. 16802 */ 16803 ; 16804 16805 _proto.stepForward = function stepForward() { 16806 var stepChild = 0; 16807 16808 if (this.focusedChild_ !== undefined) { 16809 stepChild = this.focusedChild_ + 1; 16810 } 16811 16812 this.focus(stepChild); 16813 } 16814 /** 16815 * Move to previous (higher) menu item for keyboard users. 16816 */ 16817 ; 16818 16819 _proto.stepBack = function stepBack() { 16820 var stepChild = 0; 16821 16822 if (this.focusedChild_ !== undefined) { 16823 stepChild = this.focusedChild_ - 1; 16824 } 16825 16826 this.focus(stepChild); 16827 } 16828 /** 16829 * Set focus on a {@link MenuItem} in the `Menu`. 16830 * 16831 * @param {Object|string} [item=0] 16832 * Index of child item set focus on. 16833 */ 16834 ; 16835 16836 _proto.focus = function focus(item) { 16837 if (item === void 0) { 16838 item = 0; 16839 } 16840 16841 var children = this.children().slice(); 16842 var haveTitle = children.length && children[0].className && /vjs-menu-title/.test(children[0].className); 16843 16844 if (haveTitle) { 16845 children.shift(); 16846 } 16847 16848 if (children.length > 0) { 16849 if (item < 0) { 16850 item = 0; 16851 } else if (item >= children.length) { 16852 item = children.length - 1; 16853 } 16854 16855 this.focusedChild_ = item; 16856 children[item].el_.focus(); 16857 } 16858 }; 16859 16860 return Menu; 16861 }(Component); 16862 16863 Component.registerComponent('Menu', Menu); 16864 16865 /** 16866 * A `MenuButton` class for any popup {@link Menu}. 16867 * 16868 * @extends Component 16869 */ 16870 16871 var MenuButton = 16872 /*#__PURE__*/ 16873 function (_Component) { 16874 _inheritsLoose(MenuButton, _Component); 16875 16876 /** 16877 * Creates an instance of this class. 16878 * 16879 * @param {Player} player 16880 * The `Player` that this class should be attached to. 16881 * 16882 * @param {Object} [options={}] 16883 * The key/value store of player options. 16884 */ 16885 function MenuButton(player, options) { 16886 var _this; 16887 16888 if (options === void 0) { 16889 options = {}; 16890 } 16891 16892 _this = _Component.call(this, player, options) || this; 16893 _this.menuButton_ = new Button(player, options); 16894 16895 _this.menuButton_.controlText(_this.controlText_); 16896 16897 _this.menuButton_.el_.setAttribute('aria-haspopup', 'true'); // Add buildCSSClass values to the button, not the wrapper 16898 16899 16900 var buttonClass = Button.prototype.buildCSSClass(); 16901 _this.menuButton_.el_.className = _this.buildCSSClass() + ' ' + buttonClass; 16902 16903 _this.menuButton_.removeClass('vjs-control'); 16904 16905 _this.addChild(_this.menuButton_); 16906 16907 _this.update(); 16908 16909 _this.enabled_ = true; 16910 16911 _this.on(_this.menuButton_, 'tap', _this.handleClick); 16912 16913 _this.on(_this.menuButton_, 'click', _this.handleClick); 16914 16915 _this.on(_this.menuButton_, 'keydown', _this.handleKeyDown); 16916 16917 _this.on(_this.menuButton_, 'mouseenter', function () { 16918 _this.menu.show(); 16919 }); 16920 16921 _this.on('keydown', _this.handleSubmenuKeyDown); 16922 16923 return _this; 16924 } 16925 /** 16926 * Update the menu based on the current state of its items. 16927 */ 16928 16929 16930 var _proto = MenuButton.prototype; 16931 16932 _proto.update = function update() { 16933 var menu = this.createMenu(); 16934 16935 if (this.menu) { 16936 this.menu.dispose(); 16937 this.removeChild(this.menu); 16938 } 16939 16940 this.menu = menu; 16941 this.addChild(menu); 16942 /** 16943 * Track the state of the menu button 16944 * 16945 * @type {Boolean} 16946 * @private 16947 */ 16948 16949 this.buttonPressed_ = false; 16950 this.menuButton_.el_.setAttribute('aria-expanded', 'false'); 16951 16952 if (this.items && this.items.length <= this.hideThreshold_) { 16953 this.hide(); 16954 } else { 16955 this.show(); 16956 } 16957 } 16958 /** 16959 * Create the menu and add all items to it. 16960 * 16961 * @return {Menu} 16962 * The constructed menu 16963 */ 16964 ; 16965 16966 _proto.createMenu = function createMenu() { 16967 var menu = new Menu(this.player_, { 16968 menuButton: this 16969 }); 16970 /** 16971 * Hide the menu if the number of items is less than or equal to this threshold. This defaults 16972 * to 0 and whenever we add items which can be hidden to the menu we'll increment it. We list 16973 * it here because every time we run `createMenu` we need to reset the value. 16974 * 16975 * @protected 16976 * @type {Number} 16977 */ 16978 16979 this.hideThreshold_ = 0; // Add a title list item to the top 16980 16981 if (this.options_.title) { 16982 var titleEl = createEl('li', { 16983 className: 'vjs-menu-title', 16984 innerHTML: toTitleCase(this.options_.title), 16985 tabIndex: -1 16986 }); 16987 this.hideThreshold_ += 1; 16988 var titleComponent = new Component(this.player_, { 16989 el: titleEl 16990 }); 16991 menu.addItem(titleComponent); 16992 } 16993 16994 this.items = this.createItems(); 16995 16996 if (this.items) { 16997 // Add menu items to the menu 16998 for (var i = 0; i < this.items.length; i++) { 16999 menu.addItem(this.items[i]); 17000 } 17001 } 17002 17003 return menu; 17004 } 17005 /** 17006 * Create the list of menu items. Specific to each subclass. 17007 * 17008 * @abstract 17009 */ 17010 ; 17011 17012 _proto.createItems = function createItems() {} 17013 /** 17014 * Create the `MenuButtons`s DOM element. 17015 * 17016 * @return {Element} 17017 * The element that gets created. 17018 */ 17019 ; 17020 17021 _proto.createEl = function createEl$$1() { 17022 return _Component.prototype.createEl.call(this, 'div', { 17023 className: this.buildWrapperCSSClass() 17024 }, {}); 17025 } 17026 /** 17027 * Allow sub components to stack CSS class names for the wrapper element 17028 * 17029 * @return {string} 17030 * The constructed wrapper DOM `className` 17031 */ 17032 ; 17033 17034 _proto.buildWrapperCSSClass = function buildWrapperCSSClass() { 17035 var menuButtonClass = 'vjs-menu-button'; // If the inline option is passed, we want to use different styles altogether. 17036 17037 if (this.options_.inline === true) { 17038 menuButtonClass += '-inline'; 17039 } else { 17040 menuButtonClass += '-popup'; 17041 } // TODO: Fix the CSS so that this isn't necessary 17042 17043 17044 var buttonClass = Button.prototype.buildCSSClass(); 17045 return "vjs-menu-button " + menuButtonClass + " " + buttonClass + " " + _Component.prototype.buildCSSClass.call(this); 17046 } 17047 /** 17048 * Builds the default DOM `className`. 17049 * 17050 * @return {string} 17051 * The DOM `className` for this object. 17052 */ 17053 ; 17054 17055 _proto.buildCSSClass = function buildCSSClass() { 17056 var menuButtonClass = 'vjs-menu-button'; // If the inline option is passed, we want to use different styles altogether. 17057 17058 if (this.options_.inline === true) { 17059 menuButtonClass += '-inline'; 17060 } else { 17061 menuButtonClass += '-popup'; 17062 } 17063 17064 return "vjs-menu-button " + menuButtonClass + " " + _Component.prototype.buildCSSClass.call(this); 17065 } 17066 /** 17067 * Get or set the localized control text that will be used for accessibility. 17068 * 17069 * > NOTE: This will come from the internal `menuButton_` element. 17070 * 17071 * @param {string} [text] 17072 * Control text for element. 17073 * 17074 * @param {Element} [el=this.menuButton_.el()] 17075 * Element to set the title on. 17076 * 17077 * @return {string} 17078 * - The control text when getting 17079 */ 17080 ; 17081 17082 _proto.controlText = function controlText(text, el) { 17083 if (el === void 0) { 17084 el = this.menuButton_.el(); 17085 } 17086 17087 return this.menuButton_.controlText(text, el); 17088 } 17089 /** 17090 * Handle a click on a `MenuButton`. 17091 * See {@link ClickableComponent#handleClick} for instances where this is called. 17092 * 17093 * @param {EventTarget~Event} event 17094 * The `keydown`, `tap`, or `click` event that caused this function to be 17095 * called. 17096 * 17097 * @listens tap 17098 * @listens click 17099 */ 17100 ; 17101 17102 _proto.handleClick = function handleClick(event) { 17103 if (this.buttonPressed_) { 17104 this.unpressButton(); 17105 } else { 17106 this.pressButton(); 17107 } 17108 } 17109 /** 17110 * Set the focus to the actual button, not to this element 17111 */ 17112 ; 17113 17114 _proto.focus = function focus() { 17115 this.menuButton_.focus(); 17116 } 17117 /** 17118 * Remove the focus from the actual button, not this element 17119 */ 17120 ; 17121 17122 _proto.blur = function blur() { 17123 this.menuButton_.blur(); 17124 } 17125 /** 17126 * Handle tab, escape, down arrow, and up arrow keys for `MenuButton`. See 17127 * {@link ClickableComponent#handleKeyDown} for instances where this is called. 17128 * 17129 * @param {EventTarget~Event} event 17130 * The `keydown` event that caused this function to be called. 17131 * 17132 * @listens keydown 17133 */ 17134 ; 17135 17136 _proto.handleKeyDown = function handleKeyDown(event) { 17137 // Escape or Tab unpress the 'button' 17138 if (keycode.isEventKey(event, 'Esc') || keycode.isEventKey(event, 'Tab')) { 17139 if (this.buttonPressed_) { 17140 this.unpressButton(); 17141 } // Don't preventDefault for Tab key - we still want to lose focus 17142 17143 17144 if (!keycode.isEventKey(event, 'Tab')) { 17145 event.preventDefault(); // Set focus back to the menu button's button 17146 17147 this.menuButton_.focus(); 17148 } // Up Arrow or Down Arrow also 'press' the button to open the menu 17149 17150 } else if (keycode.isEventKey(event, 'Up') || keycode.isEventKey(event, 'Down')) { 17151 if (!this.buttonPressed_) { 17152 event.preventDefault(); 17153 this.pressButton(); 17154 } 17155 } 17156 } 17157 /** 17158 * This method name now delegates to `handleSubmenuKeyDown`. This means 17159 * anyone calling `handleSubmenuKeyPress` will not see their method calls 17160 * stop working. 17161 * 17162 * @param {EventTarget~Event} event 17163 * The event that caused this function to be called. 17164 */ 17165 ; 17166 17167 _proto.handleSubmenuKeyPress = function handleSubmenuKeyPress(event) { 17168 this.handleSubmenuKeyDown(event); 17169 } 17170 /** 17171 * Handle a `keydown` event on a sub-menu. The listener for this is added in 17172 * the constructor. 17173 * 17174 * @param {EventTarget~Event} event 17175 * Key press event 17176 * 17177 * @listens keydown 17178 */ 17179 ; 17180 17181 _proto.handleSubmenuKeyDown = function handleSubmenuKeyDown(event) { 17182 // Escape or Tab unpress the 'button' 17183 if (keycode.isEventKey(event, 'Esc') || keycode.isEventKey(event, 'Tab')) { 17184 if (this.buttonPressed_) { 17185 this.unpressButton(); 17186 } // Don't preventDefault for Tab key - we still want to lose focus 17187 17188 17189 if (!keycode.isEventKey(event, 'Tab')) { 17190 event.preventDefault(); // Set focus back to the menu button's button 17191 17192 this.menuButton_.focus(); 17193 } 17194 } 17195 } 17196 /** 17197 * Put the current `MenuButton` into a pressed state. 17198 */ 17199 ; 17200 17201 _proto.pressButton = function pressButton() { 17202 if (this.enabled_) { 17203 this.buttonPressed_ = true; 17204 this.menu.show(); 17205 this.menu.lockShowing(); 17206 this.menuButton_.el_.setAttribute('aria-expanded', 'true'); // set the focus into the submenu, except on iOS where it is resulting in 17207 // undesired scrolling behavior when the player is in an iframe 17208 17209 if (IS_IOS && isInFrame()) { 17210 // Return early so that the menu isn't focused 17211 return; 17212 } 17213 17214 this.menu.focus(); 17215 } 17216 } 17217 /** 17218 * Take the current `MenuButton` out of a pressed state. 17219 */ 17220 ; 17221 17222 _proto.unpressButton = function unpressButton() { 17223 if (this.enabled_) { 17224 this.buttonPressed_ = false; 17225 this.menu.unlockShowing(); 17226 this.menu.hide(); 17227 this.menuButton_.el_.setAttribute('aria-expanded', 'false'); 17228 } 17229 } 17230 /** 17231 * Disable the `MenuButton`. Don't allow it to be clicked. 17232 */ 17233 ; 17234 17235 _proto.disable = function disable() { 17236 this.unpressButton(); 17237 this.enabled_ = false; 17238 this.addClass('vjs-disabled'); 17239 this.menuButton_.disable(); 17240 } 17241 /** 17242 * Enable the `MenuButton`. Allow it to be clicked. 17243 */ 17244 ; 17245 17246 _proto.enable = function enable() { 17247 this.enabled_ = true; 17248 this.removeClass('vjs-disabled'); 17249 this.menuButton_.enable(); 17250 }; 17251 17252 return MenuButton; 17253 }(Component); 17254 17255 Component.registerComponent('MenuButton', MenuButton); 17256 17257 /** 17258 * The base class for buttons that toggle specific track types (e.g. subtitles). 17259 * 17260 * @extends MenuButton 17261 */ 17262 17263 var TrackButton = 17264 /*#__PURE__*/ 17265 function (_MenuButton) { 17266 _inheritsLoose(TrackButton, _MenuButton); 17267 17268 /** 17269 * Creates an instance of this class. 17270 * 17271 * @param {Player} player 17272 * The `Player` that this class should be attached to. 17273 * 17274 * @param {Object} [options] 17275 * The key/value store of player options. 17276 */ 17277 function TrackButton(player, options) { 17278 var _this; 17279 17280 var tracks = options.tracks; 17281 _this = _MenuButton.call(this, player, options) || this; 17282 17283 if (_this.items.length <= 1) { 17284 _this.hide(); 17285 } 17286 17287 if (!tracks) { 17288 return _assertThisInitialized(_this); 17289 } 17290 17291 var updateHandler = bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.update); 17292 tracks.addEventListener('removetrack', updateHandler); 17293 tracks.addEventListener('addtrack', updateHandler); 17294 17295 _this.player_.on('ready', updateHandler); 17296 17297 _this.player_.on('dispose', function () { 17298 tracks.removeEventListener('removetrack', updateHandler); 17299 tracks.removeEventListener('addtrack', updateHandler); 17300 }); 17301 17302 return _this; 17303 } 17304 17305 return TrackButton; 17306 }(MenuButton); 17307 17308 Component.registerComponent('TrackButton', TrackButton); 17309 17310 /** 17311 * @file menu-keys.js 17312 */ 17313 17314 /** 17315 * All keys used for operation of a menu (`MenuButton`, `Menu`, and `MenuItem`) 17316 * Note that 'Enter' and 'Space' are not included here (otherwise they would 17317 * prevent the `MenuButton` and `MenuItem` from being keyboard-clickable) 17318 * @typedef MenuKeys 17319 * @array 17320 */ 17321 var MenuKeys = ['Tab', 'Esc', 'Up', 'Down', 'Right', 'Left']; 17322 17323 /** 17324 * The component for a menu item. `<li>` 17325 * 17326 * @extends ClickableComponent 17327 */ 17328 17329 var MenuItem = 17330 /*#__PURE__*/ 17331 function (_ClickableComponent) { 17332 _inheritsLoose(MenuItem, _ClickableComponent); 17333 17334 /** 17335 * Creates an instance of the this class. 17336 * 17337 * @param {Player} player 17338 * The `Player` that this class should be attached to. 17339 * 17340 * @param {Object} [options={}] 17341 * The key/value store of player options. 17342 * 17343 */ 17344 function MenuItem(player, options) { 17345 var _this; 17346 17347 _this = _ClickableComponent.call(this, player, options) || this; 17348 _this.selectable = options.selectable; 17349 _this.isSelected_ = options.selected || false; 17350 _this.multiSelectable = options.multiSelectable; 17351 17352 _this.selected(_this.isSelected_); 17353 17354 if (_this.selectable) { 17355 if (_this.multiSelectable) { 17356 _this.el_.setAttribute('role', 'menuitemcheckbox'); 17357 } else { 17358 _this.el_.setAttribute('role', 'menuitemradio'); 17359 } 17360 } else { 17361 _this.el_.setAttribute('role', 'menuitem'); 17362 } 17363 17364 return _this; 17365 } 17366 /** 17367 * Create the `MenuItem's DOM element 17368 * 17369 * @param {string} [type=li] 17370 * Element's node type, not actually used, always set to `li`. 17371 * 17372 * @param {Object} [props={}] 17373 * An object of properties that should be set on the element 17374 * 17375 * @param {Object} [attrs={}] 17376 * An object of attributes that should be set on the element 17377 * 17378 * @return {Element} 17379 * The element that gets created. 17380 */ 17381 17382 17383 var _proto = MenuItem.prototype; 17384 17385 _proto.createEl = function createEl(type, props, attrs) { 17386 // The control is textual, not just an icon 17387 this.nonIconControl = true; 17388 return _ClickableComponent.prototype.createEl.call(this, 'li', assign({ 17389 className: 'vjs-menu-item', 17390 innerHTML: "<span class=\"vjs-menu-item-text\">" + this.localize(this.options_.label) + "</span>", 17391 tabIndex: -1 17392 }, props), attrs); 17393 } 17394 /** 17395 * Ignore keys which are used by the menu, but pass any other ones up. See 17396 * {@link ClickableComponent#handleKeyDown} for instances where this is called. 17397 * 17398 * @param {EventTarget~Event} event 17399 * The `keydown` event that caused this function to be called. 17400 * 17401 * @listens keydown 17402 */ 17403 ; 17404 17405 _proto.handleKeyDown = function handleKeyDown(event) { 17406 if (!MenuKeys.some(function (key) { 17407 return keycode.isEventKey(event, key); 17408 })) { 17409 // Pass keydown handling up for unused keys 17410 _ClickableComponent.prototype.handleKeyDown.call(this, event); 17411 } 17412 } 17413 /** 17414 * Any click on a `MenuItem` puts it into the selected state. 17415 * See {@link ClickableComponent#handleClick} for instances where this is called. 17416 * 17417 * @param {EventTarget~Event} event 17418 * The `keydown`, `tap`, or `click` event that caused this function to be 17419 * called. 17420 * 17421 * @listens tap 17422 * @listens click 17423 */ 17424 ; 17425 17426 _proto.handleClick = function handleClick(event) { 17427 this.selected(true); 17428 } 17429 /** 17430 * Set the state for this menu item as selected or not. 17431 * 17432 * @param {boolean} selected 17433 * if the menu item is selected or not 17434 */ 17435 ; 17436 17437 _proto.selected = function selected(_selected) { 17438 if (this.selectable) { 17439 if (_selected) { 17440 this.addClass('vjs-selected'); 17441 this.el_.setAttribute('aria-checked', 'true'); // aria-checked isn't fully supported by browsers/screen readers, 17442 // so indicate selected state to screen reader in the control text. 17443 17444 this.controlText(', selected'); 17445 this.isSelected_ = true; 17446 } else { 17447 this.removeClass('vjs-selected'); 17448 this.el_.setAttribute('aria-checked', 'false'); // Indicate un-selected state to screen reader 17449 17450 this.controlText(''); 17451 this.isSelected_ = false; 17452 } 17453 } 17454 }; 17455 17456 return MenuItem; 17457 }(ClickableComponent); 17458 17459 Component.registerComponent('MenuItem', MenuItem); 17460 17461 /** 17462 * The specific menu item type for selecting a language within a text track kind 17463 * 17464 * @extends MenuItem 17465 */ 17466 17467 var TextTrackMenuItem = 17468 /*#__PURE__*/ 17469 function (_MenuItem) { 17470 _inheritsLoose(TextTrackMenuItem, _MenuItem); 17471 17472 /** 17473 * Creates an instance of this class. 17474 * 17475 * @param {Player} player 17476 * The `Player` that this class should be attached to. 17477 * 17478 * @param {Object} [options] 17479 * The key/value store of player options. 17480 */ 17481 function TextTrackMenuItem(player, options) { 17482 var _this; 17483 17484 var track = options.track; 17485 var tracks = player.textTracks(); // Modify options for parent MenuItem class's init. 17486 17487 options.label = track.label || track.language || 'Unknown'; 17488 options.selected = track.mode === 'showing'; 17489 _this = _MenuItem.call(this, player, options) || this; 17490 _this.track = track; // Determine the relevant kind(s) of tracks for this component and filter 17491 // out empty kinds. 17492 17493 _this.kinds = (options.kinds || [options.kind || _this.track.kind]).filter(Boolean); 17494 17495 var changeHandler = function changeHandler() { 17496 for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { 17497 args[_key] = arguments[_key]; 17498 } 17499 17500 _this.handleTracksChange.apply(_assertThisInitialized(_assertThisInitialized(_this)), args); 17501 }; 17502 17503 var selectedLanguageChangeHandler = function selectedLanguageChangeHandler() { 17504 for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { 17505 args[_key2] = arguments[_key2]; 17506 } 17507 17508 _this.handleSelectedLanguageChange.apply(_assertThisInitialized(_assertThisInitialized(_this)), args); 17509 }; 17510 17511 player.on(['loadstart', 'texttrackchange'], changeHandler); 17512 tracks.addEventListener('change', changeHandler); 17513 tracks.addEventListener('selectedlanguagechange', selectedLanguageChangeHandler); 17514 17515 _this.on('dispose', function () { 17516 player.off(['loadstart', 'texttrackchange'], changeHandler); 17517 tracks.removeEventListener('change', changeHandler); 17518 tracks.removeEventListener('selectedlanguagechange', selectedLanguageChangeHandler); 17519 }); // iOS7 doesn't dispatch change events to TextTrackLists when an 17520 // associated track's mode changes. Without something like 17521 // Object.observe() (also not present on iOS7), it's not 17522 // possible to detect changes to the mode attribute and polyfill 17523 // the change event. As a poor substitute, we manually dispatch 17524 // change events whenever the controls modify the mode. 17525 17526 17527 if (tracks.onchange === undefined) { 17528 var event; 17529 17530 _this.on(['tap', 'click'], function () { 17531 if (typeof window$1.Event !== 'object') { 17532 // Android 2.3 throws an Illegal Constructor error for window.Event 17533 try { 17534 event = new window$1.Event('change'); 17535 } catch (err) {// continue regardless of error 17536 } 17537 } 17538 17539 if (!event) { 17540 event = document.createEvent('Event'); 17541 event.initEvent('change', true, true); 17542 } 17543 17544 tracks.dispatchEvent(event); 17545 }); 17546 } // set the default state based on current tracks 17547 17548 17549 _this.handleTracksChange(); 17550 17551 return _this; 17552 } 17553 /** 17554 * This gets called when an `TextTrackMenuItem` is "clicked". See 17555 * {@link ClickableComponent} for more detailed information on what a click can be. 17556 * 17557 * @param {EventTarget~Event} event 17558 * The `keydown`, `tap`, or `click` event that caused this function to be 17559 * called. 17560 * 17561 * @listens tap 17562 * @listens click 17563 */ 17564 17565 17566 var _proto = TextTrackMenuItem.prototype; 17567 17568 _proto.handleClick = function handleClick(event) { 17569 var referenceTrack = this.track; 17570 var tracks = this.player_.textTracks(); 17571 17572 _MenuItem.prototype.handleClick.call(this, event); 17573 17574 if (!tracks) { 17575 return; 17576 } 17577 17578 for (var i = 0; i < tracks.length; i++) { 17579 var track = tracks[i]; // If the track from the text tracks list is not of the right kind, 17580 // skip it. We do not want to affect tracks of incompatible kind(s). 17581 17582 if (this.kinds.indexOf(track.kind) === -1) { 17583 continue; 17584 } // If this text track is the component's track and it is not showing, 17585 // set it to showing. 17586 17587 17588 if (track === referenceTrack) { 17589 if (track.mode !== 'showing') { 17590 track.mode = 'showing'; 17591 } // If this text track is not the component's track and it is not 17592 // disabled, set it to disabled. 17593 17594 } else if (track.mode !== 'disabled') { 17595 track.mode = 'disabled'; 17596 } 17597 } 17598 } 17599 /** 17600 * Handle text track list change 17601 * 17602 * @param {EventTarget~Event} event 17603 * The `change` event that caused this function to be called. 17604 * 17605 * @listens TextTrackList#change 17606 */ 17607 ; 17608 17609 _proto.handleTracksChange = function handleTracksChange(event) { 17610 var shouldBeSelected = this.track.mode === 'showing'; // Prevent redundant selected() calls because they may cause 17611 // screen readers to read the appended control text unnecessarily 17612 17613 if (shouldBeSelected !== this.isSelected_) { 17614 this.selected(shouldBeSelected); 17615 } 17616 }; 17617 17618 _proto.handleSelectedLanguageChange = function handleSelectedLanguageChange(event) { 17619 if (this.track.mode === 'showing') { 17620 var selectedLanguage = this.player_.cache_.selectedLanguage; // Don't replace the kind of track across the same language 17621 17622 if (selectedLanguage && selectedLanguage.enabled && selectedLanguage.language === this.track.language && selectedLanguage.kind !== this.track.kind) { 17623 return; 17624 } 17625 17626 this.player_.cache_.selectedLanguage = { 17627 enabled: true, 17628 language: this.track.language, 17629 kind: this.track.kind 17630 }; 17631 } 17632 }; 17633 17634 _proto.dispose = function dispose() { 17635 // remove reference to track object on dispose 17636 this.track = null; 17637 17638 _MenuItem.prototype.dispose.call(this); 17639 }; 17640 17641 return TextTrackMenuItem; 17642 }(MenuItem); 17643 17644 Component.registerComponent('TextTrackMenuItem', TextTrackMenuItem); 17645 17646 /** 17647 * A special menu item for turning of a specific type of text track 17648 * 17649 * @extends TextTrackMenuItem 17650 */ 17651 17652 var OffTextTrackMenuItem = 17653 /*#__PURE__*/ 17654 function (_TextTrackMenuItem) { 17655 _inheritsLoose(OffTextTrackMenuItem, _TextTrackMenuItem); 17656 17657 /** 17658 * Creates an instance of this class. 17659 * 17660 * @param {Player} player 17661 * The `Player` that this class should be attached to. 17662 * 17663 * @param {Object} [options] 17664 * The key/value store of player options. 17665 */ 17666 function OffTextTrackMenuItem(player, options) { 17667 // Create pseudo track info 17668 // Requires options['kind'] 17669 options.track = { 17670 player: player, 17671 // it is no longer necessary to store `kind` or `kinds` on the track itself 17672 // since they are now stored in the `kinds` property of all instances of 17673 // TextTrackMenuItem, but this will remain for backwards compatibility 17674 kind: options.kind, 17675 kinds: options.kinds, 17676 default: false, 17677 mode: 'disabled' 17678 }; 17679 17680 if (!options.kinds) { 17681 options.kinds = [options.kind]; 17682 } 17683 17684 if (options.label) { 17685 options.track.label = options.label; 17686 } else { 17687 options.track.label = options.kinds.join(' and ') + ' off'; 17688 } // MenuItem is selectable 17689 17690 17691 options.selectable = true; // MenuItem is NOT multiSelectable (i.e. only one can be marked "selected" at a time) 17692 17693 options.multiSelectable = false; 17694 return _TextTrackMenuItem.call(this, player, options) || this; 17695 } 17696 /** 17697 * Handle text track change 17698 * 17699 * @param {EventTarget~Event} event 17700 * The event that caused this function to run 17701 */ 17702 17703 17704 var _proto = OffTextTrackMenuItem.prototype; 17705 17706 _proto.handleTracksChange = function handleTracksChange(event) { 17707 var tracks = this.player().textTracks(); 17708 var shouldBeSelected = true; 17709 17710 for (var i = 0, l = tracks.length; i < l; i++) { 17711 var track = tracks[i]; 17712 17713 if (this.options_.kinds.indexOf(track.kind) > -1 && track.mode === 'showing') { 17714 shouldBeSelected = false; 17715 break; 17716 } 17717 } // Prevent redundant selected() calls because they may cause 17718 // screen readers to read the appended control text unnecessarily 17719 17720 17721 if (shouldBeSelected !== this.isSelected_) { 17722 this.selected(shouldBeSelected); 17723 } 17724 }; 17725 17726 _proto.handleSelectedLanguageChange = function handleSelectedLanguageChange(event) { 17727 var tracks = this.player().textTracks(); 17728 var allHidden = true; 17729 17730 for (var i = 0, l = tracks.length; i < l; i++) { 17731 var track = tracks[i]; 17732 17733 if (['captions', 'descriptions', 'subtitles'].indexOf(track.kind) > -1 && track.mode === 'showing') { 17734 allHidden = false; 17735 break; 17736 } 17737 } 17738 17739 if (allHidden) { 17740 this.player_.cache_.selectedLanguage = { 17741 enabled: false 17742 }; 17743 } 17744 }; 17745 17746 return OffTextTrackMenuItem; 17747 }(TextTrackMenuItem); 17748 17749 Component.registerComponent('OffTextTrackMenuItem', OffTextTrackMenuItem); 17750 17751 /** 17752 * The base class for buttons that toggle specific text track types (e.g. subtitles) 17753 * 17754 * @extends MenuButton 17755 */ 17756 17757 var TextTrackButton = 17758 /*#__PURE__*/ 17759 function (_TrackButton) { 17760 _inheritsLoose(TextTrackButton, _TrackButton); 17761 17762 /** 17763 * Creates an instance of this class. 17764 * 17765 * @param {Player} player 17766 * The `Player` that this class should be attached to. 17767 * 17768 * @param {Object} [options={}] 17769 * The key/value store of player options. 17770 */ 17771 function TextTrackButton(player, options) { 17772 if (options === void 0) { 17773 options = {}; 17774 } 17775 17776 options.tracks = player.textTracks(); 17777 return _TrackButton.call(this, player, options) || this; 17778 } 17779 /** 17780 * Create a menu item for each text track 17781 * 17782 * @param {TextTrackMenuItem[]} [items=[]] 17783 * Existing array of items to use during creation 17784 * 17785 * @return {TextTrackMenuItem[]} 17786 * Array of menu items that were created 17787 */ 17788 17789 17790 var _proto = TextTrackButton.prototype; 17791 17792 _proto.createItems = function createItems(items, TrackMenuItem) { 17793 if (items === void 0) { 17794 items = []; 17795 } 17796 17797 if (TrackMenuItem === void 0) { 17798 TrackMenuItem = TextTrackMenuItem; 17799 } 17800 17801 // Label is an override for the [track] off label 17802 // USed to localise captions/subtitles 17803 var label; 17804 17805 if (this.label_) { 17806 label = this.label_ + " off"; 17807 } // Add an OFF menu item to turn all tracks off 17808 17809 17810 items.push(new OffTextTrackMenuItem(this.player_, { 17811 kinds: this.kinds_, 17812 kind: this.kind_, 17813 label: label 17814 })); 17815 this.hideThreshold_ += 1; 17816 var tracks = this.player_.textTracks(); 17817 17818 if (!Array.isArray(this.kinds_)) { 17819 this.kinds_ = [this.kind_]; 17820 } 17821 17822 for (var i = 0; i < tracks.length; i++) { 17823 var track = tracks[i]; // only add tracks that are of an appropriate kind and have a label 17824 17825 if (this.kinds_.indexOf(track.kind) > -1) { 17826 var item = new TrackMenuItem(this.player_, { 17827 track: track, 17828 kinds: this.kinds_, 17829 kind: this.kind_, 17830 // MenuItem is selectable 17831 selectable: true, 17832 // MenuItem is NOT multiSelectable (i.e. only one can be marked "selected" at a time) 17833 multiSelectable: false 17834 }); 17835 item.addClass("vjs-" + track.kind + "-menu-item"); 17836 items.push(item); 17837 } 17838 } 17839 17840 return items; 17841 }; 17842 17843 return TextTrackButton; 17844 }(TrackButton); 17845 17846 Component.registerComponent('TextTrackButton', TextTrackButton); 17847 17848 /** 17849 * The chapter track menu item 17850 * 17851 * @extends MenuItem 17852 */ 17853 17854 var ChaptersTrackMenuItem = 17855 /*#__PURE__*/ 17856 function (_MenuItem) {
17857 _inheritsLoose(ChaptersTrackMenuItem, _MenuItem); 17858 17859 /** 17860 * Creates an instance of this class. 17861 * 17862 * @param {Player} player 17863 * The `Player` that this class should be attached to. 17864 * 17865 * @param {Object} [options] 17866 * The key/value store of player options. 17867 */ 17868 function ChaptersTrackMenuItem(player, options) { 17869 var _this; 17870 17871 var track = options.track; 17872 var cue = options.cue; 17873 var currentTime = player.currentTime(); // Modify options for parent MenuItem class's init. 17874 17875 options.selectable = true; 17876 options.multiSelectable = false; 17877 options.label = cue.text; 17878 options.selected = cue.startTime <= currentTime && currentTime < cue.endTime; 17879 _this = _MenuItem.call(this, player, options) || this; 17880 _this.track = track; 17881 _this.cue = cue; 17882 track.addEventListener('cuechange', bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.update)); 17883 return _this; 17884 } 17885 /** 17886 * This gets called when an `ChaptersTrackMenuItem` is "clicked". See 17887 * {@link ClickableComponent} for more detailed information on what a click can be. 17888 * 17889 * @param {EventTarget~Event} [event] 17890 * The `keydown`, `tap`, or `click` event that caused this function to be 17891 * called. 17892 * 17893 * @listens tap 17894 * @listens click 17895 */ 17896 17897 17898 var _proto = ChaptersTrackMenuItem.prototype; 17899 17900 _proto.handleClick = function handleClick(event) { 17901 _MenuItem.prototype.handleClick.call(this); 17902 17903 this.player_.currentTime(this.cue.startTime); 17904 this.update(this.cue.startTime); 17905 } 17906 /** 17907 * Update chapter menu item 17908 * 17909 * @param {EventTarget~Event} [event] 17910 * The `cuechange` event that caused this function to run. 17911 * 17912 * @listens TextTrack#cuechange 17913 */ 17914 ; 17915 17916 _proto.update = function update(event) { 17917 var cue = this.cue; 17918 var currentTime = this.player_.currentTime(); // vjs.log(currentTime, cue.startTime); 17919 17920 this.selected(cue.startTime <= currentTime && currentTime < cue.endTime); 17921 }; 17922 17923 return ChaptersTrackMenuItem; 17924 }(MenuItem); 17925 17926 Component.registerComponent('ChaptersTrackMenuItem', ChaptersTrackMenuItem); 17927 17928 /** 17929 * The button component for toggling and selecting chapters 17930 * Chapters act much differently than other text tracks 17931 * Cues are navigation vs. other tracks of alternative languages 17932 * 17933 * @extends TextTrackButton 17934 */ 17935 17936 var ChaptersButton = 17937 /*#__PURE__*/ 17938 function (_TextTrackButton) { 17939 _inheritsLoose(ChaptersButton, _TextTrackButton); 17940 17941 /** 17942 * Creates an instance of this class. 17943 * 17944 * @param {Player} player 17945 * The `Player` that this class should be attached to. 17946 * 17947 * @param {Object} [options] 17948 * The key/value store of player options. 17949 * 17950 * @param {Component~ReadyCallback} [ready] 17951 * The function to call when this function is ready. 17952 */ 17953 function ChaptersButton(player, options, ready) { 17954 return _TextTrackButton.call(this, player, options, ready) || this; 17955 } 17956 /** 17957 * Builds the default DOM `className`. 17958 * 17959 * @return {string} 17960 * The DOM `className` for this object. 17961 */ 17962 17963 17964 var _proto = ChaptersButton.prototype; 17965 17966 _proto.buildCSSClass = function buildCSSClass() { 17967 return "vjs-chapters-button " + _TextTrackButton.prototype.buildCSSClass.call(this); 17968 }; 17969 17970 _proto.buildWrapperCSSClass = function buildWrapperCSSClass() { 17971 return "vjs-chapters-button " + _TextTrackButton.prototype.buildWrapperCSSClass.call(this); 17972 } 17973 /** 17974 * Update the menu based on the current state of its items. 17975 * 17976 * @param {EventTarget~Event} [event] 17977 * An event that triggered this function to run. 17978 * 17979 * @listens TextTrackList#addtrack 17980 * @listens TextTrackList#removetrack 17981 * @listens TextTrackList#change 17982 */ 17983 ; 17984 17985 _proto.update = function update(event) { 17986 if (!this.track_ || event && (event.type === 'addtrack' || event.type === 'removetrack')) {
vendor: 1,499 bytes, lines 17987-18040
17987 this.setTrack(this.findChaptersTrack()); 17988 } 17989 17990 _TextTrackButton.prototype.update.call(this); 17991 } 17992 /** 17993 * Set the currently selected track for the chapters button. 17994 * 17995 * @param {TextTrack} track 17996 * The new track to select. Nothing will change if this is the currently selected 17997 * track. 17998 */ 17999 ; 18000 18001 _proto.setTrack = function setTrack(track) { 18002 if (this.track_ === track) { 18003 return; 18004 } 18005 18006 if (!this.updateHandler_) { 18007 this.updateHandler_ = this.update.bind(this); 18008 } // here this.track_ refers to the old track instance 18009 18010 18011 if (this.track_) { 18012 var remoteTextTrackEl = this.player_.remoteTextTrackEls().getTrackElementByTrack_(this.track_); 18013 18014 if (remoteTextTrackEl) { 18015 remoteTextTrackEl.removeEventListener('load', this.updateHandler_); 18016 } 18017 18018 this.track_ = null; 18019 } 18020 18021 this.track_ = track; // here this.track_ refers to the new track instance 18022 18023 if (this.track_) { 18024 this.track_.mode = 'hidden'; 18025 18026 var _remoteTextTrackEl = this.player_.remoteTextTrackEls().getTrackElementByTrack_(this.track_); 18027 18028 if (_remoteTextTrackEl) { 18029 _remoteTextTrackEl.addEventListener('load', this.updateHandler_); 18030 } 18031 } 18032 } 18033 /** 18034 * Find the track object that is currently in use by this ChaptersButton 18035 * 18036 * @return {TextTrack|undefined} 18037 * The current track or undefined if none was found. 18038 */ 18039 ; 18040
vendor: 15,626 bytes, lines 18041-18618
18041 _proto.findChaptersTrack = function findChaptersTrack() { 18042 var tracks = this.player_.textTracks() || []; 18043 18044 for (var i = tracks.length - 1; i >= 0; i--) { 18045 // We will always choose the last track as our chaptersTrack 18046 var track = tracks[i]; 18047 18048 if (track.kind === this.kind_) { 18049 return track; 18050 } 18051 } 18052 } 18053 /** 18054 * Get the caption for the ChaptersButton based on the track label. This will also 18055 * use the current tracks localized kind as a fallback if a label does not exist. 18056 * 18057 * @return {string} 18058 * The tracks current label or the localized track kind. 18059 */ 18060 ; 18061 18062 _proto.getMenuCaption = function getMenuCaption() { 18063 if (this.track_ && this.track_.label) { 18064 return this.track_.label; 18065 } 18066 18067 return this.localize(toTitleCase(this.kind_)); 18068 } 18069 /** 18070 * Create menu from chapter track 18071 * 18072 * @return {Menu} 18073 * New menu for the chapter buttons 18074 */ 18075 ; 18076 18077 _proto.createMenu = function createMenu() { 18078 this.options_.title = this.getMenuCaption(); 18079 return _TextTrackButton.prototype.createMenu.call(this); 18080 } 18081 /** 18082 * Create a menu item for each text track 18083 * 18084 * @return {TextTrackMenuItem[]} 18085 * Array of menu items 18086 */ 18087 ; 18088 18089 _proto.createItems = function createItems() { 18090 var items = []; 18091 18092 if (!this.track_) { 18093 return items; 18094 } 18095 18096 var cues = this.track_.cues; 18097 18098 if (!cues) { 18099 return items; 18100 } 18101 18102 for (var i = 0, l = cues.length; i < l; i++) { 18103 var cue = cues[i]; 18104 var mi = new ChaptersTrackMenuItem(this.player_, { 18105 track: this.track_, 18106 cue: cue 18107 }); 18108 items.push(mi); 18109 } 18110 18111 return items; 18112 }; 18113 18114 return ChaptersButton; 18115 }(TextTrackButton); 18116 /** 18117 * `kind` of TextTrack to look for to associate it with this menu. 18118 * 18119 * @type {string} 18120 * @private 18121 */ 18122 18123 18124 ChaptersButton.prototype.kind_ = 'chapters'; 18125 /** 18126 * The text that should display over the `ChaptersButton`s controls. Added for localization. 18127 * 18128 * @type {string} 18129 * @private 18130 */ 18131 18132 ChaptersButton.prototype.controlText_ = 'Chapters'; 18133 Component.registerComponent('ChaptersButton', ChaptersButton); 18134 18135 /** 18136 * The button component for toggling and selecting descriptions 18137 * 18138 * @extends TextTrackButton 18139 */ 18140 18141 var DescriptionsButton = 18142 /*#__PURE__*/ 18143 function (_TextTrackButton) { 18144 _inheritsLoose(DescriptionsButton, _TextTrackButton); 18145 18146 /** 18147 * Creates an instance of this class. 18148 * 18149 * @param {Player} player 18150 * The `Player` that this class should be attached to. 18151 * 18152 * @param {Object} [options] 18153 * The key/value store of player options. 18154 * 18155 * @param {Component~ReadyCallback} [ready] 18156 * The function to call when this component is ready. 18157 */ 18158 function DescriptionsButton(player, options, ready) { 18159 var _this; 18160 18161 _this = _TextTrackButton.call(this, player, options, ready) || this; 18162 var tracks = player.textTracks(); 18163 var changeHandler = bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.handleTracksChange); 18164 tracks.addEventListener('change', changeHandler); 18165 18166 _this.on('dispose', function () { 18167 tracks.removeEventListener('change', changeHandler); 18168 }); 18169 18170 return _this; 18171 } 18172 /** 18173 * Handle text track change 18174 * 18175 * @param {EventTarget~Event} event 18176 * The event that caused this function to run 18177 * 18178 * @listens TextTrackList#change 18179 */ 18180 18181 18182 var _proto = DescriptionsButton.prototype; 18183 18184 _proto.handleTracksChange = function handleTracksChange(event) { 18185 var tracks = this.player().textTracks(); 18186 var disabled = false; // Check whether a track of a different kind is showing 18187 18188 for (var i = 0, l = tracks.length; i < l; i++) { 18189 var track = tracks[i]; 18190 18191 if (track.kind !== this.kind_ && track.mode === 'showing') { 18192 disabled = true; 18193 break; 18194 } 18195 } // If another track is showing, disable this menu button 18196 18197 18198 if (disabled) { 18199 this.disable(); 18200 } else { 18201 this.enable(); 18202 } 18203 } 18204 /** 18205 * Builds the default DOM `className`. 18206 * 18207 * @return {string} 18208 * The DOM `className` for this object. 18209 */ 18210 ; 18211 18212 _proto.buildCSSClass = function buildCSSClass() { 18213 return "vjs-descriptions-button " + _TextTrackButton.prototype.buildCSSClass.call(this); 18214 }; 18215 18216 _proto.buildWrapperCSSClass = function buildWrapperCSSClass() { 18217 return "vjs-descriptions-button " + _TextTrackButton.prototype.buildWrapperCSSClass.call(this); 18218 }; 18219 18220 return DescriptionsButton; 18221 }(TextTrackButton); 18222 /** 18223 * `kind` of TextTrack to look for to associate it with this menu. 18224 * 18225 * @type {string} 18226 * @private 18227 */ 18228 18229 18230 DescriptionsButton.prototype.kind_ = 'descriptions'; 18231 /** 18232 * The text that should display over the `DescriptionsButton`s controls. Added for localization. 18233 * 18234 * @type {string} 18235 * @private 18236 */ 18237 18238 DescriptionsButton.prototype.controlText_ = 'Descriptions'; 18239 Component.registerComponent('DescriptionsButton', DescriptionsButton); 18240 18241 /** 18242 * The button component for toggling and selecting subtitles 18243 * 18244 * @extends TextTrackButton 18245 */ 18246 18247 var SubtitlesButton = 18248 /*#__PURE__*/ 18249 function (_TextTrackButton) { 18250 _inheritsLoose(SubtitlesButton, _TextTrackButton); 18251 18252 /** 18253 * Creates an instance of this class. 18254 * 18255 * @param {Player} player 18256 * The `Player` that this class should be attached to. 18257 * 18258 * @param {Object} [options] 18259 * The key/value store of player options. 18260 * 18261 * @param {Component~ReadyCallback} [ready] 18262 * The function to call when this component is ready. 18263 */ 18264 function SubtitlesButton(player, options, ready) { 18265 return _TextTrackButton.call(this, player, options, ready) || this; 18266 } 18267 /** 18268 * Builds the default DOM `className`. 18269 * 18270 * @return {string} 18271 * The DOM `className` for this object. 18272 */ 18273 18274 18275 var _proto = SubtitlesButton.prototype; 18276 18277 _proto.buildCSSClass = function buildCSSClass() { 18278 return "vjs-subtitles-button " + _TextTrackButton.prototype.buildCSSClass.call(this); 18279 }; 18280 18281 _proto.buildWrapperCSSClass = function buildWrapperCSSClass() { 18282 return "vjs-subtitles-button " + _TextTrackButton.prototype.buildWrapperCSSClass.call(this); 18283 }; 18284 18285 return SubtitlesButton; 18286 }(TextTrackButton); 18287 /** 18288 * `kind` of TextTrack to look for to associate it with this menu. 18289 * 18290 * @type {string} 18291 * @private 18292 */ 18293 18294 18295 SubtitlesButton.prototype.kind_ = 'subtitles'; 18296 /** 18297 * The text that should display over the `SubtitlesButton`s controls. Added for localization. 18298 * 18299 * @type {string} 18300 * @private 18301 */ 18302 18303 SubtitlesButton.prototype.controlText_ = 'Subtitles'; 18304 Component.registerComponent('SubtitlesButton', SubtitlesButton); 18305 18306 /** 18307 * The menu item for caption track settings menu 18308 * 18309 * @extends TextTrackMenuItem 18310 */ 18311 18312 var CaptionSettingsMenuItem = 18313 /*#__PURE__*/ 18314 function (_TextTrackMenuItem) { 18315 _inheritsLoose(CaptionSettingsMenuItem, _TextTrackMenuItem); 18316 18317 /** 18318 * Creates an instance of this class. 18319 * 18320 * @param {Player} player 18321 * The `Player` that this class should be attached to. 18322 * 18323 * @param {Object} [options] 18324 * The key/value store of player options. 18325 */ 18326 function CaptionSettingsMenuItem(player, options) { 18327 var _this; 18328 18329 options.track = { 18330 player: player, 18331 kind: options.kind, 18332 label: options.kind + ' settings', 18333 selectable: false, 18334 default: false, 18335 mode: 'disabled' 18336 }; // CaptionSettingsMenuItem has no concept of 'selected' 18337 18338 options.selectable = false; 18339 options.name = 'CaptionSettingsMenuItem'; 18340 _this = _TextTrackMenuItem.call(this, player, options) || this; 18341 18342 _this.addClass('vjs-texttrack-settings'); 18343 18344 _this.controlText(', opens ' + options.kind + ' settings dialog'); 18345 18346 return _this; 18347 } 18348 /** 18349 * This gets called when an `CaptionSettingsMenuItem` is "clicked". See 18350 * {@link ClickableComponent} for more detailed information on what a click can be. 18351 * 18352 * @param {EventTarget~Event} [event] 18353 * The `keydown`, `tap`, or `click` event that caused this function to be 18354 * called. 18355 * 18356 * @listens tap 18357 * @listens click 18358 */ 18359 18360 18361 var _proto = CaptionSettingsMenuItem.prototype; 18362 18363 _proto.handleClick = function handleClick(event) { 18364 this.player().getChild('textTrackSettings').open(); 18365 }; 18366 18367 return CaptionSettingsMenuItem; 18368 }(TextTrackMenuItem); 18369 18370 Component.registerComponent('CaptionSettingsMenuItem', CaptionSettingsMenuItem); 18371 18372 /** 18373 * The button component for toggling and selecting captions 18374 * 18375 * @extends TextTrackButton 18376 */ 18377 18378 var CaptionsButton = 18379 /*#__PURE__*/ 18380 function (_TextTrackButton) { 18381 _inheritsLoose(CaptionsButton, _TextTrackButton); 18382 18383 /** 18384 * Creates an instance of this class. 18385 * 18386 * @param {Player} player 18387 * The `Player` that this class should be attached to. 18388 * 18389 * @param {Object} [options] 18390 * The key/value store of player options. 18391 * 18392 * @param {Component~ReadyCallback} [ready] 18393 * The function to call when this component is ready. 18394 */ 18395 function CaptionsButton(player, options, ready) { 18396 return _TextTrackButton.call(this, player, options, ready) || this; 18397 } 18398 /** 18399 * Builds the default DOM `className`. 18400 * 18401 * @return {string} 18402 * The DOM `className` for this object. 18403 */ 18404 18405 18406 var _proto = CaptionsButton.prototype; 18407 18408 _proto.buildCSSClass = function buildCSSClass() { 18409 return "vjs-captions-button " + _TextTrackButton.prototype.buildCSSClass.call(this); 18410 }; 18411 18412 _proto.buildWrapperCSSClass = function buildWrapperCSSClass() { 18413 return "vjs-captions-button " + _TextTrackButton.prototype.buildWrapperCSSClass.call(this); 18414 } 18415 /** 18416 * Create caption menu items 18417 * 18418 * @return {CaptionSettingsMenuItem[]} 18419 * The array of current menu items. 18420 */ 18421 ; 18422 18423 _proto.createItems = function createItems() { 18424 var items = []; 18425 18426 if (!(this.player().tech_ && this.player().tech_.featuresNativeTextTracks) && this.player().getChild('textTrackSettings')) { 18427 items.push(new CaptionSettingsMenuItem(this.player_, { 18428 kind: this.kind_ 18429 })); 18430 this.hideThreshold_ += 1; 18431 } 18432 18433 return _TextTrackButton.prototype.createItems.call(this, items); 18434 }; 18435 18436 return CaptionsButton; 18437 }(TextTrackButton); 18438 /** 18439 * `kind` of TextTrack to look for to associate it with this menu. 18440 * 18441 * @type {string} 18442 * @private 18443 */ 18444 18445 18446 CaptionsButton.prototype.kind_ = 'captions'; 18447 /** 18448 * The text that should display over the `CaptionsButton`s controls. Added for localization. 18449 * 18450 * @type {string} 18451 * @private 18452 */ 18453 18454 CaptionsButton.prototype.controlText_ = 'Captions'; 18455 Component.registerComponent('CaptionsButton', CaptionsButton); 18456 18457 /** 18458 * SubsCapsMenuItem has an [cc] icon to distinguish captions from subtitles 18459 * in the SubsCapsMenu. 18460 * 18461 * @extends TextTrackMenuItem 18462 */ 18463 18464 var SubsCapsMenuItem = 18465 /*#__PURE__*/ 18466 function (_TextTrackMenuItem) { 18467 _inheritsLoose(SubsCapsMenuItem, _TextTrackMenuItem); 18468 18469 function SubsCapsMenuItem() { 18470 return _TextTrackMenuItem.apply(this, arguments) || this; 18471 } 18472 18473 var _proto = SubsCapsMenuItem.prototype; 18474 18475 _proto.createEl = function createEl(type, props, attrs) { 18476 var innerHTML = "<span class=\"vjs-menu-item-text\">" + this.localize(this.options_.label); 18477 18478 if (this.options_.track.kind === 'captions') { 18479 innerHTML += "\n <span aria-hidden=\"true\" class=\"vjs-icon-placeholder\"></span>\n <span class=\"vjs-control-text\"> " + this.localize('Captions') + "</span>\n "; 18480 } 18481 18482 innerHTML += '</span>'; 18483 18484 var el = _TextTrackMenuItem.prototype.createEl.call(this, type, assign({ 18485 innerHTML: innerHTML 18486 }, props), attrs); 18487 18488 return el; 18489 }; 18490 18491 return SubsCapsMenuItem; 18492 }(TextTrackMenuItem); 18493 18494 Component.registerComponent('SubsCapsMenuItem', SubsCapsMenuItem); 18495 18496 /** 18497 * The button component for toggling and selecting captions and/or subtitles 18498 * 18499 * @extends TextTrackButton 18500 */ 18501 18502 var SubsCapsButton = 18503 /*#__PURE__*/ 18504 function (_TextTrackButton) { 18505 _inheritsLoose(SubsCapsButton, _TextTrackButton); 18506 18507 function SubsCapsButton(player, options) { 18508 var _this; 18509 18510 if (options === void 0) { 18511 options = {}; 18512 } 18513 18514 _this = _TextTrackButton.call(this, player, options) || this; // Although North America uses "captions" in most cases for 18515 // "captions and subtitles" other locales use "subtitles" 18516 18517 _this.label_ = 'subtitles'; 18518 18519 if (['en', 'en-us', 'en-ca', 'fr-ca'].indexOf(_this.player_.language_) > -1) { 18520 _this.label_ = 'captions'; 18521 } 18522 18523 _this.menuButton_.controlText(toTitleCase(_this.label_)); 18524 18525 return _this; 18526 } 18527 /** 18528 * Builds the default DOM `className`. 18529 * 18530 * @return {string} 18531 * The DOM `className` for this object. 18532 */ 18533 18534 18535 var _proto = SubsCapsButton.prototype; 18536 18537 _proto.buildCSSClass = function buildCSSClass() { 18538 return "vjs-subs-caps-button " + _TextTrackButton.prototype.buildCSSClass.call(this); 18539 }; 18540 18541 _proto.buildWrapperCSSClass = function buildWrapperCSSClass() { 18542 return "vjs-subs-caps-button " + _TextTrackButton.prototype.buildWrapperCSSClass.call(this); 18543 } 18544 /** 18545 * Create caption/subtitles menu items 18546 * 18547 * @return {CaptionSettingsMenuItem[]} 18548 * The array of current menu items. 18549 */ 18550 ; 18551 18552 _proto.createItems = function createItems() { 18553 var items = []; 18554 18555 if (!(this.player().tech_ && this.player().tech_.featuresNativeTextTracks) && this.player().getChild('textTrackSettings')) { 18556 items.push(new CaptionSettingsMenuItem(this.player_, { 18557 kind: this.label_ 18558 })); 18559 this.hideThreshold_ += 1; 18560 } 18561 18562 items = _TextTrackButton.prototype.createItems.call(this, items, SubsCapsMenuItem); 18563 return items; 18564 }; 18565 18566 return SubsCapsButton; 18567 }(TextTrackButton); 18568 /** 18569 * `kind`s of TextTrack to look for to associate it with this menu. 18570 * 18571 * @type {array} 18572 * @private 18573 */ 18574 18575 18576 SubsCapsButton.prototype.kinds_ = ['captions', 'subtitles']; 18577 /** 18578 * The text that should display over the `SubsCapsButton`s controls. 18579 * 18580 * 18581 * @type {string} 18582 * @private 18583 */ 18584 18585 SubsCapsButton.prototype.controlText_ = 'Subtitles'; 18586 Component.registerComponent('SubsCapsButton', SubsCapsButton); 18587 18588 /** 18589 * An {@link AudioTrack} {@link MenuItem} 18590 * 18591 * @extends MenuItem 18592 */ 18593 18594 var AudioTrackMenuItem = 18595 /*#__PURE__*/ 18596 function (_MenuItem) { 18597 _inheritsLoose(AudioTrackMenuItem, _MenuItem); 18598 18599 /** 18600 * Creates an instance of this class. 18601 * 18602 * @param {Player} player 18603 * The `Player` that this class should be attached to. 18604 * 18605 * @param {Object} [options] 18606 * The key/value store of player options. 18607 */ 18608 function AudioTrackMenuItem(player, options) { 18609 var _this; 18610 18611 var track = options.track; 18612 var tracks = player.audioTracks(); // Modify options for parent MenuItem class's init. 18613 18614 options.label = track.label || track.language || 'Unknown'; 18615 options.selected = track.enabled; 18616 _this = _MenuItem.call(this, player, options) || this; 18617 _this.track = track; 18618
18619 _this.addClass("vjs-" + track.kind + "-menu-item"); 18620 18621 var changeHandler = function changeHandler() { 18622 for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { 18623 args[_key] = arguments[_key]; 18624 } 18625 18626 _this.handleTracksChange.apply(_assertThisInitialized(_assertThisInitialized(_this)), args); 18627 }; 18628 18629 tracks.addEventListener('change', changeHandler); 18630 18631 _this.on('dispose', function () { 18632 tracks.removeEventListener('change', changeHandler); 18633 }); 18634 18635 return _this; 18636 } 18637 18638 var _proto = AudioTrackMenuItem.prototype; 18639 18640 _proto.createEl = function createEl(type, props, attrs) { 18641 var innerHTML = "<span class=\"vjs-menu-item-text\">" + this.localize(this.options_.label); 18642 18643 if (this.options_.track.kind === 'main-desc') { 18644 innerHTML += "\n <span aria-hidden=\"true\" class=\"vjs-icon-placeholder\"></span>\n <span class=\"vjs-control-text\"> " + this.localize('Descriptions') + "</span>\n "; 18645 } 18646 18647 innerHTML += '</span>'; 18648 18649 var el = _MenuItem.prototype.createEl.call(this, type, assign({ 18650 innerHTML: innerHTML 18651 }, props), attrs); 18652 18653 return el; 18654 } 18655 /** 18656 * This gets called when an `AudioTrackMenuItem is "clicked". See {@link ClickableComponent} 18657 * for more detailed information on what a click can be. 18658 * 18659 * @param {EventTarget~Event} [event] 18660 * The `keydown`, `tap`, or `click` event that caused this function to be 18661 * called. 18662 * 18663 * @listens tap 18664 * @listens click 18665 */ 18666 ; 18667 18668 _proto.handleClick = function handleClick(event) { 18669 var tracks = this.player_.audioTracks(); 18670 18671 _MenuItem.prototype.handleClick.call(this, event); 18672 18673 for (var i = 0; i < tracks.length; i++) { 18674 var track = tracks[i]; 18675 track.enabled = track === this.track; 18676 } 18677 } 18678 /** 18679 * Handle any {@link AudioTrack} change. 18680 * 18681 * @param {EventTarget~Event} [event] 18682 * The {@link AudioTrackList#change} event that caused this to run. 18683 * 18684 * @listens AudioTrackList#change 18685 */ 18686 ; 18687 18688 _proto.handleTracksChange = function handleTracksChange(event) { 18689 this.selected(this.track.enabled); 18690 }; 18691 18692 return AudioTrackMenuItem; 18693 }(MenuItem); 18694 18695 Component.registerComponent('AudioTrackMenuItem', AudioTrackMenuItem); 18696 18697 /** 18698 * The base class for buttons that toggle specific {@link AudioTrack} types. 18699 * 18700 * @extends TrackButton 18701 */ 18702 18703 var AudioTrackButton = 18704 /*#__PURE__*/ 18705 function (_TrackButton) { 18706 _inheritsLoose(AudioTrackButton, _TrackButton); 18707 18708 /** 18709 * Creates an instance of this class. 18710 * 18711 * @param {Player} player 18712 * The `Player` that this class should be attached to. 18713 * 18714 * @param {Object} [options={}] 18715 * The key/value store of player options. 18716 */ 18717 function AudioTrackButton(player, options) { 18718 if (options === void 0) { 18719 options = {}; 18720 } 18721 18722 options.tracks = player.audioTracks(); 18723 return _TrackButton.call(this, player, options) || this; 18724 } 18725 /** 18726 * Builds the default DOM `className`. 18727 * 18728 * @return {string} 18729 * The DOM `className` for this object. 18730 */ 18731 18732 18733 var _proto = AudioTrackButton.prototype; 18734 18735 _proto.buildCSSClass = function buildCSSClass() { 18736 return "vjs-audio-button " + _TrackButton.prototype.buildCSSClass.call(this); 18737 }; 18738 18739 _proto.buildWrapperCSSClass = function buildWrapperCSSClass() { 18740 return "vjs-audio-button " + _TrackButton.prototype.buildWrapperCSSClass.call(this); 18741 } 18742 /** 18743 * Create a menu item for each audio track 18744 * 18745 * @param {AudioTrackMenuItem[]} [items=[]] 18746 * An array of existing menu items to use. 18747 * 18748 * @return {AudioTrackMenuItem[]} 18749 * An array of menu items 18750 */ 18751 ; 18752 18753 _proto.createItems = function createItems(items) { 18754 if (items === void 0) { 18755 items = []; 18756 } 18757 18758 // if there's only one audio track, there no point in showing it 18759 this.hideThreshold_ = 1; 18760 var tracks = this.player_.audioTracks(); 18761 18762 for (var i = 0; i < tracks.length; i++) { 18763 var track = tracks[i]; 18764 items.push(new AudioTrackMenuItem(this.player_, { 18765 track: track, 18766 // MenuItem is selectable 18767 selectable: true, 18768 // MenuItem is NOT multiSelectable (i.e. only one can be marked "selected" at a time) 18769 multiSelectable: false 18770 })); 18771 } 18772 18773 return items; 18774 }; 18775 18776 return AudioTrackButton; 18777 }(TrackButton); 18778 /** 18779 * The text that should display over the `AudioTrackButton`s controls. Added for localization. 18780 * 18781 * @type {string} 18782 * @private 18783 */ 18784 18785 18786 AudioTrackButton.prototype.controlText_ = 'Audio Track'; 18787 Component.registerComponent('AudioTrackButton', AudioTrackButton); 18788 18789 /** 18790 * The specific menu item type for selecting a playback rate. 18791 * 18792 * @extends MenuItem 18793 */ 18794 18795 var PlaybackRateMenuItem = 18796 /*#__PURE__*/ 18797 function (_MenuItem) { 18798 _inheritsLoose(PlaybackRateMenuItem, _MenuItem); 18799 18800 /** 18801 * Creates an instance of this class. 18802 * 18803 * @param {Player} player 18804 * The `Player` that this class should be attached to. 18805 * 18806 * @param {Object} [options] 18807 * The key/value store of player options. 18808 */ 18809 function PlaybackRateMenuItem(player, options) { 18810 var _this; 18811 18812 var label = options.rate; 18813 var rate = parseFloat(label, 10); // Modify options for parent MenuItem class's init. 18814 18815 options.label = label; 18816 options.selected = rate === 1; 18817 options.selectable = true; 18818 options.multiSelectable = false;
vendor: 4,379 bytes, lines 18819-18985
18819 _this = _MenuItem.call(this, player, options) || this; 18820 _this.label = label; 18821 _this.rate = rate; 18822 18823 _this.on(player, 'ratechange', _this.update); 18824 18825 return _this; 18826 } 18827 /** 18828 * This gets called when an `PlaybackRateMenuItem` is "clicked". See 18829 * {@link ClickableComponent} for more detailed information on what a click can be. 18830 * 18831 * @param {EventTarget~Event} [event] 18832 * The `keydown`, `tap`, or `click` event that caused this function to be 18833 * called. 18834 * 18835 * @listens tap 18836 * @listens click 18837 */ 18838 18839 18840 var _proto = PlaybackRateMenuItem.prototype; 18841 18842 _proto.handleClick = function handleClick(event) { 18843 _MenuItem.prototype.handleClick.call(this); 18844 18845 this.player().playbackRate(this.rate); 18846 } 18847 /** 18848 * Update the PlaybackRateMenuItem when the playbackrate changes. 18849 * 18850 * @param {EventTarget~Event} [event] 18851 * The `ratechange` event that caused this function to run. 18852 * 18853 * @listens Player#ratechange 18854 */ 18855 ; 18856 18857 _proto.update = function update(event) { 18858 this.selected(this.player().playbackRate() === this.rate); 18859 }; 18860 18861 return PlaybackRateMenuItem; 18862 }(MenuItem); 18863 /** 18864 * The text that should display over the `PlaybackRateMenuItem`s controls. Added for localization. 18865 * 18866 * @type {string} 18867 * @private 18868 */ 18869 18870 18871 PlaybackRateMenuItem.prototype.contentElType = 'button'; 18872 Component.registerComponent('PlaybackRateMenuItem', PlaybackRateMenuItem); 18873 18874 /** 18875 * The component for controlling the playback rate. 18876 * 18877 * @extends MenuButton 18878 */ 18879 18880 var PlaybackRateMenuButton = 18881 /*#__PURE__*/ 18882 function (_MenuButton) { 18883 _inheritsLoose(PlaybackRateMenuButton, _MenuButton); 18884 18885 /** 18886 * Creates an instance of this class. 18887 * 18888 * @param {Player} player 18889 * The `Player` that this class should be attached to. 18890 * 18891 * @param {Object} [options] 18892 * The key/value store of player options. 18893 */ 18894 function PlaybackRateMenuButton(player, options) { 18895 var _this; 18896 18897 _this = _MenuButton.call(this, player, options) || this; 18898 18899 _this.updateVisibility(); 18900 18901 _this.updateLabel(); 18902 18903 _this.on(player, 'loadstart', _this.updateVisibility); 18904 18905 _this.on(player, 'ratechange', _this.updateLabel); 18906 18907 return _this; 18908 } 18909 /** 18910 * Create the `Component`'s DOM element 18911 * 18912 * @return {Element} 18913 * The element that was created. 18914 */ 18915 18916 18917 var _proto = PlaybackRateMenuButton.prototype; 18918 18919 _proto.createEl = function createEl$$1() { 18920 var el = _MenuButton.prototype.createEl.call(this); 18921 18922 this.labelEl_ = createEl('div', { 18923 className: 'vjs-playback-rate-value', 18924 innerHTML: '1x' 18925 }); 18926 el.appendChild(this.labelEl_); 18927 return el; 18928 }; 18929 18930 _proto.dispose = function dispose() { 18931 this.labelEl_ = null; 18932 18933 _MenuButton.prototype.dispose.call(this); 18934 } 18935 /** 18936 * Builds the default DOM `className`. 18937 * 18938 * @return {string} 18939 * The DOM `className` for this object. 18940 */ 18941 ; 18942 18943 _proto.buildCSSClass = function buildCSSClass() { 18944 return "vjs-playback-rate " + _MenuButton.prototype.buildCSSClass.call(this); 18945 }; 18946 18947 _proto.buildWrapperCSSClass = function buildWrapperCSSClass() { 18948 return "vjs-playback-rate " + _MenuButton.prototype.buildWrapperCSSClass.call(this); 18949 } 18950 /** 18951 * Create the playback rate menu 18952 * 18953 * @return {Menu} 18954 * Menu object populated with {@link PlaybackRateMenuItem}s 18955 */ 18956 ; 18957 18958 _proto.createMenu = function createMenu() { 18959 var menu = new Menu(this.player()); 18960 var rates = this.playbackRates(); 18961 18962 if (rates) { 18963 for (var i = rates.length - 1; i >= 0; i--) { 18964 menu.addChild(new PlaybackRateMenuItem(this.player(), { 18965 rate: rates[i] + 'x' 18966 })); 18967 } 18968 } 18969 18970 return menu; 18971 } 18972 /** 18973 * Updates ARIA accessibility attributes 18974 */ 18975 ; 18976 18977 _proto.updateARIAAttributes = function updateARIAAttributes() { 18978 // Current playback rate 18979 this.el().setAttribute('aria-valuenow', this.player().playbackRate()); 18980 } 18981 /** 18982 * This gets called when an `PlaybackRateMenuButton` is "clicked". See 18983 * {@link ClickableComponent} for more detailed information on what a click can be. 18984 * 18985 * @param {EventTarget~Event} [event]
vendor: 13,790 bytes, lines 18986-19481
18986 * The `keydown`, `tap`, or `click` event that caused this function to be 18987 * called. 18988 * 18989 * @listens tap 18990 * @listens click 18991 */ 18992 ; 18993 18994 _proto.handleClick = function handleClick(event) { 18995 // select next rate option 18996 var currentRate = this.player().playbackRate(); 18997 var rates = this.playbackRates(); // this will select first one if the last one currently selected 18998 18999 var newRate = rates[0]; 19000 19001 for (var i = 0; i < rates.length; i++) { 19002 if (rates[i] > currentRate) { 19003 newRate = rates[i]; 19004 break; 19005 } 19006 } 19007 19008 this.player().playbackRate(newRate); 19009 } 19010 /** 19011 * Get possible playback rates 19012 * 19013 * @return {Array} 19014 * All possible playback rates 19015 */ 19016 ; 19017 19018 _proto.playbackRates = function playbackRates() { 19019 return this.options_.playbackRates || this.options_.playerOptions && this.options_.playerOptions.playbackRates; 19020 } 19021 /** 19022 * Get whether playback rates is supported by the tech 19023 * and an array of playback rates exists 19024 * 19025 * @return {boolean} 19026 * Whether changing playback rate is supported 19027 */ 19028 ; 19029 19030 _proto.playbackRateSupported = function playbackRateSupported() { 19031 return this.player().tech_ && this.player().tech_.featuresPlaybackRate && this.playbackRates() && this.playbackRates().length > 0; 19032 } 19033 /** 19034 * Hide playback rate controls when they're no playback rate options to select 19035 * 19036 * @param {EventTarget~Event} [event] 19037 * The event that caused this function to run. 19038 * 19039 * @listens Player#loadstart 19040 */ 19041 ; 19042 19043 _proto.updateVisibility = function updateVisibility(event) { 19044 if (this.playbackRateSupported()) { 19045 this.removeClass('vjs-hidden'); 19046 } else { 19047 this.addClass('vjs-hidden'); 19048 } 19049 } 19050 /** 19051 * Update button label when rate changed 19052 * 19053 * @param {EventTarget~Event} [event] 19054 * The event that caused this function to run. 19055 * 19056 * @listens Player#ratechange 19057 */ 19058 ; 19059 19060 _proto.updateLabel = function updateLabel(event) { 19061 if (this.playbackRateSupported()) { 19062 this.labelEl_.innerHTML = this.player().playbackRate() + 'x'; 19063 } 19064 }; 19065 19066 return PlaybackRateMenuButton; 19067 }(MenuButton); 19068 /** 19069 * The text that should display over the `FullscreenToggle`s controls. Added for localization. 19070 * 19071 * @type {string} 19072 * @private 19073 */ 19074 19075 19076 PlaybackRateMenuButton.prototype.controlText_ = 'Playback Rate'; 19077 Component.registerComponent('PlaybackRateMenuButton', PlaybackRateMenuButton); 19078 19079 /** 19080 * Just an empty spacer element that can be used as an append point for plugins, etc. 19081 * Also can be used to create space between elements when necessary. 19082 * 19083 * @extends Component 19084 */ 19085 19086 var Spacer = 19087 /*#__PURE__*/ 19088 function (_Component) { 19089 _inheritsLoose(Spacer, _Component); 19090 19091 function Spacer() { 19092 return _Component.apply(this, arguments) || this; 19093 } 19094 19095 var _proto = Spacer.prototype; 19096 19097 /** 19098 * Builds the default DOM `className`. 19099 * 19100 * @return {string} 19101 * The DOM `className` for this object. 19102 */ 19103 _proto.buildCSSClass = function buildCSSClass() { 19104 return "vjs-spacer " + _Component.prototype.buildCSSClass.call(this); 19105 } 19106 /** 19107 * Create the `Component`'s DOM element 19108 * 19109 * @return {Element} 19110 * The element that was created. 19111 */ 19112 ; 19113 19114 _proto.createEl = function createEl() { 19115 return _Component.prototype.createEl.call(this, 'div', { 19116 className: this.buildCSSClass() 19117 }); 19118 }; 19119 19120 return Spacer; 19121 }(Component); 19122 19123 Component.registerComponent('Spacer', Spacer); 19124 19125 /** 19126 * Spacer specifically meant to be used as an insertion point for new plugins, etc. 19127 * 19128 * @extends Spacer 19129 */ 19130 19131 var CustomControlSpacer = 19132 /*#__PURE__*/ 19133 function (_Spacer) { 19134 _inheritsLoose(CustomControlSpacer, _Spacer); 19135 19136 function CustomControlSpacer() { 19137 return _Spacer.apply(this, arguments) || this; 19138 } 19139 19140 var _proto = CustomControlSpacer.prototype; 19141 19142 /** 19143 * Builds the default DOM `className`. 19144 * 19145 * @return {string} 19146 * The DOM `className` for this object. 19147 */ 19148 _proto.buildCSSClass = function buildCSSClass() { 19149 return "vjs-custom-control-spacer " + _Spacer.prototype.buildCSSClass.call(this); 19150 } 19151 /** 19152 * Create the `Component`'s DOM element 19153 * 19154 * @return {Element} 19155 * The element that was created. 19156 */ 19157 ; 19158 19159 _proto.createEl = function createEl() { 19160 var el = _Spacer.prototype.createEl.call(this, { 19161 className: this.buildCSSClass() 19162 }); // No-flex/table-cell mode requires there be some content 19163 // in the cell to fill the remaining space of the table. 19164 19165 19166 el.innerHTML = "\xA0"; 19167 return el; 19168 }; 19169 19170 return CustomControlSpacer; 19171 }(Spacer); 19172 19173 Component.registerComponent('CustomControlSpacer', CustomControlSpacer); 19174 19175 /** 19176 * Container of main controls. 19177 * 19178 * @extends Component 19179 */ 19180 19181 var ControlBar = 19182 /*#__PURE__*/ 19183 function (_Component) { 19184 _inheritsLoose(ControlBar, _Component); 19185 19186 function ControlBar() { 19187 return _Component.apply(this, arguments) || this; 19188 } 19189 19190 var _proto = ControlBar.prototype; 19191 19192 /** 19193 * Create the `Component`'s DOM element 19194 * 19195 * @return {Element} 19196 * The element that was created. 19197 */ 19198 _proto.createEl = function createEl() { 19199 return _Component.prototype.createEl.call(this, 'div', { 19200 className: 'vjs-control-bar', 19201 dir: 'ltr' 19202 }); 19203 }; 19204 19205 return ControlBar; 19206 }(Component); 19207 /** 19208 * Default options for `ControlBar` 19209 * 19210 * @type {Object} 19211 * @private 19212 */ 19213 19214 19215 ControlBar.prototype.options_ = { 19216 children: ['playToggle', 'volumePanel', 'currentTimeDisplay', 'timeDivider', 'durationDisplay', 'progressControl', 'liveDisplay', 'seekToLive', 'remainingTimeDisplay', 'customControlSpacer', 'playbackRateMenuButton', 'chaptersButton', 'descriptionsButton', 'subsCapsButton', 'audioTrackButton', 'fullscreenToggle'] 19217 }; 19218 Component.registerComponent('ControlBar', ControlBar); 19219 19220 /** 19221 * A display that indicates an error has occurred. This means that the video 19222 * is unplayable. 19223 * 19224 * @extends ModalDialog 19225 */ 19226 19227 var ErrorDisplay = 19228 /*#__PURE__*/ 19229 function (_ModalDialog) { 19230 _inheritsLoose(ErrorDisplay, _ModalDialog); 19231 19232 /** 19233 * Creates an instance of this class. 19234 * 19235 * @param {Player} player 19236 * The `Player` that this class should be attached to. 19237 * 19238 * @param {Object} [options] 19239 * The key/value store of player options. 19240 */ 19241 function ErrorDisplay(player, options) { 19242 var _this; 19243 19244 _this = _ModalDialog.call(this, player, options) || this; 19245 19246 _this.on(player, 'error', _this.open); 19247 19248 return _this; 19249 } 19250 /** 19251 * Builds the default DOM `className`. 19252 * 19253 * @return {string} 19254 * The DOM `className` for this object. 19255 * 19256 * @deprecated Since version 5. 19257 */ 19258 19259 19260 var _proto = ErrorDisplay.prototype; 19261 19262 _proto.buildCSSClass = function buildCSSClass() { 19263 return "vjs-error-display " + _ModalDialog.prototype.buildCSSClass.call(this); 19264 } 19265 /** 19266 * Gets the localized error message based on the `Player`s error. 19267 * 19268 * @return {string} 19269 * The `Player`s error message localized or an empty string. 19270 */ 19271 ; 19272 19273 _proto.content = function content() { 19274 var error = this.player().error(); 19275 return error ? this.localize(error.message) : ''; 19276 }; 19277 19278 return ErrorDisplay; 19279 }(ModalDialog); 19280 /** 19281 * The default options for an `ErrorDisplay`. 19282 * 19283 * @private 19284 */ 19285 19286 19287 ErrorDisplay.prototype.options_ = mergeOptions(ModalDialog.prototype.options_, { 19288 pauseOnOpen: false, 19289 fillAlways: true, 19290 temporary: false, 19291 uncloseable: true 19292 }); 19293 Component.registerComponent('ErrorDisplay', ErrorDisplay); 19294 19295 var LOCAL_STORAGE_KEY = 'vjs-text-track-settings'; 19296 var COLOR_BLACK = ['#000', 'Black']; 19297 var COLOR_BLUE = ['#00F', 'Blue']; 19298 var COLOR_CYAN = ['#0FF', 'Cyan']; 19299 var COLOR_GREEN = ['#0F0', 'Green']; 19300 var COLOR_MAGENTA = ['#F0F', 'Magenta']; 19301 var COLOR_RED = ['#F00', 'Red']; 19302 var COLOR_WHITE = ['#FFF', 'White']; 19303 var COLOR_YELLOW = ['#FF0', 'Yellow']; 19304 var OPACITY_OPAQUE = ['1', 'Opaque']; 19305 var OPACITY_SEMI = ['0.5', 'Semi-Transparent']; 19306 var OPACITY_TRANS = ['0', 'Transparent']; // Configuration for the various <select> elements in the DOM of this component. 19307 // 19308 // Possible keys include: 19309 // 19310 // `default`: 19311 // The default option index. Only needs to be provided if not zero. 19312 // `parser`: 19313 // A function which is used to parse the value from the selected option in 19314 // a customized way. 19315 // `selector`: 19316 // The selector used to find the associated <select> element. 19317 19318 var selectConfigs = { 19319 backgroundColor: { 19320 selector: '.vjs-bg-color > select', 19321 id: 'captions-background-color-%s', 19322 label: 'Color', 19323 options: [COLOR_BLACK, COLOR_WHITE, COLOR_RED, COLOR_GREEN, COLOR_BLUE, COLOR_YELLOW, COLOR_MAGENTA, COLOR_CYAN] 19324 }, 19325 backgroundOpacity: { 19326 selector: '.vjs-bg-opacity > select', 19327 id: 'captions-background-opacity-%s', 19328 label: 'Transparency', 19329 options: [OPACITY_OPAQUE, OPACITY_SEMI, OPACITY_TRANS] 19330 }, 19331 color: { 19332 selector: '.vjs-fg-color > select', 19333 id: 'captions-foreground-color-%s', 19334 label: 'Color', 19335 options: [COLOR_WHITE, COLOR_BLACK, COLOR_RED, COLOR_GREEN, COLOR_BLUE, COLOR_YELLOW, COLOR_MAGENTA, COLOR_CYAN] 19336 }, 19337 edgeStyle: { 19338 selector: '.vjs-edge-style > select', 19339 id: '%s', 19340 label: 'Text Edge Style', 19341 options: [['none', 'None'], ['raised', 'Raised'], ['depressed', 'Depressed'], ['uniform', 'Uniform'], ['dropshadow', 'Dropshadow']] 19342 }, 19343 fontFamily: { 19344 selector: '.vjs-font-family > select', 19345 id: 'captions-font-family-%s', 19346 label: 'Font Family', 19347 options: [['proportionalSansSerif', 'Proportional Sans-Serif'], ['monospaceSansSerif', 'Monospace Sans-Serif'], ['proportionalSerif', 'Proportional Serif'], ['monospaceSerif', 'Monospace Serif'], ['casual', 'Casual'], ['script', 'Script'], ['small-caps', 'Small Caps']] 19348 }, 19349 fontPercent: { 19350 selector: '.vjs-font-percent > select', 19351 id: 'captions-font-size-%s', 19352 label: 'Font Size', 19353 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%']], 19354 default: 2, 19355 parser: function parser(v) { 19356 return v === '1.00' ? null : Number(v); 19357 } 19358 }, 19359 textOpacity: { 19360 selector: '.vjs-text-opacity > select', 19361 id: 'captions-foreground-opacity-%s', 19362 label: 'Transparency', 19363 options: [OPACITY_OPAQUE, OPACITY_SEMI] 19364 }, 19365 // Options for this object are defined below. 19366 windowColor: { 19367 selector: '.vjs-window-color > select', 19368 id: 'captions-window-color-%s', 19369 label: 'Color' 19370 }, 19371 // Options for this object are defined below. 19372 windowOpacity: { 19373 selector: '.vjs-window-opacity > select', 19374 id: 'captions-window-opacity-%s', 19375 label: 'Transparency', 19376 options: [OPACITY_TRANS, OPACITY_SEMI, OPACITY_OPAQUE] 19377 } 19378 }; 19379 selectConfigs.windowColor.options = selectConfigs.backgroundColor.options; 19380 /** 19381 * Get the actual value of an option. 19382 * 19383 * @param {string} value 19384 * The value to get 19385 * 19386 * @param {Function} [parser] 19387 * Optional function to adjust the value. 19388 * 19389 * @return {Mixed} 19390 * - Will be `undefined` if no value exists 19391 * - Will be `undefined` if the given value is "none". 19392 * - Will be the actual value otherwise. 19393 * 19394 * @private 19395 */ 19396 19397 function parseOptionValue(value, parser) { 19398 if (parser) { 19399 value = parser(value); 19400 } 19401 19402 if (value && value !== 'none') { 19403 return value; 19404 } 19405 } 19406 /** 19407 * Gets the value of the selected <option> element within a <select> element. 19408 * 19409 * @param {Element} el 19410 * the element to look in 19411 * 19412 * @param {Function} [parser] 19413 * Optional function to adjust the value. 19414 * 19415 * @return {Mixed} 19416 * - Will be `undefined` if no value exists 19417 * - Will be `undefined` if the given value is "none". 19418 * - Will be the actual value otherwise. 19419 * 19420 * @private 19421 */ 19422 19423 19424 function getSelectedOptionValue(el, parser) { 19425 var value = el.options[el.options.selectedIndex].value; 19426 return parseOptionValue(value, parser); 19427 } 19428 /** 19429 * Sets the selected <option> element within a <select> element based on a 19430 * given value. 19431 * 19432 * @param {Element} el 19433 * The element to look in. 19434 * 19435 * @param {string} value 19436 * the property to look on. 19437 * 19438 * @param {Function} [parser] 19439 * Optional function to adjust the value before comparing. 19440 * 19441 * @private 19442 */ 19443 19444 19445 function setSelectedOption(el, value, parser) { 19446 if (!value) { 19447 return; 19448 } 19449 19450 for (var i = 0; i < el.options.length; i++) { 19451 if (parseOptionValue(el.options[i].value, parser) === value) { 19452 el.selectedIndex = i; 19453 break; 19454 } 19455 } 19456 } 19457 /** 19458 * Manipulate Text Tracks settings. 19459 * 19460 * @extends ModalDialog 19461 */ 19462 19463 19464 var TextTrackSettings = 19465 /*#__PURE__*/ 19466 function (_ModalDialog) { 19467 _inheritsLoose(TextTrackSettings, _ModalDialog); 19468 19469 /** 19470 * Creates an instance of this class. 19471 * 19472 * @param {Player} player 19473 * The `Player` that this class should be attached to. 19474 * 19475 * @param {Object} [options] 19476 * The key/value store of player options. 19477 */ 19478 function TextTrackSettings(player, options) { 19479 var _this; 19480 19481 options.temporary = false;
vendor: 16,391 bytes, lines 19482-19998
19482 _this = _ModalDialog.call(this, player, options) || this; 19483 _this.updateDisplay = bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.updateDisplay); // fill the modal and pretend we have opened it 19484 19485 _this.fill(); 19486 19487 _this.hasBeenOpened_ = _this.hasBeenFilled_ = true; 19488 _this.endDialog = createEl('p', { 19489 className: 'vjs-control-text', 19490 textContent: _this.localize('End of dialog window.') 19491 }); 19492 19493 _this.el().appendChild(_this.endDialog); 19494 19495 _this.setDefaults(); // Grab `persistTextTrackSettings` from the player options if not passed in child options 19496 19497 19498 if (options.persistTextTrackSettings === undefined) { 19499 _this.options_.persistTextTrackSettings = _this.options_.playerOptions.persistTextTrackSettings; 19500 } 19501 19502 _this.on(_this.$('.vjs-done-button'), 'click', function () { 19503 _this.saveSettings(); 19504 19505 _this.close(); 19506 }); 19507 19508 _this.on(_this.$('.vjs-default-button'), 'click', function () { 19509 _this.setDefaults(); 19510 19511 _this.updateDisplay(); 19512 }); 19513 19514 each(selectConfigs, function (config) { 19515 _this.on(_this.$(config.selector), 'change', _this.updateDisplay); 19516 }); 19517 19518 if (_this.options_.persistTextTrackSettings) { 19519 _this.restoreSettings(); 19520 } 19521 19522 return _this; 19523 } 19524 19525 var _proto = TextTrackSettings.prototype; 19526 19527 _proto.dispose = function dispose() { 19528 this.endDialog = null; 19529 19530 _ModalDialog.prototype.dispose.call(this); 19531 } 19532 /** 19533 * Create a <select> element with configured options. 19534 * 19535 * @param {string} key 19536 * Configuration key to use during creation. 19537 * 19538 * @return {string} 19539 * An HTML string. 19540 * 19541 * @private 19542 */ 19543 ; 19544 19545 _proto.createElSelect_ = function createElSelect_(key, legendId, type) { 19546 var _this2 = this; 19547 19548 if (legendId === void 0) { 19549 legendId = ''; 19550 } 19551 19552 if (type === void 0) { 19553 type = 'label'; 19554 } 19555 19556 var config = selectConfigs[key]; 19557 var id = config.id.replace('%s', this.id_); 19558 var selectLabelledbyIds = [legendId, id].join(' ').trim(); 19559 return ["<" + type + " id=\"" + id + "\" class=\"" + (type === 'label' ? 'vjs-label' : '') + "\">", this.localize(config.label), "</" + type + ">", "<select aria-labelledby=\"" + selectLabelledbyIds + "\">"].concat(config.options.map(function (o) { 19560 var optionId = id + '-' + o[1].replace(/\W+/g, ''); 19561 return ["<option id=\"" + optionId + "\" value=\"" + o[0] + "\" ", "aria-labelledby=\"" + selectLabelledbyIds + " " + optionId + "\">", _this2.localize(o[1]), '</option>'].join(''); 19562 })).concat('</select>').join(''); 19563 } 19564 /** 19565 * Create foreground color element for the component 19566 * 19567 * @return {string} 19568 * An HTML string. 19569 * 19570 * @private 19571 */ 19572 ; 19573 19574 _proto.createElFgColor_ = function createElFgColor_() { 19575 var legendId = "captions-text-legend-" + this.id_; 19576 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(''); 19577 } 19578 /** 19579 * Create background color element for the component 19580 * 19581 * @return {string} 19582 * An HTML string. 19583 * 19584 * @private 19585 */ 19586 ; 19587 19588 _proto.createElBgColor_ = function createElBgColor_() { 19589 var legendId = "captions-background-" + this.id_; 19590 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(''); 19591 } 19592 /** 19593 * Create window color element for the component 19594 * 19595 * @return {string} 19596 * An HTML string. 19597 * 19598 * @private 19599 */ 19600 ; 19601 19602 _proto.createElWinColor_ = function createElWinColor_() { 19603 var legendId = "captions-window-" + this.id_; 19604 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(''); 19605 } 19606 /** 19607 * Create color elements for the component 19608 * 19609 * @return {Element} 19610 * The element that was created 19611 * 19612 * @private 19613 */ 19614 ; 19615 19616 _proto.createElColors_ = function createElColors_() { 19617 return createEl('div', { 19618 className: 'vjs-track-settings-colors', 19619 innerHTML: [this.createElFgColor_(), this.createElBgColor_(), this.createElWinColor_()].join('') 19620 }); 19621 } 19622 /** 19623 * Create font elements for the component 19624 * 19625 * @return {Element} 19626 * The element that was created. 19627 * 19628 * @private 19629 */ 19630 ; 19631 19632 _proto.createElFont_ = function createElFont_() { 19633 return createEl('div', { 19634 className: 'vjs-track-settings-font', 19635 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('') 19636 }); 19637 } 19638 /** 19639 * Create controls for the component 19640 * 19641 * @return {Element} 19642 * The element that was created. 19643 * 19644 * @private 19645 */ 19646 ; 19647 19648 _proto.createElControls_ = function createElControls_() { 19649 var defaultsDescription = this.localize('restore all settings to the default values'); 19650 return createEl('div', { 19651 className: 'vjs-track-settings-controls', 19652 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('') 19653 }); 19654 }; 19655 19656 _proto.content = function content() { 19657 return [this.createElColors_(), this.createElFont_(), this.createElControls_()]; 19658 }; 19659 19660 _proto.label = function label() { 19661 return this.localize('Caption Settings Dialog'); 19662 }; 19663 19664 _proto.description = function description() { 19665 return this.localize('Beginning of dialog window. Escape will cancel and close the window.'); 19666 }; 19667 19668 _proto.buildCSSClass = function buildCSSClass() { 19669 return _ModalDialog.prototype.buildCSSClass.call(this) + ' vjs-text-track-settings'; 19670 } 19671 /** 19672 * Gets an object of text track settings (or null). 19673 * 19674 * @return {Object} 19675 * An object with config values parsed from the DOM or localStorage. 19676 */ 19677 ; 19678 19679 _proto.getValues = function getValues() { 19680 var _this3 = this; 19681 19682 return reduce(selectConfigs, function (accum, config, key) { 19683 var value = getSelectedOptionValue(_this3.$(config.selector), config.parser); 19684 19685 if (value !== undefined) { 19686 accum[key] = value; 19687 } 19688 19689 return accum; 19690 }, {}); 19691 } 19692 /** 19693 * Sets text track settings from an object of values. 19694 * 19695 * @param {Object} values 19696 * An object with config values parsed from the DOM or localStorage. 19697 */ 19698 ; 19699 19700 _proto.setValues = function setValues(values) { 19701 var _this4 = this; 19702 19703 each(selectConfigs, function (config, key) { 19704 setSelectedOption(_this4.$(config.selector), values[key], config.parser); 19705 }); 19706 } 19707 /** 19708 * Sets all `<select>` elements to their default values. 19709 */ 19710 ; 19711 19712 _proto.setDefaults = function setDefaults() { 19713 var _this5 = this; 19714 19715 each(selectConfigs, function (config) { 19716 var index = config.hasOwnProperty('default') ? config.default : 0; 19717 _this5.$(config.selector).selectedIndex = index; 19718 }); 19719 } 19720 /** 19721 * Restore texttrack settings from localStorage 19722 */ 19723 ; 19724 19725 _proto.restoreSettings = function restoreSettings() { 19726 var values; 19727 19728 try { 19729 values = JSON.parse(window$1.localStorage.getItem(LOCAL_STORAGE_KEY)); 19730 } catch (err) { 19731 log.warn(err); 19732 } 19733 19734 if (values) { 19735 this.setValues(values); 19736 } 19737 } 19738 /** 19739 * Save text track settings to localStorage 19740 */ 19741 ; 19742 19743 _proto.saveSettings = function saveSettings() { 19744 if (!this.options_.persistTextTrackSettings) { 19745 return; 19746 } 19747 19748 var values = this.getValues(); 19749 19750 try { 19751 if (Object.keys(values).length) { 19752 window$1.localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(values)); 19753 } else { 19754 window$1.localStorage.removeItem(LOCAL_STORAGE_KEY); 19755 } 19756 } catch (err) { 19757 log.warn(err); 19758 } 19759 } 19760 /** 19761 * Update display of text track settings 19762 */ 19763 ; 19764 19765 _proto.updateDisplay = function updateDisplay() { 19766 var ttDisplay = this.player_.getChild('textTrackDisplay'); 19767 19768 if (ttDisplay) { 19769 ttDisplay.updateDisplay(); 19770 } 19771 } 19772 /** 19773 * conditionally blur the element and refocus the captions button 19774 * 19775 * @private 19776 */ 19777 ; 19778 19779 _proto.conditionalBlur_ = function conditionalBlur_() { 19780 this.previouslyActiveEl_ = null; 19781 var cb = this.player_.controlBar; 19782 var subsCapsBtn = cb && cb.subsCapsButton; 19783 var ccBtn = cb && cb.captionsButton; 19784 19785 if (subsCapsBtn) { 19786 subsCapsBtn.focus(); 19787 } else if (ccBtn) { 19788 ccBtn.focus(); 19789 } 19790 }; 19791 19792 return TextTrackSettings; 19793 }(ModalDialog); 19794 19795 Component.registerComponent('TextTrackSettings', TextTrackSettings); 19796 19797 /** 19798 * A Resize Manager. It is in charge of triggering `playerresize` on the player in the right conditions. 19799 * 19800 * 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}. 19801 * 19802 * If the ResizeObserver is available natively, it will be used. A polyfill can be passed in as an option. 19803 * If a `playerresize` event is not needed, the ResizeManager component can be removed from the player, see the example below. 19804 * @example <caption>How to disable the resize manager</caption> 19805 * const player = videojs('#vid', { 19806 * resizeManager: false 19807 * }); 19808 * 19809 * @see {@link https://wicg.github.io/ResizeObserver/|ResizeObserver specification} 19810 * 19811 * @extends Component 19812 */ 19813 19814 var ResizeManager = 19815 /*#__PURE__*/ 19816 function (_Component) { 19817 _inheritsLoose(ResizeManager, _Component); 19818 19819 /** 19820 * Create the ResizeManager. 19821 * 19822 * @param {Object} player 19823 * The `Player` that this class should be attached to. 19824 * 19825 * @param {Object} [options] 19826 * The key/value store of ResizeManager options. 19827 * 19828 * @param {Object} [options.ResizeObserver] 19829 * A polyfill for ResizeObserver can be passed in here. 19830 * If this is set to null it will ignore the native ResizeObserver and fall back to the iframe fallback. 19831 */ 19832 function ResizeManager(player, options) { 19833 var _this; 19834 19835 var RESIZE_OBSERVER_AVAILABLE = options.ResizeObserver || window$1.ResizeObserver; // if `null` was passed, we want to disable the ResizeObserver 19836 19837 if (options.ResizeObserver === null) { 19838 RESIZE_OBSERVER_AVAILABLE = false; 19839 } // Only create an element when ResizeObserver isn't available 19840 19841 19842 var options_ = mergeOptions({ 19843 createEl: !RESIZE_OBSERVER_AVAILABLE, 19844 reportTouchActivity: false 19845 }, options); 19846 _this = _Component.call(this, player, options_) || this; 19847 _this.ResizeObserver = options.ResizeObserver || window$1.ResizeObserver; 19848 _this.loadListener_ = null; 19849 _this.resizeObserver_ = null; 19850 _this.debouncedHandler_ = debounce(function () { 19851 _this.resizeHandler(); 19852 }, 100, false, _assertThisInitialized(_assertThisInitialized(_this))); 19853 19854 if (RESIZE_OBSERVER_AVAILABLE) { 19855 _this.resizeObserver_ = new _this.ResizeObserver(_this.debouncedHandler_); 19856 19857 _this.resizeObserver_.observe(player.el()); 19858 } else { 19859 _this.loadListener_ = function () { 19860 if (!_this.el_ || !_this.el_.contentWindow) { 19861 return; 19862 } 19863 19864 var debouncedHandler_ = _this.debouncedHandler_; 19865 19866 var unloadListener_ = _this.unloadListener_ = function () { 19867 off(this, 'resize', debouncedHandler_); 19868 off(this, 'unload', unloadListener_); 19869 unloadListener_ = null; 19870 }; // safari and edge can unload the iframe before resizemanager dispose 19871 // we have to dispose of event handlers correctly before that happens 19872 19873 19874 on(_this.el_.contentWindow, 'unload', unloadListener_); 19875 on(_this.el_.contentWindow, 'resize', debouncedHandler_); 19876 }; 19877 19878 _this.one('load', _this.loadListener_); 19879 } 19880 19881 return _this; 19882 } 19883 19884 var _proto = ResizeManager.prototype; 19885 19886 _proto.createEl = function createEl() { 19887 return _Component.prototype.createEl.call(this, 'iframe', { 19888 className: 'vjs-resize-manager', 19889 tabIndex: -1 19890 }, { 19891 'aria-hidden': 'true' 19892 }); 19893 } 19894 /** 19895 * Called when a resize is triggered on the iframe or a resize is observed via the ResizeObserver 19896 * 19897 * @fires Player#playerresize 19898 */ 19899 ; 19900 19901 _proto.resizeHandler = function resizeHandler() { 19902 /** 19903 * Called when the player size has changed 19904 * 19905 * @event Player#playerresize 19906 * @type {EventTarget~Event} 19907 */ 19908 // make sure player is still around to trigger 19909 // prevents this from causing an error after dispose 19910 if (!this.player_ || !this.player_.trigger) { 19911 return; 19912 } 19913 19914 this.player_.trigger('playerresize'); 19915 }; 19916 19917 _proto.dispose = function dispose() { 19918 if (this.debouncedHandler_) { 19919 this.debouncedHandler_.cancel(); 19920 } 19921 19922 if (this.resizeObserver_) { 19923 if (this.player_.el()) { 19924 this.resizeObserver_.unobserve(this.player_.el()); 19925 } 19926 19927 this.resizeObserver_.disconnect(); 19928 } 19929 19930 if (this.loadListener_) { 19931 this.off('load', this.loadListener_); 19932 } 19933 19934 if (this.el_ && this.el_.contentWindow && this.unloadListener_) { 19935 this.unloadListener_.call(this.el_.contentWindow); 19936 } 19937 19938 this.ResizeObserver = null; 19939 this.resizeObserver = null; 19940 this.debouncedHandler_ = null; 19941 this.loadListener_ = null; 19942 19943 _Component.prototype.dispose.call(this); 19944 }; 19945 19946 return ResizeManager; 19947 }(Component); 19948 19949 Component.registerComponent('ResizeManager', ResizeManager); 19950 19951 /* track when we are at the live edge, and other helpers for live playback */ 19952 19953 var LiveTracker = 19954 /*#__PURE__*/ 19955 function (_Component) { 19956 _inheritsLoose(LiveTracker, _Component); 19957 19958 function LiveTracker(player, options) { 19959 var _this; 19960 19961 // LiveTracker does not need an element 19962 var options_ = mergeOptions({ 19963 createEl: false 19964 }, options); 19965 _this = _Component.call(this, player, options_) || this; 19966 19967 _this.reset_(); 19968 19969 _this.on(_this.player_, 'durationchange', _this.handleDurationchange); // we don't need to track live playback if the document is hidden, 19970 // also, tracking when the document is hidden can 19971 // cause the CPU to spike and eventually crash the page on IE11. 19972 19973 19974 if (IE_VERSION && 'hidden' in document && 'visibilityState' in document) { 19975 _this.on(document, 'visibilitychange', _this.handleVisibilityChange); 19976 } 19977 19978 return _this; 19979 } 19980 19981 var _proto = LiveTracker.prototype; 19982 19983 _proto.handleVisibilityChange = function handleVisibilityChange() { 19984 if (this.player_.duration() !== Infinity) { 19985 return; 19986 } 19987 19988 if (document.hidden) { 19989 this.stopTracking(); 19990 } else { 19991 this.startTracking(); 19992 } 19993 }; 19994 19995 _proto.isBehind_ = function isBehind_() { 19996 // don't report that we are behind until a timeupdate has been seen 19997 if (!this.timeupdateSeen_) { 19998 return false;
19999 } 20000 20001 var liveCurrentTime = this.liveCurrentTime(); 20002 var currentTime = this.player_.currentTime(); 20003 var seekableIncrement = this.seekableIncrement_; // the live edge window is the amount of seconds away from live 20004 // that a player can be, but still be considered live. 20005 // we add 0.07 because the live tracking happens every 30ms 20006 // and we want some wiggle room for short segment live playback 20007 20008 var liveEdgeWindow = seekableIncrement * 2 + 0.07; // on Android liveCurrentTime can bee Infinity, because seekableEnd 20009 // can be Infinity, so we handle that case. 20010 20011 return liveCurrentTime !== Infinity && liveCurrentTime - liveEdgeWindow >= currentTime; 20012 } // all the functionality for tracking when seek end changes 20013 // and for tracking how far past seek end we should be 20014 ; 20015 20016 _proto.trackLive_ = function trackLive_() { 20017 this.pastSeekEnd_ = this.pastSeekEnd_; 20018 var seekable = this.player_.seekable(); // skip undefined seekable 20019 20020 if (!seekable || !seekable.length) { 20021 return; 20022 } 20023 20024 var newSeekEnd = this.seekableEnd(); // we can only tell if we are behind live, when seekable changes 20025 // once we detect that seekable has changed we check the new seek 20026 // end against current time, with a fudge value of half a second. 20027 20028 if (newSeekEnd !== this.lastSeekEnd_) { 20029 if (this.lastSeekEnd_) { 20030 this.seekableIncrement_ = Math.abs(newSeekEnd - this.lastSeekEnd_); 20031 } 20032 20033 this.pastSeekEnd_ = 0; 20034 this.lastSeekEnd_ = newSeekEnd; 20035 this.trigger('seekableendchange'); 20036 } 20037 20038 this.pastSeekEnd_ = this.pastSeekEnd() + 0.03; 20039 20040 if (this.isBehind_() !== this.behindLiveEdge()) { 20041 this.behindLiveEdge_ = this.isBehind_(); 20042 this.trigger('liveedgechange'); 20043 } 20044 } 20045 /** 20046 * handle a durationchange event on the player 20047 * and start/stop tracking accordingly. 20048 */ 20049 ; 20050 20051 _proto.handleDurationchange = function handleDurationchange() { 20052 if (this.player_.duration() === Infinity) { 20053 this.startTracking(); 20054 } else { 20055 this.stopTracking(); 20056 } 20057 } 20058 /** 20059 * start tracking live playback 20060 */ 20061 ; 20062 20063 _proto.startTracking = function startTracking() { 20064 var _this2 = this; 20065 20066 if (this.isTracking()) { 20067 return; 20068 } 20069 20070 this.trackingInterval_ = this.setInterval(this.trackLive_, 30); 20071 this.trackLive_(); 20072 this.on(this.player_, 'play', this.trackLive_); 20073 this.on(this.player_, 'pause', this.trackLive_); 20074 this.one(this.player_, 'play', this.handlePlay); // this is to prevent showing that we are not live 20075 // before a video starts to play 20076 20077 if (!this.timeupdateSeen_) { 20078 this.handleTimeupdate = function () { 20079 _this2.timeupdateSeen_ = true; 20080 _this2.handleTimeupdate = null; 20081 }; 20082 20083 this.one(this.player_, 'timeupdate', this.handleTimeupdate); 20084 } 20085 }; 20086 20087 _proto.handlePlay = function handlePlay() { 20088 this.one(this.player_, 'timeupdate', this.seekToLiveEdge); 20089 } 20090 /** 20091 * Stop tracking, and set all internal variables to 20092 * their initial value. 20093 */ 20094 ; 20095 20096 _proto.reset_ = function reset_() { 20097 this.pastSeekEnd_ = 0; 20098 this.lastSeekEnd_ = null; 20099 this.behindLiveEdge_ = null; 20100 this.timeupdateSeen_ = false;
vendor: 21,481 bytes, lines 20101-20775
20101 this.clearInterval(this.trackingInterval_); 20102 this.trackingInterval_ = null; 20103 this.seekableIncrement_ = 12; 20104 this.off(this.player_, 'play', this.trackLive_); 20105 this.off(this.player_, 'pause', this.trackLive_); 20106 this.off(this.player_, 'play', this.handlePlay); 20107 this.off(this.player_, 'timeupdate', this.seekToLiveEdge); 20108 20109 if (this.handleTimeupdate) { 20110 this.off(this.player_, 'timeupdate', this.handleTimeupdate); 20111 this.handleTimeupdate = null; 20112 } 20113 } 20114 /** 20115 * stop tracking live playback 20116 */ 20117 ; 20118 20119 _proto.stopTracking = function stopTracking() { 20120 if (!this.isTracking()) { 20121 return; 20122 } 20123 20124 this.reset_(); 20125 } 20126 /** 20127 * A helper to get the player seekable end 20128 * so that we don't have to null check everywhere 20129 */ 20130 ; 20131 20132 _proto.seekableEnd = function seekableEnd() { 20133 var seekable = this.player_.seekable(); 20134 var seekableEnds = []; 20135 var i = seekable ? seekable.length : 0; 20136 20137 while (i--) { 20138 seekableEnds.push(seekable.end(i)); 20139 } // grab the furthest seekable end after sorting, or if there are none 20140 // default to Infinity 20141 20142 20143 return seekableEnds.length ? seekableEnds.sort()[seekableEnds.length - 1] : Infinity; 20144 } 20145 /** 20146 * A helper to get the player seekable start 20147 * so that we don't have to null check everywhere 20148 */ 20149 ; 20150 20151 _proto.seekableStart = function seekableStart() { 20152 var seekable = this.player_.seekable(); 20153 var seekableStarts = []; 20154 var i = seekable ? seekable.length : 0; 20155 20156 while (i--) { 20157 seekableStarts.push(seekable.start(i)); 20158 } // grab the first seekable start after sorting, or if there are none 20159 // default to 0 20160 20161 20162 return seekableStarts.length ? seekableStarts.sort()[0] : 0; 20163 } 20164 /** 20165 * Get the live time window 20166 */ 20167 ; 20168 20169 _proto.liveWindow = function liveWindow() { 20170 var liveCurrentTime = this.liveCurrentTime(); 20171 20172 if (liveCurrentTime === Infinity) { 20173 return Infinity; 20174 } 20175 20176 return liveCurrentTime - this.seekableStart(); 20177 } 20178 /** 20179 * Determines if the player is live, only checks if this component 20180 * is tracking live playback or not 20181 */ 20182 ; 20183 20184 _proto.isLive = function isLive() { 20185 return this.isTracking(); 20186 } 20187 /** 20188 * Determines if currentTime is at the live edge and won't fall behind 20189 * on each seekableendchange 20190 */ 20191 ; 20192 20193 _proto.atLiveEdge = function atLiveEdge() { 20194 return !this.behindLiveEdge(); 20195 } 20196 /** 20197 * get what we expect the live current time to be 20198 */ 20199 ; 20200 20201 _proto.liveCurrentTime = function liveCurrentTime() { 20202 return this.pastSeekEnd() + this.seekableEnd(); 20203 } 20204 /** 20205 * Returns how far past seek end we expect current time to be 20206 */ 20207 ; 20208 20209 _proto.pastSeekEnd = function pastSeekEnd() { 20210 return this.pastSeekEnd_; 20211 } 20212 /** 20213 * If we are currently behind the live edge, aka currentTime will be 20214 * behind on a seekableendchange 20215 */ 20216 ; 20217 20218 _proto.behindLiveEdge = function behindLiveEdge() { 20219 return this.behindLiveEdge_; 20220 }; 20221 20222 _proto.isTracking = function isTracking() { 20223 return typeof this.trackingInterval_ === 'number'; 20224 } 20225 /** 20226 * Seek to the live edge if we are behind the live edge 20227 */ 20228 ; 20229 20230 _proto.seekToLiveEdge = function seekToLiveEdge() { 20231 if (this.atLiveEdge()) { 20232 return; 20233 } 20234 20235 this.player_.currentTime(this.liveCurrentTime()); 20236 20237 if (this.player_.paused()) { 20238 this.player_.play(); 20239 } 20240 }; 20241 20242 _proto.dispose = function dispose() { 20243 this.stopTracking(); 20244 20245 _Component.prototype.dispose.call(this); 20246 }; 20247 20248 return LiveTracker; 20249 }(Component); 20250 20251 Component.registerComponent('LiveTracker', LiveTracker); 20252 20253 /** 20254 * This function is used to fire a sourceset when there is something 20255 * similar to `mediaEl.load()` being called. It will try to find the source via 20256 * the `src` attribute and then the `<source>` elements. It will then fire `sourceset` 20257 * with the source that was found or empty string if we cannot know. If it cannot 20258 * find a source then `sourceset` will not be fired. 20259 * 20260 * @param {Html5} tech 20261 * The tech object that sourceset was setup on 20262 * 20263 * @return {boolean} 20264 * returns false if the sourceset was not fired and true otherwise. 20265 */ 20266 20267 var sourcesetLoad = function sourcesetLoad(tech) { 20268 var el = tech.el(); // if `el.src` is set, that source will be loaded. 20269 20270 if (el.hasAttribute('src')) { 20271 tech.triggerSourceset(el.src); 20272 return true; 20273 } 20274 /** 20275 * Since there isn't a src property on the media element, source elements will be used for 20276 * implementing the source selection algorithm. This happens asynchronously and 20277 * for most cases were there is more than one source we cannot tell what source will 20278 * be loaded, without re-implementing the source selection algorithm. At this time we are not 20279 * going to do that. There are three special cases that we do handle here though: 20280 * 20281 * 1. If there are no sources, do not fire `sourceset`. 20282 * 2. If there is only one `<source>` with a `src` property/attribute that is our `src` 20283 * 3. If there is more than one `<source>` but all of them have the same `src` url. 20284 * That will be our src. 20285 */ 20286 20287 20288 var sources = tech.$$('source'); 20289 var srcUrls = []; 20290 var src = ''; // if there are no sources, do not fire sourceset 20291 20292 if (!sources.length) { 20293 return false; 20294 } // only count valid/non-duplicate source elements 20295 20296 20297 for (var i = 0; i < sources.length; i++) { 20298 var url = sources[i].src; 20299 20300 if (url && srcUrls.indexOf(url) === -1) { 20301 srcUrls.push(url); 20302 } 20303 } // there were no valid sources 20304 20305 20306 if (!srcUrls.length) { 20307 return false; 20308 } // there is only one valid source element url 20309 // use that 20310 20311 20312 if (srcUrls.length === 1) { 20313 src = srcUrls[0]; 20314 } 20315 20316 tech.triggerSourceset(src); 20317 return true; 20318 }; 20319 /** 20320 * our implementation of an `innerHTML` descriptor for browsers 20321 * that do not have one. 20322 */ 20323 20324 20325 var innerHTMLDescriptorPolyfill = Object.defineProperty({}, 'innerHTML', { 20326 get: function get() { 20327 return this.cloneNode(true).innerHTML; 20328 }, 20329 set: function set(v) { 20330 // make a dummy node to use innerHTML on 20331 var dummy = document.createElement(this.nodeName.toLowerCase()); // set innerHTML to the value provided 20332 20333 dummy.innerHTML = v; // make a document fragment to hold the nodes from dummy 20334 20335 var docFrag = document.createDocumentFragment(); // copy all of the nodes created by the innerHTML on dummy 20336 // to the document fragment 20337 20338 while (dummy.childNodes.length) { 20339 docFrag.appendChild(dummy.childNodes[0]); 20340 } // remove content 20341 20342 20343 this.innerText = ''; // now we add all of that html in one by appending the 20344 // document fragment. This is how innerHTML does it. 20345 20346 window$1.Element.prototype.appendChild.call(this, docFrag); // then return the result that innerHTML's setter would 20347 20348 return this.innerHTML; 20349 } 20350 }); 20351 /** 20352 * Get a property descriptor given a list of priorities and the 20353 * property to get. 20354 */ 20355 20356 var getDescriptor = function getDescriptor(priority, prop) { 20357 var descriptor = {}; 20358 20359 for (var i = 0; i < priority.length; i++) { 20360 descriptor = Object.getOwnPropertyDescriptor(priority[i], prop); 20361 20362 if (descriptor && descriptor.set && descriptor.get) { 20363 break; 20364 } 20365 } 20366 20367 descriptor.enumerable = true; 20368 descriptor.configurable = true; 20369 return descriptor; 20370 }; 20371 20372 var getInnerHTMLDescriptor = function getInnerHTMLDescriptor(tech) { 20373 return getDescriptor([tech.el(), window$1.HTMLMediaElement.prototype, window$1.Element.prototype, innerHTMLDescriptorPolyfill], 'innerHTML'); 20374 }; 20375 /** 20376 * Patches browser internal functions so that we can tell synchronously 20377 * if a `<source>` was appended to the media element. For some reason this 20378 * causes a `sourceset` if the the media element is ready and has no source. 20379 * This happens when: 20380 * - The page has just loaded and the media element does not have a source. 20381 * - The media element was emptied of all sources, then `load()` was called. 20382 * 20383 * It does this by patching the following functions/properties when they are supported: 20384 * 20385 * - `append()` - can be used to add a `<source>` element to the media element 20386 * - `appendChild()` - can be used to add a `<source>` element to the media element 20387 * - `insertAdjacentHTML()` - can be used to add a `<source>` element to the media element 20388 * - `innerHTML` - can be used to add a `<source>` element to the media element 20389 * 20390 * @param {Html5} tech 20391 * The tech object that sourceset is being setup on. 20392 */ 20393 20394 20395 var firstSourceWatch = function firstSourceWatch(tech) { 20396 var el = tech.el(); // make sure firstSourceWatch isn't setup twice. 20397 20398 if (el.resetSourceWatch_) { 20399 return; 20400 } 20401 20402 var old = {}; 20403 var innerDescriptor = getInnerHTMLDescriptor(tech); 20404 20405 var appendWrapper = function appendWrapper(appendFn) { 20406 return function () { 20407 for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { 20408 args[_key] = arguments[_key]; 20409 } 20410 20411 var retval = appendFn.apply(el, args); 20412 sourcesetLoad(tech); 20413 return retval; 20414 }; 20415 }; 20416 20417 ['append', 'appendChild', 'insertAdjacentHTML'].forEach(function (k) { 20418 if (!el[k]) { 20419 return; 20420 } // store the old function 20421 20422 20423 old[k] = el[k]; // call the old function with a sourceset if a source 20424 // was loaded 20425 20426 el[k] = appendWrapper(old[k]); 20427 }); 20428 Object.defineProperty(el, 'innerHTML', mergeOptions(innerDescriptor, { 20429 set: appendWrapper(innerDescriptor.set) 20430 })); 20431 20432 el.resetSourceWatch_ = function () { 20433 el.resetSourceWatch_ = null; 20434 Object.keys(old).forEach(function (k) { 20435 el[k] = old[k]; 20436 }); 20437 Object.defineProperty(el, 'innerHTML', innerDescriptor); 20438 }; // on the first sourceset, we need to revert our changes 20439 20440 20441 tech.one('sourceset', el.resetSourceWatch_); 20442 }; 20443 /** 20444 * our implementation of a `src` descriptor for browsers 20445 * that do not have one. 20446 */ 20447 20448 20449 var srcDescriptorPolyfill = Object.defineProperty({}, 'src', { 20450 get: function get() { 20451 if (this.hasAttribute('src')) { 20452 return getAbsoluteURL(window$1.Element.prototype.getAttribute.call(this, 'src')); 20453 } 20454 20455 return ''; 20456 }, 20457 set: function set(v) { 20458 window$1.Element.prototype.setAttribute.call(this, 'src', v); 20459 return v; 20460 } 20461 }); 20462 20463 var getSrcDescriptor = function getSrcDescriptor(tech) { 20464 return getDescriptor([tech.el(), window$1.HTMLMediaElement.prototype, srcDescriptorPolyfill], 'src'); 20465 }; 20466 /** 20467 * setup `sourceset` handling on the `Html5` tech. This function 20468 * patches the following element properties/functions: 20469 * 20470 * - `src` - to determine when `src` is set 20471 * - `setAttribute()` - to determine when `src` is set 20472 * - `load()` - this re-triggers the source selection algorithm, and can 20473 * cause a sourceset. 20474 * 20475 * If there is no source when we are adding `sourceset` support or during a `load()` 20476 * we also patch the functions listed in `firstSourceWatch`. 20477 * 20478 * @param {Html5} tech 20479 * The tech to patch 20480 */ 20481 20482 20483 var setupSourceset = function setupSourceset(tech) { 20484 if (!tech.featuresSourceset) { 20485 return; 20486 } 20487 20488 var el = tech.el(); // make sure sourceset isn't setup twice. 20489 20490 if (el.resetSourceset_) { 20491 return; 20492 } 20493 20494 var srcDescriptor = getSrcDescriptor(tech); 20495 var oldSetAttribute = el.setAttribute; 20496 var oldLoad = el.load; 20497 Object.defineProperty(el, 'src', mergeOptions(srcDescriptor, { 20498 set: function set(v) { 20499 var retval = srcDescriptor.set.call(el, v); // we use the getter here to get the actual value set on src 20500 20501 tech.triggerSourceset(el.src); 20502 return retval; 20503 } 20504 })); 20505 20506 el.setAttribute = function (n, v) { 20507 var retval = oldSetAttribute.call(el, n, v); 20508 20509 if (/src/i.test(n)) { 20510 tech.triggerSourceset(el.src); 20511 } 20512 20513 return retval; 20514 }; 20515 20516 el.load = function () { 20517 var retval = oldLoad.call(el); // if load was called, but there was no source to fire 20518 // sourceset on. We have to watch for a source append 20519 // as that can trigger a `sourceset` when the media element 20520 // has no source 20521 20522 if (!sourcesetLoad(tech)) { 20523 tech.triggerSourceset(''); 20524 firstSourceWatch(tech); 20525 } 20526 20527 return retval; 20528 }; 20529 20530 if (el.currentSrc) { 20531 tech.triggerSourceset(el.currentSrc); 20532 } else if (!sourcesetLoad(tech)) { 20533 firstSourceWatch(tech); 20534 } 20535 20536 el.resetSourceset_ = function () { 20537 el.resetSourceset_ = null; 20538 el.load = oldLoad; 20539 el.setAttribute = oldSetAttribute; 20540 Object.defineProperty(el, 'src', srcDescriptor); 20541 20542 if (el.resetSourceWatch_) { 20543 el.resetSourceWatch_(); 20544 } 20545 }; 20546 }; 20547 20548 function _templateObject$1() { 20549 var data = _taggedTemplateLiteralLoose(["Text Tracks are being loaded from another origin but the crossorigin attribute isn't used.\n This may prevent text tracks from loading."]); 20550 20551 _templateObject$1 = function _templateObject() { 20552 return data; 20553 }; 20554 20555 return data; 20556 } 20557 /** 20558 * HTML5 Media Controller - Wrapper for HTML5 Media API 20559 * 20560 * @mixes Tech~SourceHandlerAdditions 20561 * @extends Tech 20562 */ 20563 20564 var Html5 = 20565 /*#__PURE__*/ 20566 function (_Tech) { 20567 _inheritsLoose(Html5, _Tech); 20568 20569 /** 20570 * Create an instance of this Tech. 20571 * 20572 * @param {Object} [options] 20573 * The key/value store of player options. 20574 * 20575 * @param {Component~ReadyCallback} ready 20576 * Callback function to call when the `HTML5` Tech is ready. 20577 */ 20578 function Html5(options, ready) { 20579 var _this; 20580 20581 _this = _Tech.call(this, options, ready) || this; 20582 var source = options.source; 20583 var crossoriginTracks = false; // Set the source if one is provided 20584 // 1) Check if the source is new (if not, we want to keep the original so playback isn't interrupted) 20585 // 2) Check to see if the network state of the tag was failed at init, and if so, reset the source 20586 // anyway so the error gets fired. 20587 20588 if (source && (_this.el_.currentSrc !== source.src || options.tag && options.tag.initNetworkState_ === 3)) { 20589 _this.setSource(source); 20590 } else { 20591 _this.handleLateInit_(_this.el_); 20592 } // setup sourceset after late sourceset/init 20593 20594 20595 if (options.enableSourceset) { 20596 _this.setupSourcesetHandling_(); 20597 } 20598 20599 if (_this.el_.hasChildNodes()) { 20600 var nodes = _this.el_.childNodes; 20601 var nodesLength = nodes.length; 20602 var removeNodes = []; 20603 20604 while (nodesLength--) { 20605 var node = nodes[nodesLength]; 20606 var nodeName = node.nodeName.toLowerCase(); 20607 20608 if (nodeName === 'track') { 20609 if (!_this.featuresNativeTextTracks) { 20610 // Empty video tag tracks so the built-in player doesn't use them also. 20611 // This may not be fast enough to stop HTML5 browsers from reading the tags 20612 // so we'll need to turn off any default tracks if we're manually doing 20613 // captions and subtitles. videoElement.textTracks 20614 removeNodes.push(node); 20615 } else { 20616 // store HTMLTrackElement and TextTrack to remote list 20617 _this.remoteTextTrackEls().addTrackElement_(node); 20618 20619 _this.remoteTextTracks().addTrack(node.track); 20620 20621 _this.textTracks().addTrack(node.track); 20622 20623 if (!crossoriginTracks && !_this.el_.hasAttribute('crossorigin') && isCrossOrigin(node.src)) { 20624 crossoriginTracks = true; 20625 } 20626 } 20627 } 20628 } 20629 20630 for (var i = 0; i < removeNodes.length; i++) { 20631 _this.el_.removeChild(removeNodes[i]); 20632 } 20633 } 20634 20635 _this.proxyNativeTracks_(); 20636 20637 if (_this.featuresNativeTextTracks && crossoriginTracks) { 20638 log.warn(tsml(_templateObject$1())); 20639 } // prevent iOS Safari from disabling metadata text tracks during native playback 20640 20641 20642 _this.restoreMetadataTracksInIOSNativePlayer_(); // Determine if native controls should be used 20643 // Our goal should be to get the custom controls on mobile solid everywhere 20644 // so we can remove this all together. Right now this will block custom 20645 // controls on touch enabled laptops like the Chrome Pixel 20646 20647 20648 if ((TOUCH_ENABLED || IS_IPHONE || IS_NATIVE_ANDROID) && options.nativeControlsForTouch === true) { 20649 _this.setControls(true); 20650 } // on iOS, we want to proxy `webkitbeginfullscreen` and `webkitendfullscreen` 20651 // into a `fullscreenchange` event 20652 20653 20654 _this.proxyWebkitFullscreen_(); 20655 20656 _this.triggerReady(); 20657 20658 return _this; 20659 } 20660 /** 20661 * Dispose of `HTML5` media element and remove all tracks. 20662 */ 20663 20664 20665 var _proto = Html5.prototype; 20666 20667 _proto.dispose = function dispose() { 20668 if (this.el_ && this.el_.resetSourceset_) { 20669 this.el_.resetSourceset_(); 20670 } 20671 20672 Html5.disposeMediaElement(this.el_); 20673 this.options_ = null; // tech will handle clearing of the emulated track list 20674 20675 _Tech.prototype.dispose.call(this); 20676 } 20677 /** 20678 * Modify the media element so that we can detect when 20679 * the source is changed. Fires `sourceset` just after the source has changed 20680 */ 20681 ; 20682 20683 _proto.setupSourcesetHandling_ = function setupSourcesetHandling_() { 20684 setupSourceset(this); 20685 } 20686 /** 20687 * When a captions track is enabled in the iOS Safari native player, all other 20688 * tracks are disabled (including metadata tracks), which nulls all of their 20689 * associated cue points. This will restore metadata tracks to their pre-fullscreen 20690 * state in those cases so that cue points are not needlessly lost. 20691 * 20692 * @private 20693 */ 20694 ; 20695 20696 _proto.restoreMetadataTracksInIOSNativePlayer_ = function restoreMetadataTracksInIOSNativePlayer_() { 20697 var textTracks = this.textTracks(); 20698 var metadataTracksPreFullscreenState; // captures a snapshot of every metadata track's current state 20699 20700 var takeMetadataTrackSnapshot = function takeMetadataTrackSnapshot() { 20701 metadataTracksPreFullscreenState = []; 20702 20703 for (var i = 0; i < textTracks.length; i++) { 20704 var track = textTracks[i]; 20705 20706 if (track.kind === 'metadata') { 20707 metadataTracksPreFullscreenState.push({ 20708 track: track, 20709 storedMode: track.mode 20710 }); 20711 } 20712 } 20713 }; // snapshot each metadata track's initial state, and update the snapshot 20714 // each time there is a track 'change' event 20715 20716 20717 takeMetadataTrackSnapshot(); 20718 textTracks.addEventListener('change', takeMetadataTrackSnapshot); 20719 this.on('dispose', function () { 20720 return textTracks.removeEventListener('change', takeMetadataTrackSnapshot); 20721 }); 20722 20723 var restoreTrackMode = function restoreTrackMode() { 20724 for (var i = 0; i < metadataTracksPreFullscreenState.length; i++) { 20725 var storedTrack = metadataTracksPreFullscreenState[i]; 20726 20727 if (storedTrack.track.mode === 'disabled' && storedTrack.track.mode !== storedTrack.storedMode) { 20728 storedTrack.track.mode = storedTrack.storedMode; 20729 } 20730 } // we only want this handler to be executed on the first 'change' event 20731 20732 20733 textTracks.removeEventListener('change', restoreTrackMode); 20734 }; // when we enter fullscreen playback, stop updating the snapshot and 20735 // restore all track modes to their pre-fullscreen state 20736 20737 20738 this.on('webkitbeginfullscreen', function () { 20739 textTracks.removeEventListener('change', takeMetadataTrackSnapshot); // remove the listener before adding it just in case it wasn't previously removed 20740 20741 textTracks.removeEventListener('change', restoreTrackMode); 20742 textTracks.addEventListener('change', restoreTrackMode); 20743 }); // start updating the snapshot again after leaving fullscreen 20744 20745 this.on('webkitendfullscreen', function () { 20746 // remove the listener before adding it just in case it wasn't previously removed 20747 textTracks.removeEventListener('change', takeMetadataTrackSnapshot); 20748 textTracks.addEventListener('change', takeMetadataTrackSnapshot); // remove the restoreTrackMode handler in case it wasn't triggered during fullscreen playback 20749 20750 textTracks.removeEventListener('change', restoreTrackMode); 20751 }); 20752 } 20753 /** 20754 * Attempt to force override of tracks for the given type 20755 * 20756 * @param {string} type - Track type to override, possible values include 'Audio', 20757 * 'Video', and 'Text'. 20758 * @param {boolean} override - If set to true native audio/video will be overridden, 20759 * otherwise native audio/video will potentially be used. 20760 * @private 20761 */ 20762 ; 20763 20764 _proto.overrideNative_ = function overrideNative_(type, override) { 20765 var _this2 = this; 20766 20767 // If there is no behavioral change don't add/remove listeners 20768 if (override !== this["featuresNative" + type + "Tracks"]) { 20769 return; 20770 } 20771 20772 var lowerCaseType = type.toLowerCase(); 20773 20774 if (this[lowerCaseType + "TracksListeners_"]) { 20775 Object.keys(this[lowerCaseType + "TracksListeners_"]).forEac
vendor: 9,613 bytes, lines 20775-21067
20775h(function (eventName) { 20776 var elTracks = _this2.el()[lowerCaseType + "Tracks"]; 20777 20778 elTracks.removeEventListener(eventName, _this2[lowerCaseType + "TracksListeners_"][eventName]); 20779 }); 20780 } 20781 20782 this["featuresNative" + type + "Tracks"] = !override; 20783 this[lowerCaseType + "TracksListeners_"] = null; 20784 this.proxyNativeTracksForType_(lowerCaseType); 20785 } 20786 /** 20787 * Attempt to force override of native audio tracks. 20788 * 20789 * @param {boolean} override - If set to true native audio will be overridden, 20790 * otherwise native audio will potentially be used. 20791 */ 20792 ; 20793 20794 _proto.overrideNativeAudioTracks = function overrideNativeAudioTracks(override) { 20795 this.overrideNative_('Audio', override); 20796 } 20797 /** 20798 * Attempt to force override of native video tracks. 20799 * 20800 * @param {boolean} override - If set to true native video will be overridden, 20801 * otherwise native video will potentially be used. 20802 */ 20803 ; 20804 20805 _proto.overrideNativeVideoTracks = function overrideNativeVideoTracks(override) { 20806 this.overrideNative_('Video', override); 20807 } 20808 /** 20809 * Proxy native track list events for the given type to our track 20810 * lists if the browser we are playing in supports that type of track list. 20811 * 20812 * @param {string} name - Track type; values include 'audio', 'video', and 'text' 20813 * @private 20814 */ 20815 ; 20816 20817 _proto.proxyNativeTracksForType_ = function proxyNativeTracksForType_(name) { 20818 var _this3 = this; 20819 20820 var props = NORMAL[name]; 20821 var elTracks = this.el()[props.getterName]; 20822 var techTracks = this[props.getterName](); 20823 20824 if (!this["featuresNative" + props.capitalName + "Tracks"] || !elTracks || !elTracks.addEventListener) { 20825 return; 20826 } 20827 20828 var listeners = { 20829 change: function change(e) { 20830 techTracks.trigger({ 20831 type: 'change', 20832 target: techTracks, 20833 currentTarget: techTracks, 20834 srcElement: techTracks 20835 }); 20836 }, 20837 addtrack: function addtrack(e) { 20838 techTracks.addTrack(e.track); 20839 }, 20840 removetrack: function removetrack(e) { 20841 techTracks.removeTrack(e.track); 20842 } 20843 }; 20844 20845 var removeOldTracks = function removeOldTracks() { 20846 var removeTracks = []; 20847 20848 for (var i = 0; i < techTracks.length; i++) { 20849 var found = false; 20850 20851 for (var j = 0; j < elTracks.length; j++) { 20852 if (elTracks[j] === techTracks[i]) { 20853 found = true; 20854 break; 20855 } 20856 } 20857 20858 if (!found) { 20859 removeTracks.push(techTracks[i]); 20860 } 20861 } 20862 20863 while (removeTracks.length) { 20864 techTracks.removeTrack(removeTracks.shift()); 20865 } 20866 }; 20867 20868 this[props.getterName + 'Listeners_'] = listeners; 20869 Object.keys(listeners).forEach(function (eventName) { 20870 var listener = listeners[eventName]; 20871 elTracks.addEventListener(eventName, listener); 20872 20873 _this3.on('dispose', function (e) { 20874 return elTracks.removeEventListener(eventName, listener); 20875 }); 20876 }); // Remove (native) tracks that are not used anymore 20877 20878 this.on('loadstart', removeOldTracks); 20879 this.on('dispose', function (e) { 20880 return _this3.off('loadstart', removeOldTracks); 20881 }); 20882 } 20883 /** 20884 * Proxy all native track list events to our track lists if the browser we are playing 20885 * in supports that type of track list. 20886 * 20887 * @private 20888 */ 20889 ; 20890 20891 _proto.proxyNativeTracks_ = function proxyNativeTracks_() { 20892 var _this4 = this; 20893 20894 NORMAL.names.forEach(function (name) { 20895 _this4.proxyNativeTracksForType_(name); 20896 }); 20897 } 20898 /** 20899 * Create the `Html5` Tech's DOM element. 20900 * 20901 * @return {Element} 20902 * The element that gets created. 20903 */ 20904 ; 20905 20906 _proto.createEl = function createEl$$1() { 20907 var el = this.options_.tag; // Check if this browser supports moving the element into the box. 20908 // On the iPhone video will break if you move the element, 20909 // So we have to create a brand new element. 20910 // If we ingested the player div, we do not need to move the media element. 20911 20912 if (!el || !(this.options_.playerElIngest || this.movingMediaElementInDOM)) { 20913 // If the original tag is still there, clone and remove it. 20914 if (el) { 20915 var clone = el.cloneNode(true); 20916 20917 if (el.parentNode) { 20918 el.parentNode.insertBefore(clone, el); 20919 } 20920 20921 Html5.disposeMediaElement(el); 20922 el = clone; 20923 } else { 20924 el = document.createElement('video'); // determine if native controls should be used 20925 20926 var tagAttributes = this.options_.tag && getAttributes(this.options_.tag); 20927 var attributes = mergeOptions({}, tagAttributes); 20928 20929 if (!TOUCH_ENABLED || this.options_.nativeControlsForTouch !== true) { 20930 delete attributes.controls; 20931 } 20932 20933 setAttributes(el, assign(attributes, { 20934 id: this.options_.techId, 20935 class: 'vjs-tech' 20936 })); 20937 } 20938 20939 el.playerId = this.options_.playerId; 20940 } 20941 20942 if (typeof this.options_.preload !== 'undefined') { 20943 setAttribute(el, 'preload', this.options_.preload); 20944 } // Update specific tag settings, in case they were overridden 20945 // `autoplay` has to be *last* so that `muted` and `playsinline` are present 20946 // when iOS/Safari or other browsers attempt to autoplay. 20947 20948 20949 var settingsAttrs = ['loop', 'muted', 'playsinline', 'autoplay']; 20950 20951 for (var i = 0; i < settingsAttrs.length; i++) { 20952 var attr = settingsAttrs[i]; 20953 var value = this.options_[attr]; 20954 20955 if (typeof value !== 'undefined') { 20956 if (value) { 20957 setAttribute(el, attr, attr); 20958 } else { 20959 removeAttribute(el, attr); 20960 } 20961 20962 el[attr] = value; 20963 } 20964 } 20965 20966 return el; 20967 } 20968 /** 20969 * This will be triggered if the loadstart event has already fired, before videojs was 20970 * ready. Two known examples of when this can happen are: 20971 * 1. If we're loading the playback object after it has started loading 20972 * 2. The media is already playing the (often with autoplay on) then 20973 * 20974 * This function will fire another loadstart so that videojs can catchup. 20975 * 20976 * @fires Tech#loadstart 20977 * 20978 * @return {undefined} 20979 * returns nothing. 20980 */ 20981 ; 20982 20983 _proto.handleLateInit_ = function handleLateInit_(el) { 20984 if (el.networkState === 0 || el.networkState === 3) { 20985 // The video element hasn't started loading the source yet 20986 // or didn't find a source 20987 return; 20988 } 20989 20990 if (el.readyState === 0) { 20991 // NetworkState is set synchronously BUT loadstart is fired at the 20992 // end of the current stack, usually before setInterval(fn, 0). 20993 // So at this point we know loadstart may have already fired or is 20994 // about to fire, and either way the player hasn't seen it yet. 20995 // We don't want to fire loadstart prematurely here and cause a 20996 // double loadstart so we'll wait and see if it happens between now 20997 // and the next loop, and fire it if not. 20998 // HOWEVER, we also want to make sure it fires before loadedmetadata 20999 // which could also happen between now and the next loop, so we'll 21000 // watch for that also. 21001 var loadstartFired = false; 21002 21003 var setLoadstartFired = function setLoadstartFired() { 21004 loadstartFired = true; 21005 }; 21006 21007 this.on('loadstart', setLoadstartFired); 21008 21009 var triggerLoadstart = function triggerLoadstart() { 21010 // We did miss the original loadstart. Make sure the player 21011 // sees loadstart before loadedmetadata 21012 if (!loadstartFired) { 21013 this.trigger('loadstart'); 21014 } 21015 }; 21016 21017 this.on('loadedmetadata', triggerLoadstart); 21018 this.ready(function () { 21019 this.off('loadstart', setLoadstartFired); 21020 this.off('loadedmetadata', triggerLoadstart); 21021 21022 if (!loadstartFired) { 21023 // We did miss the original native loadstart. Fire it now. 21024 this.trigger('loadstart'); 21025 } 21026 }); 21027 return; 21028 } // From here on we know that loadstart already fired and we missed it. 21029 // The other readyState events aren't as much of a problem if we double 21030 // them, so not going to go to as much trouble as loadstart to prevent 21031 // that unless we find reason to. 21032 21033 21034 var eventsToTrigger = ['loadstart']; // loadedmetadata: newly equal to HAVE_METADATA (1) or greater 21035 21036 eventsToTrigger.push('loadedmetadata'); // loadeddata: newly increased to HAVE_CURRENT_DATA (2) or greater 21037 21038 if (el.readyState >= 2) { 21039 eventsToTrigger.push('loadeddata'); 21040 } // canplay: newly increased to HAVE_FUTURE_DATA (3) or greater 21041 21042 21043 if (el.readyState >= 3) { 21044 eventsToTrigger.push('canplay'); 21045 } // canplaythrough: newly equal to HAVE_ENOUGH_DATA (4) 21046 21047 21048 if (el.readyState >= 4) { 21049 eventsToTrigger.push('canplaythrough'); 21050 } // We still need to give the player time to add event listeners 21051 21052 21053 this.ready(function () { 21054 eventsToTrigger.forEach(function (type) { 21055 this.trigger(type); 21056 }, this); 21057 }); 21058 } 21059 /** 21060 * Set current time for the `HTML5` tech. 21061 * 21062 * @param {number} seconds 21063 * Set the current time of the media to this. 21064 */ 21065 ; 21066 21067 _proto.setCurrentTime = function setCurrentTime(seconds) {
vendor: 11,918 bytes, lines 21068-21458
21068 try { 21069 this.el_.currentTime = seconds; 21070 } catch (e) { 21071 log(e, 'Video is not ready. (Video.js)'); // this.warning(VideoJS.warnings.videoNotReady); 21072 } 21073 } 21074 /** 21075 * Get the current duration of the HTML5 media element. 21076 * 21077 * @return {number} 21078 * The duration of the media or 0 if there is no duration. 21079 */ 21080 ; 21081 21082 _proto.duration = function duration() { 21083 var _this5 = this; 21084 21085 // Android Chrome will report duration as Infinity for VOD HLS until after 21086 // playback has started, which triggers the live display erroneously. 21087 // Return NaN if playback has not started and trigger a durationupdate once 21088 // the duration can be reliably known. 21089 if (this.el_.duration === Infinity && IS_ANDROID && IS_CHROME && this.el_.currentTime === 0) { 21090 // Wait for the first `timeupdate` with currentTime > 0 - there may be 21091 // several with 0 21092 var checkProgress = function checkProgress() { 21093 if (_this5.el_.currentTime > 0) { 21094 // Trigger durationchange for genuinely live video 21095 if (_this5.el_.duration === Infinity) { 21096 _this5.trigger('durationchange'); 21097 } 21098 21099 _this5.off('timeupdate', checkProgress); 21100 } 21101 }; 21102 21103 this.on('timeupdate', checkProgress); 21104 return NaN; 21105 } 21106 21107 return this.el_.duration || NaN; 21108 } 21109 /** 21110 * Get the current width of the HTML5 media element. 21111 * 21112 * @return {number} 21113 * The width of the HTML5 media element. 21114 */ 21115 ; 21116 21117 _proto.width = function width() { 21118 return this.el_.offsetWidth; 21119 } 21120 /** 21121 * Get the current height of the HTML5 media element. 21122 * 21123 * @return {number} 21124 * The height of the HTML5 media element. 21125 */ 21126 ; 21127 21128 _proto.height = function height() { 21129 return this.el_.offsetHeight; 21130 } 21131 /** 21132 * Proxy iOS `webkitbeginfullscreen` and `webkitendfullscreen` into 21133 * `fullscreenchange` event. 21134 * 21135 * @private 21136 * @fires fullscreenchange 21137 * @listens webkitendfullscreen 21138 * @listens webkitbeginfullscreen 21139 * @listens webkitbeginfullscreen 21140 */ 21141 ; 21142 21143 _proto.proxyWebkitFullscreen_ = function proxyWebkitFullscreen_() { 21144 var _this6 = this; 21145 21146 if (!('webkitDisplayingFullscreen' in this.el_)) { 21147 return; 21148 } 21149 21150 var endFn = function endFn() { 21151 this.trigger('fullscreenchange', { 21152 isFullscreen: false 21153 }); 21154 }; 21155 21156 var beginFn = function beginFn() { 21157 if ('webkitPresentationMode' in this.el_ && this.el_.webkitPresentationMode !== 'picture-in-picture') { 21158 this.one('webkitendfullscreen', endFn); 21159 this.trigger('fullscreenchange', { 21160 isFullscreen: true 21161 }); 21162 } 21163 }; 21164 21165 this.on('webkitbeginfullscreen', beginFn); 21166 this.on('dispose', function () { 21167 _this6.off('webkitbeginfullscreen', beginFn); 21168 21169 _this6.off('webkitendfullscreen', endFn); 21170 }); 21171 } 21172 /** 21173 * Check if fullscreen is supported on the current playback device. 21174 * 21175 * @return {boolean} 21176 * - True if fullscreen is supported. 21177 * - False if fullscreen is not supported. 21178 */ 21179 ; 21180 21181 _proto.supportsFullScreen = function supportsFullScreen() { 21182 if (typeof this.el_.webkitEnterFullScreen === 'function') { 21183 var userAgent = window$1.navigator && window$1.navigator.userAgent || ''; // Seems to be broken in Chromium/Chrome && Safari in Leopard 21184 21185 if (/Android/.test(userAgent) || !/Chrome|Mac OS X 10.5/.test(userAgent)) { 21186 return true; 21187 } 21188 } 21189 21190 return false; 21191 } 21192 /** 21193 * Request that the `HTML5` Tech enter fullscreen. 21194 */ 21195 ; 21196 21197 _proto.enterFullScreen = function enterFullScreen() { 21198 var video = this.el_; 21199 21200 if (video.paused && video.networkState <= video.HAVE_METADATA) { 21201 // attempt to prime the video element for programmatic access 21202 // this isn't necessary on the desktop but shouldn't hurt 21203 this.el_.play(); // playing and pausing synchronously during the transition to fullscreen 21204 // can get iOS ~6.1 devices into a play/pause loop 21205 21206 this.setTimeout(function () { 21207 video.pause(); 21208 video.webkitEnterFullScreen(); 21209 }, 0); 21210 } else { 21211 video.webkitEnterFullScreen(); 21212 } 21213 } 21214 /** 21215 * Request that the `HTML5` Tech exit fullscreen. 21216 */ 21217 ; 21218 21219 _proto.exitFullScreen = function exitFullScreen() { 21220 this.el_.webkitExitFullScreen(); 21221 } 21222 /** 21223 * A getter/setter for the `Html5` Tech's source object. 21224 * > Note: Please use {@link Html5#setSource} 21225 * 21226 * @param {Tech~SourceObject} [src] 21227 * The source object you want to set on the `HTML5` techs element. 21228 * 21229 * @return {Tech~SourceObject|undefined} 21230 * - The current source object when a source is not passed in. 21231 * - undefined when setting 21232 * 21233 * @deprecated Since version 5. 21234 */ 21235 ; 21236 21237 _proto.src = function src(_src) { 21238 if (_src === undefined) { 21239 return this.el_.src; 21240 } // Setting src through `src` instead of `setSrc` will be deprecated 21241 21242 21243 this.setSrc(_src); 21244 } 21245 /** 21246 * Reset the tech by removing all sources and then calling 21247 * {@link Html5.resetMediaElement}. 21248 */ 21249 ; 21250 21251 _proto.reset = function reset() { 21252 Html5.resetMediaElement(this.el_); 21253 } 21254 /** 21255 * Get the current source on the `HTML5` Tech. Falls back to returning the source from 21256 * the HTML5 media element. 21257 * 21258 * @return {Tech~SourceObject} 21259 * The current source object from the HTML5 tech. With a fallback to the 21260 * elements source. 21261 */ 21262 ; 21263 21264 _proto.currentSrc = function currentSrc() { 21265 if (this.currentSource_) { 21266 return this.currentSource_.src; 21267 } 21268 21269 return this.el_.currentSrc; 21270 } 21271 /** 21272 * Set controls attribute for the HTML5 media Element. 21273 * 21274 * @param {string} val 21275 * Value to set the controls attribute to 21276 */ 21277 ; 21278 21279 _proto.setControls = function setControls(val) { 21280 this.el_.controls = !!val; 21281 } 21282 /** 21283 * Create and returns a remote {@link TextTrack} object. 21284 * 21285 * @param {string} kind 21286 * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata) 21287 * 21288 * @param {string} [label] 21289 * Label to identify the text track 21290 * 21291 * @param {string} [language] 21292 * Two letter language abbreviation 21293 * 21294 * @return {TextTrack} 21295 * The TextTrack that gets created. 21296 */ 21297 ; 21298 21299 _proto.addTextTrack = function addTextTrack(kind, label, language) { 21300 if (!this.featuresNativeTextTracks) { 21301 return _Tech.prototype.addTextTrack.call(this, kind, label, language); 21302 } 21303 21304 return this.el_.addTextTrack(kind, label, language); 21305 } 21306 /** 21307 * Creates either native TextTrack or an emulated TextTrack depending 21308 * on the value of `featuresNativeTextTracks` 21309 * 21310 * @param {Object} options 21311 * The object should contain the options to initialize the TextTrack with. 21312 * 21313 * @param {string} [options.kind] 21314 * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata). 21315 * 21316 * @param {string} [options.label] 21317 * Label to identify the text track 21318 * 21319 * @param {string} [options.language] 21320 * Two letter language abbreviation. 21321 * 21322 * @param {boolean} [options.default] 21323 * Default this track to on. 21324 * 21325 * @param {string} [options.id] 21326 * The internal id to assign this track. 21327 * 21328 * @param {string} [options.src] 21329 * A source url for the track. 21330 * 21331 * @return {HTMLTrackElement} 21332 * The track element that gets created. 21333 */ 21334 ; 21335 21336 _proto.createRemoteTextTrack = function createRemoteTextTrack(options) { 21337 if (!this.featuresNativeTextTracks) { 21338 return _Tech.prototype.createRemoteTextTrack.call(this, options); 21339 } 21340 21341 var htmlTrackElement = document.createElement('track'); 21342 21343 if (options.kind) { 21344 htmlTrackElement.kind = options.kind; 21345 } 21346 21347 if (options.label) { 21348 htmlTrackElement.label = options.label; 21349 } 21350 21351 if (options.language || options.srclang) { 21352 htmlTrackElement.srclang = options.language || options.srclang; 21353 } 21354 21355 if (options.default) { 21356 htmlTrackElement.default = options.default; 21357 } 21358 21359 if (options.id) { 21360 htmlTrackElement.id = options.id; 21361 } 21362 21363 if (options.src) { 21364 htmlTrackElement.src = options.src; 21365 } 21366 21367 return htmlTrackElement; 21368 } 21369 /** 21370 * Creates a remote text track object and returns an html track element. 21371 * 21372 * @param {Object} options The object should contain values for 21373 * kind, language, label, and src (location of the WebVTT file) 21374 * @param {boolean} [manualCleanup=true] if set to false, the TextTrack will be 21375 * automatically removed from the video element whenever the source changes 21376 * @return {HTMLTrackElement} An Html Track Element. 21377 * This can be an emulated {@link HTMLTrackElement} or a native one. 21378 * @deprecated The default value of the "manualCleanup" parameter will default 21379 * to "false" in upcoming versions of Video.js 21380 */ 21381 ; 21382 21383 _proto.addRemoteTextTrack = function addRemoteTextTrack(options, manualCleanup) { 21384 var htmlTrackElement = _Tech.prototype.addRemoteTextTrack.call(this, options, manualCleanup); 21385 21386 if (this.featuresNativeTextTracks) { 21387 this.el().appendChild(htmlTrackElement); 21388 } 21389 21390 return htmlTrackElement; 21391 } 21392 /** 21393 * Remove remote `TextTrack` from `TextTrackList` object 21394 * 21395 * @param {TextTrack} track 21396 * `TextTrack` object to remove 21397 */ 21398 ; 21399 21400 _proto.removeRemoteTextTrack = function removeRemoteTextTrack(track) { 21401 _Tech.prototype.removeRemoteTextTrack.call(this, track); 21402 21403 if (this.featuresNativeTextTracks) { 21404 var tracks = this.$$('track'); 21405 var i = tracks.length; 21406 21407 while (i--) { 21408 if (track === tracks[i] || track === tracks[i].track) { 21409 this.el().removeChild(tracks[i]); 21410 } 21411 } 21412 } 21413 } 21414 /** 21415 * Gets available media playback quality metrics as specified by the W3C's Media 21416 * Playback Quality API. 21417 * 21418 * @see [Spec]{@link https://wicg.github.io/media-playback-quality} 21419 * 21420 * @return {Object} 21421 * An object with supported media playback quality metrics 21422 */ 21423 ; 21424 21425 _proto.getVideoPlaybackQuality = function getVideoPlaybackQuality() { 21426 if (typeof this.el().getVideoPlaybackQuality === 'function') { 21427 return this.el().getVideoPlaybackQuality(); 21428 } 21429 21430 var videoPlaybackQuality = {}; 21431 21432 if (typeof this.el().webkitDroppedFrameCount !== 'undefined' && typeof this.el().webkitDecodedFrameCount !== 'undefined') { 21433 videoPlaybackQuality.droppedVideoFrames = this.el().webkitDroppedFrameCount; 21434 videoPlaybackQuality.totalVideoFrames = this.el().webkitDecodedFrameCount; 21435 } 21436 21437 if (window$1.performance && typeof window$1.performance.now === 'function') { 21438 videoPlaybackQuality.creationTime = window$1.performance.now(); 21439 } else if (window$1.performance && window$1.performance.timing && typeof window$1.performance.timing.navigationStart === 'number') { 21440 videoPlaybackQuality.creationTime = window$1.Date.now() - window$1.performance.timing.navigationStart; 21441 } 21442 21443 return videoPlaybackQuality; 21444 }; 21445 21446 return Html5; 21447 }(Tech); 21448 /* HTML5 Support Testing ---------------------------------------------------- */ 21449 21450 21451 if (isReal()) { 21452 /** 21453 * Element for testing browser HTML5 media capabilities 21454 * 21455 * @type {Element} 21456 * @constant 21457 * @private 21458 */
vendor: 4,434 bytes, lines 21459-21619
21459 Html5.TEST_VID = document.createElement('video'); 21460 var track = document.createElement('track'); 21461 track.kind = 'captions'; 21462 track.srclang = 'en'; 21463 track.label = 'English'; 21464 Html5.TEST_VID.appendChild(track); 21465 } 21466 /** 21467 * Check if HTML5 media is supported by this browser/device. 21468 * 21469 * @return {boolean} 21470 * - True if HTML5 media is supported. 21471 * - False if HTML5 media is not supported. 21472 */ 21473 21474 21475 Html5.isSupported = function () { 21476 // IE with no Media Player is a LIAR! (#984) 21477 try { 21478 Html5.TEST_VID.volume = 0.5; 21479 } catch (e) { 21480 return false; 21481 } 21482 21483 return !!(Html5.TEST_VID && Html5.TEST_VID.canPlayType); 21484 }; 21485 /** 21486 * Check if the tech can support the given type 21487 * 21488 * @param {string} type 21489 * The mimetype to check 21490 * @return {string} 'probably', 'maybe', or '' (empty string) 21491 */ 21492 21493 21494 Html5.canPlayType = function (type) { 21495 return Html5.TEST_VID.canPlayType(type); 21496 }; 21497 /** 21498 * Check if the tech can support the given source 21499 * 21500 * @param {Object} srcObj 21501 * The source object 21502 * @param {Object} options 21503 * The options passed to the tech 21504 * @return {string} 'probably', 'maybe', or '' (empty string) 21505 */ 21506 21507 21508 Html5.canPlaySource = function (srcObj, options) { 21509 return Html5.canPlayType(srcObj.type); 21510 }; 21511 /** 21512 * Check if the volume can be changed in this browser/device. 21513 * Volume cannot be changed in a lot of mobile devices. 21514 * Specifically, it can't be changed from 1 on iOS. 21515 * 21516 * @return {boolean} 21517 * - True if volume can be controlled 21518 * - False otherwise 21519 */ 21520 21521 21522 Html5.canControlVolume = function () { 21523 // IE will error if Windows Media Player not installed #3315 21524 try { 21525 var volume = Html5.TEST_VID.volume; 21526 Html5.TEST_VID.volume = volume / 2 + 0.1; 21527 return volume !== Html5.TEST_VID.volume; 21528 } catch (e) { 21529 return false; 21530 } 21531 }; 21532 /** 21533 * Check if the volume can be muted in this browser/device. 21534 * Some devices, e.g. iOS, don't allow changing volume 21535 * but permits muting/unmuting. 21536 * 21537 * @return {bolean} 21538 * - True if volume can be muted 21539 * - False otherwise 21540 */ 21541 21542 21543 Html5.canMuteVolume = function () { 21544 try { 21545 var muted = Html5.TEST_VID.muted; // in some versions of iOS muted property doesn't always 21546 // work, so we want to set both property and attribute 21547 21548 Html5.TEST_VID.muted = !muted; 21549 21550 if (Html5.TEST_VID.muted) { 21551 setAttribute(Html5.TEST_VID, 'muted', 'muted'); 21552 } else { 21553 removeAttribute(Html5.TEST_VID, 'muted', 'muted'); 21554 } 21555 21556 return muted !== Html5.TEST_VID.muted; 21557 } catch (e) { 21558 return false; 21559 } 21560 }; 21561 /** 21562 * Check if the playback rate can be changed in this browser/device. 21563 * 21564 * @return {boolean} 21565 * - True if playback rate can be controlled 21566 * - False otherwise 21567 */ 21568 21569 21570 Html5.canControlPlaybackRate = function () { 21571 // Playback rate API is implemented in Android Chrome, but doesn't do anything 21572 // https://github.com/videojs/video.js/issues/3180 21573 if (IS_ANDROID && IS_CHROME && CHROME_VERSION < 58) { 21574 return false; 21575 } // IE will error if Windows Media Player not installed #3315 21576 21577 21578 try { 21579 var playbackRate = Html5.TEST_VID.playbackRate; 21580 Html5.TEST_VID.playbackRate = playbackRate / 2 + 0.1; 21581 return playbackRate !== Html5.TEST_VID.playbackRate; 21582 } catch (e) { 21583 return false; 21584 } 21585 }; 21586 /** 21587 * Check if we can override a video/audio elements attributes, with 21588 * Object.defineProperty. 21589 * 21590 * @return {boolean} 21591 * - True if builtin attributes can be overridden 21592 * - False otherwise 21593 */ 21594 21595 21596 Html5.canOverrideAttributes = function () { 21597 // if we cannot overwrite the src/innerHTML property, there is no support 21598 // iOS 7 safari for instance cannot do this. 21599 try { 21600 var noop = function noop() {}; 21601 21602 Object.defineProperty(document.createElement('video'), 'src', { 21603 get: noop, 21604 set: noop 21605 }); 21606 Object.defineProperty(document.createElement('audio'), 'src', { 21607 get: noop, 21608 set: noop 21609 }); 21610 Object.defineProperty(document.createElement('video'), 'innerHTML', { 21611 get: noop, 21612 set: noop 21613 }); 21614 Object.defineProperty(document.createElement('audio'), 'innerHTML', { 21615 get: noop, 21616 set: noop 21617 }); 21618 } catch (e) { 21619 return false;
21620 } 21621 21622 return true; 21623 }; 21624 /** 21625 * Check to see if native `TextTrack`s are supported by this browser/device. 21626 * 21627 * @return {boolean} 21628 * - True if native `TextTrack`s are supported. 21629 * - False otherwise 21630 */ 21631 21632 21633 Html5.supportsNativeTextTracks = function () { 21634 return IS_ANY_SAFARI || IS_IOS && IS_CHROME; 21635 }; 21636 /** 21637 * Check to see if native `VideoTrack`s are supported by this browser/device 21638 * 21639 * @return {boolean} 21640 * - True if native `VideoTrack`s are supported. 21641 * - False otherwise 21642 */ 21643 21644 21645 Html5.supportsNativeVideoTracks = function () { 21646 return !!(Html5.TEST_VID && Html5.TEST_VID.videoTracks); 21647 }; 21648 /** 21649 * Check to see if native `AudioTrack`s are supported by this browser/device 21650 * 21651 * @return {boolean} 21652 * - True if native `AudioTrack`s are supported. 21653 * - False otherwise 21654 */ 21655 21656 21657 Html5.supportsNativeAudioTracks = function () { 21658 return !!(Html5.TEST_VID && Html5.TEST_VID.audioTracks); 21659 }; 21660 /** 21661 * An array of events available on the Html5 tech. 21662 * 21663 * @private 21664 * @type {Array} 21665 */ 21666 21667 21668 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']; 21669 /** 21670 * Boolean indicating whether the `Tech` supports volume control. 21671 * 21672 * @type {boolean} 21673 * @default {@link Html5.canControlVolume} 21674 */ 21675 21676 Html5.prototype.featuresVolumeControl = Html5.canControlVolume(); 21677 /** 21678 * Boolean indicating whether the `Tech` supports muting volume. 21679 * 21680 * @type {bolean} 21681 * @default {@link Html5.canMuteVolume} 21682 */ 21683 21684 Html5.prototype.featuresMuteControl = Html5.canMuteVolume(); 21685 /** 21686 * Boolean indicating whether the `Tech` supports changing the speed at which the media 21687 * plays. Examples: 21688 * - Set player to play 2x (twice) as fast 21689 * - Set player to play 0.5x (half) as fast 21690 * 21691 * @type {boolean} 21692 * @default {@link Html5.canControlPlaybackRate} 21693 */ 21694 21695 Html5.prototype.featuresPlaybackRate = Html5.canControlPlaybackRate(); 21696 /** 21697 * Boolean indicating whether the `Tech` supports the `sourceset` event. 21698 * 21699 * @type {boolean} 21700 * @default 21701 */ 21702 21703 Html5.prototype.featuresSourceset = Html5.canOverrideAttributes(); 21704 /** 21705 * Boolean indicating whether the `HTML5` tech currently supports the media element 21706 * moving in the DOM. iOS breaks if you move the media element, so this is set this to 21707 * false there. Everywhere else this should be true. 21708 * 21709 * @type {boolean} 21710 * @default 21711 */ 21712 21713 Html5.prototype.movingMediaElementInDOM = !IS_IOS; // TODO: Previous comment: No longer appears to be used. Can probably be removed. 21714 // Is this true? 21715 21716 /** 21717 * Boolean indicating whether the `HTML5` tech currently supports automatic media resize 21718 * when going into fullscreen. 21719 * 21720 * @type {boolean} 21721 * @default 21722 */ 21723 21724 Html5.prototype.featuresFullscreenResize = true; 21725 /** 21726 * Boolean indicating whether the `HTML5` tech currently supports the progress event. 21727 * If this is false, manual `progress` events will be triggered instead. 21728 * 21729 * @type {boolean} 21730 * @default 21731 */ 21732 21733 Html5.prototype.featuresProgressEvents = true; 21734 /** 21735 * Boolean indicating whether the `HTML5` tech currently supports the timeupdate event. 21736 * If this is false, manual `timeupdate` events will be triggered instead. 21737 * 21738 * @default 21739 */ 21740 21741 Html5.prototype.featuresTimeupdateEvents = true; 21742 /** 21743 * Boolean indicating whether the `HTML5` tech currently supports native `TextTrack`s. 21744 * 21745 * @type {boolean} 21746 * @default {@link Html5.supportsNativeTextTracks} 21747 */ 21748 21749 Html5.prototype.featuresNativeTextTracks = Html5.supportsNativeTextTracks(); 21750 /** 21751 * Boolean indicating whether the `HTML5` tech currently supports native `VideoTrack`s. 21752 * 21753 * @type {boolean} 21754 * @default {@link Html5.supportsNativeVideoTracks} 21755 */ 21756 21757 Html5.prototype.featuresNativeVideoTracks = Html5.supportsNativeVideoTracks(); 21758 /** 21759 * Boolean indicating whether the `HTML5` tech currently supports native `AudioTrack`s. 21760 * 21761 * @type {boolean} 21762 * @default {@link Html5.supportsNativeAudioTracks} 21763 */ 21764 21765 Html5.prototype.featuresNativeAudioTracks = Html5.supportsNativeAudioTracks(); // HTML5 Feature detection and Device Fixes --------------------------------- // 21766
vendor: 14,581 bytes, lines 21767-22157
21767 var canPlayType = Html5.TEST_VID && Html5.TEST_VID.constructor.prototype.canPlayType; 21768 var mpegurlRE = /^application\/(?:x-|vnd\.apple\.)mpegurl/i; 21769 21770 Html5.patchCanPlayType = function () { 21771 // Android 4.0 and above can play HLS to some extent but it reports being unable to do so 21772 // Firefox and Chrome report correctly 21773 if (ANDROID_VERSION >= 4.0 && !IS_FIREFOX && !IS_CHROME) { 21774 Html5.TEST_VID.constructor.prototype.canPlayType = function (type) { 21775 if (type && mpegurlRE.test(type)) { 21776 return 'maybe'; 21777 } 21778 21779 return canPlayType.call(this, type); 21780 }; 21781 } 21782 }; 21783 21784 Html5.unpatchCanPlayType = function () { 21785 var r = Html5.TEST_VID.constructor.prototype.canPlayType; 21786 Html5.TEST_VID.constructor.prototype.canPlayType = canPlayType; 21787 return r; 21788 }; // by default, patch the media element 21789 21790 21791 Html5.patchCanPlayType(); 21792 21793 Html5.disposeMediaElement = function (el) { 21794 if (!el) { 21795 return; 21796 } 21797 21798 if (el.parentNode) { 21799 el.parentNode.removeChild(el); 21800 } // remove any child track or source nodes to prevent their loading 21801 21802 21803 while (el.hasChildNodes()) { 21804 el.removeChild(el.firstChild); 21805 } // remove any src reference. not setting `src=''` because that causes a warning 21806 // in firefox 21807 21808 21809 el.removeAttribute('src'); // force the media element to update its loading state by calling load() 21810 // however IE on Windows 7N has a bug that throws an error so need a try/catch (#793) 21811 21812 if (typeof el.load === 'function') { 21813 // wrapping in an iife so it's not deoptimized (#1060#discussion_r10324473) 21814 (function () { 21815 try { 21816 el.load(); 21817 } catch (e) {// not supported 21818 } 21819 })(); 21820 } 21821 }; 21822 21823 Html5.resetMediaElement = function (el) { 21824 if (!el) { 21825 return; 21826 } 21827 21828 var sources = el.querySelectorAll('source'); 21829 var i = sources.length; 21830 21831 while (i--) { 21832 el.removeChild(sources[i]); 21833 } // remove any src reference. 21834 // not setting `src=''` because that throws an error 21835 21836 21837 el.removeAttribute('src'); 21838 21839 if (typeof el.load === 'function') { 21840 // wrapping in an iife so it's not deoptimized (#1060#discussion_r10324473) 21841 (function () { 21842 try { 21843 el.load(); 21844 } catch (e) {// satisfy linter 21845 } 21846 })(); 21847 } 21848 }; 21849 /* Native HTML5 element property wrapping ----------------------------------- */ 21850 // Wrap native boolean attributes with getters that check both property and attribute 21851 // The list is as followed: 21852 // muted, defaultMuted, autoplay, controls, loop, playsinline 21853 21854 21855 [ 21856 /** 21857 * Get the value of `muted` from the media element. `muted` indicates 21858 * that the volume for the media should be set to silent. This does not actually change 21859 * the `volume` attribute. 21860 * 21861 * @method Html5#muted 21862 * @return {boolean} 21863 * - True if the value of `volume` should be ignored and the audio set to silent. 21864 * - False if the value of `volume` should be used. 21865 * 21866 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-muted} 21867 */ 21868 'muted', 21869 /** 21870 * Get the value of `defaultMuted` from the media element. `defaultMuted` indicates 21871 * whether the media should start muted or not. Only changes the default state of the 21872 * media. `muted` and `defaultMuted` can have different values. {@link Html5#muted} indicates the 21873 * current state. 21874 * 21875 * @method Html5#defaultMuted 21876 * @return {boolean} 21877 * - The value of `defaultMuted` from the media element. 21878 * - True indicates that the media should start muted. 21879 * - False indicates that the media should not start muted 21880 * 21881 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultmuted} 21882 */ 21883 'defaultMuted', 21884 /** 21885 * Get the value of `autoplay` from the media element. `autoplay` indicates 21886 * that the media should start to play as soon as the page is ready. 21887 * 21888 * @method Html5#autoplay 21889 * @return {boolean} 21890 * - The value of `autoplay` from the media element. 21891 * - True indicates that the media should start as soon as the page loads. 21892 * - False indicates that the media should not start as soon as the page loads. 21893 * 21894 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-autoplay} 21895 */ 21896 'autoplay', 21897 /** 21898 * Get the value of `controls` from the media element. `controls` indicates 21899 * whether the native media controls should be shown or hidden. 21900 * 21901 * @method Html5#controls 21902 * @return {boolean} 21903 * - The value of `controls` from the media element. 21904 * - True indicates that native controls should be showing. 21905 * - False indicates that native controls should be hidden. 21906 * 21907 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-controls} 21908 */ 21909 'controls', 21910 /** 21911 * Get the value of `loop` from the media element. `loop` indicates 21912 * that the media should return to the start of the media and continue playing once 21913 * it reaches the end. 21914 * 21915 * @method Html5#loop 21916 * @return {boolean} 21917 * - The value of `loop` from the media element. 21918 * - True indicates that playback should seek back to start once 21919 * the end of a media is reached. 21920 * - False indicates that playback should not loop back to the start when the 21921 * end of the media is reached. 21922 * 21923 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-loop} 21924 */ 21925 'loop', 21926 /** 21927 * Get the value of `playsinline` from the media element. `playsinline` indicates 21928 * to the browser that non-fullscreen playback is preferred when fullscreen 21929 * playback is the native default, such as in iOS Safari. 21930 * 21931 * @method Html5#playsinline 21932 * @return {boolean} 21933 * - The value of `playsinline` from the media element. 21934 * - True indicates that the media should play inline. 21935 * - False indicates that the media should not play inline. 21936 * 21937 * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline} 21938 */ 21939 'playsinline'].forEach(function (prop) { 21940 Html5.prototype[prop] = function () { 21941 return this.el_[prop] || this.el_.hasAttribute(prop); 21942 }; 21943 }); // Wrap native boolean attributes with setters that set both property and attribute 21944 // The list is as followed: 21945 // setMuted, setDefaultMuted, setAutoplay, setLoop, setPlaysinline 21946 // setControls is special-cased above 21947 21948 [ 21949 /** 21950 * Set the value of `muted` on the media element. `muted` indicates that the current 21951 * audio level should be silent. 21952 * 21953 * @method Html5#setMuted 21954 * @param {boolean} muted 21955 * - True if the audio should be set to silent 21956 * - False otherwise 21957 * 21958 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-muted} 21959 */ 21960 'muted', 21961 /** 21962 * Set the value of `defaultMuted` on the media element. `defaultMuted` indicates that the current 21963 * audio level should be silent, but will only effect the muted level on intial playback.. 21964 * 21965 * @method Html5.prototype.setDefaultMuted 21966 * @param {boolean} defaultMuted 21967 * - True if the audio should be set to silent 21968 * - False otherwise 21969 * 21970 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultmuted} 21971 */ 21972 'defaultMuted', 21973 /** 21974 * Set the value of `autoplay` on the media element. `autoplay` indicates 21975 * that the media should start to play as soon as the page is ready. 21976 * 21977 * @method Html5#setAutoplay 21978 * @param {boolean} autoplay 21979 * - True indicates that the media should start as soon as the page loads. 21980 * - False indicates that the media should not start as soon as the page loads. 21981 * 21982 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-autoplay} 21983 */ 21984 'autoplay', 21985 /** 21986 * Set the value of `loop` on the media element. `loop` indicates 21987 * that the media should return to the start of the media and continue playing once 21988 * it reaches the end. 21989 * 21990 * @method Html5#setLoop 21991 * @param {boolean} loop 21992 * - True indicates that playback should seek back to start once 21993 * the end of a media is reached. 21994 * - False indicates that playback should not loop back to the start when the 21995 * end of the media is reached. 21996 * 21997 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-loop} 21998 */ 21999 'loop', 22000 /** 22001 * Set the value of `playsinline` from the media element. `playsinline` indicates 22002 * to the browser that non-fullscreen playback is preferred when fullscreen 22003 * playback is the native default, such as in iOS Safari. 22004 * 22005 * @method Html5#setPlaysinline 22006 * @param {boolean} playsinline 22007 * - True indicates that the media should play inline. 22008 * - False indicates that the media should not play inline. 22009 * 22010 * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline} 22011 */ 22012 'playsinline'].forEach(function (prop) { 22013 Html5.prototype['set' + toTitleCase(prop)] = function (v) { 22014 this.el_[prop] = v; 22015 22016 if (v) { 22017 this.el_.setAttribute(prop, prop); 22018 } else { 22019 this.el_.removeAttribute(prop); 22020 } 22021 }; 22022 }); // Wrap native properties with a getter 22023 // The list is as followed 22024 // paused, currentTime, buffered, volume, poster, preload, error, seeking 22025 // seekable, ended, playbackRate, defaultPlaybackRate, played, networkState 22026 // readyState, videoWidth, videoHeight 22027 22028 [ 22029 /** 22030 * Get the value of `paused` from the media element. `paused` indicates whether the media element 22031 * is currently paused or not. 22032 * 22033 * @method Html5#paused 22034 * @return {boolean} 22035 * The value of `paused` from the media element. 22036 * 22037 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-paused} 22038 */ 22039 'paused', 22040 /** 22041 * Get the value of `currentTime` from the media element. `currentTime` indicates 22042 * the current second that the media is at in playback. 22043 * 22044 * @method Html5#currentTime 22045 * @return {number} 22046 * The value of `currentTime` from the media element. 22047 * 22048 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-currenttime} 22049 */ 22050 'currentTime', 22051 /** 22052 * Get the value of `buffered` from the media element. `buffered` is a `TimeRange` 22053 * object that represents the parts of the media that are already downloaded and 22054 * available for playback. 22055 * 22056 * @method Html5#buffered 22057 * @return {TimeRange} 22058 * The value of `buffered` from the media element. 22059 * 22060 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-buffered} 22061 */ 22062 'buffered', 22063 /** 22064 * Get the value of `volume` from the media element. `volume` indicates 22065 * the current playback volume of audio for a media. `volume` will be a value from 0 22066 * (silent) to 1 (loudest and default). 22067 * 22068 * @method Html5#volume 22069 * @return {number} 22070 * The value of `volume` from the media element. Value will be between 0-1. 22071 * 22072 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-a-volume} 22073 */ 22074 'volume', 22075 /** 22076 * Get the value of `poster` from the media element. `poster` indicates 22077 * that the url of an image file that can/will be shown when no media data is available. 22078 * 22079 * @method Html5#poster 22080 * @return {string} 22081 * The value of `poster` from the media element. Value will be a url to an 22082 * image. 22083 * 22084 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-video-poster} 22085 */ 22086 'poster', 22087 /** 22088 * Get the value of `preload` from the media element. `preload` indicates 22089 * what should download before the media is interacted with. It can have the following 22090 * values: 22091 * - none: nothing should be downloaded 22092 * - metadata: poster and the first few frames of the media may be downloaded to get 22093 * media dimensions and other metadata 22094 * - auto: allow the media and metadata for the media to be downloaded before 22095 * interaction 22096 * 22097 * @method Html5#preload 22098 * @return {string} 22099 * The value of `preload` from the media element. Will be 'none', 'metadata', 22100 * or 'auto'. 22101 * 22102 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-preload} 22103 */ 22104 'preload', 22105 /** 22106 * Get the value of the `error` from the media element. `error` indicates any 22107 * MediaError that may have occurred during playback. If error returns null there is no 22108 * current error. 22109 * 22110 * @method Html5#error 22111 * @return {MediaError|null} 22112 * The value of `error` from the media element. Will be `MediaError` if there 22113 * is a current error and null otherwise. 22114 * 22115 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-error} 22116 */ 22117 'error', 22118 /** 22119 * Get the value of `seeking` from the media element. `seeking` indicates whether the 22120 * media is currently seeking to a new position or not. 22121 * 22122 * @method Html5#seeking 22123 * @return {boolean} 22124 * - The value of `seeking` from the media element. 22125 * - True indicates that the media is currently seeking to a new position. 22126 * - False indicates that the media is not seeking to a new position at this time. 22127 * 22128 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-seeking} 22129 */ 22130 'seeking', 22131 /** 22132 * Get the value of `seekable` from the media element. `seekable` returns a 22133 * `TimeRange` object indicating ranges of time that can currently be `seeked` to. 22134 * 22135 * @method Html5#seekable 22136 * @return {TimeRange} 22137 * The value of `seekable` from the media element. A `TimeRange` object 22138 * indicating the current ranges of time that can be seeked to. 22139 * 22140 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-seekable} 22141 */ 22142 'seekable', 22143 /** 22144 * Get the value of `ended` from the media element. `ended` indicates whether 22145 * the media has reached the end or not. 22146 * 22147 * @method Html5#ended 22148 * @return {boolean} 22149 * - The value of `ended` from the media element. 22150 * - True indicates that the media has ended. 22151 * - False indicates that the media has not ended. 22152 * 22153 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-ended} 22154 */ 22155 'ended', 22156 /** 22157 * Get the value of `playbackRate` from the media element. `playbackRate` indicates
22158 * the rate at which the media is currently playing back. Examples: 22159 * - if playbackRate is set to 2, media will play twice as fast. 22160 * - if playbackRate is set to 0.5, media will play half as fast. 22161 * 22162 * @method Html5#playbackRate 22163 * @return {number} 22164 * The value of `playbackRate` from the media element. A number indicating 22165 * the current playback speed of the media, where 1 is normal speed. 22166 * 22167 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate} 22168 */ 22169 'playbackRate', 22170 /** 22171 * Get the value of `defaultPlaybackRate` from the media element. `defaultPlaybackRate` indicates 22172 * the rate at which the media is currently playing back. This value will not indicate the current 22173 * `playbackRate` after playback has started, use {@link Html5#playbackRate} for that. 22174 * 22175 * Examples: 22176 * - if defaultPlaybackRate is set to 2, media will play twice as fast. 22177 * - if defaultPlaybackRate is set to 0.5, media will play half as fast. 22178 * 22179 * @method Html5.prototype.defaultPlaybackRate 22180 * @return {number} 22181 * The value of `defaultPlaybackRate` from the media element. A number indicating 22182 * the current playback speed of the media, where 1 is normal speed. 22183 * 22184 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate} 22185 */ 22186 'defaultPlaybackRate', 22187 /** 22188 * Get the value of `played` from the media element. `played` returns a `TimeRange` 22189 * object representing points in the media timeline that have been played. 22190 * 22191 * @method Html5#played 22192 * @return {TimeRange} 22193 * The value of `played` from the media element. A `TimeRange` object indicating 22194 * the ranges of time that have been played. 22195 * 22196 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-played} 22197 */ 22198 'played', 22199 /** 22200 * Get the value of `networkState` from the media element. `networkState` indicates 22201 * the current network state. It returns an enumeration from the following list: 22202 * - 0: NETWORK_EMPTY 22203 * - 1: NETWORK_IDLE 22204 * - 2: NETWORK_LOADING 22205 * - 3: NETWORK_NO_SOURCE 22206 * 22207 * @method Html5#networkState 22208 * @return {number} 22209 * The value of `networkState` from the media element. This will be a number 22210 * from the list in the description. 22211 * 22212 * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-networkstate} 22213 */ 22214 'networkState', 22215 /** 22216 * Get the value of `readyState` from the media element. `readyState` indicates 22217 * the current state of the media element. It returns an enumeration from the 22218 * following list: 22219 * - 0: HAVE_NOTHING 22220 * - 1: HAVE_METADATA 22221 * - 2: HAVE_CURRENT_DATA 22222 * - 3: HAVE_FUTURE_DATA 22223 * - 4: HAVE_ENOUGH_DATA 22224 * 22225 * @method Html5#readyState 22226 * @return {number} 22227 * The value of `readyState` from the media element. This will be a number 22228 * from the list in the description. 22229 * 22230 * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#ready-states} 22231 */ 22232 'readyState', 22233 /** 22234 * Get the value of `videoWidth` from the video element. `videoWidth` indicates 22235 * the current width of the video in css pixels. 22236 * 22237 * @method Html5#videoWidth 22238 * @return {number} 22239 * The value of `videoWidth` from the video element. This will be a number 22240 * in css pixels. 22241 * 22242 * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-video-videowidth} 22243 */ 22244 'videoWidth', 22245 /** 22246 * Get the value of `videoHeight` from the video element. `videoHeight` indicates 22247 * the current height of the video in css pixels. 22248 * 22249 * @method Html5#videoHeight 22250 * @return {number} 22251 * The value of `videoHeight` from the video element. This will be a number 22252 * in css pixels. 22253 * 22254 * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-video-videowidth} 22255 */ 22256 'videoHeight'].forEach(function (prop) { 22257 Html5.prototype[prop] = function () { 22258 return this.el_[prop]; 22259 }; 22260 }); // Wrap native properties with a setter in this format: 22261 // set + toTitleCase(name) 22262 // The list is as follows: 22263 // setVolume, setSrc, setPoster, setPreload, setPlaybackRate, setDefaultPlaybackRate 22264 22265 [ 22266 /** 22267 * Set the value of `volume` on the media element. `volume` indicates the current 22268 * audio level as a percentage in decimal form. This means that 1 is 100%, 0.5 is 50%, and 22269 * so on. 22270 * 22271 * @method Html5#setVolume 22272 * @param {number} percentAsDecimal 22273 * The volume percent as a decimal. Valid range is from 0-1. 22274 * 22275 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-a-volume} 22276 */ 22277 'volume', 22278 /** 22279 * Set the value of `src` on the media element. `src` indicates the current 22280 * {@link Tech~SourceObject} for the media. 22281 * 22282 * @method Html5#setSrc 22283 * @param {Tech~SourceObject} src 22284 * The source object to set as the current source. 22285 * 22286 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-src} 22287 */ 22288 'src', 22289 /** 22290 * Set the value of `poster` on the media element. `poster` is the url to 22291 * an image file that can/will be shown when no media data is available. 22292 * 22293 * @method Html5#setPoster 22294 * @param {string} poster 22295 * The url to an image that should be used as the `poster` for the media 22296 * element. 22297 * 22298 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-poster} 22299 */ 22300 'poster', 22301 /** 22302 * Set the value of `preload` on the media element. `preload` indicates 22303 * what should download before the media is interacted with. It can have the following 22304 * values: 22305 * - none: nothing should be downloaded 22306 * - metadata: poster and the first few frames of the media may be downloaded to get 22307 * media dimensions and other metadata 22308 * - auto: allow the media and metadata for the media to be downloaded before 22309 * interaction 22310 * 22311 * @method Html5#setPreload 22312 * @param {string} preload 22313 * The value of `preload` to set on the media element. Must be 'none', 'metadata', 22314 * or 'auto'. 22315 * 22316 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-preload} 22317 */ 22318 'preload', 22319 /** 22320 * Set the value of `playbackRate` on the media element. `playbackRate` indicates 22321 * the rate at which the media should play back. Examples: 22322 * - if playbackRate is set to 2, media will play twice as fast. 22323 * - if playbackRate is set to 0.5, media will play half as fast. 22324 * 22325 * @method Html5#setPlaybackRate 22326 * @return {number} 22327 * The value of `playbackRate` from the media element. A number indicating 22328 * the current playback speed of the media, where 1 is normal speed. 22329 * 22330 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate} 22331 */ 22332 'playbackRate', 22333 /** 22334 * Set the value of `defaultPlaybackRate` on the media element. `defaultPlaybackRate` indicates 22335 * the rate at which the media should play back upon initial startup. Changing this value 22336 * after a video has started will do nothing. Instead you should used {@link Html5#setPlaybackRate}. 22337 * 22338 * Example Values: 22339 * - if playbackRate is set to 2, media will play twice as fast. 22340 * - if playbackRate is set to 0.5, media will play half as fast. 22341 * 22342 * @method Html5.prototype.setDefaultPlaybackRate 22343 * @return {number} 22344 * The value of `defaultPlaybackRate` from the media element. A number indicating 22345 * the current playback speed of the media, where 1 is normal speed. 22346 * 22347 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultplaybackrate} 22348 */ 22349 'defaultPlaybackRate'].forEach(function (prop) { 22350 Html5.prototype['set' + toTitleCase(prop)] = function (v) { 22351 this.el_[prop] = v; 22352 }; 22353 }); // wrap native functions with a function 22354 // The list is as follows: 22355 // pause, load, play 22356 22357 [ 22358 /** 22359 * A wrapper around the media elements `pause` function. This will call the `HTML5` 22360 * media elements `pause` function. 22361 * 22362 * @method Html5#pause 22363 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-pause} 22364 */ 22365 'pause', 22366 /** 22367 * A wrapper around the media elements `load` function. This will call the `HTML5`s 22368 * media element `load` function. 22369 * 22370 * @method Html5#load 22371 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-load} 22372 */ 22373 'load', 22374 /** 22375 * A wrapper around the media elements `play` function. This will call the `HTML5`s 22376 * media element `play` function. 22377 * 22378 * @method Html5#play 22379 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-play} 22380 */ 22381 'play'].forEach(function (prop) { 22382 Html5.prototype[prop] = function () { 22383 return this.el_[prop](); 22384 }; 22385 }); 22386 Tech.withSourceHandlers(Html5); 22387 /** 22388 * Native source handler for Html5, simply passes the source to the media element. 22389 * 22390 * @property {Tech~SourceObject} source 22391 * The source object 22392 * 22393 * @property {Html5} tech 22394 * The instance of the HTML5 tech. 22395 */ 22396 22397 Html5.nativeSourceHandler = {}; 22398 /** 22399 * Check if the media element can play the given mime type. 22400 * 22401 * @param {string} type 22402 * The mimetype to check 22403 * 22404 * @return {string} 22405 * 'probably', 'maybe', or '' (empty string) 22406 */ 22407 22408 Html5.nativeSourceHandler.canPlayType = function (type) { 22409 // IE without MediaPlayer throws an error (#519) 22410 try {
22411 return Html5.TEST_VID.canPlayType(type); 22412 } catch (e) { 22413 return ''; 22414 } 22415 }; 22416 /** 22417 * Check if the media element can handle a source natively. 22418 * 22419 * @param {Tech~SourceObject} source 22420 * The source object 22421 * 22422 * @param {Object} [options] 22423 * Options to be passed to the tech. 22424 * 22425 * @return {string} 22426 * 'probably', 'maybe', or '' (empty string). 22427 */ 22428 22429 22430 Html5.nativeSourceHandler.canHandleSource = function (source, options) { 22431 // If a type was provided we should rely on that 22432 if (source.type) { 22433 return Html5.nativeSourceHandler.canPlayType(source.type); // If no type, fall back to checking 'video/[EXTENSION]' 22434 } else if (source.src) { 22435 var ext = getFileExtension(source.src); 22436 return Html5.nativeSourceHandler.canPlayType("video/" + ext); 22437 } 22438 22439 return ''; 22440 }; 22441 /** 22442 * Pass the source to the native media element. 22443 * 22444 * @param {Tech~SourceObject} source 22445 * The source object 22446 * 22447 * @param {Html5} tech 22448 * The instance of the Html5 tech 22449 * 22450 * @param {Object} [options] 22451 * The options to pass to the source 22452 */ 22453 22454 22455 Html5.nativeSourceHandler.handleSource = function (source, tech, options) { 22456 tech.setSrc(source.src); 22457 }; 22458 /** 22459 * A noop for the native dispose function, as cleanup is not needed. 22460 */ 22461 22462 22463 Html5.nativeSourceHandler.dispose = function () {}; // Register the native source handler 22464 22465 22466 Html5.registerSourceHandler(Html5.nativeSourceHandler); 22467 Tech.registerTech('Html5', Html5); 22468 22469 function _templateObject$2() { 22470 var data = _taggedTemplateLiteralLoose(["\n Using the tech directly can be dangerous. I hope you know
22470what you're doing.\n See https://github.com/videojs/video.js/issues/2617 for more info.\n "]); 22471 22472 _templateObject$2 = function _templateObject() { 22473 return data; 22474 }; 22475 22476 return data; 22477 } 22478 // on the player when they happen 22479 22480 var TECH_EVENTS_RETRIGGER = [ 22481 /** 22482 * Fired while the user agent is downloading media data. 22483 * 22484 * @event Player#progress 22485 * @type {EventTarget~Event} 22486 */ 22487 22488 /** 22489 * Retrigger the `progress` event that was triggered by the {@link Tech}. 22490 * 22491 * @private 22492 * @method Player#handleTechProgress_ 22493 * @fires Player#progress 22494 * @listens Tech#progress 22495 */ 22496 'progress', 22497 /** 22498 * Fires when the loading of an audio/video is aborted. 22499 * 22500 * @event Player#abort 22501 * @type {EventTarget~Event} 22502 */ 22503 22504 /** 22505 * Retrigger the `abort` event that was triggered by the {@link Tech}. 22506 * 22507 * @private 22508 * @method Player#handleTechAbort_ 22509 * @fires Player#abort 22510 * @listens Tech#abort 22511 */ 22512 'abort', 22513 /** 22514 * Fires when the browser is intentionally not getting media data. 22515 * 22516 * @event Player#suspend 22517 * @type {EventTarget~Event} 22518 */ 22519 22520 /** 22521 * Retrigger the `suspend` event that was triggered by the {@link Tech}. 22522 * 22523 * @private 22524 * @method Player#handleTechSuspend_ 22525 * @fires Player#suspend 22526 * @listens Tech#suspend 22527 */ 22528 'suspend', 22529 /** 22530 * Fires when the current playlist is empty. 22531 * 22532 * @event Player#emptied 22533 * @type {EventTarget~Event} 22534 */ 22535 22536 /** 22537 * Retrigger the `emptied` event that was triggered by the {@link Tech}. 22538 * 22539 * @private 22540 * @method Player#handleTechEmptied_ 22541 * @fires Player#emptied 22542 * @listens Tech#emptied 22543 */ 22544 'emptied', 22545 /** 22546 * Fires when the browser is trying to get media data, but data is not available. 22547 * 22548 * @event Player#stalled 22549 * @type {EventTarget~Event} 22550 */ 22551 22552 /** 22553 * Retrigger the `stalled` event that was triggered by the {@link Tech}. 22554 * 22555 * @private 22556 * @method Player#handleTechStalled_ 22557 * @fires Player#stalled 22558 * @listens Tech#stalled 22559 */ 22560 'stalled', 22561 /** 22562 * Fires when the browser has loaded meta data for the audio/video. 22563 * 22564 * @event Player#loadedmetadata 22565 * @type {EventTarget~Event} 22566 */ 22567 22568 /** 22569 * Retrigger the `stalled` event that was triggered by the {@link Tech}. 22570 * 22571 * @private 22572 * @method Player#handleTechLoadedmetadata_ 22573 * @fires Player#loadedmetadata 22574 * @listens Tech#loadedmetadata 22575 */ 22576 'loadedmetadata', 22577 /** 22578 * Fires when the browser has loaded the current frame of the audio/video. 22579 * 22580 * @event Player#loadeddata 22581 * @type {event} 22582 */ 22583 22584 /** 22585 * Retrigger the `loadeddata` event that was triggered by the {@link Tech}. 22586 * 22587 * @private 22588 * @method Player#handleTechLoaddeddata_ 22589 * @fires Player#loadeddata 22590 * @listens Tech#loadeddata 22591 */ 22592 'loadeddata', 22593 /** 22594 * Fires when the current playback position has changed. 22595 * 22596 * @event Player#timeupdate 22597 * @type {event} 22598 */ 22599 22600 /** 22601 * Retrigger the `timeupdate` event that was triggered by the {@link Tech}. 22602 * 22603 * @private 22604 * @method Player#handleTechTimeUpdate_ 22605 * @fires Player#timeupdate 22606 * @listens Tech#timeupdate 22607 */ 22608 'timeupdate', 22609 /** 22610 * Fires when the video's intrinsic dimensions change 22611 * 22612 * @event Player#resize 22613 * @type {event} 22614 */ 22615 22616 /** 22617 * Retrigger the `resize` event that was triggered by the {@link Tech}. 22618 * 22619 * @private 22620 * @method Player#handleTechResize_ 22621 * @fires Player#resize 22622 * @listens Tech#resize 22623 */ 22624 'resize', 22625 /** 22626 * Fires when the volume has been changed 22627 * 22628 * @event Player#volumechange 22629 * @type {event} 22630 */ 22631 22632 /** 22633 * Retrigger the `volumechange` event that was triggered by the {@link Tech}. 22634 * 22635 * @private 22636 * @method Player#handleTechVolumechange_ 22637 * @fires Player#volumechange 22638 * @listens Tech#volumechange 22639 */ 22640 'volumechange', 22641 /** 22642 * Fires when the text track has been changed 22643 * 22644 * @event Player#texttrackchange 22645 * @type {event} 22646 */ 22647 22648 /** 22649 * Retrigger the `texttrackchange` event that was triggered by the {@link Tech}. 22650 * 22651 * @private 22652 * @method Player#handleTechTexttrackchange_ 22653 * @fires Player#texttrackchange 22654 * @listens Tech#texttrackchange 22655 */ 22656 'texttrackchange']; // events to queue when playback rate is zero 22657 // this is a hash for the sole purpose of mapping non-camel-cased event names 22658 // to camel-cased function names 22659 22660 var TECH_EVENTS_QUEUE = { 22661 canplay: 'CanPlay', 22662 canplaythrough: 'CanPlayThrough', 22663 playing: 'Playing', 22664 seeked: 'Seeked' 22665 }; 22666 var BREAKPOINT_ORDER = ['tiny', 'xsmall', 'small', 'medium', 'large', 'xlarge', 'huge']; 22667 var BREAKPOINT_CLASSES = {}; // grep: vjs-layout-tiny 22668 // grep: vjs-layout-x-small 22669 // grep: vjs-layout-small 22670 // grep: vjs-layout-medium 22671 // grep: vjs-layout-large 22672 // grep: vjs-layout-x-large 22673 // grep: vjs-layout-huge 22674
vendor: 21,955 bytes, lines 22675-23351
22675 BREAKPOINT_ORDER.forEach(function (k) { 22676 var v = k.charAt(0) === 'x' ? "x-" + k.substring(1) : k; 22677 BREAKPOINT_CLASSES[k] = "vjs-layout-" + v; 22678 }); 22679 var DEFAULT_BREAKPOINTS = { 22680 tiny: 210, 22681 xsmall: 320, 22682 small: 425, 22683 medium: 768, 22684 large: 1440, 22685 xlarge: 2560, 22686 huge: Infinity 22687 }; 22688 /** 22689 * An instance of the `Player` class is created when any of the Video.js setup methods 22690 * are used to initialize a video. 22691 * 22692 * After an instance has been created it can be accessed globally in two ways: 22693 * 1. By calling `videojs('example_video_1');` 22694 * 2. By using it directly via `videojs.players.example_video_1;` 22695 * 22696 * @extends Component 22697 */ 22698 22699 var Player = 22700 /*#__PURE__*/ 22701 function (_Component) { 22702 _inheritsLoose(Player, _Component); 22703 22704 /** 22705 * Create an instance of this class. 22706 * 22707 * @param {Element} tag 22708 * The original video DOM element used for configuring options. 22709 * 22710 * @param {Object} [options] 22711 * Object of option names and values. 22712 * 22713 * @param {Component~ReadyCallback} [ready] 22714 * Ready callback function. 22715 */ 22716 function Player(tag, options, ready) { 22717 var _this; 22718 22719 // Make sure tag ID exists 22720 tag.id = tag.id || options.id || "vjs_video_" + newGUID(); // Set Options 22721 // The options argument overrides options set in the video tag 22722 // which overrides globally set options. 22723 // This latter part coincides with the load order 22724 // (tag must exist before Player) 22725 22726 options = assign(Player.getTagSettings(tag), options); // Delay the initialization of children because we need to set up 22727 // player properties first, and can't use `this` before `super()` 22728 22729 options.initChildren = false; // Same with creating the element 22730 22731 options.createEl = false; // don't auto mixin the evented mixin 22732 22733 options.evented = false; // we don't want the player to report touch activity on itself 22734 // see enableTouchActivity in Component 22735 22736 options.reportTouchActivity = false; // If language is not set, get the closest lang attribute 22737 22738 if (!options.language) { 22739 if (typeof tag.closest === 'function') { 22740 var closest = tag.closest('[lang]'); 22741 22742 if (closest && closest.getAttribute) { 22743 options.language = closest.getAttribute('lang'); 22744 } 22745 } else { 22746 var element = tag; 22747 22748 while (element && element.nodeType === 1) { 22749 if (getAttributes(element).hasOwnProperty('lang')) { 22750 options.language = element.getAttribute('lang'); 22751 break; 22752 } 22753 22754 element = element.parentNode; 22755 } 22756 } 22757 } // Run base component initializing with new options 22758 22759 22760 _this = _Component.call(this, null, options, ready) || this; // Create bound methods for document listeners. 22761 22762 _this.boundDocumentFullscreenChange_ = bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.documentFullscreenChange_); 22763 _this.boundFullWindowOnEscKey_ = bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.fullWindowOnEscKey); // create logger 22764 22765 _this.log = createLogger$1(_this.id_); // Tracks when a tech changes the poster 22766 22767 _this.isPosterFromTech_ = false; // Holds callback info that gets queued when playback rate is zero 22768 // and a seek is happening 22769 22770 _this.queuedCallbacks_ = []; // Turn off API access because we're loading a new tech that might load asynchronously 22771 22772 _this.isReady_ = false; // Init state hasStarted_ 22773 22774 _this.hasStarted_ = false; // Init state userActive_ 22775 22776 _this.userActive_ = false; // if the global option object was accidentally blown away by 22777 // someone, bail early with an informative error 22778 22779 if (!_this.options_ || !_this.options_.techOrder || !_this.options_.techOrder.length) { 22780 throw new Error('No techOrder specified. Did you overwrite ' + 'videojs.options instead of just changing the ' + 'properties you want to override?'); 22781 } // Store the original tag used to set options 22782 22783 22784 _this.tag = tag; // Store the tag attributes used to restore html5 element 22785 22786 _this.tagAttributes = tag && getAttributes(tag); // Update current language 22787 22788 _this.language(_this.options_.language); // Update Supported Languages 22789 22790 22791 if (options.languages) { 22792 // Normalise player option languages to lowercase 22793 var languagesToLower = {}; 22794 Object.getOwnPropertyNames(options.languages).forEach(function (name$$1) { 22795 languagesToLower[name$$1.toLowerCase()] = options.languages[name$$1]; 22796 }); 22797 _this.languages_ = languagesToLower; 22798 } else { 22799 _this.languages_ = Player.prototype.options_.languages; 22800 } 22801 22802 _this.resetCache_(); // Set poster 22803 22804 22805 _this.poster_ = options.poster || ''; // Set controls 22806 22807 _this.controls_ = !!options.controls; // Original tag settings stored in options 22808 // now remove immediately so native controls don't flash. 22809 // May be turned back on by HTML5 tech if nativeControlsForTouch is true 22810 22811 tag.controls = false; 22812 tag.removeAttribute('controls'); 22813 _this.changingSrc_ = false; 22814 _this.playCallbacks_ = []; 22815 _this.playTerminatedQueue_ = []; // the attribute overrides the option 22816 22817 if (tag.hasAttribute('autoplay')) { 22818 _this.autoplay(true); 22819 } else { 22820 // otherwise use the setter to validate and 22821 // set the correct value. 22822 _this.autoplay(_this.options_.autoplay); 22823 } // check plugins 22824 22825 22826 if (options.plugins) { 22827 Object.keys(options.plugins).forEach(function (name$$1) { 22828 if (typeof _this[name$$1] !== 'function') { 22829 throw new Error("plugin \"" + name$$1 + "\" does not exist"); 22830 } 22831 }); 22832 } 22833 /* 22834 * Store the internal state of scrubbing 22835 * 22836 * @private 22837 * @return {Boolean} True if the user is scrubbing 22838 */ 22839 22840 22841 _this.scrubbing_ = false; 22842 _this.el_ = _this.createEl(); // Make this an evented object and use `el_` as its event bus. 22843 22844 evented(_assertThisInitialized(_assertThisInitialized(_this)), { 22845 eventBusKey: 'el_' 22846 }); 22847 22848 if (_this.fluid_) { 22849 _this.on('playerreset', _this.updateStyleEl_); 22850 } // We also want to pass the original player options to each component and plugin 22851 // as well so they don't need to reach back into the player for options later. 22852 // We also need to do another copy of this.options_ so we don't end up with 22853 // an infinite loop. 22854 22855 22856 var playerOptionsCopy = mergeOptions(_this.options_); // Load plugins 22857 22858 if (options.plugins) { 22859 Object.keys(options.plugins).forEach(function (name$$1) { 22860 _this[name$$1](options.plugins[name$$1]); 22861 }); 22862 } 22863 22864 _this.options_.playerOptions = playerOptionsCopy; 22865 _this.middleware_ = []; 22866 22867 _this.initChildren(); // Set isAudio based on whether or not an audio tag was used 22868 22869 22870 _this.isAudio(tag.nodeName.toLowerCase() === 'audio'); // Update controls className. Can't do this when the controls are initially 22871 // set because the element doesn't exist yet. 22872 22873 22874 if (_this.controls()) { 22875 _this.addClass('vjs-controls-enabled'); 22876 } else { 22877 _this.addClass('vjs-controls-disabled'); 22878 } // Set ARIA label and region role depending on player type 22879 22880 22881 _this.el_.setAttribute('role', 'region'); 22882 22883 if (_this.isAudio()) { 22884 _this.el_.setAttribute('aria-label', _this.localize('Audio Player')); 22885 } else { 22886 _this.el_.setAttribute('aria-label', _this.localize('Video Player')); 22887 } 22888 22889 if (_this.isAudio()) { 22890 _this.addClass('vjs-audio'); 22891 } 22892 22893 if (_this.flexNotSupported_()) { 22894 _this.addClass('vjs-no-flex'); 22895 } // TODO: Make this smarter. Toggle user state between touching/mousing 22896 // using events, since devices can have both touch and mouse events. 22897 // TODO: Make this check be performed again when the window switches between monitors 22898 // (See https://github.com/videojs/video.js/issues/5683) 22899 22900 22901 if (TOUCH_ENABLED) { 22902 _this.addClass('vjs-touch-enabled'); 22903 } // iOS Safari has broken hover handling 22904 22905 22906 if (!IS_IOS) { 22907 _this.addClass('vjs-workinghover'); 22908 } // Make player easily findable by ID 22909 22910 22911 Player.players[_this.id_] = _assertThisInitialized(_assertThisInitialized(_this)); // Add a major version class to aid css in plugins 22912 22913 var majorVersion = version.split('.')[0]; 22914 22915 _this.addClass("vjs-v" + majorVersion); // When the player is first initialized, trigger activity so components 22916 // like the control bar show themselves if needed 22917 22918 22919 _this.userActive(true); 22920 22921 _this.reportUserActivity(); 22922 22923 _this.one('play', _this.listenForUserActivity_); 22924 22925 _this.on('stageclick', _this.handleStageClick_); 22926 22927 _this.on('keydown', _this.handleKeyDown); 22928 22929 _this.breakpoints(_this.options_.breakpoints); 22930 22931 _this.responsive(_this.options_.responsive); 22932 22933 return _this; 22934 } 22935 /** 22936 * Destroys the video player and does any necessary cleanup. 22937 * 22938 * This is especially helpful if you are dynamically adding and removing videos 22939 * to/from the DOM. 22940 * 22941 * @fires Player#dispose 22942 */ 22943 22944 22945 var _proto = Player.prototype; 22946 22947 _proto.dispose = function dispose() { 22948 var _this2 = this; 22949 22950 /** 22951 * Called when the player is being disposed of. 22952 * 22953 * @event Player#dispose 22954 * @type {EventTarget~Event} 22955 */ 22956 this.trigger('dispose'); // prevent dispose from being called twice 22957 22958 this.off('dispose'); // Make sure all player-specific document listeners are unbound. This is 22959 22960 off(document, FullscreenApi.fullscreenchange, this.boundDocumentFullscreenChange_); 22961 off(document, 'keydown', this.boundFullWindowOnEscKey_); 22962 22963 if (this.styleEl_ && this.styleEl_.parentNode) { 22964 this.styleEl_.parentNode.removeChild(this.styleEl_); 22965 this.styleEl_ = null; 22966 } // Kill reference to this player 22967 22968 22969 Player.players[this.id_] = null; 22970 22971 if (this.tag && this.tag.player) { 22972 this.tag.player = null; 22973 } 22974 22975 if (this.el_ && this.el_.player) { 22976 this.el_.player = null; 22977 } 22978 22979 if (this.tech_) { 22980 this.tech_.dispose(); 22981 this.isPosterFromTech_ = false; 22982 this.poster_ = ''; 22983 } 22984 22985 if (this.playerElIngest_) { 22986 this.playerElIngest_ = null; 22987 } 22988 22989 if (this.tag) { 22990 this.tag = null; 22991 } 22992 22993 clearCacheForPlayer(this); // remove all event handlers for track lists 22994 // all tracks and track listeners are removed on 22995 // tech dispose 22996 22997 ALL.names.forEach(function (name$$1) { 22998 var props = ALL[name$$1]; 22999 23000 var list = _this2[props.getterName](); // if it is not a native list 23001 // we have to manually remove event listeners 23002 23003 23004 if (list && list.off) { 23005 list.off(); 23006 } 23007 }); // the actual .el_ is removed here 23008 23009 _Component.prototype.dispose.call(this); 23010 } 23011 /** 23012 * Create the `Player`'s DOM element. 23013 * 23014 * @return {Element} 23015 * The DOM element that gets created. 23016 */ 23017 ; 23018 23019 _proto.createEl = function createEl$$1() { 23020 var tag = this.tag; 23021 var el; 23022 var playerElIngest = this.playerElIngest_ = tag.parentNode && tag.parentNode.hasAttribute && tag.parentNode.hasAttribute('data-vjs-player'); 23023 var divEmbed = this.tag.tagName.toLowerCase() === 'video-js'; 23024 23025 if (playerElIngest) { 23026 el = this.el_ = tag.parentNode; 23027 } else if (!divEmbed) { 23028 el = this.el_ = _Component.prototype.createEl.call(this, 'div'); 23029 } // Copy over all the attributes from the tag, including ID and class 23030 // ID will now reference player box, not the video tag 23031 23032 23033 var attrs = getAttributes(tag); 23034 23035 if (divEmbed) { 23036 el = this.el_ = tag; 23037 tag = this.tag = document.createElement('video'); 23038 23039 while (el.children.length) { 23040 tag.appendChild(el.firstChild); 23041 } 23042 23043 if (!hasClass(el, 'video-js')) { 23044 addClass(el, 'video-js'); 23045 } 23046 23047 el.appendChild(tag); 23048 playerElIngest = this.playerElIngest_ = el; // move properties over from our custom `video-js` element 23049 // to our new `video` element. This will move things like 23050 // `src` or `controls` that were set via js before the player 23051 // was initialized. 23052 23053 Object.keys(el).forEach(function (k) { 23054 tag[k] = el[k]; 23055 }); 23056 } // set tabindex to -1 to remove the video element from the focus order 23057 23058 23059 tag.setAttribute('tabindex', '-1'); 23060 attrs.tabindex = '-1'; // Workaround for #4583 (JAWS+IE doesn't announce BPB or play button) 23061 // See https://github.com/FreedomScientific/VFO-standards-support/issues/78 23062 // Note that we can't detect if JAWS is being used, but this ARIA attribute 23063 // doesn't change behavior of IE11 if JAWS is not being used 23064 23065 if (IE_VERSION) { 23066 tag.setAttribute('role', 'application'); 23067 attrs.role = 'application'; 23068 } // Remove width/height attrs from tag so CSS can make it 100% width/height 23069 23070 23071 tag.removeAttribute('width'); 23072 tag.removeAttribute('height'); 23073 23074 if ('width' in attrs) { 23075 delete attrs.width; 23076 } 23077 23078 if ('height' in attrs) { 23079 delete attrs.height; 23080 } 23081 23082 Object.getOwnPropertyNames(attrs).forEach(function (attr) { 23083 // don't copy over the class attribute to the player element when we're in a div embed 23084 // the class is already set up properly in the divEmbed case 23085 // and we want to make sure that the `video-js` class doesn't get lost 23086 if (!(divEmbed && attr === 'class')) { 23087 el.setAttribute(attr, attrs[attr]); 23088 } 23089 23090 if (divEmbed) { 23091 tag.setAttribute(attr, attrs[attr]); 23092 } 23093 }); // Update tag id/class for use as HTML5 playback tech 23094 // Might think we should do this after embedding in container so .vjs-tech class 23095 // doesn't flash 100% width/height, but class only applies with .video-js parent 23096 23097 tag.playerId = tag.id; 23098 tag.id += '_html5_api'; 23099 tag.className = 'vjs-tech'; // Make player findable on elements 23100 23101 tag.player = el.player = this; // Default state of video is paused 23102 23103 this.addClass('vjs-paused'); // Add a style element in the player that we'll use to set the width/height 23104 // of the player in a way that's still overrideable by CSS, just like the 23105 // video element 23106 23107 if (window$1.VIDEOJS_NO_DYNAMIC_STYLE !== true) { 23108 this.styleEl_ = createStyleElement('vjs-styles-dimensions'); 23109 var defaultsStyleEl = $('.vjs-styles-defaults'); 23110 var head = $('head'); 23111 head.insertBefore(this.styleEl_, defaultsStyleEl ? defaultsStyleEl.nextSibling : head.firstChild); 23112 } 23113 23114 this.fill_ = false; 23115 this.fluid_ = false; // Pass in the width/height/aspectRatio options which will update the style el 23116 23117 this.width(this.options_.width); 23118 this.height(this.options_.height); 23119 this.fill(this.options_.fill); 23120 this.fluid(this.options_.fluid); 23121 this.aspectRatio(this.options_.aspectRatio); // Hide any links within the video/audio tag, 23122 // because IE doesn't hide them completely from screen readers. 23123 23124 var links = tag.getElementsByTagName('a'); 23125 23126 for (var i = 0; i < links.length; i++) { 23127 var linkEl = links.item(i); 23128 addClass(linkEl, 'vjs-hidden'); 23129 linkEl.setAttribute('hidden', 'hidden'); 23130 } // insertElFirst seems to cause the networkState to flicker from 3 to 2, so 23131 // keep track of the original for later so we can know if the source originally failed 23132 23133 23134 tag.initNetworkState_ = tag.networkState; // Wrap video tag in div (el/box) container 23135 23136 if (tag.parentNode && !playerElIngest) { 23137 tag.parentNode.insertBefore(el, tag); 23138 } // insert the tag as the first child of the player element 23139 // then manually add it to the children array so that this.addChild 23140 // will work properly for other components 23141 // 23142 // Breaks iPhone, fixed in HTML5 setup. 23143 23144 23145 prependTo(tag, el); 23146 this.children_.unshift(tag); // Set lang attr on player to ensure CSS :lang() in consistent with player 23147 // if it's been set to something different to the doc 23148 23149 this.el_.setAttribute('lang', this.language_); 23150 this.el_ = el; 23151 return el; 23152 } 23153 /** 23154 * A getter/setter for the `Player`'s width. Returns the player's configured value. 23155 * To get the current width use `currentWidth()`. 23156 * 23157 * @param {number} [value] 23158 * The value to set the `Player`'s width to. 23159 * 23160 * @return {number} 23161 * The current width of the `Player` when getting. 23162 */ 23163 ; 23164 23165 _proto.width = function width(value) { 23166 return this.dimension('width', value); 23167 } 23168 /** 23169 * A getter/setter for the `Player`'s height. Returns the player's configured value. 23170 * To get the current height use `currentheight()`. 23171 * 23172 * @param {number} [value] 23173 * The value to set the `Player`'s heigth to. 23174 * 23175 * @return {number} 23176 * The current height of the `Player` when getting. 23177 */ 23178 ; 23179 23180 _proto.height = function height(value) { 23181 return this.dimension('height', value); 23182 } 23183 /** 23184 * A getter/setter for the `Player`'s width & height. 23185 * 23186 * @param {string} dimension 23187 * This string can be: 23188 * - 'width' 23189 * - 'height' 23190 * 23191 * @param {number} [value] 23192 * Value for dimension specified in the first argument. 23193 * 23194 * @return {number} 23195 * The dimension arguments value when getting (width/height). 23196 */ 23197 ; 23198 23199 _proto.dimension = function dimension(_dimension, value) { 23200 var privDimension = _dimension + '_'; 23201 23202 if (value === undefined) { 23203 return this[privDimension] || 0; 23204 } 23205 23206 if (value === '') { 23207 // If an empty string is given, reset the dimension to be automatic 23208 this[privDimension] = undefined; 23209 this.updateStyleEl_(); 23210 return; 23211 } 23212 23213 var parsedVal = parseFloat(value); 23214 23215 if (isNaN(parsedVal)) { 23216 log.error("Improper value \"" + value + "\" supplied for for " + _dimension); 23217 return; 23218 } 23219 23220 this[privDimension] = parsedVal; 23221 this.updateStyleEl_(); 23222 } 23223 /** 23224 * A getter/setter/toggler for the vjs-fluid `className` on the `Player`. 23225 * 23226 * Turning this on will turn off fill mode. 23227 * 23228 * @param {boolean} [bool] 23229 * - A value of true adds the class. 23230 * - A value of false removes the class. 23231 * - No value will be a getter. 23232 * 23233 * @return {boolean|undefined} 23234 * - The value of fluid when getting. 23235 * - `undefined` when setting. 23236 */ 23237 ; 23238 23239 _proto.fluid = function fluid(bool) { 23240 if (bool === undefined) { 23241 return !!this.fluid_; 23242 } 23243 23244 this.fluid_ = !!bool; 23245 23246 if (isEvented(this)) { 23247 this.off('playerreset', this.updateStyleEl_); 23248 } 23249 23250 if (bool) { 23251 this.addClass('vjs-fluid'); 23252 this.fill(false); 23253 addEventedCallback(function () { 23254 this.on('playerreset', this.updateStyleEl_); 23255 }); 23256 } else { 23257 this.removeClass('vjs-fluid'); 23258 } 23259 23260 this.updateStyleEl_(); 23261 } 23262 /** 23263 * A getter/setter/toggler for the vjs-fill `className` on the `Player`. 23264 * 23265 * Turning this on will turn off fluid mode. 23266 * 23267 * @param {boolean} [bool] 23268 * - A value of true adds the class. 23269 * - A value of false removes the class. 23270 * - No value will be a getter. 23271 * 23272 * @return {boolean|undefined} 23273 * - The value of fluid when getting. 23274 * - `undefined` when setting. 23275 */ 23276 ; 23277 23278 _proto.fill = function fill(bool) { 23279 if (bool === undefined) { 23280 return !!this.fill_; 23281 } 23282 23283 this.fill_ = !!bool; 23284 23285 if (bool) { 23286 this.addClass('vjs-fill'); 23287 this.fluid(false); 23288 } else { 23289 this.removeClass('vjs-fill'); 23290 } 23291 } 23292 /** 23293 * Get/Set the aspect ratio 23294 * 23295 * @param {string} [ratio] 23296 * Aspect ratio for player 23297 * 23298 * @return {string|undefined} 23299 * returns the current aspect ratio when getting 23300 */ 23301 23302 /** 23303 * A getter/setter for the `Player`'s aspect ratio. 23304 * 23305 * @param {string} [ratio] 23306 * The value to set the `Player's aspect ratio to. 23307 * 23308 * @return {string|undefined} 23309 * - The current aspect ratio of the `Player` when getting. 23310 * - undefined when setting 23311 */ 23312 ; 23313 23314 _proto.aspectRatio = function aspectRatio(ratio) { 23315 if (ratio === undefined) { 23316 return this.aspectRatio_; 23317 } // Check for width:height format 23318 23319 23320 if (!/^\d+\:\d+$/.test(ratio)) { 23321 throw new Error('Improper value supplied for aspect ratio. The format should be width:height, for example 16:9.'); 23322 } 23323 23324 this.aspectRatio_ = ratio; // We're assuming if you set an aspect ratio you want fluid mode, 23325 // because in fixed mode you could calculate width and height yourself. 23326 23327 this.fluid(true); 23328 this.updateStyleEl_(); 23329 } 23330 /** 23331 * Update styles of the `Player` element (height, width and aspect ratio). 23332 * 23333 * @private 23334 * @listens Tech#loadedmetadata 23335 */ 23336 ; 23337 23338 _proto.updateStyleEl_ = function updateStyleEl_() { 23339 if (window$1.VIDEOJS_NO_DYNAMIC_STYLE === true) { 23340 var _width = typeof this.width_ === 'number' ? this.width_ : this.options_.width; 23341 23342 var _height = typeof this.height_ === 'number' ? this.height_ : this.options_.height; 23343 23344 var techEl = this.tech_ && this.tech_.el(); 23345 23346 if (techEl) { 23347 if (_width >= 0) { 23348 techEl.width = _width; 23349 } 23350 23351 if (_height >= 0) {
vendor: 8,342 bytes, lines 23352-23560
23352 techEl.height = _height; 23353 } 23354 } 23355 23356 return; 23357 } 23358 23359 var width; 23360 var height; 23361 var aspectRatio; 23362 var idClass; // The aspect ratio is either used directly or to calculate width and height. 23363 23364 if (this.aspectRatio_ !== undefined && this.aspectRatio_ !== 'auto') { 23365 // Use any aspectRatio that's been specifically set 23366 aspectRatio = this.aspectRatio_; 23367 } else if (this.videoWidth() > 0) { 23368 // Otherwise try to get the aspect ratio from the video metadata 23369 aspectRatio = this.videoWidth() + ':' + this.videoHeight(); 23370 } else { 23371 // Or use a default. The video element's is 2:1, but 16:9 is more common. 23372 aspectRatio = '16:9'; 23373 } // Get the ratio as a decimal we can use to calculate dimensions 23374 23375 23376 var ratioParts = aspectRatio.split(':'); 23377 var ratioMultiplier = ratioParts[1] / ratioParts[0]; 23378 23379 if (this.width_ !== undefined) { 23380 // Use any width that's been specifically set 23381 width = this.width_; 23382 } else if (this.height_ !== undefined) { 23383 // Or calulate the width from the aspect ratio if a height has been set 23384 width = this.height_ / ratioMultiplier; 23385 } else { 23386 // Or use the video's metadata, or use the video el's default of 300 23387 width = this.videoWidth() || 300; 23388 } 23389 23390 if (this.height_ !== undefined) { 23391 // Use any height that's been specifically set 23392 height = this.height_; 23393 } else { 23394 // Otherwise calculate the height from the ratio and the width 23395 height = width * ratioMultiplier; 23396 } // Ensure the CSS class is valid by starting with an alpha character 23397 23398 23399 if (/^[^a-zA-Z]/.test(this.id())) { 23400 idClass = 'dimensions-' + this.id(); 23401 } else { 23402 idClass = this.id() + '-dimensions'; 23403 } // Ensure the right class is still on the player for the style element 23404 23405 23406 this.addClass(idClass); 23407 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 "); 23408 } 23409 /** 23410 * Load/Create an instance of playback {@link Tech} including element 23411 * and API methods. Then append the `Tech` element in `Player` as a child. 23412 * 23413 * @param {string} techName 23414 * name of the playback technology 23415 * 23416 * @param {string} source 23417 * video source 23418 * 23419 * @private 23420 */ 23421 ; 23422 23423 _proto.loadTech_ = function loadTech_(techName, source) { 23424 var _this3 = this; 23425 23426 // Pause and remove current playback technology 23427 if (this.tech_) { 23428 this.unloadTech_(); 23429 } 23430 23431 var titleTechName = toTitleCase(techName); 23432 var camelTechName = techName.charAt(0).toLowerCase() + techName.slice(1); // get rid of the HTML5 video tag as soon as we are using another tech 23433 23434 if (titleTechName !== 'Html5' && this.tag) { 23435 Tech.getTech('Html5').disposeMediaElement(this.tag); 23436 this.tag.player = null; 23437 this.tag = null; 23438 } 23439 23440 this.techName_ = titleTechName; // Turn off API access because we're loading a new tech that might load asynchronously 23441 23442 this.isReady_ = false; // if autoplay is a string we pass false to the tech 23443 // because the player is going to handle autoplay on `loadstart` 23444 23445 var autoplay = typeof this.autoplay() === 'string' ? false : this.autoplay(); // Grab tech-specific options from player options and add source and parent element to use. 23446 23447 var techOptions = { 23448 source: source, 23449 autoplay: autoplay, 23450 'nativeControlsForTouch': this.options_.nativeControlsForTouch, 23451 'playerId': this.id(), 23452 'techId': this.id() + "_" + camelTechName + "_api", 23453 'playsinline': this.options_.playsinline, 23454 'preload': this.options_.preload, 23455 'loop': this.options_.loop, 23456 'muted': this.options_.muted, 23457 'poster': this.poster(), 23458 'language': this.language(), 23459 'playerElIngest': this.playerElIngest_ || false, 23460 'vtt.js': this.options_['vtt.js'], 23461 'canOverridePoster': !!this.options_.techCanOverridePoster, 23462 'enableSourceset': this.options_.enableSourceset 23463 }; 23464 ALL.names.forEach(function (name$$1) { 23465 var props = ALL[name$$1]; 23466 techOptions[props.getterName] = _this3[props.privateName]; 23467 }); 23468 assign(techOptions, this.options_[titleTechName]); 23469 assign(techOptions, this.options_[camelTechName]); 23470 assign(techOptions, this.options_[techName.toLowerCase()]); 23471 23472 if (this.tag) { 23473 techOptions.tag = this.tag; 23474 } 23475 23476 if (source && source.src === this.cache_.src && this.cache_.currentTime > 0) { 23477 techOptions.startTime = this.cache_.currentTime; 23478 } // Initialize tech instance 23479 23480 23481 var TechClass = Tech.getTech(techName); 23482 23483 if (!TechClass) { 23484 throw new Error("No Tech named '" + titleTechName + "' exists! '" + titleTechName + "' should be registered using videojs.registerTech()'"); 23485 } 23486 23487 this.tech_ = new TechClass(techOptions); // player.triggerReady is always async, so don't need this to be async 23488 23489 this.tech_.ready(bind(this, this.handleTechReady_), true); 23490 textTrackConverter.jsonToTextTracks(this.textTracksJson_ || [], this.tech_); // Listen to all HTML5-defined events and trigger them on the player 23491 23492 TECH_EVENTS_RETRIGGER.forEach(function (event) { 23493 _this3.on(_this3.tech_, event, _this3["handleTech" + toTitleCase(event) + "_"]); 23494 }); 23495 Object.keys(TECH_EVENTS_QUEUE).forEach(function (event) { 23496 _this3.on(_this3.tech_, event, function (eventObj) { 23497 if (_this3.tech_.playbackRate() === 0 && _this3.tech_.seeking()) { 23498 _this3.queuedCallbacks_.push({ 23499 callback: _this3["handleTech" + TECH_EVENTS_QUEUE[event] + "_"].bind(_this3), 23500 event: eventObj 23501 }); 23502 23503 return; 23504 } 23505 23506 _this3["handleTech" + TECH_EVENTS_QUEUE[event] + "_"](eventObj); 23507 }); 23508 }); 23509 this.on(this.tech_, 'loadstart', this.handleTechLoadStart_); 23510 this.on(this.tech_, 'sourceset', this.handleTechSourceset_); 23511 this.on(this.tech_, 'waiting', this.handleTechWaiting_); 23512 this.on(this.tech_, 'ended', this.handleTechEnded_); 23513 this.on(this.tech_, 'seeking', this.handleTechSeeking_); 23514 this.on(this.tech_, 'play', this.handleTechPlay_); 23515 this.on(this.tech_, 'firstplay', this.handleTechFirstPlay_); 23516 this.on(this.tech_, 'pause', this.handleTechPause_); 23517 this.on(this.tech_, 'durationchange', this.handleTechDurationChange_); 23518 this.on(this.tech_, 'fullscreenchange', this.handleTechFullscreenChange_); 23519 this.on(this.tech_, 'error', this.handleTechError_); 23520 this.on(this.tech_, 'loadedmetadata', this.updateStyleEl_); 23521 this.on(this.tech_, 'posterchange', this.handleTechPosterChange_); 23522 this.on(this.tech_, 'textdata', this.handleTechTextData_); 23523 this.on(this.tech_, 'ratechange', this.handleTechRateChange_); 23524 this.usingNativeControls(this.techGet_('controls')); 23525 23526 if (this.controls() && !this.usingNativeControls()) { 23527 this.addTechControlsListeners_(); 23528 } // Add the tech element in the DOM if it was not already there 23529 // Make sure to not insert the original video element if using Html5 23530 23531 23532 if (this.tech_.el().parentNode !== this.el() && (titleTechName !== 'Html5' || !this.tag)) { 23533 prependTo(this.tech_.el(), this.el()); 23534 } // Get rid of the original video tag reference after the first tech is loaded 23535 23536 23537 if (this.tag) { 23538 this.tag.player = null; 23539 this.tag = null; 23540 } 23541 } 23542 /** 23543 * Unload and dispose of the current playback {@link Tech}. 23544 * 23545 * @private 23546 */ 23547 ; 23548 23549 _proto.unloadTech_ = function unloadTech_() { 23550 var _this4 = this; 23551 23552 // Save the current text tracks so that we can reuse the same text tracks with the next tech 23553 ALL.names.forEach(function (name$$1) { 23554 var props = ALL[name$$1]; 23555 _this4[props.privateName] = _this4[props.getterName](); 23556 }); 23557 this.textTracksJson_ = textTrackConverter.textTracksToJson(this.tech_); 23558 this.isReady_ = false; 23559 this.tech_.dispose(); 23560 this.tech_ = false;
vendor: 44,151 bytes, lines 23561-25017
23561 23562 if (this.isPosterFromTech_) { 23563 this.poster_ = ''; 23564 this.trigger('posterchange'); 23565 } 23566 23567 this.isPosterFromTech_ = false; 23568 } 23569 /** 23570 * Return a reference to the current {@link Tech}. 23571 * It will print a warning by default about the danger of using the tech directly 23572 * but any argument that is passed in will silence the warning. 23573 * 23574 * @param {*} [safety] 23575 * Anything passed in to silence the warning 23576 * 23577 * @return {Tech} 23578 * The Tech 23579 */ 23580 ; 23581 23582 _proto.tech = function tech(safety) { 23583 if (safety === undefined) { 23584 log.warn(tsml(_templateObject$2())); 23585 } 23586 23587 return this.tech_; 23588 } 23589 /** 23590 * Set up click and touch listeners for the playback element 23591 * 23592 * - On desktops: a click on the video itself will toggle playback 23593 * - On mobile devices: a click on the video toggles controls 23594 * which is done by toggling the user state between active and 23595 * inactive 23596 * - A tap can signal that a user has become active or has become inactive 23597 * e.g. a quick tap on an iPhone movie should reveal the controls. Another 23598 * quick tap should hide them again (signaling the user is in an inactive 23599 * viewing state) 23600 * - In addition to this, we still want the user to be considered inactive after 23601 * a few seconds of inactivity. 23602 * 23603 * > Note: the only part of iOS interaction we can't mimic with this setup 23604 * is a touch and hold on the video element counting as activity in order to 23605 * keep the controls showing, but that shouldn't be an issue. A touch and hold 23606 * on any controls will still keep the user active 23607 * 23608 * @private 23609 */ 23610 ; 23611 23612 _proto.addTechControlsListeners_ = function addTechControlsListeners_() { 23613 // Make sure to remove all the previous listeners in case we are called multiple times. 23614 this.removeTechControlsListeners_(); // Some browsers (Chrome & IE) don't trigger a click on a flash swf, but do 23615 // trigger mousedown/up. 23616 // http://stackoverflow.com/questions/1444562/javascript-onclick-event-over-flash-object 23617 // Any touch events are set to block the mousedown event from happening 23618 23619 this.on(this.tech_, 'mousedown', this.handleTechClick_); 23620 this.on(this.tech_, 'dblclick', this.handleTechDoubleClick_); // If the controls were hidden we don't want that to change without a tap event 23621 // so we'll check if the controls were already showing before reporting user 23622 // activity 23623 23624 this.on(this.tech_, 'touchstart', this.handleTechTouchStart_); 23625 this.on(this.tech_, 'touchmove', this.handleTechTouchMove_); 23626 this.on(this.tech_, 'touchend', this.handleTechTouchEnd_); // The tap listener needs to come after the touchend listener because the tap 23627 // listener cancels out any reportedUserActivity when setting userActive(false) 23628 23629 this.on(this.tech_, 'tap', this.handleTechTap_); 23630 } 23631 /** 23632 * Remove the listeners used for click and tap controls. This is needed for 23633 * toggling to controls disabled, where a tap/touch should do nothing. 23634 * 23635 * @private 23636 */ 23637 ; 23638 23639 _proto.removeTechControlsListeners_ = function removeTechControlsListeners_() { 23640 // We don't want to just use `this.off()` because there might be other needed 23641 // listeners added by techs that extend this. 23642 this.off(this.tech_, 'tap', this.handleTechTap_); 23643 this.off(this.tech_, 'touchstart', this.handleTechTouchStart_); 23644 this.off(this.tech_, 'touchmove', this.handleTechTouchMove_); 23645 this.off(this.tech_, 'touchend', this.handleTechTouchEnd_); 23646 this.off(this.tech_, 'mousedown', this.handleTechClick_); 23647 this.off(this.tech_, 'dblclick', this.handleTechDoubleClick_); 23648 } 23649 /** 23650 * Player waits for the tech to be ready 23651 * 23652 * @private 23653 */ 23654 ; 23655 23656 _proto.handleTechReady_ = function handleTechReady_() { 23657 this.triggerReady(); // Keep the same volume as before 23658 23659 if (this.cache_.volume) { 23660 this.techCall_('setVolume', this.cache_.volume); 23661 } // Look if the tech found a higher resolution poster while loading 23662 23663 23664 this.handleTechPosterChange_(); // Update the duration if available 23665 23666 this.handleTechDurationChange_(); 23667 } 23668 /** 23669 * Retrigger the `loadstart` event that was triggered by the {@link Tech}. This 23670 * function will also trigger {@link Player#firstplay} if it is the first loadstart 23671 * for a video. 23672 * 23673 * @fires Player#loadstart 23674 * @fires Player#firstplay 23675 * @listens Tech#loadstart 23676 * @private 23677 */ 23678 ; 23679 23680 _proto.handleTechLoadStart_ = function handleTechLoadStart_() { 23681 // TODO: Update to use `emptied` event instead. See #1277. 23682 this.removeClass('vjs-ended'); 23683 this.removeClass('vjs-seeking'); // reset the error state 23684 23685 this.error(null); // Update the duration 23686 23687 this.handleTechDurationChange_(); // If it's already playing we want to trigger a firstplay event now. 23688 // The firstplay event relies on both the play and loadstart events 23689 // which can happen in any order for a new source 23690 23691 if (!this.paused()) { 23692 /** 23693 * Fired when the user agent begins looking for media data 23694 * 23695 * @event Player#loadstart 23696 * @type {EventTarget~Event} 23697 */ 23698 this.trigger('loadstart'); 23699 this.trigger('firstplay'); 23700 } else { 23701 // reset the hasStarted state 23702 this.hasStarted(false); 23703 this.trigger('loadstart'); 23704 } // autoplay happens after loadstart for the browser, 23705 // so we mimic that behavior 23706 23707 23708 this.manualAutoplay_(this.autoplay()); 23709 } 23710 /** 23711 * Handle autoplay string values, rather than the typical boolean 23712 * values that should be handled by the tech. Note that this is not 23713 * part of any specification. Valid values and what they do can be 23714 * found on the autoplay getter at Player#autoplay() 23715 */ 23716 ; 23717 23718 _proto.manualAutoplay_ = function manualAutoplay_(type) { 23719 var _this5 = this; 23720 23721 if (!this.tech_ || typeof type !== 'string') { 23722 return; 23723 } 23724 23725 var muted = function muted() { 23726 var previouslyMuted = _this5.muted(); 23727 23728 _this5.muted(true); 23729 23730 var restoreMuted = function restoreMuted() { 23731 _this5.muted(previouslyMuted); 23732 }; // restore muted on play terminatation 23733 23734 23735 _this5.playTerminatedQueue_.push(restoreMuted); 23736 23737 var mutedPromise = _this5.play(); 23738 23739 if (!isPromise(mutedPromise)) { 23740 return; 23741 } 23742 23743 return mutedPromise.catch(restoreMuted); 23744 }; 23745 23746 var promise; // if muted defaults to true 23747 // the only thing we can do is call play 23748 23749 if (type === 'any' && this.muted() !== true) { 23750 promise = this.play(); 23751 23752 if (isPromise(promise)) { 23753 promise = promise.catch(muted); 23754 } 23755 } else if (type === 'muted' && this.muted() !== true) { 23756 promise = muted(); 23757 } else { 23758 promise = this.play(); 23759 } 23760 23761 if (!isPromise(promise)) { 23762 return; 23763 } 23764 23765 return promise.then(function () { 23766 _this5.trigger({ 23767 type: 'autoplay-success', 23768 autoplay: type 23769 }); 23770 }).catch(function (e) { 23771 _this5.trigger({ 23772 type: 'autoplay-failure', 23773 autoplay: type 23774 }); 23775 }); 23776 } 23777 /** 23778 * Update the internal source caches so that we return the correct source from 23779 * `src()`, `currentSource()`, and `currentSources()`. 23780 * 23781 * > Note: `currentSources` will not be updated if the source that is passed in exists 23782 * in the current `currentSources` cache. 23783 * 23784 * 23785 * @param {Tech~SourceObject} srcObj 23786 * A string or object source to update our caches to. 23787 */ 23788 ; 23789 23790 _proto.updateSourceCaches_ = function updateSourceCaches_(srcObj) { 23791 if (srcObj === void 0) { 23792 srcObj = ''; 23793 } 23794 23795 var src = srcObj; 23796 var type = ''; 23797 23798 if (typeof src !== 'string') { 23799 src = srcObj.src; 23800 type = srcObj.type; 23801 } // make sure all the caches are set to default values 23802 // to prevent null checking 23803 23804 23805 this.cache_.source = this.cache_.source || {}; 23806 this.cache_.sources = this.cache_.sources || []; // try to get the type of the src that was passed in 23807 23808 if (src && !type) { 23809 type = findMimetype(this, src); 23810 } // update `currentSource` cache always 23811 23812 23813 this.cache_.source = mergeOptions({}, srcObj, { 23814 src: src, 23815 type: type 23816 }); 23817 var matchingSources = this.cache_.sources.filter(function (s) { 23818 return s.src && s.src === src; 23819 }); 23820 var sourceElSources = []; 23821 var sourceEls = this.$$('source'); 23822 var matchingSourceEls = []; 23823 23824 for (var i = 0; i < sourceEls.length; i++) { 23825 var sourceObj = getAttributes(sourceEls[i]); 23826 sourceElSources.push(sourceObj); 23827 23828 if (sourceObj.src && sourceObj.src === src) { 23829 matchingSourceEls.push(sourceObj.src); 23830 } 23831 } // if we have matching source els but not matching sources 23832 // the current source cache is not up to date 23833 23834 23835 if (matchingSourceEls.length && !matchingSources.length) { 23836 this.cache_.sources = sourceElSources; // if we don't have matching source or source els set the 23837 // sources cache to the `currentSource` cache 23838 } else if (!matchingSources.length) { 23839 this.cache_.sources = [this.cache_.source]; 23840 } // update the tech `src` cache 23841 23842 23843 this.cache_.src = src; 23844 } 23845 /** 23846 * *EXPERIMENTAL* Fired when the source is set or changed on the {@link Tech} 23847 * causing the media element to reload. 23848 * 23849 * It will fire for the initial source and each subsequent source. 23850 * This event is a custom event from Video.js and is triggered by the {@link Tech}. 23851 * 23852 * The event object for this event contains a `src` property that will contain the source 23853 * that was available when the event was triggered. This is generally only necessary if Video.js 23854 * is switching techs while the source was being changed. 23855 * 23856 * It is also fired when `load` is called on the player (or media element) 23857 * because the {@link https://html.spec.whatwg.org/multipage/media.html#dom-media-load|specification for `load`} 23858 * says that the resource selection algorithm needs to be aborted and restarted. 23859 * In this case, it is very likely that the `src` property will be set to the 23860 * empty string `""` to indicate we do not know what the source will be but 23861 * that it is changing. 23862 * 23863 * *This event is currently still experimental and may change in minor releases.* 23864 * __To use this, pass `enableSourceset` option to the player.__ 23865 * 23866 * @event Player#sourceset 23867 * @type {EventTarget~Event} 23868 * @prop {string} src 23869 * The source url available when the `sourceset` was triggered. 23870 * It will be an empty string if we cannot know what the source is 23871 * but know that the source will change. 23872 */ 23873 23874 /** 23875 * Retrigger the `sourceset` event that was triggered by the {@link Tech}. 23876 * 23877 * @fires Player#sourceset 23878 * @listens Tech#sourceset 23879 * @private 23880 */ 23881 ; 23882 23883 _proto.handleTechSourceset_ = function handleTechSourceset_(event) { 23884 var _this6 = this; 23885 23886 // only update the source cache when the source 23887 // was not updated using the player api 23888 if (!this.changingSrc_) { 23889 var updateSourceCaches = function updateSourceCaches(src) { 23890 return _this6.updateSourceCaches_(src); 23891 }; 23892 23893 var playerSrc = this.currentSource().src; 23894 var eventSrc = event.src; // if we have a playerSrc that is not a blob, and a tech src that is a blob 23895 23896 if (playerSrc && !/^blob:/.test(playerSrc) && /^blob:/.test(eventSrc)) { 23897 // if both the tech source and the player source were updated we assume 23898 // something like @videojs/http-streaming did the sourceset and skip updating the source cache. 23899 if (!this.lastSource_ || this.lastSource_.tech !== eventSrc && this.lastSource_.player !== playerSrc) { 23900 updateSourceCaches = function updateSourceCaches() {}; 23901 } 23902 } // update the source to the intial source right away 23903 // in some cases this will be empty string 23904 23905 23906 updateSourceCaches(eventSrc); // if the `sourceset` `src` was an empty string 23907 // wait for a `loadstart` to update the cache to `currentSrc`. 23908 // If a sourceset happens before a `loadstart`, we reset the state 23909 // as this function will be called again. 23910 23911 if (!event.src) { 23912 var updateCache = function updateCache(e) { 23913 if (e.type !== 'sourceset') { 23914 var techSrc = _this6.techGet('currentSrc'); 23915 23916 _this6.lastSource_.tech = techSrc; 23917 23918 _this6.updateSourceCaches_(techSrc); 23919 } 23920 23921 _this6.tech_.off(['sourceset', 'loadstart'], updateCache); 23922 }; 23923 23924 this.tech_.one(['sourceset', 'loadstart'], updateCache); 23925 } 23926 } 23927 23928 this.lastSource_ = { 23929 player: this.currentSource().src, 23930 tech: event.src 23931 }; 23932 this.trigger({ 23933 src: event.src, 23934 type: 'sourceset' 23935 }); 23936 } 23937 /** 23938 * Add/remove the vjs-has-started class 23939 * 23940 * @fires Player#firstplay 23941 * 23942 * @param {boolean} request 23943 * - true: adds the class 23944 * - false: remove the class 23945 * 23946 * @return {boolean} 23947 * the boolean value of hasStarted_ 23948 */ 23949 ; 23950 23951 _proto.hasStarted = function hasStarted(request) { 23952 if (request === undefined) { 23953 // act as getter, if we have no request to change 23954 return this.hasStarted_; 23955 } 23956 23957 if (request === this.hasStarted_) { 23958 return; 23959 } 23960 23961 this.hasStarted_ = request; 23962 23963 if (this.hasStarted_) { 23964 this.addClass('vjs-has-started'); 23965 this.trigger('firstplay'); 23966 } else { 23967 this.removeClass('vjs-has-started'); 23968 } 23969 } 23970 /** 23971 * Fired whenever the media begins or resumes playback 23972 * 23973 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-play} 23974 * @fires Player#play 23975 * @listens Tech#play 23976 * @private 23977 */ 23978 ; 23979 23980 _proto.handleTechPlay_ = function handleTechPlay_() { 23981 this.removeClass('vjs-ended'); 23982 this.removeClass('vjs-paused'); 23983 this.addClass('vjs-playing'); // hide the poster when the user hits play 23984 23985 this.hasStarted(true); 23986 /** 23987 * Triggered whenever an {@link Tech#play} event happens. Indicates that 23988 * playback has started or resumed. 23989 * 23990 * @event Player#play 23991 * @type {EventTarget~Event} 23992 */ 23993 23994 this.trigger('play'); 23995 } 23996 /** 23997 * Retrigger the `ratechange` event that was triggered by the {@link Tech}. 23998 * 23999 * If there were any events queued while the playback rate was zero, fire 24000 * those events now. 24001 * 24002 * @private 24003 * @method Player#handleTechRateChange_ 24004 * @fires Player#ratechange 24005 * @listens Tech#ratechange 24006 */ 24007 ; 24008 24009 _proto.handleTechRateChange_ = function handleTechRateChange_() { 24010 if (this.tech_.playbackRate() > 0 && this.cache_.lastPlaybackRate === 0) { 24011 this.queuedCallbacks_.forEach(function (queued) { 24012 return queued.callback(queued.event); 24013 }); 24014 this.queuedCallbacks_ = []; 24015 } 24016 24017 this.cache_.lastPlaybackRate = this.tech_.playbackRate(); 24018 /** 24019 * Fires when the playing speed of the audio/video is changed 24020 * 24021 * @event Player#ratechange 24022 * @type {event} 24023 */ 24024 24025 this.trigger('ratechange'); 24026 } 24027 /** 24028 * Retrigger the `waiting` event that was triggered by the {@link Tech}. 24029 * 24030 * @fires Player#waiting 24031 * @listens Tech#waiting 24032 * @private 24033 */ 24034 ; 24035 24036 _proto.handleTechWaiting_ = function handleTechWaiting_() { 24037 var _this7 = this; 24038 24039 this.addClass('vjs-waiting'); 24040 /** 24041 * A readyState change on the DOM element has caused playback to stop. 24042 * 24043 * @event Player#waiting 24044 * @type {EventTarget~Event} 24045 */ 24046 24047 this.trigger('waiting'); // Browsers may emit a timeupdate event after a waiting event. In order to prevent 24048 // premature removal of the waiting class, wait for the time to change. 24049 24050 var timeWhenWaiting = this.currentTime(); 24051 24052 var timeUpdateListener = function timeUpdateListener() { 24053 if (timeWhenWaiting !== _this7.currentTime()) { 24054 _this7.removeClass('vjs-waiting'); 24055 24056 _this7.off('timeupdate', timeUpdateListener); 24057 } 24058 }; 24059 24060 this.on('timeupdate', timeUpdateListener); 24061 } 24062 /** 24063 * Retrigger the `canplay` event that was triggered by the {@link Tech}. 24064 * > Note: This is not consistent between browsers. See #1351 24065 * 24066 * @fires Player#canplay 24067 * @listens Tech#canplay 24068 * @private 24069 */ 24070 ; 24071 24072 _proto.handleTechCanPlay_ = function handleTechCanPlay_() { 24073 this.removeClass('vjs-waiting'); 24074 /** 24075 * The media has a readyState of HAVE_FUTURE_DATA or greater. 24076 * 24077 * @event Player#canplay 24078 * @type {EventTarget~Event} 24079 */ 24080 24081 this.trigger('canplay'); 24082 } 24083 /** 24084 * Retrigger the `canplaythrough` event that was triggered by the {@link Tech}. 24085 * 24086 * @fires Player#canplaythrough 24087 * @listens Tech#canplaythrough 24088 * @private 24089 */ 24090 ; 24091 24092 _proto.handleTechCanPlayThrough_ = function handleTechCanPlayThrough_() { 24093 this.removeClass('vjs-waiting'); 24094 /** 24095 * The media has a readyState of HAVE_ENOUGH_DATA or greater. This means that the 24096 * entire media file can be played without buffering. 24097 * 24098 * @event Player#canplaythrough 24099 * @type {EventTarget~Event} 24100 */ 24101 24102 this.trigger('canplaythrough'); 24103 } 24104 /** 24105 * Retrigger the `playing` event that was triggered by the {@link Tech}. 24106 * 24107 * @fires Player#playing 24108 * @listens Tech#playing 24109 * @private 24110 */ 24111 ; 24112 24113 _proto.handleTechPlaying_ = function handleTechPlaying_() { 24114 this.removeClass('vjs-waiting'); 24115 /** 24116 * The media is no longer blocked from playback, and has started playing. 24117 * 24118 * @event Player#playing 24119 * @type {EventTarget~Event} 24120 */ 24121 24122 this.trigger('playing'); 24123 } 24124 /** 24125 * Retrigger the `seeking` event that was triggered by the {@link Tech}. 24126 * 24127 * @fires Player#seeking 24128 * @listens Tech#seeking 24129 * @private 24130 */ 24131 ; 24132 24133 _proto.handleTechSeeking_ = function handleTechSeeking_() { 24134 this.addClass('vjs-seeking'); 24135 /** 24136 * Fired whenever the player is jumping to a new time 24137 * 24138 * @event Player#seeking 24139 * @type {EventTarget~Event} 24140 */ 24141 24142 this.trigger('seeking'); 24143 } 24144 /** 24145 * Retrigger the `seeked` event that was triggered by the {@link Tech}. 24146 * 24147 * @fires Player#seeked 24148 * @listens Tech#seeked 24149 * @private 24150 */ 24151 ; 24152 24153 _proto.handleTechSeeked_ = function handleTechSeeked_() { 24154 this.removeClass('vjs-seeking'); 24155 this.removeClass('vjs-ended'); 24156 /** 24157 * Fired when the player has finished jumping to a new time 24158 * 24159 * @event Player#seeked 24160 * @type {EventTarget~Event} 24161 */ 24162 24163 this.trigger('seeked'); 24164 } 24165 /** 24166 * Retrigger the `firstplay` event that was triggered by the {@link Tech}. 24167 * 24168 * @fires Player#firstplay 24169 * @listens Tech#firstplay 24170 * @deprecated As of 6.0 firstplay event is deprecated. 24171 * As of 6.0 passing the `starttime` option to the player and the firstplay event are deprecated. 24172 * @private 24173 */ 24174 ; 24175 24176 _proto.handleTechFirstPlay_ = function handleTechFirstPlay_() { 24177 // If the first starttime attribute is specified 24178 // then we will start at the given offset in seconds 24179 if (this.options_.starttime) { 24180 log.warn('Passing the `starttime` option to the player will be deprecated in 6.0'); 24181 this.currentTime(this.options_.starttime); 24182 } 24183 24184 this.addClass('vjs-has-started'); 24185 /** 24186 * Fired the first time a video is played. Not part of the HLS spec, and this is 24187 * probably not the best implementation yet, so use sparingly. If you don't have a 24188 * reason to prevent playback, use `myPlayer.one('play');` instead. 24189 * 24190 * @event Player#firstplay 24191 * @deprecated As of 6.0 firstplay event is deprecated. 24192 * @type {EventTarget~Event} 24193 */ 24194 24195 this.trigger('firstplay'); 24196 } 24197 /** 24198 * Retrigger the `pause` event that was triggered by the {@link Tech}. 24199 * 24200 * @fires Player#pause 24201 * @listens Tech#pause 24202 * @private 24203 */ 24204 ; 24205 24206 _proto.handleTechPause_ = function handleTechPause_() { 24207 this.removeClass('vjs-playing'); 24208 this.addClass('vjs-paused'); 24209 /** 24210 * Fired whenever the media has been paused 24211 * 24212 * @event Player#pause 24213 * @type {EventTarget~Event} 24214 */ 24215 24216 this.trigger('pause'); 24217 } 24218 /** 24219 * Retrigger the `ended` event that was triggered by the {@link Tech}. 24220 * 24221 * @fires Player#ended 24222 * @listens Tech#ended 24223 * @private 24224 */ 24225 ; 24226 24227 _proto.handleTechEnded_ = function handleTechEnded_() { 24228 this.addClass('vjs-ended'); 24229 24230 if (this.options_.loop) { 24231 this.currentTime(0); 24232 this.play(); 24233 } else if (!this.paused()) { 24234 this.pause(); 24235 } 24236 /** 24237 * Fired when the end of the media resource is reached (currentTime == duration) 24238 * 24239 * @event Player#ended 24240 * @type {EventTarget~Event} 24241 */ 24242 24243 24244 this.trigger('ended'); 24245 } 24246 /** 24247 * Fired when the duration of the media resource is first known or changed 24248 * 24249 * @listens Tech#durationchange 24250 * @private 24251 */ 24252 ; 24253 24254 _proto.handleTechDurationChange_ = function handleTechDurationChange_() { 24255 this.duration(this.techGet_('duration')); 24256 } 24257 /** 24258 * Handle a click on the media element to play/pause 24259 * 24260 * @param {EventTarget~Event} event 24261 * the event that caused this function to trigger 24262 * 24263 * @listens Tech#mousedown 24264 * @private 24265 */ 24266 ; 24267 24268 _proto.handleTechClick_ = function handleTechClick_(event) { 24269 if (!isSingleLeftClick(event)) { 24270 return; 24271 } // When controls are disabled a click should not toggle playback because 24272 // the click is considered a control 24273 24274 24275 if (!this.controls_) { 24276 return; 24277 } 24278 24279 if (this.paused()) { 24280 silencePromise(this.play()); 24281 } else { 24282 this.pause(); 24283 } 24284 } 24285 /** 24286 * Handle a double-click on the media element to enter/exit fullscreen 24287 * 24288 * @param {EventTarget~Event} event 24289 * the event that caused this function to trigger 24290 * 24291 * @listens Tech#dblclick 24292 * @private 24293 */ 24294 ; 24295 24296 _proto.handleTechDoubleClick_ = function handleTechDoubleClick_(event) { 24297 if (!this.controls_) { 24298 return; 24299 } // we do not want to toggle fullscreen state 24300 // when double-clicking inside a control bar or a modal 24301 24302 24303 var inAllowedEls = Array.prototype.some.call(this.$$('.vjs-control-bar, .vjs-modal-dialog'), function (el) { 24304 return el.contains(event.target); 24305 }); 24306 24307 if (!inAllowedEls) { 24308 /* 24309 * options.userActions.doubleClick 24310 * 24311 * If `undefined` or `true`, double-click toggles fullscreen if controls are present 24312 * Set to `false` to disable double-click handling 24313 * Set to a function to substitute an external double-click handler 24314 */ 24315 if (this.options_ === undefined || this.options_.userActions === undefined || this.options_.userActions.doubleClick === undefined || this.options_.userActions.doubleClick !== false) { 24316 if (this.options_ !== undefined && this.options_.userActions !== undefined && typeof this.options_.userActions.doubleClick === 'function') { 24317 this.options_.userActions.doubleClick.call(this, event); 24318 } else if (this.isFullscreen()) { 24319 this.exitFullscreen(); 24320 } else { 24321 this.requestFullscreen(); 24322 } 24323 } 24324 } 24325 } 24326 /** 24327 * Handle a tap on the media element. It will toggle the user 24328 * activity state, which hides and shows the controls. 24329 * 24330 * @listens Tech#tap 24331 * @private 24332 */ 24333 ; 24334 24335 _proto.handleTechTap_ = function handleTechTap_() { 24336 this.userActive(!this.userActive()); 24337 } 24338 /** 24339 * Handle touch to start 24340 * 24341 * @listens Tech#touchstart 24342 * @private 24343 */ 24344 ; 24345 24346 _proto.handleTechTouchStart_ = function handleTechTouchStart_() { 24347 this.userWasActive = this.userActive(); 24348 } 24349 /** 24350 * Handle touch to move 24351 * 24352 * @listens Tech#touchmove 24353 * @private 24354 */ 24355 ; 24356 24357 _proto.handleTechTouchMove_ = function handleTechTouchMove_() { 24358 if (this.userWasActive) { 24359 this.reportUserActivity(); 24360 } 24361 } 24362 /** 24363 * Handle touch to end 24364 * 24365 * @param {EventTarget~Event} event 24366 * the touchend event that triggered 24367 * this function 24368 * 24369 * @listens Tech#touchend 24370 * @private 24371 */ 24372 ; 24373 24374 _proto.handleTechTouchEnd_ = function handleTechTouchEnd_(event) { 24375 // Stop the mouse events from also happening 24376 event.preventDefault(); 24377 } 24378 /** 24379 * native click events on the SWF aren't triggered on IE11, Win8.1RT 24380 * use stageclick events triggered from inside the SWF instead 24381 * 24382 * @private 24383 * @listens stageclick 24384 */ 24385 ; 24386 24387 _proto.handleStageClick_ = function handleStageClick_() { 24388 this.reportUserActivity(); 24389 } 24390 /** 24391 * @private 24392 */ 24393 ; 24394 24395 _proto.toggleFullscreenClass_ = function toggleFullscreenClass_() { 24396 if (this.isFullscreen()) { 24397 this.addClass('vjs-fullscreen'); 24398 } else { 24399 this.removeClass('vjs-fullscreen'); 24400 } 24401 } 24402 /** 24403 * when the document fschange event triggers it calls this 24404 */ 24405 ; 24406 24407 _proto.documentFullscreenChange_ = function documentFullscreenChange_(e) { 24408 var fsApi = FullscreenApi; 24409 var el = this.el(); 24410 var isFs = document[fsApi.fullscreenElement] === el; 24411 24412 if (!isFs && el.matches) { 24413 isFs = el.matches(':' + fsApi.fullscreen); 24414 } else if (!isFs && el.msMatchesSelector) { 24415 isFs = el.msMatchesSelector(':' + fsApi.fullscreen); 24416 } 24417 24418 this.isFullscreen(isFs); // If cancelling fullscreen, remove event listener. 24419 24420 if (this.isFullscreen() === false) { 24421 off(document, fsApi.fullscreenchange, this.boundDocumentFullscreenChange_); 24422 } 24423 24424 if (!prefixedAPI) { 24425 /** 24426 * @event Player#fullscreenchange 24427 * @type {EventTarget~Event} 24428 */ 24429 this.trigger('fullscreenchange'); 24430 } 24431 } 24432 /** 24433 * Handle Tech Fullscreen Change 24434 * 24435 * @param {EventTarget~Event} event 24436 * the fullscreenchange event that triggered this function 24437 * 24438 * @param {Object} data 24439 * the data that was sent with the event 24440 * 24441 * @private 24442 * @listens Tech#fullscreenchange 24443 * @fires Player#fullscreenchange 24444 */ 24445 ; 24446 24447 _proto.handleTechFullscreenChange_ = function handleTechFullscreenChange_(event, data) { 24448 if (data) { 24449 this.isFullscreen(data.isFullscreen); 24450 } 24451 /** 24452 * Fired when going in and out of fullscreen. 24453 * 24454 * @event Player#fullscreenchange 24455 * @type {EventTarget~Event} 24456 */ 24457 24458 24459 this.trigger('fullscreenchange'); 24460 } 24461 /** 24462 * Fires when an error occurred during the loading of an audio/video. 24463 * 24464 * @private 24465 * @listens Tech#error 24466 */ 24467 ; 24468 24469 _proto.handleTechError_ = function handleTechError_() { 24470 var error = this.tech_.error(); 24471 this.error(error); 24472 } 24473 /** 24474 * Retrigger the `textdata` event that was triggered by the {@link Tech}. 24475 * 24476 * @fires Player#textdata 24477 * @listens Tech#textdata 24478 * @private 24479 */ 24480 ; 24481 24482 _proto.handleTechTextData_ = function handleTechTextData_() { 24483 var data = null; 24484 24485 if (arguments.length > 1) { 24486 data = arguments[1]; 24487 } 24488 /** 24489 * Fires when we get a textdata event from tech 24490 * 24491 * @event Player#textdata 24492 * @type {EventTarget~Event} 24493 */ 24494 24495 24496 this.trigger('textdata', data); 24497 } 24498 /** 24499 * Get object for cached values. 24500 * 24501 * @return {Object} 24502 * get the current object cache 24503 */ 24504 ; 24505 24506 _proto.getCache = function getCache() { 24507 return this.cache_; 24508 } 24509 /** 24510 * Resets the internal cache object. 24511 * 24512 * Using this function outside the player constructor or reset method may 24513 * have unintended side-effects. 24514 * 24515 * @private 24516 */ 24517 ; 24518 24519 _proto.resetCache_ = function resetCache_() { 24520 this.cache_ = { 24521 // Right now, the currentTime is not _really_ cached because it is always 24522 // retrieved from the tech (see: currentTime). However, for completeness, 24523 // we set it to zero here to ensure that if we do start actually caching 24524 // it, we reset it along with everything else. 24525 currentTime: 0, 24526 inactivityTimeout: this.options_.inactivityTimeout, 24527 duration: NaN, 24528 lastVolume: 1, 24529 lastPlaybackRate: this.defaultPlaybackRate(), 24530 media: null, 24531 src: '', 24532 source: {}, 24533 sources: [], 24534 volume: 1 24535 }; 24536 } 24537 /** 24538 * Pass values to the playback tech 24539 * 24540 * @param {string} [method] 24541 * the method to call 24542 * 24543 * @param {Object} arg 24544 * the argument to pass 24545 * 24546 * @private 24547 */ 24548 ; 24549 24550 _proto.techCall_ = function techCall_(method, arg) { 24551 // If it's not ready yet, call method when it is 24552 this.ready(function () { 24553 if (method in allowedSetters) { 24554 return set$1(this.middleware_, this.tech_, method, arg); 24555 } else if (method in allowedMediators) { 24556 return mediate(this.middleware_, this.tech_, method, arg); 24557 } 24558 24559 try { 24560 if (this.tech_) { 24561 this.tech_[method](arg); 24562 } 24563 } catch (e) { 24564 log(e); 24565 throw e; 24566 } 24567 }, true); 24568 } 24569 /** 24570 * Get calls can't wait for the tech, and sometimes don't need to. 24571 * 24572 * @param {string} method 24573 * Tech method 24574 * 24575 * @return {Function|undefined} 24576 * the method or undefined 24577 * 24578 * @private 24579 */ 24580 ; 24581 24582 _proto.techGet_ = function techGet_(method) { 24583 if (!this.tech_ || !this.tech_.isReady_) { 24584 return; 24585 } 24586 24587 if (method in allowedGetters) { 24588 return get(this.middleware_, this.tech_, method); 24589 } else if (method in allowedMediators) { 24590 return mediate(this.middleware_, this.tech_, method); 24591 } // Flash likes to die and reload when you hide or reposition it. 24592 // In these cases the object methods go away and we get errors. 24593 // When that happens we'll catch the errors and inform tech that it's not ready any more. 24594 24595 24596 try { 24597 return this.tech_[method](); 24598 } catch (e) { 24599 // When building additional tech libs, an expected method may not be defined yet 24600 if (this.tech_[method] === undefined) { 24601 log("Video.js: " + method + " method not defined for " + this.techName_ + " playback technology.", e); 24602 throw e; 24603 } // When a method isn't available on the object it throws a TypeError 24604 24605 24606 if (e.name === 'TypeError') { 24607 log("Video.js: " + method + " unavailable on " + this.techName_ + " playback technology element.", e); 24608 this.tech_.isReady_ = false; 24609 throw e; 24610 } // If error unknown, just log and throw 24611 24612 24613 log(e); 24614 throw e; 24615 } 24616 } 24617 /** 24618 * Attempt to begin playback at the first opportunity. 24619 * 24620 * @return {Promise|undefined} 24621 * Returns a promise if the browser supports Promises (or one 24622 * was passed in as an option). This promise will be resolved on 24623 * the return value of play. If this is undefined it will fulfill the 24624 * promise chain otherwise the promise chain will be fulfilled when 24625 * the promise from play is fulfilled. 24626 */ 24627 ; 24628 24629 _proto.play = function play() { 24630 var _this8 = this; 24631 24632 var PromiseClass = this.options_.Promise || window$1.Promise; 24633 24634 if (PromiseClass) { 24635 return new PromiseClass(function (resolve) { 24636 _this8.play_(resolve); 24637 }); 24638 } 24639 24640 return this.play_(); 24641 } 24642 /** 24643 * The actual logic for play, takes a callback that will be resolved on the 24644 * return value of play. This allows us to resolve to the play promise if there 24645 * is one on modern browsers. 24646 * 24647 * @private 24648 * @param {Function} [callback] 24649 * The callback that should be called when the techs play is actually called 24650 */ 24651 ; 24652 24653 _proto.play_ = function play_(callback) { 24654 var _this9 = this; 24655 24656 if (callback === void 0) { 24657 callback = silencePromise; 24658 } 24659 24660 this.playCallbacks_.push(callback); 24661 var isSrcReady = Boolean(!this.changingSrc_ && (this.src() || this.currentSrc())); // treat calls to play_ somewhat like the `one` event function 24662 24663 if (this.waitToPlay_) { 24664 this.off(['ready', 'loadstart'], this.waitToPlay_); 24665 this.waitToPlay_ = null; 24666 } // if the player/tech is not ready or the src itself is not ready 24667 // queue up a call to play on `ready` or `loadstart` 24668 24669 24670 if (!this.isReady_ || !isSrcReady) { 24671 this.waitToPlay_ = function (e) { 24672 _this9.play_(); 24673 }; 24674 24675 this.one(['ready', 'loadstart'], this.waitToPlay_); // if we are in Safari, there is a high chance that loadstart will trigger after the gesture timeperiod 24676 // in that case, we need to prime the video element by calling load so it'll be ready in time 24677 24678 if (!isSrcReady && (IS_ANY_SAFARI || IS_IOS)) { 24679 this.load(); 24680 } 24681 24682 return; 24683 } // If the player/tech is ready and we have a source, we can attempt playback. 24684 24685 24686 var val = this.techGet_('play'); // play was terminated if the returned value is null 24687 24688 if (val === null) { 24689 this.runPlayTerminatedQueue_(); 24690 } else { 24691 this.runPlayCallbacks_(val); 24692 } 24693 } 24694 /** 24695 * These functions will be run when if play is terminated. If play 24696 * runPlayCallbacks_ is run these function will not be run. This allows us 24697 * to differenciate between a terminated play and an actual call to play. 24698 */ 24699 ; 24700 24701 _proto.runPlayTerminatedQueue_ = function runPlayTerminatedQueue_() { 24702 var queue = this.playTerminatedQueue_.slice(0); 24703 this.playTerminatedQueue_ = []; 24704 queue.forEach(function (q) { 24705 q(); 24706 }); 24707 } 24708 /** 24709 * When a callback to play is delayed we have to run these 24710 * callbacks when play is actually called on the tech. This function 24711 * runs the callbacks that were delayed and accepts the return value 24712 * from the tech. 24713 * 24714 * @param {undefined|Promise} val 24715 * The return value from the tech. 24716 */ 24717 ; 24718 24719 _proto.runPlayCallbacks_ = function runPlayCallbacks_(val) { 24720 var callbacks = this.playCallbacks_.slice(0); 24721 this.playCallbacks_ = []; // clear play terminatedQueue since we finished a real play 24722 24723 this.playTerminatedQueue_ = []; 24724 callbacks.forEach(function (cb) { 24725 cb(val); 24726 }); 24727 } 24728 /** 24729 * Pause the video playback 24730 * 24731 * @return {Player} 24732 * A reference to the player object this function was called on 24733 */ 24734 ; 24735 24736 _proto.pause = function pause() { 24737 this.techCall_('pause'); 24738 } 24739 /** 24740 * Check if the player is paused or has yet to play 24741 * 24742 * @return {boolean} 24743 * - false: if the media is currently playing 24744 * - true: if media is not currently playing 24745 */ 24746 ; 24747 24748 _proto.paused = function paused() { 24749 // The initial state of paused should be true (in Safari it's actually false) 24750 return this.techGet_('paused') === false ? false : true; 24751 } 24752 /** 24753 * Get a TimeRange object representing the current ranges of time that the user 24754 * has played. 24755 * 24756 * @return {TimeRange} 24757 * A time range object that represents all the increments of time that have 24758 * been played. 24759 */ 24760 ; 24761 24762 _proto.played = function played() { 24763 return this.techGet_('played') || createTimeRanges(0, 0); 24764 } 24765 /** 24766 * Returns whether or not the user is "scrubbing". Scrubbing is 24767 * when the user has clicked the progress bar handle and is 24768 * dragging it along the progress bar. 24769 * 24770 * @param {boolean} [isScrubbing] 24771 * whether the user is or is not scrubbing 24772 * 24773 * @return {boolean} 24774 * The value of scrubbing when getting 24775 */ 24776 ; 24777 24778 _proto.scrubbing = function scrubbing(isScrubbing) { 24779 if (typeof isScrubbing === 'undefined') { 24780 return this.scrubbing_; 24781 } 24782 24783 this.scrubbing_ = !!isScrubbing; 24784 24785 if (isScrubbing) { 24786 this.addClass('vjs-scrubbing'); 24787 } else { 24788 this.removeClass('vjs-scrubbing'); 24789 } 24790 } 24791 /** 24792 * Get or set the current time (in seconds) 24793 * 24794 * @param {number|string} [seconds] 24795 * The time to seek to in seconds 24796 * 24797 * @return {number} 24798 * - the current time in seconds when getting 24799 */ 24800 ; 24801 24802 _proto.currentTime = function currentTime(seconds) { 24803 if (typeof seconds !== 'undefined') { 24804 if (seconds < 0) { 24805 seconds = 0; 24806 } 24807 24808 this.techCall_('setCurrentTime', seconds); 24809 return; 24810 } // cache last currentTime and return. default to 0 seconds 24811 // 24812 // Caching the currentTime is meant to prevent a massive amount of reads on the tech's 24813 // currentTime when scrubbing, but may not provide much performance benefit afterall. 24814 // Should be tested. Also something has to read the actual current time or the cache will 24815 // never get updated. 24816 24817 24818 this.cache_.currentTime = this.techGet_('currentTime') || 0; 24819 return this.cache_.currentTime; 24820 } 24821 /** 24822 * Normally gets the length in time of the video in seconds; 24823 * in all but the rarest use cases an argument will NOT be passed to the method 24824 * 24825 * > **NOTE**: The video must have started loading before the duration can be 24826 * known, and in the case of Flash, may not be known until the video starts 24827 * playing. 24828 * 24829 * @fires Player#durationchange 24830 * 24831 * @param {number} [seconds] 24832 * The duration of the video to set in seconds 24833 * 24834 * @return {number} 24835 * - The duration of the video in seconds when getting 24836 */ 24837 ; 24838 24839 _proto.duration = function duration(seconds) { 24840 if (seconds === undefined) { 24841 // return NaN if the duration is not known 24842 return this.cache_.duration !== undefined ? this.cache_.duration : NaN; 24843 } 24844 24845 seconds = parseFloat(seconds); // Standardize on Infinity for signaling video is live 24846 24847 if (seconds < 0) { 24848 seconds = Infinity; 24849 } 24850 24851 if (seconds !== this.cache_.duration) { 24852 // Cache the last set value for optimized scrubbing (esp. Flash) 24853 this.cache_.duration = seconds; 24854 24855 if (seconds === Infinity) { 24856 this.addClass('vjs-live'); 24857 24858 if (this.options_.liveui && this.player_.liveTracker) { 24859 this.addClass('vjs-liveui'); 24860 } 24861 } else { 24862 this.removeClass('vjs-live'); 24863 this.removeClass('vjs-liveui'); 24864 } 24865 24866 if (!isNaN(seconds)) { 24867 // Do not fire durationchange unless the duration value is known. 24868 // @see [Spec]{@link https://www.w3.org/TR/2011/WD-html5-20110113/video.html#media-element-load-algorithm} 24869 24870 /** 24871 * @event Player#durationchange 24872 * @type {EventTarget~Event} 24873 */ 24874 this.trigger('durationchange'); 24875 } 24876 } 24877 } 24878 /** 24879 * Calculates how much time is left in the video. Not part 24880 * of the native video API. 24881 * 24882 * @return {number} 24883 * The time remaining in seconds 24884 */ 24885 ; 24886 24887 _proto.remainingTime = function remainingTime() { 24888 return this.duration() - this.currentTime(); 24889 } 24890 /** 24891 * A remaining time function that is intented to be used when 24892 * the time is to be displayed directly to the user. 24893 * 24894 * @return {number} 24895 * The rounded time remaining in seconds 24896 */ 24897 ; 24898 24899 _proto.remainingTimeDisplay = function remainingTimeDisplay() { 24900 return Math.floor(this.duration()) - Math.floor(this.currentTime()); 24901 } // 24902 // Kind of like an array of portions of the video that have been downloaded. 24903 24904 /** 24905 * Get a TimeRange object with an array of the times of the video 24906 * that have been downloaded. If you just want the percent of the 24907 * video that's been downloaded, use bufferedPercent. 24908 * 24909 * @see [Buffered Spec]{@link http://dev.w3.org/html5/spec/video.html#dom-media-buffered} 24910 * 24911 * @return {TimeRange} 24912 * A mock TimeRange object (following HTML spec) 24913 */ 24914 ; 24915 24916 _proto.buffered = function buffered() { 24917 var buffered = this.techGet_('buffered'); 24918 24919 if (!buffered || !buffered.length) { 24920 buffered = createTimeRanges(0, 0); 24921 } 24922 24923 return buffered; 24924 } 24925 /** 24926 * Get the percent (as a decimal) of the video that's been downloaded. 24927 * This method is not a part of the native HTML video API. 24928 * 24929 * @return {number} 24930 * A decimal between 0 and 1 representing the percent 24931 * that is buffered 0 being 0% and 1 being 100% 24932 */ 24933 ; 24934 24935 _proto.bufferedPercent = function bufferedPercent$$1() { 24936 return bufferedPercent(this.buffered(), this.duration()); 24937 } 24938 /** 24939 * Get the ending time of the last buffered time range 24940 * This is used in the progress bar to encapsulate all time ranges. 24941 * 24942 * @return {number} 24943 * The end of the last buffered time range 24944 */ 24945 ; 24946 24947 _proto.bufferedEnd = function bufferedEnd() { 24948 var buffered = this.buffered(); 24949 var duration = this.duration(); 24950 var end = buffered.end(buffered.length - 1); 24951 24952 if (end > duration) { 24953 end = duration; 24954 } 24955 24956 return end; 24957 } 24958 /** 24959 * Get or set the current volume of the media 24960 * 24961 * @param {number} [percentAsDecimal] 24962 * The new volume as a decimal percent: 24963 * - 0 is muted/0%/off 24964 * - 1.0 is 100%/full 24965 * - 0.5 is half volume or 50% 24966 * 24967 * @return {number} 24968 * The current volume as a percent when getting 24969 */ 24970 ; 24971 24972 _proto.volume = function volume(percentAsDecimal) { 24973 var vol; 24974 24975 if (percentAsDecimal !== undefined) { 24976 // Force value to between 0 and 1 24977 vol = Math.max(0, Math.min(1, parseFloat(percentAsDecimal))); 24978 this.cache_.volume = vol; 24979 this.techCall_('setVolume', vol); 24980 24981 if (vol > 0) { 24982 this.lastVolume_(vol); 24983 } 24984 24985 return; 24986 } // Default to 1 when returning current volume. 24987 24988 24989 vol = parseFloat(this.techGet_('volume')); 24990 return isNaN(vol) ? 1 : vol; 24991 } 24992 /** 24993 * Get the current muted state, or turn mute on or off 24994 * 24995 * @param {boolean} [muted] 24996 * - true to mute 24997 * - false to unmute 24998 * 24999 * @return {boolean} 25000 * - true if mute is on and getting 25001 * - false if mute is off and getting 25002 */ 25003 ; 25004 25005 _proto.muted = function muted(_muted) { 25006 if (_muted !== undefined) { 25007 this.techCall_('setMuted', _muted); 25008 return; 25009 } 25010 25011 return this.techGet_('muted') || false; 25012 } 25013 /** 25014 * Get the current defaultMuted state, or turn defaultMuted on or off. defaultMuted 25015 * indicates the state of muted on initial playback. 25016 * 25017 * ```js
25018 * var myPlayer = videojs('some-player-id'); 25019 * 25020 * myPlayer.src("http://www.example.com/path/to/video.mp4"); 25021 * 25022 * // get, should be false 25023 * console.log(myPlayer.defaultMuted()); 25024 * // set to true 25025 * myPlayer.defaultMuted(true); 25026 * // get should be true 25027 * console.log(myPlayer.defaultMuted()); 25028 * ``` 25029 * 25030 * @param {boolean} [defaultMuted] 25031 * - true to mute 25032 * - false to unmute 25033 * 25034 * @return {boolean|Player} 25035 * - true if defaultMuted is on and getting 25036 * - false if defaultMuted is off and getting 25037 * - A reference to the current player when setting 25038 */ 25039 ; 25040 25041 _proto.defaultMuted = function defaultMuted(_defaultMuted) { 25042 if (_defaultMuted !== undefined) { 25043 return this.techCall_('setDefaultMuted', _defaultMuted); 25044 } 25045 25046 return this.techGet_('defaultMuted') || false; 25047 } 25048 /** 25049 * Get the last volume, or set it 25050 * 25051 * @param {number} [percentAsDecimal] 25052 * The new last volume as a decimal percent: 25053 * - 0 is muted/0%/off 25054 * - 1.0 is 100%/full 25055 * - 0.5 is half volume or 50% 25056 * 25057 * @return {number} 25058 * the current value of lastVolume as a percent when getting 25059 * 25060 * @private 25061 */ 25062 ; 25063 25064 _proto.lastVolume_ = function lastVolume_(percentAsDecimal) { 25065 if (percentAsDecimal !== undefined && percentAsDecimal !== 0) { 25066 this.cache_.lastVolume = percentAsDecimal; 25067 return; 25068 } 25069 25070 return this.cache_.lastVolume; 25071 } 25072 /** 25073 * Check if current tech can support native fullscreen 25074 * (e.g. with built in controls like iOS, so not our flash swf) 25075 * 25076 * @return {boolean} 25077 * if native fullscreen is supported 25078 */ 25079 ; 25080 25081 _proto.supportsFullScreen = function supportsFullScreen() { 25082 return this.techGet_('supportsFullScreen') || false; 25083 } 25084 /** 25085 * Check if the player is in fullscreen mode or tell the player that it 25086 * is or is not in fullscreen mode. 25087 * 25088 * > NOTE: As of the latest HTML5 spec, isFullscreen is no longer an official 25089 * property and instead document.fullscreenElement is used. But isFullscreen is 25090 * still a valuable property for internal player workings. 25091 * 25092 * @param {boolean} [isFS] 25093 * Set the players current fullscreen state 25094 * 25095 * @return {boolean} 25096 * - true if fullscreen is on and getting 25097 * - false if fullscreen is off and getting 25098 */ 25099 ; 25100 25101 _proto.isFullscreen = function isFullscreen(isFS) { 25102 if (isFS !== undefined) { 25103 this.isFullscreen_ = !!isFS; 25104 this.toggleFullscreenClass_(); 25105 return; 25106 } 25107 25108 if (prefixedAPI) { 25109 var fsApi = FullscreenApi; 25110 var el = this.el(); 25111 var isFs = document[fsApi.fullscreenElement] === el; 25112 25113 if (!isFs && el.matches) { 25114 isFs = el.matches(':' + fsApi.fullscreen); 25115 } else if (!isFs && el.msMatchesSelector) { 25116 isFs = el.msMatchesSelector(':' + fsApi.fullscreen); 25117 } 25118 25119 return isFs; 25120 } 25121 25122 return !!this.isFullscreen_; 25123 } 25124 /** 25125 * Increase the size of the video to full screen 25126 * In some browsers, full screen is not supported natively, so it enters 25127 * "full window mode", where the video fills the browser window. 25128 * In browsers and devices that support native full screen, sometimes the 25129 * browser's default controls will be shown, and not the Video.js custom skin. 25130 * This includes most mobile devices (iOS, Android) and older versions of 25131 * Safari. 25132 * 25133 * @fires Player#fullscreenchange 25134 */ 25135 ; 25136 25137 _proto.requestFullscreen = function requestFullscreen() { 25138 var fsApi = FullscreenApi; 25139 this.isFullscreen(true); 25140 25141 if (fsApi.requestFullscreen) { 25142 // the browser supports going fullscreen at the element level so we can 25143 // take the controls fullscreen as well as the video 25144 // Trigger fullscreenchange event after change 25145 // We have to specifically add this each time, and remove 25146 // when canceling fullscreen. Otherwise if there's multiple 25147 // players on a page, they would all be reacting to the same fullscreen 25148 // events 25149 on(document, fsApi.fullscreenchange, this.boundDocumentFullscreenChange_); 25150 this.el_[fsApi.requestFullscreen](); 25151 } else if (this.tech_.supportsFullScreen()) { 25152 // we can't take the video.js controls fullscreen but we can go fullscreen 25153 // with native controls 25154 this.techCall_('enterFullScreen'); 25155 } else { 25156 // fullscreen isn't supported so we'll just stretch the video element to 25157 // fill the viewport 25158 this.enterFullWindow(); 25159 /** 25160 * @event Player#fullscreenchange 25161 * @type {EventTarget~Event} 25162 */ 25163 25164 this.trigger('fullscreenchange'); 25165 } 25166 } 25167 /** 25168 * Return the video to its normal size after having been in full screen mode 25169 * 25170 * @fires Player#fullscreenchange 25171 */ 25172 ; 25173 25174 _proto.exitFullscreen = function exitFullscreen() { 25175 var fsApi = FullscreenApi; 25176 this.isFullscreen(false); // Check for browser element fullscreen support 25177 25178 if (fsApi.requestFullscreen) { 25179 document[fsApi.exitFullscreen](); 25180 } else if (this.tech_.supportsFullScreen()) { 25181 this.techCall_('exitFullScreen'); 25182 } else { 25183 this.exitFullWindow(); 25184 /** 25185 * @event Player#fullscreenchange 25186 * @type {EventTarget~Event} 25187 */ 25188 25189 this.trigger('fullscreenchange'); 25190 } 25191 } 25192 /** 25193 * When fullscreen isn't supported we can stretch the 25194 * video container to as wide as the browser will let us. 25195 * 25196 * @fires Player#enterFullWindow 25197 */ 25198 ; 25199 25200 _proto.enterFullWindow = function enterFullWindow() { 25201 this.isFullWindow = true; // Storing original doc overflow value to return to when fullscreen is off 25202 25203 this.docOrigOverflow = document.documentElement.style.overflow; // Add listener for esc key to exit fullscreen 25204 25205 on(document, 'keydown', this.boundFullWindowOnEscKey_); // Hide any scroll bars 25206 25207 document.documentElement.style.overflow = 'hidden'; // Apply fullscreen styles 25208 25209 addClass(document.body, 'vjs-full-window'); 25210 /** 25211 * @event Player#enterFullWindow 25212 * @type {EventTarget~Event} 25213 */ 25214 25215 this.trigger('enterFullWindow'); 25216 } 25217 /** 25218 * Check for call to either exit full window or 25219 * full screen on ESC key 25220 * 25221 * @param {string} event 25222 * Event to check for key press 25223 */ 25224 ; 25225 25226 _proto.fullWindowOnEscKey = function fullWindowOnEscKey(event) { 25227 if (keycode.isEventKey(event, 'Esc')) { 25228 if (this.isFullscreen() === true) { 25229 this.exitFullscreen(); 25230 } else { 25231 this.exitFullWindow(); 25232 } 25233 } 25234 } 25235 /** 25236 * Exit full window 25237 * 25238 * @fires Player#exitFullWindow 25239 */ 25240 ; 25241 25242 _proto.exitFullWindow = function exitFullWindow() { 25243 this.isFullWindow = false;
vendor: 47,989 bytes, lines 25244-26757
25244 off(document, 'keydown', this.boundFullWindowOnEscKey_); // Unhide scroll bars. 25245 25246 document.documentElement.style.overflow = this.docOrigOverflow; // Remove fullscreen styles 25247 25248 removeClass(document.body, 'vjs-full-window'); // Resize the box, controller, and poster to original sizes 25249 // this.positionAll(); 25250 25251 /** 25252 * @event Player#exitFullWindow 25253 * @type {EventTarget~Event} 25254 */ 25255 25256 this.trigger('exitFullWindow'); 25257 } 25258 /** 25259 * Called when this Player has focus and a key gets pressed down, or when 25260 * any Component of this player receives a key press that it doesn't handle. 25261 * This allows player-wide hotkeys (either as defined below, or optionally 25262 * by an external function). 25263 * 25264 * @param {EventTarget~Event} event 25265 * The `keydown` event that caused this function to be called. 25266 * 25267 * @listens keydown 25268 */ 25269 ; 25270 25271 _proto.handleKeyDown = function handleKeyDown(event) { 25272 var userActions = this.options_.userActions; // Bail out if hotkeys are not configured. 25273 25274 if (!userActions || !userActions.hotkeys) { 25275 return; 25276 } // Function that determines whether or not to exclude an element from 25277 // hotkeys handling. 25278 25279 25280 var excludeElement = function excludeElement(el) { 25281 var tagName = el.tagName.toLowerCase(); // These tags will be excluded entirely. 25282 25283 var excludedTags = ['textarea']; // Inputs matching these types will still trigger hotkey handling as 25284 // they are not text inputs. 25285 25286 var allowedInputTypes = ['button', 'checkbox', 'hidden', 'radio', 'reset', 'submit']; 25287 25288 if (tagName === 'input') { 25289 return allowedInputTypes.indexOf(el.type) === -1; 25290 } 25291 25292 return excludedTags.indexOf(tagName) !== -1; 25293 }; // Bail out if the user is focused on an interactive form element. 25294 25295 25296 if (excludeElement(this.el_.ownerDocument.activeElement)) { 25297 return; 25298 } 25299 25300 if (typeof userActions.hotkeys === 'function') { 25301 userActions.hotkeys.call(this, event); 25302 } else { 25303 this.handleHotkeys(event); 25304 } 25305 } 25306 /** 25307 * Called when this Player receives a hotkey keydown event. 25308 * Supported player-wide hotkeys are: 25309 * 25310 * f - toggle fullscreen 25311 * m - toggle mute 25312 * k or Space - toggle play/pause 25313 * 25314 * @param {EventTarget~Event} event 25315 * The `keydown` event that caused this function to be called. 25316 */ 25317 ; 25318 25319 _proto.handleHotkeys = function handleHotkeys(event) { 25320 var hotkeys = this.options_.userActions ? this.options_.userActions.hotkeys : {}; // set fullscreenKey, muteKey, playPauseKey from `hotkeys`, use defaults if not set 25321 25322 var _hotkeys$fullscreenKe = hotkeys.fullscreenKey, 25323 fullscreenKey = _hotkeys$fullscreenKe === void 0 ? function (keydownEvent) { 25324 return keycode.isEventKey(keydownEvent, 'f'); 25325 } : _hotkeys$fullscreenKe, 25326 _hotkeys$muteKey = hotkeys.muteKey, 25327 muteKey = _hotkeys$muteKey === void 0 ? function (keydownEvent) { 25328 return keycode.isEventKey(keydownEvent, 'm'); 25329 } : _hotkeys$muteKey, 25330 _hotkeys$playPauseKey = hotkeys.playPauseKey, 25331 playPauseKey = _hotkeys$playPauseKey === void 0 ? function (keydownEvent) { 25332 return keycode.isEventKey(keydownEvent, 'k') || keycode.isEventKey(keydownEvent, 'Space'); 25333 } : _hotkeys$playPauseKey; 25334 25335 if (fullscreenKey.call(this, event)) { 25336 event.preventDefault(); 25337 event.stopPropagation(); 25338 var FSToggle = Component.getComponent('FullscreenToggle'); 25339 25340 if (document[FullscreenApi.fullscreenEnabled] !== false) { 25341 FSToggle.prototype.handleClick.call(this); 25342 } 25343 } else if (muteKey.call(this, event)) { 25344 event.preventDefault(); 25345 event.stopPropagation(); 25346 var MuteToggle = Component.getComponent('MuteToggle'); 25347 MuteToggle.prototype.handleClick.call(this); 25348 } else if (playPauseKey.call(this, event)) { 25349 event.preventDefault(); 25350 event.stopPropagation(); 25351 var PlayToggle = Component.getComponent('PlayToggle'); 25352 PlayToggle.prototype.handleClick.call(this); 25353 } 25354 } 25355 /** 25356 * Check whether the player can play a given mimetype 25357 * 25358 * @see https://www.w3.org/TR/2011/WD-html5-20110113/video.html#dom-navigator-canplaytype 25359 * 25360 * @param {string} type 25361 * The mimetype to check 25362 * 25363 * @return {string} 25364 * 'probably', 'maybe', or '' (empty string) 25365 */ 25366 ; 25367 25368 _proto.canPlayType = function canPlayType(type) { 25369 var can; // Loop through each playback technology in the options order 25370 25371 for (var i = 0, j = this.options_.techOrder; i < j.length; i++) { 25372 var techName = j[i]; 25373 var tech = Tech.getTech(techName); // Support old behavior of techs being registered as components. 25374 // Remove once that deprecated behavior is removed. 25375 25376 if (!tech) { 25377 tech = Component.getComponent(techName); 25378 } // Check if the current tech is defined before continuing 25379 25380 25381 if (!tech) { 25382 log.error("The \"" + techName + "\" tech is undefined. Skipped browser support check for that tech."); 25383 continue; 25384 } // Check if the browser supports this technology 25385 25386 25387 if (tech.isSupported()) { 25388 can = tech.canPlayType(type); 25389 25390 if (can) { 25391 return can; 25392 } 25393 } 25394 } 25395 25396 return ''; 25397 } 25398 /** 25399 * Select source based on tech-order or source-order 25400 * Uses source-order selection if `options.sourceOrder` is truthy. Otherwise, 25401 * defaults to tech-order selection 25402 * 25403 * @param {Array} sources 25404 * The sources for a media asset 25405 * 25406 * @return {Object|boolean} 25407 * Object of source and tech order or false 25408 */ 25409 ; 25410 25411 _proto.selectSource = function selectSource(sources) { 25412 var _this10 = this; 25413 25414 // Get only the techs specified in `techOrder` that exist and are supported by the 25415 // current platform 25416 var techs = this.options_.techOrder.map(function (techName) { 25417 return [techName, Tech.getTech(techName)]; 25418 }).filter(function (_ref) { 25419 var techName = _ref[0], 25420 tech = _ref[1]; 25421 25422 // Check if the current tech is defined before continuing 25423 if (tech) { 25424 // Check if the browser supports this technology 25425 return tech.isSupported(); 25426 } 25427 25428 log.error("The \"" + techName + "\" tech is undefined. Skipped browser support check for that tech."); 25429 return false; 25430 }); // Iterate over each `innerArray` element once per `outerArray` element and execute 25431 // `tester` with both. If `tester` returns a non-falsy value, exit early and return 25432 // that value. 25433 25434 var findFirstPassingTechSourcePair = function findFirstPassingTechSourcePair(outerArray, innerArray, tester) { 25435 var found; 25436 outerArray.some(function (outerChoice) { 25437 return innerArray.some(function (innerChoice) { 25438 found = tester(outerChoice, innerChoice); 25439 25440 if (found) { 25441 return true; 25442 } 25443 }); 25444 }); 25445 return found; 25446 }; 25447 25448 var foundSourceAndTech; 25449 25450 var flip = function flip(fn) { 25451 return function (a, b) { 25452 return fn(b, a); 25453 }; 25454 }; 25455 25456 var finder = function finder(_ref2, source) { 25457 var techName = _ref2[0], 25458 tech = _ref2[1]; 25459 25460 if (tech.canPlaySource(source, _this10.options_[techName.toLowerCase()])) { 25461 return { 25462 source: source, 25463 tech: techName 25464 }; 25465 } 25466 }; // Depending on the truthiness of `options.sourceOrder`, we swap the order of techs and sources 25467 // to select from them based on their priority. 25468 25469 25470 if (this.options_.sourceOrder) { 25471 // Source-first ordering 25472 foundSourceAndTech = findFirstPassingTechSourcePair(sources, techs, flip(finder)); 25473 } else { 25474 // Tech-first ordering 25475 foundSourceAndTech = findFirstPassingTechSourcePair(techs, sources, finder); 25476 } 25477 25478 return foundSourceAndTech || false; 25479 } 25480 /** 25481 * Get or set the video source. 25482 * 25483 * @param {Tech~SourceObject|Tech~SourceObject[]|string} [source] 25484 * A SourceObject, an array of SourceObjects, or a string referencing 25485 * a URL to a media source. It is _highly recommended_ that an object 25486 * or array of objects is used here, so that source selection 25487 * algorithms can take the `type` into account. 25488 * 25489 * If not provided, this method acts as a getter. 25490 * 25491 * @return {string|undefined} 25492 * If the `source` argument is missing, returns the current source 25493 * URL. Otherwise, returns nothing/undefined. 25494 */ 25495 ; 25496 25497 _proto.src = function src(source) { 25498 var _this11 = this; 25499 25500 // getter usage 25501 if (typeof source === 'undefined') { 25502 return this.cache_.src || ''; 25503 } // filter out invalid sources and turn our source into 25504 // an array of source objects 25505 25506 25507 var sources = filterSource(source); // if a source was passed in then it is invalid because 25508 // it was filtered to a zero length Array. So we have to 25509 // show an error 25510 25511 if (!sources.length) { 25512 this.setTimeout(function () { 25513 this.error({ 25514 code: 4, 25515 message: this.localize(this.options_.notSupportedMessage) 25516 }); 25517 }, 0); 25518 return; 25519 } // intial sources 25520 25521 25522 this.changingSrc_ = true; 25523 this.cache_.sources = sources; 25524 this.updateSourceCaches_(sources[0]); // middlewareSource is the source after it has been changed by middleware 25525 25526 setSource(this, sources[0], function (middlewareSource, mws) { 25527 _this11.middleware_ = mws; // since sourceSet is async we have to update the cache again after we select a source since 25528 // the source that is selected could be out of order from the cache update above this callback. 25529 25530 _this11.cache_.sources = sources; 25531 25532 _this11.updateSourceCaches_(middlewareSource); 25533 25534 var err = _this11.src_(middlewareSource); 25535 25536 if (err) { 25537 if (sources.length > 1) { 25538 return _this11.src(sources.slice(1)); 25539 } 25540 25541 _this11.changingSrc_ = false; // We need to wrap this in a timeout to give folks a chance to add error event handlers 25542 25543 _this11.setTimeout(function () { 25544 this.error({ 25545 code: 4, 25546 message: this.localize(this.options_.notSupportedMessage) 25547 }); 25548 }, 0); // we could not find an appropriate tech, but let's still notify the delegate that this is it 25549 // this needs a better comment about why this is needed 25550 25551 25552 _this11.triggerReady(); 25553 25554 return; 25555 } 25556 25557 setTech(mws, _this11.tech_); 25558 }); 25559 } 25560 /** 25561 * Set the source object on the tech, returns a boolean that indicates whether 25562 * there is a tech that can play the source or not 25563 * 25564 * @param {Tech~SourceObject} source 25565 * The source object to set on the Tech 25566 * 25567 * @return {boolean} 25568 * - True if there is no Tech to playback this source 25569 * - False otherwise 25570 * 25571 * @private 25572 */ 25573 ; 25574 25575 _proto.src_ = function src_(source) { 25576 var _this12 = this; 25577 25578 var sourceTech = this.selectSource([source]); 25579 25580 if (!sourceTech) { 25581 return true; 25582 } 25583 25584 if (!titleCaseEquals(sourceTech.tech, this.techName_)) { 25585 this.changingSrc_ = true; // load this technology with the chosen source 25586 25587 this.loadTech_(sourceTech.tech, sourceTech.source); 25588 this.tech_.ready(function () { 25589 _this12.changingSrc_ = false; 25590 }); 25591 return false; 25592 } // wait until the tech is ready to set the source 25593 // and set it synchronously if possible (#2326) 25594 25595 25596 this.ready(function () { 25597 // The setSource tech method was added with source handlers 25598 // so older techs won't support it 25599 // We need to check the direct prototype for the case where subclasses 25600 // of the tech do not support source handlers 25601 if (this.tech_.constructor.prototype.hasOwnProperty('setSource')) { 25602 this.techCall_('setSource', source); 25603 } else { 25604 this.techCall_('src', source.src); 25605 } 25606 25607 this.changingSrc_ = false; 25608 }, true); 25609 return false; 25610 } 25611 /** 25612 * Begin loading the src data. 25613 */ 25614 ; 25615 25616 _proto.load = function load() { 25617 this.techCall_('load'); 25618 } 25619 /** 25620 * Reset the player. Loads the first tech in the techOrder, 25621 * removes all the text tracks in the existing `tech`, 25622 * and calls `reset` on the `tech`. 25623 */ 25624 ; 25625 25626 _proto.reset = function reset() { 25627 var _this13 = this; 25628 25629 var PromiseClass = this.options_.Promise || window$1.Promise; 25630 25631 if (this.paused() || !PromiseClass) { 25632 this.doReset_(); 25633 } else { 25634 var playPromise = this.play(); 25635 silencePromise(playPromise.then(function () { 25636 return _this13.doReset_(); 25637 })); 25638 } 25639 }; 25640 25641 _proto.doReset_ = function doReset_() { 25642 if (this.tech_) { 25643 this.tech_.clearTracks('text'); 25644 } 25645 25646 this.resetCache_(); 25647 this.poster(''); 25648 this.loadTech_(this.options_.techOrder[0], null); 25649 this.techCall_('reset'); 25650 this.resetControlBarUI_(); 25651 25652 if (isEvented(this)) { 25653 this.trigger('playerreset'); 25654 } 25655 } 25656 /** 25657 * Reset Control Bar's UI by calling sub-methods that reset 25658 * all of Control Bar's components 25659 */ 25660 ; 25661 25662 _proto.resetControlBarUI_ = function resetControlBarUI_() { 25663 this.resetProgressBar_(); 25664 this.resetPlaybackRate_(); 25665 this.resetVolumeBar_(); 25666 } 25667 /** 25668 * Reset tech's progress so progress bar is reset in the UI 25669 */ 25670 ; 25671 25672 _proto.resetProgressBar_ = function resetProgressBar_() { 25673 this.currentTime(0); 25674 var _this$controlBar = this.controlBar, 25675 durationDisplay = _this$controlBar.durationDisplay, 25676 remainingTimeDisplay = _this$controlBar.remainingTimeDisplay; 25677 25678 if (durationDisplay) { 25679 durationDisplay.updateContent(); 25680 } 25681 25682 if (remainingTimeDisplay) { 25683 remainingTimeDisplay.updateContent(); 25684 } 25685 } 25686 /** 25687 * Reset Playback ratio 25688 */ 25689 ; 25690 25691 _proto.resetPlaybackRate_ = function resetPlaybackRate_() { 25692 this.playbackRate(this.defaultPlaybackRate()); 25693 this.handleTechRateChange_(); 25694 } 25695 /** 25696 * Reset Volume bar 25697 */ 25698 ; 25699 25700 _proto.resetVolumeBar_ = function resetVolumeBar_() { 25701 this.volume(1.0); 25702 this.trigger('volumechange'); 25703 } 25704 /** 25705 * Returns all of the current source objects. 25706 * 25707 * @return {Tech~SourceObject[]} 25708 * The current source objects 25709 */ 25710 ; 25711 25712 _proto.currentSources = function currentSources() { 25713 var source = this.currentSource(); 25714 var sources = []; // assume `{}` or `{ src }` 25715 25716 if (Object.keys(source).length !== 0) { 25717 sources.push(source); 25718 } 25719 25720 return this.cache_.sources || sources; 25721 } 25722 /** 25723 * Returns the current source object. 25724 * 25725 * @return {Tech~SourceObject} 25726 * The current source object 25727 */ 25728 ; 25729 25730 _proto.currentSource = function currentSource() { 25731 return this.cache_.source || {}; 25732 } 25733 /** 25734 * Returns the fully qualified URL of the current source value e.g. http://mysite.com/video.mp4 25735 * Can be used in conjunction with `currentType` to assist in rebuilding the current source object. 25736 * 25737 * @return {string} 25738 * The current source 25739 */ 25740 ; 25741 25742 _proto.currentSrc = function currentSrc() { 25743 return this.currentSource() && this.currentSource().src || ''; 25744 } 25745 /** 25746 * Get the current source type e.g. video/mp4 25747 * This can allow you rebuild the current source object so that you could load the same 25748 * source and tech later 25749 * 25750 * @return {string} 25751 * The source MIME type 25752 */ 25753 ; 25754 25755 _proto.currentType = function currentType() { 25756 return this.currentSource() && this.currentSource().type || ''; 25757 } 25758 /** 25759 * Get or set the preload attribute 25760 * 25761 * @param {boolean} [value] 25762 * - true means that we should preload 25763 * - false means that we should not preload 25764 * 25765 * @return {string} 25766 * The preload attribute value when getting 25767 */ 25768 ; 25769 25770 _proto.preload = function preload(value) { 25771 if (value !== undefined) { 25772 this.techCall_('setPreload', value); 25773 this.options_.preload = value; 25774 return; 25775 } 25776 25777 return this.techGet_('preload'); 25778 } 25779 /** 25780 * Get or set the autoplay option. When this is a boolean it will 25781 * modify the attribute on the tech. When this is a string the attribute on 25782 * the tech will be removed and `Player` will handle autoplay on loadstarts. 25783 * 25784 * @param {boolean|string} [value] 25785 * - true: autoplay using the browser behavior 25786 * - false: do not autoplay 25787 * - 'play': call play() on every loadstart 25788 * - 'muted': call muted() then play() on every loadstart 25789 * - 'any': call play() on every loadstart. if that fails call muted() then play(). 25790 * - *: values other than those listed here will be set `autoplay` to true 25791 * 25792 * @return {boolean|string} 25793 * The current value of autoplay when getting 25794 */ 25795 ; 25796 25797 _proto.autoplay = function autoplay(value) { 25798 // getter usage 25799 if (value === undefined) { 25800 return this.options_.autoplay || false; 25801 } 25802 25803 var techAutoplay; // if the value is a valid string set it to that 25804 25805 if (typeof value === 'string' && /(any|play|muted)/.test(value)) { 25806 this.options_.autoplay = value; 25807 this.manualAutoplay_(value); 25808 techAutoplay = false; // any falsy value sets autoplay to false in the browser, 25809 // lets do the same 25810 } else if (!value) { 25811 this.options_.autoplay = false; // any other value (ie truthy) sets autoplay to true 25812 } else { 25813 this.options_.autoplay = true; 25814 } 25815 25816 techAutoplay = typeof techAutoplay === 'undefined' ? this.options_.autoplay : techAutoplay; // if we don't have a tech then we do not queue up 25817 // a setAutoplay call on tech ready. We do this because the 25818 // autoplay option will be passed in the constructor and we 25819 // do not need to set it twice 25820 25821 if (this.tech_) { 25822 this.techCall_('setAutoplay', techAutoplay); 25823 } 25824 } 25825 /** 25826 * Set or unset the playsinline attribute. 25827 * Playsinline tells the browser that non-fullscreen playback is preferred. 25828 * 25829 * @param {boolean} [value] 25830 * - true means that we should try to play inline by default 25831 * - false means that we should use the browser's default playback mode, 25832 * which in most cases is inline. iOS Safari is a notable exception 25833 * and plays fullscreen by default. 25834 * 25835 * @return {string|Player} 25836 * - the current value of playsinline 25837 * - the player when setting 25838 * 25839 * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline} 25840 */ 25841 ; 25842 25843 _proto.playsinline = function playsinline(value) { 25844 if (value !== undefined) { 25845 this.techCall_('setPlaysinline', value); 25846 this.options_.playsinline = value; 25847 return this; 25848 } 25849 25850 return this.techGet_('playsinline'); 25851 } 25852 /** 25853 * Get or set the loop attribute on the video element. 25854 * 25855 * @param {boolean} [value] 25856 * - true means that we should loop the video 25857 * - false means that we should not loop the video 25858 * 25859 * @return {boolean} 25860 * The current value of loop when getting 25861 */ 25862 ; 25863 25864 _proto.loop = function loop(value) { 25865 if (value !== undefined) { 25866 this.techCall_('setLoop', value); 25867 this.options_.loop = value; 25868 return; 25869 } 25870 25871 return this.techGet_('loop'); 25872 } 25873 /** 25874 * Get or set the poster image source url 25875 * 25876 * @fires Player#posterchange 25877 * 25878 * @param {string} [src] 25879 * Poster image source URL 25880 * 25881 * @return {string} 25882 * The current value of poster when getting 25883 */ 25884 ; 25885 25886 _proto.poster = function poster(src) { 25887 if (src === undefined) { 25888 return this.poster_; 25889 } // The correct way to remove a poster is to set as an empty string 25890 // other falsey values will throw errors 25891 25892 25893 if (!src) { 25894 src = ''; 25895 } 25896 25897 if (src === this.poster_) { 25898 return; 25899 } // update the internal poster variable 25900 25901 25902 this.poster_ = src; // update the tech's poster 25903 25904 this.techCall_('setPoster', src); 25905 this.isPosterFromTech_ = false; // alert components that the poster has been set 25906 25907 /** 25908 * This event fires when the poster image is changed on the player. 25909 * 25910 * @event Player#posterchange 25911 * @type {EventTarget~Event} 25912 */ 25913 25914 this.trigger('posterchange'); 25915 } 25916 /** 25917 * Some techs (e.g. YouTube) can provide a poster source in an 25918 * asynchronous way. We want the poster component to use this 25919 * poster source so that it covers up the tech's controls. 25920 * (YouTube's play button). However we only want to use this 25921 * source if the player user hasn't set a poster through 25922 * the normal APIs. 25923 * 25924 * @fires Player#posterchange 25925 * @listens Tech#posterchange 25926 * @private 25927 */ 25928 ; 25929 25930 _proto.handleTechPosterChange_ = function handleTechPosterChange_() { 25931 if ((!this.poster_ || this.options_.techCanOverridePoster) && this.tech_ && this.tech_.poster) { 25932 var newPoster = this.tech_.poster() || ''; 25933 25934 if (newPoster !== this.poster_) { 25935 this.poster_ = newPoster; 25936 this.isPosterFromTech_ = true; // Let components know the poster has changed 25937 25938 this.trigger('posterchange'); 25939 } 25940 } 25941 } 25942 /** 25943 * Get or set whether or not the controls are showing. 25944 * 25945 * @fires Player#controlsenabled 25946 * 25947 * @param {boolean} [bool] 25948 * - true to turn controls on 25949 * - false to turn controls off 25950 * 25951 * @return {boolean} 25952 * The current value of controls when getting 25953 */ 25954 ; 25955 25956 _proto.controls = function controls(bool) { 25957 if (bool === undefined) { 25958 return !!this.controls_; 25959 } 25960 25961 bool = !!bool; // Don't trigger a change event unless it actually changed 25962 25963 if (this.controls_ === bool) { 25964 return; 25965 } 25966 25967 this.controls_ = bool; 25968 25969 if (this.usingNativeControls()) { 25970 this.techCall_('setControls', bool); 25971 } 25972 25973 if (this.controls_) { 25974 this.removeClass('vjs-controls-disabled'); 25975 this.addClass('vjs-controls-enabled'); 25976 /** 25977 * @event Player#controlsenabled 25978 * @type {EventTarget~Event} 25979 */ 25980 25981 this.trigger('controlsenabled'); 25982 25983 if (!this.usingNativeControls()) { 25984 this.addTechControlsListeners_(); 25985 } 25986 } else { 25987 this.removeClass('vjs-controls-enabled'); 25988 this.addClass('vjs-controls-disabled'); 25989 /** 25990 * @event Player#controlsdisabled 25991 * @type {EventTarget~Event} 25992 */ 25993 25994 this.trigger('controlsdisabled'); 25995 25996 if (!this.usingNativeControls()) { 25997 this.removeTechControlsListeners_(); 25998 } 25999 } 26000 } 26001 /** 26002 * Toggle native controls on/off. Native controls are the controls built into 26003 * devices (e.g. default iPhone controls), Flash, or other techs 26004 * (e.g. Vimeo Controls) 26005 * **This should only be set by the current tech, because only the tech knows 26006 * if it can support native controls** 26007 * 26008 * @fires Player#usingnativecontrols 26009 * @fires Player#usingcustomcontrols 26010 * 26011 * @param {boolean} [bool] 26012 * - true to turn native controls on 26013 * - false to turn native controls off 26014 * 26015 * @return {boolean} 26016 * The current value of native controls when getting 26017 */ 26018 ; 26019 26020 _proto.usingNativeControls = function usingNativeControls(bool) { 26021 if (bool === undefined) { 26022 return !!this.usingNativeControls_; 26023 } 26024 26025 bool = !!bool; // Don't trigger a change event unless it actually changed 26026 26027 if (this.usingNativeControls_ === bool) { 26028 return; 26029 } 26030 26031 this.usingNativeControls_ = bool; 26032 26033 if (this.usingNativeControls_) { 26034 this.addClass('vjs-using-native-controls'); 26035 /** 26036 * player is using the native device controls 26037 * 26038 * @event Player#usingnativecontrols 26039 * @type {EventTarget~Event} 26040 */ 26041 26042 this.trigger('usingnativecontrols'); 26043 } else { 26044 this.removeClass('vjs-using-native-controls'); 26045 /** 26046 * player is using the custom HTML controls 26047 * 26048 * @event Player#usingcustomcontrols 26049 * @type {EventTarget~Event} 26050 */ 26051 26052 this.trigger('usingcustomcontrols'); 26053 } 26054 } 26055 /** 26056 * Set or get the current MediaError 26057 * 26058 * @fires Player#error 26059 * 26060 * @param {MediaError|string|number} [err] 26061 * A MediaError or a string/number to be turned 26062 * into a MediaError 26063 * 26064 * @return {MediaError|null} 26065 * The current MediaError when getting (or null) 26066 */ 26067 ; 26068 26069 _proto.error = function error(err) { 26070 if (err === undefined) { 26071 return this.error_ || null; 26072 } // restoring to default 26073 26074 26075 if (err === null) { 26076 this.error_ = err; 26077 this.removeClass('vjs-error'); 26078 26079 if (this.errorDisplay) { 26080 this.errorDisplay.close(); 26081 } 26082 26083 return; 26084 } 26085 26086 this.error_ = new MediaError(err); // add the vjs-error classname to the player 26087 26088 this.addClass('vjs-error'); // log the name of the error type and any message 26089 // IE11 logs "[object object]" and required you to expand message to see error object 26090 26091 log.error("(CODE:" + this.error_.code + " " + MediaError.errorTypes[this.error_.code] + ")", this.error_.message, this.error_); 26092 /** 26093 * @event Player#error 26094 * @type {EventTarget~Event} 26095 */ 26096 26097 this.trigger('error'); 26098 return; 26099 } 26100 /** 26101 * Report user activity 26102 * 26103 * @param {Object} event 26104 * Event object 26105 */ 26106 ; 26107 26108 _proto.reportUserActivity = function reportUserActivity(event) { 26109 this.userActivity_ = true; 26110 } 26111 /** 26112 * Get/set if user is active 26113 * 26114 * @fires Player#useractive 26115 * @fires Player#userinactive 26116 * 26117 * @param {boolean} [bool] 26118 * - true if the user is active 26119 * - false if the user is inactive 26120 * 26121 * @return {boolean} 26122 * The current value of userActive when getting 26123 */ 26124 ; 26125 26126 _proto.userActive = function userActive(bool) { 26127 if (bool === undefined) { 26128 return this.userActive_; 26129 } 26130 26131 bool = !!bool; 26132 26133 if (bool === this.userActive_) { 26134 return; 26135 } 26136 26137 this.userActive_ = bool; 26138 26139 if (this.userActive_) { 26140 this.userActivity_ = true; 26141 this.removeClass('vjs-user-inactive'); 26142 this.addClass('vjs-user-active'); 26143 /** 26144 * @event Player#useractive 26145 * @type {EventTarget~Event} 26146 */ 26147 26148 this.trigger('useractive'); 26149 return; 26150 } // Chrome/Safari/IE have bugs where when you change the cursor it can 26151 // trigger a mousemove event. This causes an issue when you're hiding 26152 // the cursor when the user is inactive, and a mousemove signals user 26153 // activity. Making it impossible to go into inactive mode. Specifically 26154 // this happens in fullscreen when we really need to hide the cursor. 26155 // 26156 // When this gets resolved in ALL browsers it can be removed 26157 // https://code.google.com/p/chromium/issues/detail?id=103041 26158 26159 26160 if (this.tech_) { 26161 this.tech_.one('mousemove', function (e) { 26162 e.stopPropagation(); 26163 e.preventDefault(); 26164 }); 26165 } 26166 26167 this.userActivity_ = false; 26168 this.removeClass('vjs-user-active'); 26169 this.addClass('vjs-user-inactive'); 26170 /** 26171 * @event Player#userinactive 26172 * @type {EventTarget~Event} 26173 */ 26174 26175 this.trigger('userinactive'); 26176 } 26177 /** 26178 * Listen for user activity based on timeout value 26179 * 26180 * @private 26181 */ 26182 ; 26183 26184 _proto.listenForUserActivity_ = function listenForUserActivity_() { 26185 var mouseInProgress; 26186 var lastMoveX; 26187 var lastMoveY; 26188 var handleActivity = bind(this, this.reportUserActivity); 26189 26190 var handleMouseMove = function handleMouseMove(e) { 26191 // #1068 - Prevent mousemove spamming 26192 // Chrome Bug: https://code.google.com/p/chromium/issues/detail?id=366970 26193 if (e.screenX !== lastMoveX || e.screenY !== lastMoveY) { 26194 lastMoveX = e.screenX; 26195 lastMoveY = e.screenY; 26196 handleActivity(); 26197 } 26198 }; 26199 26200 var handleMouseDown = function handleMouseDown() { 26201 handleActivity(); // For as long as the they are touching the device or have their mouse down, 26202 // we consider them active even if they're not moving their finger or mouse. 26203 // So we want to continue to update that they are active 26204 26205 this.clearInterval(mouseInProgress); // Setting userActivity=true now and setting the interval to the same time 26206 // as the activityCheck interval (250) should ensure we never miss the 26207 // next activityCheck 26208 26209 mouseInProgress = this.setInterval(handleActivity, 250); 26210 }; 26211 26212 var handleMouseUp = function handleMouseUp(event) { 26213 handleActivity(); // Stop the interval that maintains activity if the mouse/touch is down 26214 26215 this.clearInterval(mouseInProgress); 26216 }; // Any mouse movement will be considered user activity 26217 26218 26219 this.on('mousedown', handleMouseDown); 26220 this.on('mousemove', handleMouseMove); 26221 this.on('mouseup', handleMouseUp); 26222 var controlBar = this.getChild('controlBar'); // Fixes bug on Android & iOS where when tapping progressBar (when control bar is displayed) 26223 // controlBar would no longer be hidden by default timeout. 26224 26225 if (controlBar && !IS_IOS && !IS_ANDROID) { 26226 controlBar.on('mouseenter', function (event) { 26227 this.player().cache_.inactivityTimeout = this.player().options_.inactivityTimeout; 26228 this.player().options_.inactivityTimeout = 0; 26229 }); 26230 controlBar.on('mouseleave', function (event) { 26231 this.player().options_.inactivityTimeout = this.player().cache_.inactivityTimeout; 26232 }); 26233 } // Listen for keyboard navigation 26234 // Shouldn't need to use inProgress interval because of key repeat 26235 26236 26237 this.on('keydown', handleActivity); 26238 this.on('keyup', handleActivity); // Run an interval every 250 milliseconds instead of stuffing everything into 26239 // the mousemove/touchmove function itself, to prevent performance degradation. 26240 // `this.reportUserActivity` simply sets this.userActivity_ to true, which 26241 // then gets picked up by this loop 26242 // http://ejohn.org/blog/learning-from-twitter/ 26243 26244 var inactivityTimeout; 26245 this.setInterval(function () { 26246 // Check to see if mouse/touch activity has happened 26247 if (!this.userActivity_) { 26248 return; 26249 } // Reset the activity tracker 26250 26251 26252 this.userActivity_ = false; // If the user state was inactive, set the state to active 26253 26254 this.userActive(true); // Clear any existing inactivity timeout to start the timer over 26255 26256 this.clearTimeout(inactivityTimeout); 26257 var timeout = this.options_.inactivityTimeout; 26258 26259 if (timeout <= 0) { 26260 return; 26261 } // In <timeout> milliseconds, if no more activity has occurred the 26262 // user will be considered inactive 26263 26264 26265 inactivityTimeout = this.setTimeout(function () { 26266 // Protect against the case where the inactivityTimeout can trigger just 26267 // before the next user activity is picked up by the activity check loop 26268 // causing a flicker 26269 if (!this.userActivity_) { 26270 this.userActive(false); 26271 } 26272 }, timeout); 26273 }, 250); 26274 } 26275 /** 26276 * Gets or sets the current playback rate. A playback rate of 26277 * 1.0 represents normal speed and 0.5 would indicate half-speed 26278 * playback, for instance. 26279 * 26280 * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-playbackrate 26281 * 26282 * @param {number} [rate] 26283 * New playback rate to set. 26284 * 26285 * @return {number} 26286 * The current playback rate when getting or 1.0 26287 */ 26288 ; 26289 26290 _proto.playbackRate = function playbackRate(rate) { 26291 if (rate !== undefined) { 26292 // NOTE: this.cache_.lastPlaybackRate is set from the tech handler 26293 // that is registered above 26294 this.techCall_('setPlaybackRate', rate); 26295 return; 26296 } 26297 26298 if (this.tech_ && this.tech_.featuresPlaybackRate) { 26299 return this.cache_.lastPlaybackRate || this.techGet_('playbackRate'); 26300 } 26301 26302 return 1.0; 26303 } 26304 /** 26305 * Gets or sets the current default playback rate. A default playback rate of 26306 * 1.0 represents normal speed and 0.5 would indicate half-speed playback, for instance. 26307 * defaultPlaybackRate will only represent what the initial playbackRate of a video was, not 26308 * not the current playbackRate. 26309 * 26310 * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-defaultplaybackrate 26311 * 26312 * @param {number} [rate] 26313 * New default playback rate to set. 26314 * 26315 * @return {number|Player} 26316 * - The default playback rate when getting or 1.0 26317 * - the player when setting 26318 */ 26319 ; 26320 26321 _proto.defaultPlaybackRate = function defaultPlaybackRate(rate) { 26322 if (rate !== undefined) { 26323 return this.techCall_('setDefaultPlaybackRate', rate); 26324 } 26325 26326 if (this.tech_ && this.tech_.featuresPlaybackRate) { 26327 return this.techGet_('defaultPlaybackRate'); 26328 } 26329 26330 return 1.0; 26331 } 26332 /** 26333 * Gets or sets the audio flag 26334 * 26335 * @param {boolean} bool 26336 * - true signals that this is an audio player 26337 * - false signals that this is not an audio player 26338 * 26339 * @return {boolean} 26340 * The current value of isAudio when getting 26341 */ 26342 ; 26343 26344 _proto.isAudio = function isAudio(bool) { 26345 if (bool !== undefined) { 26346 this.isAudio_ = !!bool; 26347 return; 26348 } 26349 26350 return !!this.isAudio_; 26351 } 26352 /** 26353 * A helper method for adding a {@link TextTrack} to our 26354 * {@link TextTrackList}. 26355 * 26356 * In addition to the W3C settings we allow adding additional info through options. 26357 * 26358 * @see http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-addtexttrack 26359 * 26360 * @param {string} [kind] 26361 * the kind of TextTrack you are adding 26362 * 26363 * @param {string} [label] 26364 * the label to give the TextTrack label 26365 * 26366 * @param {string} [language] 26367 * the language to set on the TextTrack 26368 * 26369 * @return {TextTrack|undefined} 26370 * the TextTrack that was added or undefined 26371 * if there is no tech 26372 */ 26373 ; 26374 26375 _proto.addTextTrack = function addTextTrack(kind, label, language) { 26376 if (this.tech_) { 26377 return this.tech_.addTextTrack(kind, label, language); 26378 } 26379 } 26380 /** 26381 * Create a remote {@link TextTrack} and an {@link HTMLTrackElement}. It will 26382 * automatically removed from the video element whenever the source changes, unless 26383 * manualCleanup is set to false. 26384 * 26385 * @param {Object} options 26386 * Options to pass to {@link HTMLTrackElement} during creation. See 26387 * {@link HTMLTrackElement} for object properties that you should use. 26388 * 26389 * @param {boolean} [manualCleanup=true] if set to false, the TextTrack will be 26390 * 26391 * @return {HtmlTrackElement} 26392 * the HTMLTrackElement that was created and added 26393 * to the HtmlTrackElementList and the remote 26394 * TextTrackList 26395 * 26396 * @deprecated The default value of the "manualCleanup" parameter will default 26397 * to "false" in upcoming versions of Video.js 26398 */ 26399 ; 26400 26401 _proto.addRemoteTextTrack = function addRemoteTextTrack(options, manualCleanup) { 26402 if (this.tech_) { 26403 return this.tech_.addRemoteTextTrack(options, manualCleanup); 26404 } 26405 } 26406 /** 26407 * Remove a remote {@link TextTrack} from the respective 26408 * {@link TextTrackList} and {@link HtmlTrackElementList}. 26409 * 26410 * @param {Object} track 26411 * Remote {@link TextTrack} to remove 26412 * 26413 * @return {undefined} 26414 * does not return anything 26415 */ 26416 ; 26417 26418 _proto.removeRemoteTextTrack = function removeRemoteTextTrack(obj) { 26419 if (obj === void 0) { 26420 obj = {}; 26421 } 26422 26423 var _obj = obj, 26424 track = _obj.track; 26425 26426 if (!track) { 26427 track = obj; 26428 } // destructure the input into an object with a track argument, defaulting to arguments[0] 26429 // default the whole argument to an empty object if nothing was passed in 26430 26431 26432 if (this.tech_) { 26433 return this.tech_.removeRemoteTextTrack(track); 26434 } 26435 } 26436 /** 26437 * Gets available media playback quality metrics as specified by the W3C's Media 26438 * Playback Quality API. 26439 * 26440 * @see [Spec]{@link https://wicg.github.io/media-playback-quality} 26441 * 26442 * @return {Object|undefined} 26443 * An object with supported media playback quality metrics or undefined if there 26444 * is no tech or the tech does not support it. 26445 */ 26446 ; 26447 26448 _proto.getVideoPlaybackQuality = function getVideoPlaybackQuality() { 26449 return this.techGet_('getVideoPlaybackQuality'); 26450 } 26451 /** 26452 * Get video width 26453 * 26454 * @return {number} 26455 * current video width 26456 */ 26457 ; 26458 26459 _proto.videoWidth = function videoWidth() { 26460 return this.tech_ && this.tech_.videoWidth && this.tech_.videoWidth() || 0; 26461 } 26462 /** 26463 * Get video height 26464 * 26465 * @return {number} 26466 * current video height 26467 */ 26468 ; 26469 26470 _proto.videoHeight = function videoHeight() { 26471 return this.tech_ && this.tech_.videoHeight && this.tech_.videoHeight() || 0; 26472 } 26473 /** 26474 * The player's language code 26475 * NOTE: The language should be set in the player options if you want the 26476 * the controls to be built with a specific language. Changing the language 26477 * later will not update controls text. 26478 * 26479 * @param {string} [code] 26480 * the language code to set the player to 26481 * 26482 * @return {string} 26483 * The current language code when getting 26484 */ 26485 ; 26486 26487 _proto.language = function language(code) { 26488 if (code === undefined) { 26489 return this.language_; 26490 } 26491 26492 this.language_ = String(code).toLowerCase(); 26493 } 26494 /** 26495 * Get the player's language dictionary 26496 * Merge every time, because a newly added plugin might call videojs.addLanguage() at any time 26497 * Languages specified directly in the player options have precedence 26498 * 26499 * @return {Array} 26500 * An array of of supported languages 26501 */ 26502 ; 26503 26504 _proto.languages = function languages() { 26505 return mergeOptions(Player.prototype.options_.languages, this.languages_); 26506 } 26507 /** 26508 * returns a JavaScript object reperesenting the current track 26509 * information. **DOES not return it as JSON** 26510 * 26511 * @return {Object} 26512 * Object representing the current of track info 26513 */ 26514 ; 26515 26516 _proto.toJSON = function toJSON() { 26517 var options = mergeOptions(this.options_); 26518 var tracks = options.tracks; 26519 options.tracks = []; 26520 26521 for (var i = 0; i < tracks.length; i++) { 26522 var track = tracks[i]; // deep merge tracks and null out player so no circular references 26523 26524 track = mergeOptions(track); 26525 track.player = undefined; 26526 options.tracks[i] = track; 26527 } 26528 26529 return options; 26530 } 26531 /** 26532 * Creates a simple modal dialog (an instance of the {@link ModalDialog} 26533 * component) that immediately overlays the player with arbitrary 26534 * content and removes itself when closed. 26535 * 26536 * @param {string|Function|Element|Array|null} content 26537 * Same as {@link ModalDialog#content}'s param of the same name. 26538 * The most straight-forward usage is to provide a string or DOM 26539 * element. 26540 * 26541 * @param {Object} [options] 26542 * Extra options which will be passed on to the {@link ModalDialog}. 26543 * 26544 * @return {ModalDialog} 26545 * the {@link ModalDialog} that was created 26546 */ 26547 ; 26548 26549 _proto.createModal = function createModal(content, options) { 26550 var _this14 = this; 26551 26552 options = options || {}; 26553 options.content = content || ''; 26554 var modal = new ModalDialog(this, options); 26555 this.addChild(modal); 26556 modal.on('dispose', function () { 26557 _this14.removeChild(modal); 26558 }); 26559 modal.open(); 26560 return modal; 26561 } 26562 /** 26563 * Change breakpoint classes when the player resizes. 26564 * 26565 * @private 26566 */ 26567 ; 26568 26569 _proto.updateCurrentBreakpoint_ = function updateCurrentBreakpoint_() { 26570 if (!this.responsive()) { 26571 return; 26572 } 26573 26574 var currentBreakpoint = this.currentBreakpoint(); 26575 var currentWidth = this.currentWidth(); 26576 26577 for (var i = 0; i < BREAKPOINT_ORDER.length; i++) { 26578 var candidateBreakpoint = BREAKPOINT_ORDER[i]; 26579 var maxWidth = this.breakpoints_[candidateBreakpoint]; 26580 26581 if (currentWidth <= maxWidth) { 26582 // The current breakpoint did not change, nothing to do. 26583 if (currentBreakpoint === candidateBreakpoint) { 26584 return; 26585 } // Only remove a class if there is a current breakpoint. 26586 26587 26588 if (currentBreakpoint) { 26589 this.removeClass(BREAKPOINT_CLASSES[currentBreakpoint]); 26590 } 26591 26592 this.addClass(BREAKPOINT_CLASSES[candidateBreakpoint]); 26593 this.breakpoint_ = candidateBreakpoint; 26594 break; 26595 } 26596 } 26597 } 26598 /** 26599 * Removes the current breakpoint. 26600 * 26601 * @private 26602 */ 26603 ; 26604 26605 _proto.removeCurrentBreakpoint_ = function removeCurrentBreakpoint_() { 26606 var className = this.currentBreakpointClass(); 26607 this.breakpoint_ = ''; 26608 26609 if (className) { 26610 this.removeClass(className); 26611 } 26612 } 26613 /** 26614 * Get or set breakpoints on the player. 26615 * 26616 * Calling this method with an object or `true` will remove any previous 26617 * custom breakpoints and start from the defaults again. 26618 * 26619 * @param {Object|boolean} [breakpoints] 26620 * If an object is given, it can be used to provide custom 26621 * breakpoints. If `true` is given, will set default breakpoints. 26622 * If this argument is not given, will simply return the current 26623 * breakpoints. 26624 * 26625 * @param {number} [breakpoints.tiny] 26626 * The maximum width for the "vjs-layout-tiny" class. 26627 * 26628 * @param {number} [breakpoints.xsmall] 26629 * The maximum width for the "vjs-layout-x-small" class. 26630 * 26631 * @param {number} [breakpoints.small] 26632 * The maximum width for the "vjs-layout-small" class. 26633 * 26634 * @param {number} [breakpoints.medium] 26635 * The maximum width for the "vjs-layout-medium" class. 26636 * 26637 * @param {number} [breakpoints.large] 26638 * The maximum width for the "vjs-layout-large" class. 26639 * 26640 * @param {number} [breakpoints.xlarge] 26641 * The maximum width for the "vjs-layout-x-large" class. 26642 * 26643 * @param {number} [breakpoints.huge] 26644 * The maximum width for the "vjs-layout-huge" class. 26645 * 26646 * @return {Object} 26647 * An object mapping breakpoint names to maximum width values. 26648 */ 26649 ; 26650 26651 _proto.breakpoints = function breakpoints(_breakpoints) { 26652 // Used as a getter. 26653 if (_breakpoints === undefined) { 26654 return assign(this.breakpoints_); 26655 } 26656 26657 this.breakpoint_ = ''; 26658 this.breakpoints_ = assign({}, DEFAULT_BREAKPOINTS, _breakpoints); // When breakpoint definitions change, we need to update the currently 26659 // selected breakpoint. 26660 26661 this.updateCurrentBreakpoint_(); // Clone the breakpoints before returning. 26662 26663 return assign(this.breakpoints_); 26664 } 26665 /** 26666 * Get or set a flag indicating whether or not this player should adjust 26667 * its UI based on its dimensions. 26668 * 26669 * @param {boolean} value 26670 * Should be `true` if the player should adjust its UI based on its 26671 * dimensions; otherwise, should be `false`. 26672 * 26673 * @return {boolean} 26674 * Will be `true` if this player should adjust its UI based on its 26675 * dimensions; otherwise, will be `false`. 26676 */ 26677 ; 26678 26679 _proto.responsive = function responsive(value) { 26680 // Used as a getter. 26681 if (value === undefined) { 26682 return this.responsive_; 26683 } 26684 26685 value = Boolean(value); 26686 var current = this.responsive_; // Nothing changed. 26687 26688 if (value === current) { 26689 return; 26690 } // The value actually changed, set it. 26691 26692 26693 this.responsive_ = value; // Start listening for breakpoints and set the initial breakpoint if the 26694 // player is now responsive. 26695 26696 if (value) { 26697 this.on('playerresize', this.updateCurrentBreakpoint_); 26698 this.updateCurrentBreakpoint_(); // Stop listening for breakpoints if the player is no longer responsive. 26699 } else { 26700 this.off('playerresize', this.updateCurrentBreakpoint_); 26701 this.removeCurrentBreakpoint_(); 26702 } 26703 26704 return value; 26705 } 26706 /** 26707 * Get current breakpoint name, if any. 26708 * 26709 * @return {string} 26710 * If there is currently a breakpoint set, returns a the key from the 26711 * breakpoints object matching it. Otherwise, returns an empty string. 26712 */ 26713 ; 26714 26715 _proto.currentBreakpoint = function currentBreakpoint() { 26716 return this.breakpoint_; 26717 } 26718 /** 26719 * Get the current breakpoint class name. 26720 * 26721 * @return {string} 26722 * The matching class name (e.g. `"vjs-layout-tiny"` or 26723 * `"vjs-layout-large"`) for the current breakpoint. Empty string if 26724 * there is no current breakpoint. 26725 */ 26726 ; 26727 26728 _proto.currentBreakpointClass = function currentBreakpointClass() { 26729 return BREAKPOINT_CLASSES[this.breakpoint_] || ''; 26730 } 26731 /** 26732 * An object that describes a single piece of media. 26733 * 26734 * Properties that are not part of this type description will be retained; so, 26735 * this can be viewed as a generic metadata storage mechanism as well. 26736 * 26737 * @see {@link https://wicg.github.io/mediasession/#the-mediametadata-interface} 26738 * @typedef {Object} Player~MediaObject 26739 * 26740 * @property {string} [album] 26741 * Unused, except if this object is passed to the `MediaSession` 26742 * API. 26743 * 26744 * @property {string} [artist] 26745 * Unused, except if this object is passed to the `MediaSession` 26746 * API. 26747 * 26748 * @property {Object[]} [artwork] 26749 * Unused, except if this object is passed to the `MediaSession` 26750 * API. If not specified, will be populated via the `poster`, if 26751 * available. 26752 * 26753 * @property {string} [poster] 26754 * URL to an image that will display before playback. 26755 * 26756 * @property {Tech~SourceObject|Tech~SourceObject[]|string} [src] 26757 * A single source object, an array of source objects, or a string
26758 * referencing a URL to a media source. It is _highly recommended_ 26759 * that an object or array of objects is used here, so that source 26760 * selection algorithms can take the `type` into account. 26761 * 26762 * @property {string} [title] 26763 * Unused, except if this object is passed to the `MediaSession` 26764 * API. 26765 * 26766 * @property {Object[]} [textTracks] 26767 * An array of objects to be used to create text tracks, following 26768 * the {@link https://www.w3.org/TR/html50/embedded-content-0.html#the-track-element|native track element format}. 26769 * For ease of removal, these will be created as "remote" text 26770 * tracks and set to automatically clean up on source changes. 26771 * 26772 * These objects may have properties like `src`, `kind`, `label`, 26773 * and `language`, see {@link Tech#createRemoteTextTrack}. 26774 */ 26775 26776 /** 26777 * Populate the player using a {@link Player~MediaObject|MediaObject}. 26778 * 26779 * @param {Player~MediaObject} media 26780 * A media object. 26781 * 26782 * @param {Function} ready 26783 * A callback to be called when the player is ready. 26784 */ 26785 ; 26786 26787 _proto.loadMedia = function loadMedia(media, ready) { 26788 var _this15 = this; 26789 26790 if (!media || typeof media !== 'object') { 26791 return; 26792 } 26793 26794 this.reset(); // Clone the media object so it cannot be mutated from outside. 26795 26796 this.cache_.media = mergeOptions(media); 26797 var _this$cache_$media = this.cache_.media, 26798 artwork = _this$cache_$media.artwork, 26799 poster = _this$cache_$media.poster, 26800 src = _this$cache_$media.src, 26801 textTracks = _this$cache_$media.textTracks; // If `artwork` is not given, create it using `poster`. 26802 26803 if (!artwork && poster) { 26804 this.cache_.media.artwork = [{ 26805 src: poster, 26806 type: getMimetype(poster) 26807 }]; 26808 } 26809 26810 if (src) { 26811 this.src(src); 26812 } 26813 26814 if (poster) { 26815 this.poster(poster); 26816 } 26817 26818 if (Array.isArray(textTracks)) { 26819 textTracks.forEach(function (tt) { 26820 return _this15.addRemoteTextTrack(tt, false); 26821 }); 26822 } 26823 26824 this.ready(ready); 26825 } 26826 /** 26827 * Get a clone of the current {@link Player~MediaObject} for this player. 26828 * 26829 * If the `loadMedia` method has not been used, will attempt to return a 26830 * {@link Player~MediaObject} based on the current state of the player. 26831 * 26832 * @return {Player~MediaObject} 26833 */ 26834 ; 26835 26836 _proto.getMedia = function getMedia() { 26837 if (!this.cache_.media) { 26838 var poster = this.poster(); 26839 var src = this.currentSources(); 26840 var textTracks = Array.prototype.map.call(this.remoteTextTracks(), function (tt) { 26841 return { 26842 kind: tt.kind, 26843 label: tt.label, 26844 language: tt.language, 26845 src: tt.src 26846 }; 26847 }); 26848 var media = { 26849 src: src, 26850 textTracks: textTracks 26851 }; 26852 26853 if (poster) { 26854 media.poster = poster; 26855 media.artwork = [{ 26856 src: media.poster, 26857 type: getMimetype(media.poster) 26858 }]; 26859 } 26860 26861 return media; 26862 } 26863 26864 return mergeOptions(this.cache_.media); 26865 } 26866 /** 26867 * Gets tag settings 26868 * 26869 * @param {Element} tag 26870 * The player tag 26871 * 26872 * @return {Object} 26873 * An object containing all of the settings 26874 * for a player tag 26875 */ 26876 ; 26877 26878 Player.getTagSettings = function getTagSettings(tag) { 26879 var baseOptions = { 26880 sources: [], 26881 tracks: [] 26882 }; 26883 var tagOptions = getAttributes(tag); 26884 var dataSetup = tagOptions['data-setup']; 26885 26886 if (hasClass(tag, 'vjs-fill')) { 26887 tagOptions.fill = true; 26888 } 26889 26890 if (hasClass(tag, 'vjs-fluid')) { 26891 tagOptions.fluid = true; 26892 } // Check if data-setup attr exists. 26893 26894 26895 if (dataSetup !== null) { 26896 // Parse options JSON 26897 // If empty string, make it a parsable json object. 26898 var _safeParseTuple = tuple(dataSetup || '{}'), 26899 err = _safeParseTuple[0], 26900 data = _safeParseTuple[1]; 26901 26902 if (err) { 26903 log.error(err); 26904 } 26905 26906 assign(tagOptions, data); 26907 } 26908 26909 assign(baseOptions, tagOptions); // Get tag children settings 26910 26911 if (tag.hasChildNodes()) { 26912 var children = tag.childNodes; 26913 26914 for (var i = 0, j = children.length; i < j; i++) { 26915 var child = children[i]; // Change case needed: http://ejohn.org/blog/nodename-case-sensitivity/ 26916 26917 var childName = child.nodeName.toLowerCase(); 26918 26919 if (childName === 'source') { 26920 baseOptions.sources.push(getAttributes(child)); 26921 } else if (childName === 'track') { 26922 baseOptions.tracks.push(getAttributes(child)); 26923 } 26924 } 26925 } 26926 26927 return baseOptions; 26928 } 26929 /** 26930 * Determine whether or not flexbox is supported 26931 * 26932 * @return {boolean} 26933 * - true if flexbox is supported 26934 * - false if flexbox is not supported 26935 */ 26936 ; 26937 26938 _proto.flexNotSupported_ = function flexNotSupported_() { 26939 var elem = document.createElement('i'); // Note: We don't actually use flexBasis (or flexOrder), but it's one of the more 26940 // common flex features that we can rely on when checking for flex support. 26941 26942 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 26943 'msFlexOrder' in elem.style); 26944 }; 26945 26946 return Player; 26947 }(Component); 26948 /** 26949 * Get the {@link VideoTrackList} 26950 * @link https://html.spec.whatwg.org/multipage/embedded-content.html#videotracklist 26951 * 26952 * @return {VideoTrackList} 26953 * the current video track list 26954 * 26955 * @method Player.prototype.videoTracks 26956 */ 26957 26958 /** 26959 * Get the {@link AudioTrackList} 26960 * @link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotracklist 26961 * 26962 * @return {AudioTrackList} 26963 * the current audio track list 26964 * 26965 * @method Player.prototype.audioTracks 26966 */ 26967 26968 /** 26969 * Get the {@link TextTrackList} 26970 * 26971 * @link http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-texttracks 26972 * 26973 * @return {TextTrackList} 26974 * the current text track list 26975 * 26976 * @method Player.prototype.textTracks 26977 */ 26978 26979 /** 26980 * Get the remote {@link TextTrackList} 26981 * 26982 * @return {TextTrackList} 26983 * The current remote text track list 26984 * 26985 * @method Player.prototype.remoteTextTracks 26986 */ 26987 26988 /** 26989 * Get the remote {@link HtmlTrackElementList} tracks. 26990 * 26991 * @return {HtmlTrackElementList} 26992 * The current remote text track element list 26993 * 26994 * @method Player.prototype.remoteTextTrackEls 26995 */ 26996 26997
vendor: 4,663 bytes, lines 26998-27125
26998 ALL.names.forEach(function (name$$1) { 26999 var props = ALL[name$$1]; 27000 27001 Player.prototype[props.getterName] = function () { 27002 if (this.tech_) { 27003 return this.tech_[props.getterName](); 27004 } // if we have not yet loadTech_, we create {video,audio,text}Tracks_ 27005 // these will be passed to the tech during loading 27006 27007 27008 this[props.privateName] = this[props.privateName] || new props.ListClass(); 27009 return this[props.privateName]; 27010 }; 27011 }); 27012 /** 27013 * Global enumeration of players. 27014 * 27015 * The keys are the player IDs and the values are either the {@link Player} 27016 * instance or `null` for disposed players. 27017 * 27018 * @type {Object} 27019 */ 27020 27021 Player.players = {}; 27022 var navigator = window$1.navigator; 27023 /* 27024 * Player instance options, surfaced using options 27025 * options = Player.prototype.options_ 27026 * Make changes in options, not here. 27027 * 27028 * @type {Object} 27029 * @private 27030 */ 27031 27032 Player.prototype.options_ = { 27033 // Default order of fallback technology 27034 techOrder: Tech.defaultTechOrder_, 27035 html5: {}, 27036 flash: {}, 27037 // default inactivity timeout 27038 inactivityTimeout: 2000, 27039 // default playback rates 27040 playbackRates: [], 27041 // Add playback rate selection by adding rates 27042 // 'playbackRates': [0.5, 1, 1.5, 2], 27043 liveui: false, 27044 // Included control sets 27045 children: ['mediaLoader', 'posterImage', 'textTrackDisplay', 'loadingSpinner', 'bigPlayButton', 'liveTracker', 'controlBar', 'errorDisplay', 'textTrackSettings', 'resizeManager'], 27046 language: navigator && (navigator.languages && navigator.languages[0] || navigator.userLanguage || navigator.language) || 'en', 27047 // locales and their language translations 27048 languages: {}, 27049 // Default message to show when a video cannot be played. 27050 notSupportedMessage: 'No compatible source was found for this media.', 27051 breakpoints: {}, 27052 responsive: false 27053 }; 27054 [ 27055 /** 27056 * Returns whether or not the player is in the "ended" state. 27057 * 27058 * @return {Boolean} True if the player is in the ended state, false if not. 27059 * @method Player#ended 27060 */ 27061 'ended', 27062 /** 27063 * Returns whether or not the player is in the "seeking" state. 27064 * 27065 * @return {Boolean} True if the player is in the seeking state, false if not. 27066 * @method Player#seeking 27067 */ 27068 'seeking', 27069 /** 27070 * Returns the TimeRanges of the media that are currently available 27071 * for seeking to. 27072 * 27073 * @return {TimeRanges} the seekable intervals of the media timeline 27074 * @method Player#seekable 27075 */ 27076 'seekable', 27077 /** 27078 * Returns the current state of network activity for the element, from 27079 * the codes in the list below. 27080 * - NETWORK_EMPTY (numeric value 0) 27081 * The element has not yet been initialised. All attributes are in 27082 * their initial states. 27083 * - NETWORK_IDLE (numeric value 1) 27084 * The element's resource selection algorithm is active and has 27085 * selected a resource, but it is not actually using the network at 27086 * this time. 27087 * - NETWORK_LOADING (numeric value 2) 27088 * The user agent is actively trying to download data. 27089 * - NETWORK_NO_SOURCE (numeric value 3) 27090 * The element's resource selection algorithm is active, but it has 27091 * not yet found a resource to use. 27092 * 27093 * @see https://html.spec.whatwg.org/multipage/embedded-content.html#network-states 27094 * @return {number} the current network activity state 27095 * @method Player#networkState 27096 */ 27097 'networkState', 27098 /** 27099 * Returns a value that expresses the current state of the element 27100 * with respect to rendering the current playback position, from the 27101 * codes in the list below. 27102 * - HAVE_NOTHING (numeric value 0) 27103 * No information regarding the media resource is available. 27104 * - HAVE_METADATA (numeric value 1) 27105 * Enough of the resource has been obtained that the duration of the 27106 * resource is available. 27107 * - HAVE_CURRENT_DATA (numeric value 2) 27108 * Data for the immediate current playback position is available. 27109 * - HAVE_FUTURE_DATA (numeric value 3) 27110 * Data for the immediate current playback position is available, as 27111 * well as enough data for the user agent to advance the current 27112 * playback position in the direction of playback. 27113 * - HAVE_ENOUGH_DATA (numeric value 4) 27114 * The user agent estimates that enough data is available for 27115 * playback to proceed uninterrupted. 27116 * 27117 * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-readystate 27118 * @return {number} the current playback rendering state 27119 * @method Player#readyState 27120 */ 27121 'readyState'].forEach(function (fn) { 27122 Player.prototype[fn] = function () { 27123 return this.techGet_(fn); 27124 }; 27125 });
vendor: 5,226 bytes, lines 27126-27323
27126 TECH_EVENTS_RETRIGGER.forEach(function (event) { 27127 Player.prototype["handleTech" + toTitleCase(event) + "_"] = function () { 27128 return this.trigger(event); 27129 }; 27130 }); 27131 /** 27132 * Fired when the player has initial duration and dimension information 27133 * 27134 * @event Player#loadedmetadata 27135 * @type {EventTarget~Event} 27136 */ 27137 27138 /** 27139 * Fired when the player has downloaded data at the current playback position 27140 * 27141 * @event Player#loadeddata 27142 * @type {EventTarget~Event} 27143 */ 27144 27145 /** 27146 * Fired when the current playback position has changed * 27147 * During playback this is fired every 15-250 milliseconds, depending on the 27148 * playback technology in use. 27149 * 27150 * @event Player#timeupdate 27151 * @type {EventTarget~Event} 27152 */ 27153 27154 /** 27155 * Fired when the volume changes 27156 * 27157 * @event Player#volumechange 27158 * @type {EventTarget~Event} 27159 */ 27160 27161 /** 27162 * Reports whether or not a player has a plugin available. 27163 * 27164 * This does not report whether or not the plugin has ever been initialized 27165 * on this player. For that, [usingPlugin]{@link Player#usingPlugin}. 27166 * 27167 * @method Player#hasPlugin 27168 * @param {string} name 27169 * The name of a plugin. 27170 * 27171 * @return {boolean} 27172 * Whether or not this player has the requested plugin available. 27173 */ 27174 27175 /** 27176 * Reports whether or not a player is using a plugin by name. 27177 * 27178 * For basic plugins, this only reports whether the plugin has _ever_ been 27179 * initialized on this player. 27180 * 27181 * @method Player#usingPlugin 27182 * @param {string} name 27183 * The name of a plugin. 27184 * 27185 * @return {boolean} 27186 * Whether or not this player is using the requested plugin. 27187 */ 27188 27189 Component.registerComponent('Player', Player); 27190 27191 /** 27192 * The base plugin name. 27193 * 27194 * @private 27195 * @constant 27196 * @type {string} 27197 */ 27198 27199 var BASE_PLUGIN_NAME = 'plugin'; 27200 /** 27201 * The key on which a player's active plugins cache is stored. 27202 * 27203 * @private 27204 * @constant 27205 * @type {string} 27206 */ 27207 27208 var PLUGIN_CACHE_KEY = 'activePlugins_'; 27209 /** 27210 * Stores registered plugins in a private space. 27211 * 27212 * @private 27213 * @type {Object} 27214 */ 27215 27216 var pluginStorage = {}; 27217 /** 27218 * Reports whether or not a plugin has been registered. 27219 * 27220 * @private 27221 * @param {string} name 27222 * The name of a plugin. 27223 * 27224 * @return {boolean} 27225 * Whether or not the plugin has been registered. 27226 */ 27227 27228 var pluginExists = function pluginExists(name) { 27229 return pluginStorage.hasOwnProperty(name); 27230 }; 27231 /** 27232 * Get a single registered plugin by name. 27233 * 27234 * @private 27235 * @param {string} name 27236 * The name of a plugin. 27237 * 27238 * @return {Function|undefined} 27239 * The plugin (or undefined). 27240 */ 27241 27242 27243 var getPlugin = function getPlugin(name) { 27244 return pluginExists(name) ? pluginStorage[name] : undefined; 27245 }; 27246 /** 27247 * Marks a plugin as "active" on a player. 27248 * 27249 * Also, ensures that the player has an object for tracking active plugins. 27250 * 27251 * @private 27252 * @param {Player} player 27253 * A Video.js player instance. 27254 * 27255 * @param {string} name 27256 * The name of a plugin. 27257 */ 27258 27259 27260 var markPluginAsActive = function markPluginAsActive(player, name) { 27261 player[PLUGIN_CACHE_KEY] = player[PLUGIN_CACHE_KEY] || {}; 27262 player[PLUGIN_CACHE_KEY][name] = true; 27263 }; 27264 /** 27265 * Triggers a pair of plugin setup events. 27266 * 27267 * @private 27268 * @param {Player} player 27269 * A Video.js player instance. 27270 * 27271 * @param {Plugin~PluginEventHash} hash 27272 * A plugin event hash. 27273 * 27274 * @param {boolean} [before] 27275 * If true, prefixes the event name with "before". In other words, 27276 * use this to trigger "beforepluginsetup" instead of "pluginsetup". 27277 */ 27278 27279 27280 var triggerSetupEvent = function triggerSetupEvent(player, hash, before) { 27281 var eventName = (before ? 'before' : '') + 'pluginsetup'; 27282 player.trigger(eventName, hash); 27283 player.trigger(eventName + ':' + hash.name, hash); 27284 }; 27285 /** 27286 * Takes a basic plugin function and returns a wrapper function which marks 27287 * on the player that the plugin has been activated. 27288 * 27289 * @private 27290 * @param {string} name 27291 * The name of the plugin. 27292 * 27293 * @param {Function} plugin 27294 * The basic plugin. 27295 * 27296 * @return {Function} 27297 * A wrapper function for the given plugin. 27298 */ 27299 27300 27301 var createBasicPlugin = function createBasicPlugin(name, plugin) { 27302 var basicPluginWrapper = function basicPluginWrapper() { 27303 // We trigger the "beforepluginsetup" and "pluginsetup" events on the player 27304 // regardless, but we want the hash to be consistent with the hash provided 27305 // for advanced plugins. 27306 // 27307 // The only potentially counter-intuitive thing here is the `instance` in 27308 // the "pluginsetup" event is the value returned by the `plugin` function. 27309 triggerSetupEvent(this, { 27310 name: name, 27311 plugin: plugin, 27312 instance: null 27313 }, true); 27314 var instance = plugin.apply(this, arguments); 27315 markPluginAsActive(this, name); 27316 triggerSetupEvent(this, { 27317 name: name, 27318 plugin: plugin, 27319 instance: instance 27320 }); 27321 return instance; 27322 }; 27323
vendor: 8,907 bytes, lines 27324-27605
27324 Object.keys(plugin).forEach(function (prop) { 27325 basicPluginWrapper[prop] = plugin[prop]; 27326 }); 27327 return basicPluginWrapper; 27328 }; 27329 /** 27330 * Takes a plugin sub-class and returns a factory function for generating 27331 * instances of it. 27332 * 27333 * This factory function will replace itself with an instance of the requested 27334 * sub-class of Plugin. 27335 * 27336 * @private 27337 * @param {string} name 27338 * The name of the plugin. 27339 * 27340 * @param {Plugin} PluginSubClass 27341 * The advanced plugin. 27342 * 27343 * @return {Function} 27344 */ 27345 27346 27347 var createPluginFactory = function createPluginFactory(name, PluginSubClass) { 27348 // Add a `name` property to the plugin prototype so that each plugin can 27349 // refer to itself by name. 27350 PluginSubClass.prototype.name = name; 27351 return function () { 27352 triggerSetupEvent(this, { 27353 name: name, 27354 plugin: PluginSubClass, 27355 instance: null 27356 }, true); 27357 27358 for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { 27359 args[_key] = arguments[_key]; 27360 } 27361 27362 var instance = _construct(PluginSubClass, [this].concat(args)); // The plugin is replaced by a function that returns the current instance. 27363 27364 27365 this[name] = function () { 27366 return instance; 27367 }; 27368 27369 triggerSetupEvent(this, instance.getEventHash()); 27370 return instance; 27371 }; 27372 }; 27373 /** 27374 * Parent class for all advanced plugins. 27375 * 27376 * @mixes module:evented~EventedMixin 27377 * @mixes module:stateful~StatefulMixin 27378 * @fires Player#beforepluginsetup 27379 * @fires Player#beforepluginsetup:$name 27380 * @fires Player#pluginsetup 27381 * @fires Player#pluginsetup:$name 27382 * @listens Player#dispose 27383 * @throws {Error} 27384 * If attempting to instantiate the base {@link Plugin} class 27385 * directly instead of via a sub-class. 27386 */ 27387 27388 27389 var Plugin = 27390 /*#__PURE__*/ 27391 function () { 27392 /** 27393 * Creates an instance of this class. 27394 * 27395 * Sub-classes should call `super` to ensure plugins are properly initialized. 27396 * 27397 * @param {Player} player 27398 * A Video.js player instance. 27399 */ 27400 function Plugin(player) { 27401 if (this.constructor === Plugin) { 27402 throw new Error('Plugin must be sub-classed; not directly instantiated.'); 27403 } 27404 27405 this.player = player; // Make this object evented, but remove the added `trigger` method so we 27406 // use the prototype version instead. 27407 27408 evented(this); 27409 delete this.trigger; 27410 stateful(this, this.constructor.defaultState); 27411 markPluginAsActive(player, this.name); // Auto-bind the dispose method so we can use it as a listener and unbind 27412 // it later easily. 27413 27414 this.dispose = bind(this, this.dispose); // If the player is disposed, dispose the plugin. 27415 27416 player.on('dispose', this.dispose); 27417 } 27418 /** 27419 * Get the version of the plugin that was set on <pluginName>.VERSION 27420 */ 27421 27422 27423 var _proto = Plugin.prototype; 27424 27425 _proto.version = function version() { 27426 return this.constructor.VERSION; 27427 } 27428 /** 27429 * Each event triggered by plugins includes a hash of additional data with 27430 * conventional properties. 27431 * 27432 * This returns that object or mutates an existing hash. 27433 * 27434 * @param {Object} [hash={}] 27435 * An object to be used as event an event hash. 27436 * 27437 * @return {Plugin~PluginEventHash} 27438 * An event hash object with provided properties mixed-in. 27439 */ 27440 ; 27441 27442 _proto.getEventHash = function getEventHash(hash) { 27443 if (hash === void 0) { 27444 hash = {}; 27445 } 27446 27447 hash.name = this.name; 27448 hash.plugin = this.constructor; 27449 hash.instance = this; 27450 return hash; 27451 } 27452 /** 27453 * Triggers an event on the plugin object and overrides 27454 * {@link module:evented~EventedMixin.trigger|EventedMixin.trigger}. 27455 * 27456 * @param {string|Object} event 27457 * An event type or an object with a type property. 27458 * 27459 * @param {Object} [hash={}] 27460 * Additional data hash to merge with a 27461 * {@link Plugin~PluginEventHash|PluginEventHash}. 27462 * 27463 * @return {boolean} 27464 * Whether or not default was prevented. 27465 */ 27466 ; 27467 27468 _proto.trigger = function trigger$$1(event, hash) { 27469 if (hash === void 0) { 27470 hash = {}; 27471 } 27472 27473 return trigger(this.eventBusEl_, event, this.getEventHash(hash)); 27474 } 27475 /** 27476 * Handles "statechanged" events on the plugin. No-op by default, override by 27477 * subclassing. 27478 * 27479 * @abstract 27480 * @param {Event} e 27481 * An event object provided by a "statechanged" event. 27482 * 27483 * @param {Object} e.changes 27484 * An object describing changes that occurred with the "statechanged" 27485 * event. 27486 */ 27487 ; 27488 27489 _proto.handleStateChanged = function handleStateChanged(e) {} 27490 /** 27491 * Disposes a plugin. 27492 * 27493 * Subclasses can override this if they want, but for the sake of safety, 27494 * it's probably best to subscribe the "dispose" event. 27495 * 27496 * @fires Plugin#dispose 27497 */ 27498 ; 27499 27500 _proto.dispose = function dispose() { 27501 var name = this.name, 27502 player = this.player; 27503 /** 27504 * Signals that a advanced plugin is about to be disposed. 27505 * 27506 * @event Plugin#dispose 27507 * @type {EventTarget~Event} 27508 */ 27509 27510 this.trigger('dispose'); 27511 this.off(); 27512 player.off('dispose', this.dispose); // Eliminate any possible sources of leaking memory by clearing up 27513 // references between the player and the plugin instance and nulling out 27514 // the plugin's state and replacing methods with a function that throws. 27515 27516 player[PLUGIN_CACHE_KEY][name] = false; 27517 this.player = this.state = null; // Finally, replace the plugin name on the player with a new factory 27518 // function, so that the plugin is ready to be set up again. 27519 27520 player[name] = createPluginFactory(name, pluginStorage[name]); 27521 } 27522 /** 27523 * Determines if a plugin is a basic plugin (i.e. not a sub-class of `Plugin`). 27524 * 27525 * @param {string|Function} plugin 27526 * If a string, matches the name of a plugin. If a function, will be 27527 * tested directly. 27528 * 27529 * @return {boolean} 27530 * Whether or not a plugin is a basic plugin. 27531 */ 27532 ; 27533 27534 Plugin.isBasic = function isBasic(plugin) { 27535 var p = typeof plugin === 'string' ? getPlugin(plugin) : plugin; 27536 return typeof p === 'function' && !Plugin.prototype.isPrototypeOf(p.prototype); 27537 } 27538 /** 27539 * Register a Video.js plugin. 27540 * 27541 * @param {string} name 27542 * The name of the plugin to be registered. Must be a string and 27543 * must not match an existing plugin or a method on the `Player` 27544 * prototype. 27545 * 27546 * @param {Function} plugin 27547 * A sub-class of `Plugin` or a function for basic plugins. 27548 * 27549 * @return {Function} 27550 * For advanced plugins, a factory function for that plugin. For 27551 * basic plugins, a wrapper function that initializes the plugin. 27552 */ 27553 ; 27554 27555 Plugin.registerPlugin = function registerPlugin(name, plugin) { 27556 if (typeof name !== 'string') { 27557 throw new Error("Illegal plugin name, \"" + name + "\", must be a string, was " + typeof name + "."); 27558 } 27559 27560 if (pluginExists(name)) { 27561 log.warn("A plugin named \"" + name + "\" already exists. You may want to avoid re-registering plugins!"); 27562 } else if (Player.prototype.hasOwnProperty(name)) { 27563 throw new Error("Illegal plugin name, \"" + name + "\", cannot share a name with an existing player method!"); 27564 } 27565 27566 if (typeof plugin !== 'function') { 27567 throw new Error("Illegal plugin for \"" + name + "\", must be a function, was " + typeof plugin + "."); 27568 } 27569 27570 pluginStorage[name] = plugin; // Add a player prototype method for all sub-classed plugins (but not for 27571 // the base Plugin class). 27572 27573 if (name !== BASE_PLUGIN_NAME) { 27574 if (Plugin.isBasic(plugin)) { 27575 Player.prototype[name] = createBasicPlugin(name, plugin); 27576 } else { 27577 Player.prototype[name] = createPluginFactory(name, plugin); 27578 } 27579 } 27580 27581 return plugin; 27582 } 27583 /** 27584 * De-register a Video.js plugin. 27585 * 27586 * @param {string} name 27587 * The name of the plugin to be de-registered. Must be a string that 27588 * matches an existing plugin. 27589 * 27590 * @throws {Error} 27591 * If an attempt is made to de-register the base plugin. 27592 */ 27593 ; 27594 27595 Plugin.deregisterPlugin = function deregisterPlugin(name) { 27596 if (name === BASE_PLUGIN_NAME) { 27597 throw new Error('Cannot de-register base plugin.'); 27598 } 27599 27600 if (pluginExists(name)) { 27601 delete pluginStorage[name]; 27602 delete Player.prototype[name]; 27603 } 27604 } 27605 /**
27606 * Gets an object containing multiple Video.js plugins. 27607 * 27608 * @param {Array} [names] 27609 * If provided, should be an array of plugin names. Defaults to _all_ 27610 * plugin names. 27611 * 27612 * @return {Object|undefined} 27613 * An object containing plugin(s) associated with their name(s) or 27614 * `undefined` if no matching plugins exist). 27615 */ 27616 ; 27617 27618 Plugin.getPlugins = function getPlugins(names) { 27619 if (names === void 0) { 27620 names = Object.keys(pluginStorage); 27621 } 27622 27623 var result; 27624 names.forEach(function (name) { 27625 var plugin = getPlugin(name); 27626 27627 if (plugin) { 27628 result = result || {}; 27629 result[name] = plugin; 27630 } 27631 }); 27632 return result; 27633 } 27634 /** 27635 * Gets a plugin's version, if available 27636 * 27637 * @param {string} name 27638 * The name of a plugin. 27639 * 27640 * @return {string} 27641 * The plugin's version or an empty string. 27642 */ 27643 ; 27644 27645 Plugin.getPluginVersion = function getPluginVersion(name) { 27646 var plugin = getPlugin(name); 27647 return plugin && plugin.VERSION || ''; 27648 }; 27649 27650 return Plugin; 27651 }(); 27652 /** 27653 * Gets a plugin by name if it exists. 27654 * 27655 * @static 27656 * @method getPlugin 27657 * @memberOf Plugin 27658 * @param {string} name 27659 * The name of a plugin. 27660 * 27661 * @returns {Function|undefined} 27662 * The plugin (or `undefined`). 27663 */ 27664 27665 27666 Plugin.getPlugin = getPlugin; 27667 /** 27668 * The name of the base plugin class as it is registered. 27669 * 27670 * @type {string} 27671 */ 27672 27673 Plugin.BASE_PLUGIN_NAME = BASE_PLUGIN_NAME; 27674 Plugin.registerPlugin(BASE_PLUGIN_NAME, Plugin); 27675 /** 27676 * Documented in player.js 27677 * 27678 * @ignore 27679 */ 27680 27681 Player.prototype.usingPlugin = function (name) { 27682 return !!this[PLUGIN_CACHE_KEY] && this[PLUGIN_CACHE_KEY][name] === true; 27683 }; 27684 /** 27685 * Documented in player.js 27686 * 27687 * @ignore 27688 */ 27689 27690 27691 Player.prototype.hasPlugin = function (name) { 27692 return !!pluginExists(name); 27693 }; 27694 /** 27695 * Signals that a plugin is about to be set up on a player. 27696 * 27697 * @event Player#beforepluginsetup 27698 * @type {Plugin~PluginEventHash} 27699 */ 27700 27701 /** 27702 * Signals that a plugin is about to be set up on a player - by name. The name 27703 * is the name of the plugin. 27704 * 27705 * @event Player#beforepluginsetup:$name 27706 * @type {Plugin~PluginEventHash} 27707 */ 27708 27709 /** 27710 * Signals that a plugin has just been set up on a player. 27711 * 27712 * @event Player#pluginsetup 27713 * @type {Plugin~PluginEventHash} 27714 */ 27715 27716 /** 27717 * Signals that a plugin has just been set up on a player - by name. The name 27718 * is the name of the plugin. 27719 * 27720 * @event Player#pluginsetup:$name 27721 * @type {Plugin~PluginEventHash} 27722 */ 27723 27724 /** 27725 * @typedef {Object} Plugin~PluginEventHash 27726 * 27727 * @property {string} instance 27728 * For basic plugins, the return value of the plugin function. For 27729 * advanced plugins, the plugin instance on which the event is fired. 27730 * 27731 * @property {string} name 27732 * The name of the plugin. 27733 * 27734 * @property {string} plugin 27735 * For basic plugins, the plugin function. For advanced plugins, the 27736 * plugin class/constructor. 27737 */ 27738 27739 /** 27740 * @file extend.js 27741 * @module extend 27742 */ 27743 27744 /** 27745 * A combination of node inherits and babel's inherits (after transpile). 27746 * Both work the same but node adds `super_` to the subClass 27747 * and Bable adds the superClass as __proto__. Both seem useful. 27748 * 27749 * @param {Object} subClass 27750 * The class to inherit to 27751 * 27752 * @param {Object} superClass 27753 * The class to inherit from 27754 * 27755 * @private 27756 */ 27757 var _inherits$1 = function _inherits(subClass, superClass) { 27758 if (typeof superClass !== 'function' && superClass !== null) { 27759 throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); 27760 } 27761 27762 subClass.prototype = Object.create(superClass && superClass.prototype, { 27763 constructor: { 27764 value: subClass, 27765 enumerable: false, 27766 writable: true, 27767 configurable: true 27768 } 27769 }); 27770 27771 if (superClass) { 27772 // node 27773 subClass.super_ = superClass; 27774 } 27775 }; 27776 /** 27777 * Used to subclass an existing class by emulating ES subclassing using the 27778 * `extends` keyword. 27779 * 27780 * @function 27781 * @example 27782 * var MyComponent = videojs.extend(videojs.getComponent('Component'), { 27783 * myCustomMethod: function() { 27784 * // Do things in my method. 27785 * } 27786 * }); 27787 * 27788 * @param {Function} superClass 27789 * The class to inherit from 27790 * 27791 * @param {Object} [subClassMethods={}] 27792 * Methods of the new class 27793 * 27794 * @return {Function} 27795 * The new class with subClassMethods that inherited superClass. 27796 */ 27797 27798 27799 var extend$1 = function extend(superClass, subClassMethods) { 27800 if (subClassMethods === void 0) { 27801 subClassMethods = {}; 27802 } 27803 27804 var subClass = function subClass() { 27805 superClass.apply(this, arguments); 27806 }; 27807 27808 var methods = {}; 27809 27810 if (typeof subClassMethods === 'object') { 27811 if (subClassMethods.constructor !== Object.prototype.constructor) { 27812 subClass = subClassMethods.constructor; 27813 } 27814 27815 methods = subClassMethods; 27816 } else if (typeof subClassMethods === 'function') { 27817 subClass = subClassMethods; 27818 } 27819 27820 _inherits$1(subClass, superClass); // Extend subObj's prototype with functions and other properties from props 27821 27822 27823 for (var name in methods) { 27824 if (methods.hasOwnProperty(name)) { 27825 subClass.prototype[name] = methods[name]; 27826 } 27827 } 27828 27829 return subClass; 27830 }; 27831 27832 /** 27833 * @file video.js 27834 * @module videojs 27835 */ 27836 /** 27837 * Normalize an `id` value by trimming off a leading `#` 27838 * 27839 * @private 27840 * @param {string} id 27841 * A string, maybe with a leading `#`. 27842 * 27843 * @return {string} 27844 * The string, without any leading `#`. 27845 */ 27846 27847 var normalizeId = function normalizeId(id) { 27848 return id.indexOf('#') === 0 ? id.slice(1) : id; 27849 }; 27850 /** 27851 * The `videojs()` function doubles as the main function for users to create a 27852 * {@link Player} instance as well as the main library namespace. 27853 * 27854 * It can also be used as a getter for a pre-existing {@link Player} instance. 27855 * However, we _strongly_ recommend using `videojs.getPlayer()` for this 27856 * purpose because it avoids any potential for unintended initialization. 27857 * 27858 * Due to [limitations](https://github.com/jsdoc3/jsdoc/issues/955#issuecomment-313829149) 27859 * of our JSDoc template, we cannot properly document this as both a function 27860 * and a namespace, so its function signature is documented here. 27861 * 27862 * #### Arguments 27863 * ##### id 27864 * string|Element, **required** 27865 * 27866 * Video element or video element ID. 27867 * 27868 * ##### options 27869 * Object, optional 27870 * 27871 * Options object for providing settings. 27872 * See: [Options Guide](https://docs.videojs.com/tutorial-options.html). 27873 * 27874 * ##### ready 27875 * {@link Component~ReadyCallback}, optional 27876 * 27877 * A function to be called when the {@link Player} and {@link Tech} are ready. 27878 * 27879 * #### Return Value 27880 * 27881 * The `videojs()` function returns a {@link Player} instance. 27882 * 27883 * @namespace 27884 * 27885 * @borrows AudioTrack as AudioTrack 27886 * @borrows Component.getComponent as getComponent 27887 * @borrows module:computed-style~computedStyle as computedStyle 27888 * @borrows module:events.on as on 27889 * @borrows module:events.one as one 27890 * @borrows module:events.off as off 27891 * @borrows module:events.trigger as trigger 27892 * @borrows EventTarget as EventTarget 27893 * @borrows module:extend~extend as extend 27894 * @borrows module:fn.bind as bind 27895 * @borrows module:format-time.formatTime as formatTime 27896 * @borrows module:format-time.resetFormatTime as resetFormatTime 27897 * @borrows module:format-time.setFormatTime as setFormatTime 27898 * @borrows module:merge-options.mergeOptions as mergeOptions 27899 * @borrows module:middleware.use as use 27900 * @borrows Player.players as players 27901 * @borrows Plugin.registerPlugin as registerPlugin 27902 * @borrows Plugin.deregisterPlugin as deregisterPlugin 27903 * @borrows Plugin.getPlugins as getPlugins 27904 * @borrows Plugin.getPlugin as getPlugin 27905 * @borrows Plugin.getPluginVersion as getPluginVersion 27906 * @borrows Tech.getTech as getTech 27907 * @borrows Tech.registerTech as registerTech 27908 * @borrows TextTrack as TextTrack 27909 * @borrows module:time-ranges.createTimeRanges as createTimeRange 27910 * @borrows module:time-ranges.createTimeRanges as createTimeRanges 27911 * @borrows module:url.isCrossOrigin as isCrossOrigin 27912 * @borrows module:url.parseUrl as parseUrl 27913 * @borrows VideoTrack as VideoTrack 27914 * 27915 * @param {string|Element}
27915 id 27916 * Video element or video element ID. 27917 * 27918 * @param {Object} [options] 27919 * Options object for providing settings. 27920 * See: [Options Guide](https://docs.videojs.com/tutorial-options.html). 27921 * 27922 * @param {Component~ReadyCallback} [ready] 27923 * A function to be called when the {@link Player} and {@link Tech} are 27924 * ready. 27925 * 27926 * @return {Player} 27927 * The `videojs()` function returns a {@link Player|Player} instance. 27928 */ 27929 27930 27931 function videojs$1(id, options, ready) { 27932 var player = videojs$1.getPlayer(id); 27933 27934 if (player) { 27935 if (options) { 27936 log.warn("Player \"" + id + "\" is already initialised. Options will not be applied."); 27937 } 27938 27939 if (ready) { 27940 player.ready(ready); 27941 } 27942 27943 return player; 27944 } 27945 27946 var el = typeof id === 'string' ? $('#' + normalizeId(id)) : id; 27947 27948 if (!isEl(el)) { 27949 throw new TypeError('The element or ID supplied is not valid. (videojs)'); 27950 } // document.body.contains(el) will only check if el is contained within that one document. 27951 // This causes problems for elements in iframes. 27952 // Instead, use the element's ownerDocument instead of the global document. 27953 // This will make sure that the element is indeed in the dom of that document. 27954 // Additionally, check that the document in question has a default view. 27955 // If the document is no longer attached to the dom, the defaultView of the document will be null. 27956 27957 27958 if (!el.ownerDocument.defaultView || !el.ownerDocument.body.contains(el)) { 27959 log.warn('The element supplied is not included in the DOM'); 27960 } 27961 27962 options = options || {}; 27963 videojs$1.hooks('beforesetup').forEach(function (hookFunction) { 27964 var opts = hookFunction(el, mergeOptions(options)); 27965 27966 if (!isObject(opts) || Array.isArray(opts)) { 27967 log.error('please return an object in beforesetup hooks'); 27968 return; 27969 } 27970 27971 options = mergeOptions(options, opts); 27972 }); // We get the current "Player" component here in case an integration has 27973 // replaced it with a custom player. 27974 27975 var PlayerComponent = Component.getComponent('Player'); 27976 player = new PlayerComponent(el, options, ready); 27977 videojs$1.hooks('setup').forEach(function (hookFunction) { 27978 return hookFunction(player); 27979 }); 27980 return player; 27981 } 27982 /** 27983 * An Object that contains lifecycle hooks as keys which point to an array 27984 * of functions that are run when a lifecycle is triggered 27985 * 27986 * @private 27987 */ 27988 27989 27990 videojs$1.hooks_ = {}; 27991 /** 27992 * Get a list of hooks for a specific lifecycle 27993 * 27994 * @param {string} type 27995 * the lifecyle to get hooks from 27996 * 27997 * @param {Function|Function[]} [fn] 27998 * Optionally add a hook (or hooks) to the lifecycle that your are getting. 27999 * 28000 * @return {Array} 28001 * an array of hooks, or an empty array if there are none. 28002 */ 28003 28004 videojs$1.hooks = function (type, fn) { 28005 videojs$1.hooks_[type] = videojs$1.hooks_[type] || []; 28006 28007 if (fn) { 28008 videojs$1.hooks_[type] = videojs$1.hooks_[type].concat(fn); 28009 } 28010 28011 return videojs$1.hooks_[type]; 28012 }; 28013 /** 28014 * Add a function hook to a specific videojs lifecycle. 28015 * 28016 * @param {string} type 28017 * the lifecycle to hook the function to. 28018 * 28019 * @param {Function|Function[]} 28020 * The function or array of functions to attach. 28021 */ 28022 28023 28024 videojs$1.hook = function (type, fn) { 28025 videojs$1.hooks(type, fn); 28026 }; 28027 /** 28028 * Add a function hook that will only run once to a specific videojs lifecycle. 28029 * 28030 * @param {string} type 28031 * the lifecycle to hook the function to. 28032 * 28033 * @param {Function|Function[]} 28034 * The function or array of functions to attach. 28035 */ 28036 28037 28038 videojs$1.hookOnce = function (type, fn) { 28039 videojs$1.hooks(type, [].concat(fn).map(function (original) { 28040 var wrapper = function wrapper() { 28041 videojs$1.removeHook(type, wrapper); 28042 return original.apply(void 0, arguments); 28043 }; 28044 28045 return wrapper; 28046 })); 28047 }; 28048 /** 28049 * Remove a hook from a specific videojs lifecycle. 28050 * 28051 * @param {string} type 28052 * the lifecycle that the function hooked to 28053 * 28054 * @param {Function} fn 28055 * The hooked function to remove 28056 * 28057 * @return {boolean} 28058 * The function that was removed or undef 28059 */ 28060 28061
vendor: 8,781 bytes, lines 28062-28357
28062 videojs$1.removeHook = function (type, fn) { 28063 var index = videojs$1.hooks(type).indexOf(fn); 28064 28065 if (index <= -1) { 28066 return false; 28067 } 28068 28069 videojs$1.hooks_[type] = videojs$1.hooks_[type].slice(); 28070 videojs$1.hooks_[type].splice(index, 1); 28071 return true; 28072 }; // Add default styles 28073 28074 28075 if (window$1.VIDEOJS_NO_DYNAMIC_STYLE !== true && isReal()) { 28076 var style$1 = $('.vjs-styles-defaults'); 28077 28078 if (!style$1) { 28079 style$1 = createStyleElement('vjs-styles-defaults'); 28080 var head = $('head'); 28081 28082 if (head) { 28083 head.insertBefore(style$1, head.firstChild); 28084 } 28085 28086 setTextContent(style$1, "\n .video-js {\n width: 300px;\n height: 150px;\n }\n\n .vjs-fluid {\n padding-top: 56.25%\n }\n "); 28087 } 28088 } // Run Auto-load players 28089 // You have to wait at least once in case this script is loaded after your 28090 // video in the DOM (weird behavior only with minified version) 28091 28092 28093 autoSetupTimeout(1, videojs$1); 28094 /** 28095 * Current Video.js version. Follows [semantic versioning](https://semver.org/). 28096 * 28097 * @type {string} 28098 */ 28099 28100 videojs$1.VERSION = version; 28101 /** 28102 * The global options object. These are the settings that take effect 28103 * if no overrides are specified when the player is created. 28104 * 28105 * @type {Object} 28106 */ 28107 28108 videojs$1.options = Player.prototype.options_; 28109 /** 28110 * Get an object with the currently created players, keyed by player ID 28111 * 28112 * @return {Object} 28113 * The created players 28114 */ 28115 28116 videojs$1.getPlayers = function () { 28117 return Player.players; 28118 }; 28119 /** 28120 * Get a single player based on an ID or DOM element. 28121 * 28122 * This is useful if you want to check if an element or ID has an associated 28123 * Video.js player, but not create one if it doesn't. 28124 * 28125 * @param {string|Element} id 28126 * An HTML element - `<video>`, `<audio>`, or `<video-js>` - 28127 * or a string matching the `id` of such an element. 28128 * 28129 * @return {Player|undefined} 28130 * A player instance or `undefined` if there is no player instance 28131 * matching the argument. 28132 */ 28133 28134 28135 videojs$1.getPlayer = function (id) { 28136 var players = Player.players; 28137 var tag; 28138 28139 if (typeof id === 'string') { 28140 var nId = normalizeId(id); 28141 var player = players[nId]; 28142 28143 if (player) { 28144 return player; 28145 } 28146 28147 tag = $('#' + nId); 28148 } else { 28149 tag = id; 28150 } 28151 28152 if (isEl(tag)) { 28153 var _tag = tag, 28154 _player = _tag.player, 28155 playerId = _tag.playerId; // Element may have a `player` property referring to an already created 28156 // player instance. If so, return that. 28157 28158 if (_player || players[playerId]) { 28159 return _player || players[playerId]; 28160 } 28161 } 28162 }; 28163 /** 28164 * Returns an array of all current players. 28165 * 28166 * @return {Array} 28167 * An array of all players. The array will be in the order that 28168 * `Object.keys` provides, which could potentially vary between 28169 * JavaScript engines. 28170 * 28171 */ 28172 28173 28174 videojs$1.getAllPlayers = function () { 28175 return (// Disposed players leave a key with a `null` value, so we need to make sure 28176 // we filter those out. 28177 Object.keys(Player.players).map(function (k) { 28178 return Player.players[k]; 28179 }).filter(Boolean) 28180 ); 28181 }; 28182 28183 videojs$1.players = Player.players; 28184 videojs$1.getComponent = Component.getComponent; 28185 /** 28186 * Register a component so it can referred to by name. Used when adding to other 28187 * components, either through addChild `component.addChild('myComponent')` or through 28188 * default children options `{ children: ['myComponent'] }`. 28189 * 28190 * > NOTE: You could also just initialize the component before adding. 28191 * `component.addChild(new MyComponent());` 28192 * 28193 * @param {string} name 28194 * The class name of the component 28195 * 28196 * @param {Component} comp 28197 * The component class 28198 * 28199 * @return {Component} 28200 * The newly registered component 28201 */ 28202 28203 videojs$1.registerComponent = function (name$$1, comp) { 28204 if (Tech.isTech(comp)) { 28205 log.warn("The " + name$$1 + " tech was registered as a component. It should instead be registered using videojs.registerTech(name, tech)"); 28206 } 28207 28208 Component.registerComponent.call(Component, name$$1, comp); 28209 }; 28210 28211 videojs$1.getTech = Tech.getTech; 28212 videojs$1.registerTech = Tech.registerTech; 28213 videojs$1.use = use; 28214 /** 28215 * An object that can be returned by a middleware to signify 28216 * that the middleware is being terminated. 28217 * 28218 * @type {object} 28219 * @property {object} middleware.TERMINATOR 28220 */ 28221 28222 Object.defineProperty(videojs$1, 'middleware', { 28223 value: {}, 28224 writeable: false, 28225 enumerable: true 28226 }); 28227 Object.defineProperty(videojs$1.middleware, 'TERMINATOR', { 28228 value: TERMINATOR, 28229 writeable: false, 28230 enumerable: true 28231 }); 28232 /** 28233 * A reference to the {@link module:browser|browser utility module} as an object. 28234 * 28235 * @type {Object} 28236 * @see {@link module:browser|browser} 28237 */ 28238 28239 videojs$1.browser = browser; 28240 /** 28241 * Use {@link module:browser.TOUCH_ENABLED|browser.TOUCH_ENABLED} instead; only 28242 * included for backward-compatibility with 4.x. 28243 * 28244 * @deprecated Since version 5.0, use {@link module:browser.TOUCH_ENABLED|browser.TOUCH_ENABLED instead. 28245 * @type {boolean} 28246 */ 28247 28248 videojs$1.TOUCH_ENABLED = TOUCH_ENABLED; 28249 videojs$1.extend = extend$1; 28250 videojs$1.mergeOptions = mergeOptions; 28251 videojs$1.bind = bind; 28252 videojs$1.registerPlugin = Plugin.registerPlugin; 28253 videojs$1.deregisterPlugin = Plugin.deregisterPlugin; 28254 /** 28255 * Deprecated method to register a plugin with Video.js 28256 * 28257 * @deprecated videojs.plugin() is deprecated; use videojs.registerPlugin() instead 28258 * 28259 * @param {string} name 28260 * The plugin name 28261 * 28262 * @param {Plugin|Function} plugin 28263 * The plugin sub-class or function 28264 */ 28265 28266 videojs$1.plugin = function (name$$1, plugin) { 28267 log.warn('videojs.plugin() is deprecated; use videojs.registerPlugin() instead'); 28268 return Plugin.registerPlugin(name$$1, plugin); 28269 }; 28270 28271 videojs$1.getPlugins = Plugin.getPlugins; 28272 videojs$1.getPlugin = Plugin.getPlugin; 28273 videojs$1.getPluginVersion = Plugin.getPluginVersion; 28274 /** 28275 * Adding languages so that they're available to all players. 28276 * Example: `videojs.addLanguage('es', { 'Hello': 'Hola' });` 28277 * 28278 * @param {string} code 28279 * The language code or dictionary property 28280 * 28281 * @param {Object} data 28282 * The data values to be translated 28283 * 28284 * @return {Object} 28285 * The resulting language dictionary object 28286 */ 28287 28288 videojs$1.addLanguage = function (code, data) { 28289 var _mergeOptions; 28290 28291 code = ('' + code).toLowerCase(); 28292 videojs$1.options.languages = mergeOptions(videojs$1.options.languages, (_mergeOptions = {}, _mergeOptions[code] = data, _mergeOptions)); 28293 return videojs$1.options.languages[code]; 28294 }; 28295 /** 28296 * A reference to the {@link module:log|log utility module} as an object. 28297 * 28298 * @type {Function} 28299 * @see {@link module:log|log} 28300 */ 28301 28302 28303 videojs$1.log = log; 28304 videojs$1.createLogger = createLogger$1; 28305 videojs$1.createTimeRange = videojs$1.createTimeRanges = createTimeRanges; 28306 videojs$1.formatTime = formatTime; 28307 videojs$1.setFormatTime = setFormatTime; 28308 videojs$1.resetFormatTime = resetFormatTime; 28309 videojs$1.parseUrl = parseUrl; 28310 videojs$1.isCrossOrigin = isCrossOrigin; 28311 videojs$1.EventTarget = EventTarget; 28312 videojs$1.on = on; 28313 videojs$1.one = one; 28314 videojs$1.off = off; 28315 videojs$1.trigger = trigger; 28316 /** 28317 * A cross-browser XMLHttpRequest wrapper. 28318 * 28319 * @function 28320 * @param {Object} options 28321 * Settings for the request. 28322 * 28323 * @return {XMLHttpRequest|XDomainRequest} 28324 * The request object. 28325 * 28326 * @see https://github.com/Raynos/xhr 28327 */ 28328 28329 videojs$1.xhr = xhr; 28330 videojs$1.TextTrack = TextTrack; 28331 videojs$1.AudioTrack = AudioTrack; 28332 videojs$1.VideoTrack = VideoTrack; 28333 ['isEl', 'isTextNode', 'createEl', 'hasClass', 'addClass', 'removeClass', 'toggleClass', 'setAttributes', 'getAttributes', 'emptyEl', 'appendContent', 'insertContent'].forEach(function (k) { 28334 videojs$1[k] = function () { 28335 log.warn("videojs." + k + "() is deprecated; use videojs.dom." + k + "() instead"); 28336 return Dom[k].apply(null, arguments); 28337 }; 28338 }); 28339 videojs$1.computedStyle = computedStyle; 28340 /** 28341 * A reference to the {@link module:dom|DOM utility module} as an object. 28342 * 28343 * @type {Object} 28344 * @see {@link module:dom|dom} 28345 */ 28346 28347 videojs$1.dom = Dom; 28348 /** 28349 * A reference to the {@link module:url|URL utility module} as an object. 28350 * 28351 * @type {Object} 28352 * @see {@link module:url|url} 28353 */ 28354 28355 videojs$1.url = Url; 28356 28357 var urlToolkit = createCommonjsModule(function (module, exports
vendor: 4,642 bytes, lines 28357-28464
28357) { 28358 // see https://tools.ietf.org/html/rfc1808 28359 28360 /* jshint ignore:start */ 28361 (function (root) { 28362 /* jshint ignore:end */ 28363 var URL_REGEX = /^((?:[a-zA-Z0-9+\-.]+:)?)(\/\/[^\/?#]*)?((?:[^\/\?#]*\/)*.*?)??(;.*?)?(\?.*?)?(#.*?)?$/; 28364 var FIRST_SEGMENT_REGEX = /^([^\/?#]*)(.*)$/; 28365 var SLASH_DOT_REGEX = /(?:\/|^)\.(?=\/)/g; 28366 var SLASH_DOT_DOT_REGEX = /(?:\/|^)\.\.\/(?!\.\.\/).*?(?=\/)/g; 28367 var URLToolkit = { 28368 // jshint ignore:line 28369 // If opts.alwaysNormalize is true then the path will always be normalized even when it starts with / or // 28370 // E.g 28371 // With opts.alwaysNormalize = false (default, spec compliant) 28372 // http://a.com/b/cd + /e/f/../g => http://a.com/e/f/../g 28373 // With opts.alwaysNormalize = true (not spec compliant) 28374 // http://a.com/b/cd + /e/f/../g => http://a.com/e/g 28375 buildAbsoluteURL: function buildAbsoluteURL(baseURL, relativeURL, opts) { 28376 opts = opts || {}; // remove any remaining space and CRLF 28377 28378 baseURL = baseURL.trim(); 28379 relativeURL = relativeURL.trim(); 28380 28381 if (!relativeURL) { 28382 // 2a) If the embedded URL is entirely empty, it inherits the 28383 // entire base URL (i.e., is set equal to the base URL) 28384 // and we are done. 28385 if (!opts.alwaysNormalize) { 28386 return baseURL; 28387 } 28388 28389 var basePartsForNormalise = URLToolkit.parseURL(baseURL); 28390 28391 if (!basePartsForNormalise) { 28392 throw new Error('Error trying to parse base URL.'); 28393 } 28394 28395 basePartsForNormalise.path = URLToolkit.normalizePath(basePartsForNormalise.path); 28396 return URLToolkit.buildURLFromParts(basePartsForNormalise); 28397 } 28398 28399 var relativeParts = URLToolkit.parseURL(relativeURL); 28400 28401 if (!relativeParts) { 28402 throw new Error('Error trying to parse relative URL.'); 28403 } 28404 28405 if (relativeParts.scheme) { 28406 // 2b) If the embedded URL starts with a scheme name, it is 28407 // interpreted as an absolute URL and we are done. 28408 if (!opts.alwaysNormalize) { 28409 return relativeURL; 28410 } 28411 28412 relativeParts.path = URLToolkit.normalizePath(relativeParts.path); 28413 return URLToolkit.buildURLFromParts(relativeParts); 28414 } 28415 28416 var baseParts = URLToolkit.parseURL(baseURL); 28417 28418 if (!baseParts) { 28419 throw new Error('Error trying to parse base URL.'); 28420 } 28421 28422 if (!baseParts.netLoc && baseParts.path && baseParts.path[0] !== '/') { 28423 // If netLoc missing and path doesn't start with '/', assume everthing before the first '/' is the netLoc 28424 // This causes 'example.com/a' to be handled as '//example.com/a' instead of '/example.com/a' 28425 var pathParts = FIRST_SEGMENT_REGEX.exec(baseParts.path); 28426 baseParts.netLoc = pathParts[1]; 28427 baseParts.path = pathParts[2]; 28428 } 28429 28430 if (baseParts.netLoc && !baseParts.path) { 28431 baseParts.path = '/'; 28432 } 28433 28434 var builtParts = { 28435 // 2c) Otherwise, the embedded URL inherits the scheme of 28436 // the base URL. 28437 scheme: baseParts.scheme, 28438 netLoc: relativeParts.netLoc, 28439 path: null, 28440 params: relativeParts.params, 28441 query: relativeParts.query, 28442 fragment: relativeParts.fragment 28443 }; 28444 28445 if (!relativeParts.netLoc) { 28446 // 3) If the embedded URL's <net_loc> is non-empty, we skip to 28447 // Step 7. Otherwise, the embedded URL inherits the <net_loc> 28448 // (if any) of the base URL. 28449 builtParts.netLoc = baseParts.netLoc; // 4) If the embedded URL path is preceded by a slash "/", the 28450 // path is not relative and we skip to Step 7. 28451 28452 if (relativeParts.path[0] !== '/') { 28453 if (!relativeParts.path) { 28454 // 5) If the embedded URL path is empty (and not preceded by a 28455 // slash), then the embedded URL inherits the base URL path 28456 builtParts.path = baseParts.path; // 5a) if the embedded URL's <params> is non-empty, we skip to 28457 // step 7; otherwise, it inherits the <params> of the base 28458 // URL (if any) and 28459 28460 if (!relativeParts.params) { 28461 builtParts.params = baseParts.params; // 5b) if the embedded URL's <query> is non-empty, we skip to 28462 // step 7; otherwise, it inherits the <query> of the base 28463 // URL (if any) and we skip to step 7. 28464
28465 if (!relativeParts.query) { 28466 builtParts.query = baseParts.query; 28467 } 28468 } 28469 } else { 28470 // 6) The last segment of the base URL's path (anything 28471 // following the rightmost slash "/", or the entire path if no 28472 // slash is present) is removed and the embedded URL's path is 28473 // appended in its place. 28474 var baseURLPath = baseParts.path; 28475 var newPath = baseURLPath.substring(0, baseURLPath.lastIndexOf('/') + 1) + relativeParts.path; 28476 builtParts.path = URLToolkit.normalizePath(newPath); 28477 } 28478 } 28479 } 28480 28481 if (builtParts.path === null) { 28482 builtParts.path = opts.alwaysNormalize ? URLToolkit.normalizePath(relativeParts.path) : relativeParts.path; 28483 } 28484 28485 return URLToolkit.buildURLFromParts(builtParts); 28486 }, 28487 parseURL: function parseURL(url) { 28488 var parts = URL_REGEX.exec(url); 28489 28490 if (!parts) { 28491 return null; 28492 } 28493 28494 return { 28495 scheme: parts[1] || '', 28496 netLoc: parts[2] || '', 28497 path: parts[3] || '', 28498 params: parts[4] || '', 28499 query: parts[5] || '', 28500 fragment: parts[6] || '' 28501 }; 28502 }, 28503 normalizePath: function normalizePath(path) { 28504 // The following operations are 28505 // then applied, in order, to the new path: 28506 // 6a) All occurrences of "./", where "." is a complete path 28507 // segment, are removed. 28508 // 6b) If the path ends with "." as a complete path segment, 28509 // that "." is removed. 28510 path = path.split('').reverse().join('').replace(SLASH_DOT_REGEX, ''); // 6c) All occurrences of "<segment>/../", where <segment> is a 28511 // complete path segment not equal to "..", are removed. 28512 // Removal of these path segments is performed iteratively, 28513 // removing the leftmost matching pattern on each iteration, 28514 // until no matching pattern remains. 28515 // 6d) If the path ends with "<segment>/..", where <segment> is a 28516 // complete path segment not equal to "..", that 28517 // "<segment>/.." is removed. 28518 28519 while (path.length !== (path = path.replace(SLASH_DOT_DOT_REGEX, '')).length) {} // jshint ignore:line 28520 28521 28522 return path.split('').reverse().join(''); 28523 }, 28524 buildURLFromParts: function buildURLFromParts(parts) { 28525 return parts.scheme + parts.netLoc + parts.path + parts.params + parts.query + parts.fragment; 28526 } 28527 }; 28528 /* jshint ignore:start */ 28529 28530 module.exports = URLToolkit; 28531 })(commonjsGlobal); 28532 /* jshint ignore:end */ 28533 28534 }); 28535 28536 /*! @name m3u8-parser @version 4.3.0 @license Apache-2.0 */ 28537 function _extends$1() { 28538 _extends$1 = Object.assign || function (target) { 28539 for (var i = 1; i < arguments.length; i++) { 28540 var source = arguments[i]; 28541 28542 for (var key in source) { 28543 if (Object.prototype.hasOwnProperty.call(source, key)) { 28544 target[key] = source[key]; 28545 } 28546 } 28547 } 28548 28549 return target; 28550 }; 28551 28552 return _extends$1.apply(this, arguments); 28553 } 28554 28555 function _inheritsLoose$1(subClass, superClass) { 28556 subClass.prototype = Object.create(superClass.prototype); 28557 subClass.prototype.constructor = subClass; 28558 subClass.__proto__ = superClass; 28559 } 28560 28561 function _assertThisInitialized$1(self) { 28562 if (self === void 0) { 28563 throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); 28564 } 28565 28566 return self; 28567 } 28568 /** 28569 * @file stream.js 28570 */ 28571 28572 /** 28573 * A lightweight readable stream implementation that handles event dispatching. 28574 * 28575 * @class Stream 28576 */ 28577 28578 28579 var Stream = 28580 /*#__PURE__*/ 28581 function () { 28582 function Stream() { 28583 this.listeners = {}; 28584 } 28585 /** 28586 * Add a listener for a specified event type. 28587 * 28588 * @param {string} type the event name 28589 * @param {Function} listener the callback to be invoked when an event of 28590 * the specified type occurs 28591 */ 28592 28593 28594 var _proto = Stream.prototype; 28595 28596 _proto.on = function on(type, listener) { 28597 if (!this.listeners[type]) { 28598 this.listeners[type] = []; 28599 } 28600 28601 this.listeners[type].push(listener); 28602 }; 28603 /** 28604 * Remove a listener for a specified event type. 28605 * 28606 * @param {string} type the event name 28607 * @param {Function} listener a function previously registered for this 28608 * type of event through `on` 28609 * @return {boolean} if we could turn it off or not 28610 */ 28611 28612 28613 _proto.off = function off(type, listener) { 28614 if (!this.listeners[type]) { 28615 return false;
vendor: 33,768 bytes, lines 28616-29795
28616 } 28617 28618 var index = this.listeners[type].indexOf(listener); 28619 this.listeners[type].splice(index, 1); 28620 return index > -1; 28621 }; 28622 /** 28623 * Trigger an event of the specified type on this stream. Any additional 28624 * arguments to this function are passed as parameters to event listeners. 28625 * 28626 * @param {string} type the event name 28627 */ 28628 28629 28630 _proto.trigger = function trigger(type) { 28631 var callbacks = this.listeners[type]; 28632 var i; 28633 var length; 28634 var args; 28635 28636 if (!callbacks) { 28637 return; 28638 } // Slicing the arguments on every invocation of this method 28639 // can add a significant amount of overhead. Avoid the 28640 // intermediate object creation for the common case of a 28641 // single callback argument 28642 28643 28644 if (arguments.length === 2) { 28645 length = callbacks.length; 28646 28647 for (i = 0; i < length; ++i) { 28648 callbacks[i].call(this, arguments[1]); 28649 } 28650 } else { 28651 args = Array.prototype.slice.call(arguments, 1); 28652 length = callbacks.length; 28653 28654 for (i = 0; i < length; ++i) { 28655 callbacks[i].apply(this, args); 28656 } 28657 } 28658 }; 28659 /** 28660 * Destroys the stream and cleans up. 28661 */ 28662 28663 28664 _proto.dispose = function dispose() { 28665 this.listeners = {}; 28666 }; 28667 /** 28668 * Forwards all `data` events on this stream to the destination stream. The 28669 * destination stream should provide a method `push` to receive the data 28670 * events as they arrive. 28671 * 28672 * @param {Stream} destination the stream that will receive all `data` events 28673 * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options 28674 */ 28675 28676 28677 _proto.pipe = function pipe(destination) { 28678 this.on('data', function (data) { 28679 destination.push(data); 28680 }); 28681 }; 28682 28683 return Stream; 28684 }(); 28685 /** 28686 * A stream that buffers string input and generates a `data` event for each 28687 * line. 28688 * 28689 * @class LineStream 28690 * @extends Stream 28691 */ 28692 28693 28694 var LineStream = 28695 /*#__PURE__*/ 28696 function (_Stream) { 28697 _inheritsLoose$1(LineStream, _Stream); 28698 28699 function LineStream() { 28700 var _this; 28701 28702 _this = _Stream.call(this) || this; 28703 _this.buffer = ''; 28704 return _this; 28705 } 28706 /** 28707 * Add new data to be parsed. 28708 * 28709 * @param {string} data the text to process 28710 */ 28711 28712 28713 var _proto = LineStream.prototype; 28714 28715 _proto.push = function push(data) { 28716 var nextNewline; 28717 this.buffer += data; 28718 nextNewline = this.buffer.indexOf('\n'); 28719 28720 for (; nextNewline > -1; nextNewline = this.buffer.indexOf('\n')) { 28721 this.trigger('data', this.buffer.substring(0, nextNewline)); 28722 this.buffer = this.buffer.substring(nextNewline + 1); 28723 } 28724 }; 28725 28726 return LineStream; 28727 }(Stream); 28728 /** 28729 * "forgiving" attribute list psuedo-grammar: 28730 * attributes -> keyvalue (',' keyvalue)* 28731 * keyvalue -> key '=' value 28732 * key -> [^=]* 28733 * value -> '"' [^"]* '"' | [^,]* 28734 */ 28735 28736 28737 var attributeSeparator = function attributeSeparator() { 28738 var key = '[^=]*'; 28739 var value = '"[^"]*"|[^,]*'; 28740 var keyvalue = '(?:' + key + ')=(?:' + value + ')'; 28741 return new RegExp('(?:^|,)(' + keyvalue + ')'); 28742 }; 28743 /** 28744 * Parse attributes from a line given the separator 28745 * 28746 * @param {string} attributes the attribute line to parse 28747 */ 28748 28749 28750 var parseAttributes = function parseAttributes(attributes) { 28751 // split the string using attributes as the separator 28752 var attrs = attributes.split(attributeSeparator()); 28753 var result = {}; 28754 var i = attrs.length; 28755 var attr; 28756 28757 while (i--) { 28758 // filter out unmatched portions of the string 28759 if (attrs[i] === '') { 28760 continue; 28761 } // split the key and value 28762 28763 28764 attr = /([^=]*)=(.*)/.exec(attrs[i]).slice(1); // trim whitespace and remove optional quotes around the value 28765 28766 attr[0] = attr[0].replace(/^\s+|\s+$/g, ''); 28767 attr[1] = attr[1].replace(/^\s+|\s+$/g, ''); 28768 attr[1] = attr[1].replace(/^['"](.*)['"]$/g, '$1'); 28769 result[attr[0]] = attr[1]; 28770 } 28771 28772 return result; 28773 }; 28774 /** 28775 * A line-level M3U8 parser event stream. It expects to receive input one 28776 * line at a time and performs a context-free parse of its contents. A stream 28777 * interpretation of a manifest can be useful if the manifest is expected to 28778 * be too large to fit comfortably into memory or the entirety of the input 28779 * is not immediately available. Otherwise, it's probably much easier to work 28780 * with a regular `Parser` object. 28781 * 28782 * Produces `data` events with an object that captures the parser's 28783 * interpretation of the input. That object has a property `tag` that is one 28784 * of `uri`, `comment`, or `tag`. URIs only have a single additional 28785 * property, `line`, which captures the entirety of the input without 28786 * interpretation. Comments similarly have a single additional property 28787 * `text` which is the input without the leading `#`. 28788 * 28789 * Tags always have a property `tagType` which is the lower-cased version of 28790 * the M3U8 directive without the `#EXT` or `#EXT-X-` prefix. For instance, 28791 * `#EXT-X-MEDIA-SEQUENCE` becomes `media-sequence` when parsed. Unrecognized 28792 * tags are given the tag type `unknown` and a single additional property 28793 * `data` with the remainder of the input. 28794 * 28795 * @class ParseStream 28796 * @extends Stream 28797 */ 28798 28799 28800 var ParseStream = 28801 /*#__PURE__*/ 28802 function (_Stream) { 28803 _inheritsLoose$1(ParseStream, _Stream); 28804 28805 function ParseStream() { 28806 var _this; 28807 28808 _this = _Stream.call(this) || this; 28809 _this.customParsers = []; 28810 _this.tagMappers = []; 28811 return _this; 28812 } 28813 /** 28814 * Parses an additional line of input. 28815 * 28816 * @param {string} line a single line of an M3U8 file to parse 28817 */ 28818 28819 28820 var _proto = ParseStream.prototype; 28821 28822 _proto.push = function push(line) { 28823 var _this2 = this; 28824 28825 var match; 28826 var event; // strip whitespace 28827 28828 line = line.trim(); 28829 28830 if (line.length === 0) { 28831 // ignore empty lines 28832 return; 28833 } // URIs 28834 28835 28836 if (line[0] !== '#') { 28837 this.trigger('data', { 28838 type: 'uri', 28839 uri: line 28840 }); 28841 return; 28842 } // map tags 28843 28844 28845 var newLines = this.tagMappers.reduce(function (acc, mapper) { 28846 var mappedLine = mapper(line); // skip if unchanged 28847 28848 if (mappedLine === line) { 28849 return acc; 28850 } 28851 28852 return acc.concat([mappedLine]); 28853 }, [line]); 28854 newLines.forEach(function (newLine) { 28855 for (var i = 0; i < _this2.customParsers.length; i++) { 28856 if (_this2.customParsers[i].call(_this2, newLine)) { 28857 return; 28858 } 28859 } // Comments 28860 28861 28862 if (newLine.indexOf('#EXT') !== 0) { 28863 _this2.trigger('data', { 28864 type: 'comment', 28865 text: newLine.slice(1) 28866 }); 28867 28868 return; 28869 } // strip off any carriage returns here so the regex matching 28870 // doesn't have to account for them. 28871 28872 28873 newLine = newLine.replace('\r', ''); // Tags 28874 28875 match = /^#EXTM3U/.exec(newLine); 28876 28877 if (match) { 28878 _this2.trigger('data', { 28879 type: 'tag', 28880 tagType: 'm3u' 28881 }); 28882 28883 return; 28884 } 28885 28886 match = /^#EXTINF:?([0-9\.]*)?,?(.*)?$/.exec(newLine); 28887 28888 if (match) { 28889 event = { 28890 type: 'tag', 28891 tagType: 'inf' 28892 }; 28893 28894 if (match[1]) { 28895 event.duration = parseFloat(match[1]); 28896 } 28897 28898 if (match[2]) { 28899 event.title = match[2]; 28900 } 28901 28902 _this2.trigger('data', event); 28903 28904 return; 28905 } 28906 28907 match = /^#EXT-X-TARGETDURATION:?([0-9.]*)?/.exec(newLine); 28908 28909 if (match) { 28910 event = { 28911 type: 'tag', 28912 tagType: 'targetduration' 28913 }; 28914 28915 if (match[1]) { 28916 event.duration = parseInt(match[1], 10); 28917 } 28918 28919 _this2.trigger('data', event); 28920 28921 return; 28922 } 28923 28924 match = /^#ZEN-TOTAL-DURATION:?([0-9.]*)?/.exec(newLine); 28925 28926 if (match) { 28927 event = { 28928 type: 'tag', 28929 tagType: 'totalduration' 28930 }; 28931 28932 if (match[1]) { 28933 event.duration = parseInt(match[1], 10); 28934 } 28935 28936 _this2.trigger('data', event); 28937 28938 return; 28939 } 28940 28941 match = /^#EXT-X-VERSION:?([0-9.]*)?/.exec(newLine); 28942 28943 if (match) { 28944 event = { 28945 type: 'tag', 28946 tagType: 'version' 28947 }; 28948 28949 if (match[1]) { 28950 event.version = parseInt(match[1], 10); 28951 } 28952 28953 _this2.trigger('data', event); 28954 28955 return; 28956 } 28957 28958 match = /^#EXT-X-MEDIA-SEQUENCE:?(\-?[0-9.]*)?/.exec(newLine); 28959 28960 if (match) { 28961 event = { 28962 type: 'tag', 28963 tagType: 'media-sequence' 28964 }; 28965 28966 if (match[1]) { 28967 event.number = parseInt(match[1], 10); 28968 } 28969 28970 _this2.trigger('data', event); 28971 28972 return; 28973 } 28974 28975 match = /^#EXT-X-DISCONTINUITY-SEQUENCE:?(\-?[0-9.]*)?/.exec(newLine); 28976 28977 if (match) { 28978 event = { 28979 type: 'tag', 28980 tagType: 'discontinuity-sequence' 28981 }; 28982 28983 if (match[1]) { 28984 event.number = parseInt(match[1], 10); 28985 } 28986 28987 _this2.trigger('data', event); 28988 28989 return; 28990 } 28991 28992 match = /^#EXT-X-PLAYLIST-TYPE:?(.*)?$/.exec(newLine); 28993 28994 if (match) { 28995 event = { 28996 type: 'tag', 28997 tagType: 'playlist-type' 28998 }; 28999 29000 if (match[1]) { 29001 event.playlistType = match[1]; 29002 } 29003 29004 _this2.trigger('data', event); 29005 29006 return; 29007 } 29008 29009 match = /^#EXT-X-BYTERANGE:?([0-9.]*)?@?([0-9.]*)?/.exec(newLine); 29010 29011 if (match) { 29012 event = { 29013 type: 'tag', 29014 tagType: 'byterange' 29015 }; 29016 29017 if (match[1]) { 29018 event.length = parseInt(match[1], 10); 29019 } 29020 29021 if (match[2]) { 29022 event.offset = parseInt(match[2], 10); 29023 } 29024 29025 _this2.trigger('data', event); 29026 29027 return; 29028 } 29029 29030 match = /^#EXT-X-ALLOW-CACHE:?(YES|NO)?/.exec(newLine); 29031 29032 if (match) { 29033 event = { 29034 type: 'tag', 29035 tagType: 'allow-cache' 29036 }; 29037 29038 if (match[1]) { 29039 event.allowed = !/NO/.test(match[1]); 29040 } 29041 29042 _this2.trigger('data', event); 29043 29044 return; 29045 } 29046 29047 match = /^#EXT-X-MAP:?(.*)$/.exec(newLine); 29048 29049 if (match) { 29050 event = { 29051 type: 'tag', 29052 tagType: 'map' 29053 }; 29054 29055 if (match[1]) { 29056 var attributes = parseAttributes(match[1]); 29057 29058 if (attributes.URI) { 29059 event.uri = attributes.URI; 29060 } 29061 29062 if (attributes.BYTERANGE) { 29063 var _attributes$BYTERANGE = attributes.BYTERANGE.split('@'), 29064 length = _attributes$BYTERANGE[0], 29065 offset = _attributes$BYTERANGE[1]; 29066 29067 event.byterange = {}; 29068 29069 if (length) { 29070 event.byterange.length = parseInt(length, 10); 29071 } 29072 29073 if (offset) { 29074 event.byterange.offset = parseInt(offset, 10); 29075 } 29076 } 29077 } 29078 29079 _this2.trigger('data', event); 29080 29081 return; 29082 } 29083 29084 match = /^#EXT-X-STREAM-INF:?(.*)$/.exec(newLine); 29085 29086 if (match) { 29087 event = { 29088 type: 'tag', 29089 tagType: 'stream-inf' 29090 }; 29091 29092 if (match[1]) { 29093 event.attributes = parseAttributes(match[1]); 29094 29095 if (event.attributes.RESOLUTION) { 29096 var split = event.attributes.RESOLUTION.split('x'); 29097 var resolution = {}; 29098 29099 if (split[0]) { 29100 resolution.width = parseInt(split[0], 10); 29101 } 29102 29103 if (split[1]) { 29104 resolution.height = parseInt(split[1], 10); 29105 } 29106 29107 event.attributes.RESOLUTION = resolution; 29108 } 29109 29110 if (event.attributes.BANDWIDTH) { 29111 event.attributes.BANDWIDTH = parseInt(event.attributes.BANDWIDTH, 10); 29112 } 29113 29114 if (event.attributes['PROGRAM-ID']) { 29115 event.attributes['PROGRAM-ID'] = parseInt(event.attributes['PROGRAM-ID'], 10); 29116 } 29117 } 29118 29119 _this2.trigger('data', event); 29120 29121 return; 29122 } 29123 29124 match = /^#EXT-X-MEDIA:?(.*)$/.exec(newLine); 29125 29126 if (match) { 29127 event = { 29128 type: 'tag', 29129 tagType: 'media' 29130 }; 29131 29132 if (match[1]) { 29133 event.attributes = parseAttributes(match[1]); 29134 } 29135 29136 _this2.trigger('data', event); 29137 29138 return; 29139 } 29140 29141 match = /^#EXT-X-ENDLIST/.exec(newLine); 29142 29143 if (match) { 29144 _this2.trigger('data', { 29145 type: 'tag', 29146 tagType: 'endlist' 29147 }); 29148 29149 return; 29150 } 29151 29152 match = /^#EXT-X-DISCONTINUITY/.exec(newLine); 29153 29154 if (match) { 29155 _this2.trigger('data', { 29156 type: 'tag', 29157 tagType: 'discontinuity' 29158 }); 29159 29160 return; 29161 } 29162 29163 match = /^#EXT-X-PROGRAM-DATE-TIME:?(.*)$/.exec(newLine); 29164 29165 if (match) { 29166 event = { 29167 type: 'tag', 29168 tagType: 'program-date-time' 29169 }; 29170 29171 if (match[1]) { 29172 event.dateTimeString = match[1]; 29173 event.dateTimeObject = new Date(match[1]); 29174 } 29175 29176 _this2.trigger('data', event); 29177 29178 return; 29179 } 29180 29181 match = /^#EXT-X-KEY:?(.*)$/.exec(newLine); 29182 29183 if (match) { 29184 event = { 29185 type: 'tag', 29186 tagType: 'key' 29187 }; 29188 29189 if (match[1]) { 29190 event.attributes = parseAttributes(match[1]); // parse the IV string into a Uint32Array 29191 29192 if (event.attributes.IV) { 29193 if (event.attributes.IV.substring(0, 2).toLowerCase() === '0x') { 29194 event.attributes.IV = event.attributes.IV.substring(2); 29195 } 29196 29197 event.attributes.IV = event.attributes.IV.match(/.{8}/g); 29198 event.attributes.IV[0] = parseInt(event.attributes.IV[0], 16); 29199 event.attributes.IV[1] = parseInt(event.attributes.IV[1], 16); 29200 event.attributes.IV[2] = parseInt(event.attributes.IV[2], 16); 29201 event.attributes.IV[3] = parseInt(event.attributes.IV[3], 16); 29202 event.attributes.IV = new Uint32Array(event.attributes.IV); 29203 } 29204 } 29205 29206 _this2.trigger('data', event); 29207 29208 return; 29209 } 29210 29211 match = /^#EXT-X-START:?(.*)$/.exec(newLine); 29212 29213 if (match) { 29214 event = { 29215 type: 'tag', 29216 tagType: 'start' 29217 }; 29218 29219 if (match[1]) { 29220 event.attributes = parseAttributes(match[1]); 29221 event.attributes['TIME-OFFSET'] = parseFloat(event.attributes['TIME-OFFSET']); 29222 event.attributes.PRECISE = /YES/.test(event.attributes.PRECISE); 29223 } 29224 29225 _this2.trigger('data', event); 29226 29227 return; 29228 } 29229 29230 match = /^#EXT-X-CUE-OUT-CONT:?(.*)?$/.exec(newLine); 29231 29232 if (match) { 29233 event = { 29234 type: 'tag', 29235 tagType: 'cue-out-cont' 29236 }; 29237 29238 if (match[1]) { 29239 event.data = match[1]; 29240 } else { 29241 event.data = ''; 29242 } 29243 29244 _this2.trigger('data', event); 29245 29246 return; 29247 } 29248 29249 match = /^#EXT-X-CUE-OUT:?(.*)?$/.exec(newLine); 29250 29251 if (match) { 29252 event = { 29253 type: 'tag', 29254 tagType: 'cue-out' 29255 }; 29256 29257 if (match[1]) { 29258 event.data = match[1]; 29259 } else { 29260 event.data = ''; 29261 } 29262 29263 _this2.trigger('data', event); 29264 29265 return; 29266 } 29267 29268 match = /^#EXT-X-CUE-IN:?(.*)?$/.exec(newLine); 29269 29270 if (match) { 29271 event = { 29272 type: 'tag', 29273 tagType: 'cue-in' 29274 }; 29275 29276 if (match[1]) { 29277 event.data = match[1]; 29278 } else { 29279 event.data = ''; 29280 } 29281 29282 _this2.trigger('data', event); 29283 29284 return; 29285 } // unknown tag type 29286 29287 29288 _this2.trigger('data', { 29289 type: 'tag', 29290 data: newLine.slice(4) 29291 }); 29292 }); 29293 }; 29294 /** 29295 * Add a parser for custom headers 29296 * 29297 * @param {Object} options a map of options for the added parser 29298 * @param {RegExp} options.expression a regular expression to match the custom header 29299 * @param {string} options.customType the custom type to register to the output 29300 * @param {Function} [options.dataParser] function to parse the line into an object 29301 * @param {boolean} [options.segment] should tag data be attached to the segment object 29302 */ 29303 29304 29305 _proto.addParser = function addParser(_ref) { 29306 var _this3 = this; 29307 29308 var expression = _ref.expression, 29309 customType = _ref.customType, 29310 dataParser = _ref.dataParser, 29311 segment = _ref.segment; 29312 29313 if (typeof dataParser !== 'function') { 29314 dataParser = function dataParser(line) { 29315 return line; 29316 }; 29317 } 29318 29319 this.customParsers.push(function (line) { 29320 var match = expression.exec(line); 29321 29322 if (match) { 29323 _this3.trigger('data', { 29324 type: 'custom', 29325 data: dataParser(line), 29326 customType: customType, 29327 segment: segment 29328 }); 29329 29330 return true; 29331 } 29332 }); 29333 }; 29334 /** 29335 * Add a custom header mapper 29336 * 29337 * @param {Object} options 29338 * @param {RegExp} options.expression a regular expression to match the custom header 29339 * @param {Function} options.map function to translate tag into a different tag 29340 */ 29341 29342 29343 _proto.addTagMapper = function addTagMapper(_ref2) { 29344 var expression = _ref2.expression, 29345 map = _ref2.map; 29346 29347 var mapFn = function mapFn(line) { 29348 if (expression.test(line)) { 29349 return map(line); 29350 } 29351 29352 return line; 29353 }; 29354 29355 this.tagMappers.push(mapFn); 29356 }; 29357 29358 return ParseStream; 29359 }(Stream); 29360 /** 29361 * A parser for M3U8 files. The current interpretation of the input is 29362 * exposed as a property `manifest` on parser objects. It's just two lines to 29363 * create and parse a manifest once you have the contents available as a string: 29364 * 29365 * ```js 29366 * var parser = new m3u8.Parser(); 29367 * parser.push(xhr.responseText); 29368 * ``` 29369 * 29370 * New input can later be applied to update the manifest object by calling 29371 * `push` again. 29372 * 29373 * The parser attempts to create a usable manifest object even if the 29374 * underlying input is somewhat nonsensical. It emits `info` and `warning` 29375 * events during the parse if it encounters input that seems invalid or 29376 * requires some property of the manifest object to be defaulted. 29377 * 29378 * @class Parser 29379 * @extends Stream 29380 */ 29381 29382 29383 var Parser = 29384 /*#__PURE__*/ 29385 function (_Stream) { 29386 _inheritsLoose$1(Parser, _Stream); 29387 29388 function Parser() { 29389 var _this; 29390 29391 _this = _Stream.call(this) || this; 29392 _this.lineStream = new LineStream(); 29393 _this.parseStream = new ParseStream(); 29394 29395 _this.lineStream.pipe(_this.parseStream); 29396 /* eslint-disable consistent-this */ 29397 29398 29399 var self = _assertThisInitialized$1(_assertThisInitialized$1(_this)); 29400 /* eslint-enable consistent-this */ 29401 29402 29403 var uris = []; 29404 var currentUri = {}; // if specified, the active EXT-X-MAP definition 29405 29406 var currentMap; // if specified, the active decryption key 29407 29408 var _key; 29409 29410 var noop = function noop() {}; 29411 29412 var defaultMediaGroups = { 29413 'AUDIO': {}, 29414 'VIDEO': {}, 29415 'CLOSED-CAPTIONS': {}, 29416 'SUBTITLES': {} 29417 }; // group segments into numbered timelines delineated by discontinuities 29418 29419 var currentTimeline = 0; // the manifest is empty until the parse stream begins delivering data 29420 29421 _this.manifest = { 29422 allowCache: true, 29423 discontinuityStarts: [], 29424 segments: [] 29425 }; // update the manifest with the m3u8 entry from the parse stream 29426 29427 _this.parseStream.on('data', function (entry) { 29428 var mediaGroup; 29429 var rendition; 29430 ({ 29431 tag: function tag() { 29432 // switch based on the tag type 29433 (({ 29434 'allow-cache': function allowCache() { 29435 this.manifest.allowCache = entry.allowed; 29436 29437 if (!('allowed' in entry)) { 29438 this.trigger('info', { 29439 message: 'defaulting allowCache to YES' 29440 }); 29441 this.manifest.allowCache = true; 29442 } 29443 }, 29444 byterange: function byterange() { 29445 var byterange = {}; 29446 29447 if ('length' in entry) { 29448 currentUri.byterange = byterange; 29449 byterange.length = entry.length; 29450 29451 if (!('offset' in entry)) { 29452 this.trigger('info', { 29453 message: 'defaulting offset to zero' 29454 }); 29455 entry.offset = 0; 29456 } 29457 } 29458 29459 if ('offset' in entry) { 29460 currentUri.byterange = byterange; 29461 byterange.offset = entry.offset; 29462 } 29463 }, 29464 endlist: function endlist() { 29465 this.manifest.endList = true; 29466 }, 29467 inf: function inf() { 29468 if (!('mediaSequence' in this.manifest)) { 29469 this.manifest.mediaSequence = 0; 29470 this.trigger('info', { 29471 message: 'defaulting media sequence to zero' 29472 }); 29473 } 29474 29475 if (!('discontinuitySequence' in this.manifest)) { 29476 this.manifest.discontinuitySequence = 0; 29477 this.trigger('info', { 29478 message: 'defaulting discontinuity sequence to zero' 29479 }); 29480 } 29481 29482 if (entry.duration > 0) { 29483 currentUri.duration = entry.duration; 29484 } 29485 29486 if (entry.duration === 0) { 29487 currentUri.duration = 0.01; 29488 this.trigger('info', { 29489 message: 'updating zero segment duration to a small value' 29490 }); 29491 } 29492 29493 this.manifest.segments = uris; 29494 }, 29495 key: function key() { 29496 if (!entry.attributes) { 29497 this.trigger('warn', { 29498 message: 'ignoring key declaration without attribute list' 29499 }); 29500 return; 29501 } // clear the active encryption key 29502 29503 29504 if (entry.attributes.METHOD === 'NONE') { 29505 _key = null; 29506 return; 29507 } 29508 29509 if (!entry.attributes.URI) { 29510 this.trigger('warn', { 29511 message: 'ignoring key declaration without URI' 29512 }); 29513 return; 29514 } 29515 29516 if (!entry.attributes.METHOD) { 29517 this.trigger('warn', { 29518 message: 'defaulting key method to AES-128' 29519 }); 29520 } // setup an encryption key for upcoming segments 29521 29522 29523 _key = { 29524 method: entry.attributes.METHOD || 'AES-128', 29525 uri: entry.attributes.URI 29526 }; 29527 29528 if (typeof entry.attributes.IV !== 'undefined') { 29529 _key.iv = entry.attributes.IV; 29530 } 29531 }, 29532 'media-sequence': function mediaSequence() { 29533 if (!isFinite(entry.number)) { 29534 this.trigger('warn', { 29535 message: 'ignoring invalid media sequence: ' + entry.number 29536 }); 29537 return; 29538 } 29539 29540 this.manifest.mediaSequence = entry.number; 29541 }, 29542 'discontinuity-sequence': function discontinuitySequence() { 29543 if (!isFinite(entry.number)) { 29544 this.trigger('warn', { 29545 message: 'ignoring invalid discontinuity sequence: ' + entry.number 29546 }); 29547 return; 29548 } 29549 29550 this.manifest.discontinuitySequence = entry.number; 29551 currentTimeline = entry.number; 29552 }, 29553 'playlist-type': function playlistType() { 29554 if (!/VOD|EVENT/.test(entry.playlistType)) { 29555 this.trigger('warn', { 29556 message: 'ignoring unknown playlist type: ' + entry.playlist 29557 }); 29558 return; 29559 } 29560 29561 this.manifest.playlistType = entry.playlistType; 29562 }, 29563 map: function map() { 29564 currentMap = {}; 29565 29566 if (entry.uri) { 29567 currentMap.uri = entry.uri; 29568 } 29569 29570 if (entry.byterange) { 29571 currentMap.byterange = entry.byterange; 29572 } 29573 }, 29574 'stream-inf': function streamInf() { 29575 this.manifest.playlists = uris; 29576 this.manifest.mediaGroups = this.manifest.mediaGroups || defaultMediaGroups; 29577 29578 if (!entry.attributes) { 29579 this.trigger('warn', { 29580 message: 'ignoring empty stream-inf attributes' 29581 }); 29582 return; 29583 } 29584 29585 if (!currentUri.attributes) { 29586 currentUri.attributes = {}; 29587 } 29588 29589 _extends$1(currentUri.attributes, entry.attributes); 29590 }, 29591 media: function media() { 29592 this.manifest.mediaGroups = this.manifest.mediaGroups || defaultMediaGroups; 29593 29594 if (!(entry.attributes && entry.attributes.TYPE && entry.attributes['GROUP-ID'] && entry.attributes.NAME)) { 29595 this.trigger('warn', { 29596 message: 'ignoring incomplete or missing media group' 29597 }); 29598 return; 29599 } // find the media group, creating defaults as necessary 29600 29601 29602 var mediaGroupType = this.manifest.mediaGroups[entry.attributes.TYPE]; 29603 mediaGroupType[entry.attributes['GROUP-ID']] = mediaGroupType[entry.attributes['GROUP-ID']] || {}; 29604 mediaGroup = mediaGroupType[entry.attributes['GROUP-ID']]; // collect the rendition metadata 29605 29606 rendition = { 29607 default: /yes/i.test(entry.attributes.DEFAULT) 29608 }; 29609 29610 if (rendition.default) { 29611 rendition.autoselect = true; 29612 } else { 29613 rendition.autoselect = /yes/i.test(entry.attributes.AUTOSELECT); 29614 } 29615 29616 if (entry.attributes.LANGUAGE) { 29617 rendition.language = entry.attributes.LANGUAGE; 29618 } 29619 29620 if (entry.attributes.URI) { 29621 rendition.uri = entry.attributes.URI; 29622 } 29623 29624 if (entry.attributes['INSTREAM-ID']) { 29625 rendition.instreamId = entry.attributes['INSTREAM-ID']; 29626 } 29627 29628 if (entry.attributes.CHARACTERISTICS) { 29629 rendition.characteristics = entry.attributes.CHARACTERISTICS; 29630 } 29631 29632 if (entry.attributes.FORCED) { 29633 rendition.forced = /yes/i.test(entry.attributes.FORCED); 29634 } // insert the new rendition 29635 29636 29637 mediaGroup[entry.attributes.NAME] = rendition; 29638 }, 29639 discontinuity: function discontinuity() { 29640 currentTimeline += 1; 29641 currentUri.discontinuity = true; 29642 this.manifest.discontinuityStarts.push(uris.length); 29643 }, 29644 'program-date-time': function programDateTime() { 29645 if (typeof this.manifest.dateTimeString === 'undefined') { 29646 // PROGRAM-DATE-TIME is a media-segment tag, but for backwards 29647 // compatibility, we add the first occurence of the PROGRAM-DATE-TIME tag 29648 // to the manifest object 29649 // TODO: Consider removing this in future major version 29650 this.manifest.dateTimeString = entry.dateTimeString; 29651 this.manifest.dateTimeObject = entry.dateTimeObject; 29652 } 29653 29654 currentUri.dateTimeString = entry.dateTimeString; 29655 currentUri.dateTimeObject = entry.dateTimeObject; 29656 }, 29657 targetduration: function targetduration() { 29658 if (!isFinite(entry.duration) || entry.duration < 0) { 29659 this.trigger('warn', { 29660 message: 'ignoring invalid target duration: ' + entry.duration 29661 }); 29662 return; 29663 } 29664 29665 this.manifest.targetDuration = entry.duration; 29666 }, 29667 totalduration: function totalduration() { 29668 if (!isFinite(entry.duration) || entry.duration < 0) { 29669 this.trigger('warn', { 29670 message: 'ignoring invalid total duration: ' + entry.duration 29671 }); 29672 return; 29673 } 29674 29675 this.manifest.totalDuration = entry.duration; 29676 }, 29677 start: function start() { 29678 if (!entry.attributes || isNaN(entry.attributes['TIME-OFFSET'])) { 29679 this.trigger('warn', { 29680 message: 'ignoring start declaration without appropriate attribute list' 29681 }); 29682 return; 29683 } 29684 29685 this.manifest.start = { 29686 timeOffset: entry.attributes['TIME-OFFSET'], 29687 precise: entry.attributes.PRECISE 29688 }; 29689 }, 29690 'cue-out': function cueOut() { 29691 currentUri.cueOut = entry.data; 29692 }, 29693 'cue-out-cont': function cueOutCont() { 29694 currentUri.cueOutCont = entry.data; 29695 }, 29696 'cue-in': function cueIn() { 29697 currentUri.cueIn = entry.data; 29698 } 29699 })[entry.tagType] || noop).call(self); 29700 }, 29701 uri: function uri() { 29702 currentUri.uri = entry.uri; 29703 uris.push(currentUri); // if no explicit duration was declared, use the target duration 29704 29705 if (this.manifest.targetDuration && !('duration' in currentUri)) { 29706 this.trigger('warn', { 29707 message: 'defaulting segment duration to the target duration' 29708 }); 29709 currentUri.duration = this.manifest.targetDuration; 29710 } // annotate with encryption information, if necessary 29711 29712 29713 if (_key) { 29714 currentUri.key = _key; 29715 } 29716 29717 currentUri.timeline = currentTimeline; // annotate with initialization segment information, if necessary 29718 29719 if (currentMap) { 29720 currentUri.map = currentMap; 29721 } // prepare for the next URI 29722 29723 29724 currentUri = {}; 29725 }, 29726 comment: function comment() {// comments are not important for playback 29727 }, 29728 custom: function custom() { 29729 // if this is segment-level data attach the output to the segment 29730 if (entry.segment) { 29731 currentUri.custom = currentUri.custom || {}; 29732 currentUri.custom[entry.customType] = entry.data; // if this is manifest-level data attach to the top level manifest object 29733 } else { 29734 this.manifest.custom = this.manifest.custom || {}; 29735 this.manifest.custom[entry.customType] = entry.data; 29736 } 29737 } 29738 })[entry.type].call(self); 29739 }); 29740 29741 return _this; 29742 } 29743 /** 29744 * Parse the input string and update the manifest object. 29745 * 29746 * @param {string} chunk a potentially incomplete portion of the manifest 29747 */ 29748 29749 29750 var _proto = Parser.prototype; 29751 29752 _proto.push = function push(chunk) { 29753 this.lineStream.push(chunk); 29754 }; 29755 /** 29756 * Flush any remaining input. This can be handy if the last line of an M3U8 29757 * manifest did not contain a trailing newline but the file has been 29758 * completely received. 29759 */ 29760 29761 29762 _proto.end = function end() { 29763 // flush any buffered input 29764 this.lineStream.push('\n'); 29765 }; 29766 /** 29767 * Add an additional parser for non-standard tags 29768 * 29769 * @param {Object} options a map of options for the added parser 29770 * @param {RegExp} options.expression a regular expression to match the custom header 29771 * @param {string} options.type the type to register to the output 29772 * @param {Function} [options.dataParser] function to parse the line into an object 29773 * @param {boolean} [options.segment] should tag data be attached to the segment object 29774 */ 29775 29776 29777 _proto.addParser = function addParser(options) { 29778 this.parseStream.addParser(options); 29779 }; 29780 /** 29781 * Add a custom header mapper 29782 * 29783 * @param {Object} options 29784 * @param {RegExp} options.expression a regular expression to match the custom header 29785 * @param {Function} options.map function to translate tag into a different tag 29786 */ 29787 29788 29789 _proto.addTagMapper = function addTagMapper(options) { 29790 this.parseStream.addTagMapper(options); 29791 }; 29792 29793 return Parser; 29794 }(Stream); 29795
29796 /*! @name mpd-parser @version 0.7.0 @license Apache-2.0 */ 29797 29798 var isObject$1 = function isObject(obj) { 29799 return !!obj && typeof obj === 'object'; 29800 }; 29801 29802 var merge = function merge() { 29803 for (var _len = arguments.length, objects = new Array(_len), _key = 0; _key < _len; _key++) { 29804 objects[_key] = arguments[_key]; 29805 } 29806 29807 return objects.reduce(function (result, source) { 29808 Object.keys(source).forEach(function (key) { 29809 if (Array.isArray(result[key]) && Array.isArray(source[key])) { 29810 result[key] = result[key].concat(source[key]); 29811 } else if (isObject$1(result[key]) && isObject$1(source[key])) { 29812 result[key] = merge(result[key], source[key]); 29813 } else { 29814 result[key] = source[key]; 29815 } 29816 }); 29817 return result; 29818 }, {}); 29819 }; 29820 29821 var values = function values(o) { 29822 return Object.keys(o).map(function (k) { 29823 return o[k]; 29824 }); 29825 }; 29826 29827 var range = function range(start, end) { 29828 var result = []; 29829 29830 for (var i = start; i < end; i++) { 29831 result.push(i); 29832 } 29833 29834 return result; 29835 }; 29836 29837 var flatten = function flatten(lists) { 29838 return lists.reduce(function (x, y) { 29839 return x.concat(y); 29840 }, []); 29841 }; 29842 29843 var from = function from(list) { 29844 if (!list.length) { 29845 return []; 29846 } 29847 29848 var result = []; 29849 29850 for (var i = 0; i < list.length; i++) { 29851 result.push(list[i]); 29852 } 29853 29854 return result; 29855 }; 29856 29857 var findIndexes = function findIndexes(l, key) { 29858 return l.reduce(function (a, e, i) { 29859 if (e[key]) { 29860 a.push(i); 29861 } 29862 29863 return a; 29864 }, []); 29865 }; 29866 29867 var mergeDiscontiguousPlaylists = function mergeDiscontiguousPlaylists(playlists) { 29868 var mergedPlaylists = values(playlists.reduce(function (acc, playlist) { 29869 // assuming playlist IDs are the same across periods 29870 // TODO: handle multiperiod where representation sets are not the same 29871 // across periods 29872 var name = playlist.attributes.id + (playlist.attributes.lang || ''); // Periods after first 29873 29874 if (acc[name]) { 29875 var _acc$name$segments; // first segment of subsequent periods signal a discontinuity 29876 29877 29878 playlist.segments[0].discontinuity = true; 29879 29880 (_acc$name$segments = acc[name].segments).push.apply(_acc$name$segments, playlist.segments); // bubble up contentProtection, this assumes all DRM content 29881 // has the same contentProtection 29882 29883 29884 if (playlist.attributes.contentProtection) { 29885 acc[name].attributes.contentProtection = playlist.attributes.contentProtection; 29886 } 29887 } else { 29888 // first Period 29889 acc[name] = playlist; 29890 } 29891 29892 return acc; 29893 }, {})); 29894 return mergedPlaylists.map(function (playlist) { 29895 playlist.discontinuityStarts = findIndexes(playlist.segments, 'discontinuity'); 29896 return playlist; 29897 }); 29898 }; 29899 29900 var formatAudioPlaylist = function formatAudioPlaylist(_ref) { 29901 var _attributes; 29902 29903 var attributes = _ref.attributes, 29904 segments = _ref.segments; 29905 var playlist = { 29906 attributes: (_attributes = { 29907 NAME: attributes.id, 29908 BANDWIDTH: attributes.bandwidth, 29909 CODECS: attributes.codecs 29910 }, _attributes['PROGRAM-ID'] = 1, _attributes), 29911 uri: '', 29912 endList: (attributes.type || 'static') === 'static', 29913 timeline: attributes.periodIndex, 29914 resolvedUri: '', 29915 targetDuration: attributes.duration, 29916 segments: segments, 29917 mediaSequence: segments.length ? segments[0].number : 1 29918 }; 29919 29920 if (attributes.contentProtection) { 29921 playlist.contentProtection = attributes.contentProtection; 29922 } 29923 29924 return playlist; 29925 }; 29926 29927 var formatVttPlaylist = function formatVttPlaylist(_ref2) { 29928 var _attributes2; 29929 29930 var attributes = _ref2.attributes, 29931 segments = _ref2.segments; 29932 29933 if (typeof segments === 'undefined') { 29934 // vtt tracks may use single file in BaseURL 29935 segments = [{ 29936 uri: attributes.baseUrl, 29937 timeline: attributes.periodIndex, 29938 resolvedUri: attributes.baseUrl || '', 29939 duration: attributes.sourceDuration, 29940 number: 0 29941 }]; // targetDuration should be the same duration as the only segment 29942 29943 attributes.duration = attributes.sourceDuration; 29944 } 29945 29946 return { 29947 attributes: (_attributes2 = { 29948 NAME: attributes.id, 29949 BANDWIDTH: attributes.bandwidth 29950 }, _attributes2['PROGRAM-ID'] = 1, _attributes2), 29951 uri: '', 29952 endList: (attributes.type || 'static') === 'static', 29953 timeline: attributes.periodIndex, 29954 resolvedUri: attributes.baseUrl || '', 29955 targetDuration: attributes.duration, 29956 segments: segments, 29957 mediaSequence: segments.length ? segments[0].number : 1 29958 }; 29959 }; 29960 29961 var organizeAudioPlaylists = function organizeAudioPlaylists(playlists) { 29962 return playlists.reduce(function (a, playlist) { 29963 var role = playlist.attributes.role && playlist.attributes.role.value || 'main'; 29964 var language = playlist.attributes.lang || ''; 29965 var label = 'main'; 29966 29967 if (language) { 29968 label = playlist.attributes.lang + " (" + role + ")"; 29969 } // skip if we already have the highest quality audio for a language 29970 29971 29972 if (a[label] && a[label].playlists[0].attributes.BANDWIDTH > playlist.attributes.bandwidth) { 29973 return a; 29974 } 29975 29976 a[label] = { 29977 language: language, 29978 autoselect: true, 29979 default: role === 'main', 29980 playlists: [formatAudioPlaylist(playlist)], 29981 uri: '' 29982 }; 29983 return a; 29984 }, {}); 29985 }; 29986 29987 var organizeVttPlaylists = function organizeVttPlaylists(playlists) { 29988 return playlists.reduce(function (a, playlist) { 29989 var label = playlist.attributes.lang || 'text'; // skip if we already have subtitles 29990 29991 if (a[label]) { 29992 return a; 29993 } 29994 29995 a[label] = { 29996 language: label, 29997 default: false, 29998 autoselect: false, 29999 playlists: [formatVttPlaylist(playlist)], 30000 uri: '' 30001 }; 30002 return a; 30003 }, {}); 30004 }; 30005 30006 var formatVideoPlaylist = function formatVideoPlaylist(_ref3) { 30007 var _attributes3; 30008 30009 var attributes = _ref3.attributes, 30010 segments = _ref3.segments; 30011 var playlist = { 30012 attributes: (_attributes3 = { 30013 NAME: attributes.id, 30014 AUDIO: 'audio', 30015 SUBTITLES: 'subs', 30016 RESOLUTION: { 30017 width: attributes.width, 30018 height: attributes.height 30019 }, 30020 CODECS: attributes.codecs, 30021 BANDWIDTH: attributes.bandwidth 30022 }, _attributes3['PROGRAM-ID'] = 1, _attributes3), 30023 uri: '', 30024 endList: (attributes.type || 'static') === 'static', 30025 timeline: attributes.periodIndex, 30026 resolvedUri: '', 30027 targetDuration: attributes.duration, 30028 segments: segments, 30029 mediaSequence: segments.length ? segments[0].number : 1 30030 }; 30031 30032 if (attributes.contentProtection) { 30033 playlist.contentProtection = attributes.contentProtection; 30034 } 30035 30036 return playlist; 30037 }; 30038 30039 var toM3u8 = function toM3u8(dashPlaylists) { 30040 var _mediaGroups; 30041 30042 if (!dashPlaylists.length) { 30043 return {}; 30044 } // grab all master attributes 30045 30046 30047 var _dashPlaylists$0$attr = dashPlaylists[0].attributes, 30048 duration = _dashPlaylists$0$attr.sourceDuration, 30049 _dashPlaylists$0$attr2 = _dashPlaylists$0$attr.minimumUpdatePeriod, 30050 minimumUpdatePeriod = _dashPlaylists$0$attr2 === void 0 ? 0 : _dashPlaylists$0$attr2; 30051 30052 var videoOnly = function videoOnly(_ref4) { 30053 var attributes = _ref4.attributes; 30054 return attributes.mimeType === 'video/mp4' || attributes.contentType === 'video'; 30055 }; 30056 30057 var audioOnly = function audioOnly(_ref5) { 30058 var attributes = _ref5.attributes; 30059 return attributes.mimeType === 'audio/mp4' || attributes.contentType === 'audio'; 30060 }; 30061 30062 var vttOnly = function vttOnly(_ref6) { 30063 var attributes = _ref6.attributes; 30064 return attributes.mimeType === 'text/vtt' || attributes.contentType === 'text'; 30065 }; 30066 30067 var videoPlaylists = mergeDiscontiguousPlaylists(dashPlaylists.filter(videoOnly)).map(formatVideoPlaylist); 30068 var audioPlaylists = mergeDiscontiguousPlaylists(dashPlaylists.filter(audioOnly)); 30069 var vttPlaylists = dashPlaylists.filter(vttOnly); 30070 var master = { 30071 allowCache: true, 30072 discontinuityStarts: [], 30073 segments: [], 30074 endList: true, 30075 mediaGroups: (_mediaGroups = { 30076 AUDIO: {}, 30077 VIDEO: {} 30078 }, _mediaGroups['CLOSED-CAPTIONS'] = {}, _mediaGroups.SUBTITLES = {}, _mediaGroups), 30079 uri: '', 30080 duration: duration, 30081 playlists: videoPlaylists, 30082 minimumUpdatePeriod: minimumUpdatePeriod * 1000 30083 }; 30084 30085 if (audioPlaylists.length) { 30086 master.mediaGroups.AUDIO.audio = organizeAudioPlaylists(audioPlaylists); 30087 } 30088 30089 if (vttPlaylists.length) { 30090 master.mediaGroups.SUBTITLES.subs = organizeVttPlaylists(vttPlaylists); 30091 } 30092 30093 return master; 30094 }; 30095 30096 var commonjsGlobal$1 = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}; 30097 30098 function createCommonjsModule$1(fn, module) { 30099 return module = { 30100 exports: {} 30101 }, fn(module, module.exports), module.exports; 30102 } 30103 30104 var urlToolkit$1 = createCommonjsModule$1(function (module, exports
vendor: 4,642 bytes, lines 30104-30211
30104) { 30105 // see https://tools.ietf.org/html/rfc1808 30106 30107 /* jshint ignore:start */ 30108 (function (root) { 30109 /* jshint ignore:end */ 30110 var URL_REGEX = /^((?:[a-zA-Z0-9+\-.]+:)?)(\/\/[^\/?#]*)?((?:[^\/\?#]*\/)*.*?)??(;.*?)?(\?.*?)?(#.*?)?$/; 30111 var FIRST_SEGMENT_REGEX = /^([^\/?#]*)(.*)$/; 30112 var SLASH_DOT_REGEX = /(?:\/|^)\.(?=\/)/g; 30113 var SLASH_DOT_DOT_REGEX = /(?:\/|^)\.\.\/(?!\.\.\/).*?(?=\/)/g; 30114 var URLToolkit = { 30115 // jshint ignore:line 30116 // If opts.alwaysNormalize is true then the path will always be normalized even when it starts with / or // 30117 // E.g 30118 // With opts.alwaysNormalize = false (default, spec compliant) 30119 // http://a.com/b/cd + /e/f/../g => http://a.com/e/f/../g 30120 // With opts.alwaysNormalize = true (not spec compliant) 30121 // http://a.com/b/cd + /e/f/../g => http://a.com/e/g 30122 buildAbsoluteURL: function buildAbsoluteURL(baseURL, relativeURL, opts) { 30123 opts = opts || {}; // remove any remaining space and CRLF 30124 30125 baseURL = baseURL.trim(); 30126 relativeURL = relativeURL.trim(); 30127 30128 if (!relativeURL) { 30129 // 2a) If the embedded URL is entirely empty, it inherits the 30130 // entire base URL (i.e., is set equal to the base URL) 30131 // and we are done. 30132 if (!opts.alwaysNormalize) { 30133 return baseURL; 30134 } 30135 30136 var basePartsForNormalise = URLToolkit.parseURL(baseURL); 30137 30138 if (!basePartsForNormalise) { 30139 throw new Error('Error trying to parse base URL.'); 30140 } 30141 30142 basePartsForNormalise.path = URLToolkit.normalizePath(basePartsForNormalise.path); 30143 return URLToolkit.buildURLFromParts(basePartsForNormalise); 30144 } 30145 30146 var relativeParts = URLToolkit.parseURL(relativeURL); 30147 30148 if (!relativeParts) { 30149 throw new Error('Error trying to parse relative URL.'); 30150 } 30151 30152 if (relativeParts.scheme) { 30153 // 2b) If the embedded URL starts with a scheme name, it is 30154 // interpreted as an absolute URL and we are done. 30155 if (!opts.alwaysNormalize) { 30156 return relativeURL; 30157 } 30158 30159 relativeParts.path = URLToolkit.normalizePath(relativeParts.path); 30160 return URLToolkit.buildURLFromParts(relativeParts); 30161 } 30162 30163 var baseParts = URLToolkit.parseURL(baseURL); 30164 30165 if (!baseParts) { 30166 throw new Error('Error trying to parse base URL.'); 30167 } 30168 30169 if (!baseParts.netLoc && baseParts.path && baseParts.path[0] !== '/') { 30170 // If netLoc missing and path doesn't start with '/', assume everthing before the first '/' is the netLoc 30171 // This causes 'example.com/a' to be handled as '//example.com/a' instead of '/example.com/a' 30172 var pathParts = FIRST_SEGMENT_REGEX.exec(baseParts.path); 30173 baseParts.netLoc = pathParts[1]; 30174 baseParts.path = pathParts[2]; 30175 } 30176 30177 if (baseParts.netLoc && !baseParts.path) { 30178 baseParts.path = '/'; 30179 } 30180 30181 var builtParts = { 30182 // 2c) Otherwise, the embedded URL inherits the scheme of 30183 // the base URL. 30184 scheme: baseParts.scheme, 30185 netLoc: relativeParts.netLoc, 30186 path: null, 30187 params: relativeParts.params, 30188 query: relativeParts.query, 30189 fragment: relativeParts.fragment 30190 }; 30191 30192 if (!relativeParts.netLoc) { 30193 // 3) If the embedded URL's <net_loc> is non-empty, we skip to 30194 // Step 7. Otherwise, the embedded URL inherits the <net_loc> 30195 // (if any) of the base URL. 30196 builtParts.netLoc = baseParts.netLoc; // 4) If the embedded URL path is preceded by a slash "/", the 30197 // path is not relative and we skip to Step 7. 30198 30199 if (relativeParts.path[0] !== '/') { 30200 if (!relativeParts.path) { 30201 // 5) If the embedded URL path is empty (and not preceded by a 30202 // slash), then the embedded URL inherits the base URL path 30203 builtParts.path = baseParts.path; // 5a) if the embedded URL's <params> is non-empty, we skip to 30204 // step 7; otherwise, it inherits the <params> of the base 30205 // URL (if any) and 30206 30207 if (!relativeParts.params) { 30208 builtParts.params = baseParts.params; // 5b) if the embedded URL's <query> is non-empty, we skip to 30209 // step 7; otherwise, it inherits the <query> of the base 30210 // URL (if any) and we skip to step 7. 30211
vendor: 21,464 bytes, lines 30212-30787
30212 if (!relativeParts.query) { 30213 builtParts.query = baseParts.query; 30214 } 30215 } 30216 } else { 30217 // 6) The last segment of the base URL's path (anything 30218 // following the rightmost slash "/", or the entire path if no 30219 // slash is present) is removed and the embedded URL's path is 30220 // appended in its place. 30221 var baseURLPath = baseParts.path; 30222 var newPath = baseURLPath.substring(0, baseURLPath.lastIndexOf('/') + 1) + relativeParts.path; 30223 builtParts.path = URLToolkit.normalizePath(newPath); 30224 } 30225 } 30226 } 30227 30228 if (builtParts.path === null) { 30229 builtParts.path = opts.alwaysNormalize ? URLToolkit.normalizePath(relativeParts.path) : relativeParts.path; 30230 } 30231 30232 return URLToolkit.buildURLFromParts(builtParts); 30233 }, 30234 parseURL: function parseURL(url) { 30235 var parts = URL_REGEX.exec(url); 30236 30237 if (!parts) { 30238 return null; 30239 } 30240 30241 return { 30242 scheme: parts[1] || '', 30243 netLoc: parts[2] || '', 30244 path: parts[3] || '', 30245 params: parts[4] || '', 30246 query: parts[5] || '', 30247 fragment: parts[6] || '' 30248 }; 30249 }, 30250 normalizePath: function normalizePath(path) { 30251 // The following operations are 30252 // then applied, in order, to the new path: 30253 // 6a) All occurrences of "./", where "." is a complete path 30254 // segment, are removed. 30255 // 6b) If the path ends with "." as a complete path segment, 30256 // that "." is removed. 30257 path = path.split('').reverse().join('').replace(SLASH_DOT_REGEX, ''); // 6c) All occurrences of "<segment>/../", where <segment> is a 30258 // complete path segment not equal to "..", are removed. 30259 // Removal of these path segments is performed iteratively, 30260 // removing the leftmost matching pattern on each iteration, 30261 // until no matching pattern remains. 30262 // 6d) If the path ends with "<segment>/..", where <segment> is a 30263 // complete path segment not equal to "..", that 30264 // "<segment>/.." is removed. 30265 30266 while (path.length !== (path = path.replace(SLASH_DOT_DOT_REGEX, '')).length) {} // jshint ignore:line 30267 30268 30269 return path.split('').reverse().join(''); 30270 }, 30271 buildURLFromParts: function buildURLFromParts(parts) { 30272 return parts.scheme + parts.netLoc + parts.path + parts.params + parts.query + parts.fragment; 30273 } 30274 }; 30275 /* jshint ignore:start */ 30276 30277 module.exports = URLToolkit; 30278 })(commonjsGlobal$1); 30279 /* jshint ignore:end */ 30280 30281 }); 30282 30283 var resolveUrl = function resolveUrl(baseUrl, relativeUrl) { 30284 // return early if we don't need to resolve 30285 if (/^[a-z]+:/i.test(relativeUrl)) { 30286 return relativeUrl; 30287 } // if the base URL is relative then combine with the current location 30288 30289 30290 if (!/\/\//i.test(baseUrl)) { 30291 baseUrl = urlToolkit$1.buildAbsoluteURL(window$1.location.href, baseUrl); 30292 } 30293 30294 return urlToolkit$1.buildAbsoluteURL(baseUrl, relativeUrl); 30295 }; 30296 /** 30297 * @typedef {Object} SingleUri 30298 * @property {string} uri - relative location of segment 30299 * @property {string} resolvedUri - resolved location of segment 30300 * @property {Object} byterange - Object containing information on how to make byte range 30301 * requests following byte-range-spec per RFC2616. 30302 * @property {String} byterange.length - length of range request 30303 * @property {String} byterange.offset - byte offset of range request 30304 * 30305 * @see https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35.1 30306 */ 30307 30308 /** 30309 * Converts a URLType node (5.3.9.2.3 Table 13) to a segment object 30310 * that conforms to how m3u8-parser is structured 30311 * 30312 * @see https://github.com/videojs/m3u8-parser 30313 * 30314 * @param {string} baseUrl - baseUrl provided by <BaseUrl> nodes 30315 * @param {string} source - source url for segment 30316 * @param {string} range - optional range used for range calls, follows 30317 * @return {SingleUri} full segment information transformed into a format similar 30318 * to m3u8-parser 30319 */ 30320 30321 30322 var urlTypeToSegment = function urlTypeToSegment(_ref) { 30323 var _ref$baseUrl = _ref.baseUrl, 30324 baseUrl = _ref$baseUrl === void 0 ? '' : _ref$baseUrl, 30325 _ref$source = _ref.source, 30326 source = _ref$source === void 0 ? '' : _ref$source, 30327 _ref$range = _ref.range, 30328 range = _ref$range === void 0 ? '' : _ref$range; 30329 var init = { 30330 uri: source, 30331 resolvedUri: resolveUrl(baseUrl || '', source) 30332 }; 30333 30334 if (range) { 30335 var ranges = range.split('-'); 30336 var startRange = parseInt(ranges[0], 10); 30337 var endRange = parseInt(ranges[1], 10); 30338 init.byterange = { 30339 length: endRange - startRange, 30340 offset: startRange 30341 }; 30342 } 30343 30344 return init; 30345 }; 30346 /** 30347 * Calculates the R (repetition) value for a live stream (for the final segment 30348 * in a manifest where the r value is negative 1) 30349 * 30350 * @param {Object} attributes 30351 * Object containing all inherited attributes from parent elements with attribute 30352 * names as keys 30353 * @param {number} time 30354 * current time (typically the total time up until the final segment) 30355 * @param {number} duration 30356 * duration property for the given <S /> 30357 * 30358 * @return {number} 30359 * R value to reach the end of the given period 30360 */ 30361 30362 30363 var getLiveRValue = function getLiveRValue(attributes, time, duration) { 30364 var NOW = attributes.NOW, 30365 clientOffset = attributes.clientOffset, 30366 availabilityStartTime = attributes.availabilityStartTime, 30367 _attributes$timescale = attributes.timescale, 30368 timescale = _attributes$timescale === void 0 ? 1 : _attributes$timescale, 30369 _attributes$start = attributes.start, 30370 start = _attributes$start === void 0 ? 0 : _attributes$start, 30371 _attributes$minimumUp = attributes.minimumUpdatePeriod, 30372 minimumUpdatePeriod = _attributes$minimumUp === void 0 ? 0 : _attributes$minimumUp; 30373 var now = (NOW + clientOffset) / 1000; 30374 var periodStartWC = availabilityStartTime + start; 30375 var periodEndWC = now + minimumUpdatePeriod; 30376 var periodDuration = periodEndWC - periodStartWC; 30377 return Math.ceil((periodDuration * timescale - time) / duration); 30378 }; 30379 /** 30380 * Uses information provided by SegmentTemplate.SegmentTimeline to determine segment 30381 * timing and duration 30382 * 30383 * @param {Object} attributes 30384 * Object containing all inherited attributes from parent elements with attribute 30385 * names as keys 30386 * @param {Object[]} segmentTimeline 30387 * List of objects representing the attributes of each S element contained within 30388 * 30389 * @return {{number: number, duration: number, time: number, timeline: number}[]} 30390 * List of Objects with segment timing and duration info 30391 */ 30392 30393 30394 var parseByTimeline = function parseByTimeline(attributes, segmentTimeline) { 30395 var _attributes$type = attributes.type, 30396 type = _attributes$type === void 0 ? 'static' : _attributes$type, 30397 _attributes$minimumUp2 = attributes.minimumUpdatePeriod, 30398 minimumUpdatePeriod = _attributes$minimumUp2 === void 0 ? 0 : _attributes$minimumUp2, 30399 _attributes$media = attributes.media, 30400 media = _attributes$media === void 0 ? '' : _attributes$media, 30401 sourceDuration = attributes.sourceDuration, 30402 _attributes$timescale2 = attributes.timescale, 30403 timescale = _attributes$timescale2 === void 0 ? 1 : _attributes$timescale2, 30404 _attributes$startNumb = attributes.startNumber, 30405 startNumber = _attributes$startNumb === void 0 ? 1 : _attributes$startNumb, 30406 timeline = attributes.periodIndex; 30407 var segments = []; 30408 var time = -1; 30409 30410 for (var sIndex = 0; sIndex < segmentTimeline.length; sIndex++) { 30411 var S = segmentTimeline[sIndex]; 30412 var duration = S.d; 30413 var repeat = S.r || 0; 30414 var segmentTime = S.t || 0; 30415 30416 if (time < 0) { 30417 // first segment 30418 time = segmentTime; 30419 } 30420 30421 if (segmentTime && segmentTime > time) { 30422 // discontinuity 30423 // TODO: How to handle this type of discontinuity 30424 // timeline++ here would treat it like HLS discontuity and content would 30425 // get appended without gap 30426 // E.G. 30427 // <S t="0" d="1" /> 30428 // <S d="1" /> 30429 // <S d="1" /> 30430 // <S t="5" d="1" /> 30431 // would have $Time$ values of [0, 1, 2, 5] 30432 // should this be appened at time positions [0, 1, 2, 3],(#EXT-X-DISCONTINUITY) 30433 // or [0, 1, 2, gap, gap, 5]? (#EXT-X-GAP) 30434 // does the value of sourceDuration consider this when calculating arbitrary 30435 // negative @r repeat value? 30436 // E.G. Same elements as above with this added at the end 30437 // <S d="1" r="-1" /> 30438 // with a sourceDuration of 10 30439 // Would the 2 gaps be included in the time duration calculations resulting in 30440 // 8 segments with $Time$ values of [0, 1, 2, 5, 6, 7, 8, 9] or 10 segments 30441 // with $Time$ values of [0, 1, 2, 5, 6, 7, 8, 9, 10, 11] ? 30442 time = segmentTime; 30443 } 30444 30445 var count = void 0; 30446 30447 if (repeat < 0) { 30448 var nextS = sIndex + 1; 30449 30450 if (nextS === segmentTimeline.length) { 30451 // last segment 30452 if (type === 'dynamic' && minimumUpdatePeriod > 0 && media.indexOf('$Number$') > 0) { 30453 count = getLiveRValue(attributes, time, duration); 30454 } else { 30455 // TODO: This may be incorrect depending on conclusion of TODO above 30456 count = (sourceDuration * timescale - time) / duration; 30457 } 30458 } else { 30459 count = (segmentTimeline[nextS].t - time) / duration; 30460 } 30461 } else { 30462 count = repeat + 1; 30463 } 30464 30465 var end = startNumber + segments.length + count; 30466 var number = startNumber + segments.length; 30467 30468 while (number < end) { 30469 segments.push({ 30470 number: number, 30471 duration: duration / timescale, 30472 time: time, 30473 timeline: timeline 30474 }); 30475 time += duration; 30476 number++; 30477 } 30478 } 30479 30480 return segments; 30481 }; 30482 /** 30483 * Functions for calculating the range of available segments in static and dynamic 30484 * manifests. 30485 */ 30486 30487 30488 var segmentRange = { 30489 /** 30490 * Returns the entire range of available segments for a static MPD 30491 * 30492 * @param {Object} attributes 30493 * Inheritied MPD attributes 30494 * @return {{ start: number, end: number }} 30495 * The start and end numbers for available segments 30496 */ 30497 static: function _static(attributes) { 30498 var duration = attributes.duration, 30499 _attributes$timescale = attributes.timescale, 30500 timescale = _attributes$timescale === void 0 ? 1 : _attributes$timescale, 30501 sourceDuration = attributes.sourceDuration; 30502 return { 30503 start: 0, 30504 end: Math.ceil(sourceDuration / (duration / timescale)) 30505 }; 30506 }, 30507 30508 /** 30509 * Returns the current live window range of available segments for a dynamic MPD 30510 * 30511 * @param {Object} attributes 30512 * Inheritied MPD attributes 30513 * @return {{ start: number, end: number }} 30514 * The start and end numbers for available segments 30515 */ 30516 dynamic: function dynamic(attributes) { 30517 var NOW = attributes.NOW, 30518 clientOffset = attributes.clientOffset, 30519 availabilityStartTime = attributes.availabilityStartTime, 30520 _attributes$timescale2 = attributes.timescale, 30521 timescale = _attributes$timescale2 === void 0 ? 1 : _attributes$timescale2, 30522 duration = attributes.duration, 30523 _attributes$start = attributes.start, 30524 start = _attributes$start === void 0 ? 0 : _attributes$start, 30525 _attributes$minimumUp = attributes.minimumUpdatePeriod, 30526 minimumUpdatePeriod = _attributes$minimumUp === void 0 ? 0 : _attributes$minimumUp, 30527 _attributes$timeShift = attributes.timeShiftBufferDepth, 30528 timeShiftBufferDepth = _attributes$timeShift === void 0 ? Infinity : _attributes$timeShift; 30529 var now = (NOW + clientOffset) / 1000; 30530 var periodStartWC = availabilityStartTime + start; 30531 var periodEndWC = now + minimumUpdatePeriod; 30532 var periodDuration = periodEndWC - periodStartWC; 30533 var segmentCount = Math.ceil(periodDuration * timescale / duration); 30534 var availableStart = Math.floor((now - periodStartWC - timeShiftBufferDepth) * timescale / duration); 30535 var availableEnd = Math.floor((now - periodStartWC) * timescale / duration); 30536 return { 30537 start: Math.max(0, availableStart), 30538 end: Math.min(segmentCount, availableEnd) 30539 }; 30540 } 30541 }; 30542 /** 30543 * Maps a range of numbers to objects with information needed to build the corresponding 30544 * segment list 30545 * 30546 * @name toSegmentsCallback 30547 * @function 30548 * @param {number} number 30549 * Number of the segment 30550 * @param {number} index 30551 * Index of the number in the range list 30552 * @return {{ number: Number, duration: Number, timeline: Number, time: Number }} 30553 * Object with segment timing and duration info 30554 */ 30555 30556 /** 30557 * Returns a callback for Array.prototype.map for mapping a range of numbers to 30558 * information needed to build the segment list. 30559 * 30560 * @param {Object} attributes 30561 * Inherited MPD attributes 30562 * @return {toSegmentsCallback} 30563 * Callback map function 30564 */ 30565 30566 var toSegments = function toSegments(attributes) { 30567 return function (number, index) { 30568 var duration = attributes.duration, 30569 _attributes$timescale3 = attributes.timescale, 30570 timescale = _attributes$timescale3 === void 0 ? 1 : _attributes$timescale3, 30571 periodIndex = attributes.periodIndex, 30572 _attributes$startNumb = attributes.startNumber, 30573 startNumber = _attributes$startNumb === void 0 ? 1 : _attributes$startNumb; 30574 return { 30575 number: startNumber + number, 30576 duration: duration / timescale, 30577 timeline: periodIndex, 30578 time: index * duration 30579 }; 30580 }; 30581 }; 30582 /** 30583 * Returns a list of objects containing segment timing and duration info used for 30584 * building the list of segments. This uses the @duration attribute specified 30585 * in the MPD manifest to derive the range of segments. 30586 * 30587 * @param {Object} attributes 30588 * Inherited MPD attributes 30589 * @return {{number: number, duration: number, time: number, timeline: number}[]} 30590 * List of Objects with segment timing and duration info 30591 */ 30592 30593 30594 var parseByDuration = function parseByDuration(attributes) { 30595 var _attributes$type = attributes.type, 30596 type = _attributes$type === void 0 ? 'static' : _attributes$type, 30597 duration = attributes.duration, 30598 _attributes$timescale4 = attributes.timescale, 30599 timescale = _attributes$timescale4 === void 0 ? 1 : _attributes$timescale4, 30600 sourceDuration = attributes.sourceDuration; 30601 30602 var _segmentRange$type = segmentRange[type](attributes), 30603 start = _segmentRange$type.start, 30604 end = _segmentRange$type.end; 30605 30606 var segments = range(start, end).map(toSegments(attributes)); 30607 30608 if (type === 'static') { 30609 var index = segments.length - 1; // final segment may be less than full segment duration 30610 30611 segments[index].duration = sourceDuration - duration / timescale * index; 30612 } 30613 30614 return segments; 30615 }; 30616 30617 var identifierPattern = /\$([A-z]*)(?:(%0)([0-9]+)d)?\$/g; 30618 /** 30619 * Replaces template identifiers with corresponding values. To be used as the callback 30620 * for String.prototype.replace 30621 * 30622 * @name replaceCallback 30623 * @function 30624 * @param {string} match 30625 * Entire match of identifier 30626 * @param {string} identifier 30627 * Name of matched identifier 30628 * @param {string} format 30629 * Format tag string. Its presence indicates that padding is expected 30630 * @param {string} width 30631 * Desired length of the replaced value. Values less than this width shall be left 30632 * zero padded 30633 * @return {string} 30634 * Replacement for the matched identifier 30635 */ 30636 30637 /** 30638 * Returns a function to be used as a callback for String.prototype.replace to replace 30639 * template identifiers 30640 * 30641 * @param {Obect} values 30642 * Object containing values that shall be used to replace known identifiers 30643 * @param {number} values.RepresentationID 30644 * Value of the Representation@id attribute 30645 * @param {number} values.Number 30646 * Number of the corresponding segment 30647 * @param {number} values.Bandwidth 30648 * Value of the Representation@bandwidth attribute. 30649 * @param {number} values.Time 30650 * Timestamp value of the corresponding segment 30651 * @return {replaceCallback} 30652 * Callback to be used with String.prototype.replace to replace identifiers 30653 */ 30654 30655 var identifierReplacement = function identifierReplacement(values) { 30656 return function (match, identifier, format, width) { 30657 if (match === '$$') { 30658 // escape sequence 30659 return '$'; 30660 } 30661 30662 if (typeof values[identifier] === 'undefined') { 30663 return match; 30664 } 30665 30666 var value = '' + values[identifier]; 30667 30668 if (identifier === 'RepresentationID') { 30669 // Format tag shall not be present with RepresentationID 30670 return value; 30671 } 30672 30673 if (!format) { 30674 width = 1; 30675 } else { 30676 width = parseInt(width, 10); 30677 } 30678 30679 if (value.length >= width) { 30680 return value; 30681 } 30682 30683 return "" + new Array(width - value.length + 1).join('0') + value; 30684 }; 30685 }; 30686 /** 30687 * Constructs a segment url from a template string 30688 * 30689 * @param {string} url 30690 * Template string to construct url from 30691 * @param {Obect} values 30692 * Object containing values that shall be used to replace known identifiers 30693 * @param {number} values.RepresentationID 30694 * Value of the Representation@id attribute 30695 * @param {number} values.Number 30696 * Number of the corresponding segment 30697 * @param {number} values.Bandwidth 30698 * Value of the Representation@bandwidth attribute. 30699 * @param {number} values.Time 30700 * Timestamp value of the corresponding segment 30701 * @return {string} 30702 * Segment url with identifiers replaced 30703 */ 30704 30705 30706 var constructTemplateUrl = function constructTemplateUrl(url, values) { 30707 return url.replace(identifierPattern, identifierReplacement(values)); 30708 }; 30709 /** 30710 * Generates a list of objects containing timing and duration information about each 30711 * segment needed to generate segment uris and the complete segment object 30712 * 30713 * @param {Object} attributes 30714 * Object containing all inherited attributes from parent elements with attribute 30715 * names as keys 30716 * @param {Object[]|undefined} segmentTimeline 30717 * List of objects representing the attributes of each S element contained within 30718 * the SegmentTimeline element 30719 * @return {{number: number, duration: number, time: number, timeline: number}[]} 30720 * List of Objects with segment timing and duration info 30721 */ 30722 30723 30724 var parseTemplateInfo = function parseTemplateInfo(attributes, segmentTimeline) { 30725 if (!attributes.duration && !segmentTimeline) { 30726 // if neither @duration or SegmentTimeline are present, then there shall be exactly 30727 // one media segment 30728 return [{ 30729 number: attributes.startNumber || 1, 30730 duration: attributes.sourceDuration, 30731 time: 0, 30732 timeline: attributes.periodIndex 30733 }]; 30734 } 30735 30736 if (attributes.duration) { 30737 return parseByDuration(attributes); 30738 } 30739 30740 return parseByTimeline(attributes, segmentTimeline); 30741 }; 30742 /** 30743 * Generates a list of segments using information provided by the SegmentTemplate element 30744 * 30745 * @param {Object} attributes 30746 * Object containing all inherited attributes from parent elements with attribute 30747 * names as keys 30748 * @param {Object[]|undefined} segmentTimeline 30749 * List of objects representing the attributes of each S element contained within 30750 * the SegmentTimeline element 30751 * @return {Object[]} 30752 * List of segment objects 30753 */ 30754 30755 30756 var segmentsFromTemplate = function segmentsFromTemplate(attributes, segmentTimeline) { 30757 var templateValues = { 30758 RepresentationID: attributes.id, 30759 Bandwidth: attributes.bandwidth || 0 30760 }; 30761 var _attributes$initializ = attributes.initialization, 30762 initialization = _attributes$initializ === void 0 ? { 30763 sourceURL: '', 30764 range: '' 30765 } : _attributes$initializ; 30766 var mapSegment = urlTypeToSegment({ 30767 baseUrl: attributes.baseUrl, 30768 source: constructTemplateUrl(initialization.sourceURL, templateValues), 30769 range: initialization.range 30770 }); 30771 var segments = parseTemplateInfo(attributes, segmentTimeline); 30772 return segments.map(function (segment) { 30773 templateValues.Number = segment.number; 30774 templateValues.Time = segment.time; 30775 var uri = constructTemplateUrl(attributes.media || '', templateValues); 30776 return { 30777 uri: uri, 30778 timeline: segment.timeline, 30779 duration: segment.duration, 30780 resolvedUri: resolveUrl(attributes.baseUrl || '', uri), 30781 map: mapSegment, 30782 number: segment.number 30783 }; 30784 }); 30785 }; 30786 30787 var errors = {
30788 INVALID_NUMBER_OF_PERIOD: 'INVALID_NUMBER_OF_PERIOD', 30789 DASH_EMPTY_MANIFEST: 'DASH_EMPTY_MANIFEST', 30790 DASH_INVALID_XML: 'DASH_INVALID_XML', 30791 NO_BASE_URL: 'NO_BASE_URL', 30792 MISSING_SEGMENT_INFORMATION: 'MISSING_SEGMENT_INFORMATION', 30793 SEGMENT_TIME_UNSPECIFIED: 'SEGMENT_TIME_UNSPECIFIED', 30794 UNSUPPORTED_UTC_TIMING_SCHEME: 'UNSUPPORTED_UTC_TIMING_SCHEME' 30795 }; 30796 /** 30797 * Converts a <SegmentUrl> (of type URLType from the DASH spec 5.3.9.2 Table 14) 30798 * to an object that matches the output of a segment in videojs/mpd-parser 30799 * 30800 * @param {Object} attributes 30801 * Object containing all inherited attributes from parent elements with attribute 30802 * names as keys 30803 * @param {Object} segmentUrl 30804 * <SegmentURL> node to translate into a segment object 30805 * @return {Object} translated segment object 30806 */ 30807 30808 var SegmentURLToSegmentObject = function SegmentURLToSegmentObject(attributes, segmentUrl) { 30809 var baseUrl = attributes.baseUrl, 30810 _attributes$initializ = attributes.initialization, 30811 initialization = _attributes$initializ === void 0 ? {} : _attributes$initializ; 30812 var initSegment = urlTypeToSegment({ 30813 baseUrl: baseUrl, 30814 source: initialization.sourceURL, 30815 range: initialization.range 30816 }); 30817 var segment = urlTypeToSegment({ 30818 baseUrl: baseUrl, 30819 source: segmentUrl.media, 30820 range: segmentUrl.mediaRange 30821 }); 30822 segment.map = initSegment; 30823 return segment; 30824 }; 30825 /** 30826 * Generates a list of segments using information provided by the SegmentList element 30827 * SegmentList (DASH SPEC Section 5.3.9.3.2) contains a set of <SegmentURL> nodes. Each 30828 * node should be translated into segment. 30829 * 30830 * @param {Object} attributes 30831 * Object containing all inherited attributes from parent elements with attribute 30832 * names as keys 30833 * @param {Object[]|undefined} segmentTimeline 30834 * List of objects representing the attributes of each S element contained within 30835 * the SegmentTimeline element 30836 * @return {Object.<Array>} list of segments 30837 */ 30838 30839
vendor: 18,546 bytes, lines 30840-31385
30840 var segmentsFromList = function segmentsFromList(attributes, segmentTimeline) { 30841 var duration = attributes.duration, 30842 _attributes$segmentUr = attributes.segmentUrls, 30843 segmentUrls = _attributes$segmentUr === void 0 ? [] : _attributes$segmentUr; // Per spec (5.3.9.2.1) no way to determine segment duration OR 30844 // if both SegmentTimeline and @duration are defined, it is outside of spec. 30845 30846 if (!duration && !segmentTimeline || duration && segmentTimeline) { 30847 throw new Error(errors.SEGMENT_TIME_UNSPECIFIED); 30848 } 30849 30850 var segmentUrlMap = segmentUrls.map(function (segmentUrlObject) { 30851 return SegmentURLToSegmentObject(attributes, segmentUrlObject); 30852 }); 30853 var segmentTimeInfo; 30854 30855 if (duration) { 30856 segmentTimeInfo = parseByDuration(attributes); 30857 } 30858 30859 if (segmentTimeline) { 30860 segmentTimeInfo = parseByTimeline(attributes, segmentTimeline); 30861 } 30862 30863 var segments = segmentTimeInfo.map(function (segmentTime, index) { 30864 if (segmentUrlMap[index]) { 30865 var segment = segmentUrlMap[index]; 30866 segment.timeline = segmentTime.timeline; 30867 segment.duration = segmentTime.duration; 30868 segment.number = segmentTime.number; 30869 return segment; 30870 } // Since we're mapping we should get rid of any blank segments (in case 30871 // the given SegmentTimeline is handling for more elements than we have 30872 // SegmentURLs for). 30873 30874 }).filter(function (segment) { 30875 return segment; 30876 }); 30877 return segments; 30878 }; 30879 /** 30880 * Translates SegmentBase into a set of segments. 30881 * (DASH SPEC Section 5.3.9.3.2) contains a set of <SegmentURL> nodes. Each 30882 * node should be translated into segment. 30883 * 30884 * @param {Object} attributes 30885 * Object containing all inherited attributes from parent elements with attribute 30886 * names as keys 30887 * @return {Object.<Array>} list of segments 30888 */ 30889 30890 30891 var segmentsFromBase = function segmentsFromBase(attributes) { 30892 var baseUrl = attributes.baseUrl, 30893 _attributes$initializ = attributes.initialization, 30894 initialization = _attributes$initializ === void 0 ? {} : _attributes$initializ, 30895 sourceDuration = attributes.sourceDuration, 30896 _attributes$timescale = attributes.timescale, 30897 timescale = _attributes$timescale === void 0 ? 1 : _attributes$timescale, 30898 _attributes$indexRang = attributes.indexRange, 30899 indexRange = _attributes$indexRang === void 0 ? '' : _attributes$indexRang, 30900 duration = attributes.duration; // base url is required for SegmentBase to work, per spec (Section 5.3.9.2.1) 30901 30902 if (!baseUrl) { 30903 throw new Error(errors.NO_BASE_URL); 30904 } 30905 30906 var initSegment = urlTypeToSegment({ 30907 baseUrl: baseUrl, 30908 source: initialization.sourceURL, 30909 range: initialization.range 30910 }); 30911 var segment = urlTypeToSegment({ 30912 baseUrl: baseUrl, 30913 source: baseUrl, 30914 range: indexRange 30915 }); 30916 segment.map = initSegment; // If there is a duration, use it, otherwise use the given duration of the source 30917 // (since SegmentBase is only for one total segment) 30918 30919 if (duration) { 30920 var segmentTimeInfo = parseByDuration(attributes); 30921 30922 if (segmentTimeInfo.length) { 30923 segment.duration = segmentTimeInfo[0].duration; 30924 segment.timeline = segmentTimeInfo[0].timeline; 30925 } 30926 } else if (sourceDuration) { 30927 segment.duration = sourceDuration / timescale; 30928 segment.timeline = 0; 30929 } // This is used for mediaSequence 30930 30931 30932 segment.number = 0; 30933 return [segment]; 30934 }; 30935 30936 var generateSegments = function generateSegments(_ref) { 30937 var attributes = _ref.attributes, 30938 segmentInfo = _ref.segmentInfo; 30939 var segmentAttributes; 30940 var segmentsFn; 30941 30942 if (segmentInfo.template) { 30943 segmentsFn = segmentsFromTemplate; 30944 segmentAttributes = merge(attributes, segmentInfo.template); 30945 } else if (segmentInfo.base) { 30946 segmentsFn = segmentsFromBase; 30947 segmentAttributes = merge(attributes, segmentInfo.base); 30948 } else if (segmentInfo.list) { 30949 segmentsFn = segmentsFromList; 30950 segmentAttributes = merge(attributes, segmentInfo.list); 30951 } 30952 30953 if (!segmentsFn) { 30954 return { 30955 attributes: attributes 30956 }; 30957 } 30958 30959 var segments = segmentsFn(segmentAttributes, segmentInfo.timeline); // The @duration attribute will be used to determin the playlist's targetDuration which 30960 // must be in seconds. Since we've generated the segment list, we no longer need 30961 // @duration to be in @timescale units, so we can convert it here. 30962 30963 if (segmentAttributes.duration) { 30964 var _segmentAttributes = segmentAttributes, 30965 duration = _segmentAttributes.duration, 30966 _segmentAttributes$ti = _segmentAttributes.timescale, 30967 timescale = _segmentAttributes$ti === void 0 ? 1 : _segmentAttributes$ti; 30968 segmentAttributes.duration = duration / timescale; 30969 } else if (segments.length) { 30970 // if there is no @duration attribute, use the largest segment duration as 30971 // as target duration 30972 segmentAttributes.duration = segments.reduce(function (max, segment) { 30973 return Math.max(max, Math.ceil(segment.duration)); 30974 }, 0); 30975 } else { 30976 segmentAttributes.duration = 0; 30977 } 30978 30979 return { 30980 attributes: segmentAttributes, 30981 segments: segments 30982 }; 30983 }; 30984 30985 var toPlaylists = function toPlaylists(representations) { 30986 return representations.map(generateSegments); 30987 }; 30988 30989 var findChildren = function findChildren(element, name) { 30990 return from(element.childNodes).filter(function (_ref) { 30991 var tagName = _ref.tagName; 30992 return tagName === name; 30993 }); 30994 }; 30995 30996 var getContent = function getContent(element) { 30997 return element.textContent.trim(); 30998 }; 30999 31000 var parseDuration = function parseDuration(str) { 31001 var SECONDS_IN_YEAR = 365 * 24 * 60 * 60; 31002 var SECONDS_IN_MONTH = 30 * 24 * 60 * 60; 31003 var SECONDS_IN_DAY = 24 * 60 * 60; 31004 var SECONDS_IN_HOUR = 60 * 60; 31005 var SECONDS_IN_MIN = 60; // P10Y10M10DT10H10M10.1S 31006 31007 var durationRegex = /P(?:(\d*)Y)?(?:(\d*)M)?(?:(\d*)D)?(?:T(?:(\d*)H)?(?:(\d*)M)?(?:([\d.]*)S)?)?/; 31008 var match = durationRegex.exec(str); 31009 31010 if (!match) { 31011 return 0; 31012 } 31013 31014 var _match$slice = match.slice(1), 31015 year = _match$slice[0], 31016 month = _match$slice[1], 31017 day = _match$slice[2], 31018 hour = _match$slice[3], 31019 minute = _match$slice[4], 31020 second = _match$slice[5]; 31021 31022 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); 31023 }; 31024 31025 var parseDate = function parseDate(str) { 31026 // Date format without timezone according to ISO 8601 31027 // YYY-MM-DDThh:mm:ss.ssssss 31028 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 31029 // expressed by ending with 'Z' 31030 31031 if (dateRegex.test(str)) { 31032 str += 'Z'; 31033 } 31034 31035 return Date.parse(str); 31036 }; 31037 31038 var parsers = { 31039 /** 31040 * Specifies the duration of the entire Media Presentation. Format is a duration string 31041 * as specified in ISO 8601 31042 * 31043 * @param {string} value 31044 * value of attribute as a string 31045 * @return {number} 31046 * The duration in seconds 31047 */ 31048 mediaPresentationDuration: function mediaPresentationDuration(value) { 31049 return parseDuration(value); 31050 }, 31051 31052 /** 31053 * Specifies the Segment availability start time for all Segments referred to in this 31054 * MPD. For a dynamic manifest, it specifies the anchor for the earliest availability 31055 * time. Format is a date string as specified in ISO 8601 31056 * 31057 * @param {string} value 31058 * value of attribute as a string 31059 * @return {number} 31060 * The date as seconds from unix epoch 31061 */ 31062 availabilityStartTime: function availabilityStartTime(value) { 31063 return parseDate(value) / 1000; 31064 }, 31065 31066 /** 31067 * Specifies the smallest period between potential changes to the MPD. Format is a 31068 * duration string as specified in ISO 8601 31069 * 31070 * @param {string} value 31071 * value of attribute as a string 31072 * @return {number} 31073 * The duration in seconds 31074 */ 31075 minimumUpdatePeriod: function minimumUpdatePeriod(value) { 31076 return parseDuration(value); 31077 }, 31078 31079 /** 31080 * Specifies the duration of the smallest time shifting buffer for any Representation 31081 * in the MPD. Format is a duration string as specified in ISO 8601 31082 * 31083 * @param {string} value 31084 * value of attribute as a string 31085 * @return {number} 31086 * The duration in seconds 31087 */ 31088 timeShiftBufferDepth: function timeShiftBufferDepth(value) { 31089 return parseDuration(value); 31090 }, 31091 31092 /** 31093 * Specifies the PeriodStart time of the Period relative to the availabilityStarttime. 31094 * Format is a duration string as specified in ISO 8601 31095 * 31096 * @param {string} value 31097 * value of attribute as a string 31098 * @return {number} 31099 * The duration in seconds 31100 */ 31101 start: function start(value) { 31102 return parseDuration(value); 31103 }, 31104 31105 /** 31106 * Specifies the width of the visual presentation 31107 * 31108 * @param {string} value 31109 * value of attribute as a string 31110 * @return {number} 31111 * The parsed width 31112 */ 31113 width: function width(value) { 31114 return parseInt(value, 10); 31115 }, 31116 31117 /** 31118 * Specifies the height of the visual presentation 31119 * 31120 * @param {string} value 31121 * value of attribute as a string 31122 * @return {number} 31123 * The parsed height 31124 */ 31125 height: function height(value) { 31126 return parseInt(value, 10); 31127 }, 31128 31129 /** 31130 * Specifies the bitrate of the representation 31131 * 31132 * @param {string} value 31133 * value of attribute as a string 31134 * @return {number} 31135 * The parsed bandwidth 31136 */ 31137 bandwidth: function bandwidth(value) { 31138 return parseInt(value, 10); 31139 }, 31140 31141 /** 31142 * Specifies the number of the first Media Segment in this Representation in the Period 31143 * 31144 * @param {string} value 31145 * value of attribute as a string 31146 * @return {number} 31147 * The parsed number 31148 */ 31149 startNumber: function startNumber(value) { 31150 return parseInt(value, 10); 31151 }, 31152 31153 /** 31154 * Specifies the timescale in units per seconds 31155 * 31156 * @param {string} value 31157 * value of attribute as a string 31158 * @return {number} 31159 * The aprsed timescale 31160 */ 31161 timescale: function timescale(value) { 31162 return parseInt(value, 10); 31163 }, 31164 31165 /** 31166 * Specifies the constant approximate Segment duration 31167 * NOTE: The <Period> element also contains an @duration attribute. This duration 31168 * specifies the duration of the Period. This attribute is currently not 31169 * supported by the rest of the parser, however we still check for it to prevent 31170 * errors. 31171 * 31172 * @param {string} value 31173 * value of attribute as a string 31174 * @return {number} 31175 * The parsed duration 31176 */ 31177 duration: function duration(value) { 31178 var parsedValue = parseInt(value, 10); 31179 31180 if (isNaN(parsedValue)) { 31181 return parseDuration(value); 31182 } 31183 31184 return parsedValue; 31185 }, 31186 31187 /** 31188 * Specifies the Segment duration, in units of the value of the @timescale. 31189 * 31190 * @param {string} value 31191 * value of attribute as a string 31192 * @return {number} 31193 * The parsed duration 31194 */ 31195 d: function d(value) { 31196 return parseInt(value, 10); 31197 }, 31198 31199 /** 31200 * Specifies the MPD start time, in @timescale units, the first Segment in the series 31201 * starts relative to the beginning of the Period 31202 * 31203 * @param {string} value 31204 * value of attribute as a string 31205 * @return {number} 31206 * The parsed time 31207 */ 31208 t: function t(value) { 31209 return parseInt(value, 10); 31210 }, 31211 31212 /** 31213 * Specifies the repeat count of the number of following contiguous Segments with the 31214 * same duration expressed by the value of @d 31215 * 31216 * @param {string} value 31217 * value of attribute as a string 31218 * @return {number} 31219 * The parsed number 31220 */ 31221 r: function r(value) { 31222 return parseInt(value, 10); 31223 }, 31224 31225 /** 31226 * Default parser for all other attributes. Acts as a no-op and just returns the value 31227 * as a string 31228 * 31229 * @param {string} value 31230 * value of attribute as a string 31231 * @return {string} 31232 * Unparsed value 31233 */ 31234 DEFAULT: function DEFAULT(value) { 31235 return value; 31236 } 31237 }; 31238 /** 31239 * Gets all the attributes and values of the provided node, parses attributes with known 31240 * types, and returns an object with attribute names mapped to values. 31241 * 31242 * @param {Node} el 31243 * The node to parse attributes from 31244 * @return {Object} 31245 * Object with all attributes of el parsed 31246 */ 31247 31248 var parseAttributes$1 = function parseAttributes(el) { 31249 if (!(el && el.attributes)) { 31250 return {}; 31251 } 31252 31253 return from(el.attributes).reduce(function (a, e) { 31254 var parseFn = parsers[e.name] || parsers.DEFAULT; 31255 a[e.name] = parseFn(e.value); 31256 return a; 31257 }, {}); 31258 }; 31259 31260 function decodeB64ToUint8Array(b64Text) { 31261 var decodedString = window$1.atob(b64Text); 31262 var array = new Uint8Array(decodedString.length); 31263 31264 for (var i = 0; i < decodedString.length; i++) { 31265 array[i] = decodedString.charCodeAt(i); 31266 } 31267 31268 return array; 31269 } 31270 31271 var keySystemsMap = { 31272 'urn:uuid:1077efec-c0b2-4d02-ace3-3c1e52e2fb4b': 'org.w3.clearkey', 31273 'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed': 'com.widevine.alpha', 31274 'urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95': 'com.microsoft.playready', 31275 'urn:uuid:f239e769-efa3-4850-9c16-a903c6932efb': 'com.adobe.primetime' 31276 }; 31277 /** 31278 * Builds a list of urls that is the product of the reference urls and BaseURL values 31279 * 31280 * @param {string[]} referenceUrls 31281 * List of reference urls to resolve to 31282 * @param {Node[]} baseUrlElements 31283 * List of BaseURL nodes from the mpd 31284 * @return {string[]} 31285 * List of resolved urls 31286 */ 31287 31288 var buildBaseUrls = function buildBaseUrls(referenceUrls, baseUrlElements) { 31289 if (!baseUrlElements.length) { 31290 return referenceUrls; 31291 } 31292 31293 return flatten(referenceUrls.map(function (reference) { 31294 return baseUrlElements.map(function (baseUrlElement) { 31295 return resolveUrl(reference, getContent(baseUrlElement)); 31296 }); 31297 })); 31298 }; 31299 /** 31300 * Contains all Segment information for its containing AdaptationSet 31301 * 31302 * @typedef {Object} SegmentInformation 31303 * @property {Object|undefined} template 31304 * Contains the attributes for the SegmentTemplate node 31305 * @property {Object[]|undefined} timeline 31306 * Contains a list of atrributes for each S node within the SegmentTimeline node 31307 * @property {Object|undefined} list 31308 * Contains the attributes for the SegmentList node 31309 * @property {Object|undefined} base 31310 * Contains the attributes for the SegmentBase node 31311 */ 31312 31313 /** 31314 * Returns all available Segment information contained within the AdaptationSet node 31315 * 31316 * @param {Node} adaptationSet 31317 * The AdaptationSet node to get Segment information from 31318 * @return {SegmentInformation} 31319 * The Segment information contained within the provided AdaptationSet 31320 */ 31321 31322 31323 var getSegmentInformation = function getSegmentInformation(adaptationSet) { 31324 var segmentTemplate = findChildren(adaptationSet, 'SegmentTemplate')[0]; 31325 var segmentList = findChildren(adaptationSet, 'SegmentList')[0]; 31326 var segmentUrls = segmentList && findChildren(segmentList, 'SegmentURL').map(function (s) { 31327 return merge({ 31328 tag: 'SegmentURL' 31329 }, parseAttributes$1(s)); 31330 }); 31331 var segmentBase = findChildren(adaptationSet, 'SegmentBase')[0]; 31332 var segmentTimelineParentNode = segmentList || segmentTemplate; 31333 var segmentTimeline = segmentTimelineParentNode && findChildren(segmentTimelineParentNode, 'SegmentTimeline')[0]; 31334 var segmentInitializationParentNode = segmentList || segmentBase || segmentTemplate; 31335 var segmentInitialization = segmentInitializationParentNode && findChildren(segmentInitializationParentNode, 'Initialization')[0]; // SegmentTemplate is handled slightly differently, since it can have both 31336 // @initialization and an <Initialization> node. @initialization can be templated, 31337 // while the node can have a url and range specified. If the <SegmentTemplate> has 31338 // both @initialization and an <Initialization> subelement we opt to override with 31339 // the node, as this interaction is not defined in the spec. 31340 31341 var template = segmentTemplate && parseAttributes$1(segmentTemplate); 31342 31343 if (template && segmentInitialization) { 31344 template.initialization = segmentInitialization && parseAttributes$1(segmentInitialization); 31345 } else if (template && template.initialization) { 31346 // If it is @initialization we convert it to an object since this is the format that 31347 // later functions will rely on for the initialization segment. This is only valid 31348 // for <SegmentTemplate> 31349 template.initialization = { 31350 sourceURL: template.initialization 31351 }; 31352 } 31353 31354 var segmentInfo = { 31355 template: template, 31356 timeline: segmentTimeline && findChildren(segmentTimeline, 'S').map(function (s) { 31357 return parseAttributes$1(s); 31358 }), 31359 list: segmentList && merge(parseAttributes$1(segmentList), { 31360 segmentUrls: segmentUrls, 31361 initialization: parseAttributes$1(segmentInitialization) 31362 }), 31363 base: segmentBase && merge(parseAttributes$1(segmentBase), { 31364 initialization: parseAttributes$1(segmentInitialization) 31365 }) 31366 }; 31367 Object.keys(segmentInfo).forEach(function (key) { 31368 if (!segmentInfo[key]) { 31369 delete segmentInfo[key]; 31370 } 31371 }); 31372 return segmentInfo; 31373 }; 31374 /** 31375 * Contains Segment information and attributes needed to construct a Playlist object 31376 * from a Representation 31377 * 31378 * @typedef {Object} RepresentationInformation 31379 * @property {SegmentInformation} segmentInfo 31380 * Segment information for this Representation 31381 * @property {Object} attributes 31382 * Inherited attributes for this Representation 31383 */ 31384 31385 /**
31386 * Maps a Representation node to an object containing Segment information and attributes 31387 * 31388 * @name inheritBaseUrlsCallback 31389 * @function 31390 * @param {Node} representation 31391 * Representation node from the mpd 31392 * @return {RepresentationInformation} 31393 * Representation information needed to construct a Playlist object 31394 */ 31395 31396 /** 31397 * Returns a callback for Array.prototype.map for mapping Representation nodes to 31398 * Segment information and attributes using inherited BaseURL nodes. 31399 * 31400 * @param {Object} adaptationSetAttributes 31401 * Contains attributes inherited by the AdaptationSet 31402 * @param {string[]} adaptationSetBaseUrls 31403 * Contains list of resolved base urls inherited by the AdaptationSet 31404 * @param {SegmentInformation} adaptationSetSegmentInfo 31405 * Contains Segment information for the AdaptationSet 31406 * @return {inheritBaseUrlsCallback} 31407 * Callback map function 31408 */ 31409 31410 31411 var inheritBaseUrls = function inheritBaseUrls(adaptationSetAttributes, adaptationSetBaseUrls, adaptationSetSegmentInfo) { 31412 return function (representation) { 31413 var repBaseUrlElements = findChildren(representation, 'BaseURL'); 31414 var repBaseUrls = buildBaseUrls(adaptationSetBaseUrls, repBaseUrlElements); 31415 var attributes = merge(adaptationSetAttributes, parseAttributes$1(representation)); 31416 var representationSegmentInfo = getSegmentInformation(representation); 31417 return repBaseUrls.map(function (baseUrl) { 31418 return { 31419 segmentInfo: merge(adaptationSetSegmentInfo, representationSegmentInfo), 31420 attributes: merge(attributes, { 31421 baseUrl: baseUrl 31422 }) 31423 }; 31424 }); 31425 }; 31426 }; 31427 /** 31428 * Tranforms a series of content protection nodes to
31429 * an object containing pssh data by key system 31430 * 31431 * @param {Node[]} contentProtectionNodes 31432 * Content protection nodes 31433 * @return {Object} 31434 * Object containing pssh data by key system 31435 */ 31436 31437 31438 var generateKeySystemInformation = function generateKeySystemInformation(contentProtectionNodes) { 31439 return contentProtectionNodes.reduce(function (acc, node) { 31440 var attributes = parseAttributes$1(node); 31441 var keySystem = keySystemsMap[attributes.schemeIdUri]; 31442 31443 if (keySystem) { 31444 acc[keySystem] = { 31445 attributes: attributes 31446 }; 31447 var psshNode = findChildren(node, 'cenc:pssh')[0]; 31448 31449 if (psshNode) { 31450 var pssh = getContent(psshNode); 31451 var psshBuffer = pssh && decodeB64ToUint8Array(pssh); 31452 acc[keySystem].pssh = psshBuffer; 31453 } 31454 } 31455 31456 return acc; 31457 }, {}); 31458 }; 31459 /** 31460 * Maps an AdaptationSet node to a list of Representation information objects 31461 * 31462 * @name toRepresentationsCallback 31463 * @function 31464 * @param {Node} adaptationSet 31465 * AdaptationSet node from the mpd 31466 * @return {RepresentationInformation[]} 31467 * List of objects containing Representaion information 31468 */ 31469 31470 /** 31471 * Returns a callback for Array.prototype.map for mapping AdaptationSet nodes to a list of 31472 * Representation information objects 31473 * 31474 * @param {Object} periodAttributes 31475 * Contains attributes inherited by the Period 31476 * @param {string[]} periodBaseUrls 31477 * Contains list of resolved base urls inherited by the Period 31478 * @param {string[]} periodSegmentInfo 31479 * Contains Segment Information at the period level 31480 * @return {toRepresentationsCallback} 31481 * Callback map function 31482 */ 31483 31484 31485 var toRepresentations = function toRepresentations(periodAttributes, periodBaseUrls, periodSegmentInfo) { 31486 return function (adaptationSet) { 31487 var adaptationSetAttributes = parseAttributes$1(adaptationSet); 31488 var adaptationSetBaseUrls = buildBaseUrls(periodBaseUrls, findChildren(adaptationSet, 'BaseURL')); 31489 var role = findChildren(adaptationSet, 'Role')[0]; 31490 var roleAttributes = { 31491 role: parseAttributes$1(role) 31492 }; 31493 var attrs = merge(periodAttributes, adaptationSetAttributes, roleAttributes); 31494 var contentProtection = generateKeySystemInformation(findChildren(adaptationSet, 'ContentProtection')); 31495 31496 if (Object.keys(contentProtection).length) { 31497 attrs = merge(attrs, { 31498 contentProtection: contentProtection 31499 }); 31500 } 31501 31502 var segmentInfo = getSegmentInformation(adaptationSet); 31503 var representations = findChildren(adaptationSet, 'Representation'); 31504 var adaptationSetSegmentInfo = merge(periodSegmentInfo, segmentInfo); 31505 return flatten(representations.map(inheritBaseUrls(attrs, adaptationSetBaseUrls, adaptationSetSegmentInfo))); 31506 }; 31507 }; 31508 /** 31509 * Maps an Period node to a list of Representation inforamtion objects for all 31510 * AdaptationSet nodes contained within the Period 31511 * 31512 * @name toAdaptationSetsCallback 31513 * @function 31514 * @param {Node} period 31515 * Period node from the mpd 31516 * @param {number} periodIndex 31517 * Index of the Period within the mpd 31518 * @return {RepresentationInformation[]} 31519 * List of objects containing Representaion information 31520 */ 31521 31522 /** 31523 * Returns a callback for Array.prototype.map for mapping Period nodes to a list of 31524 * Representation information objects 31525 * 31526 * @param {Object} mpdAttributes 31527 * Contains attributes inherited by the mpd 31528 * @param {string[]} mpdBaseUrls 31529 * Contains list of resolved base urls inherited by the mpd 31530 * @return {toAdaptationSetsCallback} 31531 * Callback map function 31532 */ 31533 31534 31535 var toAdaptationSets = function toAdaptationSets(mpdAttributes, mpdBaseUrls) { 31536 return function (period, index) { 31537 var periodBaseUrls = buildBaseUrls(mpdBaseUrls, findChildren(period, 'BaseURL')); 31538 var periodAtt = parseAttributes$1(period); 31539 var parsedPeriodId = parseInt(periodAtt.id, 10); // fallback to mapping index if Period@id is not a number 31540 31541 var periodIndex = window$1.isNaN(parsedPeriodId) ? index : parsedPeriodId; 31542 var periodAttributes = merge(mpdAttributes, { 31543 periodIndex: periodIndex 31544 }); 31545 var adaptationSets = findChildren(period, 'AdaptationSet'); 31546 var periodSegmentInfo = getSegmentInformation(period); 31547 return flatten(adaptationSets.map(toRepresentations(periodAttributes, periodBaseUrls, periodSegmentInfo))); 31548 }; 31549 }; 31550 /** 31551 * Traverses the mpd xml tree to generate a list of Representation information objects 31552 * that have inherited attributes from parent nodes 31553 * 31554 * @param {Node} mpd 31555 * The root node of the mpd 31556 * @param {Object} options 31557 * Available options for inheritAttributes 31558 * @param {string} options.manifestUri 31559 * The uri source of the mpd 31560 * @param {number} options.NOW 31561 * Current time per DASH IOP. Default is current time in ms since epoch 31562 * @param {number} options.clientOffset 31563 * Client time difference from NOW (in milliseconds) 31564 * @return {RepresentationInformation[]} 31565 * List of objects containing Representation information 31566 */ 31567 31568 31569 var inheritAttributes = function inheritAttributes(mpd, options) { 31570 if (options === void 0) { 31571 options = {}; 31572 } 31573 31574 var _options = options, 31575 _options$manifestUri = _options.manifestUri, 31576 manifestUri = _options$manifestUri === void 0 ? '' : _options$manifestUri, 31577 _options$NOW = _options.NOW, 31578 NOW = _options$NOW === void 0 ? Date.now() : _options$NOW, 31579 _options$clientOffset = _options.clientOffset, 31580 clientOffset = _options$clientOffset === void 0 ? 0 : _options$clientOffset;
vendor: 37,813 bytes, lines 31581-32670
31581 var periods = findChildren(mpd, 'Period'); 31582 31583 if (!periods.length) { 31584 throw new Error(errors.INVALID_NUMBER_OF_PERIOD); 31585 } 31586 31587 var mpdAttributes = parseAttributes$1(mpd); 31588 var mpdBaseUrls = buildBaseUrls([manifestUri], findChildren(mpd, 'BaseURL')); 31589 mpdAttributes.sourceDuration = mpdAttributes.mediaPresentationDuration || 0; 31590 mpdAttributes.NOW = NOW; 31591 mpdAttributes.clientOffset = clientOffset; 31592 return flatten(periods.map(toAdaptationSets(mpdAttributes, mpdBaseUrls))); 31593 }; 31594 31595 var stringToMpdXml = function stringToMpdXml(manifestString) { 31596 if (manifestString === '') { 31597 throw new Error(errors.DASH_EMPTY_MANIFEST); 31598 } 31599 31600 var parser = new window$1.DOMParser(); 31601 var xml = parser.parseFromString(manifestString, 'application/xml'); 31602 var mpd = xml && xml.documentElement.tagName === 'MPD' ? xml.documentElement : null; 31603 31604 if (!mpd || mpd && mpd.getElementsByTagName('parsererror').length > 0) { 31605 throw new Error(errors.DASH_INVALID_XML); 31606 } 31607 31608 return mpd; 31609 }; 31610 /** 31611 * Parses the manifest for a UTCTiming node, returning the nodes attributes if found 31612 * 31613 * @param {string} mpd 31614 * XML string of the MPD manifest 31615 * @return {Object|null} 31616 * Attributes of UTCTiming node specified in the manifest. Null if none found 31617 */ 31618 31619 31620 var parseUTCTimingScheme = function parseUTCTimingScheme(mpd) { 31621 var UTCTimingNode = findChildren(mpd, 'UTCTiming')[0]; 31622 31623 if (!UTCTimingNode) { 31624 return null; 31625 } 31626 31627 var attributes = parseAttributes$1(UTCTimingNode); 31628 31629 switch (attributes.schemeIdUri) { 31630 case 'urn:mpeg:dash:utc:http-head:2014': 31631 case 'urn:mpeg:dash:utc:http-head:2012': 31632 attributes.method = 'HEAD'; 31633 break; 31634 31635 case 'urn:mpeg:dash:utc:http-xsdate:2014': 31636 case 'urn:mpeg:dash:utc:http-iso:2014': 31637 case 'urn:mpeg:dash:utc:http-xsdate:2012': 31638 case 'urn:mpeg:dash:utc:http-iso:2012': 31639 attributes.method = 'GET'; 31640 break; 31641 31642 case 'urn:mpeg:dash:utc:direct:2014': 31643 case 'urn:mpeg:dash:utc:direct:2012': 31644 attributes.method = 'DIRECT'; 31645 attributes.value = Date.parse(attributes.value); 31646 break; 31647 31648 case 'urn:mpeg:dash:utc:http-ntp:2014': 31649 case 'urn:mpeg:dash:utc:ntp:2014': 31650 case 'urn:mpeg:dash:utc:sntp:2014': 31651 default: 31652 throw new Error(errors.UNSUPPORTED_UTC_TIMING_SCHEME); 31653 } 31654 31655 return attributes; 31656 }; 31657 31658 var parse = function parse(manifestString, options) { 31659 return toM3u8(toPlaylists(inheritAttributes(stringToMpdXml(manifestString), options))); 31660 }; 31661 /** 31662 * Parses the manifest for a UTCTiming node, returning the nodes attributes if found 31663 * 31664 * @param {string} manifestString 31665 * XML string of the MPD manifest 31666 * @return {Object|null} 31667 * Attributes of UTCTiming node specified in the manifest. Null if none found 31668 */ 31669 31670 31671 var parseUTCTiming = function parseUTCTiming(manifestString) { 31672 return parseUTCTimingScheme(stringToMpdXml(manifestString)); 31673 }; 31674 31675 var toUnsigned = function toUnsigned(value) { 31676 return value >>> 0; 31677 }; 31678 31679 var bin = { 31680 toUnsigned: toUnsigned 31681 }; 31682 31683 var toUnsigned$1 = bin.toUnsigned; 31684 31685 var _findBox, parseType, timescale, startTime, getVideoTrackIds; // Find the data for a box specified by its path 31686 31687 31688 _findBox = function findBox(data, path) { 31689 var results = [], 31690 i, 31691 size, 31692 type, 31693 end, 31694 subresults; 31695 31696 if (!path.length) { 31697 // short-circuit the search for empty paths 31698 return null; 31699 } 31700 31701 for (i = 0; i < data.byteLength;) { 31702 size = toUnsigned$1(data[i] << 24 | data[i + 1] << 16 | data[i + 2] << 8 | data[i + 3]); 31703 type = parseType(data.subarray(i + 4, i + 8)); 31704 end = size > 1 ? i + size : data.byteLength; 31705 31706 if (type === path[0]) { 31707 if (path.length === 1) { 31708 // this is the end of the path and we've found the box we were 31709 // looking for 31710 results.push(data.subarray(i + 8, end)); 31711 } else { 31712 // recursively search for the next box along the path 31713 subresults = _findBox(data.subarray(i + 8, end), path.slice(1)); 31714 31715 if (subresults.length) { 31716 results = results.concat(subresults); 31717 } 31718 } 31719 } 31720 31721 i = end; 31722 } // we've finished searching all of data 31723 31724 31725 return results; 31726 }; 31727 /** 31728 * Returns the string representation of an ASCII encoded four byte buffer. 31729 * @param buffer {Uint8Array} a four-byte buffer to translate 31730 * @return {string} the corresponding string 31731 */ 31732 31733 31734 parseType = function parseType(buffer) { 31735 var result = ''; 31736 result += String.fromCharCode(buffer[0]); 31737 result += String.fromCharCode(buffer[1]); 31738 result += String.fromCharCode(buffer[2]); 31739 result += String.fromCharCode(buffer[3]); 31740 return result; 31741 }; 31742 /** 31743 * Parses an MP4 initialization segment and extracts the timescale 31744 * values for any declared tracks. Timescale values indicate the 31745 * number of clock ticks per second to assume for time-based values 31746 * elsewhere in the MP4. 31747 * 31748 * To determine the start time of an MP4, you need two pieces of 31749 * information: the timescale unit and the earliest base media decode 31750 * time. Multiple timescales can be specified within an MP4 but the 31751 * base media decode time is always expressed in the timescale from 31752 * the media header box for the track: 31753 * ``` 31754 * moov > trak > mdia > mdhd.timescale 31755 * ``` 31756 * @param init {Uint8Array} the bytes of the init segment 31757 * @return {object} a hash of track ids to timescale values or null if 31758 * the init segment is malformed. 31759 */ 31760 31761 31762 timescale = function timescale(init) { 31763 var result = {}, 31764 traks = _findBox(init, ['moov', 'trak']); // mdhd timescale 31765 31766 31767 return traks.reduce(function (result, trak) { 31768 var tkhd, version, index, id, mdhd; 31769 tkhd = _findBox(trak, ['tkhd'])[0]; 31770 31771 if (!tkhd) { 31772 return null; 31773 } 31774 31775 version = tkhd[0]; 31776 index = version === 0 ? 12 : 20; 31777 id = toUnsigned$1(tkhd[index] << 24 | tkhd[index + 1] << 16 | tkhd[index + 2] << 8 | tkhd[index + 3]); 31778 mdhd = _findBox(trak, ['mdia', 'mdhd'])[0]; 31779 31780 if (!mdhd) { 31781 return null; 31782 } 31783 31784 version = mdhd[0]; 31785 index = version === 0 ? 12 : 20; 31786 result[id] = toUnsigned$1(mdhd[index] << 24 | mdhd[index + 1] << 16 | mdhd[index + 2] << 8 | mdhd[index + 3]); 31787 return result; 31788 }, result); 31789 }; 31790 /** 31791 * Determine the base media decode start time, in seconds, for an MP4 31792 * fragment. If multiple fragments are specified, the earliest time is 31793 * returned. 31794 * 31795 * The base media decode time can be parsed from track fragment 31796 * metadata: 31797 * ``` 31798 * moof > traf > tfdt.baseMediaDecodeTime 31799 * ``` 31800 * It requires the timescale value from the mdhd to interpret. 31801 * 31802 * @param timescale {object} a hash of track ids to timescale values. 31803 * @return {number} the earliest base media decode start time for the 31804 * fragment, in seconds 31805 */ 31806 31807 31808 startTime = function startTime(timescale, fragment) { 31809 var trafs, baseTimes, result; // we need info from two childrend of each track fragment box 31810 31811 trafs = _findBox(fragment, ['moof', 'traf']); // determine the start times for each track 31812 31813 baseTimes = [].concat.apply([], trafs.map(function (traf) { 31814 return _findBox(traf, ['tfhd']).map(function (tfhd) { 31815 var id, scale, baseTime; // get the track id from the tfhd 31816 31817 id = toUnsigned$1(tfhd[4] << 24 | tfhd[5] << 16 | tfhd[6] << 8 | tfhd[7]); // assume a 90kHz clock if no timescale was specified 31818 31819 scale = timescale[id] || 90e3; // get the base media decode time from the tfdt 31820 31821 baseTime = _findBox(traf, ['tfdt']).map(function (tfdt) { 31822 var version, result; 31823 version = tfdt[0]; 31824 result = toUnsigned$1(tfdt[4] << 24 | tfdt[5] << 16 | tfdt[6] << 8 | tfdt[7]); 31825 31826 if (version === 1) { 31827 result *= Math.pow(2, 32); 31828 result += toUnsigned$1(tfdt[8] << 24 | tfdt[9] << 16 | tfdt[10] << 8 | tfdt[11]); 31829 } 31830 31831 return result; 31832 })[0]; 31833 baseTime = baseTime || Infinity; // convert base time to seconds 31834 31835 return baseTime / scale; 31836 }); 31837 })); // return the minimum 31838 31839 result = Math.min.apply(null, baseTimes); 31840 return isFinite(result) ? result : 0; 31841 }; 31842 /** 31843 * Find the trackIds of the video tracks in this source. 31844 * Found by parsing the Handler Reference and Track Header Boxes: 31845 * moov > trak > mdia > hdlr 31846 * moov > trak > tkhd 31847 * 31848 * @param {Uint8Array} init - The bytes of the init segment for this source 31849 * @return {Number[]} A list of trackIds 31850 * 31851 * @see ISO-BMFF-12/2015, Section 8.4.3 31852 **/ 31853 31854 31855 getVideoTrackIds = function getVideoTrackIds(init) { 31856 var traks = _findBox(init, ['moov', 'trak']); 31857 31858 var videoTrackIds = []; 31859 traks.forEach(function (trak) { 31860 var hdlrs = _findBox(trak, ['mdia', 'hdlr']); 31861 31862 var tkhds = _findBox(trak, ['tkhd']); 31863 31864 hdlrs.forEach(function (hdlr, index) { 31865 var handlerType = parseType(hdlr.subarray(8, 12)); 31866 var tkhd = tkhds[index]; 31867 var view; 31868 var version; 31869 var trackId; 31870 31871 if (handlerType === 'vide') { 31872 view = new DataView(tkhd.buffer, tkhd.byteOffset, tkhd.byteLength); 31873 version = view.getUint8(0); 31874 trackId = version === 0 ? view.getUint32(12) : view.getUint32(20); 31875 videoTrackIds.push(trackId); 31876 } 31877 }); 31878 }); 31879 return videoTrackIds; 31880 }; 31881 31882 var probe = { 31883 findBox: _findBox, 31884 parseType: parseType, 31885 timescale: timescale, 31886 startTime: startTime, 31887 videoTrackIds: getVideoTrackIds 31888 }; 31889 31890 /** 31891 * mux.js 31892 * 31893 * Copyright (c) 2015 Brightcove 31894 * All rights reserved. 31895 * 31896 * Functions that generate fragmented MP4s suitable for use with Media 31897 * Source Extensions. 31898 */ 31899 31900 var UINT32_MAX = Math.pow(2, 32) - 1; 31901 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 31902 31903 (function () { 31904 var i; 31905 types = { 31906 avc1: [], 31907 // codingname 31908 avcC: [], 31909 btrt: [], 31910 dinf: [], 31911 dref: [], 31912 esds: [], 31913 ftyp: [], 31914 hdlr: [], 31915 mdat: [], 31916 mdhd: [], 31917 mdia: [], 31918 mfhd: [], 31919 minf: [], 31920 moof: [], 31921 moov: [], 31922 mp4a: [], 31923 // codingname 31924 mvex: [], 31925 mvhd: [], 31926 sdtp: [], 31927 smhd: [], 31928 stbl: [], 31929 stco: [], 31930 stsc: [], 31931 stsd: [], 31932 stsz: [], 31933 stts: [], 31934 styp: [], 31935 tfdt: [], 31936 tfhd: [], 31937 traf: [], 31938 trak: [], 31939 trun: [], 31940 trex: [], 31941 tkhd: [], 31942 vmhd: [] 31943 }; // In environments where Uint8Array is undefined (e.g., IE8), skip set up so that we 31944 // don't throw an error 31945 31946 if (typeof Uint8Array === 'undefined') { 31947 return; 31948 } 31949 31950 for (i in types) { 31951 if (types.hasOwnProperty(i)) { 31952 types[i] = [i.charCodeAt(0), i.charCodeAt(1), i.charCodeAt(2), i.charCodeAt(3)]; 31953 } 31954 } 31955 31956 MAJOR_BRAND = new Uint8Array(['i'.charCodeAt(0), 's'.charCodeAt(0), 'o'.charCodeAt(0), 'm'.charCodeAt(0)]); 31957 AVC1_BRAND = new Uint8Array(['a'.charCodeAt(0), 'v'.charCodeAt(0), 'c'.charCodeAt(0), '1'.charCodeAt(0)]); 31958 MINOR_VERSION = new Uint8Array([0, 0, 0, 1]); 31959 VIDEO_HDLR = new Uint8Array([0x00, // version 0 31960 0x00, 0x00, 0x00, // flags 31961 0x00, 0x00, 0x00, 0x00, // pre_defined 31962 0x76, 0x69, 0x64, 0x65, // handler_type: 'vide' 31963 0x00, 0x00, 0x00, 0x00, // reserved 31964 0x00, 0x00, 0x00, 0x00, // reserved 31965 0x00, 0x00, 0x00, 0x00, // reserved 31966 0x56, 0x69, 0x64, 0x65, 0x6f, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'VideoHandler' 31967 ]); 31968 AUDIO_HDLR = new Uint8Array([0x00, // version 0 31969 0x00, 0x00, 0x00, // flags 31970 0x00, 0x00, 0x00, 0x00, // pre_defined 31971 0x73, 0x6f, 0x75, 0x6e, // handler_type: 'soun' 31972 0x00, 0x00, 0x00, 0x00, // reserved 31973 0x00, 0x00, 0x00, 0x00, // reserved 31974 0x00, 0x00, 0x00, 0x00, // reserved 31975 0x53, 0x6f, 0x75, 0x6e, 0x64, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'SoundHandler' 31976 ]); 31977 HDLR_TYPES = { 31978 video: VIDEO_HDLR, 31979 audio: AUDIO_HDLR 31980 }; 31981 DREF = new Uint8Array([0x00, // version 0 31982 0x00, 0x00, 0x00, // flags 31983 0x00, 0x00, 0x00, 0x01, // entry_count 31984 0x00, 0x00, 0x00, 0x0c, // entry_size 31985 0x75, 0x72, 0x6c, 0x20, // 'url' type 31986 0x00, // version 0 31987 0x00, 0x00, 0x01 // entry_flags 31988 ]); 31989 SMHD = new Uint8Array([0x00, // version 31990 0x00, 0x00, 0x00, // flags 31991 0x00, 0x00, // balance, 0 means centered 31992 0x00, 0x00 // reserved 31993 ]); 31994 STCO = new Uint8Array([0x00, // version 31995 0x00, 0x00, 0x00, // flags 31996 0x00, 0x00, 0x00, 0x00 // entry_count 31997 ]); 31998 STSC = STCO; 31999 STSZ = new Uint8Array([0x00, // version 32000 0x00, 0x00, 0x00, // flags 32001 0x00, 0x00, 0x00, 0x00, // sample_size 32002 0x00, 0x00, 0x00, 0x00 // sample_count 32003 ]); 32004 STTS = STCO; 32005 VMHD = new Uint8Array([0x00, // version 32006 0x00, 0x00, 0x01, // flags 32007 0x00, 0x00, // graphicsmode 32008 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // opcolor 32009 ]); 32010 })(); 32011 32012 box = function box(type) { 32013 var payload = [], 32014 size = 0, 32015 i, 32016 result, 32017 view; 32018 32019 for (i = 1; i < arguments.length; i++) { 32020 payload.push(arguments[i]); 32021 } 32022 32023 i = payload.length; // calculate the total size we need to allocate 32024 32025 while (i--) { 32026 size += payload[i].byteLength; 32027 } 32028 32029 result = new Uint8Array(size + 8); 32030 view = new DataView(result.buffer, result.byteOffset, result.byteLength); 32031 view.setUint32(0, result.byteLength); 32032 result.set(type, 4); // copy the payload into the result 32033 32034 for (i = 0, size = 8; i < payload.length; i++) { 32035 result.set(payload[i], size); 32036 size += payload[i].byteLength; 32037 } 32038 32039 return result; 32040 }; 32041 32042 dinf = function dinf() { 32043 return box(types.dinf, box(types.dref, DREF)); 32044 }; 32045 32046 esds = function esds(track) { 32047 return box(types.esds, new Uint8Array([0x00, // version 32048 0x00, 0x00, 0x00, // flags 32049 // ES_Descriptor 32050 0x03, // tag, ES_DescrTag 32051 0x19, // length 32052 0x00, 0x00, // ES_ID 32053 0x00, // streamDependenceFlag, URL_flag, reserved, streamPriority 32054 // DecoderConfigDescriptor 32055 0x04, // tag, DecoderConfigDescrTag 32056 0x11, // length 32057 0x40, // object type 32058 0x15, // streamType 32059 0x00, 0x06, 0x00, // bufferSizeDB 32060 0x00, 0x00, 0xda, 0xc0, // maxBitrate 32061 0x00, 0x00, 0xda, 0xc0, // avgBitrate 32062 // DecoderSpecificInfo 32063 0x05, // tag, DecoderSpecificInfoTag 32064 0x02, // length 32065 // ISO/IEC 14496-3, AudioSpecificConfig 32066 // for samplingFrequencyIndex see ISO/IEC 13818-7:2006, 8.1.3.2.2, Table 35 32067 track.audioobjecttype << 3 | track.samplingfrequencyindex >>> 1, track.samplingfrequencyindex << 7 | track.channelcount << 3, 0x06, 0x01, 0x02 // GASpecificConfig 32068 ])); 32069 }; 32070 32071 ftyp = function ftyp() { 32072 return box(types.ftyp, MAJOR_BRAND, MINOR_VERSION, MAJOR_BRAND, AVC1_BRAND); 32073 }; 32074 32075 hdlr = function hdlr(type) { 32076 return box(types.hdlr, HDLR_TYPES[type]); 32077 }; 32078 32079 mdat = function mdat(data) { 32080 return box(types.mdat, data); 32081 }; 32082 32083 mdhd = function mdhd(track) { 32084 var result = new Uint8Array([0x00, // version 0 32085 0x00, 0x00, 0x00, // flags 32086 0x00, 0x00, 0x00, 0x02, // creation_time 32087 0x00, 0x00, 0x00, 0x03, // modification_time 32088 0x00, 0x01, 0x5f, 0x90, // timescale, 90,000 "ticks" per second 32089 track.duration >>> 24 & 0xFF, track.duration >>> 16 & 0xFF, track.duration >>> 8 & 0xFF, track.duration & 0xFF, // duration 32090 0x55, 0xc4, // 'und' language (undetermined) 32091 0x00, 0x00]); // Use the sample rate from the track metadata, when it is 32092 // defined. The sample rate can be parsed out of an ADTS header, for 32093 // instance. 32094 32095 if (track.samplerate) { 32096 result[12] = track.samplerate >>> 24 & 0xFF; 32097 result[13] = track.samplerate >>> 16 & 0xFF; 32098 result[14] = track.samplerate >>> 8 & 0xFF; 32099 result[15] = track.samplerate & 0xFF; 32100 } 32101 32102 return box(types.mdhd, result); 32103 }; 32104 32105 mdia = function mdia(track) { 32106 return box(types.mdia, mdhd(track), hdlr(track.type), minf(track)); 32107 }; 32108 32109 mfhd = function mfhd(sequenceNumber) { 32110 return box(types.mfhd, new Uint8Array([0x00, 0x00, 0x00, 0x00, // flags 32111 (sequenceNumber & 0xFF000000) >> 24, (sequenceNumber & 0xFF0000) >> 16, (sequenceNumber & 0xFF00) >> 8, sequenceNumber & 0xFF // sequence_number 32112 ])); 32113 }; 32114 32115 minf = function minf(track) { 32116 return box(types.minf, track.type === 'video' ? box(types.vmhd, VMHD) : box(types.smhd, SMHD), dinf(), stbl(track)); 32117 }; 32118 32119 moof = function moof(sequenceNumber, tracks) { 32120 var trackFragments = [], 32121 i = tracks.length; // build traf boxes for each track fragment 32122 32123 while (i--) { 32124 trackFragments[i] = traf(tracks[i]); 32125 } 32126 32127 return box.apply(null, [types.moof, mfhd(sequenceNumber)].concat(trackFragments)); 32128 }; 32129 /** 32130 * Returns a movie box. 32131 * @param tracks {array} the tracks associated with this movie 32132 * @see ISO/IEC 14496-12:2012(E), section 8.2.1 32133 */ 32134 32135 32136 moov = function moov(tracks) { 32137 var i = tracks.length, 32138 boxes = []; 32139 32140 while (i--) { 32141 boxes[i] = trak(tracks[i]); 32142 } 32143 32144 return box.apply(null, [types.moov, mvhd(0xffffffff)].concat(boxes).concat(mvex(tracks))); 32145 }; 32146 32147 mvex = function mvex(tracks) { 32148 var i = tracks.length, 32149 boxes = []; 32150 32151 while (i--) { 32152 boxes[i] = trex(tracks[i]); 32153 } 32154 32155 return box.apply(null, [types.mvex].concat(boxes)); 32156 }; 32157 32158 mvhd = function mvhd(duration) { 32159 var bytes = new Uint8Array([0x00, // version 0 32160 0x00, 0x00, 0x00, // flags 32161 0x00, 0x00, 0x00, 0x01, // creation_time 32162 0x00, 0x00, 0x00, 0x02, // modification_time 32163 0x00, 0x01, 0x5f, 0x90, // timescale, 90,000 "ticks" per second 32164 (duration & 0xFF000000) >> 24, (duration & 0xFF0000) >> 16, (duration & 0xFF00) >> 8, duration & 0xFF, // duration 32165 0x00, 0x01, 0x00, 0x00, // 1.0 rate 32166 0x01, 0x00, // 1.0 volume 32167 0x00, 0x00, // reserved 32168 0x00, 0x00, 0x00, 0x00, // reserved 32169 0x00, 0x00, 0x00, 0x00, // reserved 32170 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 32171 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 32172 0xff, 0xff, 0xff, 0xff // next_track_ID 32173 ]); 32174 return box(types.mvhd, bytes); 32175 }; 32176 32177 sdtp = function sdtp(track) { 32178 var samples = track.samples || [], 32179 bytes = new Uint8Array(4 + samples.length), 32180 flags, 32181 i; // leave the full box header (4 bytes) all zero 32182 // write the sample table 32183 32184 for (i = 0; i < samples.length; i++) { 32185 flags = samples[i].flags; 32186 bytes[i + 4] = flags.dependsOn << 4 | flags.isDependedOn << 2 | flags.hasRedundancy; 32187 } 32188 32189 return box(types.sdtp, bytes); 32190 }; 32191 32192 stbl = function stbl(track) { 32193 return box(types.stbl, stsd(track), box(types.stts, STTS), box(types.stsc, STSC), box(types.stsz, STSZ), box(types.stco, STCO)); 32194 }; 32195 32196 (function () { 32197 var videoSample, audioSample; 32198 32199 stsd = function stsd(track) { 32200 return box(types.stsd, new Uint8Array([0x00, // version 0 32201 0x00, 0x00, 0x00, // flags 32202 0x00, 0x00, 0x00, 0x01]), track.type === 'video' ? videoSample(track) : audioSample(track)); 32203 }; 32204 32205 videoSample = function videoSample(track) { 32206 var sps = track.sps || [], 32207 pps = track.pps || [], 32208 sequenceParameterSets = [], 32209 pictureParameterSets = [], 32210 i; // assemble the SPSs 32211 32212 for (i = 0; i < sps.length; i++) { 32213 sequenceParameterSets.push((sps[i].byteLength & 0xFF00) >>> 8); 32214 sequenceParameterSets.push(sps[i].byteLength & 0xFF); // sequenceParameterSetLength 32215 32216 sequenceParameterSets = sequenceParameterSets.concat(Array.prototype.slice.call(sps[i])); // SPS 32217 } // assemble the PPSs 32218 32219 32220 for (i = 0; i < pps.length; i++) { 32221 pictureParameterSets.push((pps[i].byteLength & 0xFF00) >>> 8); 32222 pictureParameterSets.push(pps[i].byteLength & 0xFF); 32223 pictureParameterSets = pictureParameterSets.concat(Array.prototype.slice.call(pps[i])); 32224 } 32225 32226 return box(types.avc1, new Uint8Array([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved 32227 0x00, 0x01, // data_reference_index 32228 0x00, 0x00, // pre_defined 32229 0x00, 0x00, // reserved 32230 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // pre_defined 32231 (track.width & 0xff00) >> 8, track.width & 0xff, // width 32232 (track.height & 0xff00) >> 8, track.height & 0xff, // height 32233 0x00, 0x48, 0x00, 0x00, // horizresolution 32234 0x00, 0x48, 0x00, 0x00, // vertresolution 32235 0x00, 0x00, 0x00, 0x00, // reserved 32236 0x00, 0x01, // frame_count 32237 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 32238 0x00, 0x18, // depth = 24 32239 0x11, 0x11 // pre_defined = -1 32240 ]), box(types.avcC, new Uint8Array([0x01, // configurationVersion 32241 track.profileIdc, // AVCProfileIndication 32242 track.profileCompatibility, // profile_compatibility 32243 track.levelIdc, // AVCLevelIndication 32244 0xff // lengthSizeMinusOne, hard-coded to 4 bytes 32245 ].concat([sps.length // numOfSequenceParameterSets 32246 ]).concat(sequenceParameterSets).concat([pps.length // numOfPictureParameterSets 32247 ]).concat(pictureParameterSets))), // "PPS" 32248 box(types.btrt, new Uint8Array([0x00, 0x1c, 0x9c, 0x80, // bufferSizeDB 32249 0x00, 0x2d, 0xc6, 0xc0, // maxBitrate 32250 0x00, 0x2d, 0xc6, 0xc0])) // avgBitrate 32251 ); 32252 }; 32253 32254 audioSample = function audioSample(track) { 32255 return box(types.mp4a, new Uint8Array([// SampleEntry, ISO/IEC 14496-12 32256 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved 32257 0x00, 0x01, // data_reference_index 32258 // AudioSampleEntry, ISO/IEC 14496-12 32259 0x00, 0x00, 0x00, 0x00, // reserved 32260 0x00, 0x00, 0x00, 0x00, // reserved 32261 (track.channelcount & 0xff00) >> 8, track.channelcount & 0xff, // channelcount 32262 (track.samplesize & 0xff00) >> 8, track.samplesize & 0xff, // samplesize 32263 0x00, 0x00, // pre_defined 32264 0x00, 0x00, // reserved 32265 (track.samplerate & 0xff00) >> 8, track.samplerate & 0xff, 0x00, 0x00 // samplerate, 16.16 32266 // MP4AudioSampleEntry, ISO/IEC 14496-14 32267 ]), esds(track)); 32268 }; 32269 })(); 32270 32271 tkhd = function tkhd(track) { 32272 var result = new Uint8Array([0x00, // version 0 32273 0x00, 0x00, 0x07, // flags 32274 0x00, 0x00, 0x00, 0x00, // creation_time 32275 0x00, 0x00, 0x00, 0x00, // modification_time 32276 (track.id & 0xFF000000) >> 24, (track.id & 0xFF0000) >> 16, (track.id & 0xFF00) >> 8, track.id & 0xFF, // track_ID 32277 0x00, 0x00, 0x00, 0x00, // reserved 32278 (track.duration & 0xFF000000) >> 24, (track.duration & 0xFF0000) >> 16, (track.duration & 0xFF00) >> 8, track.duration & 0xFF, // duration 32279 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved 32280 0x00, 0x00, // layer 32281 0x00, 0x00, // alternate_group 32282 0x01, 0x00, // non-audio track volume 32283 0x00, 0x00, // reserved 32284 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 32285 (track.width & 0xFF00) >> 8, track.width & 0xFF, 0x00, 0x00, // width 32286 (track.height & 0xFF00) >> 8, track.height & 0xFF, 0x00, 0x00 // height 32287 ]); 32288 return box(types.tkhd, result); 32289 }; 32290 /** 32291 * Generate a track fragment (traf) box. A traf box collects metadata 32292 * about tracks in a movie fragment (moof) box. 32293 */ 32294 32295 32296 traf = function traf(track) { 32297 var trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun, sampleDependencyTable, dataOffset, upperWordBaseMediaDecodeTime, lowerWordBaseMediaDecodeTime; 32298 trackFragmentHeader = box(types.tfhd, new Uint8Array([0x00, // version 0 32299 0x00, 0x00, 0x3a, // flags 32300 (track.id & 0xFF000000) >> 24, (track.id & 0xFF0000) >> 16, (track.id & 0xFF00) >> 8, track.id & 0xFF, // track_ID 32301 0x00, 0x00, 0x00, 0x01, // sample_description_index 32302 0x00, 0x00, 0x00, 0x00, // default_sample_duration 32303 0x00, 0x00, 0x00, 0x00, // default_sample_size 32304 0x00, 0x00, 0x00, 0x00 // default_sample_flags 32305 ])); 32306 upperWordBaseMediaDecodeTime = Math.floor(track.baseMediaDecodeTime / (UINT32_MAX + 1)); 32307 lowerWordBaseMediaDecodeTime = Math.floor(track.baseMediaDecodeTime % (UINT32_MAX + 1)); 32308 trackFragmentDecodeTime = box(types.tfdt, new Uint8Array([0x01, // version 1 32309 0x00, 0x00, 0x00, // flags 32310 // baseMediaDecodeTime 32311 upperWordBaseMediaDecodeTime >>> 24 & 0xFF, upperWordBaseMediaDecodeTime >>> 16 & 0xFF, upperWordBaseMediaDecodeTime >>> 8 & 0xFF, upperWordBaseMediaDecodeTime & 0xFF, lowerWordBaseMediaDecodeTime >>> 24 & 0xFF, lowerWordBaseMediaDecodeTime >>> 16 & 0xFF, lowerWordBaseMediaDecodeTime >>> 8 & 0xFF, lowerWordBaseMediaDecodeTime & 0xFF])); // the data offset specifies the number of bytes from the start of 32312 // the containing moof to the first payload byte of the associated 32313 // mdat 32314 32315 dataOffset = 32 + // tfhd 32316 20 + // tfdt 32317 8 + // traf header 32318 16 + // mfhd 32319 8 + // moof header 32320 8; // mdat header 32321 // audio tracks require less metadata 32322 32323 if (track.type === 'audio') { 32324 trackFragmentRun = trun(track, dataOffset); 32325 return box(types.traf, trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun); 32326 } // video tracks should contain an independent and disposable samples 32327 // box (sdtp) 32328 // generate one and adjust offsets to match 32329 32330 32331 sampleDependencyTable = sdtp(track); 32332 trackFragmentRun = trun(track, sampleDependencyTable.length + dataOffset); 32333 return box(types.traf, trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun, sampleDependencyTable); 32334 }; 32335 /** 32336 * Generate a track box. 32337 * @param track {object} a track definition 32338 * @return {Uint8Array} the track box 32339 */ 32340 32341 32342 trak = function trak(track) { 32343 track.duration = track.duration || 0xffffffff; 32344 return box(types.trak, tkhd(track), mdia(track)); 32345 }; 32346 32347 trex = function trex(track) { 32348 var result = new Uint8Array([0x00, // version 0 32349 0x00, 0x00, 0x00, // flags 32350 (track.id & 0xFF000000) >> 24, (track.id & 0xFF0000) >> 16, (track.id & 0xFF00) >> 8, track.id & 0xFF, // track_ID 32351 0x00, 0x00, 0x00, 0x01, // default_sample_description_index 32352 0x00, 0x00, 0x00, 0x00, // default_sample_duration 32353 0x00, 0x00, 0x00, 0x00, // default_sample_size 32354 0x00, 0x01, 0x00, 0x01 // default_sample_flags 32355 ]); // the last two bytes of default_sample_flags is the sample 32356 // degradation priority, a hint about the importance of this sample 32357 // relative to others. Lower the degradation priority for all sample 32358 // types other than video. 32359 32360 if (track.type !== 'video') { 32361 result[result.length - 1] = 0x00; 32362 } 32363 32364 return box(types.trex, result); 32365 }; 32366 32367 (function () { 32368 var audioTrun, videoTrun, trunHeader; // This method assumes all samples are uniform. That is, if a 32369 // duration is present for the first sample, it will be present for 32370 // all subsequent samples. 32371 // see ISO/IEC 14496-12:2012, Section 8.8.8.1 32372 32373 trunHeader = function trunHeader(samples, offset) { 32374 var durationPresent = 0, 32375 sizePresent = 0, 32376 flagsPresent = 0, 32377 compositionTimeOffset = 0; // trun flag constants 32378 32379 if (samples.length) { 32380 if (samples[0].duration !== undefined) { 32381 durationPresent = 0x1; 32382 } 32383 32384 if (samples[0].size !== undefined) { 32385 sizePresent = 0x2; 32386 } 32387 32388 if (samples[0].flags !== undefined) { 32389 flagsPresent = 0x4; 32390 } 32391 32392 if (samples[0].compositionTimeOffset !== undefined) { 32393 compositionTimeOffset = 0x8; 32394 } 32395 } 32396 32397 return [0x00, // version 0 32398 0x00, durationPresent | sizePresent | flagsPresent | compositionTimeOffset, 0x01, // flags 32399 (samples.length & 0xFF000000) >>> 24, (samples.length & 0xFF0000) >>> 16, (samples.length & 0xFF00) >>> 8, samples.length & 0xFF, // sample_count 32400 (offset & 0xFF000000) >>> 24, (offset & 0xFF0000) >>> 16, (offset & 0xFF00) >>> 8, offset & 0xFF // data_offset 32401 ]; 32402 }; 32403 32404 videoTrun = function videoTrun(track, offset) { 32405 var bytes, samples, sample, i; 32406 samples = track.samples || []; 32407 offset += 8 + 12 + 16 * samples.length; 32408 bytes = trunHeader(samples, offset); 32409 32410 for (i = 0; i < samples.length; i++) { 32411 sample = samples[i]; 32412 bytes = bytes.concat([(sample.duration & 0xFF000000) >>> 24, (sample.duration & 0xFF0000) >>> 16, (sample.duration & 0xFF00) >>> 8, sample.duration & 0xFF, // sample_duration 32413 (sample.size & 0xFF000000) >>> 24, (sample.size & 0xFF0000) >>> 16, (sample.size & 0xFF00) >>> 8, sample.size & 0xFF, // sample_size 32414 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 32415 (sample.compositionTimeOffset & 0xFF000000) >>> 24, (sample.compositionTimeOffset & 0xFF0000) >>> 16, (sample.compositionTimeOffset & 0xFF00) >>> 8, sample.compositionTimeOffset & 0xFF // sample_composition_time_offset 32416 ]); 32417 } 32418 32419 return box(types.trun, new Uint8Array(bytes)); 32420 }; 32421 32422 audioTrun = function audioTrun(track, offset) { 32423 var bytes, samples, sample, i; 32424 samples = track.samples || []; 32425 offset += 8 + 12 + 8 * samples.length; 32426 bytes = trunHeader(samples, offset); 32427 32428 for (i = 0; i < samples.length; i++) { 32429 sample = samples[i]; 32430 bytes = bytes.concat([(sample.duration & 0xFF000000) >>> 24, (sample.duration & 0xFF0000) >>> 16, (sample.duration & 0xFF00) >>> 8, sample.duration & 0xFF, // sample_duration 32431 (sample.size & 0xFF000000) >>> 24, (sample.size & 0xFF0000) >>> 16, (sample.size & 0xFF00) >>> 8, sample.size & 0xFF]); // sample_size 32432 } 32433 32434 return box(types.trun, new Uint8Array(bytes)); 32435 }; 32436 32437 trun = function trun(track, offset) { 32438 if (track.type === 'audio') { 32439 return audioTrun(track, offset); 32440 } 32441 32442 return videoTrun(track, offset); 32443 }; 32444 })(); 32445 32446 var mp4Generator = { 32447 ftyp: ftyp, 32448 mdat: mdat, 32449 moof: moof, 32450 moov: moov, 32451 initSegment: function initSegment(tracks) { 32452 var fileType = ftyp(), 32453 movie = moov(tracks), 32454 result; 32455 result = new Uint8Array(fileType.byteLength + movie.byteLength); 32456 result.set(fileType); 32457 result.set(movie, fileType.byteLength); 32458 return result; 32459 } 32460 }; 32461 32462 /** 32463 * mux.js 32464 * 32465 * Copyright (c) 2014 Brightcove 32466 * All rights reserved. 32467 * 32468 * A lightweight readable stream implemention that handles event dispatching. 32469 * Objects that inherit from streams should call init in their constructors. 32470 */ 32471 32472 var Stream$1 = function Stream() { 32473 this.init = function () { 32474 var listeners = {}; 32475 /** 32476 * Add a listener for a specified event type. 32477 * @param type {string} the event name 32478 * @param listener {function} the callback to be invoked when an event of 32479 * the specified type occurs 32480 */ 32481 32482 this.on = function (type, listener) { 32483 if (!listeners[type]) { 32484 listeners[type] = []; 32485 } 32486 32487 listeners[type] = listeners[type].concat(listener); 32488 }; 32489 /** 32490 * Remove a listener for a specified event type. 32491 * @param type {string} the event name 32492 * @param listener {function} a function previously registered for this 32493 * type of event through `on` 32494 */ 32495 32496 32497 this.off = function (type, listener) { 32498 var index; 32499 32500 if (!listeners[type]) { 32501 return false; 32502 } 32503 32504 index = listeners[type].indexOf(listener); 32505 listeners[type] = listeners[type].slice(); 32506 listeners[type].splice(index, 1); 32507 return index > -1; 32508 }; 32509 /** 32510 * Trigger an event of the specified type on this stream. Any additional 32511 * arguments to this function are passed as parameters to event listeners. 32512 * @param type {string} the event name 32513 */ 32514 32515 32516 this.trigger = function (type) { 32517 var callbacks, i, length, args; 32518 callbacks = listeners[type]; 32519 32520 if (!callbacks) { 32521 return; 32522 } // Slicing the arguments on every invocation of this method 32523 // can add a significant amount of overhead. Avoid the 32524 // intermediate object creation for the common case of a 32525 // single callback argument 32526 32527 32528 if (arguments.length === 2) { 32529 length = callbacks.length; 32530 32531 for (i = 0; i < length; ++i) { 32532 callbacks[i].call(this, arguments[1]); 32533 } 32534 } else { 32535 args = []; 32536 i = arguments.length; 32537 32538 for (i = 1; i < arguments.length; ++i) { 32539 args.push(arguments[i]); 32540 } 32541 32542 length = callbacks.length; 32543 32544 for (i = 0; i < length; ++i) { 32545 callbacks[i].apply(this, args); 32546 } 32547 } 32548 }; 32549 /** 32550 * Destroys the stream and cleans up. 32551 */ 32552 32553 32554 this.dispose = function () { 32555 listeners = {}; 32556 }; 32557 }; 32558 }; 32559 /** 32560 * Forwards all `data` events on this stream to the destination stream. The 32561 * destination stream should provide a method `push` to receive the data 32562 * events as they arrive. 32563 * @param destination {stream} the stream that will receive all `data` events 32564 * @param autoFlush {boolean} if false, we will not call `flush` on the destination 32565 * when the current stream emits a 'done' event 32566 * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options 32567 */ 32568 32569 32570 Stream$1.prototype.pipe = function (destination) { 32571 this.on('data', function (data) { 32572 destination.push(data); 32573 }); 32574 this.on('done', function (flushSource) { 32575 destination.flush(flushSource); 32576 }); 32577 return destination; 32578 }; // Default stream functions that are expected to be overridden to perform 32579 // actual work. These are provided by the prototype as a sort of no-op 32580 // implementation so that we don't have to check for their existence in the 32581 // `pipe` function above. 32582 32583 32584 Stream$1.prototype.push = function (data) { 32585 this.trigger('data', data); 32586 }; 32587 32588 Stream$1.prototype.flush = function (flushSource) { 32589 this.trigger('done', flushSource); 32590 }; 32591 32592 var stream = Stream$1; 32593 32594 // Convert an array of nal units into an array of frames with each frame being 32595 // composed of the nal units that make up that frame 32596 // Also keep track of cummulative data about the frame from the nal units such 32597 // as the frame duration, starting pts, etc. 32598 var groupNalsIntoFrames = function groupNalsIntoFrames(nalUnits) { 32599 var i, 32600 currentNal, 32601 currentFrame = [], 32602 frames = []; 32603 currentFrame.byteLength = 0; 32604 32605 for (i = 0; i < nalUnits.length; i++) { 32606 currentNal = nalUnits[i]; // Split on 'aud'-type nal units 32607 32608 if (currentNal.nalUnitType === 'access_unit_delimiter_rbsp') { 32609 // Since the very first nal unit is expected to be an AUD 32610 // only push to the frames array when currentFrame is not empty 32611 if (currentFrame.length) { 32612 currentFrame.duration = currentNal.dts - currentFrame.dts; 32613 frames.push(currentFrame); 32614 } 32615 32616 currentFrame = [currentNal]; 32617 currentFrame.byteLength = currentNal.data.byteLength; 32618 currentFrame.pts = currentNal.pts; 32619 currentFrame.dts = currentNal.dts; 32620 } else { 32621 // Specifically flag key frames for ease of use later 32622 if (currentNal.nalUnitType === 'slice_layer_without_partitioning_rbsp_idr') { 32623 currentFrame.keyFrame = true; 32624 } 32625 32626 currentFrame.duration = currentNal.dts - currentFrame.dts; 32627 currentFrame.byteLength += currentNal.data.byteLength; 32628 currentFrame.push(currentNal); 32629 } 32630 } // For the last frame, use the duration of the previous frame if we 32631 // have nothing better to go on 32632 32633 32634 if (frames.length && (!currentFrame.duration || currentFrame.duration <= 0)) { 32635 currentFrame.duration = frames[frames.length - 1].duration; 32636 } // Push the final frame 32637 32638 32639 frames.push(currentFrame); 32640 return frames; 32641 }; // Convert an array of frames into an array of Gop with each Gop being composed 32642 // of the frames that make up that Gop 32643 // Also keep track of cummulative data about the Gop from the frames such as the 32644 // Gop duration, starting pts, etc. 32645 32646 32647 var groupFramesIntoGops = function groupFramesIntoGops(frames) { 32648 var i, 32649 currentFrame, 32650 currentGop = [], 32651 gops = []; // We must pre-set some of the values on the Gop since we 32652 // keep running totals of these values 32653 32654 currentGop.byteLength = 0; 32655 currentGop.nalCount = 0; 32656 currentGop.duration = 0; 32657 currentGop.pts = frames[0].pts; 32658 currentGop.dts = frames[0].dts; // store some metadata about all the Gops 32659 32660 gops.byteLength = 0; 32661 gops.nalCount = 0; 32662 gops.duration = 0; 32663 gops.pts = frames[0].pts; 32664 gops.dts = frames[0].dts; 32665 32666 for (i = 0; i < frames.length; i++) { 32667 currentFrame = frames[i]; 32668 32669 if (currentFrame.keyFrame) { 32670 // Since the very first frame is expected to be an keyframe
vendor: 2,691 bytes, lines 32671-32758
32671 // only push to the gops array when currentGop is not empty 32672 if (currentGop.length) { 32673 gops.push(currentGop); 32674 gops.byteLength += currentGop.byteLength; 32675 gops.nalCount += currentGop.nalCount; 32676 gops.duration += currentGop.duration; 32677 } 32678 32679 currentGop = [currentFrame]; 32680 currentGop.nalCount = currentFrame.length; 32681 currentGop.byteLength = currentFrame.byteLength; 32682 currentGop.pts = currentFrame.pts; 32683 currentGop.dts = currentFrame.dts; 32684 currentGop.duration = currentFrame.duration; 32685 } else { 32686 currentGop.duration += currentFrame.duration; 32687 currentGop.nalCount += currentFrame.length; 32688 currentGop.byteLength += currentFrame.byteLength; 32689 currentGop.push(currentFrame); 32690 } 32691 } 32692 32693 if (gops.length && currentGop.duration <= 0) { 32694 currentGop.duration = gops[gops.length - 1].duration; 32695 } 32696 32697 gops.byteLength += currentGop.byteLength; 32698 gops.nalCount += currentGop.nalCount; 32699 gops.duration += currentGop.duration; // push the final Gop 32700 32701 gops.push(currentGop); 32702 return gops; 32703 }; 32704 /* 32705 * Search for the first keyframe in the GOPs and throw away all frames 32706 * until that keyframe. Then extend the duration of the pulled keyframe 32707 * and pull the PTS and DTS of the keyframe so that it covers the time 32708 * range of the frames that were disposed. 32709 * 32710 * @param {Array} gops video GOPs 32711 * @returns {Array} modified video GOPs 32712 */ 32713 32714 32715 var extendFirstKeyFrame = function extendFirstKeyFrame(gops) { 32716 var currentGop; 32717 32718 if (!gops[0][0].keyFrame && gops.length > 1) { 32719 // Remove the first GOP 32720 currentGop = gops.shift(); 32721 gops.byteLength -= currentGop.byteLength; 32722 gops.nalCount -= currentGop.nalCount; // Extend the first frame of what is now the 32723 // first gop to cover the time period of the 32724 // frames we just removed 32725 32726 gops[0][0].dts = currentGop.dts; 32727 gops[0][0].pts = currentGop.pts; 32728 gops[0][0].duration += currentGop.duration; 32729 } 32730 32731 return gops; 32732 }; 32733 /** 32734 * Default sample object 32735 * see ISO/IEC 14496-12:2012, section 8.6.4.3 32736 */ 32737 32738 32739 var createDefaultSample = function createDefaultSample() { 32740 return { 32741 size: 0, 32742 flags: { 32743 isLeading: 0, 32744 dependsOn: 1, 32745 isDependedOn: 0, 32746 hasRedundancy: 0, 32747 degradationPriority: 0, 32748 isNonSyncSample: 1 32749 } 32750 }; 32751 }; 32752 /* 32753 * Collates information from a video frame into an object for eventual 32754 * entry into an MP4 sample table. 32755 * 32756 * @param {Object} frame the video frame 32757 * @param {Number} dataOffset the byte offset to position the sample 32758 * @return {Object}
vendor: 22,301 bytes, lines 32758-33460
32758 object containing sample table info for a frame 32759 */ 32760 32761 32762 var sampleForFrame = function sampleForFrame(frame, dataOffset) { 32763 var sample = createDefaultSample(); 32764 sample.dataOffset = dataOffset; 32765 sample.compositionTimeOffset = frame.pts - frame.dts; 32766 sample.duration = frame.duration; 32767 sample.size = 4 * frame.length; // Space for nal unit size 32768 32769 sample.size += frame.byteLength; 32770 32771 if (frame.keyFrame) { 32772 sample.flags.dependsOn = 2; 32773 sample.flags.isNonSyncSample = 0; 32774 } 32775 32776 return sample; 32777 }; // generate the track's sample table from an array of gops 32778 32779 32780 var generateSampleTable = function generateSampleTable(gops, baseDataOffset) { 32781 var h, 32782 i, 32783 sample, 32784 currentGop, 32785 currentFrame, 32786 dataOffset = baseDataOffset || 0, 32787 samples = []; 32788 32789 for (h = 0; h < gops.length; h++) { 32790 currentGop = gops[h]; 32791 32792 for (i = 0; i < currentGop.length; i++) { 32793 currentFrame = currentGop[i]; 32794 sample = sampleForFrame(currentFrame, dataOffset); 32795 dataOffset += sample.size; 32796 samples.push(sample); 32797 } 32798 } 32799 32800 return samples; 32801 }; // generate the track's raw mdat data from an array of gops 32802 32803 32804 var concatenateNalData = function concatenateNalData(gops) { 32805 var h, 32806 i, 32807 j, 32808 currentGop, 32809 currentFrame, 32810 currentNal, 32811 dataOffset = 0, 32812 nalsByteLength = gops.byteLength, 32813 numberOfNals = gops.nalCount, 32814 totalByteLength = nalsByteLength + 4 * numberOfNals, 32815 data = new Uint8Array(totalByteLength), 32816 view = new DataView(data.buffer); // For each Gop.. 32817 32818 for (h = 0; h < gops.length; h++) { 32819 currentGop = gops[h]; // For each Frame.. 32820 32821 for (i = 0; i < currentGop.length; i++) { 32822 currentFrame = currentGop[i]; // For each NAL.. 32823 32824 for (j = 0; j < currentFrame.length; j++) { 32825 currentNal = currentFrame[j]; 32826 view.setUint32(dataOffset, currentNal.data.byteLength); 32827 dataOffset += 4; 32828 data.set(currentNal.data, dataOffset); 32829 dataOffset += currentNal.data.byteLength; 32830 } 32831 } 32832 } 32833 32834 return data; 32835 }; 32836 32837 var frameUtils = { 32838 groupNalsIntoFrames: groupNalsIntoFrames, 32839 groupFramesIntoGops: groupFramesIntoGops, 32840 extendFirstKeyFrame: extendFirstKeyFrame, 32841 generateSampleTable: generateSampleTable, 32842 concatenateNalData: concatenateNalData 32843 }; 32844 32845 var highPrefix = [33, 16, 5, 32, 164, 27]; 32846 var lowPrefix = [33, 65, 108, 84, 1, 2, 4, 8, 168, 2, 4, 8, 17, 191, 252]; 32847 32848 var zeroFill = function zeroFill(count) { 32849 var a = []; 32850 32851 while (count--) { 32852 a.push(0); 32853 } 32854 32855 return a; 32856 }; 32857 32858 var makeTable = function makeTable(metaTable) { 32859 return Object.keys(metaTable).reduce(function (obj, key) { 32860 obj[key] = new Uint8Array(metaTable[key].reduce(function (arr, part) { 32861 return arr.concat(part); 32862 }, [])); 32863 return obj; 32864 }, {}); 32865 }; // Frames-of-silence to use for filling in missing AAC frames 32866 32867 32868 var coneOfSilence = { 32869 96000: [highPrefix, [227, 64], zeroFill(154), [56]], 32870 88200: [highPrefix, [231], zeroFill(170), [56]], 32871 64000: [highPrefix, [248, 192], zeroFill(240), [56]], 32872 48000: [highPrefix, [255, 192], zeroFill(268), [55, 148, 128], zeroFill(54), [112]], 32873 44100: [highPrefix, [255, 192], zeroFill(268), [55, 163, 128], zeroFill(84), [112]], 32874 32000: [highPrefix, [255, 192], zeroFill(268), [55, 234], zeroFill(226), [112]], 32875 24000: [highPrefix, [255, 192], zeroFill(268), [55, 255, 128], zeroFill(268), [111, 112], zeroFill(126), [224]], 32876 16000: [highPrefix, [255, 192], zeroFill(268), [55, 255, 128], zeroFill(268), [111, 255], zeroFill(269), [223, 108], zeroFill(195), [1, 192]], 32877 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]], 32878 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]], 32879 8000: [lowPrefix, zeroFill(268), [3, 121, 16], zeroFill(47), [7]] 32880 }; 32881 var silence = makeTable(coneOfSilence); 32882 32883 var ONE_SECOND_IN_TS = 90000, 32884 // 90kHz clock 32885 secondsToVideoTs, 32886 secondsToAudioTs, 32887 videoTsToSeconds, 32888 audioTsToSeconds, 32889 audioTsToVideoTs, 32890 videoTsToAudioTs; 32891 32892 secondsToVideoTs = function secondsToVideoTs(seconds) { 32893 return seconds * ONE_SECOND_IN_TS; 32894 }; 32895 32896 secondsToAudioTs = function secondsToAudioTs(seconds, sampleRate) { 32897 return seconds * sampleRate; 32898 }; 32899 32900 videoTsToSeconds = function videoTsToSeconds(timestamp) { 32901 return timestamp / ONE_SECOND_IN_TS; 32902 }; 32903 32904 audioTsToSeconds = function audioTsToSeconds(timestamp, sampleRate) { 32905 return timestamp / sampleRate; 32906 }; 32907 32908 audioTsToVideoTs = function audioTsToVideoTs(timestamp, sampleRate) { 32909 return secondsToVideoTs(audioTsToSeconds(timestamp, sampleRate)); 32910 }; 32911 32912 videoTsToAudioTs = function videoTsToAudioTs(timestamp, sampleRate) { 32913 return secondsToAudioTs(videoTsToSeconds(timestamp), sampleRate); 32914 }; 32915 32916 var clock = { 32917 secondsToVideoTs: secondsToVideoTs, 32918 secondsToAudioTs: secondsToAudioTs, 32919 videoTsToSeconds: videoTsToSeconds, 32920 audioTsToSeconds: audioTsToSeconds, 32921 audioTsToVideoTs: audioTsToVideoTs, 32922 videoTsToAudioTs: videoTsToAudioTs 32923 }; 32924 32925 var ONE_SECOND_IN_TS$1 = 90000; // 90kHz clock 32926 32927 /** 32928 * Sum the `byteLength` properties of the data in each AAC frame 32929 */ 32930 32931 var sumFrameByteLengths = function sumFrameByteLengths(array) { 32932 var i, 32933 currentObj, 32934 sum = 0; // sum the byteLength's all each nal unit in the frame 32935 32936 for (i = 0; i < array.length; i++) { 32937 currentObj = array[i]; 32938 sum += currentObj.data.byteLength; 32939 } 32940 32941 return sum; 32942 }; // Possibly pad (prefix) the audio track with silence if appending this track 32943 // would lead to the introduction of a gap in the audio buffer 32944 32945 32946 var prefixWithSilence = function prefixWithSilence(track, frames, audioAppendStartTs, videoBaseMediaDecodeTime) { 32947 var baseMediaDecodeTimeTs, 32948 frameDuration = 0, 32949 audioGapDuration = 0, 32950 audioFillFrameCount = 0, 32951 audioFillDuration = 0, 32952 silentFrame, 32953 i; 32954 32955 if (!frames.length) { 32956 return; 32957 } 32958 32959 baseMediaDecodeTimeTs = clock.audioTsToVideoTs(track.baseMediaDecodeTime, track.samplerate); // determine frame clock duration based on sample rate, round up to avoid overfills 32960 32961 frameDuration = Math.ceil(ONE_SECOND_IN_TS$1 / (track.samplerate / 1024)); 32962 32963 if (audioAppendStartTs && videoBaseMediaDecodeTime) { 32964 // insert the shortest possible amount (audio gap or audio to video gap) 32965 audioGapDuration = baseMediaDecodeTimeTs - Math.max(audioAppendStartTs, videoBaseMediaDecodeTime); // number of full frames in the audio gap 32966 32967 audioFillFrameCount = Math.floor(audioGapDuration / frameDuration); 32968 audioFillDuration = audioFillFrameCount * frameDuration; 32969 } // don't attempt to fill gaps smaller than a single frame or larger 32970 // than a half second 32971 32972 32973 if (audioFillFrameCount < 1 || audioFillDuration > ONE_SECOND_IN_TS$1 / 2) { 32974 return; 32975 } 32976 32977 silentFrame = silence[track.samplerate]; 32978 32979 if (!silentFrame) { 32980 // we don't have a silent frame pregenerated for the sample rate, so use a frame 32981 // from the content instead 32982 silentFrame = frames[0].data; 32983 } 32984 32985 for (i = 0; i < audioFillFrameCount; i++) { 32986 frames.splice(i, 0, { 32987 data: silentFrame 32988 }); 32989 } 32990 32991 track.baseMediaDecodeTime -= Math.floor(clock.videoTsToAudioTs(audioFillDuration, track.samplerate)); 32992 }; // If the audio segment extends before the earliest allowed dts 32993 // value, remove AAC frames until starts at or after the earliest 32994 // allowed DTS so that we don't end up with a negative baseMedia- 32995 // DecodeTime for the audio track 32996 32997 32998 var trimAdtsFramesByEarliestDts = function trimAdtsFramesByEarliestDts(adtsFrames, track, earliestAllowedDts) { 32999 if (track.minSegmentDts >= earliestAllowedDts) { 33000 return adtsFrames; 33001 } // We will need to recalculate the earliest segment Dts 33002 33003 33004 track.minSegmentDts = Infinity; 33005 return adtsFrames.filter(function (currentFrame) { 33006 // If this is an allowed frame, keep it and record it's Dts 33007 if (currentFrame.dts >= earliestAllowedDts) { 33008 track.minSegmentDts = Math.min(track.minSegmentDts, currentFrame.dts); 33009 track.minSegmentPts = track.minSegmentDts; 33010 return true; 33011 } // Otherwise, discard it 33012 33013 33014 return false; 33015 }); 33016 }; // generate the track's raw mdat data from an array of frames 33017 33018 33019 var generateSampleTable$1 = function generateSampleTable(frames) { 33020 var i, 33021 currentFrame, 33022 samples = []; 33023 33024 for (i = 0; i < frames.length; i++) { 33025 currentFrame = frames[i]; 33026 samples.push({ 33027 size: currentFrame.data.byteLength, 33028 duration: 1024 // For AAC audio, all samples contain 1024 samples 33029 33030 }); 33031 } 33032 33033 return samples; 33034 }; // generate the track's sample table from an array of frames 33035 33036 33037 var concatenateFrameData = function concatenateFrameData(frames) { 33038 var i, 33039 currentFrame, 33040 dataOffset = 0, 33041 data = new Uint8Array(sumFrameByteLengths(frames)); 33042 33043 for (i = 0; i < frames.length; i++) { 33044 currentFrame = frames[i]; 33045 data.set(currentFrame.data, dataOffset); 33046 dataOffset += currentFrame.data.byteLength; 33047 } 33048 33049 return data; 33050 }; 33051 33052 var audioFrameUtils = { 33053 prefixWithSilence: prefixWithSilence, 33054 trimAdtsFramesByEarliestDts: trimAdtsFramesByEarliestDts, 33055 generateSampleTable: generateSampleTable$1, 33056 concatenateFrameData: concatenateFrameData 33057 }; 33058 33059 var ONE_SECOND_IN_TS$2 = 90000; // 90kHz clock 33060 33061 /** 33062 * Store information about the start and end of the track and the 33063 * duration for each frame/sample we process in order to calculate 33064 * the baseMediaDecodeTime 33065 */ 33066 33067 var collectDtsInfo = function collectDtsInfo(track, data) { 33068 if (typeof data.pts === 'number') { 33069 if (track.timelineStartInfo.pts === undefined) { 33070 track.timelineStartInfo.pts = data.pts; 33071 } 33072 33073 if (track.minSegmentPts === undefined) { 33074 track.minSegmentPts = data.pts; 33075 } else { 33076 track.minSegmentPts = Math.min(track.minSegmentPts, data.pts); 33077 } 33078 33079 if (track.maxSegmentPts === undefined) { 33080 track.maxSegmentPts = data.pts; 33081 } else { 33082 track.maxSegmentPts = Math.max(track.maxSegmentPts, data.pts); 33083 } 33084 } 33085 33086 if (typeof data.dts === 'number') { 33087 if (track.timelineStartInfo.dts === undefined) { 33088 track.timelineStartInfo.dts = data.dts; 33089 } 33090 33091 if (track.minSegmentDts === undefined) { 33092 track.minSegmentDts = data.dts; 33093 } else { 33094 track.minSegmentDts = Math.min(track.minSegmentDts, data.dts); 33095 } 33096 33097 if (track.maxSegmentDts === undefined) { 33098 track.maxSegmentDts = data.dts; 33099 } else { 33100 track.maxSegmentDts = Math.max(track.maxSegmentDts, data.dts); 33101 } 33102 } 33103 }; 33104 /** 33105 * Clear values used to calculate the baseMediaDecodeTime between 33106 * tracks 33107 */ 33108 33109 33110 var clearDtsInfo = function clearDtsInfo(track) { 33111 delete track.minSegmentDts; 33112 delete track.maxSegmentDts; 33113 delete track.minSegmentPts; 33114 delete track.maxSegmentPts; 33115 }; 33116 /** 33117 * Calculate the track's baseMediaDecodeTime based on the earliest 33118 * DTS the transmuxer has ever seen and the minimum DTS for the 33119 * current track 33120 * @param track {object} track metadata configuration 33121 * @param keepOriginalTimestamps {boolean} If true, keep the timestamps 33122 * in the source; false to adjust the first segment to start at 0. 33123 */ 33124 33125 33126 var calculateTrackBaseMediaDecodeTime = function calculateTrackBaseMediaDecodeTime(track, keepOriginalTimestamps) { 33127 var baseMediaDecodeTime, 33128 scale, 33129 minSegmentDts = track.minSegmentDts; // Optionally adjust the time so the first segment starts at zero. 33130 33131 if (!keepOriginalTimestamps) { 33132 minSegmentDts -= track.timelineStartInfo.dts; 33133 } // track.timelineStartInfo.baseMediaDecodeTime is the location, in time, where 33134 // we want the start of the first segment to be placed 33135 33136 33137 baseMediaDecodeTime = track.timelineStartInfo.baseMediaDecodeTime; // Add to that the distance this segment is from the very first 33138 33139 baseMediaDecodeTime += minSegmentDts; // baseMediaDecodeTime must not become negative 33140 33141 baseMediaDecodeTime = Math.max(0, baseMediaDecodeTime); 33142 33143 if (track.type === 'audio') { 33144 // Audio has a different clock equal to the sampling_rate so we need to 33145 // scale the PTS values into the clock rate of the track 33146 scale = track.samplerate / ONE_SECOND_IN_TS$2; 33147 baseMediaDecodeTime *= scale; 33148 baseMediaDecodeTime = Math.floor(baseMediaDecodeTime); 33149 } 33150 33151 return baseMediaDecodeTime; 33152 }; 33153 33154 var trackDecodeInfo = { 33155 clearDtsInfo: clearDtsInfo, 33156 calculateTrackBaseMediaDecodeTime: calculateTrackBaseMediaDecodeTime, 33157 collectDtsInfo: collectDtsInfo 33158 }; 33159 33160 /** 33161 * mux.js 33162 * 33163 * Copyright (c) 2015 Brightcove 33164 * All rights reserved. 33165 * 33166 * Reads in-band caption information from a video elementary 33167 * stream. Captions must follow the CEA-708 standard for injection 33168 * into an MPEG-2 transport streams. 33169 * @see https://en.wikipedia.org/wiki/CEA-708 33170 * @see https://www.gpo.gov/fdsys/pkg/CFR-2007-title47-vol1/pdf/CFR-2007-title47-vol1-sec15-119.pdf 33171 */ 33172 // payload type field to indicate how they are to be 33173 // interpreted. CEAS-708 caption content is always transmitted with 33174 // payload type 0x04. 33175 33176 var USER_DATA_REGISTERED_ITU_T_T35 = 4, 33177 RBSP_TRAILING_BITS = 128; 33178 /** 33179 * Parse a supplemental enhancement information (SEI) NAL unit. 33180 * Stops parsing once a message of type ITU T T35 has been found. 33181 * 33182 * @param bytes {Uint8Array} the bytes of a SEI NAL unit 33183 * @return {object} the parsed SEI payload 33184 * @see Rec. ITU-T H.264, 7.3.2.3.1 33185 */ 33186 33187 var parseSei = function parseSei(bytes) { 33188 var i = 0, 33189 result = { 33190 payloadType: -1, 33191 payloadSize: 0 33192 }, 33193 payloadType = 0, 33194 payloadSize = 0; // go through the sei_rbsp parsing each each individual sei_message 33195 33196 while (i < bytes.byteLength) { 33197 // stop once we have hit the end of the sei_rbsp 33198 if (bytes[i] === RBSP_TRAILING_BITS) { 33199 break; 33200 } // Parse payload type 33201 33202 33203 while (bytes[i] === 0xFF) { 33204 payloadType += 255; 33205 i++; 33206 } 33207 33208 payloadType += bytes[i++]; // Parse payload size 33209 33210 while (bytes[i] === 0xFF) { 33211 payloadSize += 255; 33212 i++; 33213 } 33214 33215 payloadSize += bytes[i++]; // this sei_message is a 608/708 caption so save it and break 33216 // there can only ever be one caption message in a frame's sei 33217 33218 if (!result.payload && payloadType === USER_DATA_REGISTERED_ITU_T_T35) { 33219 result.payloadType = payloadType; 33220 result.payloadSize = payloadSize; 33221 result.payload = bytes.subarray(i, i + payloadSize); 33222 break; 33223 } // skip the payload and parse the next message 33224 33225 33226 i += payloadSize; 33227 payloadType = 0; 33228 payloadSize = 0; 33229 } 33230 33231 return result; 33232 }; // see ANSI/SCTE 128-1 (2013), section 8.1 33233 33234 33235 var parseUserData = function parseUserData(sei) { 33236 // itu_t_t35_contry_code must be 181 (United States) for 33237 // captions 33238 if (sei.payload[0] !== 181) { 33239 return null; 33240 } // itu_t_t35_provider_code should be 49 (ATSC) for captions 33241 33242 33243 if ((sei.payload[1] << 8 | sei.payload[2]) !== 49) { 33244 return null; 33245 } // the user_identifier should be "GA94" to indicate ATSC1 data 33246 33247 33248 if (String.fromCharCode(sei.payload[3], sei.payload[4], sei.payload[5], sei.payload[6]) !== 'GA94') { 33249 return null; 33250 } // finally, user_data_type_code should be 0x03 for caption data 33251 33252 33253 if (sei.payload[7] !== 0x03) { 33254 return null; 33255 } // return the user_data_type_structure and strip the trailing 33256 // marker bits 33257 33258 33259 return sei.payload.subarray(8, sei.payload.length - 1); 33260 }; // see CEA-708-D, section 4.4 33261 33262 33263 var parseCaptionPackets = function parseCaptionPackets(pts, userData) { 33264 var results = [], 33265 i, 33266 count, 33267 offset, 33268 data; // if this is just filler, return immediately 33269 33270 if (!(userData[0] & 0x40)) { 33271 return results; 33272 } // parse out the cc_data_1 and cc_data_2 fields 33273 33274 33275 count = userData[0] & 0x1f; 33276 33277 for (i = 0; i < count; i++) { 33278 offset = i * 3; 33279 data = { 33280 type: userData[offset + 2] & 0x03, 33281 pts: pts 33282 }; // capture cc data when cc_valid is 1 33283 33284 if (userData[offset + 2] & 0x04) { 33285 data.ccData = userData[offset + 3] << 8 | userData[offset + 4]; 33286 results.push(data); 33287 } 33288 } 33289 33290 return results; 33291 }; 33292 33293 var discardEmulationPreventionBytes = function discardEmulationPreventionBytes(data) { 33294 var length = data.byteLength, 33295 emulationPreventionBytesPositions = [], 33296 i = 1, 33297 newLength, 33298 newData; // Find all `Emulation Prevention Bytes` 33299 33300 while (i < length - 2) { 33301 if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0x03) { 33302 emulationPreventionBytesPositions.push(i + 2); 33303 i += 2; 33304 } else { 33305 i++; 33306 } 33307 } // If no Emulation Prevention Bytes were found just return the original 33308 // array 33309 33310 33311 if (emulationPreventionBytesPositions.length === 0) { 33312 return data; 33313 } // Create a new array to hold the NAL unit data 33314 33315 33316 newLength = length - emulationPreventionBytesPositions.length; 33317 newData = new Uint8Array(newLength); 33318 var sourceIndex = 0; 33319 33320 for (i = 0; i < newLength; sourceIndex++, i++) { 33321 if (sourceIndex === emulationPreventionBytesPositions[0]) { 33322 // Skip this byte 33323 sourceIndex++; // Remove this position index 33324 33325 emulationPreventionBytesPositions.shift(); 33326 } 33327 33328 newData[i] = data[sourceIndex]; 33329 } 33330 33331 return newData; 33332 }; // exports 33333 33334 33335 var captionPacketParser = { 33336 parseSei: parseSei, 33337 parseUserData: parseUserData, 33338 parseCaptionPackets: parseCaptionPackets, 33339 discardEmulationPreventionBytes: discardEmulationPreventionBytes, 33340 USER_DATA_REGISTERED_ITU_T_T35: USER_DATA_REGISTERED_ITU_T_T35 33341 }; 33342 33343 // Link To Transport 33344 // ----------------- 33345 33346 33347 var CaptionStream = function CaptionStream() { 33348 CaptionStream.prototype.init.call(this); 33349 this.captionPackets_ = []; 33350 this.ccStreams_ = [new Cea608Stream(0, 0), // eslint-disable-line no-use-before-define 33351 new Cea608Stream(0, 1), // eslint-disable-line no-use-before-define 33352 new Cea608Stream(1, 0), // eslint-disable-line no-use-before-define 33353 new Cea608Stream(1, 1) // eslint-disable-line no-use-before-define 33354 ]; 33355 this.reset(); // forward data and done events from CCs to this CaptionStream 33356 33357 this.ccStreams_.forEach(function (cc) { 33358 cc.on('data', this.trigger.bind(this, 'data')); 33359 cc.on('done', this.trigger.bind(this, 'done')); 33360 }, this); 33361 }; 33362 33363 CaptionStream.prototype = new stream(); 33364 33365 CaptionStream.prototype.push = function (event) { 33366 var sei, userData, newCaptionPackets; // only examine SEI NALs 33367 33368 if (event.nalUnitType !== 'sei_rbsp') { 33369 return; 33370 } // parse the sei 33371 33372 33373 sei = captionPacketParser.parseSei(event.escapedRBSP); // ignore everything but user_data_registered_itu_t_t35 33374 33375 if (sei.payloadType !== captionPacketParser.USER_DATA_REGISTERED_ITU_T_T35) { 33376 return; 33377 } // parse out the user data payload 33378 33379 33380 userData = captionPacketParser.parseUserData(sei); // ignore unrecognized userData 33381 33382 if (!userData) { 33383 return; 33384 } // Sometimes, the same segment # will be downloaded twice. To stop the 33385 // caption data from being processed twice, we track the latest dts we've 33386 // received and ignore everything with a dts before that. However, since 33387 // data for a specific dts can be split across packets on either side of 33388 // a segment boundary, we need to make sure we *don't* ignore the packets 33389 // from the *next* segment that have dts === this.latestDts_. By constantly 33390 // tracking the number of packets received with dts === this.latestDts_, we 33391 // know how many should be ignored once we start receiving duplicates. 33392 33393 33394 if (event.dts < this.latestDts_) { 33395 // We've started getting older data, so set the flag. 33396 this.ignoreNextEqualDts_ = true; 33397 return; 33398 } else if (event.dts === this.latestDts_ && this.ignoreNextEqualDts_) { 33399 this.numSameDts_--; 33400 33401 if (!this.numSameDts_) { 33402 // We've received the last duplicate packet, time to start processing again 33403 this.ignoreNextEqualDts_ = false; 33404 } 33405 33406 return; 33407 } // parse out CC data packets and save them for later 33408 33409 33410 newCaptionPackets = captionPacketParser.parseCaptionPackets(event.pts, userData); 33411 this.captionPackets_ = this.captionPackets_.concat(newCaptionPackets); 33412 33413 if (this.latestDts_ !== event.dts) { 33414 this.numSameDts_ = 0; 33415 } 33416 33417 this.numSameDts_++; 33418 this.latestDts_ = event.dts; 33419 }; 33420 33421 CaptionStream.prototype.flush = function () { 33422 // make sure we actually parsed captions before proceeding 33423 if (!this.captionPackets_.length) { 33424 this.ccStreams_.forEach(function (cc) { 33425 cc.flush(); 33426 }, this); 33427 return; 33428 } // In Chrome, the Array#sort function is not stable so add a 33429 // presortIndex that we can use to ensure we get a stable-sort 33430 33431 33432 this.captionPackets_.forEach(function (elem, idx) { 33433 elem.presortIndex = idx; 33434 }); // sort caption byte-pairs based on their PTS values 33435 33436 this.captionPackets_.sort(function (a, b) { 33437 if (a.pts === b.pts) { 33438 return a.presortIndex - b.presortIndex; 33439 } 33440 33441 return a.pts - b.pts; 33442 }); 33443 this.captionPackets_.forEach(function (packet) { 33444 if (packet.type < 2) { 33445 // Dispatch packet to the right Cea608Stream 33446 this.dispatchCea608Packet(packet); 33447 } // this is where an 'else' would go for a dispatching packets 33448 // to a theoretical Cea708Stream that handles SERVICEn data 33449 33450 }, this); 33451 this.captionPackets_.length = 0; 33452 this.ccStreams_.forEach(function (cc) { 33453 cc.flush(); 33454 }, this); 33455 return; 33456 }; 33457 33458 CaptionStream.prototype.reset = function () { 33459 this.latestDts_ = null; 33460 this.ignoreNextEqualDts_ = false;
vendor: 1,105 bytes, lines 33461-33494
33461 this.numSameDts_ = 0; 33462 this.activeCea608Channel_ = [null, null]; 33463 this.ccStreams_.forEach(function (ccStream) { 33464 ccStream.reset(); 33465 }); 33466 }; 33467 33468 CaptionStream.prototype.dispatchCea608Packet = function (packet) { 33469 // NOTE: packet.type is the CEA608 field 33470 if (this.setsChannel1Active(packet)) { 33471 this.activeCea608Channel_[packet.type] = 0; 33472 } else if (this.setsChannel2Active(packet)) { 33473 this.activeCea608Channel_[packet.type] = 1; 33474 } 33475 33476 if (this.activeCea608Channel_[packet.type] === null) { 33477 // If we haven't received anything to set the active channel, discard the 33478 // data; we don't want jumbled captions 33479 return; 33480 } 33481 33482 this.ccStreams_[(packet.type << 1) + this.activeCea608Channel_[packet.type]].push(packet); 33483 }; 33484 33485 CaptionStream.prototype.setsChannel1Active = function (packet) { 33486 return (packet.ccData & 0x7800) === 0x1000; 33487 }; 33488 33489 CaptionStream.prototype.setsChannel2Active = function (packet) { 33490 return (packet.ccData & 0x7800) === 0x1800; 33491 }; // ---------------------- 33492 // Session to Application 33493 // ---------------------- 33494
vendor: 12,175 bytes, lines 33494-33911
33494 // This hash maps non-ASCII, special, and extended character codes to their 33495 // proper Unicode equivalent. The first keys that are only a single byte 33496 // are the non-standard ASCII characters, which simply map the CEA608 byte 33497 // to the standard ASCII/Unicode. The two-byte keys that follow are the CEA608 33498 // character codes, but have their MSB bitmasked with 0x03 so that a lookup 33499 // can be performed regardless of the field and data channel on which the 33500 // character code was received. 33501 33502 33503 var CHARACTER_TRANSLATION = { 33504 0x2a: 0xe1, 33505 // á 33506 0x5c: 0xe9, 33507 // é 33508 0x5e: 0xed, 33509 // à 33510 0x5f: 0xf3, 33511 // ó 33512 0x60: 0xfa, 33513 // ú 33514 0x7b: 0xe7, 33515 // ç 33516 0x7c: 0xf7, 33517 // ÷ 33518 0x7d: 0xd1, 33519 // à 33520 0x7e: 0xf1, 33521 // ñ 33522 0x7f: 0x2588, 33523 // â 33524 0x0130: 0xae, 33525 // ® 33526 0x0131: 0xb0, 33527 // ° 33528 0x0132: 0xbd, 33529 // ½ 33530 0x0133: 0xbf, 33531 // ¿ 33532 0x0134: 0x2122, 33533 // ⢠33534 0x0135: 0xa2, 33535 // ¢ 33536 0x0136: 0xa3, 33537 // £ 33538 0x0137: 0x266a, 33539 // ⪠33540 0x0138: 0xe0, 33541 // à 33542 0x0139: 0xa0, 33543 // 33544 0x013a: 0xe8, 33545 // è 33546 0x013b: 0xe2, 33547 // â 33548 0x013c: 0xea, 33549 // ê 33550 0x013d: 0xee, 33551 // î 33552 0x013e: 0xf4, 33553 // ô 33554 0x013f: 0xfb, 33555 // û 33556 0x0220: 0xc1, 33557 // à 33558 0x0221: 0xc9, 33559 // à 33560 0x0222: 0xd3, 33561 // à 33562 0x0223: 0xda, 33563 // à 33564 0x0224: 0xdc, 33565 // à 33566 0x0225: 0xfc, 33567 // ü 33568 0x0226: 0x2018, 33569 // â 33570 0x0227: 0xa1, 33571 // ¡ 33572 0x0228: 0x2a, 33573 // * 33574 0x0229: 0x27, 33575 // ' 33576 0x022a: 0x2014, 33577 // â 33578 0x022b: 0xa9, 33579 // © 33580 0x022c: 0x2120, 33581 // â 33582 0x022d: 0x2022, 33583 // ⢠33584 0x022e: 0x201c, 33585 // â 33586 0x022f: 0x201d, 33587 // â 33588 0x0230: 0xc0, 33589 // à 33590 0x0231: 0xc2, 33591 // à 33592 0x0232: 0xc7, 33593 // à 33594 0x0233: 0xc8, 33595 // à 33596 0x0234: 0xca, 33597 // à 33598 0x0235: 0xcb, 33599 // à 33600 0x0236: 0xeb, 33601 // ë 33602 0x0237: 0xce, 33603 // à 33604 0x0238: 0xcf, 33605 // à 33606 0x0239: 0xef, 33607 // ï 33608 0x023a: 0xd4, 33609 // à 33610 0x023b: 0xd9, 33611 // à 33612 0x023c: 0xf9, 33613 // ù 33614 0x023d: 0xdb, 33615 // à 33616 0x023e: 0xab, 33617 // « 33618 0x023f: 0xbb, 33619 // » 33620 0x0320: 0xc3, 33621 // à 33622 0x0321: 0xe3, 33623 // ã 33624 0x0322: 0xcd, 33625 // à 33626 0x0323: 0xcc, 33627 // à 33628 0x0324: 0xec, 33629 // ì 33630 0x0325: 0xd2, 33631 // à 33632 0x0326: 0xf2, 33633 // ò 33634 0x0327: 0xd5, 33635 // à 33636 0x0328: 0xf5, 33637 // õ 33638 0x0329: 0x7b, 33639 // { 33640 0x032a: 0x7d, 33641 // } 33642 0x032b: 0x5c, 33643 // \ 33644 0x032c: 0x5e, 33645 // ^ 33646 0x032d: 0x5f, 33647 // _ 33648 0x032e: 0x7c, 33649 // | 33650 0x032f: 0x7e, 33651 // ~ 33652 0x0330: 0xc4, 33653 // à 33654 0x0331: 0xe4, 33655 // ä 33656 0x0332: 0xd6, 33657 // à 33658 0x0333: 0xf6, 33659 // ö 33660 0x0334: 0xdf, 33661 // à 33662 0x0335: 0xa5, 33663 // Â¥ 33664 0x0336: 0xa4, 33665 // ¤ 33666 0x0337: 0x2502, 33667 // â 33668 0x0338: 0xc5, 33669 // à 33670 0x0339: 0xe5, 33671 // Ã¥ 33672 0x033a: 0xd8, 33673 // à 33674 0x033b: 0xf8, 33675 // ø 33676 0x033c: 0x250c, 33677 // â 33678 0x033d: 0x2510, 33679 // â 33680 0x033e: 0x2514, 33681 // â 33682 0x033f: 0x2518 // â 33683 33684 }; 33685 33686 var getCharFromCode = function getCharFromCode(code) { 33687 if (code === null) { 33688 return ''; 33689 } 33690 33691 code = CHARACTER_TRANSLATION[code] || code; 33692 return String.fromCharCode(code); 33693 }; // the index of the last row in a CEA-608 display buffer 33694 33695 33696 var BOTTOM_ROW = 14; // This array is used for mapping PACs -> row #, since there's no way of 33697 // getting it through bit logic. 33698 33699 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 33700 // cells. The "bottom" row is the last element in the outer array. 33701 33702 var createDisplayBuffer = function createDisplayBuffer() { 33703 var result = [], 33704 i = BOTTOM_ROW + 1; 33705 33706 while (i--) { 33707 result.push(''); 33708 } 33709 33710 return result; 33711 }; 33712 33713 var Cea608Stream = function Cea608Stream(field, dataChannel) { 33714 Cea608Stream.prototype.init.call(this); 33715 this.field_ = field || 0; 33716 this.dataChannel_ = dataChannel || 0; 33717 this.name_ = 'CC' + ((this.field_ << 1 | this.dataChannel_) + 1); 33718 this.setConstants(); 33719 this.reset(); 33720 33721 this.push = function (packet) { 33722 var data, swap, char0, char1, text; // remove the parity bits 33723 33724 data = packet.ccData & 0x7f7f; // ignore duplicate control codes; the spec demands they're sent twice 33725 33726 if (data === this.lastControlCode_) { 33727 this.lastControlCode_ = null; 33728 return; 33729 } // Store control codes 33730 33731 33732 if ((data & 0xf000) === 0x1000) { 33733 this.lastControlCode_ = data; 33734 } else if (data !== this.PADDING_) { 33735 this.lastControlCode_ = null; 33736 } 33737 33738 char0 = data >>> 8; 33739 char1 = data & 0xff; 33740 33741 if (data === this.PADDING_) { 33742 return; 33743 } else if (data === this.RESUME_CAPTION_LOADING_) { 33744 this.mode_ = 'popOn'; 33745 } else if (data === this.END_OF_CAPTION_) { 33746 // If an EOC is received while in paint-on mode, the displayed caption 33747 // text should be swapped to non-displayed memory as if it was a pop-on 33748 // caption. Because of that, we should explicitly switch back to pop-on 33749 // mode 33750 this.mode_ = 'popOn'; 33751 this.clearFormatting(packet.pts); // if a caption was being displayed, it's gone now 33752 33753 this.flushDisplayed(packet.pts); // flip memory 33754 33755 swap = this.displayed_; 33756 this.displayed_ = this.nonDisplayed_; 33757 this.nonDisplayed_ = swap; // start measuring the time to display the caption 33758 33759 this.startPts_ = packet.pts; 33760 } else if (data === this.ROLL_UP_2_ROWS_) { 33761 this.rollUpRows_ = 2; 33762 this.setRollUp(packet.pts); 33763 } else if (data === this.ROLL_UP_3_ROWS_) { 33764 this.rollUpRows_ = 3; 33765 this.setRollUp(packet.pts); 33766 } else if (data === this.ROLL_UP_4_ROWS_) { 33767 this.rollUpRows_ = 4; 33768 this.setRollUp(packet.pts); 33769 } else if (data === this.CARRIAGE_RETURN_) { 33770 this.clearFormatting(packet.pts); 33771 this.flushDisplayed(packet.pts); 33772 this.shiftRowsUp_(); 33773 this.startPts_ = packet.pts; 33774 } else if (data === this.BACKSPACE_) { 33775 if (this.mode_ === 'popOn') { 33776 this.nonDisplayed_[this.row_] = this.nonDisplayed_[this.row_].slice(0, -1); 33777 } else { 33778 this.displayed_[this.row_] = this.displayed_[this.row_].slice(0, -1); 33779 } 33780 } else if (data === this.ERASE_DISPLAYED_MEMORY_) { 33781 this.flushDisplayed(packet.pts); 33782 this.displayed_ = createDisplayBuffer(); 33783 } else if (data === this.ERASE_NON_DISPLAYED_MEMORY_) { 33784 this.nonDisplayed_ = createDisplayBuffer(); 33785 } else if (data === this.RESUME_DIRECT_CAPTIONING_) { 33786 if (this.mode_ !== 'paintOn') { 33787 // NOTE: This should be removed when proper caption positioning is 33788 // implemented 33789 this.flushDisplayed(packet.pts); 33790 this.displayed_ = createDisplayBuffer(); 33791 } 33792 33793 this.mode_ = 'paintOn'; 33794 this.startPts_ = packet.pts; // Append special characters to caption text 33795 } else if (this.isSpecialCharacter(char0, char1)) { 33796 // Bitmask char0 so that we can apply character transformations 33797 // regardless of field and data channel. 33798 // Then byte-shift to the left and OR with char1 so we can pass the 33799 // entire character code to `getCharFromCode`. 33800 char0 = (char0 & 0x03) << 8; 33801 text = getCharFromCode(char0 | char1); 33802 this[this.mode_](packet.pts, text); 33803 this.column_++; // Append extended characters to caption text 33804 } else if (this.isExtCharacter(char0, char1)) { 33805 // Extended characters always follow their "non-extended" equivalents. 33806 // IE if a "è" is desired, you'll always receive "eè"; non-compliant 33807 // decoders are supposed to drop the "è", while compliant decoders 33808 // backspace the "e" and insert "è". 33809 // Delete the previous character 33810 if (this.mode_ === 'popOn') { 33811 this.nonDisplayed_[this.row_] = this.nonDisplayed_[this.row_].slice(0, -1); 33812 } else { 33813 this.displayed_[this.row_] = this.displayed_[this.row_].slice(0, -1); 33814 } // Bitmask char0 so that we can apply character transformations 33815 // regardless of field and data channel. 33816 // Then byte-shift to the left and OR with char1 so we can pass the 33817 // entire character code to `getCharFromCode`. 33818 33819 33820 char0 = (char0 & 0x03) << 8; 33821 text = getCharFromCode(char0 | char1); 33822 this[this.mode_](packet.pts, text); 33823 this.column_++; // Process mid-row codes 33824 } else if (this.isMidRowCode(char0, char1)) { 33825 // Attributes are not additive, so clear all formatting 33826 this.clearFormatting(packet.pts); // According to the standard, mid-row codes 33827 // should be replaced with spaces, so add one now 33828 33829 this[this.mode_](packet.pts, ' '); 33830 this.column_++; 33831 33832 if ((char1 & 0xe) === 0xe) { 33833 this.addFormatting(packet.pts, ['i']); 33834 } 33835 33836 if ((char1 & 0x1) === 0x1) { 33837 this.addFormatting(packet.pts, ['u']); 33838 } // Detect offset control codes and adjust cursor 33839 33840 } else if (this.isOffsetControlCode(char0, char1)) { 33841 // Cursor position is set by indent PAC (see below) in 4-column 33842 // increments, with an additional offset code of 1-3 to reach any 33843 // of the 32 columns specified by CEA-608. So all we need to do 33844 // here is increment the column cursor by the given offset. 33845 this.column_ += char1 & 0x03; // Detect PACs (Preamble Address Codes) 33846 } else if (this.isPAC(char0, char1)) { 33847 // There's no logic for PAC -> row mapping, so we have to just 33848 // find the row code in an array and use its index :( 33849 var row = ROWS.indexOf(data & 0x1f20); // Configure the caption window if we're in roll-up mode 33850 33851 if (this.mode_ === 'rollUp') { 33852 // This implies that the base row is incorrectly set. 33853 // As per the recommendation in CEA-608(Base Row Implementation), defer to the number 33854 // of roll-up rows set. 33855 if (row - this.rollUpRows_ + 1 < 0) { 33856 row = this.rollUpRows_ - 1; 33857 } 33858 33859 this.setRollUp(packet.pts, row); 33860 } 33861 33862 if (row !== this.row_) { 33863 // formatting is only persistent for current row 33864 this.clearFormatting(packet.pts); 33865 this.row_ = row; 33866 } // All PACs can apply underline, so detect and apply 33867 // (All odd-numbered second bytes set underline) 33868 33869 33870 if (char1 & 0x1 && this.formatting_.indexOf('u') === -1) { 33871 this.addFormatting(packet.pts, ['u']); 33872 } 33873 33874 if ((data & 0x10) === 0x10) { 33875 // We've got an indent level code. Each successive even number 33876 // increments the column cursor by 4, so we can get the desired 33877 // column position by bit-shifting to the right (to get n/2) 33878 // and multiplying by 4. 33879 this.column_ = ((data & 0xe) >> 1) * 4; 33880 } 33881 33882 if (this.isColorPAC(char1)) { 33883 // it's a color code, though we only support white, which 33884 // can be either normal or italicized. white italics can be 33885 // either 0x4e or 0x6e depending on the row, so we just 33886 // bitwise-and with 0xe to see if italics should be turned on 33887 if ((char1 & 0xe) === 0xe) { 33888 this.addFormatting(packet.pts, ['i']); 33889 } 33890 } // We have a normal character in char0, and possibly one in char1 33891 33892 } else if (this.isNormalChar(char0)) { 33893 if (char1 === 0x00) { 33894 char1 = null; 33895 } 33896 33897 text = getCharFromCode(char0); 33898 text += getCharFromCode(char1); 33899 this[this.mode_](packet.pts, text); 33900 this.column_ += text.length; 33901 } // finish data processing 33902 33903 }; 33904 }; 33905 33906 Cea608Stream.prototype = new stream(); // Trigger a cue point that captures the current state of the 33907 // display buffer 33908 33909 Cea608Stream.prototype.flushDisplayed = function (pts) { 33910 var content = this.displayed_ // remove spaces from the start and end of the string 33911 .map(function (row
vendor: 8,441 bytes, lines 33911-34154
33911) { 33912 try { 33913 return row.trim(); 33914 } catch (e) { 33915 // Ordinarily, this shouldn't happen. However, caption 33916 // parsing errors should not throw exceptions and 33917 // break playback. 33918 // eslint-disable-next-line no-console 33919 console.error('Skipping malformed caption.'); 33920 return ''; 33921 } 33922 }) // combine all text rows to display in one cue 33923 .join('\n') // and remove blank rows from the start and end, but not the middle 33924 .replace(/^\n+|\n+$/g, ''); 33925 33926 if (content.length) { 33927 this.trigger('data', { 33928 startPts: this.startPts_, 33929 endPts: pts, 33930 text: content, 33931 stream: this.name_ 33932 }); 33933 } 33934 }; 33935 /** 33936 * Zero out the data, used for startup and on seek 33937 */ 33938 33939 33940 Cea608Stream.prototype.reset = function () { 33941 this.mode_ = 'popOn'; // When in roll-up mode, the index of the last row that will 33942 // actually display captions. If a caption is shifted to a row 33943 // with a lower index than this, it is cleared from the display 33944 // buffer 33945 33946 this.topRow_ = 0; 33947 this.startPts_ = 0; 33948 this.displayed_ = createDisplayBuffer(); 33949 this.nonDisplayed_ = createDisplayBuffer(); 33950 this.lastControlCode_ = null; // Track row and column for proper line-breaking and spacing 33951 33952 this.column_ = 0; 33953 this.row_ = BOTTOM_ROW; 33954 this.rollUpRows_ = 2; // This variable holds currently-applied formatting 33955 33956 this.formatting_ = []; 33957 }; 33958 /** 33959 * Sets up control code and related constants for this instance 33960 */ 33961 33962 33963 Cea608Stream.prototype.setConstants = function () { 33964 // The following attributes have these uses: 33965 // ext_ : char0 for mid-row codes, and the base for extended 33966 // chars (ext_+0, ext_+1, and ext_+2 are char0s for 33967 // extended codes) 33968 // control_: char0 for control codes, except byte-shifted to the 33969 // left so that we can do this.control_ | CONTROL_CODE 33970 // offset_: char0 for tab offset codes 33971 // 33972 // It's also worth noting that control codes, and _only_ control codes, 33973 // differ between field 1 and field2. Field 2 control codes are always 33974 // their field 1 value plus 1. That's why there's the "| field" on the 33975 // control value. 33976 if (this.dataChannel_ === 0) { 33977 this.BASE_ = 0x10; 33978 this.EXT_ = 0x11; 33979 this.CONTROL_ = (0x14 | this.field_) << 8; 33980 this.OFFSET_ = 0x17; 33981 } else if (this.dataChannel_ === 1) { 33982 this.BASE_ = 0x18; 33983 this.EXT_ = 0x19; 33984 this.CONTROL_ = (0x1c | this.field_) << 8; 33985 this.OFFSET_ = 0x1f; 33986 } // Constants for the LSByte command codes recognized by Cea608Stream. This 33987 // list is not exhaustive. For a more comprehensive listing and semantics see 33988 // http://www.gpo.gov/fdsys/pkg/CFR-2010-title47-vol1/pdf/CFR-2010-title47-vol1-sec15-119.pdf 33989 // Padding 33990 33991 33992 this.PADDING_ = 0x0000; // Pop-on Mode 33993 33994 this.RESUME_CAPTION_LOADING_ = this.CONTROL_ | 0x20; 33995 this.END_OF_CAPTION_ = this.CONTROL_ | 0x2f; // Roll-up Mode 33996 33997 this.ROLL_UP_2_ROWS_ = this.CONTROL_ | 0x25; 33998 this.ROLL_UP_3_ROWS_ = this.CONTROL_ | 0x26; 33999 this.ROLL_UP_4_ROWS_ = this.CONTROL_ | 0x27; 34000 this.CARRIAGE_RETURN_ = this.CONTROL_ | 0x2d; // paint-on mode 34001 34002 this.RESUME_DIRECT_CAPTIONING_ = this.CONTROL_ | 0x29; // Erasure 34003 34004 this.BACKSPACE_ = this.CONTROL_ | 0x21; 34005 this.ERASE_DISPLAYED_MEMORY_ = this.CONTROL_ | 0x2c; 34006 this.ERASE_NON_DISPLAYED_MEMORY_ = this.CONTROL_ | 0x2e; 34007 }; 34008 /** 34009 * Detects if the 2-byte packet data is a special character 34010 * 34011 * Special characters have a second byte in the range 0x30 to 0x3f, 34012 * with the first byte being 0x11 (for data channel 1) or 0x19 (for 34013 * data channel 2). 34014 * 34015 * @param {Integer} char0 The first byte 34016 * @param {Integer} char1 The second byte 34017 * @return {Boolean} Whether the 2 bytes are an special character 34018 */ 34019 34020 34021 Cea608Stream.prototype.isSpecialCharacter = function (char0, char1) { 34022 return char0 === this.EXT_ && char1 >= 0x30 && char1 <= 0x3f; 34023 }; 34024 /** 34025 * Detects if the 2-byte packet data is an extended character 34026 * 34027 * Extended characters have a second byte in the range 0x20 to 0x3f, 34028 * with the first byte being 0x12 or 0x13 (for data channel 1) or 34029 * 0x1a or 0x1b (for data channel 2). 34030 * 34031 * @param {Integer} char0 The first byte 34032 * @param {Integer} char1 The second byte 34033 * @return {Boolean} Whether the 2 bytes are an extended character 34034 */ 34035 34036 34037 Cea608Stream.prototype.isExtCharacter = function (char0, char1) { 34038 return (char0 === this.EXT_ + 1 || char0 === this.EXT_ + 2) && char1 >= 0x20 && char1 <= 0x3f; 34039 }; 34040 /** 34041 * Detects if the 2-byte packet is a mid-row code 34042 * 34043 * Mid-row codes have a second byte in the range 0x20 to 0x2f, with 34044 * the first byte being 0x11 (for data channel 1) or 0x19 (for data 34045 * channel 2). 34046 * 34047 * @param {Integer} char0 The first byte 34048 * @param {Integer} char1 The second byte 34049 * @return {Boolean} Whether the 2 bytes are a mid-row code 34050 */ 34051 34052 34053 Cea608Stream.prototype.isMidRowCode = function (char0, char1) { 34054 return char0 === this.EXT_ && char1 >= 0x20 && char1 <= 0x2f; 34055 }; 34056 /** 34057 * Detects if the 2-byte packet is an offset control code 34058 * 34059 * Offset control codes have a second byte in the range 0x21 to 0x23, 34060 * with the first byte being 0x17 (for data channel 1) or 0x1f (for 34061 * data channel 2). 34062 * 34063 * @param {Integer} char0 The first byte 34064 * @param {Integer} char1 The second byte 34065 * @return {Boolean} Whether the 2 bytes are an offset control code 34066 */ 34067 34068 34069 Cea608Stream.prototype.isOffsetControlCode = function (char0, char1) { 34070 return char0 === this.OFFSET_ && char1 >= 0x21 && char1 <= 0x23; 34071 }; 34072 /** 34073 * Detects if the 2-byte packet is a Preamble Address Code 34074 * 34075 * PACs have a first byte in the range 0x10 to 0x17 (for data channel 1) 34076 * or 0x18 to 0x1f (for data channel 2), with the second byte in the 34077 * range 0x40 to 0x7f. 34078 * 34079 * @param {Integer} char0 The first byte 34080 * @param {Integer} char1 The second byte 34081 * @return {Boolean} Whether the 2 bytes are a PAC 34082 */ 34083 34084 34085 Cea608Stream.prototype.isPAC = function (char0, char1) { 34086 return char0 >= this.BASE_ && char0 < this.BASE_ + 8 && char1 >= 0x40 && char1 <= 0x7f; 34087 }; 34088 /** 34089 * Detects if a packet's second byte is in the range of a PAC color code 34090 * 34091 * PAC color codes have the second byte be in the range 0x40 to 0x4f, or 34092 * 0x60 to 0x6f. 34093 * 34094 * @param {Integer} char1 The second byte 34095 * @return {Boolean} Whether the byte is a color PAC 34096 */ 34097 34098 34099 Cea608Stream.prototype.isColorPAC = function (char1) { 34100 return char1 >= 0x40 && char1 <= 0x4f || char1 >= 0x60 && char1 <= 0x7f; 34101 }; 34102 /** 34103 * Detects if a single byte is in the range of a normal character 34104 * 34105 * Normal text bytes are in the range 0x20 to 0x7f. 34106 * 34107 * @param {Integer} char The byte 34108 * @return {Boolean} Whether the byte is a normal character 34109 */ 34110 34111 34112 Cea608Stream.prototype.isNormalChar = function (char) { 34113 return char >= 0x20 && char <= 0x7f; 34114 }; 34115 /** 34116 * Configures roll-up 34117 * 34118 * @param {Integer} pts Current PTS 34119 * @param {Integer} newBaseRow Used by PACs to slide the current window to 34120 * a new position 34121 */ 34122 34123 34124 Cea608Stream.prototype.setRollUp = function (pts, newBaseRow) { 34125 // Reset the base row to the bottom row when switching modes 34126 if (this.mode_ !== 'rollUp') { 34127 this.row_ = BOTTOM_ROW; 34128 this.mode_ = 'rollUp'; // Spec says to wipe memories when switching to roll-up 34129 34130 this.flushDisplayed(pts); 34131 this.nonDisplayed_ = createDisplayBuffer(); 34132 this.displayed_ = createDisplayBuffer(); 34133 } 34134 34135 if (newBaseRow !== undefined && newBaseRow !== this.row_) { 34136 // move currently displayed captions (up or down) to the new base row 34137 for (var i = 0; i < this.rollUpRows_; i++) { 34138 this.displayed_[newBaseRow - i] = this.displayed_[this.row_ - i]; 34139 this.displayed_[this.row_ - i] = ''; 34140 } 34141 } 34142 34143 if (newBaseRow === undefined) { 34144 newBaseRow = this.row_; 34145 } 34146 34147 this.topRow_ = newBaseRow - this.rollUpRows_ + 1; 34148 }; // Adds the opening HTML tag for the passed character to the caption text, 34149 // and keeps track of it for later closing 34150 34151 34152 Cea608Stream.prototype.addFormatting = function (pts, format) { 34153 this.formatting_ = this.formatting_.concat(format); 34154 var text = format.reduce(function (text, format) {
vendor: 40,049 bytes, lines 34155-35389
34155 return text + '<' + format + '>'; 34156 }, ''); 34157 this[this.mode_](pts, text); 34158 }; // Adds HTML closing tags for current formatting to caption text and 34159 // clears remembered formatting 34160 34161 34162 Cea608Stream.prototype.clearFormatting = function (pts) { 34163 if (!this.formatting_.length) { 34164 return; 34165 } 34166 34167 var text = this.formatting_.reverse().reduce(function (text, format) { 34168 return text + '</' + format + '>'; 34169 }, ''); 34170 this.formatting_ = []; 34171 this[this.mode_](pts, text); 34172 }; // Mode Implementations 34173 34174 34175 Cea608Stream.prototype.popOn = function (pts, text) { 34176 var baseRow = this.nonDisplayed_[this.row_]; // buffer characters 34177 34178 baseRow += text; 34179 this.nonDisplayed_[this.row_] = baseRow; 34180 }; 34181 34182 Cea608Stream.prototype.rollUp = function (pts, text) { 34183 var baseRow = this.displayed_[this.row_]; 34184 baseRow += text; 34185 this.displayed_[this.row_] = baseRow; 34186 }; 34187 34188 Cea608Stream.prototype.shiftRowsUp_ = function () { 34189 var i; // clear out inactive rows 34190 34191 for (i = 0; i < this.topRow_; i++) { 34192 this.displayed_[i] = ''; 34193 } 34194 34195 for (i = this.row_ + 1; i < BOTTOM_ROW + 1; i++) { 34196 this.displayed_[i] = ''; 34197 } // shift displayed rows up 34198 34199 34200 for (i = this.topRow_; i < this.row_; i++) { 34201 this.displayed_[i] = this.displayed_[i + 1]; 34202 } // clear out the bottom row 34203 34204 34205 this.displayed_[this.row_] = ''; 34206 }; 34207 34208 Cea608Stream.prototype.paintOn = function (pts, text) { 34209 var baseRow = this.displayed_[this.row_]; 34210 baseRow += text; 34211 this.displayed_[this.row_] = baseRow; 34212 }; // exports 34213 34214 34215 var captionStream = { 34216 CaptionStream: CaptionStream, 34217 Cea608Stream: Cea608Stream 34218 }; 34219 34220 var streamTypes = { 34221 H264_STREAM_TYPE: 0x1B, 34222 ADTS_STREAM_TYPE: 0x0F, 34223 METADATA_STREAM_TYPE: 0x15 34224 }; 34225 34226 var MAX_TS = 8589934592; 34227 var RO_THRESH = 4294967296; 34228 34229 var handleRollover = function handleRollover(value, reference) { 34230 var direction = 1; 34231 34232 if (value > reference) { 34233 // If the current timestamp value is greater than our reference timestamp and we detect a 34234 // timestamp rollover, this means the roll over is happening in the opposite direction. 34235 // Example scenario: Enter a long stream/video just after a rollover occurred. The reference 34236 // point will be set to a small number, e.g. 1. The user then seeks backwards over the 34237 // rollover point. In loading this segment, the timestamp values will be very large, 34238 // e.g. 2^33 - 1. Since this comes before the data we loaded previously, we want to adjust 34239 // the time stamp to be `value - 2^33`. 34240 direction = -1; 34241 } // Note: A seek forwards or back that is greater than the RO_THRESH (2^32, ~13 hours) will 34242 // cause an incorrect adjustment. 34243 34244 34245 while (Math.abs(reference - value) > RO_THRESH) { 34246 value += direction * MAX_TS; 34247 } 34248 34249 return value; 34250 }; 34251 34252 var TimestampRolloverStream = function TimestampRolloverStream(type) { 34253 var lastDTS, referenceDTS; 34254 TimestampRolloverStream.prototype.init.call(this); 34255 this.type_ = type; 34256 34257 this.push = function (data) { 34258 if (data.type !== this.type_) { 34259 return; 34260 } 34261 34262 if (referenceDTS === undefined) { 34263 referenceDTS = data.dts; 34264 } 34265 34266 data.dts = handleRollover(data.dts, referenceDTS); 34267 data.pts = handleRollover(data.pts, referenceDTS); 34268 lastDTS = data.dts; 34269 this.trigger('data', data); 34270 }; 34271 34272 this.flush = function () { 34273 referenceDTS = lastDTS; 34274 this.trigger('done'); 34275 }; 34276 34277 this.discontinuity = function () { 34278 referenceDTS = void 0; 34279 lastDTS = void 0; 34280 }; 34281 }; 34282 34283 TimestampRolloverStream.prototype = new stream(); 34284 var timestampRolloverStream = { 34285 TimestampRolloverStream: TimestampRolloverStream, 34286 handleRollover: handleRollover 34287 }; 34288 34289 var percentEncode = function percentEncode(bytes, start, end) { 34290 var i, 34291 result = ''; 34292 34293 for (i = start; i < end; i++) { 34294 result += '%' + ('00' + bytes[i].toString(16)).slice(-2); 34295 } 34296 34297 return result; 34298 }, 34299 // return the string representation of the specified byte range, 34300 // interpreted as UTf-8. 34301 parseUtf8 = function parseUtf8(bytes, start, end) { 34302 return decodeURIComponent(percentEncode(bytes, start, end)); 34303 }, 34304 // return the string representation of the specified byte range, 34305 // interpreted as ISO-8859-1. 34306 parseIso88591 = function parseIso88591(bytes, start, end) { 34307 return unescape(percentEncode(bytes, start, end)); // jshint ignore:line 34308 }, 34309 parseSyncSafeInteger = function parseSyncSafeInteger(data) { 34310 return data[0] << 21 | data[1] << 14 | data[2] << 7 | data[3]; 34311 }, 34312 tagParsers = { 34313 TXXX: function TXXX(tag) { 34314 var i; 34315 34316 if (tag.data[0] !== 3) { 34317 // ignore frames with unrecognized character encodings 34318 return; 34319 } 34320 34321 for (i = 1; i < tag.data.length; i++) { 34322 if (tag.data[i] === 0) { 34323 // parse the text fields 34324 tag.description = parseUtf8(tag.data, 1, i); // do not include the null terminator in the tag value 34325 34326 tag.value = parseUtf8(tag.data, i + 1, tag.data.length).replace(/\0*$/, ''); 34327 break; 34328 } 34329 } 34330 34331 tag.data = tag.value; 34332 }, 34333 WXXX: function WXXX(tag) { 34334 var i; 34335 34336 if (tag.data[0] !== 3) { 34337 // ignore frames with unrecognized character encodings 34338 return; 34339 } 34340 34341 for (i = 1; i < tag.data.length; i++) { 34342 if (tag.data[i] === 0) { 34343 // parse the description and URL fields 34344 tag.description = parseUtf8(tag.data, 1, i); 34345 tag.url = parseUtf8(tag.data, i + 1, tag.data.length); 34346 break; 34347 } 34348 } 34349 }, 34350 PRIV: function PRIV(tag) { 34351 var i; 34352 34353 for (i = 0; i < tag.data.length; i++) { 34354 if (tag.data[i] === 0) { 34355 // parse the description and URL fields 34356 tag.owner = parseIso88591(tag.data, 0, i); 34357 break; 34358 } 34359 } 34360 34361 tag.privateData = tag.data.subarray(i + 1); 34362 tag.data = tag.privateData; 34363 } 34364 }, 34365 _MetadataStream; 34366 34367 _MetadataStream = function MetadataStream(options) { 34368 var settings = { 34369 debug: !!(options && options.debug), 34370 // the bytes of the program-level descriptor field in MP2T 34371 // see ISO/IEC 13818-1:2013 (E), section 2.6 "Program and 34372 // program element descriptors" 34373 descriptor: options && options.descriptor 34374 }, 34375 // the total size in bytes of the ID3 tag being parsed 34376 tagSize = 0, 34377 // tag data that is not complete enough to be parsed 34378 buffer = [], 34379 // the total number of bytes currently in the buffer 34380 bufferSize = 0, 34381 i; 34382 34383 _MetadataStream.prototype.init.call(this); // calculate the text track in-band metadata track dispatch type 34384 // https://html.spec.whatwg.org/multipage/embedded-content.html#steps-to-expose-a-media-resource-specific-text-track 34385 34386 34387 this.dispatchType = streamTypes.METADATA_STREAM_TYPE.toString(16); 34388 34389 if (settings.descriptor) { 34390 for (i = 0; i < settings.descriptor.length; i++) { 34391 this.dispatchType += ('00' + settings.descriptor[i].toString(16)).slice(-2); 34392 } 34393 } 34394 34395 this.push = function (chunk) { 34396 var tag, frameStart, frameSize, frame, i, frameHeader; 34397 34398 if (chunk.type !== 'timed-metadata') { 34399 return; 34400 } // if data_alignment_indicator is set in the PES header, 34401 // we must have the start of a new ID3 tag. Assume anything 34402 // remaining in the buffer was malformed and throw it out 34403 34404 34405 if (chunk.dataAlignmentIndicator) { 34406 bufferSize = 0; 34407 buffer.length = 0; 34408 } // ignore events that don't look like ID3 data 34409 34410 34411 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))) { 34412 if (settings.debug) { 34413 // eslint-disable-next-line no-console 34414 console.log('Skipping unrecognized metadata packet'); 34415 } 34416 34417 return; 34418 } // add this chunk to the data we've collected so far 34419 34420 34421 buffer.push(chunk); 34422 bufferSize += chunk.data.byteLength; // grab the size of the entire frame from the ID3 header 34423 34424 if (buffer.length === 1) { 34425 // the frame size is transmitted as a 28-bit integer in the 34426 // last four bytes of the ID3 header. 34427 // The most significant bit of each byte is dropped and the 34428 // results concatenated to recover the actual value. 34429 tagSize = parseSyncSafeInteger(chunk.data.subarray(6, 10)); // ID3 reports the tag size excluding the header but it's more 34430 // convenient for our comparisons to include it 34431 34432 tagSize += 10; 34433 } // if the entire frame has not arrived, wait for more data 34434 34435 34436 if (bufferSize < tagSize) { 34437 return; 34438 } // collect the entire frame so it can be parsed 34439 34440 34441 tag = { 34442 data: new Uint8Array(tagSize), 34443 frames: [], 34444 pts: buffer[0].pts, 34445 dts: buffer[0].dts 34446 }; 34447 34448 for (i = 0; i < tagSize;) { 34449 tag.data.set(buffer[0].data.subarray(0, tagSize - i), i); 34450 i += buffer[0].data.byteLength; 34451 bufferSize -= buffer[0].data.byteLength; 34452 buffer.shift(); 34453 } // find the start of the first frame and the end of the tag 34454 34455 34456 frameStart = 10; 34457 34458 if (tag.data[5] & 0x40) { 34459 // advance the frame start past the extended header 34460 frameStart += 4; // header size field 34461 34462 frameStart += parseSyncSafeInteger(tag.data.subarray(10, 14)); // clip any padding off the end 34463 34464 tagSize -= parseSyncSafeInteger(tag.data.subarray(16, 20)); 34465 } // parse one or more ID3 frames 34466 // http://id3.org/id3v2.3.0#ID3v2_frame_overview 34467 34468 34469 do { 34470 // determine the number of bytes in this frame 34471 frameSize = parseSyncSafeInteger(tag.data.subarray(frameStart + 4, frameStart + 8)); 34472 34473 if (frameSize < 1) { 34474 // eslint-disable-next-line no-console 34475 return console.log('Malformed ID3 frame encountered. Skipping metadata parsing.'); 34476 } 34477 34478 frameHeader = String.fromCharCode(tag.data[frameStart], tag.data[frameStart + 1], tag.data[frameStart + 2], tag.data[frameStart + 3]); 34479 frame = { 34480 id: frameHeader, 34481 data: tag.data.subarray(frameStart + 10, frameStart + frameSize + 10) 34482 }; 34483 frame.key = frame.id; 34484 34485 if (tagParsers[frame.id]) { 34486 tagParsers[frame.id](frame); // handle the special PRIV frame used to indicate the start 34487 // time for raw AAC data 34488 34489 if (frame.owner === 'com.apple.streaming.transportStreamTimestamp') { 34490 var d = frame.data, 34491 size = (d[3] & 0x01) << 30 | d[4] << 22 | d[5] << 14 | d[6] << 6 | d[7] >>> 2; 34492 size *= 4; 34493 size += d[7] & 0x03; 34494 frame.timeStamp = size; // in raw AAC, all subsequent data will be timestamped based 34495 // on the value of this frame 34496 // we couldn't have known the appropriate pts and dts before 34497 // parsing this ID3 tag so set those values now 34498 34499 if (tag.pts === undefined && tag.dts === undefined) { 34500 tag.pts = frame.timeStamp; 34501 tag.dts = frame.timeStamp; 34502 } 34503 34504 this.trigger('timestamp', frame); 34505 } 34506 } 34507 34508 tag.frames.push(frame); 34509 frameStart += 10; // advance past the frame header 34510 34511 frameStart += frameSize; // advance past the frame body 34512 } while (frameStart < tagSize); 34513 34514 this.trigger('data', tag); 34515 }; 34516 }; 34517 34518 _MetadataStream.prototype = new stream(); 34519 var metadataStream = _MetadataStream; 34520 34521 var TimestampRolloverStream$1 = timestampRolloverStream.TimestampRolloverStream; // object types 34522 34523 var _TransportPacketStream, _TransportParseStream, _ElementaryStream; // constants 34524 34525 34526 var MP2T_PACKET_LENGTH = 188, 34527 // bytes 34528 SYNC_BYTE = 0x47; 34529 /** 34530 * Splits an incoming stream of binary data into MPEG-2 Transport 34531 * Stream packets. 34532 */ 34533 34534 _TransportPacketStream = function TransportPacketStream() { 34535 var buffer = new Uint8Array(MP2T_PACKET_LENGTH), 34536 bytesInBuffer = 0; 34537 34538 _TransportPacketStream.prototype.init.call(this); // Deliver new bytes to the stream. 34539 34540 /** 34541 * Split a stream of data into M2TS packets 34542 **/ 34543 34544 34545 this.push = function (bytes) { 34546 var startIndex = 0, 34547 endIndex = MP2T_PACKET_LENGTH, 34548 everything; // If there are bytes remaining from the last segment, prepend them to the 34549 // bytes that were pushed in 34550 34551 if (bytesInBuffer) { 34552 everything = new Uint8Array(bytes.byteLength + bytesInBuffer); 34553 everything.set(buffer.subarray(0, bytesInBuffer)); 34554 everything.set(bytes, bytesInBuffer); 34555 bytesInBuffer = 0; 34556 } else { 34557 everything = bytes; 34558 } // While we have enough data for a packet 34559 34560 34561 while (endIndex < everything.byteLength) { 34562 // Look for a pair of start and end sync bytes in the data.. 34563 if (everything[startIndex] === SYNC_BYTE && everything[endIndex] === SYNC_BYTE) { 34564 // We found a packet so emit it and jump one whole packet forward in 34565 // the stream 34566 this.trigger('data', everything.subarray(startIndex, endIndex)); 34567 startIndex += MP2T_PACKET_LENGTH; 34568 endIndex += MP2T_PACKET_LENGTH; 34569 continue; 34570 } // If we get here, we have somehow become de-synchronized and we need to step 34571 // forward one byte at a time until we find a pair of sync bytes that denote 34572 // a packet 34573 34574 34575 startIndex++; 34576 endIndex++; 34577 } // If there was some data left over at the end of the segment that couldn't 34578 // possibly be a whole packet, keep it because it might be the start of a packet 34579 // that continues in the next segment 34580 34581 34582 if (startIndex < everything.byteLength) { 34583 buffer.set(everything.subarray(startIndex), 0); 34584 bytesInBuffer = everything.byteLength - startIndex; 34585 } 34586 }; 34587 /** 34588 * Passes identified M2TS packets to the TransportParseStream to be parsed 34589 **/ 34590 34591 34592 this.flush = function () { 34593 // If the buffer contains a whole packet when we are being flushed, emit it 34594 // and empty the buffer. Otherwise hold onto the data because it may be 34595 // important for decoding the next segment 34596 if (bytesInBuffer === MP2T_PACKET_LENGTH && buffer[0] === SYNC_BYTE) { 34597 this.trigger('data', buffer); 34598 bytesInBuffer = 0; 34599 } 34600 34601 this.trigger('done'); 34602 }; 34603 }; 34604 34605 _TransportPacketStream.prototype = new stream(); 34606 /** 34607 * Accepts an MP2T TransportPacketStream and emits data events with parsed 34608 * forms of the individual transport stream packets. 34609 */ 34610 34611 _TransportParseStream = function TransportParseStream() { 34612 var parsePsi, parsePat, parsePmt, self; 34613 34614 _TransportParseStream.prototype.init.call(this); 34615 34616 self = this; 34617 this.packetsWaitingForPmt = []; 34618 this.programMapTable = undefined; 34619 34620 parsePsi = function parsePsi(payload, psi) { 34621 var offset = 0; // PSI packets may be split into multiple sections and those 34622 // sections may be split into multiple packets. If a PSI 34623 // section starts in this packet, the payload_unit_start_indicator 34624 // will be true and the first byte of the payload will indicate 34625 // the offset from the current position to the start of the 34626 // section. 34627 34628 if (psi.payloadUnitStartIndicator) { 34629 offset += payload[offset] + 1; 34630 } 34631 34632 if (psi.type === 'pat') { 34633 parsePat(payload.subarray(offset), psi); 34634 } else { 34635 parsePmt(payload.subarray(offset), psi); 34636 } 34637 }; 34638 34639 parsePat = function parsePat(payload, pat) { 34640 pat.section_number = payload[7]; // eslint-disable-line camelcase 34641 34642 pat.last_section_number = payload[8]; // eslint-disable-line camelcase 34643 // skip the PSI header and parse the first PMT entry 34644 34645 self.pmtPid = (payload[10] & 0x1F) << 8 | payload[11]; 34646 pat.pmtPid = self.pmtPid; 34647 }; 34648 /** 34649 * Parse out the relevant fields of a Program Map Table (PMT). 34650 * @param payload {Uint8Array} the PMT-specific portion of an MP2T 34651 * packet. The first byte in this array should be the table_id 34652 * field. 34653 * @param pmt {object} the object that should be decorated with 34654 * fields parsed from the PMT. 34655 */ 34656 34657 34658 parsePmt = function parsePmt(payload, pmt) { 34659 var sectionLength, tableEnd, programInfoLength, offset; // PMTs can be sent ahead of the time when they should actually 34660 // take effect. We don't believe this should ever be the case 34661 // for HLS but we'll ignore "forward" PMT declarations if we see 34662 // them. Future PMT declarations have the current_next_indicator 34663 // set to zero. 34664 34665 if (!(payload[5] & 0x01)) { 34666 return; 34667 } // overwrite any existing program map table 34668 34669 34670 self.programMapTable = { 34671 video: null, 34672 audio: null, 34673 'timed-metadata': {} 34674 }; // the mapping table ends at the end of the current section 34675 34676 sectionLength = (payload[1] & 0x0f) << 8 | payload[2]; 34677 tableEnd = 3 + sectionLength - 4; // to determine where the table is, we have to figure out how 34678 // long the program info descriptors are 34679 34680 programInfoLength = (payload[10] & 0x0f) << 8 | payload[11]; // advance the offset to the first entry in the mapping table 34681 34682 offset = 12 + programInfoLength; 34683 34684 while (offset < tableEnd) { 34685 var streamType = payload[offset]; 34686 var pid = (payload[offset + 1] & 0x1F) << 8 | payload[offset + 2]; // only map a single elementary_pid for audio and video stream types 34687 // TODO: should this be done for metadata too? for now maintain behavior of 34688 // multiple metadata streams 34689 34690 if (streamType === streamTypes.H264_STREAM_TYPE && self.programMapTable.video === null) { 34691 self.programMapTable.video = pid; 34692 } else if (streamType === streamTypes.ADTS_STREAM_TYPE && self.programMapTable.audio === null) { 34693 self.programMapTable.audio = pid; 34694 } else if (streamType === streamTypes.METADATA_STREAM_TYPE) { 34695 // map pid to stream type for metadata streams 34696 self.programMapTable['timed-metadata'][pid] = streamType; 34697 } // move to the next table entry 34698 // skip past the elementary stream descriptors, if present 34699 34700 34701 offset += ((payload[offset + 3] & 0x0F) << 8 | payload[offset + 4]) + 5; 34702 } // record the map on the packet as well 34703 34704 34705 pmt.programMapTable = self.programMapTable; 34706 }; 34707 /** 34708 * Deliver a new MP2T packet to the next stream in the pipeline. 34709 */ 34710 34711 34712 this.push = function (packet) { 34713 var result = {}, 34714 offset = 4; 34715 result.payloadUnitStartIndicator = !!(packet[1] & 0x40); // pid is a 13-bit field starting at the last bit of packet[1] 34716 34717 result.pid = packet[1] & 0x1f; 34718 result.pid <<= 8; 34719 result.pid |= packet[2]; // if an adaption field is present, its length is specified by the 34720 // fifth byte of the TS packet header. The adaptation field is 34721 // used to add stuffing to PES packets that don't fill a complete 34722 // TS packet, and to specify some forms of timing and control data 34723 // that we do not currently use. 34724 34725 if ((packet[3] & 0x30) >>> 4 > 0x01) { 34726 offset += packet[offset] + 1; 34727 } // parse the rest of the packet based on the type 34728 34729 34730 if (result.pid === 0) { 34731 result.type = 'pat'; 34732 parsePsi(packet.subarray(offset), result); 34733 this.trigger('data', result); 34734 } else if (result.pid === this.pmtPid) { 34735 result.type = 'pmt'; 34736 parsePsi(packet.subarray(offset), result); 34737 this.trigger('data', result); // if there are any packets waiting for a PMT to be found, process them now 34738 34739 while (this.packetsWaitingForPmt.length) { 34740 this.processPes_.apply(this, this.packetsWaitingForPmt.shift()); 34741 } 34742 } else if (this.programMapTable === undefined) { 34743 // When we have not seen a PMT yet, defer further processing of 34744 // PES packets until one has been parsed 34745 this.packetsWaitingForPmt.push([packet, offset, result]); 34746 } else { 34747 this.processPes_(packet, offset, result); 34748 } 34749 }; 34750 34751 this.processPes_ = function (packet, offset, result) { 34752 // set the appropriate stream type 34753 if (result.pid === this.programMapTable.video) { 34754 result.streamType = streamTypes.H264_STREAM_TYPE; 34755 } else if (result.pid === this.programMapTable.audio) { 34756 result.streamType = streamTypes.ADTS_STREAM_TYPE; 34757 } else { 34758 // if not video or audio, it is timed-metadata or unknown 34759 // if unknown, streamType will be undefined 34760 result.streamType = this.programMapTable['timed-metadata'][result.pid]; 34761 } 34762 34763 result.type = 'pes'; 34764 result.data = packet.subarray(offset); 34765 this.trigger('data', result); 34766 }; 34767 }; 34768 34769 _TransportParseStream.prototype = new stream(); 34770 _TransportParseStream.STREAM_TYPES = { 34771 h264: 0x1b, 34772 adts: 0x0f 34773 }; 34774 /** 34775 * Reconsistutes program elementary stream (PES) packets from parsed 34776 * transport stream packets. That is, if you pipe an 34777 * mp2t.TransportParseStream into a mp2t.ElementaryStream, the output 34778 * events will be events which capture the bytes for individual PES 34779 * packets plus relevant metadata that has been extracted from the 34780 * container. 34781 */ 34782 34783 _ElementaryStream = function ElementaryStream() { 34784 var self = this, 34785 // PES packet fragments 34786 video = { 34787 data: [], 34788 size: 0 34789 }, 34790 audio = { 34791 data: [], 34792 size: 0 34793 }, 34794 timedMetadata = { 34795 data: [], 34796 size: 0 34797 }, 34798 parsePes = function parsePes(payload, pes) { 34799 var ptsDtsFlags; // get the packet length, this will be 0 for video 34800 34801 pes.packetLength = 6 + (payload[4] << 8 | payload[5]); // find out if this packets starts a new keyframe 34802 34803 pes.dataAlignmentIndicator = (payload[6] & 0x04) !== 0; // PES packets may be annotated with a PTS value, or a PTS value 34804 // and a DTS value. Determine what combination of values is 34805 // available to work with. 34806 34807 ptsDtsFlags = payload[7]; // PTS and DTS are normally stored as a 33-bit number. Javascript 34808 // performs all bitwise operations on 32-bit integers but javascript 34809 // supports a much greater range (52-bits) of integer using standard 34810 // mathematical operations. 34811 // We construct a 31-bit value using bitwise operators over the 31 34812 // most significant bits and then multiply by 4 (equal to a left-shift 34813 // of 2) before we add the final 2 least significant bits of the 34814 // timestamp (equal to an OR.) 34815 34816 if (ptsDtsFlags & 0xC0) { 34817 // the PTS and DTS are not written out directly. For information 34818 // on how they are encoded, see 34819 // http://dvd.sourceforge.net/dvdinfo/pes-hdr.html 34820 pes.pts = (payload[9] & 0x0E) << 27 | (payload[10] & 0xFF) << 20 | (payload[11] & 0xFE) << 12 | (payload[12] & 0xFF) << 5 | (payload[13] & 0xFE) >>> 3; 34821 pes.pts *= 4; // Left shift by 2 34822 34823 pes.pts += (payload[13] & 0x06) >>> 1; // OR by the two LSBs 34824 34825 pes.dts = pes.pts; 34826 34827 if (ptsDtsFlags & 0x40) { 34828 pes.dts = (payload[14] & 0x0E) << 27 | (payload[15] & 0xFF) << 20 | (payload[16] & 0xFE) << 12 | (payload[17] & 0xFF) << 5 | (payload[18] & 0xFE) >>> 3; 34829 pes.dts *= 4; // Left shift by 2 34830 34831 pes.dts += (payload[18] & 0x06) >>> 1; // OR by the two LSBs 34832 } 34833 } // the data section starts immediately after the PES header. 34834 // pes_header_data_length specifies the number of header bytes 34835 // that follow the last byte of the field. 34836 34837 34838 pes.data = payload.subarray(9 + payload[8]); 34839 }, 34840 34841 /** 34842 * Pass completely parsed PES packets to the next stream in the pipeline 34843 **/ 34844 flushStream = function flushStream(stream$$1, type, forceFlush) { 34845 var packetData = new Uint8Array(stream$$1.size), 34846 event = { 34847 type: type 34848 }, 34849 i = 0, 34850 offset = 0, 34851 packetFlushable = false, 34852 fragment; // do nothing if there is not enough buffered data for a complete 34853 // PES header 34854 34855 if (!stream$$1.data.length || stream$$1.size < 9) { 34856 return; 34857 } 34858 34859 event.trackId = stream$$1.data[0].pid; // reassemble the packet 34860 34861 for (i = 0; i < stream$$1.data.length; i++) { 34862 fragment = stream$$1.data[i]; 34863 packetData.set(fragment.data, offset); 34864 offset += fragment.data.byteLength; 34865 } // parse assembled packet's PES header 34866 34867 34868 parsePes(packetData, event); // non-video PES packets MUST have a non-zero PES_packet_length 34869 // check that there is enough stream data to fill the packet 34870 34871 packetFlushable = type === 'video' || event.packetLength <= stream$$1.size; // flush pending packets if the conditions are right 34872 34873 if (forceFlush || packetFlushable) { 34874 stream$$1.size = 0; 34875 stream$$1.data.length = 0; 34876 } // only emit packets that are complete. this is to avoid assembling 34877 // incomplete PES packets due to poor segmentation 34878 34879 34880 if (packetFlushable) { 34881 self.trigger('data', event); 34882 } 34883 }; 34884 34885 _ElementaryStream.prototype.init.call(this); 34886 /** 34887 * Identifies M2TS packet types and parses PES packets using metadata 34888 * parsed from the PMT 34889 **/ 34890 34891 34892 this.push = function (data) { 34893 ({ 34894 pat: function pat() {// we have to wait for the PMT to arrive as well before we 34895 // have any meaningful metadata 34896 }, 34897 pes: function pes() { 34898 var stream$$1, streamType; 34899 34900 switch (data.streamType) { 34901 case streamTypes.H264_STREAM_TYPE: 34902 case streamTypes.H264_STREAM_TYPE: 34903 stream$$1 = video; 34904 streamType = 'video'; 34905 break; 34906 34907 case streamTypes.ADTS_STREAM_TYPE: 34908 stream$$1 = audio; 34909 streamType = 'audio'; 34910 break; 34911 34912 case streamTypes.METADATA_STREAM_TYPE: 34913 stream$$1 = timedMetadata; 34914 streamType = 'timed-metadata'; 34915 break; 34916 34917 default: 34918 // ignore unknown stream types 34919 return; 34920 } // if a new packet is starting, we can flush the completed 34921 // packet 34922 34923 34924 if (data.payloadUnitStartIndicator) { 34925 flushStream(stream$$1, streamType, true); 34926 } // buffer this fragment until we are sure we've received the 34927 // complete payload 34928 34929 34930 stream$$1.data.push(data); 34931 stream$$1.size += data.data.byteLength; 34932 }, 34933 pmt: function pmt() { 34934 var event = { 34935 type: 'metadata', 34936 tracks: [] 34937 }, 34938 programMapTable = data.programMapTable; // translate audio and video streams to tracks 34939 34940 if (programMapTable.video !== null) { 34941 event.tracks.push({ 34942 timelineStartInfo: { 34943 baseMediaDecodeTime: 0 34944 }, 34945 id: +programMapTable.video, 34946 codec: 'avc', 34947 type: 'video' 34948 }); 34949 } 34950 34951 if (programMapTable.audio !== null) { 34952 event.tracks.push({ 34953 timelineStartInfo: { 34954 baseMediaDecodeTime: 0 34955 }, 34956 id: +programMapTable.audio, 34957 codec: 'adts', 34958 type: 'audio' 34959 }); 34960 } 34961 34962 self.trigger('data', event); 34963 } 34964 })[data.type](); 34965 }; 34966 /** 34967 * Flush any remaining input. Video PES packets may be of variable 34968 * length. Normally, the start of a new video packet can trigger the 34969 * finalization of the previous packet. That is not possible if no 34970 * more video is forthcoming, however. In that case, some other 34971 * mechanism (like the end of the file) has to be employed. When it is 34972 * clear that no additional data is forthcoming, calling this method 34973 * will flush the buffered packets. 34974 */ 34975 34976 34977 this.flush = function () { 34978 // !!THIS ORDER IS IMPORTANT!! 34979 // video first then audio 34980 flushStream(video, 'video'); 34981 flushStream(audio, 'audio'); 34982 flushStream(timedMetadata, 'timed-metadata'); 34983 this.trigger('done'); 34984 }; 34985 }; 34986 34987 _ElementaryStream.prototype = new stream(); 34988 var m2ts = { 34989 PAT_PID: 0x0000, 34990 MP2T_PACKET_LENGTH: MP2T_PACKET_LENGTH, 34991 TransportPacketStream: _TransportPacketStream, 34992 TransportParseStream: _TransportParseStream, 34993 ElementaryStream: _ElementaryStream, 34994 TimestampRolloverStream: TimestampRolloverStream$1, 34995 CaptionStream: captionStream.CaptionStream, 34996 Cea608Stream: captionStream.Cea608Stream, 34997 MetadataStream: metadataStream 34998 }; 34999 35000 for (var type$1 in streamTypes) { 35001 if (streamTypes.hasOwnProperty(type$1)) { 35002 m2ts[type$1] = streamTypes[type$1]; 35003 } 35004 } 35005 35006 var m2ts_1 = m2ts; 35007 35008 var _AdtsStream; 35009 35010 var ADTS_SAMPLING_FREQUENCIES = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350]; 35011 /* 35012 * Accepts a ElementaryStream and emits data events with parsed 35013 * AAC Audio Frames of the individual packets. Input audio in ADTS 35014 * format is unpacked and re-emitted as AAC frames. 35015 * 35016 * @see http://wiki.multimedia.cx/index.php?title=ADTS 35017 * @see http://wiki.multimedia.cx/?title=Understanding_AAC 35018 */ 35019 35020 _AdtsStream = function AdtsStream() { 35021 var buffer; 35022 35023 _AdtsStream.prototype.init.call(this); 35024 35025 this.push = function (packet) { 35026 var i = 0, 35027 frameNum = 0, 35028 frameLength, 35029 protectionSkipBytes, 35030 frameEnd, 35031 oldBuffer, 35032 sampleCount, 35033 adtsFrameDuration; 35034 35035 if (packet.type !== 'audio') { 35036 // ignore non-audio data 35037 return; 35038 } // Prepend any data in the buffer to the input data so that we can parse 35039 // aac frames the cross a PES packet boundary 35040 35041 35042 if (buffer) { 35043 oldBuffer = buffer; 35044 buffer = new Uint8Array(oldBuffer.byteLength + packet.data.byteLength); 35045 buffer.set(oldBuffer); 35046 buffer.set(packet.data, oldBuffer.byteLength); 35047 } else { 35048 buffer = packet.data; 35049 } // unpack any ADTS frames which have been fully received 35050 // for details on the ADTS header, see http://wiki.multimedia.cx/index.php?title=ADTS 35051 35052 35053 while (i + 5 < buffer.length) { 35054 // Loook for the start of an ADTS header.. 35055 if (buffer[i] !== 0xFF || (buffer[i + 1] & 0xF6) !== 0xF0) { 35056 // If a valid header was not found, jump one forward and attempt to 35057 // find a valid ADTS header starting at the next byte 35058 i++; 35059 continue; 35060 } // The protection skip bit tells us if we have 2 bytes of CRC data at the 35061 // end of the ADTS header 35062 35063 35064 protectionSkipBytes = (~buffer[i + 1] & 0x01) * 2; // Frame length is a 13 bit integer starting 16 bits from the 35065 // end of the sync sequence 35066 35067 frameLength = (buffer[i + 3] & 0x03) << 11 | buffer[i + 4] << 3 | (buffer[i + 5] & 0xe0) >> 5; 35068 sampleCount = ((buffer[i + 6] & 0x03) + 1) * 1024; 35069 adtsFrameDuration = sampleCount * 90000 / ADTS_SAMPLING_FREQUENCIES[(buffer[i + 2] & 0x3c) >>> 2]; 35070 frameEnd = i + frameLength; // If we don't have enough data to actually finish this ADTS frame, return 35071 // and wait for more data 35072 35073 if (buffer.byteLength < frameEnd) { 35074 return; 35075 } // Otherwise, deliver the complete AAC frame 35076 35077 35078 this.trigger('data', { 35079 pts: packet.pts + frameNum * adtsFrameDuration, 35080 dts: packet.dts + frameNum * adtsFrameDuration, 35081 sampleCount: sampleCount, 35082 audioobjecttype: (buffer[i + 2] >>> 6 & 0x03) + 1, 35083 channelcount: (buffer[i + 2] & 1) << 2 | (buffer[i + 3] & 0xc0) >>> 6, 35084 samplerate: ADTS_SAMPLING_FREQUENCIES[(buffer[i + 2] & 0x3c) >>> 2], 35085 samplingfrequencyindex: (buffer[i + 2] & 0x3c) >>> 2, 35086 // assume ISO/IEC 14496-12 AudioSampleEntry default of 16 35087 samplesize: 16, 35088 data: buffer.subarray(i + 7 + protectionSkipBytes, frameEnd) 35089 }); // If the buffer is empty, clear it and return 35090 35091 if (buffer.byteLength === frameEnd) { 35092 buffer = undefined; 35093 return; 35094 } 35095 35096 frameNum++; // Remove the finished frame from the buffer and start the process again 35097 35098 buffer = buffer.subarray(frameEnd); 35099 } 35100 }; 35101 35102 this.flush = function () { 35103 this.trigger('done'); 35104 }; 35105 }; 35106 35107 _AdtsStream.prototype = new stream(); 35108 var adts = _AdtsStream; 35109 35110 var ExpGolomb; 35111 /** 35112 * Parser for exponential Golomb codes, a variable-bitwidth number encoding 35113 * scheme used by h264. 35114 */ 35115 35116 ExpGolomb = function ExpGolomb(workingData) { 35117 var // the number of bytes left to examine in workingData 35118 workingBytesAvailable = workingData.byteLength, 35119 // the current word being examined 35120 workingWord = 0, 35121 // :uint 35122 // the number of bits left to examine in the current word 35123 workingBitsAvailable = 0; // :uint; 35124 // ():uint 35125 35126 this.length = function () { 35127 return 8 * workingBytesAvailable; 35128 }; // ():uint 35129 35130 35131 this.bitsAvailable = function () { 35132 return 8 * workingBytesAvailable + workingBitsAvailable; 35133 }; // ():void 35134 35135 35136 this.loadWord = function () { 35137 var position = workingData.byteLength - workingBytesAvailable, 35138 workingBytes = new Uint8Array(4), 35139 availableBytes = Math.min(4, workingBytesAvailable); 35140 35141 if (availableBytes === 0) { 35142 throw new Error('no bytes available'); 35143 } 35144 35145 workingBytes.set(workingData.subarray(position, position + availableBytes)); 35146 workingWord = new DataView(workingBytes.buffer).getUint32(0); // track the amount of workingData that has been processed 35147 35148 workingBitsAvailable = availableBytes * 8; 35149 workingBytesAvailable -= availableBytes; 35150 }; // (count:int):void 35151 35152 35153 this.skipBits = function (count) { 35154 var skipBytes; // :int 35155 35156 if (workingBitsAvailable > count) { 35157 workingWord <<= count; 35158 workingBitsAvailable -= count; 35159 } else { 35160 count -= workingBitsAvailable; 35161 skipBytes = Math.floor(count / 8); 35162 count -= skipBytes * 8; 35163 workingBytesAvailable -= skipBytes; 35164 this.loadWord(); 35165 workingWord <<= count; 35166 workingBitsAvailable -= count; 35167 } 35168 }; // (size:int):uint 35169 35170 35171 this.readBits = function (size) { 35172 var bits = Math.min(workingBitsAvailable, size), 35173 // :uint 35174 valu = workingWord >>> 32 - bits; // :uint 35175 // if size > 31, handle error 35176 35177 workingBitsAvailable -= bits; 35178 35179 if (workingBitsAvailable > 0) { 35180 workingWord <<= bits; 35181 } else if (workingBytesAvailable > 0) { 35182 this.loadWord(); 35183 } 35184 35185 bits = size - bits; 35186 35187 if (bits > 0) { 35188 return valu << bits | this.readBits(bits); 35189 } 35190 35191 return valu; 35192 }; // ():uint 35193 35194 35195 this.skipLeadingZeros = function () { 35196 var leadingZeroCount; // :uint 35197 35198 for (leadingZeroCount = 0; leadingZeroCount < workingBitsAvailable; ++leadingZeroCount) { 35199 if ((workingWord & 0x80000000 >>> leadingZeroCount) !== 0) { 35200 // the first bit of working word is 1 35201 workingWord <<= leadingZeroCount; 35202 workingBitsAvailable -= leadingZeroCount; 35203 return leadingZeroCount; 35204 } 35205 } // we exhausted workingWord and still have not found a 1 35206 35207 35208 this.loadWord(); 35209 return leadingZeroCount + this.skipLeadingZeros(); 35210 }; // ():void 35211 35212 35213 this.skipUnsignedExpGolomb = function () { 35214 this.skipBits(1 + this.skipLeadingZeros()); 35215 }; // ():void 35216 35217 35218 this.skipExpGolomb = function () { 35219 this.skipBits(1 + this.skipLeadingZeros()); 35220 }; // ():uint 35221 35222 35223 this.readUnsignedExpGolomb = function () { 35224 var clz = this.skipLeadingZeros(); // :uint 35225 35226 return this.readBits(clz + 1) - 1; 35227 }; // ():int 35228 35229 35230 this.readExpGolomb = function () { 35231 var valu = this.readUnsignedExpGolomb(); // :int 35232 35233 if (0x01 & valu) { 35234 // the number is odd if the low order bit is set 35235 return 1 + valu >>> 1; // add 1 to make it even, and divide by 2 35236 } 35237 35238 return -1 * (valu >>> 1); // divide by two then make it negative 35239 }; // Some convenience functions 35240 // :Boolean 35241 35242 35243 this.readBoolean = function () { 35244 return this.readBits(1) === 1; 35245 }; // ():int 35246 35247 35248 this.readUnsignedByte = function () { 35249 return this.readBits(8); 35250 }; 35251 35252 this.loadWord(); 35253 }; 35254 35255 var expGolomb = ExpGolomb; 35256 35257 var _H264Stream, _NalByteStream; 35258 35259 var PROFILES_WITH_OPTIONAL_SPS_DATA; 35260 /** 35261 * Accepts a NAL unit byte stream and unpacks the embedded NAL units. 35262 */ 35263 35264 _NalByteStream = function NalByteStream() { 35265 var syncPoint = 0, 35266 i, 35267 buffer; 35268 35269 _NalByteStream.prototype.init.call(this); 35270 /* 35271 * Scans a byte stream and triggers a data event with the NAL units found. 35272 * @param {Object} data Event received from H264Stream 35273 * @param {Uint8Array} data.data The h264 byte stream to be scanned 35274 * 35275 * @see H264Stream.push 35276 */ 35277 35278 35279 this.push = function (data) { 35280 var swapBuffer; 35281 35282 if (!buffer) { 35283 buffer = data.data; 35284 } else { 35285 swapBuffer = new Uint8Array(buffer.byteLength + data.data.byteLength); 35286 swapBuffer.set(buffer); 35287 swapBuffer.set(data.data, buffer.byteLength); 35288 buffer = swapBuffer; 35289 } // Rec. ITU-T H.264, Annex B 35290 // scan for NAL unit boundaries 35291 // a match looks like this: 35292 // 0 0 1 .. NAL .. 0 0 1 35293 // ^ sync point ^ i 35294 // or this: 35295 // 0 0 1 .. NAL .. 0 0 0 35296 // ^ sync point ^ i 35297 // advance the sync point to a NAL start, if necessary 35298 35299 35300 for (; syncPoint < buffer.byteLength - 3; syncPoint++) { 35301 if (buffer[syncPoint + 2] === 1) { 35302 // the sync point is properly aligned 35303 i = syncPoint + 5; 35304 break; 35305 } 35306 } 35307 35308 while (i < buffer.byteLength) { 35309 // look at the current byte to determine if we've hit the end of 35310 // a NAL unit boundary 35311 switch (buffer[i]) { 35312 case 0: 35313 // skip past non-sync sequences 35314 if (buffer[i - 1] !== 0) { 35315 i += 2; 35316 break; 35317 } else if (buffer[i - 2] !== 0) { 35318 i++; 35319 break; 35320 } // deliver the NAL unit if it isn't empty 35321 35322 35323 if (syncPoint + 3 !== i - 2) { 35324 this.trigger('data', buffer.subarray(syncPoint + 3, i - 2)); 35325 } // drop trailing zeroes 35326 35327 35328 do { 35329 i++; 35330 } while (buffer[i] !== 1 && i < buffer.length); 35331 35332 syncPoint = i - 2; 35333 i += 3; 35334 break; 35335 35336 case 1: 35337 // skip past non-sync sequences 35338 if (buffer[i - 1] !== 0 || buffer[i - 2] !== 0) { 35339 i += 3; 35340 break; 35341 } // deliver the NAL unit 35342 35343 35344 this.trigger('data', buffer.subarray(syncPoint + 3, i - 2)); 35345 syncPoint = i - 2; 35346 i += 3; 35347 break; 35348 35349 default: 35350 // the current byte isn't a one or zero, so it cannot be part 35351 // of a sync sequence 35352 i += 3; 35353 break; 35354 } 35355 } // filter out the NAL units that were delivered 35356 35357 35358 buffer = buffer.subarray(syncPoint); 35359 i -= syncPoint; 35360 syncPoint = 0; 35361 }; 35362 35363 this.flush = function () { 35364 // deliver the last buffered NAL unit 35365 if (buffer && buffer.byteLength > 3) { 35366 this.trigger('data', buffer.subarray(syncPoint + 3)); 35367 } // reset the stream state 35368 35369 35370 buffer = null; 35371 syncPoint = 0; 35372 this.trigger('done'); 35373 }; 35374 }; 35375 35376 _NalByteStream.prototype = new stream(); // values of profile_idc that indicate additional fields are included in the SPS 35377 // see Recommendation ITU-T H.264 (4/2013), 35378 // 7.3.2.1.1 Sequence parameter set data syntax 35379 35380 PROFILES_WITH_OPTIONAL_SPS_DATA = { 35381 100: true, 35382 110: true, 35383 122: true, 35384 244: true, 35385 44: true, 35386 83: true, 35387 86: true, 35388 118: true, 35389 128: true,
vendor: 1,727 bytes, lines 35389-35449
35389 35390 138: true, 35391 139: true, 35392 134: true 35393 }; 35394 /** 35395 * Accepts input from a ElementaryStream and produces H.264 NAL unit data 35396 * events. 35397 */ 35398 35399 _H264Stream = function H264Stream() { 35400 var nalByteStream = new _NalByteStream(), 35401 self, 35402 trackId, 35403 currentPts, 35404 currentDts, 35405 discardEmulationPreventionBytes, 35406 readSequenceParameterSet, 35407 skipScalingList; 35408 35409 _H264Stream.prototype.init.call(this); 35410 35411 self = this; 35412 /* 35413 * Pushes a packet from a stream onto the NalByteStream 35414 * 35415 * @param {Object} packet - A packet received from a stream 35416 * @param {Uint8Array} packet.data - The raw bytes of the packet 35417 * @param {Number} packet.dts - Decode timestamp of the packet 35418 * @param {Number} packet.pts - Presentation timestamp of the packet 35419 * @param {Number} packet.trackId - The id of the h264 track this packet came from 35420 * @param {('video'|'audio')} packet.type - The type of packet 35421 * 35422 */ 35423 35424 this.push = function (packet) { 35425 if (packet.type !== 'video') { 35426 return; 35427 } 35428 35429 trackId = packet.trackId; 35430 currentPts = packet.pts; 35431 currentDts = packet.dts; 35432 nalByteStream.push(packet); 35433 }; 35434 /* 35435 * Identify NAL unit types and pass on the NALU, trackId, presentation and decode timestamps 35436 * for the NALUs to the next stream component. 35437 * Also, preprocess caption and sequence parameter NALUs. 35438 * 35439 * @param {Uint8Array} data - A NAL unit identified by `NalByteStream.push` 35440 * @see NalByteStream.push 35441 */ 35442 35443 35444 nalByteStream.on('data', function (data) { 35445 var event = { 35446 trackId: trackId, 35447 pts: currentPts, 35448 dts: currentDts, 35449 data: data
vendor: 10,467 bytes, lines 35449-35795
35449 35450 }; 35451 35452 switch (data[0] & 0x1f) { 35453 case 0x05: 35454 event.nalUnitType = 'slice_layer_without_partitioning_rbsp_idr'; 35455 break; 35456 35457 case 0x06: 35458 event.nalUnitType = 'sei_rbsp'; 35459 event.escapedRBSP = discardEmulationPreventionBytes(data.subarray(1)); 35460 break; 35461 35462 case 0x07: 35463 event.nalUnitType = 'seq_parameter_set_rbsp'; 35464 event.escapedRBSP = discardEmulationPreventionBytes(data.subarray(1)); 35465 event.config = readSequenceParameterSet(event.escapedRBSP); 35466 break; 35467 35468 case 0x08: 35469 event.nalUnitType = 'pic_parameter_set_rbsp'; 35470 break; 35471 35472 case 0x09: 35473 event.nalUnitType = 'access_unit_delimiter_rbsp'; 35474 break; 35475 35476 default: 35477 break; 35478 } // This triggers data on the H264Stream 35479 35480 35481 self.trigger('data', event); 35482 }); 35483 nalByteStream.on('done', function () { 35484 self.trigger('done'); 35485 }); 35486 35487 this.flush = function () { 35488 nalByteStream.flush(); 35489 }; 35490 /** 35491 * Advance the ExpGolomb decoder past a scaling list. The scaling 35492 * list is optionally transmitted as part of a sequence parameter 35493 * set and is not relevant to transmuxing. 35494 * @param count {number} the number of entries in this scaling list 35495 * @param expGolombDecoder {object} an ExpGolomb pointed to the 35496 * start of a scaling list 35497 * @see Recommendation ITU-T H.264, Section 7.3.2.1.1.1 35498 */ 35499 35500 35501 skipScalingList = function skipScalingList(count, expGolombDecoder) { 35502 var lastScale = 8, 35503 nextScale = 8, 35504 j, 35505 deltaScale; 35506 35507 for (j = 0; j < count; j++) { 35508 if (nextScale !== 0) { 35509 deltaScale = expGolombDecoder.readExpGolomb(); 35510 nextScale = (lastScale + deltaScale + 256) % 256; 35511 } 35512 35513 lastScale = nextScale === 0 ? lastScale : nextScale; 35514 } 35515 }; 35516 /** 35517 * Expunge any "Emulation Prevention" bytes from a "Raw Byte 35518 * Sequence Payload" 35519 * @param data {Uint8Array} the bytes of a RBSP from a NAL 35520 * unit 35521 * @return {Uint8Array} the RBSP without any Emulation 35522 * Prevention Bytes 35523 */ 35524 35525 35526 discardEmulationPreventionBytes = function discardEmulationPreventionBytes(data) { 35527 var length = data.byteLength, 35528 emulationPreventionBytesPositions = [], 35529 i = 1, 35530 newLength, 35531 newData; // Find all `Emulation Prevention Bytes` 35532 35533 while (i < length - 2) { 35534 if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0x03) { 35535 emulationPreventionBytesPositions.push(i + 2); 35536 i += 2; 35537 } else { 35538 i++; 35539 } 35540 } // If no Emulation Prevention Bytes were found just return the original 35541 // array 35542 35543 35544 if (emulationPreventionBytesPositions.length === 0) { 35545 return data; 35546 } // Create a new array to hold the NAL unit data 35547 35548 35549 newLength = length - emulationPreventionBytesPositions.length; 35550 newData = new Uint8Array(newLength); 35551 var sourceIndex = 0; 35552 35553 for (i = 0; i < newLength; sourceIndex++, i++) { 35554 if (sourceIndex === emulationPreventionBytesPositions[0]) { 35555 // Skip this byte 35556 sourceIndex++; // Remove this position index 35557 35558 emulationPreventionBytesPositions.shift(); 35559 } 35560 35561 newData[i] = data[sourceIndex]; 35562 } 35563 35564 return newData; 35565 }; 35566 /** 35567 * Read a sequence parameter set and return some interesting video 35568 * properties. A sequence parameter set is the H264 metadata that 35569 * describes the properties of upcoming video frames. 35570 * @param data {Uint8Array} the bytes of a sequence parameter set 35571 * @return {object} an object with configuration parsed from the 35572 * sequence parameter set, including the dimensions of the 35573 * associated video frames. 35574 */ 35575 35576 35577 readSequenceParameterSet = function readSequenceParameterSet(data) { 35578 var frameCropLeftOffset = 0, 35579 frameCropRightOffset = 0, 35580 frameCropTopOffset = 0, 35581 frameCropBottomOffset = 0, 35582 sarScale = 1, 35583 expGolombDecoder, 35584 profileIdc, 35585 levelIdc, 35586 profileCompatibility, 35587 chromaFormatIdc, 35588 picOrderCntType, 35589 numRefFramesInPicOrderCntCycle, 35590 picWidthInMbsMinus1, 35591 picHeightInMapUnitsMinus1, 35592 frameMbsOnlyFlag, 35593 scalingListCount, 35594 sarRatio, 35595 aspectRatioIdc, 35596 i; 35597 expGolombDecoder = new expGolomb(data); 35598 profileIdc = expGolombDecoder.readUnsignedByte(); // profile_idc 35599 35600 profileCompatibility = expGolombDecoder.readUnsignedByte(); // constraint_set[0-5]_flag 35601 35602 levelIdc = expGolombDecoder.readUnsignedByte(); // level_idc u(8) 35603 35604 expGolombDecoder.skipUnsignedExpGolomb(); // seq_parameter_set_id 35605 // some profiles have more optional data we don't need 35606 35607 if (PROFILES_WITH_OPTIONAL_SPS_DATA[profileIdc]) { 35608 chromaFormatIdc = expGolombDecoder.readUnsignedExpGolomb(); 35609 35610 if (chromaFormatIdc === 3) { 35611 expGolombDecoder.skipBits(1); // separate_colour_plane_flag 35612 } 35613 35614 expGolombDecoder.skipUnsignedExpGolomb(); // bit_depth_luma_minus8 35615 35616 expGolombDecoder.skipUnsignedExpGolomb(); // bit_depth_chroma_minus8 35617 35618 expGolombDecoder.skipBits(1); // qpprime_y_zero_transform_bypass_flag 35619 35620 if (expGolombDecoder.readBoolean()) { 35621 // seq_scaling_matrix_present_flag 35622 scalingListCount = chromaFormatIdc !== 3 ? 8 : 12; 35623 35624 for (i = 0; i < scalingListCount; i++) { 35625 if (expGolombDecoder.readBoolean()) { 35626 // seq_scaling_list_present_flag[ i ] 35627 if (i < 6) { 35628 skipScalingList(16, expGolombDecoder); 35629 } else { 35630 skipScalingList(64, expGolombDecoder); 35631 } 35632 } 35633 } 35634 } 35635 } 35636 35637 expGolombDecoder.skipUnsignedExpGolomb(); // log2_max_frame_num_minus4 35638 35639 picOrderCntType = expGolombDecoder.readUnsignedExpGolomb(); 35640 35641 if (picOrderCntType === 0) { 35642 expGolombDecoder.readUnsignedExpGolomb(); // log2_max_pic_order_cnt_lsb_minus4 35643 } else if (picOrderCntType === 1) { 35644 expGolombDecoder.skipBits(1); // delta_pic_order_always_zero_flag 35645 35646 expGolombDecoder.skipExpGolomb(); // offset_for_non_ref_pic 35647 35648 expGolombDecoder.skipExpGolomb(); // offset_for_top_to_bottom_field 35649 35650 numRefFramesInPicOrderCntCycle = expGolombDecoder.readUnsignedExpGolomb(); 35651 35652 for (i = 0; i < numRefFramesInPicOrderCntCycle; i++) { 35653 expGolombDecoder.skipExpGolomb(); // offset_for_ref_frame[ i ] 35654 } 35655 } 35656 35657 expGolombDecoder.skipUnsignedExpGolomb(); // max_num_ref_frames 35658 35659 expGolombDecoder.skipBits(1); // gaps_in_frame_num_value_allowed_flag 35660 35661 picWidthInMbsMinus1 = expGolombDecoder.readUnsignedExpGolomb(); 35662 picHeightInMapUnitsMinus1 = expGolombDecoder.readUnsignedExpGolomb(); 35663 frameMbsOnlyFlag = expGolombDecoder.readBits(1); 35664 35665 if (frameMbsOnlyFlag === 0) { 35666 expGolombDecoder.skipBits(1); // mb_adaptive_frame_field_flag 35667 } 35668 35669 expGolombDecoder.skipBits(1); // direct_8x8_inference_flag 35670 35671 if (expGolombDecoder.readBoolean()) { 35672 // frame_cropping_flag 35673 frameCropLeftOffset = expGolombDecoder.readUnsignedExpGolomb(); 35674 frameCropRightOffset = expGolombDecoder.readUnsignedExpGolomb(); 35675 frameCropTopOffset = expGolombDecoder.readUnsignedExpGolomb(); 35676 frameCropBottomOffset = expGolombDecoder.readUnsignedExpGolomb(); 35677 } 35678 35679 if (expGolombDecoder.readBoolean()) { 35680 // vui_parameters_present_flag 35681 if (expGolombDecoder.readBoolean()) { 35682 // aspect_ratio_info_present_flag 35683 aspectRatioIdc = expGolombDecoder.readUnsignedByte(); 35684 35685 switch (aspectRatioIdc) { 35686 case 1: 35687 sarRatio = [1, 1]; 35688 break; 35689 35690 case 2: 35691 sarRatio = [12, 11]; 35692 break; 35693 35694 case 3: 35695 sarRatio = [10, 11]; 35696 break; 35697 35698 case 4: 35699 sarRatio = [16, 11]; 35700 break; 35701 35702 case 5: 35703 sarRatio = [40, 33]; 35704 break; 35705 35706 case 6: 35707 sarRatio = [24, 11]; 35708 break; 35709 35710 case 7: 35711 sarRatio = [20, 11]; 35712 break; 35713 35714 case 8: 35715 sarRatio = [32, 11]; 35716 break; 35717 35718 case 9: 35719 sarRatio = [80, 33]; 35720 break; 35721 35722 case 10: 35723 sarRatio = [18, 11]; 35724 break; 35725 35726 case 11: 35727 sarRatio = [15, 11]; 35728 break; 35729 35730 case 12: 35731 sarRatio = [64, 33]; 35732 break; 35733 35734 case 13: 35735 sarRatio = [160, 99]; 35736 break; 35737 35738 case 14: 35739 sarRatio = [4, 3]; 35740 break; 35741 35742 case 15: 35743 sarRatio = [3, 2]; 35744 break; 35745 35746 case 16: 35747 sarRatio = [2, 1]; 35748 break; 35749 35750 case 255: 35751 { 35752 sarRatio = [expGolombDecoder.readUnsignedByte() << 8 | expGolombDecoder.readUnsignedByte(), expGolombDecoder.readUnsignedByte() << 8 | expGolombDecoder.readUnsignedByte()]; 35753 break; 35754 } 35755 } 35756 35757 if (sarRatio) { 35758 sarScale = sarRatio[0] / sarRatio[1]; 35759 } 35760 } 35761 } 35762 35763 return { 35764 profileIdc: profileIdc, 35765 levelIdc: levelIdc, 35766 profileCompatibility: profileCompatibility, 35767 width: Math.ceil(((picWidthInMbsMinus1 + 1) * 16 - frameCropLeftOffset * 2 - frameCropRightOffset * 2) * sarScale), 35768 height: (2 - frameMbsOnlyFlag) * (picHeightInMapUnitsMinus1 + 1) * 16 - frameCropTopOffset * 2 - frameCropBottomOffset * 2 35769 }; 35770 }; 35771 }; 35772 35773 _H264Stream.prototype = new stream(); 35774 var h264 = { 35775 H264Stream: _H264Stream, 35776 NalByteStream: _NalByteStream 35777 }; 35778 35779 /** 35780 * mux.js 35781 * 35782 * Copyright (c) 2016 Brightcove 35783 * All rights reserved. 35784 * 35785 * Utilities to detect basic properties and metadata about Aac data. 35786 */ 35787 35788 var ADTS_SAMPLING_FREQUENCIES$1 = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350]; 35789 35790 var isLikelyAacData = function isLikelyAacData(data) { 35791 if (data[0] === 'I'.charCodeAt(0) && data[1] === 'D'.charCodeAt(0) && data[2] === '3'.charCodeAt(0)) { 35792 return true; 35793 } 35794 35795 return false;
vendor: 6,657 bytes, lines 35796-36002
35796 }; 35797 35798 var parseSyncSafeInteger$1 = function parseSyncSafeInteger(data) { 35799 return data[0] << 21 | data[1] << 14 | data[2] << 7 | data[3]; 35800 }; // return a percent-encoded representation of the specified byte range 35801 // @see http://en.wikipedia.org/wiki/Percent-encoding 35802 35803 35804 var percentEncode$1 = function percentEncode(bytes, start, end) { 35805 var i, 35806 result = ''; 35807 35808 for (i = start; i < end; i++) { 35809 result += '%' + ('00' + bytes[i].toString(16)).slice(-2); 35810 } 35811 35812 return result; 35813 }; // return the string representation of the specified byte range, 35814 // interpreted as ISO-8859-1. 35815 35816 35817 var parseIso88591$1 = function parseIso88591(bytes, start, end) { 35818 return unescape(percentEncode$1(bytes, start, end)); // jshint ignore:line 35819 }; 35820 35821 var parseId3TagSize = function parseId3TagSize(header, byteIndex) { 35822 var returnSize = header[byteIndex + 6] << 21 | header[byteIndex + 7] << 14 | header[byteIndex + 8] << 7 | header[byteIndex + 9], 35823 flags = header[byteIndex + 5], 35824 footerPresent = (flags & 16) >> 4; 35825 35826 if (footerPresent) { 35827 return returnSize + 20; 35828 } 35829 35830 return returnSize + 10; 35831 }; 35832 35833 var parseAdtsSize = function parseAdtsSize(header, byteIndex) { 35834 var lowThree = (header[byteIndex + 5] & 0xE0) >> 5, 35835 middle = header[byteIndex + 4] << 3, 35836 highTwo = header[byteIndex + 3] & 0x3 << 11; 35837 return highTwo | middle | lowThree; 35838 }; 35839 35840 var parseType$1 = function parseType(header, byteIndex) { 35841 if (header[byteIndex] === 'I'.charCodeAt(0) && header[byteIndex + 1] === 'D'.charCodeAt(0) && header[byteIndex + 2] === '3'.charCodeAt(0)) { 35842 return 'timed-metadata'; 35843 } else if (header[byteIndex] & 0xff === 0xff && (header[byteIndex + 1] & 0xf0) === 0xf0) { 35844 return 'audio'; 35845 } 35846 35847 return null; 35848 }; 35849 35850 var parseSampleRate = function parseSampleRate(packet) { 35851 var i = 0; 35852 35853 while (i + 5 < packet.length) { 35854 if (packet[i] !== 0xFF || (packet[i + 1] & 0xF6) !== 0xF0) { 35855 // If a valid header was not found, jump one forward and attempt to 35856 // find a valid ADTS header starting at the next byte 35857 i++; 35858 continue; 35859 } 35860 35861 return ADTS_SAMPLING_FREQUENCIES$1[(packet[i + 2] & 0x3c) >>> 2]; 35862 } 35863 35864 return null; 35865 }; 35866 35867 var parseAacTimestamp = function parseAacTimestamp(packet) { 35868 var frameStart, frameSize, frame, frameHeader; // find the start of the first frame and the end of the tag 35869 35870 frameStart = 10; 35871 35872 if (packet[5] & 0x40) { 35873 // advance the frame start past the extended header 35874 frameStart += 4; // header size field 35875 35876 frameStart += parseSyncSafeInteger$1(packet.subarray(10, 14)); 35877 } // parse one or more ID3 frames 35878 // http://id3.org/id3v2.3.0#ID3v2_frame_overview 35879 35880 35881 do { 35882 // determine the number of bytes in this frame 35883 frameSize = parseSyncSafeInteger$1(packet.subarray(frameStart + 4, frameStart + 8)); 35884 35885 if (frameSize < 1) { 35886 return null; 35887 } 35888 35889 frameHeader = String.fromCharCode(packet[frameStart], packet[frameStart + 1], packet[frameStart + 2], packet[frameStart + 3]); 35890 35891 if (frameHeader === 'PRIV') { 35892 frame = packet.subarray(frameStart + 10, frameStart + frameSize + 10); 35893 35894 for (var i = 0; i < frame.byteLength; i++) { 35895 if (frame[i] === 0) { 35896 var owner = parseIso88591$1(frame, 0, i); 35897 35898 if (owner === 'com.apple.streaming.transportStreamTimestamp') { 35899 var d = frame.subarray(i + 1); 35900 var size = (d[3] & 0x01) << 30 | d[4] << 22 | d[5] << 14 | d[6] << 6 | d[7] >>> 2; 35901 size *= 4; 35902 size += d[7] & 0x03; 35903 return size; 35904 } 35905 35906 break; 35907 } 35908 } 35909 } 35910 35911 frameStart += 10; // advance past the frame header 35912 35913 frameStart += frameSize; // advance past the frame body 35914 } while (frameStart < packet.byteLength); 35915 35916 return null; 35917 }; 35918 35919 var utils = { 35920 isLikelyAacData: isLikelyAacData, 35921 parseId3TagSize: parseId3TagSize, 35922 parseAdtsSize: parseAdtsSize, 35923 parseType: parseType$1, 35924 parseSampleRate: parseSampleRate, 35925 parseAacTimestamp: parseAacTimestamp 35926 }; 35927 35928 var _AacStream; 35929 /** 35930 * Splits an incoming stream of binary data into ADTS and ID3 Frames. 35931 */ 35932 35933 35934 _AacStream = function AacStream() { 35935 var everything = new Uint8Array(), 35936 timeStamp = 0; 35937 35938 _AacStream.prototype.init.call(this); 35939 35940 this.setTimestamp = function (timestamp) { 35941 timeStamp = timestamp; 35942 }; 35943 35944 this.push = function (bytes) { 35945 var frameSize = 0, 35946 byteIndex = 0, 35947 bytesLeft, 35948 chunk, 35949 packet, 35950 tempLength; // If there are bytes remaining from the last segment, prepend them to the 35951 // bytes that were pushed in 35952 35953 if (everything.length) { 35954 tempLength = everything.length; 35955 everything = new Uint8Array(bytes.byteLength + tempLength); 35956 everything.set(everything.subarray(0, tempLength)); 35957 everything.set(bytes, tempLength); 35958 } else { 35959 everything = bytes; 35960 } 35961 35962 while (everything.length - byteIndex >= 3) { 35963 if (everything[byteIndex] === 'I'.charCodeAt(0) && everything[byteIndex + 1] === 'D'.charCodeAt(0) && everything[byteIndex + 2] === '3'.charCodeAt(0)) { 35964 // Exit early because we don't have enough to parse 35965 // the ID3 tag header 35966 if (everything.length - byteIndex < 10) { 35967 break; 35968 } // check framesize 35969 35970 35971 frameSize = utils.parseId3TagSize(everything, byteIndex); // Exit early if we don't have enough in the buffer 35972 // to emit a full packet 35973 // Add to byteIndex to support multiple ID3 tags in sequence 35974 35975 if (byteIndex + frameSize > everything.length) { 35976 break; 35977 } 35978 35979 chunk = { 35980 type: 'timed-metadata', 35981 data: everything.subarray(byteIndex, byteIndex + frameSize) 35982 }; 35983 this.trigger('data', chunk); 35984 byteIndex += frameSize; 35985 continue; 35986 } else if ((everything[byteIndex] & 0xff) === 0xff && (everything[byteIndex + 1] & 0xf0) === 0xf0) { 35987 // Exit early because we don't have enough to parse 35988 // the ADTS frame header 35989 if (everything.length - byteIndex < 7) { 35990 break; 35991 } 35992 35993 frameSize = utils.parseAdtsSize(everything, byteIndex); // Exit early if we don't have enough in the buffer 35994 // to emit a full packet 35995 35996 if (byteIndex + frameSize > everything.length) { 35997 break; 35998 } 35999 36000 packet = { 36001 type: 'audio', 36002 data: everything.subarray(byteIndex, byteIndex + frameSize),
vendor: 5,973 bytes, lines 36003-36176
36003 pts: timeStamp, 36004 dts: timeStamp 36005 }; 36006 this.trigger('data', packet); 36007 byteIndex += frameSize; 36008 continue; 36009 } 36010 36011 byteIndex++; 36012 } 36013 36014 bytesLeft = everything.length - byteIndex; 36015 36016 if (bytesLeft > 0) { 36017 everything = everything.subarray(byteIndex); 36018 } else { 36019 everything = new Uint8Array(); 36020 } 36021 }; 36022 }; 36023 36024 _AacStream.prototype = new stream(); 36025 var aac = _AacStream; 36026 36027 var H264Stream = h264.H264Stream; 36028 var isLikelyAacData$1 = utils.isLikelyAacData; // constants 36029 36030 var AUDIO_PROPERTIES = ['audioobjecttype', 'channelcount', 'samplerate', 'samplingfrequencyindex', 'samplesize']; 36031 var VIDEO_PROPERTIES = ['width', 'height', 'profileIdc', 'levelIdc', 'profileCompatibility']; // object types 36032 36033 var _VideoSegmentStream, _AudioSegmentStream, _Transmuxer, _CoalesceStream; 36034 /** 36035 * Compare two arrays (even typed) for same-ness 36036 */ 36037 36038 36039 var arrayEquals = function arrayEquals(a, b) { 36040 var i; 36041 36042 if (a.length !== b.length) { 36043 return false; 36044 } // compare the value of each element in the array 36045 36046 36047 for (i = 0; i < a.length; i++) { 36048 if (a[i] !== b[i]) { 36049 return false; 36050 } 36051 } 36052 36053 return true; 36054 }; 36055 36056 var generateVideoSegmentTimingInfo = function generateVideoSegmentTimingInfo(baseMediaDecodeTime, startDts, startPts, endDts, endPts, prependedContentDuration) { 36057 var ptsOffsetFromDts = startPts - startDts, 36058 decodeDuration = endDts - startDts, 36059 presentationDuration = endPts - startPts; // The PTS and DTS values are based on the actual stream times from the segment, 36060 // however, the player time values will reflect a start from the baseMediaDecodeTime. 36061 // In order to provide relevant values for the player times, base timing info on the 36062 // baseMediaDecodeTime and the DTS and PTS durations of the segment. 36063 36064 return { 36065 start: { 36066 dts: baseMediaDecodeTime, 36067 pts: baseMediaDecodeTime + ptsOffsetFromDts 36068 }, 36069 end: { 36070 dts: baseMediaDecodeTime + decodeDuration, 36071 pts: baseMediaDecodeTime + presentationDuration 36072 }, 36073 prependedContentDuration: prependedContentDuration, 36074 baseMediaDecodeTime: baseMediaDecodeTime 36075 }; 36076 }; 36077 /** 36078 * Constructs a single-track, ISO BMFF media segment from AAC data 36079 * events. The output of this stream can be fed to a SourceBuffer 36080 * configured with a suitable initialization segment. 36081 * @param track {object} track metadata configuration 36082 * @param options {object} transmuxer options object 36083 * @param options.keepOriginalTimestamps {boolean} If true, keep the timestamps 36084 * in the source; false to adjust the first segment to start at 0. 36085 */ 36086 36087 36088 _AudioSegmentStream = function AudioSegmentStream(track, options) { 36089 var adtsFrames = [], 36090 sequenceNumber = 0, 36091 earliestAllowedDts = 0, 36092 audioAppendStartTs = 0, 36093 videoBaseMediaDecodeTime = Infinity; 36094 options = options || {}; 36095 36096 _AudioSegmentStream.prototype.init.call(this); 36097 36098 this.push = function (data) { 36099 trackDecodeInfo.collectDtsInfo(track, data); 36100 36101 if (track) { 36102 AUDIO_PROPERTIES.forEach(function (prop) { 36103 track[prop] = data[prop]; 36104 }); 36105 } // buffer audio data until end() is called 36106 36107 36108 adtsFrames.push(data); 36109 }; 36110 36111 this.setEarliestDts = function (earliestDts) { 36112 earliestAllowedDts = earliestDts - track.timelineStartInfo.baseMediaDecodeTime; 36113 }; 36114 36115 this.setVideoBaseMediaDecodeTime = function (baseMediaDecodeTime) { 36116 videoBaseMediaDecodeTime = baseMediaDecodeTime; 36117 }; 36118 36119 this.setAudioAppendStart = function (timestamp) { 36120 audioAppendStartTs = timestamp; 36121 }; 36122 36123 this.flush = function () { 36124 var frames, moof, mdat, boxes; // return early if no audio data has been observed 36125 36126 if (adtsFrames.length === 0) { 36127 this.trigger('done', 'AudioSegmentStream'); 36128 return; 36129 } 36130 36131 frames = audioFrameUtils.trimAdtsFramesByEarliestDts(adtsFrames, track, earliestAllowedDts); 36132 track.baseMediaDecodeTime = trackDecodeInfo.calculateTrackBaseMediaDecodeTime(track, options.keepOriginalTimestamps); 36133 audioFrameUtils.prefixWithSilence(track, frames, audioAppendStartTs, videoBaseMediaDecodeTime); // we have to build the index from byte locations to 36134 // samples (that is, adts frames) in the audio data 36135 36136 track.samples = audioFrameUtils.generateSampleTable(frames); // concatenate the audio data to constuct the mdat 36137 36138 mdat = mp4Generator.mdat(audioFrameUtils.concatenateFrameData(frames)); 36139 adtsFrames = []; 36140 moof = mp4Generator.moof(sequenceNumber, [track]); 36141 boxes = new Uint8Array(moof.byteLength + mdat.byteLength); // bump the sequence number for next time 36142 36143 sequenceNumber++; 36144 boxes.set(moof); 36145 boxes.set(mdat, moof.byteLength); 36146 trackDecodeInfo.clearDtsInfo(track); 36147 this.trigger('data', { 36148 track: track, 36149 boxes: boxes 36150 }); 36151 this.trigger('done', 'AudioSegmentStream'); 36152 }; 36153 }; 36154 36155 _AudioSegmentStream.prototype = new stream(); 36156 /** 36157 * Constructs a single-track, ISO BMFF media segment from H264 data 36158 * events. The output of this stream can be fed to a SourceBuffer 36159 * configured with a suitable initialization segment. 36160 * @param track {object} track metadata configuration 36161 * @param options {object} transmuxer options object 36162 * @param options.alignGopsAtEnd {boolean} If true, start from the end of the 36163 * gopsToAlignWith list when attempting to align gop pts 36164 * @param options.keepOriginalTimestamps {boolean} If true, keep the timestamps 36165 * in the source; false to adjust the first segment to start at 0. 36166 */ 36167 36168 _VideoSegmentStream = function VideoSegmentStream(track, options) { 36169 var sequenceNumber = 0, 36170 nalUnits = [], 36171 gopsToAlignWith = [], 36172 config, 36173 pps; 36174 options = options || {}; 36175 36176 _VideoSegmentStream.prototype.init.call(this);
vendor: 5,323 bytes, lines 36177-36324
36177 36178 delete track.minPTS; 36179 this.gopCache_ = []; 36180 /** 36181 * Constructs a ISO BMFF segment given H264 nalUnits 36182 * @param {Object} nalUnit A data event representing a nalUnit 36183 * @param {String} nalUnit.nalUnitType 36184 * @param {Object} nalUnit.config Properties for a mp4 track 36185 * @param {Uint8Array} nalUnit.data The nalUnit bytes 36186 * @see lib/codecs/h264.js 36187 **/ 36188 36189 this.push = function (nalUnit) { 36190 trackDecodeInfo.collectDtsInfo(track, nalUnit); // record the track config 36191 36192 if (nalUnit.nalUnitType === 'seq_parameter_set_rbsp' && !config) { 36193 config = nalUnit.config; 36194 track.sps = [nalUnit.data]; 36195 VIDEO_PROPERTIES.forEach(function (prop) { 36196 track[prop] = config[prop]; 36197 }, this); 36198 } 36199 36200 if (nalUnit.nalUnitType === 'pic_parameter_set_rbsp' && !pps) { 36201 pps = nalUnit.data; 36202 track.pps = [nalUnit.data]; 36203 } // buffer video until flush() is called 36204 36205 36206 nalUnits.push(nalUnit); 36207 }; 36208 /** 36209 * Pass constructed ISO BMFF track and boxes on to the 36210 * next stream in the pipeline 36211 **/ 36212 36213 36214 this.flush = function () { 36215 var frames, 36216 gopForFusion, 36217 gops, 36218 moof, 36219 mdat, 36220 boxes, 36221 prependedContentDuration = 0, 36222 firstGop, 36223 lastGop; // Throw away nalUnits at the start of the byte stream until 36224 // we find the first AUD 36225 36226 while (nalUnits.length) { 36227 if (nalUnits[0].nalUnitType === 'access_unit_delimiter_rbsp') { 36228 break; 36229 } 36230 36231 nalUnits.shift(); 36232 } // Return early if no video data has been observed 36233 36234 36235 if (nalUnits.length === 0) { 36236 this.resetStream_(); 36237 this.trigger('done', 'VideoSegmentStream'); 36238 return; 36239 } // Organize the raw nal-units into arrays that represent 36240 // higher-level constructs such as frames and gops 36241 // (group-of-pictures) 36242 36243 36244 frames = frameUtils.groupNalsIntoFrames(nalUnits); 36245 gops = frameUtils.groupFramesIntoGops(frames); // If the first frame of this fragment is not a keyframe we have 36246 // a problem since MSE (on Chrome) requires a leading keyframe. 36247 // 36248 // We have two approaches to repairing this situation: 36249 // 1) GOP-FUSION: 36250 // This is where we keep track of the GOPS (group-of-pictures) 36251 // from previous fragments and attempt to find one that we can 36252 // prepend to the current fragment in order to create a valid 36253 // fragment. 36254 // 2) KEYFRAME-PULLING: 36255 // Here we search for the first keyframe in the fragment and 36256 // throw away all the frames between the start of the fragment 36257 // and that keyframe. We then extend the duration and pull the 36258 // PTS of the keyframe forward so that it covers the time range 36259 // of the frames that were disposed of. 36260 // 36261 // #1 is far prefereable over #2 which can cause "stuttering" but 36262 // requires more things to be just right. 36263 36264 if (!gops[0][0].keyFrame) { 36265 // Search for a gop for fusion from our gopCache 36266 gopForFusion = this.getGopForFusion_(nalUnits[0], track); 36267 36268 if (gopForFusion) { 36269 // in order to provide more accurate timing information about the segment, save 36270 // the number of seconds prepended to the original segment due to GOP fusion 36271 prependedContentDuration = gopForFusion.duration; 36272 gops.unshift(gopForFusion); // Adjust Gops' metadata to account for the inclusion of the 36273 // new gop at the beginning 36274 36275 gops.byteLength += gopForFusion.byteLength; 36276 gops.nalCount += gopForFusion.nalCount; 36277 gops.pts = gopForFusion.pts; 36278 gops.dts = gopForFusion.dts; 36279 gops.duration += gopForFusion.duration; 36280 } else { 36281 // If we didn't find a candidate gop fall back to keyframe-pulling 36282 gops = frameUtils.extendFirstKeyFrame(gops); 36283 } 36284 } // Trim gops to align with gopsToAlignWith 36285 36286 36287 if (gopsToAlignWith.length) { 36288 var alignedGops; 36289 36290 if (options.alignGopsAtEnd) { 36291 alignedGops = this.alignGopsAtEnd_(gops); 36292 } else { 36293 alignedGops = this.alignGopsAtStart_(gops); 36294 } 36295 36296 if (!alignedGops) { 36297 // save all the nals in the last GOP into the gop cache 36298 this.gopCache_.unshift({ 36299 gop: gops.pop(), 36300 pps: track.pps, 36301 sps: track.sps 36302 }); // Keep a maximum of 6 GOPs in the cache 36303 36304 this.gopCache_.length = Math.min(6, this.gopCache_.length); // Clear nalUnits 36305 36306 nalUnits = []; // return early no gops can be aligned with desired gopsToAlignWith 36307 36308 this.resetStream_(); 36309 this.trigger('done', 'VideoSegmentStream'); 36310 return; 36311 } // Some gops were trimmed. clear dts info so minSegmentDts and pts are correct 36312 // when recalculated before sending off to CoalesceStream 36313 36314 36315 trackDecodeInfo.clearDtsInfo(track); 36316 gops = alignedGops; 36317 } 36318 36319 trackDecodeInfo.collectDtsInfo(track, gops); // First, we have to build the index from byte locations to 36320 // samples (that is, frames) in the video data 36321 36322 track.samples = frameUtils.generateSampleTable(gops); // Concatenate the video data and construct the mdat 36323 36324 mdat = mp4
vendor: 5,670 bytes, lines 36324-36479
36324Generator.mdat(frameUtils.concatenateNalData(gops)); 36325 track.baseMediaDecodeTime = trackDecodeInfo.calculateTrackBaseMediaDecodeTime(track, options.keepOriginalTimestamps); 36326 this.trigger('processedGopsInfo', gops.map(function (gop) { 36327 return { 36328 pts: gop.pts, 36329 dts: gop.dts, 36330 byteLength: gop.byteLength 36331 }; 36332 })); 36333 firstGop = gops[0]; 36334 lastGop = gops[gops.length - 1]; 36335 this.trigger('segmentTimingInfo', generateVideoSegmentTimingInfo(track.baseMediaDecodeTime, firstGop.dts, firstGop.pts, lastGop.dts + lastGop.duration, lastGop.pts + lastGop.duration, prependedContentDuration)); // save all the nals in the last GOP into the gop cache 36336 36337 this.gopCache_.unshift({ 36338 gop: gops.pop(), 36339 pps: track.pps, 36340 sps: track.sps 36341 }); // Keep a maximum of 6 GOPs in the cache 36342 36343 this.gopCache_.length = Math.min(6, this.gopCache_.length); // Clear nalUnits 36344 36345 nalUnits = []; 36346 this.trigger('baseMediaDecodeTime', track.baseMediaDecodeTime); 36347 this.trigger('timelineStartInfo', track.timelineStartInfo); 36348 moof = mp4Generator.moof(sequenceNumber, [track]); // it would be great to allocate this array up front instead of 36349 // throwing away hundreds of media segment fragments 36350 36351 boxes = new Uint8Array(moof.byteLength + mdat.byteLength); // Bump the sequence number for next time 36352 36353 sequenceNumber++; 36354 boxes.set(moof); 36355 boxes.set(mdat, moof.byteLength); 36356 this.trigger('data', { 36357 track: track, 36358 boxes: boxes 36359 }); 36360 this.resetStream_(); // Continue with the flush process now 36361 36362 this.trigger('done', 'VideoSegmentStream'); 36363 }; 36364 36365 this.resetStream_ = function () { 36366 trackDecodeInfo.clearDtsInfo(track); // reset config and pps because they may differ across segments 36367 // for instance, when we are rendition switching 36368 36369 config = undefined; 36370 pps = undefined; 36371 }; // Search for a candidate Gop for gop-fusion from the gop cache and 36372 // return it or return null if no good candidate was found 36373 36374 36375 this.getGopForFusion_ = function (nalUnit) { 36376 var halfSecond = 45000, 36377 // Half-a-second in a 90khz clock 36378 allowableOverlap = 10000, 36379 // About 3 frames @ 30fps 36380 nearestDistance = Infinity, 36381 dtsDistance, 36382 nearestGopObj, 36383 currentGop, 36384 currentGopObj, 36385 i; // Search for the GOP nearest to the beginning of this nal unit 36386 36387 for (i = 0; i < this.gopCache_.length; i++) { 36388 currentGopObj = this.gopCache_[i]; 36389 currentGop = currentGopObj.gop; // Reject Gops with different SPS or PPS 36390 36391 if (!(track.pps && arrayEquals(track.pps[0], currentGopObj.pps[0])) || !(track.sps && arrayEquals(track.sps[0], currentGopObj.sps[0]))) { 36392 continue; 36393 } // Reject Gops that would require a negative baseMediaDecodeTime 36394 36395 36396 if (currentGop.dts < track.timelineStartInfo.dts) { 36397 continue; 36398 } // The distance between the end of the gop and the start of the nalUnit 36399 36400 36401 dtsDistance = nalUnit.dts - currentGop.dts - currentGop.duration; // Only consider GOPS that start before the nal unit and end within 36402 // a half-second of the nal unit 36403 36404 if (dtsDistance >= -allowableOverlap && dtsDistance <= halfSecond) { 36405 // Always use the closest GOP we found if there is more than 36406 // one candidate 36407 if (!nearestGopObj || nearestDistance > dtsDistance) { 36408 nearestGopObj = currentGopObj; 36409 nearestDistance = dtsDistance; 36410 } 36411 } 36412 } 36413 36414 if (nearestGopObj) { 36415 return nearestGopObj.gop; 36416 } 36417 36418 return null; 36419 }; // trim gop list to the first gop found that has a matching pts with a gop in the list 36420 // of gopsToAlignWith starting from the START of the list 36421 36422 36423 this.alignGopsAtStart_ = function (gops) { 36424 var alignIndex, gopIndex, align, gop, byteLength, nalCount, duration, alignedGops; 36425 byteLength = gops.byteLength; 36426 nalCount = gops.nalCount; 36427 duration = gops.duration; 36428 alignIndex = gopIndex = 0; 36429 36430 while (alignIndex < gopsToAlignWith.length && gopIndex < gops.length) { 36431 align = gopsToAlignWith[alignIndex]; 36432 gop = gops[gopIndex]; 36433 36434 if (align.pts === gop.pts) { 36435 break; 36436 } 36437 36438 if (gop.pts > align.pts) { 36439 // this current gop starts after the current gop we want to align on, so increment 36440 // align index 36441 alignIndex++; 36442 continue; 36443 } // current gop starts before the current gop we want to align on. so increment gop 36444 // index 36445 36446 36447 gopIndex++; 36448 byteLength -= gop.byteLength; 36449 nalCount -= gop.nalCount; 36450 duration -= gop.duration; 36451 } 36452 36453 if (gopIndex === 0) { 36454 // no gops to trim 36455 return gops; 36456 } 36457 36458 if (gopIndex === gops.length) { 36459 // all gops trimmed, skip appending all gops 36460 return null; 36461 } 36462 36463 alignedGops = gops.slice(gopIndex); 36464 alignedGops.byteLength = byteLength; 36465 alignedGops.duration = duration; 36466 alignedGops.nalCount = nalCount; 36467 alignedGops.pts = alignedGops[0].pts; 36468 alignedGops.dts = alignedGops[0].dts; 36469 return alignedGops; 36470 }; // trim gop list to the first gop found that has a matching pts with a gop in the list 36471 // of gopsToAlignWith starting from the END of the list 36472 36473 36474 this.alignGopsAtEnd_ = function (gops) { 36475 var alignIndex, gopIndex, align, gop, alignEndIndex, matchFound; 36476 alignIndex = gopsToAlignWith.length - 1; 36477 gopIndex = gops.length - 1; 36478 alignEndIndex = null; 36479 matchFound = false;
vendor: 42,240 bytes, lines 36480-37758
36480 36481 while (alignIndex >= 0 && gopIndex >= 0) { 36482 align = gopsToAlignWith[alignIndex]; 36483 gop = gops[gopIndex]; 36484 36485 if (align.pts === gop.pts) { 36486 matchFound = true; 36487 break; 36488 } 36489 36490 if (align.pts > gop.pts) { 36491 alignIndex--; 36492 continue; 36493 } 36494 36495 if (alignIndex === gopsToAlignWith.length - 1) { 36496 // gop.pts is greater than the last alignment candidate. If no match is found 36497 // by the end of this loop, we still want to append gops that come after this 36498 // point 36499 alignEndIndex = gopIndex; 36500 } 36501 36502 gopIndex--; 36503 } 36504 36505 if (!matchFound && alignEndIndex === null) { 36506 return null; 36507 } 36508 36509 var trimIndex; 36510 36511 if (matchFound) { 36512 trimIndex = gopIndex; 36513 } else { 36514 trimIndex = alignEndIndex; 36515 } 36516 36517 if (trimIndex === 0) { 36518 return gops; 36519 } 36520 36521 var alignedGops = gops.slice(trimIndex); 36522 var metadata = alignedGops.reduce(function (total, gop) { 36523 total.byteLength += gop.byteLength; 36524 total.duration += gop.duration; 36525 total.nalCount += gop.nalCount; 36526 return total; 36527 }, { 36528 byteLength: 0, 36529 duration: 0, 36530 nalCount: 0 36531 }); 36532 alignedGops.byteLength = metadata.byteLength; 36533 alignedGops.duration = metadata.duration; 36534 alignedGops.nalCount = metadata.nalCount; 36535 alignedGops.pts = alignedGops[0].pts; 36536 alignedGops.dts = alignedGops[0].dts; 36537 return alignedGops; 36538 }; 36539 36540 this.alignGopsWith = function (newGopsToAlignWith) { 36541 gopsToAlignWith = newGopsToAlignWith; 36542 }; 36543 }; 36544 36545 _VideoSegmentStream.prototype = new stream(); 36546 /** 36547 * A Stream that can combine multiple streams (ie. audio & video) 36548 * into a single output segment for MSE. Also supports audio-only 36549 * and video-only streams. 36550 * @param options {object} transmuxer options object 36551 * @param options.keepOriginalTimestamps {boolean} If true, keep the timestamps 36552 * in the source; false to adjust the first segment to start at media timeline start. 36553 */ 36554 36555 _CoalesceStream = function CoalesceStream(options, metadataStream) { 36556 // Number of Tracks per output segment 36557 // If greater than 1, we combine multiple 36558 // tracks into a single segment 36559 this.numberOfTracks = 0; 36560 this.metadataStream = metadataStream; 36561 options = options || {}; 36562 36563 if (typeof options.remux !== 'undefined') { 36564 this.remuxTracks = !!options.remux; 36565 } else { 36566 this.remuxTracks = true; 36567 } 36568 36569 if (typeof options.keepOriginalTimestamps === 'boolean') { 36570 this.keepOriginalTimestamps = options.keepOriginalTimestamps; 36571 } 36572 36573 this.pendingTracks = []; 36574 this.videoTrack = null; 36575 this.pendingBoxes = []; 36576 this.pendingCaptions = []; 36577 this.pendingMetadata = []; 36578 this.pendingBytes = 0; 36579 this.emittedTracks = 0; 36580 36581 _CoalesceStream.prototype.init.call(this); // Take output from multiple 36582 36583 36584 this.push = function (output) { 36585 // buffer incoming captions until the associated video segment 36586 // finishes 36587 if (output.text) { 36588 return this.pendingCaptions.push(output); 36589 } // buffer incoming id3 tags until the final flush 36590 36591 36592 if (output.frames) { 36593 return this.pendingMetadata.push(output); 36594 } // Add this track to the list of pending tracks and store 36595 // important information required for the construction of 36596 // the final segment 36597 36598 36599 this.pendingTracks.push(output.track); 36600 this.pendingBoxes.push(output.boxes); 36601 this.pendingBytes += output.boxes.byteLength; 36602 36603 if (output.track.type === 'video') { 36604 this.videoTrack = output.track; 36605 } 36606 36607 if (output.track.type === 'audio') { 36608 this.audioTrack = output.track; 36609 } 36610 }; 36611 }; 36612 36613 _CoalesceStream.prototype = new stream(); 36614 36615 _CoalesceStream.prototype.flush = function (flushSource) { 36616 var offset = 0, 36617 event = { 36618 captions: [], 36619 captionStreams: {}, 36620 metadata: [], 36621 info: {} 36622 }, 36623 caption, 36624 id3, 36625 initSegment, 36626 timelineStartPts = 0, 36627 i; 36628 36629 if (this.pendingTracks.length < this.numberOfTracks) { 36630 if (flushSource !== 'VideoSegmentStream' && flushSource !== 'AudioSegmentStream') { 36631 // Return because we haven't received a flush from a data-generating 36632 // portion of the segment (meaning that we have only recieved meta-data 36633 // or captions.) 36634 return; 36635 } else if (this.remuxTracks) { 36636 // Return until we have enough tracks from the pipeline to remux (if we 36637 // are remuxing audio and video into a single MP4) 36638 return; 36639 } else if (this.pendingTracks.length === 0) { 36640 // In the case where we receive a flush without any data having been 36641 // received we consider it an emitted track for the purposes of coalescing 36642 // `done` events. 36643 // We do this for the case where there is an audio and video track in the 36644 // segment but no audio data. (seen in several playlists with alternate 36645 // audio tracks and no audio present in the main TS segments.) 36646 this.emittedTracks++; 36647 36648 if (this.emittedTracks >= this.numberOfTracks) { 36649 this.trigger('done'); 36650 this.emittedTracks = 0; 36651 } 36652 36653 return; 36654 } 36655 } 36656 36657 if (this.videoTrack) { 36658 timelineStartPts = this.videoTrack.timelineStartInfo.pts; 36659 VIDEO_PROPERTIES.forEach(function (prop) { 36660 event.info[prop] = this.videoTrack[prop]; 36661 }, this); 36662 } else if (this.audioTrack) { 36663 timelineStartPts = this.audioTrack.timelineStartInfo.pts; 36664 AUDIO_PROPERTIES.forEach(function (prop) { 36665 event.info[prop] = this.audioTrack[prop]; 36666 }, this); 36667 } 36668 36669 if (this.pendingTracks.length === 1) { 36670 event.type = this.pendingTracks[0].type; 36671 } else { 36672 event.type = 'combined'; 36673 } 36674 36675 this.emittedTracks += this.pendingTracks.length; 36676 initSegment = mp4Generator.initSegment(this.pendingTracks); // Create a new typed array to hold the init segment 36677 36678 event.initSegment = new Uint8Array(initSegment.byteLength); // Create an init segment containing a moov 36679 // and track definitions 36680 36681 event.initSegment.set(initSegment); // Create a new typed array to hold the moof+mdats 36682 36683 event.data = new Uint8Array(this.pendingBytes); // Append each moof+mdat (one per track) together 36684 36685 for (i = 0; i < this.pendingBoxes.length; i++) { 36686 event.data.set(this.pendingBoxes[i], offset); 36687 offset += this.pendingBoxes[i].byteLength; 36688 } // Translate caption PTS times into second offsets to match the 36689 // video timeline for the segment, and add track info 36690 36691 36692 for (i = 0; i < this.pendingCaptions.length; i++) { 36693 caption = this.pendingCaptions[i]; 36694 caption.startTime = caption.startPts; 36695 36696 if (!this.keepOriginalTimestamps) { 36697 caption.startTime -= timelineStartPts; 36698 } 36699 36700 caption.startTime /= 90e3; 36701 caption.endTime = caption.endPts; 36702 36703 if (!this.keepOriginalTimestamps) { 36704 caption.endTime -= timelineStartPts; 36705 } 36706 36707 caption.endTime /= 90e3; 36708 event.captionStreams[caption.stream] = true; 36709 event.captions.push(caption); 36710 } // Translate ID3 frame PTS times into second offsets to match the 36711 // video timeline for the segment 36712 36713 36714 for (i = 0; i < this.pendingMetadata.length; i++) { 36715 id3 = this.pendingMetadata[i]; 36716 id3.cueTime = id3.pts; 36717 36718 if (!this.keepOriginalTimestamps) { 36719 id3.cueTime -= timelineStartPts; 36720 } 36721 36722 id3.cueTime /= 90e3; 36723 event.metadata.push(id3); 36724 } // We add this to every single emitted segment even though we only need 36725 // it for the first 36726 36727 36728 event.metadata.dispatchType = this.metadataStream.dispatchType; // Reset stream state 36729 36730 this.pendingTracks.length = 0; 36731 this.videoTrack = null; 36732 this.pendingBoxes.length = 0; 36733 this.pendingCaptions.length = 0; 36734 this.pendingBytes = 0; 36735 this.pendingMetadata.length = 0; // Emit the built segment 36736 36737 this.trigger('data', event); // Only emit `done` if all tracks have been flushed and emitted 36738 36739 if (this.emittedTracks >= this.numberOfTracks) { 36740 this.trigger('done'); 36741 this.emittedTracks = 0; 36742 } 36743 }; 36744 /** 36745 * A Stream that expects MP2T binary data as input and produces 36746 * corresponding media segments, suitable for use with Media Source 36747 * Extension (MSE) implementations that support the ISO BMFF byte 36748 * stream format, like Chrome. 36749 */ 36750 36751 36752 _Transmuxer = function Transmuxer(options) { 36753 var self = this, 36754 hasFlushed = true, 36755 videoTrack, 36756 audioTrack; 36757 36758 _Transmuxer.prototype.init.call(this); 36759 36760 options = options || {}; 36761 this.baseMediaDecodeTime = options.baseMediaDecodeTime || 0; 36762 this.transmuxPipeline_ = {}; 36763 36764 this.setupAacPipeline = function () { 36765 var pipeline = {}; 36766 this.transmuxPipeline_ = pipeline; 36767 pipeline.type = 'aac'; 36768 pipeline.metadataStream = new m2ts_1.MetadataStream(); // set up the parsing pipeline 36769 36770 pipeline.aacStream = new aac(); 36771 pipeline.audioTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('audio'); 36772 pipeline.timedMetadataTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('timed-metadata'); 36773 pipeline.adtsStream = new adts(); 36774 pipeline.coalesceStream = new _CoalesceStream(options, pipeline.metadataStream); 36775 pipeline.headOfPipeline = pipeline.aacStream; 36776 pipeline.aacStream.pipe(pipeline.audioTimestampRolloverStream).pipe(pipeline.adtsStream); 36777 pipeline.aacStream.pipe(pipeline.timedMetadataTimestampRolloverStream).pipe(pipeline.metadataStream).pipe(pipeline.coalesceStream); 36778 pipeline.metadataStream.on('timestamp', function (frame) { 36779 pipeline.aacStream.setTimestamp(frame.timeStamp); 36780 }); 36781 pipeline.aacStream.on('data', function (data) { 36782 if (data.type === 'timed-metadata' && !pipeline.audioSegmentStream) { 36783 audioTrack = audioTrack || { 36784 timelineStartInfo: { 36785 baseMediaDecodeTime: self.baseMediaDecodeTime 36786 }, 36787 codec: 'adts', 36788 type: 'audio' 36789 }; // hook up the audio segment stream to the first track with aac data 36790 36791 pipeline.coalesceStream.numberOfTracks++; 36792 pipeline.audioSegmentStream = new _AudioSegmentStream(audioTrack, options); // Set up the final part of the audio pipeline 36793 36794 pipeline.adtsStream.pipe(pipeline.audioSegmentStream).pipe(pipeline.coalesceStream); 36795 } 36796 }); // Re-emit any data coming from the coalesce stream to the outside world 36797 36798 pipeline.coalesceStream.on('data', this.trigger.bind(this, 'data')); // Let the consumer know we have finished flushing the entire pipeline 36799 36800 pipeline.coalesceStream.on('done', this.trigger.bind(this, 'done')); 36801 }; 36802 36803 this.setupTsPipeline = function () { 36804 var pipeline = {}; 36805 this.transmuxPipeline_ = pipeline; 36806 pipeline.type = 'ts'; 36807 pipeline.metadataStream = new m2ts_1.MetadataStream(); // set up the parsing pipeline 36808 36809 pipeline.packetStream = new m2ts_1.TransportPacketStream(); 36810 pipeline.parseStream = new m2ts_1.TransportParseStream(); 36811 pipeline.elementaryStream = new m2ts_1.ElementaryStream(); 36812 pipeline.videoTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('video'); 36813 pipeline.audioTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('audio'); 36814 pipeline.timedMetadataTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('timed-metadata'); 36815 pipeline.adtsStream = new adts(); 36816 pipeline.h264Stream = new H264Stream(); 36817 pipeline.captionStream = new m2ts_1.CaptionStream(); 36818 pipeline.coalesceStream = new _CoalesceStream(options, pipeline.metadataStream); 36819 pipeline.headOfPipeline = pipeline.packetStream; // disassemble MPEG2-TS packets into elementary streams 36820 36821 pipeline.packetStream.pipe(pipeline.parseStream).pipe(pipeline.elementaryStream); // !!THIS ORDER IS IMPORTANT!! 36822 // demux the streams 36823 36824 pipeline.elementaryStream.pipe(pipeline.videoTimestampRolloverStream).pipe(pipeline.h264Stream); 36825 pipeline.elementaryStream.pipe(pipeline.audioTimestampRolloverStream).pipe(pipeline.adtsStream); 36826 pipeline.elementaryStream.pipe(pipeline.timedMetadataTimestampRolloverStream).pipe(pipeline.metadataStream).pipe(pipeline.coalesceStream); // Hook up CEA-608/708 caption stream 36827 36828 pipeline.h264Stream.pipe(pipeline.captionStream).pipe(pipeline.coalesceStream); 36829 pipeline.elementaryStream.on('data', function (data) { 36830 var i; 36831 36832 if (data.type === 'metadata') { 36833 i = data.tracks.length; // scan the tracks listed in the metadata 36834 36835 while (i--) { 36836 if (!videoTrack && data.tracks[i].type === 'video') { 36837 videoTrack = data.tracks[i]; 36838 videoTrack.timelineStartInfo.baseMediaDecodeTime = self.baseMediaDecodeTime; 36839 } else if (!audioTrack && data.tracks[i].type === 'audio') { 36840 audioTrack = data.tracks[i]; 36841 audioTrack.timelineStartInfo.baseMediaDecodeTime = self.baseMediaDecodeTime; 36842 } 36843 } // hook up the video segment stream to the first track with h264 data 36844 36845 36846 if (videoTrack && !pipeline.videoSegmentStream) { 36847 pipeline.coalesceStream.numberOfTracks++; 36848 pipeline.videoSegmentStream = new _VideoSegmentStream(videoTrack, options); 36849 pipeline.videoSegmentStream.on('timelineStartInfo', function (timelineStartInfo) { 36850 // When video emits timelineStartInfo data after a flush, we forward that 36851 // info to the AudioSegmentStream, if it exists, because video timeline 36852 // data takes precedence. 36853 if (audioTrack) { 36854 audioTrack.timelineStartInfo = timelineStartInfo; // On the first segment we trim AAC frames that exist before the 36855 // very earliest DTS we have seen in video because Chrome will 36856 // interpret any video track with a baseMediaDecodeTime that is 36857 // non-zero as a gap. 36858 36859 pipeline.audioSegmentStream.setEarliestDts(timelineStartInfo.dts); 36860 } 36861 }); 36862 pipeline.videoSegmentStream.on('processedGopsInfo', self.trigger.bind(self, 'gopInfo')); 36863 pipeline.videoSegmentStream.on('segmentTimingInfo', self.trigger.bind(self, 'videoSegmentTimingInfo')); 36864 pipeline.videoSegmentStream.on('baseMediaDecodeTime', function (baseMediaDecodeTime) { 36865 if (audioTrack) { 36866 pipeline.audioSegmentStream.setVideoBaseMediaDecodeTime(baseMediaDecodeTime); 36867 } 36868 }); // Set up the final part of the video pipeline 36869 36870 pipeline.h264Stream.pipe(pipeline.videoSegmentStream).pipe(pipeline.coalesceStream); 36871 } 36872 36873 if (audioTrack && !pipeline.audioSegmentStream) { 36874 // hook up the audio segment stream to the first track with aac data 36875 pipeline.coalesceStream.numberOfTracks++; 36876 pipeline.audioSegmentStream = new _AudioSegmentStream(audioTrack, options); // Set up the final part of the audio pipeline 36877 36878 pipeline.adtsStream.pipe(pipeline.audioSegmentStream).pipe(pipeline.coalesceStream); 36879 } 36880 } 36881 }); // Re-emit any data coming from the coalesce stream to the outside world 36882 36883 pipeline.coalesceStream.on('data', this.trigger.bind(this, 'data')); // Let the consumer know we have finished flushing the entire pipeline 36884 36885 pipeline.coalesceStream.on('done', this.trigger.bind(this, 'done')); 36886 }; // hook up the segment streams once track metadata is delivered 36887 36888 36889 this.setBaseMediaDecodeTime = function (baseMediaDecodeTime) { 36890 var pipeline = this.transmuxPipeline_; 36891 36892 if (!options.keepOriginalTimestamps) { 36893 this.baseMediaDecodeTime = baseMediaDecodeTime; 36894 } 36895 36896 if (audioTrack) { 36897 audioTrack.timelineStartInfo.dts = undefined; 36898 audioTrack.timelineStartInfo.pts = undefined; 36899 trackDecodeInfo.clearDtsInfo(audioTrack); 36900 36901 if (!options.keepOriginalTimestamps) { 36902 audioTrack.timelineStartInfo.baseMediaDecodeTime = baseMediaDecodeTime; 36903 } 36904 36905 if (pipeline.audioTimestampRolloverStream) { 36906 pipeline.audioTimestampRolloverStream.discontinuity(); 36907 } 36908 } 36909 36910 if (videoTrack) { 36911 if (pipeline.videoSegmentStream) { 36912 pipeline.videoSegmentStream.gopCache_ = []; 36913 pipeline.videoTimestampRolloverStream.discontinuity(); 36914 } 36915 36916 videoTrack.timelineStartInfo.dts = undefined; 36917 videoTrack.timelineStartInfo.pts = undefined; 36918 trackDecodeInfo.clearDtsInfo(videoTrack); 36919 pipeline.captionStream.reset(); 36920 36921 if (!options.keepOriginalTimestamps) { 36922 videoTrack.timelineStartInfo.baseMediaDecodeTime = baseMediaDecodeTime; 36923 } 36924 } 36925 36926 if (pipeline.timedMetadataTimestampRolloverStream) { 36927 pipeline.timedMetadataTimestampRolloverStream.discontinuity(); 36928 } 36929 }; 36930 36931 this.setAudioAppendStart = function (timestamp) { 36932 if (audioTrack) { 36933 this.transmuxPipeline_.audioSegmentStream.setAudioAppendStart(timestamp); 36934 } 36935 }; 36936 36937 this.alignGopsWith = function (gopsToAlignWith) { 36938 if (videoTrack && this.transmuxPipeline_.videoSegmentStream) { 36939 this.transmuxPipeline_.videoSegmentStream.alignGopsWith(gopsToAlignWith); 36940 } 36941 }; // feed incoming data to the front of the parsing pipeline 36942 36943 36944 this.push = function (data) { 36945 if (hasFlushed) { 36946 var isAac = isLikelyAacData$1(data); 36947 36948 if (isAac && this.transmuxPipeline_.type !== 'aac') { 36949 this.setupAacPipeline(); 36950 } else if (!isAac && this.transmuxPipeline_.type !== 'ts') { 36951 this.setupTsPipeline(); 36952 } 36953 36954 hasFlushed = false; 36955 } 36956 36957 this.transmuxPipeline_.headOfPipeline.push(data); 36958 }; // flush any buffered data 36959 36960 36961 this.flush = function () { 36962 hasFlushed = true; // Start at the top of the pipeline and flush all pending work 36963 36964 this.transmuxPipeline_.headOfPipeline.flush(); 36965 }; // Caption data has to be reset when seeking outside buffered range 36966 36967 36968 this.resetCaptions = function () { 36969 if (this.transmuxPipeline_.captionStream) { 36970 this.transmuxPipeline_.captionStream.reset(); 36971 } 36972 }; 36973 }; 36974 36975 _Transmuxer.prototype = new stream(); 36976 var transmuxer = { 36977 Transmuxer: _Transmuxer, 36978 VideoSegmentStream: _VideoSegmentStream, 36979 AudioSegmentStream: _AudioSegmentStream, 36980 AUDIO_PROPERTIES: AUDIO_PROPERTIES, 36981 VIDEO_PROPERTIES: VIDEO_PROPERTIES, 36982 // exported for testing 36983 generateVideoSegmentTimingInfo: generateVideoSegmentTimingInfo 36984 }; 36985 36986 var inspectMp4, 36987 _textifyMp, 36988 parseType$2 = probe.parseType, 36989 parseMp4Date = function parseMp4Date(seconds) { 36990 return new Date(seconds * 1000 - 2082844800000); 36991 }, 36992 parseSampleFlags = function parseSampleFlags(flags) { 36993 return { 36994 isLeading: (flags[0] & 0x0c) >>> 2, 36995 dependsOn: flags[0] & 0x03, 36996 isDependedOn: (flags[1] & 0xc0) >>> 6, 36997 hasRedundancy: (flags[1] & 0x30) >>> 4, 36998 paddingValue: (flags[1] & 0x0e) >>> 1, 36999 isNonSyncSample: flags[1] & 0x01, 37000 degradationPriority: flags[2] << 8 | flags[3] 37001 }; 37002 }, 37003 nalParse = function nalParse(avcStream) { 37004 var avcView = new DataView(avcStream.buffer, avcStream.byteOffset, avcStream.byteLength), 37005 result = [], 37006 i, 37007 length; 37008 37009 for (i = 0; i + 4 < avcStream.length; i += length) { 37010 length = avcView.getUint32(i); 37011 i += 4; // bail if this doesn't appear to be an H264 stream 37012 37013 if (length <= 0) { 37014 result.push('<span style=\'color:red;\'>MALFORMED DATA</span>'); 37015 continue; 37016 } 37017 37018 switch (avcStream[i] & 0x1F) { 37019 case 0x01: 37020 result.push('slice_layer_without_partitioning_rbsp'); 37021 break; 37022 37023 case 0x05: 37024 result.push('slice_layer_without_partitioning_rbsp_idr'); 37025 break; 37026 37027 case 0x06: 37028 result.push('sei_rbsp'); 37029 break; 37030 37031 case 0x07: 37032 result.push('seq_parameter_set_rbsp'); 37033 break; 37034 37035 case 0x08: 37036 result.push('pic_parameter_set_rbsp'); 37037 break; 37038 37039 case 0x09: 37040 result.push('access_unit_delimiter_rbsp'); 37041 break; 37042 37043 default: 37044 result.push('UNKNOWN NAL - ' + avcStream[i] & 0x1F); 37045 break; 37046 } 37047 } 37048 37049 return result; 37050 }, 37051 // registry of handlers for individual mp4 box types 37052 parse$1 = { 37053 // codingname, not a first-class box type. stsd entries share the 37054 // same format as real boxes so the parsing infrastructure can be 37055 // shared 37056 avc1: function avc1(data) { 37057 var view = new DataView(data.buffer, data.byteOffset, data.byteLength); 37058 return { 37059 dataReferenceIndex: view.getUint16(6), 37060 width: view.getUint16(24), 37061 height: view.getUint16(26), 37062 horizresolution: view.getUint16(28) + view.getUint16(30) / 16, 37063 vertresolution: view.getUint16(32) + view.getUint16(34) / 16, 37064 frameCount: view.getUint16(40), 37065 depth: view.getUint16(74), 37066 config: inspectMp4(data.subarray(78, data.byteLength)) 37067 }; 37068 }, 37069 avcC: function avcC(data) { 37070 var view = new DataView(data.buffer, data.byteOffset, data.byteLength), 37071 result = { 37072 configurationVersion: data[0], 37073 avcProfileIndication: data[1], 37074 profileCompatibility: data[2], 37075 avcLevelIndication: data[3], 37076 lengthSizeMinusOne: data[4] & 0x03, 37077 sps: [], 37078 pps: [] 37079 }, 37080 numOfSequenceParameterSets = data[5] & 0x1f, 37081 numOfPictureParameterSets, 37082 nalSize, 37083 offset, 37084 i; // iterate past any SPSs 37085 37086 offset = 6; 37087 37088 for (i = 0; i < numOfSequenceParameterSets; i++) { 37089 nalSize = view.getUint16(offset); 37090 offset += 2; 37091 result.sps.push(new Uint8Array(data.subarray(offset, offset + nalSize))); 37092 offset += nalSize; 37093 } // iterate past any PPSs 37094 37095 37096 numOfPictureParameterSets = data[offset]; 37097 offset++; 37098 37099 for (i = 0; i < numOfPictureParameterSets; i++) { 37100 nalSize = view.getUint16(offset); 37101 offset += 2; 37102 result.pps.push(new Uint8Array(data.subarray(offset, offset + nalSize))); 37103 offset += nalSize; 37104 } 37105 37106 return result; 37107 }, 37108 btrt: function btrt(data) { 37109 var view = new DataView(data.buffer, data.byteOffset, data.byteLength); 37110 return { 37111 bufferSizeDB: view.getUint32(0), 37112 maxBitrate: view.getUint32(4), 37113 avgBitrate: view.getUint32(8) 37114 }; 37115 }, 37116 esds: function esds(data) { 37117 return { 37118 version: data[0], 37119 flags: new Uint8Array(data.subarray(1, 4)), 37120 esId: data[6] << 8 | data[7], 37121 streamPriority: data[8] & 0x1f, 37122 decoderConfig: { 37123 objectProfileIndication: data[11], 37124 streamType: data[12] >>> 2 & 0x3f, 37125 bufferSize: data[13] << 16 | data[14] << 8 | data[15], 37126 maxBitrate: data[16] << 24 | data[17] << 16 | data[18] << 8 | data[19], 37127 avgBitrate: data[20] << 24 | data[21] << 16 | data[22] << 8 | data[23], 37128 decoderConfigDescriptor: { 37129 tag: data[24], 37130 length: data[25], 37131 audioObjectType: data[26] >>> 3 & 0x1f, 37132 samplingFrequencyIndex: (data[26] & 0x07) << 1 | data[27] >>> 7 & 0x01, 37133 channelConfiguration: data[27] >>> 3 & 0x0f 37134 } 37135 } 37136 }; 37137 }, 37138 ftyp: function ftyp(data) { 37139 var view = new DataView(data.buffer, data.byteOffset, data.byteLength), 37140 result = { 37141 majorBrand: parseType$2(data.subarray(0, 4)), 37142 minorVersion: view.getUint32(4), 37143 compatibleBrands: [] 37144 }, 37145 i = 8; 37146 37147 while (i < data.byteLength) { 37148 result.compatibleBrands.push(parseType$2(data.subarray(i, i + 4))); 37149 i += 4; 37150 } 37151 37152 return result; 37153 }, 37154 dinf: function dinf(data) { 37155 return { 37156 boxes: inspectMp4(data) 37157 }; 37158 }, 37159 dref: function dref(data) { 37160 return { 37161 version: data[0], 37162 flags: new Uint8Array(data.subarray(1, 4)), 37163 dataReferences: inspectMp4(data.subarray(8)) 37164 }; 37165 }, 37166 hdlr: function hdlr(data) { 37167 var view = new DataView(data.buffer, data.byteOffset, data.byteLength), 37168 result = { 37169 version: view.getUint8(0), 37170 flags: new Uint8Array(data.subarray(1, 4)), 37171 handlerType: parseType$2(data.subarray(8, 12)), 37172 name: '' 37173 }, 37174 i = 8; // parse out the name field 37175 37176 for (i = 24; i < data.byteLength; i++) { 37177 if (data[i] === 0x00) { 37178 // the name field is null-terminated 37179 i++; 37180 break; 37181 } 37182 37183 result.name += String.fromCharCode(data[i]); 37184 } // decode UTF-8 to javascript's internal representation 37185 // see http://ecmanaut.blogspot.com/2006/07/encoding-decoding-utf8-in-javascript.html 37186 37187 37188 result.name = decodeURIComponent(escape(result.name)); 37189 return result; 37190 }, 37191 mdat: function mdat(data) { 37192 return { 37193 byteLength: data.byteLength, 37194 nals: nalParse(data) 37195 }; 37196 }, 37197 mdhd: function mdhd(data) { 37198 var view = new DataView(data.buffer, data.byteOffset, data.byteLength), 37199 i = 4, 37200 language, 37201 result = { 37202 version: view.getUint8(0), 37203 flags: new Uint8Array(data.subarray(1, 4)), 37204 language: '' 37205 }; 37206 37207 if (result.version === 1) { 37208 i += 4; 37209 result.creationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes 37210 37211 i += 8; 37212 result.modificationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes 37213 37214 i += 4; 37215 result.timescale = view.getUint32(i); 37216 i += 8; 37217 result.duration = view.getUint32(i); // truncating top 4 bytes 37218 } else { 37219 result.creationTime = parseMp4Date(view.getUint32(i)); 37220 i += 4; 37221 result.modificationTime = parseMp4Date(view.getUint32(i)); 37222 i += 4; 37223 result.timescale = view.getUint32(i); 37224 i += 4; 37225 result.duration = view.getUint32(i); 37226 } 37227 37228 i += 4; // language is stored as an ISO-639-2/T code in an array of three 5-bit fields 37229 // each field is the packed difference between its ASCII value and 0x60 37230 37231 language = view.getUint16(i); 37232 result.language += String.fromCharCode((language >> 10) + 0x60); 37233 result.language += String.fromCharCode(((language & 0x03e0) >> 5) + 0x60); 37234 result.language += String.fromCharCode((language & 0x1f) + 0x60); 37235 return result; 37236 }, 37237 mdia: function mdia(data) { 37238 return { 37239 boxes: inspectMp4(data) 37240 }; 37241 }, 37242 mfhd: function mfhd(data) { 37243 return { 37244 version: data[0], 37245 flags: new Uint8Array(data.subarray(1, 4)), 37246 sequenceNumber: data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7] 37247 }; 37248 }, 37249 minf: function minf(data) { 37250 return { 37251 boxes: inspectMp4(data) 37252 }; 37253 }, 37254 // codingname, not a first-class box type. stsd entries share the 37255 // same format as real boxes so the parsing infrastructure can be 37256 // shared 37257 mp4a: function mp4a(data) { 37258 var view = new DataView(data.buffer, data.byteOffset, data.byteLength), 37259 result = { 37260 // 6 bytes reserved 37261 dataReferenceIndex: view.getUint16(6), 37262 // 4 + 4 bytes reserved 37263 channelcount: view.getUint16(16), 37264 samplesize: view.getUint16(18), 37265 // 2 bytes pre_defined 37266 // 2 bytes reserved 37267 samplerate: view.getUint16(24) + view.getUint16(26) / 65536 37268 }; // if there are more bytes to process, assume this is an ISO/IEC 37269 // 14496-14 MP4AudioSampleEntry and parse the ESDBox 37270 37271 if (data.byteLength > 28) { 37272 result.streamDescriptor = inspectMp4(data.subarray(28))[0]; 37273 } 37274 37275 return result; 37276 }, 37277 moof: function moof(data) { 37278 return { 37279 boxes: inspectMp4(data) 37280 }; 37281 }, 37282 moov: function moov(data) { 37283 return { 37284 boxes: inspectMp4(data) 37285 }; 37286 }, 37287 mvex: function mvex(data) { 37288 return { 37289 boxes: inspectMp4(data) 37290 }; 37291 }, 37292 mvhd: function mvhd(data) { 37293 var view = new DataView(data.buffer, data.byteOffset, data.byteLength), 37294 i = 4, 37295 result = { 37296 version: view.getUint8(0), 37297 flags: new Uint8Array(data.subarray(1, 4)) 37298 }; 37299 37300 if (result.version === 1) { 37301 i += 4; 37302 result.creationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes 37303 37304 i += 8; 37305 result.modificationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes 37306 37307 i += 4; 37308 result.timescale = view.getUint32(i); 37309 i += 8; 37310 result.duration = view.getUint32(i); // truncating top 4 bytes 37311 } else { 37312 result.creationTime = parseMp4Date(view.getUint32(i)); 37313 i += 4; 37314 result.modificationTime = parseMp4Date(view.getUint32(i)); 37315 i += 4; 37316 result.timescale = view.getUint32(i); 37317 i += 4; 37318 result.duration = view.getUint32(i); 37319 } 37320 37321 i += 4; // convert fixed-point, base 16 back to a number 37322 37323 result.rate = view.getUint16(i) + view.getUint16(i + 2) / 16; 37324 i += 4; 37325 result.volume = view.getUint8(i) + view.getUint8(i + 1) / 8; 37326 i += 2; 37327 i += 2; 37328 i += 2 * 4; 37329 result.matrix = new Uint32Array(data.subarray(i, i + 9 * 4)); 37330 i += 9 * 4; 37331 i += 6 * 4; 37332 result.nextTrackId = view.getUint32(i); 37333 return result; 37334 }, 37335 pdin: function pdin(data) { 37336 var view = new DataView(data.buffer, data.byteOffset, data.byteLength); 37337 return { 37338 version: view.getUint8(0), 37339 flags: new Uint8Array(data.subarray(1, 4)), 37340 rate: view.getUint32(4), 37341 initialDelay: view.getUint32(8) 37342 }; 37343 }, 37344 sdtp: function sdtp(data) { 37345 var result = { 37346 version: data[0], 37347 flags: new Uint8Array(data.subarray(1, 4)), 37348 samples: [] 37349 }, 37350 i; 37351 37352 for (i = 4; i < data.byteLength; i++) { 37353 result.samples.push({ 37354 dependsOn: (data[i] & 0x30) >> 4, 37355 isDependedOn: (data[i] & 0x0c) >> 2, 37356 hasRedundancy: data[i] & 0x03 37357 }); 37358 } 37359 37360 return result; 37361 }, 37362 sidx: function sidx(data) { 37363 var view = new DataView(data.buffer, data.byteOffset, data.byteLength), 37364 result = { 37365 version: data[0], 37366 flags: new Uint8Array(data.subarray(1, 4)), 37367 references: [], 37368 referenceId: view.getUint32(4), 37369 timescale: view.getUint32(8), 37370 earliestPresentationTime: view.getUint32(12), 37371 firstOffset: view.getUint32(16) 37372 }, 37373 referenceCount = view.getUint16(22), 37374 i; 37375 37376 for (i = 24; referenceCount; i += 12, referenceCount--) { 37377 result.references.push({ 37378 referenceType: (data[i] & 0x80) >>> 7, 37379 referencedSize: view.getUint32(i) & 0x7FFFFFFF, 37380 subsegmentDuration: view.getUint32(i + 4), 37381 startsWithSap: !!(data[i + 8] & 0x80), 37382 sapType: (data[i + 8] & 0x70) >>> 4, 37383 sapDeltaTime: view.getUint32(i + 8) & 0x0FFFFFFF 37384 }); 37385 } 37386 37387 return result; 37388 }, 37389 smhd: function smhd(data) { 37390 return { 37391 version: data[0], 37392 flags: new Uint8Array(data.subarray(1, 4)), 37393 balance: data[4] + data[5] / 256 37394 }; 37395 }, 37396 stbl: function stbl(data) { 37397 return { 37398 boxes: inspectMp4(data) 37399 }; 37400 }, 37401 stco: function stco(data) { 37402 var view = new DataView(data.buffer, data.byteOffset, data.byteLength), 37403 result = { 37404 version: data[0], 37405 flags: new Uint8Array(data.subarray(1, 4)), 37406 chunkOffsets: [] 37407 }, 37408 entryCount = view.getUint32(4), 37409 i; 37410 37411 for (i = 8; entryCount; i += 4, entryCount--) { 37412 result.chunkOffsets.push(view.getUint32(i)); 37413 } 37414 37415 return result; 37416 }, 37417 stsc: function stsc(data) { 37418 var view = new DataView(data.buffer, data.byteOffset, data.byteLength), 37419 entryCount = view.getUint32(4), 37420 result = { 37421 version: data[0], 37422 flags: new Uint8Array(data.subarray(1, 4)), 37423 sampleToChunks: [] 37424 }, 37425 i; 37426 37427 for (i = 8; entryCount; i += 12, entryCount--) { 37428 result.sampleToChunks.push({ 37429 firstChunk: view.getUint32(i), 37430 samplesPerChunk: view.getUint32(i + 4), 37431 sampleDescriptionIndex: view.getUint32(i + 8) 37432 }); 37433 } 37434 37435 return result; 37436 }, 37437 stsd: function stsd(data) { 37438 return { 37439 version: data[0], 37440 flags: new Uint8Array(data.subarray(1, 4)), 37441 sampleDescriptions: inspectMp4(data.subarray(8)) 37442 }; 37443 }, 37444 stsz: function stsz(data) { 37445 var view = new DataView(data.buffer, data.byteOffset, data.byteLength), 37446 result = { 37447 version: data[0], 37448 flags: new Uint8Array(data.subarray(1, 4)), 37449 sampleSize: view.getUint32(4), 37450 entries: [] 37451 }, 37452 i; 37453 37454 for (i = 12; i < data.byteLength; i += 4) { 37455 result.entries.push(view.getUint32(i)); 37456 } 37457 37458 return result; 37459 }, 37460 stts: function stts(data) { 37461 var view = new DataView(data.buffer, data.byteOffset, data.byteLength), 37462 result = { 37463 version: data[0], 37464 flags: new Uint8Array(data.subarray(1, 4)), 37465 timeToSamples: [] 37466 }, 37467 entryCount = view.getUint32(4), 37468 i; 37469 37470 for (i = 8; entryCount; i += 8, entryCount--) { 37471 result.timeToSamples.push({ 37472 sampleCount: view.getUint32(i), 37473 sampleDelta: view.getUint32(i + 4) 37474 }); 37475 } 37476 37477 return result; 37478 }, 37479 styp: function styp(data) { 37480 return parse$1.ftyp(data); 37481 }, 37482 tfdt: function tfdt(data) { 37483 var result = { 37484 version: data[0], 37485 flags: new Uint8Array(data.subarray(1, 4)), 37486 baseMediaDecodeTime: data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7] 37487 }; 37488 37489 if (result.version === 1) { 37490 result.baseMediaDecodeTime *= Math.pow(2, 32); 37491 result.baseMediaDecodeTime += data[8] << 24 | data[9] << 16 | data[10] << 8 | data[11]; 37492 } 37493 37494 return result; 37495 }, 37496 tfhd: function tfhd(data) { 37497 var view = new DataView(data.buffer, data.byteOffset, data.byteLength), 37498 result = { 37499 version: data[0], 37500 flags: new Uint8Array(data.subarray(1, 4)), 37501 trackId: view.getUint32(4) 37502 }, 37503 baseDataOffsetPresent = result.flags[2] & 0x01, 37504 sampleDescriptionIndexPresent = result.flags[2] & 0x02, 37505 defaultSampleDurationPresent = result.flags[2] & 0x08, 37506 defaultSampleSizePresent = result.flags[2] & 0x10, 37507 defaultSampleFlagsPresent = result.flags[2] & 0x20, 37508 durationIsEmpty = result.flags[0] & 0x010000, 37509 defaultBaseIsMoof = result.flags[0] & 0x020000, 37510 i; 37511 i = 8; 37512 37513 if (baseDataOffsetPresent) { 37514 i += 4; // truncate top 4 bytes 37515 // FIXME: should we read the full 64 bits? 37516 37517 result.baseDataOffset = view.getUint32(12); 37518 i += 4; 37519 } 37520 37521 if (sampleDescriptionIndexPresent) { 37522 result.sampleDescriptionIndex = view.getUint32(i); 37523 i += 4; 37524 } 37525 37526 if (defaultSampleDurationPresent) { 37527 result.defaultSampleDuration = view.getUint32(i); 37528 i += 4; 37529 } 37530 37531 if (defaultSampleSizePresent) { 37532 result.defaultSampleSize = view.getUint32(i); 37533 i += 4; 37534 } 37535 37536 if (defaultSampleFlagsPresent) { 37537 result.defaultSampleFlags = view.getUint32(i); 37538 } 37539 37540 if (durationIsEmpty) { 37541 result.durationIsEmpty = true; 37542 } 37543 37544 if (!baseDataOffsetPresent && defaultBaseIsMoof) { 37545 result.baseDataOffsetIsMoof = true; 37546 } 37547 37548 return result; 37549 }, 37550 tkhd: function tkhd(data) { 37551 var view = new DataView(data.buffer, data.byteOffset, data.byteLength), 37552 i = 4, 37553 result = { 37554 version: view.getUint8(0), 37555 flags: new Uint8Array(data.subarray(1, 4)) 37556 }; 37557 37558 if (result.version === 1) { 37559 i += 4; 37560 result.creationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes 37561 37562 i += 8; 37563 result.modificationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes 37564 37565 i += 4; 37566 result.trackId = view.getUint32(i); 37567 i += 4; 37568 i += 8; 37569 result.duration = view.getUint32(i); // truncating top 4 bytes 37570 } else { 37571 result.creationTime = parseMp4Date(view.getUint32(i)); 37572 i += 4; 37573 result.modificationTime = parseMp4Date(view.getUint32(i)); 37574 i += 4; 37575 result.trackId = view.getUint32(i); 37576 i += 4; 37577 i += 4; 37578 result.duration = view.getUint32(i); 37579 } 37580 37581 i += 4; 37582 i += 2 * 4; 37583 result.layer = view.getUint16(i); 37584 i += 2; 37585 result.alternateGroup = view.getUint16(i); 37586 i += 2; // convert fixed-point, base 16 back to a number 37587 37588 result.volume = view.getUint8(i) + view.getUint8(i + 1) / 8; 37589 i += 2; 37590 i += 2; 37591 result.matrix = new Uint32Array(data.subarray(i, i + 9 * 4)); 37592 i += 9 * 4; 37593 result.width = view.getUint16(i) + view.getUint16(i + 2) / 16; 37594 i += 4; 37595 result.height = view.getUint16(i) + view.getUint16(i + 2) / 16; 37596 return result; 37597 }, 37598 traf: function traf(data) { 37599 return { 37600 boxes: inspectMp4(data) 37601 }; 37602 }, 37603 trak: function trak(data) { 37604 return { 37605 boxes: inspectMp4(data) 37606 }; 37607 }, 37608 trex: function trex(data) { 37609 var view = new DataView(data.buffer, data.byteOffset, data.byteLength); 37610 return { 37611 version: data[0], 37612 flags: new Uint8Array(data.subarray(1, 4)), 37613 trackId: view.getUint32(4), 37614 defaultSampleDescriptionIndex: view.getUint32(8), 37615 defaultSampleDuration: view.getUint32(12), 37616 defaultSampleSize: view.getUint32(16), 37617 sampleDependsOn: data[20] & 0x03, 37618 sampleIsDependedOn: (data[21] & 0xc0) >> 6, 37619 sampleHasRedundancy: (data[21] & 0x30) >> 4, 37620 samplePaddingValue: (data[21] & 0x0e) >> 1, 37621 sampleIsDifferenceSample: !!(data[21] & 0x01), 37622 sampleDegradationPriority: view.getUint16(22) 37623 }; 37624 }, 37625 trun: function trun(data) { 37626 var result = { 37627 version: data[0], 37628 flags: new Uint8Array(data.subarray(1, 4)), 37629 samples: [] 37630 }, 37631 view = new DataView(data.buffer, data.byteOffset, data.byteLength), 37632 // Flag interpretation 37633 dataOffsetPresent = result.flags[2] & 0x01, 37634 // compare with 2nd byte of 0x1 37635 firstSampleFlagsPresent = result.flags[2] & 0x04, 37636 // compare with 2nd byte of 0x4 37637 sampleDurationPresent = result.flags[1] & 0x01, 37638 // compare with 2nd byte of 0x100 37639 sampleSizePresent = result.flags[1] & 0x02, 37640 // compare with 2nd byte of 0x200 37641 sampleFlagsPresent = result.flags[1] & 0x04, 37642 // compare with 2nd byte of 0x400 37643 sampleCompositionTimeOffsetPresent = result.flags[1] & 0x08, 37644 // compare with 2nd byte of 0x800 37645 sampleCount = view.getUint32(4), 37646 offset = 8, 37647 sample; 37648 37649 if (dataOffsetPresent) { 37650 // 32 bit signed integer 37651 result.dataOffset = view.getInt32(offset); 37652 offset += 4; 37653 } // Overrides the flags for the first sample only. The order of 37654 // optional values will be: duration, size, compositionTimeOffset 37655 37656 37657 if (firstSampleFlagsPresent && sampleCount) { 37658 sample = { 37659 flags: parseSampleFlags(data.subarray(offset, offset + 4)) 37660 }; 37661 offset += 4; 37662 37663 if (sampleDurationPresent) { 37664 sample.duration = view.getUint32(offset); 37665 offset += 4; 37666 } 37667 37668 if (sampleSizePresent) { 37669 sample.size = view.getUint32(offset); 37670 offset += 4; 37671 } 37672 37673 if (sampleCompositionTimeOffsetPresent) { 37674 // Note: this should be a signed int if version is 1 37675 sample.compositionTimeOffset = view.getUint32(offset); 37676 offset += 4; 37677 } 37678 37679 result.samples.push(sample); 37680 sampleCount--; 37681 } 37682 37683 while (sampleCount--) { 37684 sample = {}; 37685 37686 if (sampleDurationPresent) { 37687 sample.duration = view.getUint32(offset); 37688 offset += 4; 37689 } 37690 37691 if (sampleSizePresent) { 37692 sample.size = view.getUint32(offset); 37693 offset += 4; 37694 } 37695 37696 if (sampleFlagsPresent) { 37697 sample.flags = parseSampleFlags(data.subarray(offset, offset + 4)); 37698 offset += 4; 37699 } 37700 37701 if (sampleCompositionTimeOffsetPresent) { 37702 // Note: this should be a signed int if version is 1 37703 sample.compositionTimeOffset = view.getUint32(offset); 37704 offset += 4; 37705 } 37706 37707 result.samples.push(sample); 37708 } 37709 37710 return result; 37711 }, 37712 'url ': function url(data) { 37713 return { 37714 version: data[0], 37715 flags: new Uint8Array(data.subarray(1, 4)) 37716 }; 37717 }, 37718 vmhd: function vmhd(data) { 37719 var view = new DataView(data.buffer, data.byteOffset, data.byteLength); 37720 return { 37721 version: data[0], 37722 flags: new Uint8Array(data.subarray(1, 4)), 37723 graphicsmode: view.getUint16(4), 37724 opcolor: new Uint16Array([view.getUint16(6), view.getUint16(8), view.getUint16(10)]) 37725 }; 37726 } 37727 }; 37728 /** 37729 * Return a javascript array of box objects parsed from an ISO base 37730 * media file. 37731 * @param data {Uint8Array} the binary data of the media to be inspected 37732 * @return {array} a javascript array of potentially nested box objects 37733 */ 37734 37735 37736 inspectMp4 = function inspectMp4(data) { 37737 var i = 0, 37738 result = [], 37739 view, 37740 size, 37741 type, 37742 end, 37743 box; // Convert data from Uint8Array to ArrayBuffer, to follow Dataview API 37744 37745 var ab = new ArrayBuffer(data.length); 37746 var v = new Uint8Array(ab); 37747 37748 for (var z = 0; z < data.length; ++z) { 37749 v[z] = data[z]; 37750 } 37751 37752 view = new DataView(ab); 37753 37754 while (i < data.byteLength) { 37755 // parse box data 37756 size = view.getUint32(i); 37757 type = parseType$2(data.subarray(i + 4, i + 8)); 37758 end = size > 1 ? i + size : data.byteLength;
vendor: 4,013 bytes, lines 37758-37877
37758 // parse type-specific data 37759 37760 box = (parse$1[type] || function (data) { 37761 return { 37762 data: data 37763 }; 37764 })(data.subarray(i + 8, end)); 37765 37766 box.size = size; 37767 box.type = type; // store this box and move to the next 37768 37769 result.push(box); 37770 i = end; 37771 } 37772 37773 return result; 37774 }; 37775 /** 37776 * Returns a textual representation of the javascript represtentation 37777 * of an MP4 file. You can use it as an alternative to 37778 * JSON.stringify() to compare inspected MP4s. 37779 * @param inspectedMp4 {array} the parsed array of boxes in an MP4 37780 * file 37781 * @param depth {number} (optional) the number of ancestor boxes of 37782 * the elements of inspectedMp4. Assumed to be zero if unspecified. 37783 * @return {string} a text representation of the parsed MP4 37784 */ 37785 37786 37787 _textifyMp = function textifyMp4(inspectedMp4, depth) { 37788 var indent; 37789 depth = depth || 0; 37790 indent = new Array(depth * 2 + 1).join(' '); // iterate over all the boxes 37791 37792 return inspectedMp4.map(function (box, index) { 37793 // list the box type first at the current indentation level 37794 return indent + box.type + '\n' + // the type is already included and handle child boxes separately 37795 Object.keys(box).filter(function (key) { 37796 return key !== 'type' && key !== 'boxes'; // output all the box properties 37797 }).map(function (key) { 37798 var prefix = indent + ' ' + key + ': ', 37799 value = box[key]; // print out raw bytes as hexademical 37800 37801 if (value instanceof Uint8Array || value instanceof Uint32Array) { 37802 var bytes = Array.prototype.slice.call(new Uint8Array(value.buffer, value.byteOffset, value.byteLength)).map(function (byte) { 37803 return ' ' + ('00' + byte.toString(16)).slice(-2); 37804 }).join('').match(/.{1,24}/g); 37805 37806 if (!bytes) { 37807 return prefix + '<>'; 37808 } 37809 37810 if (bytes.length === 1) { 37811 return prefix + '<' + bytes.join('').slice(1) + '>'; 37812 } 37813 37814 return prefix + '<\n' + bytes.map(function (line) { 37815 return indent + ' ' + line; 37816 }).join('\n') + '\n' + indent + ' >'; 37817 } // stringify generic objects 37818 37819 37820 return prefix + JSON.stringify(value, null, 2).split('\n').map(function (line, index) { 37821 if (index === 0) { 37822 return line; 37823 } 37824 37825 return indent + ' ' + line; 37826 }).join('\n'); 37827 }).join('\n') + ( // recursively textify the child boxes 37828 box.boxes ? '\n' + _textifyMp(box.boxes, depth + 1) : ''); 37829 }).join('\n'); 37830 }; 37831 37832 var mp4Inspector = { 37833 inspect: inspectMp4, 37834 textify: _textifyMp, 37835 parseTfdt: parse$1.tfdt, 37836 parseHdlr: parse$1.hdlr, 37837 parseTfhd: parse$1.tfhd, 37838 parseTrun: parse$1.trun, 37839 parseSidx: parse$1.sidx 37840 }; 37841 37842 var discardEmulationPreventionBytes$1 = captionPacketParser.discardEmulationPreventionBytes; 37843 var CaptionStream$1 = captionStream.CaptionStream; 37844 /** 37845 * Maps an offset in the mdat to a sample based on the the size of the samples. 37846 * Assumes that `parseSamples` has been called first. 37847 * 37848 * @param {Number} offset - The offset into the mdat 37849 * @param {Object[]} samples - An array of samples, parsed using `parseSamples` 37850 * @return {?Object} The matching sample, or null if no match was found. 37851 * 37852 * @see ISO-BMFF-12/2015, Section 8.8.8 37853 **/ 37854 37855 var mapToSample = function mapToSample(offset, samples) { 37856 var approximateOffset = offset; 37857 37858 for (var i = 0; i < samples.length; i++) { 37859 var sample = samples[i]; 37860 37861 if (approximateOffset < sample.size) { 37862 return sample; 37863 } 37864 37865 approximateOffset -= sample.size; 37866 } 37867 37868 return null; 37869 }; 37870 /** 37871 * Finds SEI nal units contained in a Media Data Box. 37872 * Assumes that `parseSamples` has been called first. 37873 * 37874 * @param {Uint8Array} avcStream - The bytes of the mdat 37875 * @param {Object[]} samples - The samples parsed out by `parseSamples` 37876 * @param {Number} trackId - The trackId of this video track 37877 * @return {Object[]}
vendor: 11,803 bytes, lines 37877-38247
37877 seiNals - the parsed SEI NALUs found. 37878 * The contents of the seiNal should match what is expected by 37879 * CaptionStream.push (nalUnitType, size, data, escapedRBSP, pts, dts) 37880 * 37881 * @see ISO-BMFF-12/2015, Section 8.1.1 37882 * @see Rec. ITU-T H.264, 7.3.2.3.1 37883 **/ 37884 37885 37886 var findSeiNals = function findSeiNals(avcStream, samples, trackId) { 37887 var avcView = new DataView(avcStream.buffer, avcStream.byteOffset, avcStream.byteLength), 37888 result = [], 37889 seiNal, 37890 i, 37891 length, 37892 lastMatchedSample; 37893 37894 for (i = 0; i + 4 < avcStream.length; i += length) { 37895 length = avcView.getUint32(i); 37896 i += 4; // Bail if this doesn't appear to be an H264 stream 37897 37898 if (length <= 0) { 37899 continue; 37900 } 37901 37902 switch (avcStream[i] & 0x1F) { 37903 case 0x06: 37904 var data = avcStream.subarray(i + 1, i + 1 + length); 37905 var matchingSample = mapToSample(i, samples); 37906 seiNal = { 37907 nalUnitType: 'sei_rbsp', 37908 size: length, 37909 data: data, 37910 escapedRBSP: discardEmulationPreventionBytes$1(data), 37911 trackId: trackId 37912 }; 37913 37914 if (matchingSample) { 37915 seiNal.pts = matchingSample.pts; 37916 seiNal.dts = matchingSample.dts; 37917 lastMatchedSample = matchingSample; 37918 } else { 37919 // If a matching sample cannot be found, use the last 37920 // sample's values as they should be as close as possible 37921 seiNal.pts = lastMatchedSample.pts; 37922 seiNal.dts = lastMatchedSample.dts; 37923 } 37924 37925 result.push(seiNal); 37926 break; 37927 37928 default: 37929 break; 37930 } 37931 } 37932 37933 return result; 37934 }; 37935 /** 37936 * Parses sample information out of Track Run Boxes and calculates 37937 * the absolute presentation and decode timestamps of each sample. 37938 * 37939 * @param {Array<Uint8Array>} truns - The Trun Run boxes to be parsed 37940 * @param {Number} baseMediaDecodeTime - base media decode time from tfdt 37941 @see ISO-BMFF-12/2015, Section 8.8.12 37942 * @param {Object} tfhd - The parsed Track Fragment Header 37943 * @see inspect.parseTfhd 37944 * @return {Object[]} the parsed samples 37945 * 37946 * @see ISO-BMFF-12/2015, Section 8.8.8 37947 **/ 37948 37949 37950 var parseSamples = function parseSamples(truns, baseMediaDecodeTime, tfhd) { 37951 var currentDts = baseMediaDecodeTime; 37952 var defaultSampleDuration = tfhd.defaultSampleDuration || 0; 37953 var defaultSampleSize = tfhd.defaultSampleSize || 0; 37954 var trackId = tfhd.trackId; 37955 var allSamples = []; 37956 truns.forEach(function (trun) { 37957 // Note: We currently do not parse the sample table as well 37958 // as the trun. It's possible some sources will require this. 37959 // moov > trak > mdia > minf > stbl 37960 var trackRun = mp4Inspector.parseTrun(trun); 37961 var samples = trackRun.samples; 37962 samples.forEach(function (sample) { 37963 if (sample.duration === undefined) { 37964 sample.duration = defaultSampleDuration; 37965 } 37966 37967 if (sample.size === undefined) { 37968 sample.size = defaultSampleSize; 37969 } 37970 37971 sample.trackId = trackId; 37972 sample.dts = currentDts; 37973 37974 if (sample.compositionTimeOffset === undefined) { 37975 sample.compositionTimeOffset = 0; 37976 } 37977 37978 sample.pts = currentDts + sample.compositionTimeOffset; 37979 currentDts += sample.duration; 37980 }); 37981 allSamples = allSamples.concat(samples); 37982 }); 37983 return allSamples; 37984 }; 37985 /** 37986 * Parses out caption nals from an FMP4 segment's video tracks. 37987 * 37988 * @param {Uint8Array} segment - The bytes of a single segment 37989 * @param {Number} videoTrackId - The trackId of a video track in the segment 37990 * @return {Object.<Number, Object[]>} A mapping of video trackId to 37991 * a list of seiNals found in that track 37992 **/ 37993 37994 37995 var parseCaptionNals = function parseCaptionNals(segment, videoTrackId) { 37996 // To get the samples 37997 var trafs = probe.findBox(segment, ['moof', 'traf']); // To get SEI NAL units 37998 37999 var mdats = probe.findBox(segment, ['mdat']); 38000 var captionNals = {}; 38001 var mdatTrafPairs = []; // Pair up each traf with a mdat as moofs and mdats are in pairs 38002 38003 mdats.forEach(function (mdat, index) { 38004 var matchingTraf = trafs[index]; 38005 mdatTrafPairs.push({ 38006 mdat: mdat, 38007 traf: matchingTraf 38008 }); 38009 }); 38010 mdatTrafPairs.forEach(function (pair) { 38011 var mdat = pair.mdat; 38012 var traf = pair.traf; 38013 var tfhd = probe.findBox(traf, ['tfhd']); // Exactly 1 tfhd per traf 38014 38015 var headerInfo = mp4Inspector.parseTfhd(tfhd[0]); 38016 var trackId = headerInfo.trackId; 38017 var tfdt = probe.findBox(traf, ['tfdt']); // Either 0 or 1 tfdt per traf 38018 38019 var baseMediaDecodeTime = tfdt.length > 0 ? mp4Inspector.parseTfdt(tfdt[0]).baseMediaDecodeTime : 0; 38020 var truns = probe.findBox(traf, ['trun']); 38021 var samples; 38022 var seiNals; // Only parse video data for the chosen video track 38023 38024 if (videoTrackId === trackId && truns.length > 0) { 38025 samples = parseSamples(truns, baseMediaDecodeTime, headerInfo); 38026 seiNals = findSeiNals(mdat, samples, trackId); 38027 38028 if (!captionNals[trackId]) { 38029 captionNals[trackId] = []; 38030 } 38031 38032 captionNals[trackId] = captionNals[trackId].concat(seiNals); 38033 } 38034 }); 38035 return captionNals; 38036 }; 38037 /** 38038 * Parses out inband captions from an MP4 container and returns 38039 * caption objects that can be used by WebVTT and the TextTrack API. 38040 * @see https://developer.mozilla.org/en-US/docs/Web/API/VTTCue 38041 * @see https://developer.mozilla.org/en-US/docs/Web/API/TextTrack 38042 * Assumes that `probe.getVideoTrackIds` and `probe.timescale` have been called first 38043 * 38044 * @param {Uint8Array} segment - The fmp4 segment containing embedded captions 38045 * @param {Number} trackId - The id of the video track to parse 38046 * @param {Number} timescale - The timescale for the video track from the init segment 38047 * 38048 * @return {?Object[]} parsedCaptions - A list of captions or null if no video tracks 38049 * @return {Number} parsedCaptions[].startTime - The time to show the caption in seconds 38050 * @return {Number} parsedCaptions[].endTime - The time to stop showing the caption in seconds 38051 * @return {String} parsedCaptions[].text - The visible content of the caption 38052 **/ 38053 38054 38055 var parseEmbeddedCaptions = function parseEmbeddedCaptions(segment, trackId, timescale) { 38056 var seiNals; 38057 38058 if (!trackId) { 38059 return null; 38060 } 38061 38062 seiNals = parseCaptionNals(segment, trackId); 38063 return { 38064 seiNals: seiNals[trackId], 38065 timescale: timescale 38066 }; 38067 }; 38068 /** 38069 * Converts SEI NALUs into captions that can be used by video.js 38070 **/ 38071 38072 38073 var CaptionParser = function CaptionParser() { 38074 var isInitialized = false; 38075 var captionStream$$1; // Stores segments seen before trackId and timescale are set 38076 38077 var segmentCache; // Stores video track ID of the track being parsed 38078 38079 var trackId; // Stores the timescale of the track being parsed 38080 38081 var timescale; // Stores captions parsed so far 38082 38083 var parsedCaptions; 38084 /** 38085 * A method to indicate whether a CaptionParser has been initalized 38086 * @returns {Boolean} 38087 **/ 38088 38089 this.isInitialized = function () { 38090 return isInitialized; 38091 }; 38092 /** 38093 * Initializes the underlying CaptionStream, SEI NAL parsing 38094 * and management, and caption collection 38095 **/ 38096 38097 38098 this.init = function () { 38099 captionStream$$1 = new CaptionStream$1(); 38100 isInitialized = true; // Collect dispatched captions 38101 38102 captionStream$$1.on('data', function (event) { 38103 // Convert to seconds in the source's timescale 38104 event.startTime = event.startPts / timescale; 38105 event.endTime = event.endPts / timescale; 38106 parsedCaptions.captions.push(event); 38107 parsedCaptions.captionStreams[event.stream] = true; 38108 }); 38109 }; 38110 /** 38111 * Determines if a new video track will be selected 38112 * or if the timescale changed 38113 * @return {Boolean} 38114 **/ 38115 38116 38117 this.isNewInit = function (videoTrackIds, timescales) { 38118 if (videoTrackIds && videoTrackIds.length === 0 || timescales && typeof timescales === 'object' && Object.keys(timescales).length === 0) { 38119 return false; 38120 } 38121 38122 return trackId !== videoTrackIds[0] || timescale !== timescales[trackId]; 38123 }; 38124 /** 38125 * Parses out SEI captions and interacts with underlying 38126 * CaptionStream to return dispatched captions 38127 * 38128 * @param {Uint8Array} segment - The fmp4 segment containing embedded captions 38129 * @param {Number[]} videoTrackIds - A list of video tracks found in the init segment 38130 * @param {Object.<Number, Number>} timescales - The timescales found in the init segment 38131 * @see parseEmbeddedCaptions 38132 * @see m2ts/caption-stream.js 38133 **/ 38134 38135 38136 this.parse = function (segment, videoTrackIds, timescales) { 38137 var parsedData; 38138 38139 if (!this.isInitialized()) { 38140 return null; // This is not likely to be a video segment 38141 } else if (!videoTrackIds || !timescales) { 38142 return null; 38143 } else if (this.isNewInit(videoTrackIds, timescales)) { 38144 // Use the first video track only as there is no 38145 // mechanism to switch to other video tracks 38146 trackId = videoTrackIds[0]; 38147 timescale = timescales[trackId]; // If an init segment has not been seen yet, hold onto segment 38148 // data until we have one 38149 } else if (!trackId || !timescale) { 38150 segmentCache.push(segment); 38151 return null; 38152 } // Now that a timescale and trackId is set, parse cached segments 38153 38154 38155 while (segmentCache.length > 0) { 38156 var cachedSegment = segmentCache.shift(); 38157 this.parse(cachedSegment, videoTrackIds, timescales); 38158 } 38159 38160 parsedData = parseEmbeddedCaptions(segment, trackId, timescale); 38161 38162 if (parsedData === null || !parsedData.seiNals) { 38163 return null; 38164 } 38165 38166 this.pushNals(parsedData.seiNals); // Force the parsed captions to be dispatched 38167 38168 this.flushStream(); 38169 return parsedCaptions; 38170 }; 38171 /** 38172 * Pushes SEI NALUs onto CaptionStream 38173 * @param {Object[]} nals - A list of SEI nals parsed using `parseCaptionNals` 38174 * Assumes that `parseCaptionNals` has been called first 38175 * @see m2ts/caption-stream.js 38176 **/ 38177 38178 38179 this.pushNals = function (nals) { 38180 if (!this.isInitialized() || !nals || nals.length === 0) { 38181 return null; 38182 } 38183 38184 nals.forEach(function (nal) { 38185 captionStream$$1.push(nal); 38186 }); 38187 }; 38188 /** 38189 * Flushes underlying CaptionStream to dispatch processed, displayable captions 38190 * @see m2ts/caption-stream.js 38191 **/ 38192 38193 38194 this.flushStream = function () { 38195 if (!this.isInitialized()) { 38196 return null; 38197 } 38198 38199 captionStream$$1.flush(); 38200 }; 38201 /** 38202 * Reset caption buckets for new data 38203 **/ 38204 38205 38206 this.clearParsedCaptions = function () { 38207 parsedCaptions.captions = []; 38208 parsedCaptions.captionStreams = {}; 38209 }; 38210 /** 38211 * Resets underlying CaptionStream 38212 * @see m2ts/caption-stream.js 38213 **/ 38214 38215 38216 this.resetCaptionStream = function () { 38217 if (!this.isInitialized()) { 38218 return null; 38219 } 38220 38221 captionStream$$1.reset(); 38222 }; 38223 /** 38224 * Convenience method to clear all captions flushed from the 38225 * CaptionStream and still being parsed 38226 * @see m2ts/caption-stream.js 38227 **/ 38228 38229 38230 this.clearAllCaptions = function () { 38231 this.clearParsedCaptions(); 38232 this.resetCaptionStream(); 38233 }; 38234 /** 38235 * Reset caption parser 38236 **/ 38237 38238 38239 this.reset = function () { 38240 segmentCache = []; 38241 trackId = null; 38242 timescale = null; 38243 38244 if (!parsedCaptions) { 38245 parsedCaptions = { 38246 captions: [], 38247 // CC1, CC2, CC3, CC4
vendor: 4,587 bytes, lines 38248-38399
38248 captionStreams: {} 38249 }; 38250 } else { 38251 this.clearParsedCaptions(); 38252 } 38253 38254 this.resetCaptionStream(); 38255 }; 38256 38257 this.reset(); 38258 }; 38259 38260 var captionParser = CaptionParser; 38261 38262 var mp4 = { 38263 generator: mp4Generator, 38264 probe: probe, 38265 Transmuxer: transmuxer.Transmuxer, 38266 AudioSegmentStream: transmuxer.AudioSegmentStream, 38267 VideoSegmentStream: transmuxer.VideoSegmentStream, 38268 CaptionParser: captionParser 38269 }; 38270 var mp4_6 = mp4.CaptionParser; 38271 38272 var parsePid = function parsePid(packet) { 38273 var pid = packet[1] & 0x1f; 38274 pid <<= 8; 38275 pid |= packet[2]; 38276 return pid; 38277 }; 38278 38279 var parsePayloadUnitStartIndicator = function parsePayloadUnitStartIndicator(packet) { 38280 return !!(packet[1] & 0x40); 38281 }; 38282 38283 var parseAdaptionField = function parseAdaptionField(packet) { 38284 var offset = 0; // if an adaption field is present, its length is specified by the 38285 // fifth byte of the TS packet header. The adaptation field is 38286 // used to add stuffing to PES packets that don't fill a complete 38287 // TS packet, and to specify some forms of timing and control data 38288 // that we do not currently use. 38289 38290 if ((packet[3] & 0x30) >>> 4 > 0x01) { 38291 offset += packet[4] + 1; 38292 } 38293 38294 return offset; 38295 }; 38296 38297 var parseType$3 = function parseType(packet, pmtPid) { 38298 var pid = parsePid(packet); 38299 38300 if (pid === 0) { 38301 return 'pat'; 38302 } else if (pid === pmtPid) { 38303 return 'pmt'; 38304 } else if (pmtPid) { 38305 return 'pes'; 38306 } 38307 38308 return null; 38309 }; 38310 38311 var parsePat = function parsePat(packet) { 38312 var pusi = parsePayloadUnitStartIndicator(packet); 38313 var offset = 4 + parseAdaptionField(packet); 38314 38315 if (pusi) { 38316 offset += packet[offset] + 1; 38317 } 38318 38319 return (packet[offset + 10] & 0x1f) << 8 | packet[offset + 11]; 38320 }; 38321 38322 var parsePmt = function parsePmt(packet) { 38323 var programMapTable = {}; 38324 var pusi = parsePayloadUnitStartIndicator(packet); 38325 var payloadOffset = 4 + parseAdaptionField(packet); 38326 38327 if (pusi) { 38328 payloadOffset += packet[payloadOffset] + 1; 38329 } // PMTs can be sent ahead of the time when they should actually 38330 // take effect. We don't believe this should ever be the case 38331 // for HLS but we'll ignore "forward" PMT declarations if we see 38332 // them. Future PMT declarations have the current_next_indicator 38333 // set to zero. 38334 38335 38336 if (!(packet[payloadOffset + 5] & 0x01)) { 38337 return; 38338 } 38339 38340 var sectionLength, tableEnd, programInfoLength; // the mapping table ends at the end of the current section 38341 38342 sectionLength = (packet[payloadOffset + 1] & 0x0f) << 8 | packet[payloadOffset + 2]; 38343 tableEnd = 3 + sectionLength - 4; // to determine where the table is, we have to figure out how 38344 // long the program info descriptors are 38345 38346 programInfoLength = (packet[payloadOffset + 10] & 0x0f) << 8 | packet[payloadOffset + 11]; // advance the offset to the first entry in the mapping table 38347 38348 var offset = 12 + programInfoLength; 38349 38350 while (offset < tableEnd) { 38351 var i = payloadOffset + offset; // add an entry that maps the elementary_pid to the stream_type 38352 38353 programMapTable[(packet[i + 1] & 0x1F) << 8 | packet[i + 2]] = packet[i]; // move to the next table entry 38354 // skip past the elementary stream descriptors, if present 38355 38356 offset += ((packet[i + 3] & 0x0F) << 8 | packet[i + 4]) + 5; 38357 } 38358 38359 return programMapTable; 38360 }; 38361 38362 var parsePesType = function parsePesType(packet, programMapTable) { 38363 var pid = parsePid(packet); 38364 var type = programMapTable[pid]; 38365 38366 switch (type) { 38367 case streamTypes.H264_STREAM_TYPE: 38368 return 'video'; 38369 38370 case streamTypes.ADTS_STREAM_TYPE: 38371 return 'audio'; 38372 38373 case streamTypes.METADATA_STREAM_TYPE: 38374 return 'timed-metadata'; 38375 38376 default: 38377 return null; 38378 } 38379 }; 38380 38381 var parsePesTime = function parsePesTime(packet) { 38382 var pusi = parsePayloadUnitStartIndicator(packet); 38383 38384 if (!pusi) { 38385 return null; 38386 } 38387 38388 var offset = 4 + parseAdaptionField(packet); 38389 38390 if (offset >= packet.byteLength) { 38391 // From the H 222.0 MPEG-TS spec 38392 // "For transport stream packets carrying PES packets, stuffing is needed when there 38393 // is insufficient PES packet data to completely fill the transport stream packet 38394 // payload bytes. Stuffing is accomplished by defining an adaptation field longer than 38395 // the sum of the lengths of the data elements in it, so that the payload bytes 38396 // remaining after the adaptation field exactly accommodates the available PES packet 38397 // data." 38398 // 38399 // If the offset is >
vendor: 54,401 bytes, lines 38399-40144
38399= the length of the packet, then the packet contains no data 38400 // and instead is just adaption field stuffing bytes 38401 return null; 38402 } 38403 38404 var pes = null; 38405 var ptsDtsFlags; // PES packets may be annotated with a PTS value, or a PTS value 38406 // and a DTS value. Determine what combination of values is 38407 // available to work with. 38408 38409 ptsDtsFlags = packet[offset + 7]; // PTS and DTS are normally stored as a 33-bit number. Javascript 38410 // performs all bitwise operations on 32-bit integers but javascript 38411 // supports a much greater range (52-bits) of integer using standard 38412 // mathematical operations. 38413 // We construct a 31-bit value using bitwise operators over the 31 38414 // most significant bits and then multiply by 4 (equal to a left-shift 38415 // of 2) before we add the final 2 least significant bits of the 38416 // timestamp (equal to an OR.) 38417 38418 if (ptsDtsFlags & 0xC0) { 38419 pes = {}; // the PTS and DTS are not written out directly. For information 38420 // on how they are encoded, see 38421 // http://dvd.sourceforge.net/dvdinfo/pes-hdr.html 38422 38423 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; 38424 pes.pts *= 4; // Left shift by 2 38425 38426 pes.pts += (packet[offset + 13] & 0x06) >>> 1; // OR by the two LSBs 38427 38428 pes.dts = pes.pts; 38429 38430 if (ptsDtsFlags & 0x40) { 38431 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; 38432 pes.dts *= 4; // Left shift by 2 38433 38434 pes.dts += (packet[offset + 18] & 0x06) >>> 1; // OR by the two LSBs 38435 } 38436 } 38437 38438 return pes; 38439 }; 38440 38441 var parseNalUnitType = function parseNalUnitType(type) { 38442 switch (type) { 38443 case 0x05: 38444 return 'slice_layer_without_partitioning_rbsp_idr'; 38445 38446 case 0x06: 38447 return 'sei_rbsp'; 38448 38449 case 0x07: 38450 return 'seq_parameter_set_rbsp'; 38451 38452 case 0x08: 38453 return 'pic_parameter_set_rbsp'; 38454 38455 case 0x09: 38456 return 'access_unit_delimiter_rbsp'; 38457 38458 default: 38459 return null; 38460 } 38461 }; 38462 38463 var videoPacketContainsKeyFrame = function videoPacketContainsKeyFrame(packet) { 38464 var offset = 4 + parseAdaptionField(packet); 38465 var frameBuffer = packet.subarray(offset); 38466 var frameI = 0; 38467 var frameSyncPoint = 0; 38468 var foundKeyFrame = false; 38469 var nalType; // advance the sync point to a NAL start, if necessary 38470 38471 for (; frameSyncPoint < frameBuffer.byteLength - 3; frameSyncPoint++) { 38472 if (frameBuffer[frameSyncPoint + 2] === 1) { 38473 // the sync point is properly aligned 38474 frameI = frameSyncPoint + 5; 38475 break; 38476 } 38477 } 38478 38479 while (frameI < frameBuffer.byteLength) { 38480 // look at the current byte to determine if we've hit the end of 38481 // a NAL unit boundary 38482 switch (frameBuffer[frameI]) { 38483 case 0: 38484 // skip past non-sync sequences 38485 if (frameBuffer[frameI - 1] !== 0) { 38486 frameI += 2; 38487 break; 38488 } else if (frameBuffer[frameI - 2] !== 0) { 38489 frameI++; 38490 break; 38491 } 38492 38493 if (frameSyncPoint + 3 !== frameI - 2) { 38494 nalType = parseNalUnitType(frameBuffer[frameSyncPoint + 3] & 0x1f); 38495 38496 if (nalType === 'slice_layer_without_partitioning_rbsp_idr') { 38497 foundKeyFrame = true; 38498 } 38499 } // drop trailing zeroes 38500 38501 38502 do { 38503 frameI++; 38504 } while (frameBuffer[frameI] !== 1 && frameI < frameBuffer.length); 38505 38506 frameSyncPoint = frameI - 2; 38507 frameI += 3; 38508 break; 38509 38510 case 1: 38511 // skip past non-sync sequences 38512 if (frameBuffer[frameI - 1] !== 0 || frameBuffer[frameI - 2] !== 0) { 38513 frameI += 3; 38514 break; 38515 } 38516 38517 nalType = parseNalUnitType(frameBuffer[frameSyncPoint + 3] & 0x1f); 38518 38519 if (nalType === 'slice_layer_without_partitioning_rbsp_idr') { 38520 foundKeyFrame = true; 38521 } 38522 38523 frameSyncPoint = frameI - 2; 38524 frameI += 3; 38525 break; 38526 38527 default: 38528 // the current byte isn't a one or zero, so it cannot be part 38529 // of a sync sequence 38530 frameI += 3; 38531 break; 38532 } 38533 } 38534 38535 frameBuffer = frameBuffer.subarray(frameSyncPoint); 38536 frameI -= frameSyncPoint; 38537 frameSyncPoint = 0; // parse the final nal 38538 38539 if (frameBuffer && frameBuffer.byteLength > 3) { 38540 nalType = parseNalUnitType(frameBuffer[frameSyncPoint + 3] & 0x1f); 38541 38542 if (nalType === 'slice_layer_without_partitioning_rbsp_idr') { 38543 foundKeyFrame = true; 38544 } 38545 } 38546 38547 return foundKeyFrame; 38548 }; 38549 38550 var probe$1 = { 38551 parseType: parseType$3, 38552 parsePat: parsePat, 38553 parsePmt: parsePmt, 38554 parsePayloadUnitStartIndicator: parsePayloadUnitStartIndicator, 38555 parsePesType: parsePesType, 38556 parsePesTime: parsePesTime, 38557 videoPacketContainsKeyFrame: videoPacketContainsKeyFrame 38558 }; 38559 38560 var handleRollover$1 = timestampRolloverStream.handleRollover; 38561 var probe$2 = {}; 38562 probe$2.ts = probe$1; 38563 probe$2.aac = utils; 38564 var PES_TIMESCALE = 90000, 38565 MP2T_PACKET_LENGTH$1 = 188, 38566 // bytes 38567 SYNC_BYTE$1 = 0x47; 38568 /** 38569 * walks through segment data looking for pat and pmt packets to parse out 38570 * program map table information 38571 */ 38572 38573 var parsePsi_ = function parsePsi_(bytes, pmt) { 38574 var startIndex = 0, 38575 endIndex = MP2T_PACKET_LENGTH$1, 38576 packet, 38577 type; 38578 38579 while (endIndex < bytes.byteLength) { 38580 // Look for a pair of start and end sync bytes in the data.. 38581 if (bytes[startIndex] === SYNC_BYTE$1 && bytes[endIndex] === SYNC_BYTE$1) { 38582 // We found a packet 38583 packet = bytes.subarray(startIndex, endIndex); 38584 type = probe$2.ts.parseType(packet, pmt.pid); 38585 38586 switch (type) { 38587 case 'pat': 38588 if (!pmt.pid) { 38589 pmt.pid = probe$2.ts.parsePat(packet); 38590 } 38591 38592 break; 38593 38594 case 'pmt': 38595 if (!pmt.table) { 38596 pmt.table = probe$2.ts.parsePmt(packet); 38597 } 38598 38599 break; 38600 38601 default: 38602 break; 38603 } // Found the pat and pmt, we can stop walking the segment 38604 38605 38606 if (pmt.pid && pmt.table) { 38607 return; 38608 } 38609 38610 startIndex += MP2T_PACKET_LENGTH$1; 38611 endIndex += MP2T_PACKET_LENGTH$1; 38612 continue; 38613 } // If we get here, we have somehow become de-synchronized and we need to step 38614 // forward one byte at a time until we find a pair of sync bytes that denote 38615 // a packet 38616 38617 38618 startIndex++; 38619 endIndex++; 38620 } 38621 }; 38622 /** 38623 * walks through the segment data from the start and end to get timing information 38624 * for the first and last audio pes packets 38625 */ 38626 38627 38628 var parseAudioPes_ = function parseAudioPes_(bytes, pmt, result) { 38629 var startIndex = 0, 38630 endIndex = MP2T_PACKET_LENGTH$1, 38631 packet, 38632 type, 38633 pesType, 38634 pusi, 38635 parsed; 38636 var endLoop = false; // Start walking from start of segment to get first audio packet 38637 38638 while (endIndex <= bytes.byteLength) { 38639 // Look for a pair of start and end sync bytes in the data.. 38640 if (bytes[startIndex] === SYNC_BYTE$1 && (bytes[endIndex] === SYNC_BYTE$1 || endIndex === bytes.byteLength)) { 38641 // We found a packet 38642 packet = bytes.subarray(startIndex, endIndex); 38643 type = probe$2.ts.parseType(packet, pmt.pid); 38644 38645 switch (type) { 38646 case 'pes': 38647 pesType = probe$2.ts.parsePesType(packet, pmt.table); 38648 pusi = probe$2.ts.parsePayloadUnitStartIndicator(packet); 38649 38650 if (pesType === 'audio' && pusi) { 38651 parsed = probe$2.ts.parsePesTime(packet); 38652 38653 if (parsed) { 38654 parsed.type = 'audio'; 38655 result.audio.push(parsed); 38656 endLoop = true; 38657 } 38658 } 38659 38660 break; 38661 38662 default: 38663 break; 38664 } 38665 38666 if (endLoop) { 38667 break; 38668 } 38669 38670 startIndex += MP2T_PACKET_LENGTH$1; 38671 endIndex += MP2T_PACKET_LENGTH$1; 38672 continue; 38673 } // If we get here, we have somehow become de-synchronized and we need to step 38674 // forward one byte at a time until we find a pair of sync bytes that denote 38675 // a packet 38676 38677 38678 startIndex++; 38679 endIndex++; 38680 } // Start walking from end of segment to get last audio packet 38681 38682 38683 endIndex = bytes.byteLength; 38684 startIndex = endIndex - MP2T_PACKET_LENGTH$1; 38685 endLoop = false; 38686 38687 while (startIndex >= 0) { 38688 // Look for a pair of start and end sync bytes in the data.. 38689 if (bytes[startIndex] === SYNC_BYTE$1 && (bytes[endIndex] === SYNC_BYTE$1 || endIndex === bytes.byteLength)) { 38690 // We found a packet 38691 packet = bytes.subarray(startIndex, endIndex); 38692 type = probe$2.ts.parseType(packet, pmt.pid); 38693 38694 switch (type) { 38695 case 'pes': 38696 pesType = probe$2.ts.parsePesType(packet, pmt.table); 38697 pusi = probe$2.ts.parsePayloadUnitStartIndicator(packet); 38698 38699 if (pesType === 'audio' && pusi) { 38700 parsed = probe$2.ts.parsePesTime(packet); 38701 38702 if (parsed) { 38703 parsed.type = 'audio'; 38704 result.audio.push(parsed); 38705 endLoop = true; 38706 } 38707 } 38708 38709 break; 38710 38711 default: 38712 break; 38713 } 38714 38715 if (endLoop) { 38716 break; 38717 } 38718 38719 startIndex -= MP2T_PACKET_LENGTH$1; 38720 endIndex -= MP2T_PACKET_LENGTH$1; 38721 continue; 38722 } // If we get here, we have somehow become de-synchronized and we need to step 38723 // forward one byte at a time until we find a pair of sync bytes that denote 38724 // a packet 38725 38726 38727 startIndex--; 38728 endIndex--; 38729 } 38730 }; 38731 /** 38732 * walks through the segment data from the start and end to get timing information 38733 * for the first and last video pes packets as well as timing information for the first 38734 * key frame. 38735 */ 38736 38737 38738 var parseVideoPes_ = function parseVideoPes_(bytes, pmt, result) { 38739 var startIndex = 0, 38740 endIndex = MP2T_PACKET_LENGTH$1, 38741 packet, 38742 type, 38743 pesType, 38744 pusi, 38745 parsed, 38746 frame, 38747 i, 38748 pes; 38749 var endLoop = false; 38750 var currentFrame = { 38751 data: [], 38752 size: 0 38753 }; // Start walking from start of segment to get first video packet 38754 38755 while (endIndex < bytes.byteLength) { 38756 // Look for a pair of start and end sync bytes in the data.. 38757 if (bytes[startIndex] === SYNC_BYTE$1 && bytes[endIndex] === SYNC_BYTE$1) { 38758 // We found a packet 38759 packet = bytes.subarray(startIndex, endIndex); 38760 type = probe$2.ts.parseType(packet, pmt.pid); 38761 38762 switch (type) { 38763 case 'pes': 38764 pesType = probe$2.ts.parsePesType(packet, pmt.table); 38765 pusi = probe$2.ts.parsePayloadUnitStartIndicator(packet); 38766 38767 if (pesType === 'video') { 38768 if (pusi && !endLoop) { 38769 parsed = probe$2.ts.parsePesTime(packet); 38770 38771 if (parsed) { 38772 parsed.type = 'video'; 38773 result.video.push(parsed); 38774 endLoop = true; 38775 } 38776 } 38777 38778 if (!result.firstKeyFrame) { 38779 if (pusi) { 38780 if (currentFrame.size !== 0) { 38781 frame = new Uint8Array(currentFrame.size); 38782 i = 0; 38783 38784 while (currentFrame.data.length) { 38785 pes = currentFrame.data.shift(); 38786 frame.set(pes, i); 38787 i += pes.byteLength; 38788 } 38789 38790 if (probe$2.ts.videoPacketContainsKeyFrame(frame)) { 38791 result.firstKeyFrame = probe$2.ts.parsePesTime(frame); 38792 result.firstKeyFrame.type = 'video'; 38793 } 38794 38795 currentFrame.size = 0; 38796 } 38797 } 38798 38799 currentFrame.data.push(packet); 38800 currentFrame.size += packet.byteLength; 38801 } 38802 } 38803 38804 break; 38805 38806 default: 38807 break; 38808 } 38809 38810 if (endLoop && result.firstKeyFrame) { 38811 break; 38812 } 38813 38814 startIndex += MP2T_PACKET_LENGTH$1; 38815 endIndex += MP2T_PACKET_LENGTH$1; 38816 continue; 38817 } // If we get here, we have somehow become de-synchronized and we need to step 38818 // forward one byte at a time until we find a pair of sync bytes that denote 38819 // a packet 38820 38821 38822 startIndex++; 38823 endIndex++; 38824 } // Start walking from end of segment to get last video packet 38825 38826 38827 endIndex = bytes.byteLength; 38828 startIndex = endIndex - MP2T_PACKET_LENGTH$1; 38829 endLoop = false; 38830 38831 while (startIndex >= 0) { 38832 // Look for a pair of start and end sync bytes in the data.. 38833 if (bytes[startIndex] === SYNC_BYTE$1 && bytes[endIndex] === SYNC_BYTE$1) { 38834 // We found a packet 38835 packet = bytes.subarray(startIndex, endIndex); 38836 type = probe$2.ts.parseType(packet, pmt.pid); 38837 38838 switch (type) { 38839 case 'pes': 38840 pesType = probe$2.ts.parsePesType(packet, pmt.table); 38841 pusi = probe$2.ts.parsePayloadUnitStartIndicator(packet); 38842 38843 if (pesType === 'video' && pusi) { 38844 parsed = probe$2.ts.parsePesTime(packet); 38845 38846 if (parsed) { 38847 parsed.type = 'video'; 38848 result.video.push(parsed); 38849 endLoop = true; 38850 } 38851 } 38852 38853 break; 38854 38855 default: 38856 break; 38857 } 38858 38859 if (endLoop) { 38860 break; 38861 } 38862 38863 startIndex -= MP2T_PACKET_LENGTH$1; 38864 endIndex -= MP2T_PACKET_LENGTH$1; 38865 continue; 38866 } // If we get here, we have somehow become de-synchronized and we need to step 38867 // forward one byte at a time until we find a pair of sync bytes that denote 38868 // a packet 38869 38870 38871 startIndex--; 38872 endIndex--; 38873 } 38874 }; 38875 /** 38876 * Adjusts the timestamp information for the segment to account for 38877 * rollover and convert to seconds based on pes packet timescale (90khz clock) 38878 */ 38879 38880 38881 var adjustTimestamp_ = function adjustTimestamp_(segmentInfo, baseTimestamp) { 38882 if (segmentInfo.audio && segmentInfo.audio.length) { 38883 var audioBaseTimestamp = baseTimestamp; 38884 38885 if (typeof audioBaseTimestamp === 'undefined') { 38886 audioBaseTimestamp = segmentInfo.audio[0].dts; 38887 } 38888 38889 segmentInfo.audio.forEach(function (info) { 38890 info.dts = handleRollover$1(info.dts, audioBaseTimestamp); 38891 info.pts = handleRollover$1(info.pts, audioBaseTimestamp); // time in seconds 38892 38893 info.dtsTime = info.dts / PES_TIMESCALE; 38894 info.ptsTime = info.pts / PES_TIMESCALE; 38895 }); 38896 } 38897 38898 if (segmentInfo.video && segmentInfo.video.length) { 38899 var videoBaseTimestamp = baseTimestamp; 38900 38901 if (typeof videoBaseTimestamp === 'undefined') { 38902 videoBaseTimestamp = segmentInfo.video[0].dts; 38903 } 38904 38905 segmentInfo.video.forEach(function (info) { 38906 info.dts = handleRollover$1(info.dts, videoBaseTimestamp); 38907 info.pts = handleRollover$1(info.pts, videoBaseTimestamp); // time in seconds 38908 38909 info.dtsTime = info.dts / PES_TIMESCALE; 38910 info.ptsTime = info.pts / PES_TIMESCALE; 38911 }); 38912 38913 if (segmentInfo.firstKeyFrame) { 38914 var frame = segmentInfo.firstKeyFrame; 38915 frame.dts = handleRollover$1(frame.dts, videoBaseTimestamp); 38916 frame.pts = handleRollover$1(frame.pts, videoBaseTimestamp); // time in seconds 38917 38918 frame.dtsTime = frame.dts / PES_TIMESCALE; 38919 frame.ptsTime = frame.dts / PES_TIMESCALE; 38920 } 38921 } 38922 }; 38923 /** 38924 * inspects the aac data stream for start and end time information 38925 */ 38926 38927 38928 var inspectAac_ = function inspectAac_(bytes) { 38929 var endLoop = false, 38930 audioCount = 0, 38931 sampleRate = null, 38932 timestamp = null, 38933 frameSize = 0, 38934 byteIndex = 0, 38935 packet; 38936 38937 while (bytes.length - byteIndex >= 3) { 38938 var type = probe$2.aac.parseType(bytes, byteIndex); 38939 38940 switch (type) { 38941 case 'timed-metadata': 38942 // Exit early because we don't have enough to parse 38943 // the ID3 tag header 38944 if (bytes.length - byteIndex < 10) { 38945 endLoop = true; 38946 break; 38947 } 38948 38949 frameSize = probe$2.aac.parseId3TagSize(bytes, byteIndex); // Exit early if we don't have enough in the buffer 38950 // to emit a full packet 38951 38952 if (frameSize > bytes.length) { 38953 endLoop = true; 38954 break; 38955 } 38956 38957 if (timestamp === null) { 38958 packet = bytes.subarray(byteIndex, byteIndex + frameSize); 38959 timestamp = probe$2.aac.parseAacTimestamp(packet); 38960 } 38961 38962 byteIndex += frameSize; 38963 break; 38964 38965 case 'audio': 38966 // Exit early because we don't have enough to parse 38967 // the ADTS frame header 38968 if (bytes.length - byteIndex < 7) { 38969 endLoop = true; 38970 break; 38971 } 38972 38973 frameSize = probe$2.aac.parseAdtsSize(bytes, byteIndex); // Exit early if we don't have enough in the buffer 38974 // to emit a full packet 38975 38976 if (frameSize > bytes.length) { 38977 endLoop = true; 38978 break; 38979 } 38980 38981 if (sampleRate === null) { 38982 packet = bytes.subarray(byteIndex, byteIndex + frameSize); 38983 sampleRate = probe$2.aac.parseSampleRate(packet); 38984 } 38985 38986 audioCount++; 38987 byteIndex += frameSize; 38988 break; 38989 38990 default: 38991 byteIndex++; 38992 break; 38993 } 38994 38995 if (endLoop) { 38996 return null; 38997 } 38998 } 38999 39000 if (sampleRate === null || timestamp === null) { 39001 return null; 39002 } 39003 39004 var audioTimescale = PES_TIMESCALE / sampleRate; 39005 var result = { 39006 audio: [{ 39007 type: 'audio', 39008 dts: timestamp, 39009 pts: timestamp 39010 }, { 39011 type: 'audio', 39012 dts: timestamp + audioCount * 1024 * audioTimescale, 39013 pts: timestamp + audioCount * 1024 * audioTimescale 39014 }] 39015 }; 39016 return result; 39017 }; 39018 /** 39019 * inspects the transport stream segment data for start and end time information 39020 * of the audio and video tracks (when present) as well as the first key frame's 39021 * start time. 39022 */ 39023 39024 39025 var inspectTs_ = function inspectTs_(bytes) { 39026 var pmt = { 39027 pid: null, 39028 table: null 39029 }; 39030 var result = {}; 39031 parsePsi_(bytes, pmt); 39032 39033 for (var pid in pmt.table) { 39034 if (pmt.table.hasOwnProperty(pid)) { 39035 var type = pmt.table[pid]; 39036 39037 switch (type) { 39038 case streamTypes.H264_STREAM_TYPE: 39039 result.video = []; 39040 parseVideoPes_(bytes, pmt, result); 39041 39042 if (result.video.length === 0) { 39043 delete result.video; 39044 } 39045 39046 break; 39047 39048 case streamTypes.ADTS_STREAM_TYPE: 39049 result.audio = []; 39050 parseAudioPes_(bytes, pmt, result); 39051 39052 if (result.audio.length === 0) { 39053 delete result.audio; 39054 } 39055 39056 break; 39057 39058 default: 39059 break; 39060 } 39061 } 39062 } 39063 39064 return result; 39065 }; 39066 /** 39067 * Inspects segment byte data and returns an object with start and end timing information 39068 * 39069 * @param {Uint8Array} bytes The segment byte data 39070 * @param {Number} baseTimestamp Relative reference timestamp used when adjusting frame 39071 * timestamps for rollover. This value must be in 90khz clock. 39072 * @return {Object} Object containing start and end frame timing info of segment. 39073 */ 39074 39075 39076 var inspect = function inspect(bytes, baseTimestamp) { 39077 var isAacData = probe$2.aac.isLikelyAacData(bytes); 39078 var result; 39079 39080 if (isAacData) { 39081 result = inspectAac_(bytes); 39082 } else { 39083 result = inspectTs_(bytes); 39084 } 39085 39086 if (!result || !result.audio && !result.video) { 39087 return null; 39088 } 39089 39090 adjustTimestamp_(result, baseTimestamp); 39091 return result; 39092 }; 39093 39094 var tsInspector = { 39095 inspect: inspect, 39096 parseAudioPes_: parseAudioPes_ 39097 }; 39098 39099 /* 39100 * pkcs7.pad 39101 * https://github.com/brightcove/pkcs7 39102 * 39103 * Copyright (c) 2014 Brightcove 39104 * Licensed under the apache2 license. 39105 */ 39106 /** 39107 * Returns the subarray of a Uint8Array without PKCS#7 padding. 39108 * @param padded {Uint8Array} unencrypted bytes that have been padded 39109 * @return {Uint8Array} the unpadded bytes 39110 * @see http://tools.ietf.org/html/rfc5652 39111 */ 39112 39113 function unpad(padded) { 39114 return padded.subarray(0, padded.byteLength - padded[padded.byteLength - 1]); 39115 } 39116 39117 var classCallCheck = function classCallCheck(instance, Constructor) { 39118 if (!(instance instanceof Constructor)) { 39119 throw new TypeError("Cannot call a class as a function"); 39120 } 39121 }; 39122 39123 var createClass = function () { 39124 function defineProperties(target, props) { 39125 for (var i = 0; i < props.length; i++) { 39126 var descriptor = props[i]; 39127 descriptor.enumerable = descriptor.enumerable || false; 39128 descriptor.configurable = true; 39129 if ("value" in descriptor) descriptor.writable = true; 39130 Object.defineProperty(target, descriptor.key, descriptor); 39131 } 39132 } 39133 39134 return function (Constructor, protoProps, staticProps) { 39135 if (protoProps) defineProperties(Constructor.prototype, protoProps); 39136 if (staticProps) defineProperties(Constructor, staticProps); 39137 return Constructor; 39138 }; 39139 }(); 39140 39141 var inherits = function inherits(subClass, superClass) { 39142 if (typeof superClass !== "function" && superClass !== null) { 39143 throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); 39144 } 39145 39146 subClass.prototype = Object.create(superClass && superClass.prototype, { 39147 constructor: { 39148 value: subClass, 39149 enumerable: false, 39150 writable: true, 39151 configurable: true 39152 } 39153 }); 39154 if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; 39155 }; 39156 39157 var possibleConstructorReturn = function possibleConstructorReturn(self, call) { 39158 if (!self) { 39159 throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); 39160 } 39161 39162 return call && (typeof call === "object" || typeof call === "function") ? call : self; 39163 }; 39164 /** 39165 * @file aes.js 39166 * 39167 * This file contains an adaptation of the AES decryption algorithm 39168 * from the Standford Javascript Cryptography Library. That work is 39169 * covered by the following copyright and permissions notice: 39170 * 39171 * Copyright 2009-2010 Emily Stark, Mike Hamburg, Dan Boneh. 39172 * All rights reserved. 39173 * 39174 * Redistribution and use in source and binary forms, with or without 39175 * modification, are permitted provided that the following conditions are 39176 * met: 39177 * 39178 * 1. Redistributions of source code must retain the above copyright 39179 * notice, this list of conditions and the following disclaimer. 39180 * 39181 * 2. Redistributions in binary form must reproduce the above 39182 * copyright notice, this list of conditions and the following 39183 * disclaimer in the documentation and/or other materials provided 39184 * with the distribution. 39185 * 39186 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR 39187 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 39188 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 39189 * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> OR CONTRIBUTORS BE 39190 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 39191 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 39192 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 39193 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 39194 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 39195 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN 39196 * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 39197 * 39198 * The views and conclusions contained in the software and documentation 39199 * are those of the authors and should not be interpreted as representing 39200 * official policies, either expressed or implied, of the authors. 39201 */ 39202 39203 /** 39204 * Expand the S-box tables. 39205 * 39206 * @private 39207 */ 39208 39209 39210 var precompute = function precompute() { 39211 var tables = [[[], [], [], [], []], [[], [], [], [], []]]; 39212 var encTable = tables[0]; 39213 var decTable = tables[1]; 39214 var sbox = encTable[4]; 39215 var sboxInv = decTable[4]; 39216 var i = void 0; 39217 var x = void 0; 39218 var xInv = void 0; 39219 var d = []; 39220 var th = []; 39221 var x2 = void 0; 39222 var x4 = void 0; 39223 var x8 = void 0; 39224 var s = void 0; 39225 var tEnc = void 0; 39226 var tDec = void 0; // Compute double and third tables 39227 39228 for (i = 0; i < 256; i++) { 39229 th[(d[i] = i << 1 ^ (i >> 7) * 283) ^ i] = i; 39230 } 39231 39232 for (x = xInv = 0; !sbox[x]; x ^= x2 || 1, xInv = th[xInv] || 1) { 39233 // Compute sbox 39234 s = xInv ^ xInv << 1 ^ xInv << 2 ^ xInv << 3 ^ xInv << 4; 39235 s = s >> 8 ^ s & 255 ^ 99; 39236 sbox[x] = s; 39237 sboxInv[s] = x; // Compute MixColumns 39238 39239 x8 = d[x4 = d[x2 = d[x]]]; 39240 tDec = x8 * 0x1010101 ^ x4 * 0x10001 ^ x2 * 0x101 ^ x * 0x1010100; 39241 tEnc = d[s] * 0x101 ^ s * 0x1010100; 39242 39243 for (i = 0; i < 4; i++) { 39244 encTable[i][x] = tEnc = tEnc << 24 ^ tEnc >>> 8; 39245 decTable[i][s] = tDec = tDec << 24 ^ tDec >>> 8; 39246 } 39247 } // Compactify. Considerable speedup on Firefox. 39248 39249 39250 for (i = 0; i < 5; i++) { 39251 encTable[i] = encTable[i].slice(0); 39252 decTable[i] = decTable[i].slice(0); 39253 } 39254 39255 return tables; 39256 }; 39257 39258 var aesTables = null; 39259 /** 39260 * Schedule out an AES key for both encryption and decryption. This 39261 * is a low-level class. Use a cipher mode to do bulk encryption. 39262 * 39263 * @class AES 39264 * @param key {Array} The key as an array of 4, 6 or 8 words. 39265 */ 39266 39267 var AES = function () { 39268 function AES(key) { 39269 classCallCheck(this, AES); 39270 /** 39271 * The expanded S-box and inverse S-box tables. These will be computed 39272 * on the client so that we don't have to send them down the wire. 39273 * 39274 * There are two tables, _tables[0] is for encryption and 39275 * _tables[1] is for decryption. 39276 * 39277 * The first 4 sub-tables are the expanded S-box with MixColumns. The 39278 * last (_tables[01][4]) is the S-box itself. 39279 * 39280 * @private 39281 */ 39282 // if we have yet to precompute the S-box tables 39283 // do so now 39284 39285 if (!aesTables) { 39286 aesTables = precompute(); 39287 } // then make a copy of that object for use 39288 39289 39290 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()]]; 39291 var i = void 0; 39292 var j = void 0; 39293 var tmp = void 0; 39294 var encKey = void 0; 39295 var decKey = void 0; 39296 var sbox = this._tables[0][4]; 39297 var decTable = this._tables[1]; 39298 var keyLen = key.length; 39299 var rcon = 1; 39300 39301 if (keyLen !== 4 && keyLen !== 6 && keyLen !== 8) { 39302 throw new Error('Invalid aes key size'); 39303 } 39304 39305 encKey = key.slice(0); 39306 decKey = []; 39307 this._key = [encKey, decKey]; // schedule encryption keys 39308 39309 for (i = keyLen; i < 4 * keyLen + 28; i++) { 39310 tmp = encKey[i - 1]; // apply sbox 39311 39312 if (i % keyLen === 0 || keyLen === 8 && i % keyLen === 4) { 39313 tmp = sbox[tmp >>> 24] << 24 ^ sbox[tmp >> 16 & 255] << 16 ^ sbox[tmp >> 8 & 255] << 8 ^ sbox[tmp & 255]; // shift rows and add rcon 39314 39315 if (i % keyLen === 0) { 39316 tmp = tmp << 8 ^ tmp >>> 24 ^ rcon << 24; 39317 rcon = rcon << 1 ^ (rcon >> 7) * 283; 39318 } 39319 } 39320 39321 encKey[i] = encKey[i - keyLen] ^ tmp; 39322 } // schedule decryption keys 39323 39324 39325 for (j = 0; i; j++, i--) { 39326 tmp = encKey[j & 3 ? i : i - 4]; 39327 39328 if (i <= 4 || j < 4) { 39329 decKey[j] = tmp; 39330 } else { 39331 decKey[j] = decTable[0][sbox[tmp >>> 24]] ^ decTable[1][sbox[tmp >> 16 & 255]] ^ decTable[2][sbox[tmp >> 8 & 255]] ^ decTable[3][sbox[tmp & 255]]; 39332 } 39333 } 39334 } 39335 /** 39336 * Decrypt 16 bytes, specified as four 32-bit words. 39337 * 39338 * @param {Number} encrypted0 the first word to decrypt 39339 * @param {Number} encrypted1 the second word to decrypt 39340 * @param {Number} encrypted2 the third word to decrypt 39341 * @param {Number} encrypted3 the fourth word to decrypt 39342 * @param {Int32Array} out the array to write the decrypted words 39343 * into 39344 * @param {Number} offset the offset into the output array to start 39345 * writing results 39346 * @return {Array} The plaintext. 39347 */ 39348 39349 39350 AES.prototype.decrypt = function decrypt(encrypted0, encrypted1, encrypted2, encrypted3, out, offset) { 39351 var key = this._key[1]; // state variables a,b,c,d are loaded with pre-whitened data 39352 39353 var a = encrypted0 ^ key[0]; 39354 var b = encrypted3 ^ key[1]; 39355 var c = encrypted2 ^ key[2]; 39356 var d = encrypted1 ^ key[3]; 39357 var a2 = void 0; 39358 var b2 = void 0; 39359 var c2 = void 0; // key.length === 2 ? 39360 39361 var nInnerRounds = key.length / 4 - 2; 39362 var i = void 0; 39363 var kIndex = 4; 39364 var table = this._tables[1]; // load up the tables 39365 39366 var table0 = table[0]; 39367 var table1 = table[1]; 39368 var table2 = table[2]; 39369 var table3 = table[3]; 39370 var sbox = table[4]; // Inner rounds. Cribbed from OpenSSL. 39371 39372 for (i = 0; i < nInnerRounds; i++) { 39373 a2 = table0[a >>> 24] ^ table1[b >> 16 & 255] ^ table2[c >> 8 & 255] ^ table3[d & 255] ^ key[kIndex]; 39374 b2 = table0[b >>> 24] ^ table1[c >> 16 & 255] ^ table2[d >> 8 & 255] ^ table3[a & 255] ^ key[kIndex + 1]; 39375 c2 = table0[c >>> 24] ^ table1[d >> 16 & 255] ^ table2[a >> 8 & 255] ^ table3[b & 255] ^ key[kIndex + 2]; 39376 d = table0[d >>> 24] ^ table1[a >> 16 & 255] ^ table2[b >> 8 & 255] ^ table3[c & 255] ^ key[kIndex + 3]; 39377 kIndex += 4; 39378 a = a2; 39379 b = b2; 39380 c = c2; 39381 } // Last round. 39382 39383 39384 for (i = 0; i < 4; i++) { 39385 out[(3 & -i) + offset] = sbox[a >>> 24] << 24 ^ sbox[b >> 16 & 255] << 16 ^ sbox[c >> 8 & 255] << 8 ^ sbox[d & 255] ^ key[kIndex++]; 39386 a2 = a; 39387 a = b; 39388 b = c; 39389 c = d; 39390 d = a2; 39391 } 39392 }; 39393 39394 return AES; 39395 }(); 39396 /** 39397 * @file stream.js 39398 */ 39399 39400 /** 39401 * A lightweight readable stream implemention that handles event dispatching. 39402 * 39403 * @class Stream 39404 */ 39405 39406 39407 var Stream$2 = function () { 39408 function Stream() { 39409 classCallCheck(this, Stream); 39410 this.listeners = {}; 39411 } 39412 /** 39413 * Add a listener for a specified event type. 39414 * 39415 * @param {String} type the event name 39416 * @param {Function} listener the callback to be invoked when an event of 39417 * the specified type occurs 39418 */ 39419 39420 39421 Stream.prototype.on = function on(type, listener) { 39422 if (!this.listeners[type]) { 39423 this.listeners[type] = []; 39424 } 39425 39426 this.listeners[type].push(listener); 39427 }; 39428 /** 39429 * Remove a listener for a specified event type. 39430 * 39431 * @param {String} type the event name 39432 * @param {Function} listener a function previously registered for this 39433 * type of event through `on` 39434 * @return {Boolean} if we could turn it off or not 39435 */ 39436 39437 39438 Stream.prototype.off = function off(type, listener) { 39439 if (!this.listeners[type]) { 39440 return false; 39441 } 39442 39443 var index = this.listeners[type].indexOf(listener); 39444 this.listeners[type].splice(index, 1); 39445 return index > -1; 39446 }; 39447 /** 39448 * Trigger an event of the specified type on this stream. Any additional 39449 * arguments to this function are passed as parameters to event listeners. 39450 * 39451 * @param {String} type the event name 39452 */ 39453 39454 39455 Stream.prototype.trigger = function trigger(type) { 39456 var callbacks = this.listeners[type]; 39457 39458 if (!callbacks) { 39459 return; 39460 } // Slicing the arguments on every invocation of this method 39461 // can add a significant amount of overhead. Avoid the 39462 // intermediate object creation for the common case of a 39463 // single callback argument 39464 39465 39466 if (arguments.length === 2) { 39467 var length = callbacks.length; 39468 39469 for (var i = 0; i < length; ++i) { 39470 callbacks[i].call(this, arguments[1]); 39471 } 39472 } else { 39473 var args = Array.prototype.slice.call(arguments, 1); 39474 var _length = callbacks.length; 39475 39476 for (var _i = 0; _i < _length; ++_i) { 39477 callbacks[_i].apply(this, args); 39478 } 39479 } 39480 }; 39481 /** 39482 * Destroys the stream and cleans up. 39483 */ 39484 39485 39486 Stream.prototype.dispose = function dispose() { 39487 this.listeners = {}; 39488 }; 39489 /** 39490 * Forwards all `data` events on this stream to the destination stream. The 39491 * destination stream should provide a method `push` to receive the data 39492 * events as they arrive. 39493 * 39494 * @param {Stream} destination the stream that will receive all `data` events 39495 * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options 39496 */ 39497 39498 39499 Stream.prototype.pipe = function pipe(destination) { 39500 this.on('data', function (data) { 39501 destination.push(data); 39502 }); 39503 }; 39504 39505 return Stream; 39506 }(); 39507 /** 39508 * @file async-stream.js 39509 */ 39510 39511 /** 39512 * A wrapper around the Stream class to use setTiemout 39513 * and run stream "jobs" Asynchronously 39514 * 39515 * @class AsyncStream 39516 * @extends Stream 39517 */ 39518 39519 39520 var AsyncStream = function (_Stream) { 39521 inherits(AsyncStream, _Stream); 39522 39523 function AsyncStream() { 39524 classCallCheck(this, AsyncStream); 39525 39526 var _this = possibleConstructorReturn(this, _Stream.call(this, Stream$2)); 39527 39528 _this.jobs = []; 39529 _this.delay = 1; 39530 _this.timeout_ = null; 39531 return _this; 39532 } 39533 /** 39534 * process an async job 39535 * 39536 * @private 39537 */ 39538 39539 39540 AsyncStream.prototype.processJob_ = function processJob_() { 39541 this.jobs.shift()(); 39542 39543 if (this.jobs.length) { 39544 this.timeout_ = setTimeout(this.processJob_.bind(this), this.delay); 39545 } else { 39546 this.timeout_ = null; 39547 } 39548 }; 39549 /** 39550 * push a job into the stream 39551 * 39552 * @param {Function} job the job to push into the stream 39553 */ 39554 39555 39556 AsyncStream.prototype.push = function push(job) { 39557 this.jobs.push(job); 39558 39559 if (!this.timeout_) { 39560 this.timeout_ = setTimeout(this.processJob_.bind(this), this.delay); 39561 } 39562 }; 39563 39564 return AsyncStream; 39565 }(Stream$2); 39566 /** 39567 * @file decrypter.js 39568 * 39569 * An asynchronous implementation of AES-128 CBC decryption with 39570 * PKCS#7 padding. 39571 */ 39572 39573 /** 39574 * Convert network-order (big-endian) bytes into their little-endian 39575 * representation. 39576 */ 39577 39578 39579 var ntoh = function ntoh(word) { 39580 return word << 24 | (word & 0xff00) << 8 | (word & 0xff0000) >> 8 | word >>> 24; 39581 }; 39582 /** 39583 * Decrypt bytes using AES-128 with CBC and PKCS#7 padding. 39584 * 39585 * @param {Uint8Array} encrypted the encrypted bytes 39586 * @param {Uint32Array} key the bytes of the decryption key 39587 * @param {Uint32Array} initVector the initialization vector (IV) to 39588 * use for the first round of CBC. 39589 * @return {Uint8Array} the decrypted bytes 39590 * 39591 * @see http://en.wikipedia.org/wiki/Advanced_Encryption_Standard 39592 * @see http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_Block_Chaining_.28CBC.29 39593 * @see https://tools.ietf.org/html/rfc2315 39594 */ 39595 39596 39597 var decrypt = function decrypt(encrypted, key, initVector) { 39598 // word-level access to the encrypted bytes 39599 var encrypted32 = new Int32Array(encrypted.buffer, encrypted.byteOffset, encrypted.byteLength >> 2); 39600 var decipher = new AES(Array.prototype.slice.call(key)); // byte and word-level access for the decrypted output 39601 39602 var decrypted = new Uint8Array(encrypted.byteLength); 39603 var decrypted32 = new Int32Array(decrypted.buffer); // temporary variables for working with the IV, encrypted, and 39604 // decrypted data 39605 39606 var init0 = void 0; 39607 var init1 = void 0; 39608 var init2 = void 0; 39609 var init3 = void 0; 39610 var encrypted0 = void 0; 39611 var encrypted1 = void 0; 39612 var encrypted2 = void 0; 39613 var encrypted3 = void 0; // iteration variable 39614 39615 var wordIx = void 0; // pull out the words of the IV to ensure we don't modify the 39616 // passed-in reference and easier access 39617 39618 init0 = initVector[0]; 39619 init1 = initVector[1]; 39620 init2 = initVector[2]; 39621 init3 = initVector[3]; // decrypt four word sequences, applying cipher-block chaining (CBC) 39622 // to each decrypted block 39623 39624 for (wordIx = 0; wordIx < encrypted32.length; wordIx += 4) { 39625 // convert big-endian (network order) words into little-endian 39626 // (javascript order) 39627 encrypted0 = ntoh(encrypted32[wordIx]); 39628 encrypted1 = ntoh(encrypted32[wordIx + 1]); 39629 encrypted2 = ntoh(encrypted32[wordIx + 2]); 39630 encrypted3 = ntoh(encrypted32[wordIx + 3]); // decrypt the block 39631 39632 decipher.decrypt(encrypted0, encrypted1, encrypted2, encrypted3, decrypted32, wordIx); // XOR with the IV, and restore network byte-order to obtain the 39633 // plaintext 39634 39635 decrypted32[wordIx] = ntoh(decrypted32[wordIx] ^ init0); 39636 decrypted32[wordIx + 1] = ntoh(decrypted32[wordIx + 1] ^ init1); 39637 decrypted32[wordIx + 2] = ntoh(decrypted32[wordIx + 2] ^ init2); 39638 decrypted32[wordIx + 3] = ntoh(decrypted32[wordIx + 3] ^ init3); // setup the IV for the next round 39639 39640 init0 = encrypted0; 39641 init1 = encrypted1; 39642 init2 = encrypted2; 39643 init3 = encrypted3; 39644 } 39645 39646 return decrypted; 39647 }; 39648 /** 39649 * The `Decrypter` class that manages decryption of AES 39650 * data through `AsyncStream` objects and the `decrypt` 39651 * function 39652 * 39653 * @param {Uint8Array} encrypted the encrypted bytes 39654 * @param {Uint32Array} key the bytes of the decryption key 39655 * @param {Uint32Array} initVector the initialization vector (IV) to 39656 * @param {Function} done the function to run when done 39657 * @class Decrypter 39658 */ 39659 39660 39661 var Decrypter = function () { 39662 function Decrypter(encrypted, key, initVector, done) { 39663 classCallCheck(this, Decrypter); 39664 var step = Decrypter.STEP; 39665 var encrypted32 = new Int32Array(encrypted.buffer); 39666 var decrypted = new Uint8Array(encrypted.byteLength); 39667 var i = 0; 39668 this.asyncStream_ = new AsyncStream(); // split up the encryption job and do the individual chunks asynchronously 39669 39670 this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step), key, initVector, decrypted)); 39671 39672 for (i = step; i < encrypted32.length; i += step) { 39673 initVector = new Uint32Array([ntoh(encrypted32[i - 4]), ntoh(encrypted32[i - 3]), ntoh(encrypted32[i - 2]), ntoh(encrypted32[i - 1])]); 39674 this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step), key, initVector, decrypted)); 39675 } // invoke the done() callback when everything is finished 39676 39677 39678 this.asyncStream_.push(function () { 39679 // remove pkcs#7 padding from the decrypted bytes 39680 done(null, unpad(decrypted)); 39681 }); 39682 } 39683 /** 39684 * a getter for step the maximum number of bytes to process at one time 39685 * 39686 * @return {Number} the value of step 32000 39687 */ 39688 39689 /** 39690 * @private 39691 */ 39692 39693 39694 Decrypter.prototype.decryptChunk_ = function decryptChunk_(encrypted, key, initVector, decrypted) { 39695 return function () { 39696 var bytes = decrypt(encrypted, key, initVector); 39697 decrypted.set(bytes, encrypted.byteOffset); 39698 }; 39699 }; 39700 39701 createClass(Decrypter, null, [{ 39702 key: 'STEP', 39703 get: function get$$1() { 39704 // 4 * 8000; 39705 return 32000; 39706 } 39707 }]); 39708 return Decrypter; 39709 }(); 39710 39711 /** 39712 * @videojs/http-streaming 39713 * @version 1.9.3 39714 * @copyright 2019 Brightcove, Inc 39715 * @license Apache-2.0 39716 */ 39717 /** 39718 * @file resolve-url.js - Handling how URLs are resolved and manipulated 39719 */ 39720 39721 var resolveUrl$1 = function resolveUrl(baseURL, relativeURL) { 39722 // return early if we don't need to resolve 39723 if (/^[a-z]+:/i.test(relativeURL)) { 39724 return relativeURL; 39725 } // if the base URL is relative then combine with the current location 39726 39727 39728 if (!/\/\//i.test(baseURL)) { 39729 baseURL = urlToolkit.buildAbsoluteURL(window$1.location.href, baseURL); 39730 } 39731 39732 return urlToolkit.buildAbsoluteURL(baseURL, relativeURL); 39733 }; 39734 /** 39735 * Checks whether xhr request was redirected and returns correct url depending 39736 * on `handleManifestRedirects` option 39737 * 39738 * @api private 39739 * 39740 * @param {String} url - an url being requested 39741 * @param {XMLHttpRequest} req - xhr request result 39742 * 39743 * @return {String} 39744 */ 39745 39746 39747 var resolveManifestRedirect = function resolveManifestRedirect(handleManifestRedirect, url, req) { 39748 // To understand how the responseURL below is set and generated: 39749 // - https://fetch.spec.whatwg.org/#concept-response-url 39750 // - https://fetch.spec.whatwg.org/#atomic-http-redirect-handling 39751 if (handleManifestRedirect && req.responseURL && url !== req.responseURL) { 39752 return req.responseURL; 39753 } 39754 39755 return url; 39756 }; 39757 39758 var classCallCheck$1 = function classCallCheck(instance, Constructor) { 39759 if (!(instance instanceof Constructor)) { 39760 throw new TypeError("Cannot call a class as a function"); 39761 } 39762 }; 39763 39764 var createClass$1 = function () { 39765 function defineProperties(target, props) { 39766 for (var i = 0; i < props.length; i++) { 39767 var descriptor = props[i]; 39768 descriptor.enumerable = descriptor.enumerable || false; 39769 descriptor.configurable = true; 39770 if ("value" in descriptor) descriptor.writable = true; 39771 Object.defineProperty(target, descriptor.key, descriptor); 39772 } 39773 } 39774 39775 return function (Constructor, protoProps, staticProps) { 39776 if (protoProps) defineProperties(Constructor.prototype, protoProps); 39777 if (staticProps) defineProperties(Constructor, staticProps); 39778 return Constructor; 39779 }; 39780 }(); 39781 39782 var get$1 = function get(object, property, receiver) { 39783 if (object === null) object = Function.prototype; 39784 var desc = Object.getOwnPropertyDescriptor(object, property); 39785 39786 if (desc === undefined) { 39787 var parent = Object.getPrototypeOf(object); 39788 39789 if (parent === null) { 39790 return undefined; 39791 } else { 39792 return get(parent, property, receiver); 39793 } 39794 } else if ("value" in desc) { 39795 return desc.value; 39796 } else { 39797 var getter = desc.get; 39798 39799 if (getter === undefined) { 39800 return undefined; 39801 } 39802 39803 return getter.call(receiver); 39804 } 39805 }; 39806 39807 var inherits$1 = function inherits(subClass, superClass) { 39808 if (typeof superClass !== "function" && superClass !== null) { 39809 throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); 39810 } 39811 39812 subClass.prototype = Object.create(superClass && superClass.prototype, { 39813 constructor: { 39814 value: subClass, 39815 enumerable: false, 39816 writable: true, 39817 configurable: true 39818 } 39819 }); 39820 if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; 39821 }; 39822 39823 var possibleConstructorReturn$1 = function possibleConstructorReturn(self, call) { 39824 if (!self) { 39825 throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); 39826 } 39827 39828 return call && (typeof call === "object" || typeof call === "function") ? call : self; 39829 }; 39830 39831 var slicedToArray = function () { 39832 function sliceIterator(arr, i) { 39833 var _arr = []; 39834 var _n = true; 39835 var _d = false; 39836 var _e = undefined; 39837 39838 try { 39839 for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { 39840 _arr.push(_s.value); 39841 39842 if (i && _arr.length === i) break; 39843 } 39844 } catch (err) { 39845 _d = true; 39846 _e = err; 39847 } finally { 39848 try { 39849 if (!_n && _i["return"]) _i["return"](); 39850 } finally { 39851 if (_d) throw _e; 39852 } 39853 } 39854 39855 return _arr; 39856 } 39857 39858 return function (arr, i) { 39859 if (Array.isArray(arr)) { 39860 return arr; 39861 } else if (Symbol.iterator in Object(arr)) { 39862 return sliceIterator(arr, i); 39863 } else { 39864 throw new TypeError("Invalid attempt to destructure non-iterable instance"); 39865 } 39866 }; 39867 }(); 39868 /** 39869 * @file playlist-loader.js 39870 * 39871 * A state machine that manages the loading, caching, and updating of 39872 * M3U8 playlists. 39873 * 39874 */ 39875 39876 39877 var mergeOptions$1 = videojs$1.mergeOptions, 39878 EventTarget$1 = videojs$1.EventTarget, 39879 log$1 = videojs$1.log; 39880 /** 39881 * Loops through all supported media groups in master and calls the provided 39882 * callback for each group 39883 * 39884 * @param {Object} master 39885 * The parsed master manifest object 39886 * @param {Function} callback 39887 * Callback to call for each media group 39888 */ 39889 39890 var forEachMediaGroup = function forEachMediaGroup(master, callback) { 39891 ['AUDIO', 'SUBTITLES'].forEach(function (mediaType) { 39892 for (var groupKey in master.mediaGroups[mediaType]) { 39893 for (var labelKey in master.mediaGroups[mediaType][groupKey]) { 39894 var mediaProperties = master.mediaGroups[mediaType][groupKey][labelKey]; 39895 callback(mediaProperties, mediaType, groupKey, labelKey); 39896 } 39897 } 39898 }); 39899 }; 39900 /** 39901 * Returns a new array of segments that is the result of merging 39902 * properties from an older list of segments onto an updated 39903 * list. No properties on the updated playlist will be overridden. 39904 * 39905 * @param {Array} original the outdated list of segments 39906 * @param {Array} update the updated list of segments 39907 * @param {Number=} offset the index of the first update 39908 * segment in the original segment list. For non-live playlists, 39909 * this should always be zero and does not need to be 39910 * specified. For live playlists, it should be the difference 39911 * between the media sequence numbers in the original and updated 39912 * playlists. 39913 * @return a list of merged segment objects 39914 */ 39915 39916 39917 var updateSegments = function updateSegments(original, update, offset) { 39918 var result = update.slice(); 39919 offset = offset || 0; 39920 var length = Math.min(original.length, update.length + offset); 39921 39922 for (var i = offset; i < length; i++) { 39923 result[i - offset] = mergeOptions$1(original[i], result[i - offset]); 39924 } 39925 39926 return result; 39927 }; 39928 39929 var resolveSegmentUris = function resolveSegmentUris(segment, baseUri) { 39930 if (!segment.resolvedUri) { 39931 segment.resolvedUri = resolveUrl$1(baseUri, segment.uri); 39932 } 39933 39934 if (segment.key && !segment.key.resolvedUri) { 39935 segment.key.resolvedUri = resolveUrl$1(baseUri, segment.key.uri); 39936 } 39937 39938 if (segment.map && !segment.map.resolvedUri) { 39939 segment.map.resolvedUri = resolveUrl$1(baseUri, segment.map.uri); 39940 } 39941 }; 39942 /** 39943 * Returns a new master playlist that is the result of merging an 39944 * updated media playlist into the original version. If the 39945 * updated media playlist does not match any of the playlist 39946 * entries in the original master playlist, null is returned. 39947 * 39948 * @param {Object} master a parsed master M3U8 object 39949 * @param {Object} media a parsed media M3U8 object 39950 * @return {Object} a new object that represents the original 39951 * master playlist with the updated media playlist merged in, or 39952 * null if the merge produced no change. 39953 */ 39954 39955 39956 var updateMaster = function updateMaster(master, media) { 39957 var result = mergeOptions$1(master, {}); 39958 var playlist = result.playlists[media.uri]; 39959 39960 if (!playlist) { 39961 return null; 39962 } // consider the playlist unchanged if the number of segments is equal, the media 39963 // sequence number is unchanged, and this playlist hasn't become the end of the playlist 39964 39965 39966 if (playlist.segments && media.segments && playlist.segments.length === media.segments.length && playlist.endList === media.endList && playlist.mediaSequence === media.mediaSequence) { 39967 return null; 39968 } 39969 39970 var mergedPlaylist = mergeOptions$1(playlist, media); // if the update could overlap existing segment information, merge the two segment lists 39971 39972 if (playlist.segments) { 39973 mergedPlaylist.segments = updateSegments(playlist.segments, media.segments, media.mediaSequence - playlist.mediaSequence); 39974 } // resolve any segment URIs to prevent us from having to do it later 39975 39976 39977 mergedPlaylist.segments.forEach(function (segment) { 39978 resolveSegmentUris(segment, mergedPlaylist.resolvedUri); 39979 }); // TODO Right now in the playlists array there are two references to each playlist, one 39980 // that is referenced by index, and one by URI. The index reference may no longer be 39981 // necessary. 39982 39983 for (var i = 0; i < result.playlists.length; i++) { 39984 if (result.playlists[i].uri === media.uri) { 39985 result.playlists[i] = mergedPlaylist; 39986 } 39987 } 39988 39989 result.playlists[media.uri] = mergedPlaylist; 39990 return result; 39991 }; 39992 39993 var setupMediaPlaylists = function setupMediaPlaylists(master) { 39994 // setup by-URI lookups and resolve media playlist URIs 39995 var i = master.playlists.length; 39996 39997 while (i--) { 39998 var playlist = master.playlists[i]; 39999 master.playlists[playlist.uri] = playlist; 40000 playlist.resolvedUri = resolveUrl$1(master.uri, playlist.uri); 40001 playlist.id = i; 40002 40003 if (!playlist.attributes) { 40004 // Although the spec states an #EXT-X-STREAM-INF tag MUST have a 40005 // BANDWIDTH attribute, we can play the stream without it. This means a poorly 40006 // formatted master playlist may not have an attribute list. An attributes 40007 // property is added here to prevent undefined references when we encounter 40008 // this scenario. 40009 playlist.attributes = {}; 40010 log$1.warn('Invalid playlist STREAM-INF detected. Missing BANDWIDTH attribute.'); 40011 } 40012 } 40013 }; 40014 40015 var resolveMediaGroupUris = function resolveMediaGroupUris(master) { 40016 forEachMediaGroup(master, function (properties) { 40017 if (properties.uri) { 40018 properties.resolvedUri = resolveUrl$1(master.uri, properties.uri); 40019 } 40020 }); 40021 }; 40022 /** 40023 * Calculates the time to wait before refreshing a live playlist 40024 * 40025 * @param {Object} media 40026 * The current media 40027 * @param {Boolean} update 40028 * True if there were any updates from the last refresh, false otherwise 40029 * @return {Number} 40030 * The time in ms to wait before refreshing the live playlist 40031 */ 40032 40033 40034 var refreshDelay = function refreshDelay(media, update) { 40035 var lastSegment = media.segments[media.segments.length - 1]; 40036 var delay = void 0; 40037 40038 if (update && lastSegment && lastSegment.duration) { 40039 delay = lastSegment.duration * 1000; 40040 } else { 40041 // if the playlist is unchanged since the last reload or last segment duration 40042 // cannot be determined, try again after half the target duration 40043 delay = (media.targetDuration || 10) * 500; 40044 } 40045 40046 return delay; 40047 }; 40048 /** 40049 * Load a playlist from a remote location 40050 * 40051 * @class PlaylistLoader 40052 * @extends Stream 40053 * @param {String} srcUrl the url to start with 40054 * @param {Boolean} withCredentials the withCredentials xhr option 40055 * @constructor 40056 */ 40057 40058 40059 var PlaylistLoader = function (_EventTarget) { 40060 inherits$1(PlaylistLoader, _EventTarget); 40061 40062 function PlaylistLoader(srcUrl, hls) { 40063 var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; 40064 classCallCheck$1(this, PlaylistLoader); 40065 40066 var _this = possibleConstructorReturn$1(this, (PlaylistLoader.__proto__ || Object.getPrototypeOf(PlaylistLoader)).call(this)); 40067 40068 var _options$withCredenti = options.withCredentials, 40069 withCredentials = _options$withCredenti === undefined ? false : _options$withCredenti, 40070 _options$handleManife = options.handleManifestRedirects, 40071 handleManifestRedirects = _options$handleManife === undefined ? false : _options$handleManife; 40072 _this.srcUrl = srcUrl; 40073 _this.hls_ = hls; 40074 _this.withCredentials = withCredentials; 40075 _this.handleManifestRedirects = handleManifestRedirects; 40076 var hlsOptions = hls.options_; 40077 _this.customTagParsers = hlsOptions && hlsOptions.customTagParsers || []; 40078 _this.customTagMappers = hlsOptions && hlsOptions.customTagMappers || []; 40079 40080 if (!_this.srcUrl) { 40081 throw new Error('A non-empty playlist URL is required'); 40082 } // initialize the loader state 40083 40084 40085 _this.state = 'HAVE_NOTHING'; // live playlist staleness timeout 40086 40087 _this.on('mediaupdatetimeout', function () { 40088 if (_this.state !== 'HAVE_METADATA') { 40089 // only refresh the media playlist if no other activity is going on 40090 return; 40091 } 40092 40093 _this.state = 'HAVE_CURRENT_METADATA'; 40094 _this.request = _this.hls_.xhr({ 40095 uri: resolveUrl$1(_this.master.uri, _this.media().uri), 40096 withCredentials: _this.withCredentials 40097 }, function (error, req) { 40098 // disposed 40099 if (!_this.request) { 40100 return; 40101 } 40102 40103 if (error) { 40104 return _this.playlistRequestError(_this.request, _this.media().uri, 'HAVE_METADATA'); 40105 } 40106 40107 _this.haveMetadata(_this.request, _this.media().uri); 40108 }); 40109 }); 40110 40111 return _this; 40112 } 40113 40114 createClass$1(PlaylistLoader, [{ 40115 key: 'playlistRequestError', 40116 value: function playlistRequestError(xhr, url, startingState) { 40117 // any in-flight request is now finished 40118 this.request = null; 40119 40120 if (startingState) { 40121 this.state = startingState; 40122 } 40123 40124 this.error = { 40125 playlist: this.master.playlists[url], 40126 status: xhr.status, 40127 message: 'HLS playlist request error at URL: ' + url, 40128 responseText: xhr.responseText, 40129 code: xhr.status >= 500 ? 4 : 2 40130 }; 40131 this.trigger('error'); 40132 } // update the playlist loader's state in response to a new or 40133 // updated playlist. 40134 40135 }, { 40136 key: 'haveMetadata', 40137 value: function haveMetadata(xhr, url) { 40138 var _this2 = this; // any in-flight request is now finished 40139 40140 40141 this.request = null; 40142 this.state = 'HAVE_METADATA'; 40143 var parser = new Parser(); // adding custom tag parsers 40144
vendor: 4,977 bytes, lines 40145-40298
40145 this.customTagParsers.forEach(function (customParser) { 40146 return parser.addParser(customParser); 40147 }); // adding custom tag mappers 40148 40149 this.customTagMappers.forEach(function (mapper) { 40150 return parser.addTagMapper(mapper); 40151 }); 40152 parser.push(xhr.responseText); 40153 parser.end(); 40154 parser.manifest.uri = url; // m3u8-parser does not attach an attributes property to media playlists so make 40155 // sure that the property is attached to avoid undefined reference errors 40156 40157 parser.manifest.attributes = parser.manifest.attributes || {}; // merge this playlist into the master 40158 40159 var update = updateMaster(this.master, parser.manifest); 40160 this.targetDuration = parser.manifest.targetDuration; 40161 40162 if (update) { 40163 this.master = update; 40164 this.media_ = this.master.playlists[parser.manifest.uri]; 40165 } else { 40166 this.trigger('playlistunchanged'); 40167 } // refresh live playlists after a target duration passes 40168 40169 40170 if (!this.media().endList) { 40171 window$1.clearTimeout(this.mediaUpdateTimeout); 40172 this.mediaUpdateTimeout = window$1.setTimeout(function () { 40173 _this2.trigger('mediaupdatetimeout'); 40174 }, refreshDelay(this.media(), !!update)); 40175 } 40176 40177 this.trigger('loadedplaylist'); 40178 } 40179 /** 40180 * Abort any outstanding work and clean up. 40181 */ 40182 40183 }, { 40184 key: 'dispose', 40185 value: function dispose() { 40186 this.stopRequest(); 40187 window$1.clearTimeout(this.mediaUpdateTimeout); 40188 } 40189 }, { 40190 key: 'stopRequest', 40191 value: function stopRequest() { 40192 if (this.request) { 40193 var oldRequest = this.request; 40194 this.request = null; 40195 oldRequest.onreadystatechange = null; 40196 oldRequest.abort(); 40197 } 40198 } 40199 /** 40200 * When called without any arguments, returns the currently 40201 * active media playlist. When called with a single argument, 40202 * triggers the playlist loader to asynchronously switch to the 40203 * specified media playlist. Calling this method while the 40204 * loader is in the HAVE_NOTHING causes an error to be emitted 40205 * but otherwise has no effect. 40206 * 40207 * @param {Object=} playlist the parsed media playlist 40208 * object to switch to 40209 * @return {Playlist} the current loaded media 40210 */ 40211 40212 }, { 40213 key: 'media', 40214 value: function media(playlist) { 40215 var _this3 = this; // getter 40216 40217 40218 if (!playlist) { 40219 return this.media_; 40220 } // setter 40221 40222 40223 if (this.state === 'HAVE_NOTHING') { 40224 throw new Error('Cannot switch media playlist from ' + this.state); 40225 } 40226 40227 var startingState = this.state; // find the playlist object if the target playlist has been 40228 // specified by URI 40229 40230 if (typeof playlist === 'string') { 40231 if (!this.master.playlists[playlist]) { 40232 throw new Error('Unknown playlist URI: ' + playlist); 40233 } 40234 40235 playlist = this.master.playlists[playlist]; 40236 } 40237 40238 var mediaChange = !this.media_ || playlist.uri !== this.media_.uri; // switch to fully loaded playlists immediately 40239 40240 if (this.master.playlists[playlist.uri].endList) { 40241 // abort outstanding playlist requests 40242 if (this.request) { 40243 this.request.onreadystatechange = null; 40244 this.request.abort(); 40245 this.request = null; 40246 } 40247 40248 this.state = 'HAVE_METADATA'; 40249 this.media_ = playlist; // trigger media change if the active media has been updated 40250 40251 if (mediaChange) { 40252 this.trigger('mediachanging'); 40253 this.trigger('mediachange'); 40254 } 40255 40256 return; 40257 } // switching to the active playlist is a no-op 40258 40259 40260 if (!mediaChange) { 40261 return; 40262 } 40263 40264 this.state = 'SWITCHING_MEDIA'; // there is already an outstanding playlist request 40265 40266 if (this.request) { 40267 if (playlist.resolvedUri === this.request.url) { 40268 // requesting to switch to the same playlist multiple times 40269 // has no effect after the first 40270 return; 40271 } 40272 40273 this.request.onreadystatechange = null; 40274 this.request.abort(); 40275 this.request = null; 40276 } // request the new playlist 40277 40278 40279 if (this.media_) { 40280 this.trigger('mediachanging'); 40281 } 40282 40283 this.request = this.hls_.xhr({ 40284 uri: playlist.resolvedUri, 40285 withCredentials: this.withCredentials 40286 }, function (error, req) { 40287 // disposed 40288 if (!_this3.request) { 40289 return; 40290 } 40291 40292 playlist.resolvedUri = resolveManifestRedirect(_this3.handleManifestRedirects, playlist.resolvedUri, req); 40293 40294 if (error) { 40295 return _this3.playlistRequestError(_this3.request, playlist.uri, startingState); 40296 } 40297 40298 _this3.haveMetadata(req, playlist.uri);
vendor: 28,840 bytes, lines 40298-41268
40298 // fire loadedmetadata the first time a media playlist is loaded 40299 40300 40301 if (startingState === 'HAVE_MASTER') { 40302 _this3.trigger('loadedmetadata'); 40303 } else { 40304 _this3.trigger('mediachange'); 40305 } 40306 }); 40307 } 40308 /** 40309 * pause loading of the playlist 40310 */ 40311 40312 }, { 40313 key: 'pause', 40314 value: function pause() { 40315 this.stopRequest(); 40316 window$1.clearTimeout(this.mediaUpdateTimeout); 40317 40318 if (this.state === 'HAVE_NOTHING') { 40319 // If we pause the loader before any data has been retrieved, its as if we never 40320 // started, so reset to an unstarted state. 40321 this.started = false; 40322 } // Need to restore state now that no activity is happening 40323 40324 40325 if (this.state === 'SWITCHING_MEDIA') { 40326 // if the loader was in the process of switching media, it should either return to 40327 // HAVE_MASTER or HAVE_METADATA depending on if the loader has loaded a media 40328 // playlist yet. This is determined by the existence of loader.media_ 40329 if (this.media_) { 40330 this.state = 'HAVE_METADATA'; 40331 } else { 40332 this.state = 'HAVE_MASTER'; 40333 } 40334 } else if (this.state === 'HAVE_CURRENT_METADATA') { 40335 this.state = 'HAVE_METADATA'; 40336 } 40337 } 40338 /** 40339 * start loading of the playlist 40340 */ 40341 40342 }, { 40343 key: 'load', 40344 value: function load(isFinalRendition) { 40345 var _this4 = this; 40346 40347 window$1.clearTimeout(this.mediaUpdateTimeout); 40348 var media = this.media(); 40349 40350 if (isFinalRendition) { 40351 var delay = media ? media.targetDuration / 2 * 1000 : 5 * 1000; 40352 this.mediaUpdateTimeout = window$1.setTimeout(function () { 40353 return _this4.load(); 40354 }, delay); 40355 return; 40356 } 40357 40358 if (!this.started) { 40359 this.start(); 40360 return; 40361 } 40362 40363 if (media && !media.endList) { 40364 this.trigger('mediaupdatetimeout'); 40365 } else { 40366 this.trigger('loadedplaylist'); 40367 } 40368 } 40369 /** 40370 * start loading of the playlist 40371 */ 40372 40373 }, { 40374 key: 'start', 40375 value: function start() { 40376 var _this5 = this; 40377 40378 this.started = true; // request the specified URL 40379 40380 this.request = this.hls_.xhr({ 40381 uri: this.srcUrl, 40382 withCredentials: this.withCredentials 40383 }, function (error, req) { 40384 // disposed 40385 if (!_this5.request) { 40386 return; 40387 } // clear the loader's request reference 40388 40389 40390 _this5.request = null; 40391 40392 if (error) { 40393 _this5.error = { 40394 status: req.status, 40395 message: 'HLS playlist request error at URL: ' + _this5.srcUrl, 40396 responseText: req.responseText, 40397 // MEDIA_ERR_NETWORK 40398 code: 2 40399 }; 40400 40401 if (_this5.state === 'HAVE_NOTHING') { 40402 _this5.started = false; 40403 } 40404 40405 return _this5.trigger('error'); 40406 } 40407 40408 var parser = new Parser(); // adding custom tag parsers 40409 40410 _this5.customTagParsers.forEach(function (customParser) { 40411 return parser.addParser(customParser); 40412 }); // adding custom tag mappers 40413 40414 40415 _this5.customTagMappers.forEach(function (mapper) { 40416 return parser.addTagMapper(mapper); 40417 }); 40418 40419 parser.push(req.responseText); 40420 parser.end(); 40421 _this5.state = 'HAVE_MASTER'; 40422 _this5.srcUrl = resolveManifestRedirect(_this5.handleManifestRedirects, _this5.srcUrl, req); 40423 parser.manifest.uri = _this5.srcUrl; // loaded a master playlist 40424 40425 if (parser.manifest.playlists) { 40426 _this5.master = parser.manifest; 40427 setupMediaPlaylists(_this5.master); 40428 resolveMediaGroupUris(_this5.master); 40429 40430 _this5.trigger('loadedplaylist'); 40431 40432 if (!_this5.request) { 40433 // no media playlist was specifically selected so start 40434 // from the first listed one 40435 _this5.media(parser.manifest.playlists[0]); 40436 } 40437 40438 return; 40439 } // loaded a media playlist 40440 // infer a master playlist if none was previously requested 40441 40442 40443 _this5.master = { 40444 mediaGroups: { 40445 'AUDIO': {}, 40446 'VIDEO': {}, 40447 'CLOSED-CAPTIONS': {}, 40448 'SUBTITLES': {} 40449 }, 40450 uri: window$1.location.href, 40451 playlists: [{ 40452 uri: _this5.srcUrl, 40453 id: 0, 40454 resolvedUri: _this5.srcUrl, 40455 // m3u8-parser does not attach an attributes property to media playlists so make 40456 // sure that the property is attached to avoid undefined reference errors 40457 attributes: {} 40458 }] 40459 }; 40460 _this5.master.playlists[_this5.srcUrl] = _this5.master.playlists[0]; 40461 40462 _this5.haveMetadata(req, _this5.srcUrl); 40463 40464 return _this5.trigger('loadedmetadata'); 40465 }); 40466 } 40467 }]); 40468 return PlaylistLoader; 40469 }(EventTarget$1); 40470 /** 40471 * @file playlist.js 40472 * 40473 * Playlist related utilities. 40474 */ 40475 40476 40477 var createTimeRange = videojs$1.createTimeRange; 40478 /** 40479 * walk backward until we find a duration we can use 40480 * or return a failure 40481 * 40482 * @param {Playlist} playlist the playlist to walk through 40483 * @param {Number} endSequence the mediaSequence to stop walking on 40484 */ 40485 40486 var backwardDuration = function backwardDuration(playlist, endSequence) { 40487 var result = 0; 40488 var i = endSequence - playlist.mediaSequence; // if a start time is available for segment immediately following 40489 // the interval, use it 40490 40491 var segment = playlist.segments[i]; // Walk backward until we find the latest segment with timeline 40492 // information that is earlier than endSequence 40493 40494 if (segment) { 40495 if (typeof segment.start !== 'undefined') { 40496 return { 40497 result: segment.start, 40498 precise: true 40499 }; 40500 } 40501 40502 if (typeof segment.end !== 'undefined') { 40503 return { 40504 result: segment.end - segment.duration, 40505 precise: true 40506 }; 40507 } 40508 } 40509 40510 while (i--) { 40511 segment = playlist.segments[i]; 40512 40513 if (typeof segment.end !== 'undefined') { 40514 return { 40515 result: result + segment.end, 40516 precise: true 40517 }; 40518 } 40519 40520 result += segment.duration; 40521 40522 if (typeof segment.start !== 'undefined') { 40523 return { 40524 result: result + segment.start, 40525 precise: true 40526 }; 40527 } 40528 } 40529 40530 return { 40531 result: result, 40532 precise: false 40533 }; 40534 }; 40535 /** 40536 * walk forward until we find a duration we can use 40537 * or return a failure 40538 * 40539 * @param {Playlist} playlist the playlist to walk through 40540 * @param {Number} endSequence the mediaSequence to stop walking on 40541 */ 40542 40543 40544 var forwardDuration = function forwardDuration(playlist, endSequence) { 40545 var result = 0; 40546 var segment = void 0; 40547 var i = endSequence - playlist.mediaSequence; // Walk forward until we find the earliest segment with timeline 40548 // information 40549 40550 for (; i < playlist.segments.length; i++) { 40551 segment = playlist.segments[i]; 40552 40553 if (typeof segment.start !== 'undefined') { 40554 return { 40555 result: segment.start - result, 40556 precise: true 40557 }; 40558 } 40559 40560 result += segment.duration; 40561 40562 if (typeof segment.end !== 'undefined') { 40563 return { 40564 result: segment.end - result, 40565 precise: true 40566 }; 40567 } 40568 } // indicate we didn't find a useful duration estimate 40569 40570 40571 return { 40572 result: -1, 40573 precise: false 40574 }; 40575 }; 40576 /** 40577 * Calculate the media duration from the segments associated with a 40578 * playlist. The duration of a subinterval of the available segments 40579 * may be calculated by specifying an end index. 40580 * 40581 * @param {Object} playlist a media playlist object 40582 * @param {Number=} endSequence an exclusive upper boundary 40583 * for the playlist. Defaults to playlist length. 40584 * @param {Number} expired the amount of time that has dropped 40585 * off the front of the playlist in a live scenario 40586 * @return {Number} the duration between the first available segment 40587 * and end index. 40588 */ 40589 40590 40591 var intervalDuration = function intervalDuration(playlist, endSequence, expired) { 40592 var backward = void 0; 40593 var forward = void 0; 40594 40595 if (typeof endSequence === 'undefined') { 40596 endSequence = playlist.mediaSequence + playlist.segments.length; 40597 } 40598 40599 if (endSequence < playlist.mediaSequence) { 40600 return 0; 40601 } // do a backward walk to estimate the duration 40602 40603 40604 backward = backwardDuration(playlist, endSequence); 40605 40606 if (backward.precise) { 40607 // if we were able to base our duration estimate on timing 40608 // information provided directly from the Media Source, return 40609 // it 40610 return backward.result; 40611 } // walk forward to see if a precise duration estimate can be made 40612 // that way 40613 40614 40615 forward = forwardDuration(playlist, endSequence); 40616 40617 if (forward.precise) { 40618 // we found a segment that has been buffered and so it's 40619 // position is known precisely 40620 return forward.result; 40621 } // return the less-precise, playlist-based duration estimate 40622 40623 40624 return backward.result + expired; 40625 }; 40626 /** 40627 * Calculates the duration of a playlist. If a start and end index 40628 * are specified, the duration will be for the subset of the media 40629 * timeline between those two indices. The total duration for live 40630 * playlists is always Infinity. 40631 * 40632 * @param {Object} playlist a media playlist object 40633 * @param {Number=} endSequence an exclusive upper 40634 * boundary for the playlist. Defaults to the playlist media 40635 * sequence number plus its length. 40636 * @param {Number=} expired the amount of time that has 40637 * dropped off the front of the playlist in a live scenario 40638 * @return {Number} the duration between the start index and end 40639 * index. 40640 */ 40641 40642 40643 var duration = function duration(playlist, endSequence, expired) { 40644 if (!playlist) { 40645 return 0; 40646 } 40647 40648 if (typeof expired !== 'number') { 40649 expired = 0; 40650 } // if a slice of the total duration is not requested, use 40651 // playlist-level duration indicators when they're present 40652 40653 40654 if (typeof endSequence === 'undefined') { 40655 // if present, use the duration specified in the playlist 40656 if (playlist.totalDuration) { 40657 return playlist.totalDuration; 40658 } // duration should be Infinity for live playlists 40659 40660 40661 if (!playlist.endList) { 40662 return window$1.Infinity; 40663 } 40664 } // calculate the total duration based on the segment durations 40665 40666 40667 return intervalDuration(playlist, endSequence, expired); 40668 }; 40669 /** 40670 * Calculate the time between two indexes in the current playlist 40671 * neight the start- nor the end-index need to be within the current 40672 * playlist in which case, the targetDuration of the playlist is used 40673 * to approximate the durations of the segments 40674 * 40675 * @param {Object} playlist a media playlist object 40676 * @param {Number} startIndex 40677 * @param {Number} endIndex 40678 * @return {Number} the number of seconds between startIndex and endIndex 40679 */ 40680 40681 40682 var sumDurations = function sumDurations(playlist, startIndex, endIndex) { 40683 var durations = 0; 40684 40685 if (startIndex > endIndex) { 40686 var _ref = [endIndex, startIndex]; 40687 startIndex = _ref[0]; 40688 endIndex = _ref[1]; 40689 } 40690 40691 if (startIndex < 0) { 40692 for (var i = startIndex; i < Math.min(0, endIndex); i++) { 40693 durations += playlist.targetDuration; 40694 } 40695 40696 startIndex = 0; 40697 } 40698 40699 for (var _i = startIndex; _i < endIndex; _i++) { 40700 durations += playlist.segments[_i].duration; 40701 } 40702 40703 return durations; 40704 }; 40705 /** 40706 * Determines the media index of the segment corresponding to the safe edge of the live 40707 * window which is the duration of the last segment plus 2 target durations from the end 40708 * of the playlist. 40709 * 40710 * @param {Object} playlist 40711 * a media playlist object 40712 * @return {Number} 40713 * The media index of the segment at the safe live point. 0 if there is no "safe" 40714 * point. 40715 * @function safeLiveIndex 40716 */ 40717 40718 40719 var safeLiveIndex = function safeLiveIndex(playlist) { 40720 if (!playlist.segments.length) { 40721 return 0; 40722 } 40723 40724 var i = playlist.segments.length - 1; 40725 var distanceFromEnd = playlist.segments[i].duration || playlist.targetDuration; 40726 var safeDistance = distanceFromEnd + playlist.targetDuration * 2; 40727 40728 while (i--) { 40729 distanceFromEnd += playlist.segments[i].duration; 40730 40731 if (distanceFromEnd >= safeDistance) { 40732 break; 40733 } 40734 } 40735 40736 return Math.max(0, i); 40737 }; 40738 /** 40739 * Calculates the playlist end time 40740 * 40741 * @param {Object} playlist a media playlist object 40742 * @param {Number=} expired the amount of time that has 40743 * dropped off the front of the playlist in a live scenario 40744 * @param {Boolean|false} useSafeLiveEnd a boolean value indicating whether or not the 40745 * playlist end calculation should consider the safe live end 40746 * (truncate the playlist end by three segments). This is normally 40747 * used for calculating the end of the playlist's seekable range. 40748 * @returns {Number} the end time of playlist 40749 * @function playlistEnd 40750 */ 40751 40752 40753 var playlistEnd = function playlistEnd(playlist, expired, useSafeLiveEnd) { 40754 if (!playlist || !playlist.segments) { 40755 return null; 40756 } 40757 40758 if (playlist.endList) { 40759 return duration(playlist); 40760 } 40761 40762 if (expired === null) { 40763 return null; 40764 } 40765 40766 expired = expired || 0; 40767 var endSequence = useSafeLiveEnd ? safeLiveIndex(playlist) : playlist.segments.length; 40768 return intervalDuration(playlist, playlist.mediaSequence + endSequence, expired); 40769 }; 40770 /** 40771 * Calculates the interval of time that is currently seekable in a 40772 * playlist. The returned time ranges are relative to the earliest 40773 * moment in the specified playlist that is still available. A full 40774 * seekable implementation for live streams would need to offset 40775 * these values by the duration of content that has expired from the 40776 * stream. 40777 * 40778 * @param {Object} playlist a media playlist object 40779 * dropped off the front of the playlist in a live scenario 40780 * @param {Number=} expired the amount of time that has 40781 * dropped off the front of the playlist in a live scenario 40782 * @return {TimeRanges} the periods of time that are valid targets 40783 * for seeking 40784 */ 40785 40786 40787 var seekable = function seekable(playlist, expired) { 40788 var useSafeLiveEnd = true; 40789 var seekableStart = expired || 0; 40790 var seekableEnd = playlistEnd(playlist, expired, useSafeLiveEnd); 40791 40792 if (seekableEnd === null) { 40793 return createTimeRange(); 40794 } 40795 40796 return createTimeRange(seekableStart, seekableEnd); 40797 }; 40798 40799 var isWholeNumber = function isWholeNumber(num) { 40800 return num - Math.floor(num) === 0; 40801 }; 40802 40803 var roundSignificantDigit = function roundSignificantDigit(increment, num) { 40804 // If we have a whole number, just add 1 to it 40805 if (isWholeNumber(num)) { 40806 return num + increment * 0.1; 40807 } 40808 40809 var numDecimalDigits = num.toString().split('.')[1].length; 40810 40811 for (var i = 1; i <= numDecimalDigits; i++) { 40812 var scale = Math.pow(10, i); 40813 var temp = num * scale; 40814 40815 if (isWholeNumber(temp) || i === numDecimalDigits) { 40816 return (temp + increment) / scale; 40817 } 40818 } 40819 }; 40820 40821 var ceilLeastSignificantDigit = roundSignificantDigit.bind(null, 1); 40822 var floorLeastSignificantDigit = roundSignificantDigit.bind(null, -1); 40823 /** 40824 * Determine the index and estimated starting time of the segment that 40825 * contains a specified playback position in a media playlist. 40826 * 40827 * @param {Object} playlist the media playlist to query 40828 * @param {Number} currentTime The number of seconds since the earliest 40829 * possible position to determine the containing segment for 40830 * @param {Number} startIndex 40831 * @param {Number} startTime 40832 * @return {Object} 40833 */ 40834 40835 var getMediaInfoForTime = function getMediaInfoForTime(playlist, currentTime, startIndex, startTime) { 40836 var i = void 0; 40837 var segment = void 0; 40838 var numSegments = playlist.segments.length; 40839 var time = currentTime - startTime; 40840 40841 if (time < 0) { 40842 // Walk backward from startIndex in the playlist, adding durations 40843 // until we find a segment that contains `time` and return it 40844 if (startIndex > 0) { 40845 for (i = startIndex - 1; i >= 0; i--) { 40846 segment = playlist.segments[i]; 40847 time += floorLeastSignificantDigit(segment.duration); 40848 40849 if (time > 0) { 40850 return { 40851 mediaIndex: i, 40852 startTime: startTime - sumDurations(playlist, startIndex, i) 40853 }; 40854 } 40855 } 40856 } // We were unable to find a good segment within the playlist 40857 // so select the first segment 40858 40859 40860 return { 40861 mediaIndex: 0, 40862 startTime: currentTime 40863 }; 40864 } // When startIndex is negative, we first walk forward to first segment 40865 // adding target durations. If we "run out of time" before getting to 40866 // the first segment, return the first segment 40867 40868 40869 if (startIndex < 0) { 40870 for (i = startIndex; i < 0; i++) { 40871 time -= playlist.targetDuration; 40872 40873 if (time < 0) { 40874 return { 40875 mediaIndex: 0, 40876 startTime: currentTime 40877 }; 40878 } 40879 } 40880 40881 startIndex = 0; 40882 } // Walk forward from startIndex in the playlist, subtracting durations 40883 // until we find a segment that contains `time` and return it 40884 40885 40886 for (i = startIndex; i < numSegments; i++) { 40887 segment = playlist.segments[i]; 40888 time -= ceilLeastSignificantDigit(segment.duration); 40889 40890 if (time < 0) { 40891 return { 40892 mediaIndex: i, 40893 startTime: startTime + sumDurations(playlist, startIndex, i) 40894 }; 40895 } 40896 } // We are out of possible candidates so load the last one... 40897 40898 40899 return { 40900 mediaIndex: numSegments - 1, 40901 startTime: currentTime 40902 }; 40903 }; 40904 /** 40905 * Check whether the playlist is blacklisted or not. 40906 * 40907 * @param {Object} playlist the media playlist object 40908 * @return {boolean} whether the playlist is blacklisted or not 40909 * @function isBlacklisted 40910 */ 40911 40912 40913 var isBlacklisted = function isBlacklisted(playlist) { 40914 return playlist.excludeUntil && playlist.excludeUntil > Date.now(); 40915 }; 40916 /** 40917 * Check whether the playlist is compatible with current playback configuration or has 40918 * been blacklisted permanently for being incompatible. 40919 * 40920 * @param {Object} playlist the media playlist object 40921 * @return {boolean} whether the playlist is incompatible or not 40922 * @function isIncompatible 40923 */ 40924 40925 40926 var isIncompatible = function isIncompatible(playlist) { 40927 return playlist.excludeUntil && playlist.excludeUntil === Infinity; 40928 }; 40929 /** 40930 * Check whether the playlist is enabled or not. 40931 * 40932 * @param {Object} playlist the media playlist object 40933 * @return {boolean} whether the playlist is enabled or not 40934 * @function isEnabled 40935 */ 40936 40937 40938 var isEnabled = function isEnabled(playlist) { 40939 var blacklisted = isBlacklisted(playlist); 40940 return !playlist.disabled && !blacklisted; 40941 }; 40942 /** 40943 * Check whether the playlist has been manually disabled through the representations api. 40944 * 40945 * @param {Object} playlist the media playlist object 40946 * @return {boolean} whether the playlist is disabled manually or not 40947 * @function isDisabled 40948 */ 40949 40950 40951 var isDisabled = function isDisabled(playlist) { 40952 return playlist.disabled; 40953 }; 40954 /** 40955 * Returns whether the current playlist is an AES encrypted HLS stream 40956 * 40957 * @return {Boolean} true if it's an AES encrypted HLS stream 40958 */ 40959 40960 40961 var isAes = function isAes(media) { 40962 for (var i = 0; i < media.segments.length; i++) { 40963 if (media.segments[i].key) { 40964 return true; 40965 } 40966 } 40967 40968 return false; 40969 }; 40970 /** 40971 * Returns whether the current playlist contains fMP4 40972 * 40973 * @return {Boolean} true if the playlist contains fMP4 40974 */ 40975 40976 40977 var isFmp4 = function isFmp4(media) { 40978 for (var i = 0; i < media.segments.length; i++) { 40979 if (media.segments[i].map) { 40980 return true; 40981 } 40982 } 40983 40984 return false; 40985 }; 40986 /** 40987 * Checks if the playlist has a value for the specified attribute 40988 * 40989 * @param {String} attr 40990 * Attribute to check for 40991 * @param {Object} playlist 40992 * The media playlist object 40993 * @return {Boolean} 40994 * Whether the playlist contains a value for the attribute or not 40995 * @function hasAttribute 40996 */ 40997 40998 40999 var hasAttribute = function hasAttribute(attr, playlist) { 41000 return playlist.attributes && playlist.attributes[attr]; 41001 }; 41002 /** 41003 * Estimates the time required to complete a segment download from the specified playlist 41004 * 41005 * @param {Number} segmentDuration 41006 * Duration of requested segment 41007 * @param {Number} bandwidth 41008 * Current measured bandwidth of the player 41009 * @param {Object} playlist 41010 * The media playlist object 41011 * @param {Number=} bytesReceived 41012 * Number of bytes already received for the request. Defaults to 0 41013 * @return {Number|NaN} 41014 * The estimated time to request the segment. NaN if bandwidth information for 41015 * the given playlist is unavailable 41016 * @function estimateSegmentRequestTime 41017 */ 41018 41019 41020 var estimateSegmentRequestTime = function estimateSegmentRequestTime(segmentDuration, bandwidth, playlist) { 41021 var bytesReceived = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 0; 41022 41023 if (!hasAttribute('BANDWIDTH', playlist)) { 41024 return NaN; 41025 } 41026 41027 var size = segmentDuration * playlist.attributes.BANDWIDTH; 41028 return (size - bytesReceived * 8) / bandwidth; 41029 }; 41030 /* 41031 * Returns whether the current playlist is the lowest rendition 41032 * 41033 * @return {Boolean} true if on lowest rendition 41034 */ 41035 41036 41037 var isLowestEnabledRendition = function isLowestEnabledRendition(master, media) { 41038 if (master.playlists.length === 1) { 41039 return true; 41040 } 41041 41042 var currentBandwidth = media.attributes.BANDWIDTH || Number.MAX_VALUE; 41043 return master.playlists.filter(function (playlist) { 41044 if (!isEnabled(playlist)) { 41045 return false; 41046 } 41047 41048 return (playlist.attributes.BANDWIDTH || 0) < currentBandwidth; 41049 }).length === 0; 41050 }; // exports 41051 41052 41053 var Playlist = { 41054 duration: duration, 41055 seekable: seekable, 41056 safeLiveIndex: safeLiveIndex, 41057 getMediaInfoForTime: getMediaInfoForTime, 41058 isEnabled: isEnabled, 41059 isDisabled: isDisabled, 41060 isBlacklisted: isBlacklisted, 41061 isIncompatible: isIncompatible, 41062 playlistEnd: playlistEnd, 41063 isAes: isAes, 41064 isFmp4: isFmp4, 41065 hasAttribute: hasAttribute, 41066 estimateSegmentRequestTime: estimateSegmentRequestTime, 41067 isLowestEnabledRendition: isLowestEnabledRendition 41068 }; 41069 /** 41070 * @file xhr.js 41071 */ 41072 41073 var videojsXHR = videojs$1.xhr, 41074 mergeOptions$1$1 = videojs$1.mergeOptions; 41075 41076 var xhrFactory = function xhrFactory() { 41077 var xhr = function XhrFunction(options, callback) { 41078 // Add a default timeout for all hls requests 41079 options = mergeOptions$1$1({ 41080 timeout: 45e3 41081 }, options); // Allow an optional user-specified function to modify the option 41082 // object before we construct the xhr request 41083 41084 var beforeRequest = XhrFunction.beforeRequest || videojs$1.Hls.xhr.beforeRequest; 41085 41086 if (beforeRequest && typeof beforeRequest === 'function') { 41087 var newOptions = beforeRequest(options); 41088 41089 if (newOptions) { 41090 options = newOptions; 41091 } 41092 } 41093 41094 var request = videojsXHR(options, function (error, response) { 41095 var reqResponse = request.response; 41096 41097 if (!error && reqResponse) { 41098 request.responseTime = Date.now(); 41099 request.roundTripTime = request.responseTime - request.requestTime; 41100 request.bytesReceived = reqResponse.byteLength || reqResponse.length; 41101 41102 if (!request.bandwidth) { 41103 request.bandwidth = Math.floor(request.bytesReceived / request.roundTripTime * 8 * 1000); 41104 } 41105 } 41106 41107 if (response.headers) { 41108 request.responseHeaders = response.headers; 41109 } // videojs.xhr now uses a specific code on the error 41110 // object to signal that a request has timed out instead 41111 // of setting a boolean on the request object 41112 41113 41114 if (error && error.code === 'ETIMEDOUT') { 41115 request.timedout = true; 41116 } // videojs.xhr no longer considers status codes outside of 200 and 0 41117 // (for file uris) to be errors, but the old XHR did, so emulate that 41118 // behavior. Status 206 may be used in response to byterange requests. 41119 41120 41121 if (!error && !request.aborted && response.statusCode !== 200 && response.statusCode !== 206 && response.statusCode !== 0) { 41122 error = new Error('XHR Failed with a response of: ' + (request && (reqResponse || request.responseText))); 41123 } 41124 41125 callback(error, request); 41126 }); 41127 var originalAbort = request.abort; 41128 41129 request.abort = function () { 41130 request.aborted = true; 41131 return originalAbort.apply(request, arguments); 41132 }; 41133 41134 request.uri = options.uri; 41135 request.requestTime = Date.now(); 41136 return request; 41137 }; 41138 41139 return xhr; 41140 }; 41141 /** 41142 * @file bin-utils.js 41143 */ 41144 41145 /** 41146 * convert a TimeRange to text 41147 * 41148 * @param {TimeRange} range the timerange to use for conversion 41149 * @param {Number} i the iterator on the range to convert 41150 */ 41151 41152 41153 var textRange = function textRange(range, i) { 41154 return range.start(i) + '-' + range.end(i); 41155 }; 41156 /** 41157 * format a number as hex string 41158 * 41159 * @param {Number} e The number 41160 * @param {Number} i the iterator 41161 */ 41162 41163 41164 var formatHexString = function formatHexString(e, i) { 41165 var value = e.toString(16); 41166 return '00'.substring(0, 2 - value.length) + value + (i % 2 ? ' ' : ''); 41167 }; 41168 41169 var formatAsciiString = function formatAsciiString(e) { 41170 if (e >= 0x20 && e < 0x7e) { 41171 return String.fromCharCode(e); 41172 } 41173 41174 return '.'; 41175 }; 41176 /** 41177 * Creates an object for sending to a web worker modifying properties that are TypedArrays 41178 * into a new object with seperated properties for the buffer, byteOffset, and byteLength. 41179 * 41180 * @param {Object} message 41181 * Object of properties and values to send to the web worker 41182 * @return {Object} 41183 * Modified message with TypedArray values expanded 41184 * @function createTransferableMessage 41185 */ 41186 41187 41188 var createTransferableMessage = function createTransferableMessage(message) { 41189 var transferable = {}; 41190 Object.keys(message).forEach(function (key) { 41191 var value = message[key]; 41192 41193 if (ArrayBuffer.isView(value)) { 41194 transferable[key] = { 41195 bytes: value.buffer, 41196 byteOffset: value.byteOffset, 41197 byteLength: value.byteLength 41198 }; 41199 } else { 41200 transferable[key] = value; 41201 } 41202 }); 41203 return transferable; 41204 }; 41205 /** 41206 * Returns a unique string identifier for a media initialization 41207 * segment. 41208 */ 41209 41210 41211 var initSegmentId = function initSegmentId(initSegment) { 41212 var byterange = initSegment.byterange || { 41213 length: Infinity, 41214 offset: 0 41215 }; 41216 return [byterange.length, byterange.offset, initSegment.resolvedUri].join(','); 41217 }; 41218 /** 41219 * utils to help dump binary data to the console 41220 */ 41221 41222 41223 var hexDump = function hexDump(data) { 41224 var bytes = Array.prototype.slice.call(data); 41225 var step = 16; 41226 var result = ''; 41227 var hex = void 0; 41228 var ascii = void 0; 41229 41230 for (var j = 0; j < bytes.length / step; j++) { 41231 hex = bytes.slice(j * step, j * step + step).map(formatHexString).join(''); 41232 ascii = bytes.slice(j * step, j * step + step).map(formatAsciiString).join(''); 41233 result += hex + ' ' + ascii + '\n'; 41234 } 41235 41236 return result; 41237 }; 41238 41239 var tagDump = function tagDump(_ref) { 41240 var bytes = _ref.bytes; 41241 return hexDump(bytes); 41242 }; 41243 41244 var textRanges = function textRanges(ranges) { 41245 var result = ''; 41246 var i = void 0; 41247 41248 for (i = 0; i < ranges.length; i++) { 41249 result += textRange(ranges, i) + ' '; 41250 } 41251 41252 return result; 41253 }; 41254 41255 var utils$1 = 41256 /*#__PURE__*/ 41257 Object.freeze({ 41258 createTransferableMessage: createTransferableMessage, 41259 initSegmentId: initSegmentId, 41260 hexDump: hexDump, 41261 tagDump: tagDump, 41262 textRanges: textRanges 41263 }); // TODO handle fmp4 case where the timing info is accurate and doesn't involve transmux 41264 // Add 25% to the segment duration to account for small discrepencies in segment timing. 41265 // 25% was arbitrarily chosen, and may need to be refined over time. 41266 41267 var SEGMENT_END_FUDGE_PERCENT = 0.25; 41268 /**
41269 * Converts a player time (any time that can be gotten/set from player.currentTime(), 41270 * e.g., any time within player.seekable().start(0) to player.seekable().end(0)) to a 41271 * program time (any time referencing the real world (e.g., EXT-X-PROGRAM-DATE-TIME)). 41272 * 41273 * The containing segment is required as the EXT-X-PROGRAM-DATE-TIME serves as an "anchor 41274 * point" (a point where we have a mapping from program time to player time, with player 41275 * time being the post transmux start of the segment). 41276 * 41277 * For more details, see [this doc](../../docs/program-time-from-player-time.md). 41278 * 41279 * @param {Number} playerTime the player time 41280 * @param {Object} segment the segment which contains the player time 41281 * @return {Date} program time 41282 */ 41283 41284 var playerTimeToProgramTime = function playerTimeToProgramTime(playerTime, segment) { 41285 if (!segment.dateTimeObject) { 41286 // Can't convert without an "anchor point" for the program time (i.e., a time that can 41287 // be used to map the start of a segment with a real world time). 41288 return null; 41289 } 41290 41291 var transmuxerPrependedSeconds = segment.videoTimingInfo.transmuxerPrependedSeconds; 41292 var transmuxedStart = segment.videoTimingInfo.transmuxedPresentationStart; // get the start of the content from before old content is prepended 41293 41294 var startOfSegment = transmuxedStart + transmuxerPrependedSeconds; 41295 var offsetFromSegmentStart = playerTime - startOfSegment; 41296 return new Date(segment.dateTimeObject.getTime() + offsetFromSegmentStart * 1000); 41297 }; 41298 41299 var originalSegmentVideoDuration = function originalSegmentVideoDuration(videoTimingInfo) { 41300 return videoTimingInfo.transmuxedPresentationEnd - videoTimingInfo.transmuxedPresentationStart - videoTimingInfo.transmuxerPrependedSeconds; 41301 }; 41302 /** 41303 * Finds a segment that contains the time requested given as an ISO-8601 string. The 41304 * returned segment might be an estimate or an accurate match. 41305 * 41306 * @param {String} programTime The ISO-8601 programTime to find a match for 41307 * @param {Object} playlist A playlist object to search within 41308 */ 41309 41310 41311 var findSegmentForProgramTime = function findSegmentForProgramTime(programTime, playlist) { 41312 // Assumptions: 41313 // - verifyProgramDateTimeTags has already been run 41314 // - live streams have been started 41315 var dateTimeObject = void 0; 41316 41317 try { 41318 dateTimeObject = new Date(programTime); 41319 } catch (e) { 41320 return null; 41321 } 41322 41323 if (!playlist || !playlist.segments || playlist.segments.length === 0) { 41324 return null; 41325 } 41326 41327 var segment = playlist.segments[0]; 41328 41329 if (dateTimeObject < segment.dateTimeObject) { 41330 // Requested time is before stream start. 41331 return null; 41332 } 41333 41334 for (var i = 0; i < playlist.segments.length - 1; i++) { 41335 segment = playlist.segments[i]; 41336 var nextSegmentStart = playlist.segments[i + 1].dateTimeObject; 41337 41338 if (dateTimeObject < nextSegmentStart) { 41339 break; 41340 } 41341 } 41342 41343 var lastSegment = playlist.segments[playlist.segments.length - 1]; 41344 var lastSegmentStart = lastSegment.dateTimeObject; 41345 var lastSegmentDuration = lastSegment.videoTimingInfo ? originalSegmentVideoDuration(lastSegment.videoTimingInfo) : lastSegment.duration + lastSegment.duration * SEGMENT_END_FUDGE_PERCENT; 41346 var lastSegmentEnd = new Date(lastSegmentStart.getTime() + lastSegmentDuration * 1000); 41347 41348 if (dateTimeObject > lastSegmentEnd) { 41349 // Beyond the end of the stream, or our best guess of the end of the stream. 41350 return null; 41351 } 41352 41353 if (dateTimeObject > lastSegmentStart) { 41354 segment = lastSegment; 41355 } 41356 41357 return { 41358 segment: segment, 41359 estimatedStart: segment.videoTimingInfo ? segment.videoTimingInfo.transmuxedPresentationStart : Playlist.duration(playlist, playlist.mediaSequence + playlist.segments.indexOf(segment)), 41360 // Although, given that all segments have accurate date time objects, the segment 41361 // selected should be accurate, unless the video has been transmuxed at some point 41362 // (determined by the presence of the videoTimingInfo object), the segment's "player 41363 // time" (the start time in the player) can't be considered accurate. 41364 type: segment.videoTimingInfo ? 'accurate' : 'estimate' 41365 }; 41366 }; 41367 /** 41368 * Finds a segment that contains the given player time(in seconds). 41369 * 41370 * @param {Number} time The player time to find a match for 41371 * @param {Object} playlist A playlist object to search within 41372 */ 41373 41374 41375 var findSegmentForPlayerTime = function findSegmentForPlayerTime(time, playlist) { 41376 // Assumptions: 41377 // - there will always be a segment.duration 41378 // - we can start from zero
vendor: 10,433 bytes, lines 41379-41681
41379 // - segments are in time order 41380 if (!playlist || !playlist.segments || playlist.segments.length === 0) { 41381 return null; 41382 } 41383 41384 var segmentEnd = 0; 41385 var segment = void 0; 41386 41387 for (var i = 0; i < playlist.segments.length; i++) { 41388 segment = playlist.segments[i]; // videoTimingInfo is set after the segment is downloaded and transmuxed, and 41389 // should contain the most accurate values we have for the segment's player times. 41390 // 41391 // Use the accurate transmuxedPresentationEnd value if it is available, otherwise fall 41392 // back to an estimate based on the manifest derived (inaccurate) segment.duration, to 41393 // calculate an end value. 41394 41395 segmentEnd = segment.videoTimingInfo ? segment.videoTimingInfo.transmuxedPresentationEnd : segmentEnd + segment.duration; 41396 41397 if (time <= segmentEnd) { 41398 break; 41399 } 41400 } 41401 41402 var lastSegment = playlist.segments[playlist.segments.length - 1]; 41403 41404 if (lastSegment.videoTimingInfo && lastSegment.videoTimingInfo.transmuxedPresentationEnd < time) { 41405 // The time requested is beyond the stream end. 41406 return null; 41407 } 41408 41409 if (time > segmentEnd) { 41410 // The time is within or beyond the last segment. 41411 // 41412 // Check to see if the time is beyond a reasonable guess of the end of the stream. 41413 if (time > segmentEnd + lastSegment.duration * SEGMENT_END_FUDGE_PERCENT) { 41414 // Technically, because the duration value is only an estimate, the time may still 41415 // exist in the last segment, however, there isn't enough information to make even 41416 // a reasonable estimate. 41417 return null; 41418 } 41419 41420 segment = lastSegment; 41421 } 41422 41423 return { 41424 segment: segment, 41425 estimatedStart: segment.videoTimingInfo ? segment.videoTimingInfo.transmuxedPresentationStart : segmentEnd - segment.duration, 41426 // Because videoTimingInfo is only set after transmux, it is the only way to get 41427 // accurate timing values. 41428 type: segment.videoTimingInfo ? 'accurate' : 'estimate' 41429 }; 41430 }; 41431 /** 41432 * Gives the offset of the comparisonTimestamp from the programTime timestamp in seconds. 41433 * If the offset returned is positive, the programTime occurs after the 41434 * comparisonTimestamp. 41435 * If the offset is negative, the programTime occurs before the comparisonTimestamp. 41436 * 41437 * @param {String} comparisonTimeStamp An ISO-8601 timestamp to compare against 41438 * @param {String} programTime The programTime as an ISO-8601 string 41439 * @return {Number} offset 41440 */ 41441 41442 41443 var getOffsetFromTimestamp = function getOffsetFromTimestamp(comparisonTimeStamp, programTime) { 41444 var segmentDateTime = void 0; 41445 var programDateTime = void 0; 41446 41447 try { 41448 segmentDateTime = new Date(comparisonTimeStamp); 41449 programDateTime = new Date(programTime); 41450 } catch (e) {// TODO handle error 41451 } 41452 41453 var segmentTimeEpoch = segmentDateTime.getTime(); 41454 var programTimeEpoch = programDateTime.getTime(); 41455 return (programTimeEpoch - segmentTimeEpoch) / 1000; 41456 }; 41457 /** 41458 * Checks that all segments in this playlist have programDateTime tags. 41459 * 41460 * @param {Object} playlist A playlist object 41461 */ 41462 41463 41464 var verifyProgramDateTimeTags = function verifyProgramDateTimeTags(playlist) { 41465 if (!playlist.segments || playlist.segments.length === 0) { 41466 return false; 41467 } 41468 41469 for (var i = 0; i < playlist.segments.length; i++) { 41470 var segment = playlist.segments[i]; 41471 41472 if (!segment.dateTimeObject) { 41473 return false; 41474 } 41475 } 41476 41477 return true; 41478 }; 41479 /** 41480 * Returns the programTime of the media given a playlist and a playerTime. 41481 * The playlist must have programDateTime tags for a programDateTime tag to be returned. 41482 * If the segments containing the time requested have not been buffered yet, an estimate 41483 * may be returned to the callback. 41484 * 41485 * @param {Object} args 41486 * @param {Object} args.playlist A playlist object to search within 41487 * @param {Number} time A playerTime in seconds 41488 * @param {Function} callback(err, programTime) 41489 * @returns {String} err.message A detailed error message 41490 * @returns {Object} programTime 41491 * @returns {Number} programTime.mediaSeconds The streamTime in seconds 41492 * @returns {String} programTime.programDateTime The programTime as an ISO-8601 String 41493 */ 41494 41495 41496 var getProgramTime = function getProgramTime(_ref) { 41497 var playlist = _ref.playlist, 41498 _ref$time = _ref.time, 41499 time = _ref$time === undefined ? undefined : _ref$time, 41500 callback = _ref.callback; 41501 41502 if (!callback) { 41503 throw new Error('getProgramTime: callback must be provided'); 41504 } 41505 41506 if (!playlist || time === undefined) { 41507 return callback({ 41508 message: 'getProgramTime: playlist and time must be provided' 41509 }); 41510 } 41511 41512 var matchedSegment = findSegmentForPlayerTime(time, playlist); 41513 41514 if (!matchedSegment) { 41515 return callback({ 41516 message: 'valid programTime was not found' 41517 }); 41518 } 41519 41520 if (matchedSegment.type === 'estimate') { 41521 return callback({ 41522 message: 'Accurate programTime could not be determined.' + ' Please seek to e.seekTime and try again', 41523 seekTime: matchedSegment.estimatedStart 41524 }); 41525 } 41526 41527 var programTimeObject = { 41528 mediaSeconds: time 41529 }; 41530 var programTime = playerTimeToProgramTime(time, matchedSegment.segment); 41531 41532 if (programTime) { 41533 programTimeObject.programDateTime = programTime.toISOString(); 41534 } 41535 41536 return callback(null, programTimeObject); 41537 }; 41538 /** 41539 * Seeks in the player to a time that matches the given programTime ISO-8601 string. 41540 * 41541 * @param {Object} args 41542 * @param {String} args.programTime A programTime to seek to as an ISO-8601 String 41543 * @param {Object} args.playlist A playlist to look within 41544 * @param {Number} args.retryCount The number of times to try for an accurate seek. Default is 2. 41545 * @param {Function} args.seekTo A method to perform a seek 41546 * @param {Boolean} args.pauseAfterSeek Whether to end in a paused state after seeking. Default is true. 41547 * @param {Object} args.tech The tech to seek on 41548 * @param {Function} args.callback(err, newTime) A callback to return the new time to 41549 * @returns {String} err.message A detailed error message 41550 * @returns {Number} newTime The exact time that was seeked to in seconds 41551 */ 41552 41553 41554 var seekToProgramTime = function seekToProgramTime(_ref2) { 41555 var programTime = _ref2.programTime, 41556 playlist = _ref2.playlist, 41557 _ref2$retryCount = _ref2.retryCount, 41558 retryCount = _ref2$retryCount === undefined ? 2 : _ref2$retryCount, 41559 seekTo = _ref2.seekTo, 41560 _ref2$pauseAfterSeek = _ref2.pauseAfterSeek, 41561 pauseAfterSeek = _ref2$pauseAfterSeek === undefined ? true : _ref2$pauseAfterSeek, 41562 tech = _ref2.tech, 41563 callback = _ref2.callback; 41564 41565 if (!callback) { 41566 throw new Error('seekToProgramTime: callback must be provided'); 41567 } 41568 41569 if (typeof programTime === 'undefined' || !playlist || !seekTo) { 41570 return callback({ 41571 message: 'seekToProgramTime: programTime, seekTo and playlist must be provided' 41572 }); 41573 } 41574 41575 if (!playlist.endList && !tech.hasStarted_) { 41576 return callback({ 41577 message: 'player must be playing a live stream to start buffering' 41578 }); 41579 } 41580 41581 if (!verifyProgramDateTimeTags(playlist)) { 41582 return callback({ 41583 message: 'programDateTime tags must be provided in the manifest ' + playlist.resolvedUri 41584 }); 41585 } 41586 41587 var matchedSegment = findSegmentForProgramTime(programTime, playlist); // no match 41588 41589 if (!matchedSegment) { 41590 return callback({ 41591 message: programTime + ' was not found in the stream' 41592 }); 41593 } 41594 41595 var segment = matchedSegment.segment; 41596 var mediaOffset = getOffsetFromTimestamp(segment.dateTimeObject, programTime); 41597 41598 if (matchedSegment.type === 'estimate') { 41599 // we've run out of retries 41600 if (retryCount === 0) { 41601 return callback({ 41602 message: programTime + ' is not buffered yet. Try again' 41603 }); 41604 } 41605 41606 seekTo(matchedSegment.estimatedStart + mediaOffset); 41607 tech.one('seeked', function () { 41608 seekToProgramTime({ 41609 programTime: programTime, 41610 playlist: playlist, 41611 retryCount: retryCount - 1, 41612 seekTo: seekTo, 41613 pauseAfterSeek: pauseAfterSeek, 41614 tech: tech, 41615 callback: callback 41616 }); 41617 }); 41618 return; 41619 } // Since the segment.start value is determined from the buffered end or ending time 41620 // of the prior segment, the seekToTime doesn't need to account for any transmuxer 41621 // modifications. 41622 41623 41624 var seekToTime = segment.start + mediaOffset; 41625 41626 var seekedCallback = function seekedCallback() { 41627 return callback(null, tech.currentTime()); 41628 }; // listen for seeked event 41629 41630 41631 tech.one('seeked', seekedCallback); // pause before seeking as video.js will restore this state 41632 41633 if (pauseAfterSeek) { 41634 tech.pause(); 41635 } 41636 41637 seekTo(seekToTime); 41638 }; 41639 /** 41640 * ranges 41641 * 41642 * Utilities for working with TimeRanges. 41643 * 41644 */ 41645 // Fudge factor to account for TimeRanges rounding 41646 41647 41648 var TIME_FUDGE_FACTOR = 1 / 30; // Comparisons between time values such as current time and the end of the buffered range 41649 // can be misleading because of precision differences or when the current media has poorly 41650 // aligned audio and video, which can cause values to be slightly off from what you would 41651 // expect. This value is what we consider to be safe to use in such comparisons to account 41652 // for these scenarios. 41653 41654 var SAFE_TIME_DELTA = TIME_FUDGE_FACTOR * 3; 41655 41656 var filterRanges = function filterRanges(timeRanges, predicate) { 41657 var results = []; 41658 var i = void 0; 41659 41660 if (timeRanges && timeRanges.length) { 41661 // Search for ranges that match the predicate 41662 for (i = 0; i < timeRanges.length; i++) { 41663 if (predicate(timeRanges.start(i), timeRanges.end(i))) { 41664 results.push([timeRanges.start(i), timeRanges.end(i)]); 41665 } 41666 } 41667 } 41668 41669 return videojs$1.createTimeRanges(results); 41670 }; 41671 /** 41672 * Attempts to find the buffered TimeRange that contains the specified 41673 * time. 41674 * @param {TimeRanges} buffered - the TimeRanges object to query 41675 * @param {number} time - the time to filter on. 41676 * @returns {TimeRanges} a new TimeRanges object 41677 */ 41678 41679 41680 var findRange = function findRange(buffered, time) { 41681 return filterRanges(buffered, function (start, end) {
vendor: 82,872 bytes, lines 41682-44063
41682 return start - TIME_FUDGE_FACTOR <= time && end + TIME_FUDGE_FACTOR >= time; 41683 }); 41684 }; 41685 /** 41686 * Returns the TimeRanges that begin later than the specified time. 41687 * @param {TimeRanges} timeRanges - the TimeRanges object to query 41688 * @param {number} time - the time to filter on. 41689 * @returns {TimeRanges} a new TimeRanges object. 41690 */ 41691 41692 41693 var findNextRange = function findNextRange(timeRanges, time) { 41694 return filterRanges(timeRanges, function (start) { 41695 return start - TIME_FUDGE_FACTOR >= time; 41696 }); 41697 }; 41698 /** 41699 * Returns gaps within a list of TimeRanges 41700 * @param {TimeRanges} buffered - the TimeRanges object 41701 * @return {TimeRanges} a TimeRanges object of gaps 41702 */ 41703 41704 41705 var findGaps = function findGaps(buffered) { 41706 if (buffered.length < 2) { 41707 return videojs$1.createTimeRanges(); 41708 } 41709 41710 var ranges = []; 41711 41712 for (var i = 1; i < buffered.length; i++) { 41713 var start = buffered.end(i - 1); 41714 var end = buffered.start(i); 41715 ranges.push([start, end]); 41716 } 41717 41718 return videojs$1.createTimeRanges(ranges); 41719 }; 41720 /** 41721 * Gets a human readable string for a TimeRange 41722 * 41723 * @param {TimeRange} range 41724 * @returns {String} a human readable string 41725 */ 41726 41727 41728 var printableRange = function printableRange(range) { 41729 var strArr = []; 41730 41731 if (!range || !range.length) { 41732 return ''; 41733 } 41734 41735 for (var i = 0; i < range.length; i++) { 41736 strArr.push(range.start(i) + ' => ' + range.end(i)); 41737 } 41738 41739 return strArr.join(', '); 41740 }; 41741 /** 41742 * Calculates the amount of time left in seconds until the player hits the end of the 41743 * buffer and causes a rebuffer 41744 * 41745 * @param {TimeRange} buffered 41746 * The state of the buffer 41747 * @param {Numnber} currentTime 41748 * The current time of the player 41749 * @param {Number} playbackRate 41750 * The current playback rate of the player. Defaults to 1. 41751 * @return {Number} 41752 * Time until the player has to start rebuffering in seconds. 41753 * @function timeUntilRebuffer 41754 */ 41755 41756 41757 var timeUntilRebuffer = function timeUntilRebuffer(buffered, currentTime) { 41758 var playbackRate = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 1; 41759 var bufferedEnd = buffered.length ? buffered.end(buffered.length - 1) : 0; 41760 return (bufferedEnd - currentTime) / playbackRate; 41761 }; 41762 /** 41763 * Converts a TimeRanges object into an array representation 41764 * @param {TimeRanges} timeRanges 41765 * @returns {Array} 41766 */ 41767 41768 41769 var timeRangesToArray = function timeRangesToArray(timeRanges) { 41770 var timeRangesList = []; 41771 41772 for (var i = 0; i < timeRanges.length; i++) { 41773 timeRangesList.push({ 41774 start: timeRanges.start(i), 41775 end: timeRanges.end(i) 41776 }); 41777 } 41778 41779 return timeRangesList; 41780 }; 41781 /** 41782 * @file create-text-tracks-if-necessary.js 41783 */ 41784 41785 /** 41786 * Create text tracks on video.js if they exist on a segment. 41787 * 41788 * @param {Object} sourceBuffer the VSB or FSB 41789 * @param {Object} mediaSource the HTML media source 41790 * @param {Object} segment the segment that may contain the text track 41791 * @private 41792 */ 41793 41794 41795 var createTextTracksIfNecessary = function createTextTracksIfNecessary(sourceBuffer, mediaSource, segment) { 41796 var player = mediaSource.player_; // create an in-band caption track if one is present in the segment 41797 41798 if (segment.captions && segment.captions.length) { 41799 if (!sourceBuffer.inbandTextTracks_) { 41800 sourceBuffer.inbandTextTracks_ = {}; 41801 } 41802 41803 for (var trackId in segment.captionStreams) { 41804 if (!sourceBuffer.inbandTextTracks_[trackId]) { 41805 player.tech_.trigger({ 41806 type: 'usage', 41807 name: 'hls-608' 41808 }); 41809 var track = player.textTracks().getTrackById(trackId); 41810 41811 if (track) { 41812 // Resuse an existing track with a CC# id because this was 41813 // very likely created by videojs-contrib-hls from information 41814 // in the m3u8 for us to use 41815 sourceBuffer.inbandTextTracks_[trackId] = track; 41816 } else { 41817 // Otherwise, create a track with the default `CC#` label and 41818 // without a language 41819 sourceBuffer.inbandTextTracks_[trackId] = player.addRemoteTextTrack({ 41820 kind: 'captions', 41821 id: trackId, 41822 label: trackId 41823 }, false).track; 41824 } 41825 } 41826 } 41827 } 41828 41829 if (segment.metadata && segment.metadata.length && !sourceBuffer.metadataTrack_) { 41830 sourceBuffer.metadataTrack_ = player.addRemoteTextTrack({ 41831 kind: 'metadata', 41832 label: 'Timed Metadata' 41833 }, false).track; 41834 sourceBuffer.metadataTrack_.inBandMetadataTrackDispatchType = segment.metadata.dispatchType; 41835 } 41836 }; 41837 /** 41838 * @file remove-cues-from-track.js 41839 */ 41840 41841 /** 41842 * Remove cues from a track on video.js. 41843 * 41844 * @param {Double} start start of where we should remove the cue 41845 * @param {Double} end end of where the we should remove the cue 41846 * @param {Object} track the text track to remove the cues from 41847 * @private 41848 */ 41849 41850 41851 var removeCuesFromTrack = function removeCuesFromTrack(start, end, track) { 41852 var i = void 0; 41853 var cue = void 0; 41854 41855 if (!track) { 41856 return; 41857 } 41858 41859 if (!track.cues) { 41860 return; 41861 } 41862 41863 i = track.cues.length; 41864 41865 while (i--) { 41866 cue = track.cues[i]; // Remove any overlapping cue 41867 41868 if (cue.startTime <= end && cue.endTime >= start) { 41869 track.removeCue(cue); 41870 } 41871 } 41872 }; 41873 /** 41874 * @file add-text-track-data.js 41875 */ 41876 41877 /** 41878 * Define properties on a cue for backwards compatability, 41879 * but warn the user that the way that they are using it 41880 * is depricated and will be removed at a later date. 41881 * 41882 * @param {Cue} cue the cue to add the properties on 41883 * @private 41884 */ 41885 41886 41887 var deprecateOldCue = function deprecateOldCue(cue) { 41888 Object.defineProperties(cue.frame, { 41889 id: { 41890 get: function get() { 41891 videojs$1.log.warn('cue.frame.id is deprecated. Use cue.value.key instead.'); 41892 return cue.value.key; 41893 } 41894 }, 41895 value: { 41896 get: function get() { 41897 videojs$1.log.warn('cue.frame.value is deprecated. Use cue.value.data instead.'); 41898 return cue.value.data; 41899 } 41900 }, 41901 privateData: { 41902 get: function get() { 41903 videojs$1.log.warn('cue.frame.privateData is deprecated. Use cue.value.data instead.'); 41904 return cue.value.data; 41905 } 41906 } 41907 }); 41908 }; 41909 41910 var durationOfVideo = function durationOfVideo(duration) { 41911 var dur = void 0; 41912 41913 if (isNaN(duration) || Math.abs(duration) === Infinity) { 41914 dur = Number.MAX_VALUE; 41915 } else { 41916 dur = duration; 41917 } 41918 41919 return dur; 41920 }; 41921 /** 41922 * Add text track data to a source handler given the captions and 41923 * metadata from the buffer. 41924 * 41925 * @param {Object} sourceHandler the virtual source buffer 41926 * @param {Array} captionArray an array of caption data 41927 * @param {Array} metadataArray an array of meta data 41928 * @private 41929 */ 41930 41931 41932 var addTextTrackData = function addTextTrackData(sourceHandler, captionArray, metadataArray) { 41933 var Cue = window$1.WebKitDataCue || window$1.VTTCue; 41934 41935 if (captionArray) { 41936 captionArray.forEach(function (caption) { 41937 var track = caption.stream; 41938 this.inbandTextTracks_[track].addCue(new Cue(caption.startTime + this.timestampOffset, caption.endTime + this.timestampOffset, caption.text)); 41939 }, sourceHandler); 41940 } 41941 41942 if (metadataArray) { 41943 var videoDuration = durationOfVideo(sourceHandler.mediaSource_.duration); 41944 metadataArray.forEach(function (metadata) { 41945 var time = metadata.cueTime + this.timestampOffset; // if time isn't a finite number between 0 and Infinity, like NaN, 41946 // ignore this bit of metadata. 41947 // This likely occurs when you have an non-timed ID3 tag like TIT2, 41948 // which is the "Title/Songname/Content description" frame 41949 41950 if (typeof time !== 'number' || window$1.isNaN(time) || time < 0 || !(time < Infinity)) { 41951 return; 41952 } 41953 41954 metadata.frames.forEach(function (frame) { 41955 var cue = new Cue(time, time, frame.value || frame.url || frame.data || ''); 41956 cue.frame = frame; 41957 cue.value = frame; 41958 deprecateOldCue(cue); 41959 this.metadataTrack_.addCue(cue); 41960 }, this); 41961 }, sourceHandler); // Updating the metadeta cues so that 41962 // the endTime of each cue is the startTime of the next cue 41963 // the endTime of last cue is the duration of the video 41964 41965 if (sourceHandler.metadataTrack_ && sourceHandler.metadataTrack_.cues && sourceHandler.metadataTrack_.cues.length) { 41966 var cues = sourceHandler.metadataTrack_.cues; 41967 var cuesArray = []; // Create a copy of the TextTrackCueList... 41968 // ...disregarding cues with a falsey value 41969 41970 for (var i = 0; i < cues.length; i++) { 41971 if (cues[i]) { 41972 cuesArray.push(cues[i]); 41973 } 41974 } // Group cues by their startTime value 41975 41976 41977 var cuesGroupedByStartTime = cuesArray.reduce(function (obj, cue) { 41978 var timeSlot = obj[cue.startTime] || []; 41979 timeSlot.push(cue); 41980 obj[cue.startTime] = timeSlot; 41981 return obj; 41982 }, {}); // Sort startTimes by ascending order 41983 41984 var sortedStartTimes = Object.keys(cuesGroupedByStartTime).sort(function (a, b) { 41985 return Number(a) - Number(b); 41986 }); // Map each cue group's endTime to the next group's startTime 41987 41988 sortedStartTimes.forEach(function (startTime, idx) { 41989 var cueGroup = cuesGroupedByStartTime[startTime]; 41990 var nextTime = Number(sortedStartTimes[idx + 1]) || videoDuration; // Map each cue's endTime the next group's startTime 41991 41992 cueGroup.forEach(function (cue) { 41993 cue.endTime = nextTime; 41994 }); 41995 }); 41996 } 41997 } 41998 }; 41999 42000 var win = typeof window !== 'undefined' ? window : {}, 42001 TARGET = typeof Symbol === 'undefined' ? '__target' : Symbol(), 42002 SCRIPT_TYPE = 'application/javascript', 42003 BlobBuilder = win.BlobBuilder || win.WebKitBlobBuilder || win.MozBlobBuilder || win.MSBlobBuilder, 42004 URL = win.URL || win.webkitURL || URL && URL.msURL, 42005 Worker = win.Worker; 42006 /** 42007 * Returns a wrapper around Web Worker code that is constructible. 42008 * 42009 * @function shimWorker 42010 * 42011 * @param { String } filename The name of the file 42012 * @param { Function } fn Function wrapping the code of the worker 42013 */ 42014 42015 function shimWorker(filename, fn) { 42016 return function ShimWorker(forceFallback) { 42017 var o = this; 42018 42019 if (!fn) { 42020 return new Worker(filename); 42021 } else if (Worker && !forceFallback) { 42022 // Convert the function's inner code to a string to construct the worker 42023 var source = fn.toString().replace(/^function.+?{/, '').slice(0, -1), 42024 objURL = createSourceObject(source); 42025 this[TARGET] = new Worker(objURL); 42026 wrapTerminate(this[TARGET], objURL); 42027 return this[TARGET]; 42028 } else { 42029 var selfShim = { 42030 postMessage: function postMessage(m) { 42031 if (o.onmessage) { 42032 setTimeout(function () { 42033 o.onmessage({ 42034 data: m, 42035 target: selfShim 42036 }); 42037 }); 42038 } 42039 } 42040 }; 42041 fn.call(selfShim); 42042 42043 this.postMessage = function (m) { 42044 setTimeout(function () { 42045 selfShim.onmessage({ 42046 data: m, 42047 target: o 42048 }); 42049 }); 42050 }; 42051 42052 this.isThisThread = true; 42053 } 42054 }; 42055 } // Test Worker capabilities 42056 42057 42058 if (Worker) { 42059 var testWorker, 42060 objURL = createSourceObject('self.onmessage = function () {}'), 42061 testArray = new Uint8Array(1); 42062 42063 try { 42064 testWorker = new Worker(objURL); // Native browser on some Samsung devices throws for transferables, let's detect it 42065 42066 testWorker.postMessage(testArray, [testArray.buffer]); 42067 } catch (e) { 42068 Worker = null; 42069 } finally { 42070 URL.revokeObjectURL(objURL); 42071 42072 if (testWorker) { 42073 testWorker.terminate(); 42074 } 42075 } 42076 } 42077 42078 function createSourceObject(str) { 42079 try { 42080 return URL.createObjectURL(new Blob([str], { 42081 type: SCRIPT_TYPE 42082 })); 42083 } catch (e) { 42084 var blob = new BlobBuilder(); 42085 blob.append(str); 42086 return URL.createObjectURL(blob.getBlob(type)); 42087 } 42088 } 42089 42090 function wrapTerminate(worker, objURL) { 42091 if (!worker || !objURL) return; 42092 var term = worker.terminate; 42093 worker.objURL = objURL; 42094 42095 worker.terminate = function () { 42096 if (worker.objURL) URL.revokeObjectURL(worker.objURL); 42097 term.call(worker); 42098 }; 42099 } 42100 42101 var TransmuxWorker = new shimWorker("./transmuxer-worker.worker.js", function (window, document$$1) { 42102 var self = this; 42103 42104 var transmuxerWorker = function () { 42105 /** 42106 * mux.js 42107 * 42108 * Copyright (c) 2015 Brightcove 42109 * All rights reserved. 42110 * 42111 * Functions that generate fragmented MP4s suitable for use with Media 42112 * Source Extensions. 42113 */ 42114 var UINT32_MAX = Math.pow(2, 32) - 1; 42115 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 42116 42117 (function () { 42118 var i; 42119 types = { 42120 avc1: [], 42121 // codingname 42122 avcC: [], 42123 btrt: [], 42124 dinf: [], 42125 dref: [], 42126 esds: [], 42127 ftyp: [], 42128 hdlr: [], 42129 mdat: [], 42130 mdhd: [], 42131 mdia: [], 42132 mfhd: [], 42133 minf: [], 42134 moof: [], 42135 moov: [], 42136 mp4a: [], 42137 // codingname 42138 mvex: [], 42139 mvhd: [], 42140 sdtp: [], 42141 smhd: [], 42142 stbl: [], 42143 stco: [], 42144 stsc: [], 42145 stsd: [], 42146 stsz: [], 42147 stts: [], 42148 styp: [], 42149 tfdt: [], 42150 tfhd: [], 42151 traf: [], 42152 trak: [], 42153 trun: [], 42154 trex: [], 42155 tkhd: [], 42156 vmhd: [] 42157 }; // In environments where Uint8Array is undefined (e.g., IE8), skip set up so that we 42158 // don't throw an error 42159 42160 if (typeof Uint8Array === 'undefined') { 42161 return; 42162 } 42163 42164 for (i in types) { 42165 if (types.hasOwnProperty(i)) { 42166 types[i] = [i.charCodeAt(0), i.charCodeAt(1), i.charCodeAt(2), i.charCodeAt(3)]; 42167 } 42168 } 42169 42170 MAJOR_BRAND = new Uint8Array(['i'.charCodeAt(0), 's'.charCodeAt(0), 'o'.charCodeAt(0), 'm'.charCodeAt(0)]); 42171 AVC1_BRAND = new Uint8Array(['a'.charCodeAt(0), 'v'.charCodeAt(0), 'c'.charCodeAt(0), '1'.charCodeAt(0)]); 42172 MINOR_VERSION = new Uint8Array([0, 0, 0, 1]); 42173 VIDEO_HDLR = new Uint8Array([0x00, // version 0 42174 0x00, 0x00, 0x00, // flags 42175 0x00, 0x00, 0x00, 0x00, // pre_defined 42176 0x76, 0x69, 0x64, 0x65, // handler_type: 'vide' 42177 0x00, 0x00, 0x00, 0x00, // reserved 42178 0x00, 0x00, 0x00, 0x00, // reserved 42179 0x00, 0x00, 0x00, 0x00, // reserved 42180 0x56, 0x69, 0x64, 0x65, 0x6f, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'VideoHandler' 42181 ]); 42182 AUDIO_HDLR = new Uint8Array([0x00, // version 0 42183 0x00, 0x00, 0x00, // flags 42184 0x00, 0x00, 0x00, 0x00, // pre_defined 42185 0x73, 0x6f, 0x75, 0x6e, // handler_type: 'soun' 42186 0x00, 0x00, 0x00, 0x00, // reserved 42187 0x00, 0x00, 0x00, 0x00, // reserved 42188 0x00, 0x00, 0x00, 0x00, // reserved 42189 0x53, 0x6f, 0x75, 0x6e, 0x64, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'SoundHandler' 42190 ]); 42191 HDLR_TYPES = { 42192 video: VIDEO_HDLR, 42193 audio: AUDIO_HDLR 42194 }; 42195 DREF = new Uint8Array([0x00, // version 0 42196 0x00, 0x00, 0x00, // flags 42197 0x00, 0x00, 0x00, 0x01, // entry_count 42198 0x00, 0x00, 0x00, 0x0c, // entry_size 42199 0x75, 0x72, 0x6c, 0x20, // 'url' type 42200 0x00, // version 0 42201 0x00, 0x00, 0x01 // entry_flags 42202 ]); 42203 SMHD = new Uint8Array([0x00, // version 42204 0x00, 0x00, 0x00, // flags 42205 0x00, 0x00, // balance, 0 means centered 42206 0x00, 0x00 // reserved 42207 ]); 42208 STCO = new Uint8Array([0x00, // version 42209 0x00, 0x00, 0x00, // flags 42210 0x00, 0x00, 0x00, 0x00 // entry_count 42211 ]); 42212 STSC = STCO; 42213 STSZ = new Uint8Array([0x00, // version 42214 0x00, 0x00, 0x00, // flags 42215 0x00, 0x00, 0x00, 0x00, // sample_size 42216 0x00, 0x00, 0x00, 0x00 // sample_count 42217 ]); 42218 STTS = STCO; 42219 VMHD = new Uint8Array([0x00, // version 42220 0x00, 0x00, 0x01, // flags 42221 0x00, 0x00, // graphicsmode 42222 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // opcolor 42223 ]); 42224 })(); 42225 42226 box = function box(type) { 42227 var payload = [], 42228 size = 0, 42229 i, 42230 result, 42231 view; 42232 42233 for (i = 1; i < arguments.length; i++) { 42234 payload.push(arguments[i]); 42235 } 42236 42237 i = payload.length; // calculate the total size we need to allocate 42238 42239 while (i--) { 42240 size += payload[i].byteLength; 42241 } 42242 42243 result = new Uint8Array(size + 8); 42244 view = new DataView(result.buffer, result.byteOffset, result.byteLength); 42245 view.setUint32(0, result.byteLength); 42246 result.set(type, 4); // copy the payload into the result 42247 42248 for (i = 0, size = 8; i < payload.length; i++) { 42249 result.set(payload[i], size); 42250 size += payload[i].byteLength; 42251 } 42252 42253 return result; 42254 }; 42255 42256 dinf = function dinf() { 42257 return box(types.dinf, box(types.dref, DREF)); 42258 }; 42259 42260 esds = function esds(track) { 42261 return box(types.esds, new Uint8Array([0x00, // version 42262 0x00, 0x00, 0x00, // flags 42263 // ES_Descriptor 42264 0x03, // tag, ES_DescrTag 42265 0x19, // length 42266 0x00, 0x00, // ES_ID 42267 0x00, // streamDependenceFlag, URL_flag, reserved, streamPriority 42268 // DecoderConfigDescriptor 42269 0x04, // tag, DecoderConfigDescrTag 42270 0x11, // length 42271 0x40, // object type 42272 0x15, // streamType 42273 0x00, 0x06, 0x00, // bufferSizeDB 42274 0x00, 0x00, 0xda, 0xc0, // maxBitrate 42275 0x00, 0x00, 0xda, 0xc0, // avgBitrate 42276 // DecoderSpecificInfo 42277 0x05, // tag, DecoderSpecificInfoTag 42278 0x02, // length 42279 // ISO/IEC 14496-3, AudioSpecificConfig 42280 // for samplingFrequencyIndex see ISO/IEC 13818-7:2006, 8.1.3.2.2, Table 35 42281 track.audioobjecttype << 3 | track.samplingfrequencyindex >>> 1, track.samplingfrequencyindex << 7 | track.channelcount << 3, 0x06, 0x01, 0x02 // GASpecificConfig 42282 ])); 42283 }; 42284 42285 ftyp = function ftyp() { 42286 return box(types.ftyp, MAJOR_BRAND, MINOR_VERSION, MAJOR_BRAND, AVC1_BRAND); 42287 }; 42288 42289 hdlr = function hdlr(type) { 42290 return box(types.hdlr, HDLR_TYPES[type]); 42291 }; 42292 42293 mdat = function mdat(data) { 42294 return box(types.mdat, data); 42295 }; 42296 42297 mdhd = function mdhd(track) { 42298 var result = new Uint8Array([0x00, // version 0 42299 0x00, 0x00, 0x00, // flags 42300 0x00, 0x00, 0x00, 0x02, // creation_time 42301 0x00, 0x00, 0x00, 0x03, // modification_time 42302 0x00, 0x01, 0x5f, 0x90, // timescale, 90,000 "ticks" per second 42303 track.duration >>> 24 & 0xFF, track.duration >>> 16 & 0xFF, track.duration >>> 8 & 0xFF, track.duration & 0xFF, // duration 42304 0x55, 0xc4, // 'und' language (undetermined) 42305 0x00, 0x00]); // Use the sample rate from the track metadata, when it is 42306 // defined. The sample rate can be parsed out of an ADTS header, for 42307 // instance. 42308 42309 if (track.samplerate) { 42310 result[12] = track.samplerate >>> 24 & 0xFF; 42311 result[13] = track.samplerate >>> 16 & 0xFF; 42312 result[14] = track.samplerate >>> 8 & 0xFF; 42313 result[15] = track.samplerate & 0xFF; 42314 } 42315 42316 return box(types.mdhd, result); 42317 }; 42318 42319 mdia = function mdia(track) { 42320 return box(types.mdia, mdhd(track), hdlr(track.type), minf(track)); 42321 }; 42322 42323 mfhd = function mfhd(sequenceNumber) { 42324 return box(types.mfhd, new Uint8Array([0x00, 0x00, 0x00, 0x00, // flags 42325 (sequenceNumber & 0xFF000000) >> 24, (sequenceNumber & 0xFF0000) >> 16, (sequenceNumber & 0xFF00) >> 8, sequenceNumber & 0xFF // sequence_number 42326 ])); 42327 }; 42328 42329 minf = function minf(track) { 42330 return box(types.minf, track.type === 'video' ? box(types.vmhd, VMHD) : box(types.smhd, SMHD), dinf(), stbl(track)); 42331 }; 42332 42333 moof = function moof(sequenceNumber, tracks) { 42334 var trackFragments = [], 42335 i = tracks.length; // build traf boxes for each track fragment 42336 42337 while (i--) { 42338 trackFragments[i] = traf(tracks[i]); 42339 } 42340 42341 return box.apply(null, [types.moof, mfhd(sequenceNumber)].concat(trackFragments)); 42342 }; 42343 /** 42344 * Returns a movie box. 42345 * @param tracks {array} the tracks associated with this movie 42346 * @see ISO/IEC 14496-12:2012(E), section 8.2.1 42347 */ 42348 42349 42350 moov = function moov(tracks) { 42351 var i = tracks.length, 42352 boxes = []; 42353 42354 while (i--) { 42355 boxes[i] = trak(tracks[i]); 42356 } 42357 42358 return box.apply(null, [types.moov, mvhd(0xffffffff)].concat(boxes).concat(mvex(tracks))); 42359 }; 42360 42361 mvex = function mvex(tracks) { 42362 var i = tracks.length, 42363 boxes = []; 42364 42365 while (i--) { 42366 boxes[i] = trex(tracks[i]); 42367 } 42368 42369 return box.apply(null, [types.mvex].concat(boxes)); 42370 }; 42371 42372 mvhd = function mvhd(duration) { 42373 var bytes = new Uint8Array([0x00, // version 0 42374 0x00, 0x00, 0x00, // flags 42375 0x00, 0x00, 0x00, 0x01, // creation_time 42376 0x00, 0x00, 0x00, 0x02, // modification_time 42377 0x00, 0x01, 0x5f, 0x90, // timescale, 90,000 "ticks" per second 42378 (duration & 0xFF000000) >> 24, (duration & 0xFF0000) >> 16, (duration & 0xFF00) >> 8, duration & 0xFF, // duration 42379 0x00, 0x01, 0x00, 0x00, // 1.0 rate 42380 0x01, 0x00, // 1.0 volume 42381 0x00, 0x00, // reserved 42382 0x00, 0x00, 0x00, 0x00, // reserved 42383 0x00, 0x00, 0x00, 0x00, // reserved 42384 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 42385 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 42386 0xff, 0xff, 0xff, 0xff // next_track_ID 42387 ]); 42388 return box(types.mvhd, bytes); 42389 }; 42390 42391 sdtp = function sdtp(track) { 42392 var samples = track.samples || [], 42393 bytes = new Uint8Array(4 + samples.length), 42394 flags, 42395 i; // leave the full box header (4 bytes) all zero 42396 // write the sample table 42397 42398 for (i = 0; i < samples.length; i++) { 42399 flags = samples[i].flags; 42400 bytes[i + 4] = flags.dependsOn << 4 | flags.isDependedOn << 2 | flags.hasRedundancy; 42401 } 42402 42403 return box(types.sdtp, bytes); 42404 }; 42405 42406 stbl = function stbl(track) { 42407 return box(types.stbl, stsd(track), box(types.stts, STTS), box(types.stsc, STSC), box(types.stsz, STSZ), box(types.stco, STCO)); 42408 }; 42409 42410 (function () { 42411 var videoSample, audioSample; 42412 42413 stsd = function stsd(track) { 42414 return box(types.stsd, new Uint8Array([0x00, // version 0 42415 0x00, 0x00, 0x00, // flags 42416 0x00, 0x00, 0x00, 0x01]), track.type === 'video' ? videoSample(track) : audioSample(track)); 42417 }; 42418 42419 videoSample = function videoSample(track) { 42420 var sps = track.sps || [], 42421 pps = track.pps || [], 42422 sequenceParameterSets = [], 42423 pictureParameterSets = [], 42424 i; // assemble the SPSs 42425 42426 for (i = 0; i < sps.length; i++) { 42427 sequenceParameterSets.push((sps[i].byteLength & 0xFF00) >>> 8); 42428 sequenceParameterSets.push(sps[i].byteLength & 0xFF); // sequenceParameterSetLength 42429 42430 sequenceParameterSets = sequenceParameterSets.concat(Array.prototype.slice.call(sps[i])); // SPS 42431 } // assemble the PPSs 42432 42433 42434 for (i = 0; i < pps.length; i++) { 42435 pictureParameterSets.push((pps[i].byteLength & 0xFF00) >>> 8); 42436 pictureParameterSets.push(pps[i].byteLength & 0xFF); 42437 pictureParameterSets = pictureParameterSets.concat(Array.prototype.slice.call(pps[i])); 42438 } 42439 42440 return box(types.avc1, new Uint8Array([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved 42441 0x00, 0x01, // data_reference_index 42442 0x00, 0x00, // pre_defined 42443 0x00, 0x00, // reserved 42444 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // pre_defined 42445 (track.width & 0xff00) >> 8, track.width & 0xff, // width 42446 (track.height & 0xff00) >> 8, track.height & 0xff, // height 42447 0x00, 0x48, 0x00, 0x00, // horizresolution 42448 0x00, 0x48, 0x00, 0x00, // vertresolution 42449 0x00, 0x00, 0x00, 0x00, // reserved 42450 0x00, 0x01, // frame_count 42451 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 42452 0x00, 0x18, // depth = 24 42453 0x11, 0x11 // pre_defined = -1 42454 ]), box(types.avcC, new Uint8Array([0x01, // configurationVersion 42455 track.profileIdc, // AVCProfileIndication 42456 track.profileCompatibility, // profile_compatibility 42457 track.levelIdc, // AVCLevelIndication 42458 0xff // lengthSizeMinusOne, hard-coded to 4 bytes 42459 ].concat([sps.length // numOfSequenceParameterSets 42460 ]).concat(sequenceParameterSets).concat([pps.length // numOfPictureParameterSets 42461 ]).concat(pictureParameterSets))), // "PPS" 42462 box(types.btrt, new Uint8Array([0x00, 0x1c, 0x9c, 0x80, // bufferSizeDB 42463 0x00, 0x2d, 0xc6, 0xc0, // maxBitrate 42464 0x00, 0x2d, 0xc6, 0xc0])) // avgBitrate 42465 ); 42466 }; 42467 42468 audioSample = function audioSample(track) { 42469 return box(types.mp4a, new Uint8Array([// SampleEntry, ISO/IEC 14496-12 42470 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved 42471 0x00, 0x01, // data_reference_index 42472 // AudioSampleEntry, ISO/IEC 14496-12 42473 0x00, 0x00, 0x00, 0x00, // reserved 42474 0x00, 0x00, 0x00, 0x00, // reserved 42475 (track.channelcount & 0xff00) >> 8, track.channelcount & 0xff, // channelcount 42476 (track.samplesize & 0xff00) >> 8, track.samplesize & 0xff, // samplesize 42477 0x00, 0x00, // pre_defined 42478 0x00, 0x00, // reserved 42479 (track.samplerate & 0xff00) >> 8, track.samplerate & 0xff, 0x00, 0x00 // samplerate, 16.16 42480 // MP4AudioSampleEntry, ISO/IEC 14496-14 42481 ]), esds(track)); 42482 }; 42483 })(); 42484 42485 tkhd = function tkhd(track) { 42486 var result = new Uint8Array([0x00, // version 0 42487 0x00, 0x00, 0x07, // flags 42488 0x00, 0x00, 0x00, 0x00, // creation_time 42489 0x00, 0x00, 0x00, 0x00, // modification_time 42490 (track.id & 0xFF000000) >> 24, (track.id & 0xFF0000) >> 16, (track.id & 0xFF00) >> 8, track.id & 0xFF, // track_ID 42491 0x00, 0x00, 0x00, 0x00, // reserved 42492 (track.duration & 0xFF000000) >> 24, (track.duration & 0xFF0000) >> 16, (track.duration & 0xFF00) >> 8, track.duration & 0xFF, // duration 42493 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved 42494 0x00, 0x00, // layer 42495 0x00, 0x00, // alternate_group 42496 0x01, 0x00, // non-audio track volume 42497 0x00, 0x00, // reserved 42498 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 42499 (track.width & 0xFF00) >> 8, track.width & 0xFF, 0x00, 0x00, // width 42500 (track.height & 0xFF00) >> 8, track.height & 0xFF, 0x00, 0x00 // height 42501 ]); 42502 return box(types.tkhd, result); 42503 }; 42504 /** 42505 * Generate a track fragment (traf) box. A traf box collects metadata 42506 * about tracks in a movie fragment (moof) box. 42507 */ 42508 42509 42510 traf = function traf(track) { 42511 var trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun, sampleDependencyTable, dataOffset, upperWordBaseMediaDecodeTime, lowerWordBaseMediaDecodeTime; 42512 trackFragmentHeader = box(types.tfhd, new Uint8Array([0x00, // version 0 42513 0x00, 0x00, 0x3a, // flags 42514 (track.id & 0xFF000000) >> 24, (track.id & 0xFF0000) >> 16, (track.id & 0xFF00) >> 8, track.id & 0xFF, // track_ID 42515 0x00, 0x00, 0x00, 0x01, // sample_description_index 42516 0x00, 0x00, 0x00, 0x00, // default_sample_duration 42517 0x00, 0x00, 0x00, 0x00, // default_sample_size 42518 0x00, 0x00, 0x00, 0x00 // default_sample_flags 42519 ])); 42520 upperWordBaseMediaDecodeTime = Math.floor(track.baseMediaDecodeTime / (UINT32_MAX + 1)); 42521 lowerWordBaseMediaDecodeTime = Math.floor(track.baseMediaDecodeTime % (UINT32_MAX + 1)); 42522 trackFragmentDecodeTime = box(types.tfdt, new Uint8Array([0x01, // version 1 42523 0x00, 0x00, 0x00, // flags 42524 // baseMediaDecodeTime 42525 upperWordBaseMediaDecodeTime >>> 24 & 0xFF, upperWordBaseMediaDecodeTime >>> 16 & 0xFF, upperWordBaseMediaDecodeTime >>> 8 & 0xFF, upperWordBaseMediaDecodeTime & 0xFF, lowerWordBaseMediaDecodeTime >>> 24 & 0xFF, lowerWordBaseMediaDecodeTime >>> 16 & 0xFF, lowerWordBaseMediaDecodeTime >>> 8 & 0xFF, lowerWordBaseMediaDecodeTime & 0xFF])); // the data offset specifies the number of bytes from the start of 42526 // the containing moof to the first payload byte of the associated 42527 // mdat 42528 42529 dataOffset = 32 + // tfhd 42530 20 + // tfdt 42531 8 + // traf header 42532 16 + // mfhd 42533 8 + // moof header 42534 8; // mdat header 42535 // audio tracks require less metadata 42536 42537 if (track.type === 'audio') { 42538 trackFragmentRun = trun(track, dataOffset); 42539 return box(types.traf, trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun); 42540 } // video tracks should contain an independent and disposable samples 42541 // box (sdtp) 42542 // generate one and adjust offsets to match 42543 42544 42545 sampleDependencyTable = sdtp(track); 42546 trackFragmentRun = trun(track, sampleDependencyTable.length + dataOffset); 42547 return box(types.traf, trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun, sampleDependencyTable); 42548 }; 42549 /** 42550 * Generate a track box. 42551 * @param track {object} a track definition 42552 * @return {Uint8Array} the track box 42553 */ 42554 42555 42556 trak = function trak(track) { 42557 track.duration = track.duration || 0xffffffff; 42558 return box(types.trak, tkhd(track), mdia(track)); 42559 }; 42560 42561 trex = function trex(track) { 42562 var result = new Uint8Array([0x00, // version 0 42563 0x00, 0x00, 0x00, // flags 42564 (track.id & 0xFF000000) >> 24, (track.id & 0xFF0000) >> 16, (track.id & 0xFF00) >> 8, track.id & 0xFF, // track_ID 42565 0x00, 0x00, 0x00, 0x01, // default_sample_description_index 42566 0x00, 0x00, 0x00, 0x00, // default_sample_duration 42567 0x00, 0x00, 0x00, 0x00, // default_sample_size 42568 0x00, 0x01, 0x00, 0x01 // default_sample_flags 42569 ]); // the last two bytes of default_sample_flags is the sample 42570 // degradation priority, a hint about the importance of this sample 42571 // relative to others. Lower the degradation priority for all sample 42572 // types other than video. 42573 42574 if (track.type !== 'video') { 42575 result[result.length - 1] = 0x00; 42576 } 42577 42578 return box(types.trex, result); 42579 }; 42580 42581 (function () { 42582 var audioTrun, videoTrun, trunHeader; // This method assumes all samples are uniform. That is, if a 42583 // duration is present for the first sample, it will be present for 42584 // all subsequent samples. 42585 // see ISO/IEC 14496-12:2012, Section 8.8.8.1 42586 42587 trunHeader = function trunHeader(samples, offset) { 42588 var durationPresent = 0, 42589 sizePresent = 0, 42590 flagsPresent = 0, 42591 compositionTimeOffset = 0; // trun flag constants 42592 42593 if (samples.length) { 42594 if (samples[0].duration !== undefined) { 42595 durationPresent = 0x1; 42596 } 42597 42598 if (samples[0].size !== undefined) { 42599 sizePresent = 0x2; 42600 } 42601 42602 if (samples[0].flags !== undefined) { 42603 flagsPresent = 0x4; 42604 } 42605 42606 if (samples[0].compositionTimeOffset !== undefined) { 42607 compositionTimeOffset = 0x8; 42608 } 42609 } 42610 42611 return [0x00, // version 0 42612 0x00, durationPresent | sizePresent | flagsPresent | compositionTimeOffset, 0x01, // flags 42613 (samples.length & 0xFF000000) >>> 24, (samples.length & 0xFF0000) >>> 16, (samples.length & 0xFF00) >>> 8, samples.length & 0xFF, // sample_count 42614 (offset & 0xFF000000) >>> 24, (offset & 0xFF0000) >>> 16, (offset & 0xFF00) >>> 8, offset & 0xFF // data_offset 42615 ]; 42616 }; 42617 42618 videoTrun = function videoTrun(track, offset) { 42619 var bytes, samples, sample, i; 42620 samples = track.samples || []; 42621 offset += 8 + 12 + 16 * samples.length; 42622 bytes = trunHeader(samples, offset); 42623 42624 for (i = 0; i < samples.length; i++) { 42625 sample = samples[i]; 42626 bytes = bytes.concat([(sample.duration & 0xFF000000) >>> 24, (sample.duration & 0xFF0000) >>> 16, (sample.duration & 0xFF00) >>> 8, sample.duration & 0xFF, // sample_duration 42627 (sample.size & 0xFF000000) >>> 24, (sample.size & 0xFF0000) >>> 16, (sample.size & 0xFF00) >>> 8, sample.size & 0xFF, // sample_size 42628 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 42629 (sample.compositionTimeOffset & 0xFF000000) >>> 24, (sample.compositionTimeOffset & 0xFF0000) >>> 16, (sample.compositionTimeOffset & 0xFF00) >>> 8, sample.compositionTimeOffset & 0xFF // sample_composition_time_offset 42630 ]); 42631 } 42632 42633 return box(types.trun, new Uint8Array(bytes)); 42634 }; 42635 42636 audioTrun = function audioTrun(track, offset) { 42637 var bytes, samples, sample, i; 42638 samples = track.samples || []; 42639 offset += 8 + 12 + 8 * samples.length; 42640 bytes = trunHeader(samples, offset); 42641 42642 for (i = 0; i < samples.length; i++) { 42643 sample = samples[i]; 42644 bytes = bytes.concat([(sample.duration & 0xFF000000) >>> 24, (sample.duration & 0xFF0000) >>> 16, (sample.duration & 0xFF00) >>> 8, sample.duration & 0xFF, // sample_duration 42645 (sample.size & 0xFF000000) >>> 24, (sample.size & 0xFF0000) >>> 16, (sample.size & 0xFF00) >>> 8, sample.size & 0xFF]); // sample_size 42646 } 42647 42648 return box(types.trun, new Uint8Array(bytes)); 42649 }; 42650 42651 trun = function trun(track, offset) { 42652 if (track.type === 'audio') { 42653 return audioTrun(track, offset); 42654 } 42655 42656 return videoTrun(track, offset); 42657 }; 42658 })(); 42659 42660 var mp4Generator = { 42661 ftyp: ftyp, 42662 mdat: mdat, 42663 moof: moof, 42664 moov: moov, 42665 initSegment: function initSegment(tracks) { 42666 var fileType = ftyp(), 42667 movie = moov(tracks), 42668 result; 42669 result = new Uint8Array(fileType.byteLength + movie.byteLength); 42670 result.set(fileType); 42671 result.set(movie, fileType.byteLength); 42672 return result; 42673 } 42674 }; 42675 42676 var toUnsigned = function toUnsigned(value) { 42677 return value >>> 0; 42678 }; 42679 42680 var bin = { 42681 toUnsigned: toUnsigned 42682 }; 42683 var toUnsigned$1 = bin.toUnsigned; 42684 42685 var _findBox, parseType, timescale, startTime, getVideoTrackIds; // Find the data for a box specified by its path 42686 42687 42688 _findBox = function findBox(data, path) { 42689 var results = [], 42690 i, 42691 size, 42692 type, 42693 end, 42694 subresults; 42695 42696 if (!path.length) { 42697 // short-circuit the search for empty paths 42698 return null; 42699 } 42700 42701 for (i = 0; i < data.byteLength;) { 42702 size = toUnsigned$1(data[i] << 24 | data[i + 1] << 16 | data[i + 2] << 8 | data[i + 3]); 42703 type = parseType(data.subarray(i + 4, i + 8)); 42704 end = size > 1 ? i + size : data.byteLength; 42705 42706 if (type === path[0]) { 42707 if (path.length === 1) { 42708 // this is the end of the path and we've found the box we were 42709 // looking for 42710 results.push(data.subarray(i + 8, end)); 42711 } else { 42712 // recursively search for the next box along the path 42713 subresults = _findBox(data.subarray(i + 8, end), path.slice(1)); 42714 42715 if (subresults.length) { 42716 results = results.concat(subresults); 42717 } 42718 } 42719 } 42720 42721 i = end; 42722 } // we've finished searching all of data 42723 42724 42725 return results; 42726 }; 42727 /** 42728 * Returns the string representation of an ASCII encoded four byte buffer. 42729 * @param buffer {Uint8Array} a four-byte buffer to translate 42730 * @return {string} the corresponding string 42731 */ 42732 42733 42734 parseType = function parseType(buffer) { 42735 var result = ''; 42736 result += String.fromCharCode(buffer[0]); 42737 result += String.fromCharCode(buffer[1]); 42738 result += String.fromCharCode(buffer[2]); 42739 result += String.fromCharCode(buffer[3]); 42740 return result; 42741 }; 42742 /** 42743 * Parses an MP4 initialization segment and extracts the timescale 42744 * values for any declared tracks. Timescale values indicate the 42745 * number of clock ticks per second to assume for time-based values 42746 * elsewhere in the MP4. 42747 * 42748 * To determine the start time of an MP4, you need two pieces of 42749 * information: the timescale unit and the earliest base media decode 42750 * time. Multiple timescales can be specified within an MP4 but the 42751 * base media decode time is always expressed in the timescale from 42752 * the media header box for the track: 42753 * ``` 42754 * moov > trak > mdia > mdhd.timescale 42755 * ``` 42756 * @param init {Uint8Array} the bytes of the init segment 42757 * @return {object} a hash of track ids to timescale values or null if 42758 * the init segment is malformed. 42759 */ 42760 42761 42762 timescale = function timescale(init) { 42763 var result = {}, 42764 traks = _findBox(init, ['moov', 'trak']); // mdhd timescale 42765 42766 42767 return traks.reduce(function (result, trak) { 42768 var tkhd, version, index, id, mdhd; 42769 tkhd = _findBox(trak, ['tkhd'])[0]; 42770 42771 if (!tkhd) { 42772 return null; 42773 } 42774 42775 version = tkhd[0]; 42776 index = version === 0 ? 12 : 20; 42777 id = toUnsigned$1(tkhd[index] << 24 | tkhd[index + 1] << 16 | tkhd[index + 2] << 8 | tkhd[index + 3]); 42778 mdhd = _findBox(trak, ['mdia', 'mdhd'])[0]; 42779 42780 if (!mdhd) { 42781 return null; 42782 } 42783 42784 version = mdhd[0]; 42785 index = version === 0 ? 12 : 20; 42786 result[id] = toUnsigned$1(mdhd[index] << 24 | mdhd[index + 1] << 16 | mdhd[index + 2] << 8 | mdhd[index + 3]); 42787 return result; 42788 }, result); 42789 }; 42790 /** 42791 * Determine the base media decode start time, in seconds, for an MP4 42792 * fragment. If multiple fragments are specified, the earliest time is 42793 * returned. 42794 * 42795 * The base media decode time can be parsed from track fragment 42796 * metadata: 42797 * ``` 42798 * moof > traf > tfdt.baseMediaDecodeTime 42799 * ``` 42800 * It requires the timescale value from the mdhd to interpret. 42801 * 42802 * @param timescale {object} a hash of track ids to timescale values. 42803 * @return {number} the earliest base media decode start time for the 42804 * fragment, in seconds 42805 */ 42806 42807 42808 startTime = function startTime(timescale, fragment) { 42809 var trafs, baseTimes, result; // we need info from two childrend of each track fragment box 42810 42811 trafs = _findBox(fragment, ['moof', 'traf']); // determine the start times for each track 42812 42813 baseTimes = [].concat.apply([], trafs.map(function (traf) { 42814 return _findBox(traf, ['tfhd']).map(function (tfhd) { 42815 var id, scale, baseTime; // get the track id from the tfhd 42816 42817 id = toUnsigned$1(tfhd[4] << 24 | tfhd[5] << 16 | tfhd[6] << 8 | tfhd[7]); // assume a 90kHz clock if no timescale was specified 42818 42819 scale = timescale[id] || 90e3; // get the base media decode time from the tfdt 42820 42821 baseTime = _findBox(traf, ['tfdt']).map(function (tfdt) { 42822 var version, result; 42823 version = tfdt[0]; 42824 result = toUnsigned$1(tfdt[4] << 24 | tfdt[5] << 16 | tfdt[6] << 8 | tfdt[7]); 42825 42826 if (version === 1) { 42827 result *= Math.pow(2, 32); 42828 result += toUnsigned$1(tfdt[8] << 24 | tfdt[9] << 16 | tfdt[10] << 8 | tfdt[11]); 42829 } 42830 42831 return result; 42832 })[0]; 42833 baseTime = baseTime || Infinity; // convert base time to seconds 42834 42835 return baseTime / scale; 42836 }); 42837 })); // return the minimum 42838 42839 result = Math.min.apply(null, baseTimes); 42840 return isFinite(result) ? result : 0; 42841 }; 42842 /** 42843 * Find the trackIds of the video tracks in this source. 42844 * Found by parsing the Handler Reference and Track Header Boxes: 42845 * moov > trak > mdia > hdlr 42846 * moov > trak > tkhd 42847 * 42848 * @param {Uint8Array} init - The bytes of the init segment for this source 42849 * @return {Number[]} A list of trackIds 42850 * 42851 * @see ISO-BMFF-12/2015, Section 8.4.3 42852 **/ 42853 42854 42855 getVideoTrackIds = function getVideoTrackIds(init) { 42856 var traks = _findBox(init, ['moov', 'trak']); 42857 42858 var videoTrackIds = []; 42859 traks.forEach(function (trak) { 42860 var hdlrs = _findBox(trak, ['mdia', 'hdlr']); 42861 42862 var tkhds = _findBox(trak, ['tkhd']); 42863 42864 hdlrs.forEach(function (hdlr, index) { 42865 var handlerType = parseType(hdlr.subarray(8, 12)); 42866 var tkhd = tkhds[index]; 42867 var view; 42868 var version; 42869 var trackId; 42870 42871 if (handlerType === 'vide') { 42872 view = new DataView(tkhd.buffer, tkhd.byteOffset, tkhd.byteLength); 42873 version = view.getUint8(0); 42874 trackId = version === 0 ? view.getUint32(12) : view.getUint32(20); 42875 videoTrackIds.push(trackId); 42876 } 42877 }); 42878 }); 42879 return videoTrackIds; 42880 }; 42881 42882 var probe$$1 = { 42883 findBox: _findBox, 42884 parseType: parseType, 42885 timescale: timescale, 42886 startTime: startTime, 42887 videoTrackIds: getVideoTrackIds 42888 }; 42889 /** 42890 * mux.js 42891 * 42892 * Copyright (c) 2014 Brightcove 42893 * All rights reserved. 42894 * 42895 * A lightweight readable stream implemention that handles event dispatching. 42896 * Objects that inherit from streams should call init in their constructors. 42897 */ 42898 42899 var Stream = function Stream() { 42900 this.init = function () { 42901 var listeners = {}; 42902 /** 42903 * Add a listener for a specified event type. 42904 * @param type {string} the event name 42905 * @param listener {function} the callback to be invoked when an event of 42906 * the specified type occurs 42907 */ 42908 42909 this.on = function (type, listener) { 42910 if (!listeners[type]) { 42911 listeners[type] = []; 42912 } 42913 42914 listeners[type] = listeners[type].concat(listener); 42915 }; 42916 /** 42917 * Remove a listener for a specified event type. 42918 * @param type {string} the event name 42919 * @param listener {function} a function previously registered for this 42920 * type of event through `on` 42921 */ 42922 42923 42924 this.off = function (type, listener) { 42925 var index; 42926 42927 if (!listeners[type]) { 42928 return false; 42929 } 42930 42931 index = listeners[type].indexOf(listener); 42932 listeners[type] = listeners[type].slice(); 42933 listeners[type].splice(index, 1); 42934 return index > -1; 42935 }; 42936 /** 42937 * Trigger an event of the specified type on this stream. Any additional 42938 * arguments to this function are passed as parameters to event listeners. 42939 * @param type {string} the event name 42940 */ 42941 42942 42943 this.trigger = function (type) { 42944 var callbacks, i, length, args; 42945 callbacks = listeners[type]; 42946 42947 if (!callbacks) { 42948 return; 42949 } // Slicing the arguments on every invocation of this method 42950 // can add a significant amount of overhead. Avoid the 42951 // intermediate object creation for the common case of a 42952 // single callback argument 42953 42954 42955 if (arguments.length === 2) { 42956 length = callbacks.length; 42957 42958 for (i = 0; i < length; ++i) { 42959 callbacks[i].call(this, arguments[1]); 42960 } 42961 } else { 42962 args = []; 42963 i = arguments.length; 42964 42965 for (i = 1; i < arguments.length; ++i) { 42966 args.push(arguments[i]); 42967 } 42968 42969 length = callbacks.length; 42970 42971 for (i = 0; i < length; ++i) { 42972 callbacks[i].apply(this, args); 42973 } 42974 } 42975 }; 42976 /** 42977 * Destroys the stream and cleans up. 42978 */ 42979 42980 42981 this.dispose = function () { 42982 listeners = {}; 42983 }; 42984 }; 42985 }; 42986 /** 42987 * Forwards all `data` events on this stream to the destination stream. The 42988 * destination stream should provide a method `push` to receive the data 42989 * events as they arrive. 42990 * @param destination {stream} the stream that will receive all `data` events 42991 * @param autoFlush {boolean} if false, we will not call `flush` on the destination 42992 * when the current stream emits a 'done' event 42993 * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options 42994 */ 42995 42996 42997 Stream.prototype.pipe = function (destination) { 42998 this.on('data', function (data) { 42999 destination.push(data); 43000 }); 43001 this.on('done', function (flushSource) { 43002 destination.flush(flushSource); 43003 }); 43004 return destination; 43005 }; // Default stream functions that are expected to be overridden to perform 43006 // actual work. These are provided by the prototype as a sort of no-op 43007 // implementation so that we don't have to check for their existence in the 43008 // `pipe` function above. 43009 43010 43011 Stream.prototype.push = function (data) { 43012 this.trigger('data', data); 43013 }; 43014 43015 Stream.prototype.flush = function (flushSource) { 43016 this.trigger('done', flushSource); 43017 }; 43018 43019 var stream = Stream; // Convert an array of nal units into an array of frames with each frame being 43020 // composed of the nal units that make up that frame 43021 // Also keep track of cummulative data about the frame from the nal units such 43022 // as the frame duration, starting pts, etc. 43023 43024 var groupNalsIntoFrames = function groupNalsIntoFrames(nalUnits) { 43025 var i, 43026 currentNal, 43027 currentFrame = [], 43028 frames = []; 43029 currentFrame.byteLength = 0; 43030 43031 for (i = 0; i < nalUnits.length; i++) { 43032 currentNal = nalUnits[i]; // Split on 'aud'-type nal units 43033 43034 if (currentNal.nalUnitType === 'access_unit_delimiter_rbsp') { 43035 // Since the very first nal unit is expected to be an AUD 43036 // only push to the frames array when currentFrame is not empty 43037 if (currentFrame.length) { 43038 currentFrame.duration = currentNal.dts - currentFrame.dts; 43039 frames.push(currentFrame); 43040 } 43041 43042 currentFrame = [currentNal]; 43043 currentFrame.byteLength = currentNal.data.byteLength; 43044 currentFrame.pts = currentNal.pts; 43045 currentFrame.dts = currentNal.dts; 43046 } else { 43047 // Specifically flag key frames for ease of use later 43048 if (currentNal.nalUnitType === 'slice_layer_without_partitioning_rbsp_idr') { 43049 currentFrame.keyFrame = true; 43050 } 43051 43052 currentFrame.duration = currentNal.dts - currentFrame.dts; 43053 currentFrame.byteLength += currentNal.data.byteLength; 43054 currentFrame.push(currentNal); 43055 } 43056 } // For the last frame, use the duration of the previous frame if we 43057 // have nothing better to go on 43058 43059 43060 if (frames.length && (!currentFrame.duration || currentFrame.duration <= 0)) { 43061 currentFrame.duration = frames[frames.length - 1].duration; 43062 } // Push the final frame 43063 43064 43065 frames.push(currentFrame); 43066 return frames; 43067 }; // Convert an array of frames into an array of Gop with each Gop being composed 43068 // of the frames that make up that Gop 43069 // Also keep track of cummulative data about the Gop from the frames such as the 43070 // Gop duration, starting pts, etc. 43071 43072 43073 var groupFramesIntoGops = function groupFramesIntoGops(frames) { 43074 var i, 43075 currentFrame, 43076 currentGop = [], 43077 gops = []; // We must pre-set some of the values on the Gop since we 43078 // keep running totals of these values 43079 43080 currentGop.byteLength = 0; 43081 currentGop.nalCount = 0; 43082 currentGop.duration = 0; 43083 currentGop.pts = frames[0].pts; 43084 currentGop.dts = frames[0].dts; // store some metadata about all the Gops 43085 43086 gops.byteLength = 0; 43087 gops.nalCount = 0; 43088 gops.duration = 0; 43089 gops.pts = frames[0].pts; 43090 gops.dts = frames[0].dts; 43091 43092 for (i = 0; i < frames.length; i++) { 43093 currentFrame = frames[i]; 43094 43095 if (currentFrame.keyFrame) { 43096 // Since the very first frame is expected to be an keyframe 43097 // only push to the gops array when currentGop is not empty 43098 if (currentGop.length) { 43099 gops.push(currentGop); 43100 gops.byteLength += currentGop.byteLength; 43101 gops.nalCount += currentGop.nalCount; 43102 gops.duration += currentGop.duration; 43103 } 43104 43105 currentGop = [currentFrame]; 43106 currentGop.nalCount = currentFrame.length; 43107 currentGop.byteLength = currentFrame.byteLength; 43108 currentGop.pts = currentFrame.pts; 43109 currentGop.dts = currentFrame.dts; 43110 currentGop.duration = currentFrame.duration; 43111 } else { 43112 currentGop.duration += currentFrame.duration; 43113 currentGop.nalCount += currentFrame.length; 43114 currentGop.byteLength += currentFrame.byteLength; 43115 currentGop.push(currentFrame); 43116 } 43117 } 43118 43119 if (gops.length && currentGop.duration <= 0) { 43120 currentGop.duration = gops[gops.length - 1].duration; 43121 } 43122 43123 gops.byteLength += currentGop.byteLength; 43124 gops.nalCount += currentGop.nalCount; 43125 gops.duration += currentGop.duration; // push the final Gop 43126 43127 gops.push(currentGop); 43128 return gops; 43129 }; 43130 /* 43131 * Search for the first keyframe in the GOPs and throw away all frames 43132 * until that keyframe. Then extend the duration of the pulled keyframe 43133 * and pull the PTS and DTS of the keyframe so that it covers the time 43134 * range of the frames that were disposed. 43135 * 43136 * @param {Array} gops video GOPs 43137 * @returns {Array} modified video GOPs 43138 */ 43139 43140 43141 var extendFirstKeyFrame = function extendFirstKeyFrame(gops) { 43142 var currentGop; 43143 43144 if (!gops[0][0].keyFrame && gops.length > 1) { 43145 // Remove the first GOP 43146 currentGop = gops.shift(); 43147 gops.byteLength -= currentGop.byteLength; 43148 gops.nalCount -= currentGop.nalCount; // Extend the first frame of what is now the 43149 // first gop to cover the time period of the 43150 // frames we just removed 43151 43152 gops[0][0].dts = currentGop.dts; 43153 gops[0][0].pts = currentGop.pts; 43154 gops[0][0].duration += currentGop.duration; 43155 } 43156 43157 return gops; 43158 }; 43159 /** 43160 * Default sample object 43161 * see ISO/IEC 14496-12:2012, section 8.6.4.3 43162 */ 43163 43164 43165 var createDefaultSample = function createDefaultSample() { 43166 return { 43167 size: 0, 43168 flags: { 43169 isLeading: 0, 43170 dependsOn: 1, 43171 isDependedOn: 0, 43172 hasRedundancy: 0, 43173 degradationPriority: 0, 43174 isNonSyncSample: 1 43175 } 43176 }; 43177 }; 43178 /* 43179 * Collates information from a video frame into an object for eventual 43180 * entry into an MP4 sample table. 43181 * 43182 * @param {Object} frame the video frame 43183 * @param {Number} dataOffset the byte offset to position the sample 43184 * @return {Object} object containing sample table info for a frame 43185 */ 43186 43187 43188 var sampleForFrame = function sampleForFrame(frame, dataOffset) { 43189 var sample = createDefaultSample(); 43190 sample.dataOffset = dataOffset; 43191 sample.compositionTimeOffset = frame.pts - frame.dts; 43192 sample.duration = frame.duration; 43193 sample.size = 4 * frame.length; // Space for nal unit size 43194 43195 sample.size += frame.byteLength; 43196 43197 if (frame.keyFrame) { 43198 sample.flags.dependsOn = 2; 43199 sample.flags.isNonSyncSample = 0; 43200 } 43201 43202 return sample; 43203 }; // generate the track's sample table from an array of gops 43204 43205 43206 var generateSampleTable = function generateSampleTable(gops, baseDataOffset) { 43207 var h, 43208 i, 43209 sample, 43210 currentGop, 43211 currentFrame, 43212 dataOffset = baseDataOffset || 0, 43213 samples = []; 43214 43215 for (h = 0; h < gops.length; h++) { 43216 currentGop = gops[h]; 43217 43218 for (i = 0; i < currentGop.length; i++) { 43219 currentFrame = currentGop[i]; 43220 sample = sampleForFrame(currentFrame, dataOffset); 43221 dataOffset += sample.size; 43222 samples.push(sample); 43223 } 43224 } 43225 43226 return samples; 43227 }; // generate the track's raw mdat data from an array of gops 43228 43229 43230 var concatenateNalData = function concatenateNalData(gops) { 43231 var h, 43232 i, 43233 j, 43234 currentGop, 43235 currentFrame, 43236 currentNal, 43237 dataOffset = 0, 43238 nalsByteLength = gops.byteLength, 43239 numberOfNals = gops.nalCount, 43240 totalByteLength = nalsByteLength + 4 * numberOfNals, 43241 data = new Uint8Array(totalByteLength), 43242 view = new DataView(data.buffer); // For each Gop.. 43243 43244 for (h = 0; h < gops.length; h++) { 43245 currentGop = gops[h]; // For each Frame.. 43246 43247 for (i = 0; i < currentGop.length; i++) { 43248 currentFrame = currentGop[i]; // For each NAL.. 43249 43250 for (j = 0; j < currentFrame.length; j++) { 43251 currentNal = currentFrame[j]; 43252 view.setUint32(dataOffset, currentNal.data.byteLength); 43253 dataOffset += 4; 43254 data.set(currentNal.data, dataOffset); 43255 dataOffset += currentNal.data.byteLength; 43256 } 43257 } 43258 } 43259 43260 return data; 43261 }; 43262 43263 var frameUtils = { 43264 groupNalsIntoFrames: groupNalsIntoFrames, 43265 groupFramesIntoGops: groupFramesIntoGops, 43266 extendFirstKeyFrame: extendFirstKeyFrame, 43267 generateSampleTable: generateSampleTable, 43268 concatenateNalData: concatenateNalData 43269 }; 43270 var highPrefix = [33, 16, 5, 32, 164, 27]; 43271 var lowPrefix = [33, 65, 108, 84, 1, 2, 4, 8, 168, 2, 4, 8, 17, 191, 252]; 43272 43273 var zeroFill = function zeroFill(count) { 43274 var a = []; 43275 43276 while (count--) { 43277 a.push(0); 43278 } 43279 43280 return a; 43281 }; 43282 43283 var makeTable = function makeTable(metaTable) { 43284 return Object.keys(metaTable).reduce(function (obj, key) { 43285 obj[key] = new Uint8Array(metaTable[key].reduce(function (arr, part) { 43286 return arr.concat(part); 43287 }, [])); 43288 return obj; 43289 }, {}); 43290 }; // Frames-of-silence to use for filling in missing AAC frames 43291 43292 43293 var coneOfSilence = { 43294 96000: [highPrefix, [227, 64], zeroFill(154), [56]], 43295 88200: [highPrefix, [231], zeroFill(170), [56]], 43296 64000: [highPrefix, [248, 192], zeroFill(240), [56]], 43297 48000: [highPrefix, [255, 192], zeroFill(268), [55, 148, 128], zeroFill(54), [112]], 43298 44100: [highPrefix, [255, 192], zeroFill(268), [55, 163, 128], zeroFill(84), [112]], 43299 32000: [highPrefix, [255, 192], zeroFill(268), [55, 234], zeroFill(226), [112]], 43300 24000: [highPrefix, [255, 192], zeroFill(268), [55, 255, 128], zeroFill(268), [111, 112], zeroFill(126), [224]], 43301 16000: [highPrefix, [255, 192], zeroFill(268), [55, 255, 128], zeroFill(268), [111, 255], zeroFill(269), [223, 108], zeroFill(195), [1, 192]], 43302 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]], 43303 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]], 43304 8000: [lowPrefix, zeroFill(268), [3, 121, 16], zeroFill(47), [7]] 43305 }; 43306 var silence = makeTable(coneOfSilence); 43307 var ONE_SECOND_IN_TS = 90000, 43308 // 90kHz clock 43309 secondsToVideoTs, 43310 secondsToAudioTs, 43311 videoTsToSeconds, 43312 audioTsToSeconds, 43313 audioTsToVideoTs, 43314 videoTsToAudioTs; 43315 43316 secondsToVideoTs = function secondsToVideoTs(seconds) { 43317 return seconds * ONE_SECOND_IN_TS; 43318 }; 43319 43320 secondsToAudioTs = function secondsToAudioTs(seconds, sampleRate) { 43321 return seconds * sampleRate; 43322 }; 43323 43324 videoTsToSeconds = function videoTsToSeconds(timestamp) { 43325 return timestamp / ONE_SECOND_IN_TS; 43326 }; 43327 43328 audioTsToSeconds = function audioTsToSeconds(timestamp, sampleRate) { 43329 return timestamp / sampleRate; 43330 }; 43331 43332 audioTsToVideoTs = function audioTsToVideoTs(timestamp, sampleRate) { 43333 return secondsToVideoTs(audioTsToSeconds(timestamp, sampleRate)); 43334 }; 43335 43336 videoTsToAudioTs = function videoTsToAudioTs(timestamp, sampleRate) { 43337 return secondsToAudioTs(videoTsToSeconds(timestamp), sampleRate); 43338 }; 43339 43340 var clock = { 43341 secondsToVideoTs: secondsToVideoTs, 43342 secondsToAudioTs: secondsToAudioTs, 43343 videoTsToSeconds: videoTsToSeconds, 43344 audioTsToSeconds: audioTsToSeconds, 43345 audioTsToVideoTs: audioTsToVideoTs, 43346 videoTsToAudioTs: videoTsToAudioTs 43347 }; 43348 var ONE_SECOND_IN_TS$1 = 90000; // 90kHz clock 43349 43350 /** 43351 * Sum the `byteLength` properties of the data in each AAC frame 43352 */ 43353 43354 var sumFrameByteLengths = function sumFrameByteLengths(array) { 43355 var i, 43356 currentObj, 43357 sum = 0; // sum the byteLength's all each nal unit in the frame 43358 43359 for (i = 0; i < array.length; i++) { 43360 currentObj = array[i]; 43361 sum += currentObj.data.byteLength; 43362 } 43363 43364 return sum; 43365 }; // Possibly pad (prefix) the audio track with silence if appending this track 43366 // would lead to the introduction of a gap in the audio buffer 43367 43368 43369 var prefixWithSilence = function prefixWithSilence(track, frames, audioAppendStartTs, videoBaseMediaDecodeTime) { 43370 var baseMediaDecodeTimeTs, 43371 frameDuration = 0, 43372 audioGapDuration = 0, 43373 audioFillFrameCount = 0, 43374 audioFillDuration = 0, 43375 silentFrame, 43376 i; 43377 43378 if (!frames.length) { 43379 return; 43380 } 43381 43382 baseMediaDecodeTimeTs = clock.audioTsToVideoTs(track.baseMediaDecodeTime, track.samplerate); // determine frame clock duration based on sample rate, round up to avoid overfills 43383 43384 frameDuration = Math.ceil(ONE_SECOND_IN_TS$1 / (track.samplerate / 1024)); 43385 43386 if (audioAppendStartTs && videoBaseMediaDecodeTime) { 43387 // insert the shortest possible amount (audio gap or audio to video gap) 43388 audioGapDuration = baseMediaDecodeTimeTs - Math.max(audioAppendStartTs, videoBaseMediaDecodeTime); // number of full frames in the audio gap 43389 43390 audioFillFrameCount = Math.floor(audioGapDuration / frameDuration); 43391 audioFillDuration = audioFillFrameCount * frameDuration; 43392 } // don't attempt to fill gaps smaller than a single frame or larger 43393 // than a half second 43394 43395 43396 if (audioFillFrameCount < 1 || audioFillDuration > ONE_SECOND_IN_TS$1 / 2) { 43397 return; 43398 } 43399 43400 silentFrame = silence[track.samplerate]; 43401 43402 if (!silentFrame) { 43403 // we don't have a silent frame pregenerated for the sample rate, so use a frame 43404 // from the content instead 43405 silentFrame = frames[0].data; 43406 } 43407 43408 for (i = 0; i < audioFillFrameCount; i++) { 43409 frames.splice(i, 0, { 43410 data: silentFrame 43411 }); 43412 } 43413 43414 track.baseMediaDecodeTime -= Math.floor(clock.videoTsToAudioTs(audioFillDuration, track.samplerate)); 43415 }; // If the audio segment extends before the earliest allowed dts 43416 // value, remove AAC frames until starts at or after the earliest 43417 // allowed DTS so that we don't end up with a negative baseMedia- 43418 // DecodeTime for the audio track 43419 43420 43421 var trimAdtsFramesByEarliestDts = function trimAdtsFramesByEarliestDts(adtsFrames, track, earliestAllowedDts) { 43422 if (track.minSegmentDts >= earliestAllowedDts) { 43423 return adtsFrames; 43424 } // We will need to recalculate the earliest segment Dts 43425 43426 43427 track.minSegmentDts = Infinity; 43428 return adtsFrames.filter(function (currentFrame) { 43429 // If this is an allowed frame, keep it and record it's Dts 43430 if (currentFrame.dts >= earliestAllowedDts) { 43431 track.minSegmentDts = Math.min(track.minSegmentDts, currentFrame.dts); 43432 track.minSegmentPts = track.minSegmentDts; 43433 return true; 43434 } // Otherwise, discard it 43435 43436 43437 return false; 43438 }); 43439 }; // generate the track's raw mdat data from an array of frames 43440 43441 43442 var generateSampleTable$1 = function generateSampleTable(frames) { 43443 var i, 43444 currentFrame, 43445 samples = []; 43446 43447 for (i = 0; i < frames.length; i++) { 43448 currentFrame = frames[i]; 43449 samples.push({ 43450 size: currentFrame.data.byteLength, 43451 duration: 1024 // For AAC audio, all samples contain 1024 samples 43452 43453 }); 43454 } 43455 43456 return samples; 43457 }; // generate the track's sample table from an array of frames 43458 43459 43460 var concatenateFrameData = function concatenateFrameData(frames) { 43461 var i, 43462 currentFrame, 43463 dataOffset = 0, 43464 data = new Uint8Array(sumFrameByteLengths(frames)); 43465 43466 for (i = 0; i < frames.length; i++) { 43467 currentFrame = frames[i]; 43468 data.set(currentFrame.data, dataOffset); 43469 dataOffset += currentFrame.data.byteLength; 43470 } 43471 43472 return data; 43473 }; 43474 43475 var audioFrameUtils = { 43476 prefixWithSilence: prefixWithSilence, 43477 trimAdtsFramesByEarliestDts: trimAdtsFramesByEarliestDts, 43478 generateSampleTable: generateSampleTable$1, 43479 concatenateFrameData: concatenateFrameData 43480 }; 43481 var ONE_SECOND_IN_TS$2 = 90000; // 90kHz clock 43482 43483 /** 43484 * Store information about the start and end of the track and the 43485 * duration for each frame/sample we process in order to calculate 43486 * the baseMediaDecodeTime 43487 */ 43488 43489 var collectDtsInfo = function collectDtsInfo(track, data) { 43490 if (typeof data.pts === 'number') { 43491 if (track.timelineStartInfo.pts === undefined) { 43492 track.timelineStartInfo.pts = data.pts; 43493 } 43494 43495 if (track.minSegmentPts === undefined) { 43496 track.minSegmentPts = data.pts; 43497 } else { 43498 track.minSegmentPts = Math.min(track.minSegmentPts, data.pts); 43499 } 43500 43501 if (track.maxSegmentPts === undefined) { 43502 track.maxSegmentPts = data.pts; 43503 } else { 43504 track.maxSegmentPts = Math.max(track.maxSegmentPts, data.pts); 43505 } 43506 } 43507 43508 if (typeof data.dts === 'number') { 43509 if (track.timelineStartInfo.dts === undefined) { 43510 track.timelineStartInfo.dts = data.dts; 43511 } 43512 43513 if (track.minSegmentDts === undefined) { 43514 track.minSegmentDts = data.dts; 43515 } else { 43516 track.minSegmentDts = Math.min(track.minSegmentDts, data.dts); 43517 } 43518 43519 if (track.maxSegmentDts === undefined) { 43520 track.maxSegmentDts = data.dts; 43521 } else { 43522 track.maxSegmentDts = Math.max(track.maxSegmentDts, data.dts); 43523 } 43524 } 43525 }; 43526 /** 43527 * Clear values used to calculate the baseMediaDecodeTime between 43528 * tracks 43529 */ 43530 43531 43532 var clearDtsInfo = function clearDtsInfo(track) { 43533 delete track.minSegmentDts; 43534 delete track.maxSegmentDts; 43535 delete track.minSegmentPts; 43536 delete track.maxSegmentPts; 43537 }; 43538 /** 43539 * Calculate the track's baseMediaDecodeTime based on the earliest 43540 * DTS the transmuxer has ever seen and the minimum DTS for the 43541 * current track 43542 * @param track {object} track metadata configuration 43543 * @param keepOriginalTimestamps {boolean} If true, keep the timestamps 43544 * in the source; false to adjust the first segment to start at 0. 43545 */ 43546 43547 43548 var calculateTrackBaseMediaDecodeTime = function calculateTrackBaseMediaDecodeTime(track, keepOriginalTimestamps) { 43549 var baseMediaDecodeTime, 43550 scale, 43551 minSegmentDts = track.minSegmentDts; // Optionally adjust the time so the first segment starts at zero. 43552 43553 if (!keepOriginalTimestamps) { 43554 minSegmentDts -= track.timelineStartInfo.dts; 43555 } // track.timelineStartInfo.baseMediaDecodeTime is the location, in time, where 43556 // we want the start of the first segment to be placed 43557 43558 43559 baseMediaDecodeTime = track.timelineStartInfo.baseMediaDecodeTime; // Add to that the distance this segment is from the very first 43560 43561 baseMediaDecodeTime += minSegmentDts; // baseMediaDecodeTime must not become negative 43562 43563 baseMediaDecodeTime = Math.max(0, baseMediaDecodeTime); 43564 43565 if (track.type === 'audio') { 43566 // Audio has a different clock equal to the sampling_rate so we need to 43567 // scale the PTS values into the clock rate of the track 43568 scale = track.samplerate / ONE_SECOND_IN_TS$2; 43569 baseMediaDecodeTime *= scale; 43570 baseMediaDecodeTime = Math.floor(baseMediaDecodeTime); 43571 } 43572 43573 return baseMediaDecodeTime; 43574 }; 43575 43576 var trackDecodeInfo = { 43577 clearDtsInfo: clearDtsInfo, 43578 calculateTrackBaseMediaDecodeTime: calculateTrackBaseMediaDecodeTime, 43579 collectDtsInfo: collectDtsInfo 43580 }; 43581 /** 43582 * mux.js 43583 * 43584 * Copyright (c) 2015 Brightcove 43585 * All rights reserved. 43586 * 43587 * Reads in-band caption information from a video elementary 43588 * stream. Captions must follow the CEA-708 standard for injection 43589 * into an MPEG-2 transport streams. 43590 * @see https://en.wikipedia.org/wiki/CEA-708 43591 * @see https://www.gpo.gov/fdsys/pkg/CFR-2007-title47-vol1/pdf/CFR-2007-title47-vol1-sec15-119.pdf 43592 */ 43593 // Supplemental enhancement information (SEI) NAL units have a 43594 // payload type field to indicate how they are to be 43595 // interpreted. CEAS-708 caption content is always transmitted with 43596 // payload type 0x04. 43597 43598 var USER_DATA_REGISTERED_ITU_T_T35 = 4, 43599 RBSP_TRAILING_BITS = 128; 43600 /** 43601 * Parse a supplemental enhancement information (SEI) NAL unit. 43602 * Stops parsing once a message of type ITU T T35 has been found. 43603 * 43604 * @param bytes {Uint8Array} the bytes of a SEI NAL unit 43605 * @return {object} the parsed SEI payload 43606 * @see Rec. ITU-T H.264, 7.3.2.3.1 43607 */ 43608 43609 var parseSei = function parseSei(bytes) { 43610 var i = 0, 43611 result = { 43612 payloadType: -1, 43613 payloadSize: 0 43614 }, 43615 payloadType = 0, 43616 payloadSize = 0; // go through the sei_rbsp parsing each each individual sei_message 43617 43618 while (i < bytes.byteLength) { 43619 // stop once we have hit the end of the sei_rbsp 43620 if (bytes[i] === RBSP_TRAILING_BITS) { 43621 break; 43622 } // Parse payload type 43623 43624 43625 while (bytes[i] === 0xFF) { 43626 payloadType += 255; 43627 i++; 43628 } 43629 43630 payloadType += bytes[i++]; // Parse payload size 43631 43632 while (bytes[i] === 0xFF) { 43633 payloadSize += 255; 43634 i++; 43635 } 43636 43637 payloadSize += bytes[i++]; // this sei_message is a 608/708 caption so save it and break 43638 // there can only ever be one caption message in a frame's sei 43639 43640 if (!result.payload && payloadType === USER_DATA_REGISTERED_ITU_T_T35) { 43641 result.payloadType = payloadType; 43642 result.payloadSize = payloadSize; 43643 result.payload = bytes.subarray(i, i + payloadSize); 43644 break; 43645 } // skip the payload and parse the next message 43646 43647 43648 i += payloadSize; 43649 payloadType = 0; 43650 payloadSize = 0; 43651 } 43652 43653 return result; 43654 }; // see ANSI/SCTE 128-1 (2013), section 8.1 43655 43656 43657 var parseUserData = function parseUserData(sei) { 43658 // itu_t_t35_contry_code must be 181 (United States) for 43659 // captions 43660 if (sei.payload[0] !== 181) { 43661 return null; 43662 } // itu_t_t35_provider_code should be 49 (ATSC) for captions 43663 43664 43665 if ((sei.payload[1] << 8 | sei.payload[2]) !== 49) { 43666 return null; 43667 } // the user_identifier should be "GA94" to indicate ATSC1 data 43668 43669 43670 if (String.fromCharCode(sei.payload[3], sei.payload[4], sei.payload[5], sei.payload[6]) !== 'GA94') { 43671 return null; 43672 } // finally, user_data_type_code should be 0x03 for caption data 43673 43674 43675 if (sei.payload[7] !== 0x03) { 43676 return null; 43677 } // return the user_data_type_structure and strip the trailing 43678 // marker bits 43679 43680 43681 return sei.payload.subarray(8, sei.payload.length - 1); 43682 }; // see CEA-708-D, section 4.4 43683 43684 43685 var parseCaptionPackets = function parseCaptionPackets(pts, userData) { 43686 var results = [], 43687 i, 43688 count, 43689 offset, 43690 data; // if this is just filler, return immediately 43691 43692 if (!(userData[0] & 0x40)) { 43693 return results; 43694 } // parse out the cc_data_1 and cc_data_2 fields 43695 43696 43697 count = userData[0] & 0x1f; 43698 43699 for (i = 0; i < count; i++) { 43700 offset = i * 3; 43701 data = { 43702 type: userData[offset + 2] & 0x03, 43703 pts: pts 43704 }; // capture cc data when cc_valid is 1 43705 43706 if (userData[offset + 2] & 0x04) { 43707 data.ccData = userData[offset + 3] << 8 | userData[offset + 4]; 43708 results.push(data); 43709 } 43710 } 43711 43712 return results; 43713 }; 43714 43715 var discardEmulationPreventionBytes = function discardEmulationPreventionBytes(data) { 43716 var length = data.byteLength, 43717 emulationPreventionBytesPositions = [], 43718 i = 1, 43719 newLength, 43720 newData; // Find all `Emulation Prevention Bytes` 43721 43722 while (i < length - 2) { 43723 if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0x03) { 43724 emulationPreventionBytesPositions.push(i + 2); 43725 i += 2; 43726 } else { 43727 i++; 43728 } 43729 } // If no Emulation Prevention Bytes were found just return the original 43730 // array 43731 43732 43733 if (emulationPreventionBytesPositions.length === 0) { 43734 return data; 43735 } // Create a new array to hold the NAL unit data 43736 43737 43738 newLength = length - emulationPreventionBytesPositions.length; 43739 newData = new Uint8Array(newLength); 43740 var sourceIndex = 0; 43741 43742 for (i = 0; i < newLength; sourceIndex++, i++) { 43743 if (sourceIndex === emulationPreventionBytesPositions[0]) { 43744 // Skip this byte 43745 sourceIndex++; // Remove this position index 43746 43747 emulationPreventionBytesPositions.shift(); 43748 } 43749 43750 newData[i] = data[sourceIndex]; 43751 } 43752 43753 return newData; 43754 }; // exports 43755 43756 43757 var captionPacketParser = { 43758 parseSei: parseSei, 43759 parseUserData: parseUserData, 43760 parseCaptionPackets: parseCaptionPackets, 43761 discardEmulationPreventionBytes: discardEmulationPreventionBytes, 43762 USER_DATA_REGISTERED_ITU_T_T35: USER_DATA_REGISTERED_ITU_T_T35 43763 }; // ----------------- 43764 // Link To Transport 43765 // ----------------- 43766 43767 var CaptionStream = function CaptionStream() { 43768 CaptionStream.prototype.init.call(this); 43769 this.captionPackets_ = []; 43770 this.ccStreams_ = [new Cea608Stream(0, 0), // eslint-disable-line no-use-before-define 43771 new Cea608Stream(0, 1), // eslint-disable-line no-use-before-define 43772 new Cea608Stream(1, 0), // eslint-disable-line no-use-before-define 43773 new Cea608Stream(1, 1) // eslint-disable-line no-use-before-define 43774 ]; 43775 this.reset(); // forward data and done events from CCs to this CaptionStream 43776 43777 this.ccStreams_.forEach(function (cc) { 43778 cc.on('data', this.trigger.bind(this, 'data')); 43779 cc.on('done', this.trigger.bind(this, 'done')); 43780 }, this); 43781 }; 43782 43783 CaptionStream.prototype = new stream(); 43784 43785 CaptionStream.prototype.push = function (event) { 43786 var sei, userData, newCaptionPackets; // only examine SEI NALs 43787 43788 if (event.nalUnitType !== 'sei_rbsp') { 43789 return; 43790 } // parse the sei 43791 43792 43793 sei = captionPacketParser.parseSei(event.escapedRBSP); // ignore everything but user_data_registered_itu_t_t35 43794 43795 if (sei.payloadType !== captionPacketParser.USER_DATA_REGISTERED_ITU_T_T35) { 43796 return; 43797 } // parse out the user data payload 43798 43799 43800 userData = captionPacketParser.parseUserData(sei); // ignore unrecognized userData 43801 43802 if (!userData) { 43803 return; 43804 } // Sometimes, the same segment # will be downloaded twice. To stop the 43805 // caption data from being processed twice, we track the latest dts we've 43806 // received and ignore everything with a dts before that. However, since 43807 // data for a specific dts can be split across packets on either side of 43808 // a segment boundary, we need to make sure we *don't* ignore the packets 43809 // from the *next* segment that have dts === this.latestDts_. By constantly 43810 // tracking the number of packets received with dts === this.latestDts_, we 43811 // know how many should be ignored once we start receiving duplicates. 43812 43813 43814 if (event.dts < this.latestDts_) { 43815 // We've started getting older data, so set the flag. 43816 this.ignoreNextEqualDts_ = true; 43817 return; 43818 } else if (event.dts === this.latestDts_ && this.ignoreNextEqualDts_) { 43819 this.numSameDts_--; 43820 43821 if (!this.numSameDts_) { 43822 // We've received the last duplicate packet, time to start processing again 43823 this.ignoreNextEqualDts_ = false; 43824 } 43825 43826 return; 43827 } // parse out CC data packets and save them for later 43828 43829 43830 newCaptionPackets = captionPacketParser.parseCaptionPackets(event.pts, userData); 43831 this.captionPackets_ = this.captionPackets_.concat(newCaptionPackets); 43832 43833 if (this.latestDts_ !== event.dts) { 43834 this.numSameDts_ = 0; 43835 } 43836 43837 this.numSameDts_++; 43838 this.latestDts_ = event.dts; 43839 }; 43840 43841 CaptionStream.prototype.flush = function () { 43842 // make sure we actually parsed captions before proceeding 43843 if (!this.captionPackets_.length) { 43844 this.ccStreams_.forEach(function (cc) { 43845 cc.flush(); 43846 }, this); 43847 return; 43848 } // In Chrome, the Array#sort function is not stable so add a 43849 // presortIndex that we can use to ensure we get a stable-sort 43850 43851 43852 this.captionPackets_.forEach(function (elem, idx) { 43853 elem.presortIndex = idx; 43854 }); // sort caption byte-pairs based on their PTS values 43855 43856 this.captionPackets_.sort(function (a, b) { 43857 if (a.pts === b.pts) { 43858 return a.presortIndex - b.presortIndex; 43859 } 43860 43861 return a.pts - b.pts; 43862 }); 43863 this.captionPackets_.forEach(function (packet) { 43864 if (packet.type < 2) { 43865 // Dispatch packet to the right Cea608Stream 43866 this.dispatchCea608Packet(packet); 43867 } // this is where an 'else' would go for a dispatching packets 43868 // to a theoretical Cea708Stream that handles SERVICEn data 43869 43870 }, this); 43871 this.captionPackets_.length = 0; 43872 this.ccStreams_.forEach(function (cc) { 43873 cc.flush(); 43874 }, this); 43875 return; 43876 }; 43877 43878 CaptionStream.prototype.reset = function () { 43879 this.latestDts_ = null; 43880 this.ignoreNextEqualDts_ = false; 43881 this.numSameDts_ = 0; 43882 this.activeCea608Channel_ = [null, null]; 43883 this.ccStreams_.forEach(function (ccStream) { 43884 ccStream.reset(); 43885 }); 43886 }; 43887 43888 CaptionStream.prototype.dispatchCea608Packet = function (packet) { 43889 // NOTE: packet.type is the CEA608 field 43890 if (this.setsChannel1Active(packet)) { 43891 this.activeCea608Channel_[packet.type] = 0; 43892 } else if (this.setsChannel2Active(packet)) { 43893 this.activeCea608Channel_[packet.type] = 1; 43894 } 43895 43896 if (this.activeCea608Channel_[packet.type] === null) { 43897 // If we haven't received anything to set the active channel, discard the 43898 // data; we don't want jumbled captions 43899 return; 43900 } 43901 43902 this.ccStreams_[(packet.type << 1) + this.activeCea608Channel_[packet.type]].push(packet); 43903 }; 43904 43905 CaptionStream.prototype.setsChannel1Active = function (packet) { 43906 return (packet.ccData & 0x7800) === 0x1000; 43907 }; 43908 43909 CaptionStream.prototype.setsChannel2Active = function (packet) { 43910 return (packet.ccData & 0x7800) === 0x1800; 43911 }; // ---------------------- 43912 // Session to Application 43913 // ---------------------- 43914 // This hash maps non-ASCII, special, and extended character codes to their 43915 // proper Unicode equivalent. The first keys that are only a single byte 43916 // are the non-standard ASCII characters, which simply map the CEA608 byte 43917 // to the standard ASCII/Unicode. The two-byte keys that follow are the CEA608 43918 // character codes, but have their MSB bitmasked with 0x03 so that a lookup 43919 // can be performed regardless of the field and data channel on which the 43920 // character code was received. 43921 43922 43923 var CHARACTER_TRANSLATION = { 43924 0x2a: 0xe1, 43925 // á 43926 0x5c: 0xe9, 43927 // é 43928 0x5e: 0xed, 43929 // à 43930 0x5f: 0xf3, 43931 // ó 43932 0x60: 0xfa, 43933 // ú 43934 0x7b: 0xe7, 43935 // ç 43936 0x7c: 0xf7, 43937 // ÷ 43938 0x7d: 0xd1, 43939 // à 43940 0x7e: 0xf1, 43941 // ñ 43942 0x7f: 0x2588, 43943 // â 43944 0x0130: 0xae, 43945 // ® 43946 0x0131: 0xb0, 43947 // ° 43948 0x0132: 0xbd, 43949 // ½ 43950 0x0133: 0xbf, 43951 // ¿ 43952 0x0134: 0x2122, 43953 // ⢠43954 0x0135: 0xa2, 43955 // ¢ 43956 0x0136: 0xa3, 43957 // £ 43958 0x0137: 0x266a, 43959 // ⪠43960 0x0138: 0xe0, 43961 // à 43962 0x0139: 0xa0, 43963 // 43964 0x013a: 0xe8, 43965 // è 43966 0x013b: 0xe2, 43967 // â 43968 0x013c: 0xea, 43969 // ê 43970 0x013d: 0xee, 43971 // î 43972 0x013e: 0xf4, 43973 // ô 43974 0x013f: 0xfb, 43975 // û 43976 0x0220: 0xc1, 43977 // à 43978 0x0221: 0xc9, 43979 // à 43980 0x0222: 0xd3, 43981 // à 43982 0x0223: 0xda, 43983 // à 43984 0x0224: 0xdc, 43985 // à 43986 0x0225: 0xfc, 43987 // ü 43988 0x0226: 0x2018, 43989 // â 43990 0x0227: 0xa1, 43991 // ¡ 43992 0x0228: 0x2a, 43993 // * 43994 0x0229: 0x27, 43995 // ' 43996 0x022a: 0x2014, 43997 // â 43998 0x022b: 0xa9, 43999 // © 44000 0x022c: 0x2120, 44001 // â 44002 0x022d: 0x2022, 44003 // ⢠44004 0x022e: 0x201c, 44005 // â 44006 0x022f: 0x201d, 44007 // â 44008 0x0230: 0xc0, 44009 // à 44010 0x0231: 0xc2, 44011 // à 44012 0x0232: 0xc7, 44013 // à 44014 0x0233: 0xc8, 44015 // à 44016 0x0234: 0xca, 44017 // à 44018 0x0235: 0xcb, 44019 // à 44020 0x0236: 0xeb, 44021 // ë 44022 0x0237: 0xce, 44023 // à 44024 0x0238: 0xcf, 44025 // à 44026 0x0239: 0xef, 44027 // ï 44028 0x023a: 0xd4, 44029 // à 44030 0x023b: 0xd9, 44031 // à 44032 0x023c: 0xf9, 44033 // ù 44034 0x023d: 0xdb, 44035 // à 44036 0x023e: 0xab, 44037 // « 44038 0x023f: 0xbb, 44039 // » 44040 0x0320: 0xc3, 44041 // à 44042 0x0321: 0xe3, 44043 // ã 44044 0x0322: 0xcd, 44045 // à 44046 0x0323: 0xcc, 44047 // à 44048 0x0324: 0xec, 44049 // ì 44050 0x0325: 0xd2, 44051 // à 44052 0x0326: 0xf2, 44053 // ò 44054 0x0327: 0xd5, 44055 // à 44056 0x0328: 0xf5, 44057 // õ 44058 0x0329: 0x7b, 44059 // { 44060 0x032a: 0x7d, 44061 // } 44062 0x032b: 0x5c, 44063 // \
vendor: 19,869 bytes, lines 44064-44574
44064 0x032c: 0x5e, 44065 // ^ 44066 0x032d: 0x5f, 44067 // _ 44068 0x032e: 0x7c, 44069 // | 44070 0x032f: 0x7e, 44071 // ~ 44072 0x0330: 0xc4, 44073 // à 44074 0x0331: 0xe4, 44075 // ä 44076 0x0332: 0xd6, 44077 // à 44078 0x0333: 0xf6, 44079 // ö 44080 0x0334: 0xdf, 44081 // à 44082 0x0335: 0xa5, 44083 // Â¥ 44084 0x0336: 0xa4, 44085 // ¤ 44086 0x0337: 0x2502, 44087 // â 44088 0x0338: 0xc5, 44089 // à 44090 0x0339: 0xe5, 44091 // Ã¥ 44092 0x033a: 0xd8, 44093 // à 44094 0x033b: 0xf8, 44095 // ø 44096 0x033c: 0x250c, 44097 // â 44098 0x033d: 0x2510, 44099 // â 44100 0x033e: 0x2514, 44101 // â 44102 0x033f: 0x2518 // â 44103 44104 }; 44105 44106 var getCharFromCode = function getCharFromCode(code) { 44107 if (code === null) { 44108 return ''; 44109 } 44110 44111 code = CHARACTER_TRANSLATION[code] || code; 44112 return String.fromCharCode(code); 44113 }; // the index of the last row in a CEA-608 display buffer 44114 44115 44116 var BOTTOM_ROW = 14; // This array is used for mapping PACs -> row #, since there's no way of 44117 // getting it through bit logic. 44118 44119 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 44120 // cells. The "bottom" row is the last element in the outer array. 44121 44122 var createDisplayBuffer = function createDisplayBuffer() { 44123 var result = [], 44124 i = BOTTOM_ROW + 1; 44125 44126 while (i--) { 44127 result.push(''); 44128 } 44129 44130 return result; 44131 }; 44132 44133 var Cea608Stream = function Cea608Stream(field, dataChannel) { 44134 Cea608Stream.prototype.init.call(this); 44135 this.field_ = field || 0; 44136 this.dataChannel_ = dataChannel || 0; 44137 this.name_ = 'CC' + ((this.field_ << 1 | this.dataChannel_) + 1); 44138 this.setConstants(); 44139 this.reset(); 44140 44141 this.push = function (packet) { 44142 var data, swap, char0, char1, text; // remove the parity bits 44143 44144 data = packet.ccData & 0x7f7f; // ignore duplicate control codes; the spec demands they're sent twice 44145 44146 if (data === this.lastControlCode_) { 44147 this.lastControlCode_ = null; 44148 return; 44149 } // Store control codes 44150 44151 44152 if ((data & 0xf000) === 0x1000) { 44153 this.lastControlCode_ = data; 44154 } else if (data !== this.PADDING_) { 44155 this.lastControlCode_ = null; 44156 } 44157 44158 char0 = data >>> 8; 44159 char1 = data & 0xff; 44160 44161 if (data === this.PADDING_) { 44162 return; 44163 } else if (data === this.RESUME_CAPTION_LOADING_) { 44164 this.mode_ = 'popOn'; 44165 } else if (data === this.END_OF_CAPTION_) { 44166 // If an EOC is received while in paint-on mode, the displayed caption 44167 // text should be swapped to non-displayed memory as if it was a pop-on 44168 // caption. Because of that, we should explicitly switch back to pop-on 44169 // mode 44170 this.mode_ = 'popOn'; 44171 this.clearFormatting(packet.pts); // if a caption was being displayed, it's gone now 44172 44173 this.flushDisplayed(packet.pts); // flip memory 44174 44175 swap = this.displayed_; 44176 this.displayed_ = this.nonDisplayed_; 44177 this.nonDisplayed_ = swap; // start measuring the time to display the caption 44178 44179 this.startPts_ = packet.pts; 44180 } else if (data === this.ROLL_UP_2_ROWS_) { 44181 this.rollUpRows_ = 2; 44182 this.setRollUp(packet.pts); 44183 } else if (data === this.ROLL_UP_3_ROWS_) { 44184 this.rollUpRows_ = 3; 44185 this.setRollUp(packet.pts); 44186 } else if (data === this.ROLL_UP_4_ROWS_) { 44187 this.rollUpRows_ = 4; 44188 this.setRollUp(packet.pts); 44189 } else if (data === this.CARRIAGE_RETURN_) { 44190 this.clearFormatting(packet.pts); 44191 this.flushDisplayed(packet.pts); 44192 this.shiftRowsUp_(); 44193 this.startPts_ = packet.pts; 44194 } else if (data === this.BACKSPACE_) { 44195 if (this.mode_ === 'popOn') { 44196 this.nonDisplayed_[this.row_] = this.nonDisplayed_[this.row_].slice(0, -1); 44197 } else { 44198 this.displayed_[this.row_] = this.displayed_[this.row_].slice(0, -1); 44199 } 44200 } else if (data === this.ERASE_DISPLAYED_MEMORY_) { 44201 this.flushDisplayed(packet.pts); 44202 this.displayed_ = createDisplayBuffer(); 44203 } else if (data === this.ERASE_NON_DISPLAYED_MEMORY_) { 44204 this.nonDisplayed_ = createDisplayBuffer(); 44205 } else if (data === this.RESUME_DIRECT_CAPTIONING_) { 44206 if (this.mode_ !== 'paintOn') { 44207 // NOTE: This should be removed when proper caption positioning is 44208 // implemented 44209 this.flushDisplayed(packet.pts); 44210 this.displayed_ = createDisplayBuffer(); 44211 } 44212 44213 this.mode_ = 'paintOn'; 44214 this.startPts_ = packet.pts; // Append special characters to caption text 44215 } else if (this.isSpecialCharacter(char0, char1)) { 44216 // Bitmask char0 so that we can apply character transformations 44217 // regardless of field and data channel. 44218 // Then byte-shift to the left and OR with char1 so we can pass the 44219 // entire character code to `getCharFromCode`. 44220 char0 = (char0 & 0x03) << 8; 44221 text = getCharFromCode(char0 | char1); 44222 this[this.mode_](packet.pts, text); 44223 this.column_++; // Append extended characters to caption text 44224 } else if (this.isExtCharacter(char0, char1)) { 44225 // Extended characters always follow their "non-extended" equivalents. 44226 // IE if a "è" is desired, you'll always receive "eè"; non-compliant 44227 // decoders are supposed to drop the "è", while compliant decoders 44228 // backspace the "e" and insert "è". 44229 // Delete the previous character 44230 if (this.mode_ === 'popOn') { 44231 this.nonDisplayed_[this.row_] = this.nonDisplayed_[this.row_].slice(0, -1); 44232 } else { 44233 this.displayed_[this.row_] = this.displayed_[this.row_].slice(0, -1); 44234 } // Bitmask char0 so that we can apply character transformations 44235 // regardless of field and data channel. 44236 // Then byte-shift to the left and OR with char1 so we can pass the 44237 // entire character code to `getCharFromCode`. 44238 44239 44240 char0 = (char0 & 0x03) << 8; 44241 text = getCharFromCode(char0 | char1); 44242 this[this.mode_](packet.pts, text); 44243 this.column_++; // Process mid-row codes 44244 } else if (this.isMidRowCode(char0, char1)) { 44245 // Attributes are not additive, so clear all formatting 44246 this.clearFormatting(packet.pts); // According to the standard, mid-row codes 44247 // should be replaced with spaces, so add one now 44248 44249 this[this.mode_](packet.pts, ' '); 44250 this.column_++; 44251 44252 if ((char1 & 0xe) === 0xe) { 44253 this.addFormatting(packet.pts, ['i']); 44254 } 44255 44256 if ((char1 & 0x1) === 0x1) { 44257 this.addFormatting(packet.pts, ['u']); 44258 } // Detect offset control codes and adjust cursor 44259 44260 } else if (this.isOffsetControlCode(char0, char1)) { 44261 // Cursor position is set by indent PAC (see below) in 4-column 44262 // increments, with an additional offset code of 1-3 to reach any 44263 // of the 32 columns specified by CEA-608. So all we need to do 44264 // here is increment the column cursor by the given offset. 44265 this.column_ += char1 & 0x03; // Detect PACs (Preamble Address Codes) 44266 } else if (this.isPAC(char0, char1)) { 44267 // There's no logic for PAC -> row mapping, so we have to just 44268 // find the row code in an array and use its index :( 44269 var row = ROWS.indexOf(data & 0x1f20); // Configure the caption window if we're in roll-up mode 44270 44271 if (this.mode_ === 'rollUp') { 44272 // This implies that the base row is incorrectly set. 44273 // As per the recommendation in CEA-608(Base Row Implementation), defer to the number 44274 // of roll-up rows set. 44275 if (row - this.rollUpRows_ + 1 < 0) { 44276 row = this.rollUpRows_ - 1; 44277 } 44278 44279 this.setRollUp(packet.pts, row); 44280 } 44281 44282 if (row !== this.row_) { 44283 // formatting is only persistent for current row 44284 this.clearFormatting(packet.pts); 44285 this.row_ = row; 44286 } // All PACs can apply underline, so detect and apply 44287 // (All odd-numbered second bytes set underline) 44288 44289 44290 if (char1 & 0x1 && this.formatting_.indexOf('u') === -1) { 44291 this.addFormatting(packet.pts, ['u']); 44292 } 44293 44294 if ((data & 0x10) === 0x10) { 44295 // We've got an indent level code. Each successive even number 44296 // increments the column cursor by 4, so we can get the desired 44297 // column position by bit-shifting to the right (to get n/2) 44298 // and multiplying by 4. 44299 this.column_ = ((data & 0xe) >> 1) * 4; 44300 } 44301 44302 if (this.isColorPAC(char1)) { 44303 // it's a color code, though we only support white, which 44304 // can be either normal or italicized. white italics can be 44305 // either 0x4e or 0x6e depending on the row, so we just 44306 // bitwise-and with 0xe to see if italics should be turned on 44307 if ((char1 & 0xe) === 0xe) { 44308 this.addFormatting(packet.pts, ['i']); 44309 } 44310 } // We have a normal character in char0, and possibly one in char1 44311 44312 } else if (this.isNormalChar(char0)) { 44313 if (char1 === 0x00) { 44314 char1 = null; 44315 } 44316 44317 text = getCharFromCode(char0); 44318 text += getCharFromCode(char1); 44319 this[this.mode_](packet.pts, text); 44320 this.column_ += text.length; 44321 } // finish data processing 44322 44323 }; 44324 }; 44325 44326 Cea608Stream.prototype = new stream(); // Trigger a cue point that captures the current state of the 44327 // display buffer 44328 44329 Cea608Stream.prototype.flushDisplayed = function (pts) { 44330 var content = this.displayed_ // remove spaces from the start and end of the string 44331 .map(function (row) { 44332 try { 44333 return row.trim(); 44334 } catch (e) { 44335 // Ordinarily, this shouldn't happen. However, caption 44336 // parsing errors should not throw exceptions and 44337 // break playback. 44338 // eslint-disable-next-line no-console 44339 console.error('Skipping malformed caption.'); 44340 return ''; 44341 } 44342 }) // combine all text rows to display in one cue 44343 .join('\n') // and remove blank rows from the start and end, but not the middle 44344 .replace(/^\n+|\n+$/g, ''); 44345 44346 if (content.length) { 44347 this.trigger('data', { 44348 startPts: this.startPts_, 44349 endPts: pts, 44350 text: content, 44351 stream: this.name_ 44352 }); 44353 } 44354 }; 44355 /** 44356 * Zero out the data, used for startup and on seek 44357 */ 44358 44359 44360 Cea608Stream.prototype.reset = function () { 44361 this.mode_ = 'popOn'; // When in roll-up mode, the index of the last row that will 44362 // actually display captions. If a caption is shifted to a row 44363 // with a lower index than this, it is cleared from the display 44364 // buffer 44365 44366 this.topRow_ = 0; 44367 this.startPts_ = 0; 44368 this.displayed_ = createDisplayBuffer(); 44369 this.nonDisplayed_ = createDisplayBuffer(); 44370 this.lastControlCode_ = null; // Track row and column for proper line-breaking and spacing 44371 44372 this.column_ = 0; 44373 this.row_ = BOTTOM_ROW; 44374 this.rollUpRows_ = 2; // This variable holds currently-applied formatting 44375 44376 this.formatting_ = []; 44377 }; 44378 /** 44379 * Sets up control code and related constants for this instance 44380 */ 44381 44382 44383 Cea608Stream.prototype.setConstants = function () { 44384 // The following attributes have these uses: 44385 // ext_ : char0 for mid-row codes, and the base for extended 44386 // chars (ext_+0, ext_+1, and ext_+2 are char0s for 44387 // extended codes) 44388 // control_: char0 for control codes, except byte-shifted to the 44389 // left so that we can do this.control_ | CONTROL_CODE 44390 // offset_: char0 for tab offset codes 44391 // 44392 // It's also worth noting that control codes, and _only_ control codes, 44393 // differ between field 1 and field2. Field 2 control codes are always 44394 // their field 1 value plus 1. That's why there's the "| field" on the 44395 // control value. 44396 if (this.dataChannel_ === 0) { 44397 this.BASE_ = 0x10; 44398 this.EXT_ = 0x11; 44399 this.CONTROL_ = (0x14 | this.field_) << 8; 44400 this.OFFSET_ = 0x17; 44401 } else if (this.dataChannel_ === 1) { 44402 this.BASE_ = 0x18; 44403 this.EXT_ = 0x19; 44404 this.CONTROL_ = (0x1c | this.field_) << 8; 44405 this.OFFSET_ = 0x1f; 44406 } // Constants for the LSByte command codes recognized by Cea608Stream. This 44407 // list is not exhaustive. For a more comprehensive listing and semantics see 44408 // http://www.gpo.gov/fdsys/pkg/CFR-2010-title47-vol1/pdf/CFR-2010-title47-vol1-sec15-119.pdf 44409 // Padding 44410 44411 44412 this.PADDING_ = 0x0000; // Pop-on Mode 44413 44414 this.RESUME_CAPTION_LOADING_ = this.CONTROL_ | 0x20; 44415 this.END_OF_CAPTION_ = this.CONTROL_ | 0x2f; // Roll-up Mode 44416 44417 this.ROLL_UP_2_ROWS_ = this.CONTROL_ | 0x25; 44418 this.ROLL_UP_3_ROWS_ = this.CONTROL_ | 0x26; 44419 this.ROLL_UP_4_ROWS_ = this.CONTROL_ | 0x27; 44420 this.CARRIAGE_RETURN_ = this.CONTROL_ | 0x2d; // paint-on mode 44421 44422 this.RESUME_DIRECT_CAPTIONING_ = this.CONTROL_ | 0x29; // Erasure 44423 44424 this.BACKSPACE_ = this.CONTROL_ | 0x21; 44425 this.ERASE_DISPLAYED_MEMORY_ = this.CONTROL_ | 0x2c; 44426 this.ERASE_NON_DISPLAYED_MEMORY_ = this.CONTROL_ | 0x2e; 44427 }; 44428 /** 44429 * Detects if the 2-byte packet data is a special character 44430 * 44431 * Special characters have a second byte in the range 0x30 to 0x3f, 44432 * with the first byte being 0x11 (for data channel 1) or 0x19 (for 44433 * data channel 2). 44434 * 44435 * @param {Integer} char0 The first byte 44436 * @param {Integer} char1 The second byte 44437 * @return {Boolean} Whether the 2 bytes are an special character 44438 */ 44439 44440 44441 Cea608Stream.prototype.isSpecialCharacter = function (char0, char1) { 44442 return char0 === this.EXT_ && char1 >= 0x30 && char1 <= 0x3f; 44443 }; 44444 /** 44445 * Detects if the 2-byte packet data is an extended character 44446 * 44447 * Extended characters have a second byte in the range 0x20 to 0x3f, 44448 * with the first byte being 0x12 or 0x13 (for data channel 1) or 44449 * 0x1a or 0x1b (for data channel 2). 44450 * 44451 * @param {Integer} char0 The first byte 44452 * @param {Integer} char1 The second byte 44453 * @return {Boolean} Whether the 2 bytes are an extended character 44454 */ 44455 44456 44457 Cea608Stream.prototype.isExtCharacter = function (char0, char1) { 44458 return (char0 === this.EXT_ + 1 || char0 === this.EXT_ + 2) && char1 >= 0x20 && char1 <= 0x3f; 44459 }; 44460 /** 44461 * Detects if the 2-byte packet is a mid-row code 44462 * 44463 * Mid-row codes have a second byte in the range 0x20 to 0x2f, with 44464 * the first byte being 0x11 (for data channel 1) or 0x19 (for data 44465 * channel 2). 44466 * 44467 * @param {Integer} char0 The first byte 44468 * @param {Integer} char1 The second byte 44469 * @return {Boolean} Whether the 2 bytes are a mid-row code 44470 */ 44471 44472 44473 Cea608Stream.prototype.isMidRowCode = function (char0, char1) { 44474 return char0 === this.EXT_ && char1 >= 0x20 && char1 <= 0x2f; 44475 }; 44476 /** 44477 * Detects if the 2-byte packet is an offset control code 44478 * 44479 * Offset control codes have a second byte in the range 0x21 to 0x23, 44480 * with the first byte being 0x17 (for data channel 1) or 0x1f (for 44481 * data channel 2). 44482 * 44483 * @param {Integer} char0 The first byte 44484 * @param {Integer} char1 The second byte 44485 * @return {Boolean} Whether the 2 bytes are an offset control code 44486 */ 44487 44488 44489 Cea608Stream.prototype.isOffsetControlCode = function (char0, char1) { 44490 return char0 === this.OFFSET_ && char1 >= 0x21 && char1 <= 0x23; 44491 }; 44492 /** 44493 * Detects if the 2-byte packet is a Preamble Address Code 44494 * 44495 * PACs have a first byte in the range 0x10 to 0x17 (for data channel 1) 44496 * or 0x18 to 0x1f (for data channel 2), with the second byte in the 44497 * range 0x40 to 0x7f. 44498 * 44499 * @param {Integer} char0 The first byte 44500 * @param {Integer} char1 The second byte 44501 * @return {Boolean} Whether the 2 bytes are a PAC 44502 */ 44503 44504 44505 Cea608Stream.prototype.isPAC = function (char0, char1) { 44506 return char0 >= this.BASE_ && char0 < this.BASE_ + 8 && char1 >= 0x40 && char1 <= 0x7f; 44507 }; 44508 /** 44509 * Detects if a packet's second byte is in the range of a PAC color code 44510 * 44511 * PAC color codes have the second byte be in the range 0x40 to 0x4f, or 44512 * 0x60 to 0x6f. 44513 * 44514 * @param {Integer} char1 The second byte 44515 * @return {Boolean} Whether the byte is a color PAC 44516 */ 44517 44518 44519 Cea608Stream.prototype.isColorPAC = function (char1) { 44520 return char1 >= 0x40 && char1 <= 0x4f || char1 >= 0x60 && char1 <= 0x7f; 44521 }; 44522 /** 44523 * Detects if a single byte is in the range of a normal character 44524 * 44525 * Normal text bytes are in the range 0x20 to 0x7f. 44526 * 44527 * @param {Integer} char The byte 44528 * @return {Boolean} Whether the byte is a normal character 44529 */ 44530 44531 44532 Cea608Stream.prototype.isNormalChar = function (char) { 44533 return char >= 0x20 && char <= 0x7f; 44534 }; 44535 /** 44536 * Configures roll-up 44537 * 44538 * @param {Integer} pts Current PTS 44539 * @param {Integer} newBaseRow Used by PACs to slide the current window to 44540 * a new position 44541 */ 44542 44543 44544 Cea608Stream.prototype.setRollUp = function (pts, newBaseRow) { 44545 // Reset the base row to the bottom row when switching modes 44546 if (this.mode_ !== 'rollUp') { 44547 this.row_ = BOTTOM_ROW; 44548 this.mode_ = 'rollUp'; // Spec says to wipe memories when switching to roll-up 44549 44550 this.flushDisplayed(pts); 44551 this.nonDisplayed_ = createDisplayBuffer(); 44552 this.displayed_ = createDisplayBuffer(); 44553 } 44554 44555 if (newBaseRow !== undefined && newBaseRow !== this.row_) { 44556 // move currently displayed captions (up or down) to the new base row 44557 for (var i = 0; i < this.rollUpRows_; i++) { 44558 this.displayed_[newBaseRow - i] = this.displayed_[this.row_ - i]; 44559 this.displayed_[this.row_ - i] = ''; 44560 } 44561 } 44562 44563 if (newBaseRow === undefined) { 44564 newBaseRow = this.row_; 44565 } 44566 44567 this.topRow_ = newBaseRow - this.rollUpRows_ + 1; 44568 }; // Adds the opening HTML tag for the passed character to the caption text, 44569 // and keeps track of it for later closing 44570 44571 44572 Cea608Stream.prototype.addFormatting = function (pts, format) { 44573 this.formatting_ = this.formatting_.concat(format); 44574 var text = format.reduce(function (text, format) {
vendor: 44,246 bytes, lines 44575-45818
44575 return text + '<' + format + '>'; 44576 }, ''); 44577 this[this.mode_](pts, text); 44578 }; // Adds HTML closing tags for current formatting to caption text and 44579 // clears remembered formatting 44580 44581 44582 Cea608Stream.prototype.clearFormatting = function (pts) { 44583 if (!this.formatting_.length) { 44584 return; 44585 } 44586 44587 var text = this.formatting_.reverse().reduce(function (text, format) { 44588 return text + '</' + format + '>'; 44589 }, ''); 44590 this.formatting_ = []; 44591 this[this.mode_](pts, text); 44592 }; // Mode Implementations 44593 44594 44595 Cea608Stream.prototype.popOn = function (pts, text) { 44596 var baseRow = this.nonDisplayed_[this.row_]; // buffer characters 44597 44598 baseRow += text; 44599 this.nonDisplayed_[this.row_] = baseRow; 44600 }; 44601 44602 Cea608Stream.prototype.rollUp = function (pts, text) { 44603 var baseRow = this.displayed_[this.row_]; 44604 baseRow += text; 44605 this.displayed_[this.row_] = baseRow; 44606 }; 44607 44608 Cea608Stream.prototype.shiftRowsUp_ = function () { 44609 var i; // clear out inactive rows 44610 44611 for (i = 0; i < this.topRow_; i++) { 44612 this.displayed_[i] = ''; 44613 } 44614 44615 for (i = this.row_ + 1; i < BOTTOM_ROW + 1; i++) { 44616 this.displayed_[i] = ''; 44617 } // shift displayed rows up 44618 44619 44620 for (i = this.topRow_; i < this.row_; i++) { 44621 this.displayed_[i] = this.displayed_[i + 1]; 44622 } // clear out the bottom row 44623 44624 44625 this.displayed_[this.row_] = ''; 44626 }; 44627 44628 Cea608Stream.prototype.paintOn = function (pts, text) { 44629 var baseRow = this.displayed_[this.row_]; 44630 baseRow += text; 44631 this.displayed_[this.row_] = baseRow; 44632 }; // exports 44633 44634 44635 var captionStream = { 44636 CaptionStream: CaptionStream, 44637 Cea608Stream: Cea608Stream 44638 }; 44639 var streamTypes = { 44640 H264_STREAM_TYPE: 0x1B, 44641 ADTS_STREAM_TYPE: 0x0F, 44642 METADATA_STREAM_TYPE: 0x15 44643 }; 44644 var MAX_TS = 8589934592; 44645 var RO_THRESH = 4294967296; 44646 44647 var handleRollover = function handleRollover(value, reference) { 44648 var direction = 1; 44649 44650 if (value > reference) { 44651 // If the current timestamp value is greater than our reference timestamp and we detect a 44652 // timestamp rollover, this means the roll over is happening in the opposite direction. 44653 // Example scenario: Enter a long stream/video just after a rollover occurred. The reference 44654 // point will be set to a small number, e.g. 1. The user then seeks backwards over the 44655 // rollover point. In loading this segment, the timestamp values will be very large, 44656 // e.g. 2^33 - 1. Since this comes before the data we loaded previously, we want to adjust 44657 // the time stamp to be `value - 2^33`. 44658 direction = -1; 44659 } // Note: A seek forwards or back that is greater than the RO_THRESH (2^32, ~13 hours) will 44660 // cause an incorrect adjustment. 44661 44662 44663 while (Math.abs(reference - value) > RO_THRESH) { 44664 value += direction * MAX_TS; 44665 } 44666 44667 return value; 44668 }; 44669 44670 var TimestampRolloverStream = function TimestampRolloverStream(type) { 44671 var lastDTS, referenceDTS; 44672 TimestampRolloverStream.prototype.init.call(this); 44673 this.type_ = type; 44674 44675 this.push = function (data) { 44676 if (data.type !== this.type_) { 44677 return; 44678 } 44679 44680 if (referenceDTS === undefined) { 44681 referenceDTS = data.dts; 44682 } 44683 44684 data.dts = handleRollover(data.dts, referenceDTS); 44685 data.pts = handleRollover(data.pts, referenceDTS); 44686 lastDTS = data.dts; 44687 this.trigger('data', data); 44688 }; 44689 44690 this.flush = function () { 44691 referenceDTS = lastDTS; 44692 this.trigger('done'); 44693 }; 44694 44695 this.discontinuity = function () { 44696 referenceDTS = void 0; 44697 lastDTS = void 0; 44698 }; 44699 }; 44700 44701 TimestampRolloverStream.prototype = new stream(); 44702 var timestampRolloverStream = { 44703 TimestampRolloverStream: TimestampRolloverStream, 44704 handleRollover: handleRollover 44705 }; 44706 44707 var percentEncode = function percentEncode(bytes, start, end) { 44708 var i, 44709 result = ''; 44710 44711 for (i = start; i < end; i++) { 44712 result += '%' + ('00' + bytes[i].toString(16)).slice(-2); 44713 } 44714 44715 return result; 44716 }, 44717 // return the string representation of the specified byte range, 44718 // interpreted as UTf-8. 44719 parseUtf8 = function parseUtf8(bytes, start, end) { 44720 return decodeURIComponent(percentEncode(bytes, start, end)); 44721 }, 44722 // return the string representation of the specified byte range, 44723 // interpreted as ISO-8859-1. 44724 parseIso88591 = function parseIso88591(bytes, start, end) { 44725 return unescape(percentEncode(bytes, start, end)); // jshint ignore:line 44726 }, 44727 parseSyncSafeInteger = function parseSyncSafeInteger(data) { 44728 return data[0] << 21 | data[1] << 14 | data[2] << 7 | data[3]; 44729 }, 44730 tagParsers = { 44731 TXXX: function TXXX(tag) { 44732 var i; 44733 44734 if (tag.data[0] !== 3) { 44735 // ignore frames with unrecognized character encodings 44736 return; 44737 } 44738 44739 for (i = 1; i < tag.data.length; i++) { 44740 if (tag.data[i] === 0) { 44741 // parse the text fields 44742 tag.description = parseUtf8(tag.data, 1, i); // do not include the null terminator in the tag value 44743 44744 tag.value = parseUtf8(tag.data, i + 1, tag.data.length).replace(/\0*$/, ''); 44745 break; 44746 } 44747 } 44748 44749 tag.data = tag.value; 44750 }, 44751 WXXX: function WXXX(tag) { 44752 var i; 44753 44754 if (tag.data[0] !== 3) { 44755 // ignore frames with unrecognized character encodings 44756 return; 44757 } 44758 44759 for (i = 1; i < tag.data.length; i++) { 44760 if (tag.data[i] === 0) { 44761 // parse the description and URL fields 44762 tag.description = parseUtf8(tag.data, 1, i); 44763 tag.url = parseUtf8(tag.data, i + 1, tag.data.length); 44764 break; 44765 } 44766 } 44767 }, 44768 PRIV: function PRIV(tag) { 44769 var i; 44770 44771 for (i = 0; i < tag.data.length; i++) { 44772 if (tag.data[i] === 0) { 44773 // parse the description and URL fields 44774 tag.owner = parseIso88591(tag.data, 0, i); 44775 break; 44776 } 44777 } 44778 44779 tag.privateData = tag.data.subarray(i + 1); 44780 tag.data = tag.privateData; 44781 } 44782 }, 44783 _MetadataStream; 44784 44785 _MetadataStream = function MetadataStream(options) { 44786 var settings = { 44787 debug: !!(options && options.debug), 44788 // the bytes of the program-level descriptor field in MP2T 44789 // see ISO/IEC 13818-1:2013 (E), section 2.6 "Program and 44790 // program element descriptors" 44791 descriptor: options && options.descriptor 44792 }, 44793 // the total size in bytes of the ID3 tag being parsed 44794 tagSize = 0, 44795 // tag data that is not complete enough to be parsed 44796 buffer = [], 44797 // the total number of bytes currently in the buffer 44798 bufferSize = 0, 44799 i; 44800 44801 _MetadataStream.prototype.init.call(this); // calculate the text track in-band metadata track dispatch type 44802 // https://html.spec.whatwg.org/multipage/embedded-content.html#steps-to-expose-a-media-resource-specific-text-track 44803 44804 44805 this.dispatchType = streamTypes.METADATA_STREAM_TYPE.toString(16); 44806 44807 if (settings.descriptor) { 44808 for (i = 0; i < settings.descriptor.length; i++) { 44809 this.dispatchType += ('00' + settings.descriptor[i].toString(16)).slice(-2); 44810 } 44811 } 44812 44813 this.push = function (chunk) { 44814 var tag, frameStart, frameSize, frame, i, frameHeader; 44815 44816 if (chunk.type !== 'timed-metadata') { 44817 return; 44818 } // if data_alignment_indicator is set in the PES header, 44819 // we must have the start of a new ID3 tag. Assume anything 44820 // remaining in the buffer was malformed and throw it out 44821 44822 44823 if (chunk.dataAlignmentIndicator) { 44824 bufferSize = 0; 44825 buffer.length = 0; 44826 } // ignore events that don't look like ID3 data 44827 44828 44829 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))) { 44830 if (settings.debug) { 44831 // eslint-disable-next-line no-console 44832 console.log('Skipping unrecognized metadata packet'); 44833 } 44834 44835 return; 44836 } // add this chunk to the data we've collected so far 44837 44838 44839 buffer.push(chunk); 44840 bufferSize += chunk.data.byteLength; // grab the size of the entire frame from the ID3 header 44841 44842 if (buffer.length === 1) { 44843 // the frame size is transmitted as a 28-bit integer in the 44844 // last four bytes of the ID3 header. 44845 // The most significant bit of each byte is dropped and the 44846 // results concatenated to recover the actual value. 44847 tagSize = parseSyncSafeInteger(chunk.data.subarray(6, 10)); // ID3 reports the tag size excluding the header but it's more 44848 // convenient for our comparisons to include it 44849 44850 tagSize += 10; 44851 } // if the entire frame has not arrived, wait for more data 44852 44853 44854 if (bufferSize < tagSize) { 44855 return; 44856 } // collect the entire frame so it can be parsed 44857 44858 44859 tag = { 44860 data: new Uint8Array(tagSize), 44861 frames: [], 44862 pts: buffer[0].pts, 44863 dts: buffer[0].dts 44864 }; 44865 44866 for (i = 0; i < tagSize;) { 44867 tag.data.set(buffer[0].data.subarray(0, tagSize - i), i); 44868 i += buffer[0].data.byteLength; 44869 bufferSize -= buffer[0].data.byteLength; 44870 buffer.shift(); 44871 } // find the start of the first frame and the end of the tag 44872 44873 44874 frameStart = 10; 44875 44876 if (tag.data[5] & 0x40) { 44877 // advance the frame start past the extended header 44878 frameStart += 4; // header size field 44879 44880 frameStart += parseSyncSafeInteger(tag.data.subarray(10, 14)); // clip any padding off the end 44881 44882 tagSize -= parseSyncSafeInteger(tag.data.subarray(16, 20)); 44883 } // parse one or more ID3 frames 44884 // http://id3.org/id3v2.3.0#ID3v2_frame_overview 44885 44886 44887 do { 44888 // determine the number of bytes in this frame 44889 frameSize = parseSyncSafeInteger(tag.data.subarray(frameStart + 4, frameStart + 8)); 44890 44891 if (frameSize < 1) { 44892 // eslint-disable-next-line no-console 44893 return console.log('Malformed ID3 frame encountered. Skipping metadata parsing.'); 44894 } 44895 44896 frameHeader = String.fromCharCode(tag.data[frameStart], tag.data[frameStart + 1], tag.data[frameStart + 2], tag.data[frameStart + 3]); 44897 frame = { 44898 id: frameHeader, 44899 data: tag.data.subarray(frameStart + 10, frameStart + frameSize + 10) 44900 }; 44901 frame.key = frame.id; 44902 44903 if (tagParsers[frame.id]) { 44904 tagParsers[frame.id](frame); // handle the special PRIV frame used to indicate the start 44905 // time for raw AAC data 44906 44907 if (frame.owner === 'com.apple.streaming.transportStreamTimestamp') { 44908 var d = frame.data, 44909 size = (d[3] & 0x01) << 30 | d[4] << 22 | d[5] << 14 | d[6] << 6 | d[7] >>> 2; 44910 size *= 4; 44911 size += d[7] & 0x03; 44912 frame.timeStamp = size; // in raw AAC, all subsequent data will be timestamped based 44913 // on the value of this frame 44914 // we couldn't have known the appropriate pts and dts before 44915 // parsing this ID3 tag so set those values now 44916 44917 if (tag.pts === undefined && tag.dts === undefined) { 44918 tag.pts = frame.timeStamp; 44919 tag.dts = frame.timeStamp; 44920 } 44921 44922 this.trigger('timestamp', frame); 44923 } 44924 } 44925 44926 tag.frames.push(frame); 44927 frameStart += 10; // advance past the frame header 44928 44929 frameStart += frameSize; // advance past the frame body 44930 } while (frameStart < tagSize); 44931 44932 this.trigger('data', tag); 44933 }; 44934 }; 44935 44936 _MetadataStream.prototype = new stream(); 44937 var metadataStream = _MetadataStream; 44938 var TimestampRolloverStream$1 = timestampRolloverStream.TimestampRolloverStream; // object types 44939 44940 var _TransportPacketStream, _TransportParseStream, _ElementaryStream; // constants 44941 44942 44943 var MP2T_PACKET_LENGTH = 188, 44944 // bytes 44945 SYNC_BYTE = 0x47; 44946 /** 44947 * Splits an incoming stream of binary data into MPEG-2 Transport 44948 * Stream packets. 44949 */ 44950 44951 _TransportPacketStream = function TransportPacketStream() { 44952 var buffer = new Uint8Array(MP2T_PACKET_LENGTH), 44953 bytesInBuffer = 0; 44954 44955 _TransportPacketStream.prototype.init.call(this); // Deliver new bytes to the stream. 44956 44957 /** 44958 * Split a stream of data into M2TS packets 44959 **/ 44960 44961 44962 this.push = function (bytes) { 44963 var startIndex = 0, 44964 endIndex = MP2T_PACKET_LENGTH, 44965 everything; // If there are bytes remaining from the last segment, prepend them to the 44966 // bytes that were pushed in 44967 44968 if (bytesInBuffer) { 44969 everything = new Uint8Array(bytes.byteLength + bytesInBuffer); 44970 everything.set(buffer.subarray(0, bytesInBuffer)); 44971 everything.set(bytes, bytesInBuffer); 44972 bytesInBuffer = 0; 44973 } else { 44974 everything = bytes; 44975 } // While we have enough data for a packet 44976 44977 44978 while (endIndex < everything.byteLength) { 44979 // Look for a pair of start and end sync bytes in the data.. 44980 if (everything[startIndex] === SYNC_BYTE && everything[endIndex] === SYNC_BYTE) { 44981 // We found a packet so emit it and jump one whole packet forward in 44982 // the stream 44983 this.trigger('data', everything.subarray(startIndex, endIndex)); 44984 startIndex += MP2T_PACKET_LENGTH; 44985 endIndex += MP2T_PACKET_LENGTH; 44986 continue; 44987 } // If we get here, we have somehow become de-synchronized and we need to step 44988 // forward one byte at a time until we find a pair of sync bytes that denote 44989 // a packet 44990 44991 44992 startIndex++; 44993 endIndex++; 44994 } // If there was some data left over at the end of the segment that couldn't 44995 // possibly be a whole packet, keep it because it might be the start of a packet 44996 // that continues in the next segment 44997 44998 44999 if (startIndex < everything.byteLength) { 45000 buffer.set(everything.subarray(startIndex), 0); 45001 bytesInBuffer = everything.byteLength - startIndex; 45002 } 45003 }; 45004 /** 45005 * Passes identified M2TS packets to the TransportParseStream to be parsed 45006 **/ 45007 45008 45009 this.flush = function () { 45010 // If the buffer contains a whole packet when we are being flushed, emit it 45011 // and empty the buffer. Otherwise hold onto the data because it may be 45012 // important for decoding the next segment 45013 if (bytesInBuffer === MP2T_PACKET_LENGTH && buffer[0] === SYNC_BYTE) { 45014 this.trigger('data', buffer); 45015 bytesInBuffer = 0; 45016 } 45017 45018 this.trigger('done'); 45019 }; 45020 }; 45021 45022 _TransportPacketStream.prototype = new stream(); 45023 /** 45024 * Accepts an MP2T TransportPacketStream and emits data events with parsed 45025 * forms of the individual transport stream packets. 45026 */ 45027 45028 _TransportParseStream = function TransportParseStream() { 45029 var parsePsi, parsePat, parsePmt, self; 45030 45031 _TransportParseStream.prototype.init.call(this); 45032 45033 self = this; 45034 this.packetsWaitingForPmt = []; 45035 this.programMapTable = undefined; 45036 45037 parsePsi = function parsePsi(payload, psi) { 45038 var offset = 0; // PSI packets may be split into multiple sections and those 45039 // sections may be split into multiple packets. If a PSI 45040 // section starts in this packet, the payload_unit_start_indicator 45041 // will be true and the first byte of the payload will indicate 45042 // the offset from the current position to the start of the 45043 // section. 45044 45045 if (psi.payloadUnitStartIndicator) { 45046 offset += payload[offset] + 1; 45047 } 45048 45049 if (psi.type === 'pat') { 45050 parsePat(payload.subarray(offset), psi); 45051 } else { 45052 parsePmt(payload.subarray(offset), psi); 45053 } 45054 }; 45055 45056 parsePat = function parsePat(payload, pat) { 45057 pat.section_number = payload[7]; // eslint-disable-line camelcase 45058 45059 pat.last_section_number = payload[8]; // eslint-disable-line camelcase 45060 // skip the PSI header and parse the first PMT entry 45061 45062 self.pmtPid = (payload[10] & 0x1F) << 8 | payload[11]; 45063 pat.pmtPid = self.pmtPid; 45064 }; 45065 /** 45066 * Parse out the relevant fields of a Program Map Table (PMT). 45067 * @param payload {Uint8Array} the PMT-specific portion of an MP2T 45068 * packet. The first byte in this array should be the table_id 45069 * field. 45070 * @param pmt {object} the object that should be decorated with 45071 * fields parsed from the PMT. 45072 */ 45073 45074 45075 parsePmt = function parsePmt(payload, pmt) { 45076 var sectionLength, tableEnd, programInfoLength, offset; // PMTs can be sent ahead of the time when they should actually 45077 // take effect. We don't believe this should ever be the case 45078 // for HLS but we'll ignore "forward" PMT declarations if we see 45079 // them. Future PMT declarations have the current_next_indicator 45080 // set to zero. 45081 45082 if (!(payload[5] & 0x01)) { 45083 return; 45084 } // overwrite any existing program map table 45085 45086 45087 self.programMapTable = { 45088 video: null, 45089 audio: null, 45090 'timed-metadata': {} 45091 }; // the mapping table ends at the end of the current section 45092 45093 sectionLength = (payload[1] & 0x0f) << 8 | payload[2]; 45094 tableEnd = 3 + sectionLength - 4; // to determine where the table is, we have to figure out how 45095 // long the program info descriptors are 45096 45097 programInfoLength = (payload[10] & 0x0f) << 8 | payload[11]; // advance the offset to the first entry in the mapping table 45098 45099 offset = 12 + programInfoLength; 45100 45101 while (offset < tableEnd) { 45102 var streamType = payload[offset]; 45103 var pid = (payload[offset + 1] & 0x1F) << 8 | payload[offset + 2]; // only map a single elementary_pid for audio and video stream types 45104 // TODO: should this be done for metadata too? for now maintain behavior of 45105 // multiple metadata streams 45106 45107 if (streamType === streamTypes.H264_STREAM_TYPE && self.programMapTable.video === null) { 45108 self.programMapTable.video = pid; 45109 } else if (streamType === streamTypes.ADTS_STREAM_TYPE && self.programMapTable.audio === null) { 45110 self.programMapTable.audio = pid; 45111 } else if (streamType === streamTypes.METADATA_STREAM_TYPE) { 45112 // map pid to stream type for metadata streams 45113 self.programMapTable['timed-metadata'][pid] = streamType; 45114 } // move to the next table entry 45115 // skip past the elementary stream descriptors, if present 45116 45117 45118 offset += ((payload[offset + 3] & 0x0F) << 8 | payload[offset + 4]) + 5; 45119 } // record the map on the packet as well 45120 45121 45122 pmt.programMapTable = self.programMapTable; 45123 }; 45124 /** 45125 * Deliver a new MP2T packet to the next stream in the pipeline. 45126 */ 45127 45128 45129 this.push = function (packet) { 45130 var result = {}, 45131 offset = 4; 45132 result.payloadUnitStartIndicator = !!(packet[1] & 0x40); // pid is a 13-bit field starting at the last bit of packet[1] 45133 45134 result.pid = packet[1] & 0x1f; 45135 result.pid <<= 8; 45136 result.pid |= packet[2]; // if an adaption field is present, its length is specified by the 45137 // fifth byte of the TS packet header. The adaptation field is 45138 // used to add stuffing to PES packets that don't fill a complete 45139 // TS packet, and to specify some forms of timing and control data 45140 // that we do not currently use. 45141 45142 if ((packet[3] & 0x30) >>> 4 > 0x01) { 45143 offset += packet[offset] + 1; 45144 } // parse the rest of the packet based on the type 45145 45146 45147 if (result.pid === 0) { 45148 result.type = 'pat'; 45149 parsePsi(packet.subarray(offset), result); 45150 this.trigger('data', result); 45151 } else if (result.pid === this.pmtPid) { 45152 result.type = 'pmt'; 45153 parsePsi(packet.subarray(offset), result); 45154 this.trigger('data', result); // if there are any packets waiting for a PMT to be found, process them now 45155 45156 while (this.packetsWaitingForPmt.length) { 45157 this.processPes_.apply(this, this.packetsWaitingForPmt.shift()); 45158 } 45159 } else if (this.programMapTable === undefined) { 45160 // When we have not seen a PMT yet, defer further processing of 45161 // PES packets until one has been parsed 45162 this.packetsWaitingForPmt.push([packet, offset, result]); 45163 } else { 45164 this.processPes_(packet, offset, result); 45165 } 45166 }; 45167 45168 this.processPes_ = function (packet, offset, result) { 45169 // set the appropriate stream type 45170 if (result.pid === this.programMapTable.video) { 45171 result.streamType = streamTypes.H264_STREAM_TYPE; 45172 } else if (result.pid === this.programMapTable.audio) { 45173 result.streamType = streamTypes.ADTS_STREAM_TYPE; 45174 } else { 45175 // if not video or audio, it is timed-metadata or unknown 45176 // if unknown, streamType will be undefined 45177 result.streamType = this.programMapTable['timed-metadata'][result.pid]; 45178 } 45179 45180 result.type = 'pes'; 45181 result.data = packet.subarray(offset); 45182 this.trigger('data', result); 45183 }; 45184 }; 45185 45186 _TransportParseStream.prototype = new stream(); 45187 _TransportParseStream.STREAM_TYPES = { 45188 h264: 0x1b, 45189 adts: 0x0f 45190 }; 45191 /** 45192 * Reconsistutes program elementary stream (PES) packets from parsed 45193 * transport stream packets. That is, if you pipe an 45194 * mp2t.TransportParseStream into a mp2t.ElementaryStream, the output 45195 * events will be events which capture the bytes for individual PES 45196 * packets plus relevant metadata that has been extracted from the 45197 * container. 45198 */ 45199 45200 _ElementaryStream = function ElementaryStream() { 45201 var self = this, 45202 // PES packet fragments 45203 video = { 45204 data: [], 45205 size: 0 45206 }, 45207 audio = { 45208 data: [], 45209 size: 0 45210 }, 45211 timedMetadata = { 45212 data: [], 45213 size: 0 45214 }, 45215 parsePes = function parsePes(payload, pes) { 45216 var ptsDtsFlags; // get the packet length, this will be 0 for video 45217 45218 pes.packetLength = 6 + (payload[4] << 8 | payload[5]); // find out if this packets starts a new keyframe 45219 45220 pes.dataAlignmentIndicator = (payload[6] & 0x04) !== 0; // PES packets may be annotated with a PTS value, or a PTS value 45221 // and a DTS value. Determine what combination of values is 45222 // available to work with. 45223 45224 ptsDtsFlags = payload[7]; // PTS and DTS are normally stored as a 33-bit number. Javascript 45225 // performs all bitwise operations on 32-bit integers but javascript 45226 // supports a much greater range (52-bits) of integer using standard 45227 // mathematical operations. 45228 // We construct a 31-bit value using bitwise operators over the 31 45229 // most significant bits and then multiply by 4 (equal to a left-shift 45230 // of 2) before we add the final 2 least significant bits of the 45231 // timestamp (equal to an OR.) 45232 45233 if (ptsDtsFlags & 0xC0) { 45234 // the PTS and DTS are not written out directly. For information 45235 // on how they are encoded, see 45236 // http://dvd.sourceforge.net/dvdinfo/pes-hdr.html 45237 pes.pts = (payload[9] & 0x0E) << 27 | (payload[10] & 0xFF) << 20 | (payload[11] & 0xFE) << 12 | (payload[12] & 0xFF) << 5 | (payload[13] & 0xFE) >>> 3; 45238 pes.pts *= 4; // Left shift by 2 45239 45240 pes.pts += (payload[13] & 0x06) >>> 1; // OR by the two LSBs 45241 45242 pes.dts = pes.pts; 45243 45244 if (ptsDtsFlags & 0x40) { 45245 pes.dts = (payload[14] & 0x0E) << 27 | (payload[15] & 0xFF) << 20 | (payload[16] & 0xFE) << 12 | (payload[17] & 0xFF) << 5 | (payload[18] & 0xFE) >>> 3; 45246 pes.dts *= 4; // Left shift by 2 45247 45248 pes.dts += (payload[18] & 0x06) >>> 1; // OR by the two LSBs 45249 } 45250 } // the data section starts immediately after the PES header. 45251 // pes_header_data_length specifies the number of header bytes 45252 // that follow the last byte of the field. 45253 45254 45255 pes.data = payload.subarray(9 + payload[8]); 45256 }, 45257 45258 /** 45259 * Pass completely parsed PES packets to the next stream in the pipeline 45260 **/ 45261 flushStream = function flushStream(stream$$1, type, forceFlush) { 45262 var packetData = new Uint8Array(stream$$1.size), 45263 event = { 45264 type: type 45265 }, 45266 i = 0, 45267 offset = 0, 45268 packetFlushable = false, 45269 fragment; // do nothing if there is not enough buffered data for a complete 45270 // PES header 45271 45272 if (!stream$$1.data.length || stream$$1.size < 9) { 45273 return; 45274 } 45275 45276 event.trackId = stream$$1.data[0].pid; // reassemble the packet 45277 45278 for (i = 0; i < stream$$1.data.length; i++) { 45279 fragment = stream$$1.data[i]; 45280 packetData.set(fragment.data, offset); 45281 offset += fragment.data.byteLength; 45282 } // parse assembled packet's PES header 45283 45284 45285 parsePes(packetData, event); // non-video PES packets MUST have a non-zero PES_packet_length 45286 // check that there is enough stream data to fill the packet 45287 45288 packetFlushable = type === 'video' || event.packetLength <= stream$$1.size; // flush pending packets if the conditions are right 45289 45290 if (forceFlush || packetFlushable) { 45291 stream$$1.size = 0; 45292 stream$$1.data.length = 0; 45293 } // only emit packets that are complete. this is to avoid assembling 45294 // incomplete PES packets due to poor segmentation 45295 45296 45297 if (packetFlushable) { 45298 self.trigger('data', event); 45299 } 45300 }; 45301 45302 _ElementaryStream.prototype.init.call(this); 45303 /** 45304 * Identifies M2TS packet types and parses PES packets using metadata 45305 * parsed from the PMT 45306 **/ 45307 45308 45309 this.push = function (data) { 45310 ({ 45311 pat: function pat() {// we have to wait for the PMT to arrive as well before we 45312 // have any meaningful metadata 45313 }, 45314 pes: function pes() { 45315 var stream$$1, streamType; 45316 45317 switch (data.streamType) { 45318 case streamTypes.H264_STREAM_TYPE: 45319 case streamTypes.H264_STREAM_TYPE: 45320 stream$$1 = video; 45321 streamType = 'video'; 45322 break; 45323 45324 case streamTypes.ADTS_STREAM_TYPE: 45325 stream$$1 = audio; 45326 streamType = 'audio'; 45327 break; 45328 45329 case streamTypes.METADATA_STREAM_TYPE: 45330 stream$$1 = timedMetadata; 45331 streamType = 'timed-metadata'; 45332 break; 45333 45334 default: 45335 // ignore unknown stream types 45336 return; 45337 } // if a new packet is starting, we can flush the completed 45338 // packet 45339 45340 45341 if (data.payloadUnitStartIndicator) { 45342 flushStream(stream$$1, streamType, true); 45343 } // buffer this fragment until we are sure we've received the 45344 // complete payload 45345 45346 45347 stream$$1.data.push(data); 45348 stream$$1.size += data.data.byteLength; 45349 }, 45350 pmt: function pmt() { 45351 var event = { 45352 type: 'metadata', 45353 tracks: [] 45354 }, 45355 programMapTable = data.programMapTable; // translate audio and video streams to tracks 45356 45357 if (programMapTable.video !== null) { 45358 event.tracks.push({ 45359 timelineStartInfo: { 45360 baseMediaDecodeTime: 0 45361 }, 45362 id: +programMapTable.video, 45363 codec: 'avc', 45364 type: 'video' 45365 }); 45366 } 45367 45368 if (programMapTable.audio !== null) { 45369 event.tracks.push({ 45370 timelineStartInfo: { 45371 baseMediaDecodeTime: 0 45372 }, 45373 id: +programMapTable.audio, 45374 codec: 'adts', 45375 type: 'audio' 45376 }); 45377 } 45378 45379 self.trigger('data', event); 45380 } 45381 })[data.type](); 45382 }; 45383 /** 45384 * Flush any remaining input. Video PES packets may be of variable 45385 * length. Normally, the start of a new video packet can trigger the 45386 * finalization of the previous packet. That is not possible if no 45387 * more video is forthcoming, however. In that case, some other 45388 * mechanism (like the end of the file) has to be employed. When it is 45389 * clear that no additional data is forthcoming, calling this method 45390 * will flush the buffered packets. 45391 */ 45392 45393 45394 this.flush = function () { 45395 // !!THIS ORDER IS IMPORTANT!! 45396 // video first then audio 45397 flushStream(video, 'video'); 45398 flushStream(audio, 'audio'); 45399 flushStream(timedMetadata, 'timed-metadata'); 45400 this.trigger('done'); 45401 }; 45402 }; 45403 45404 _ElementaryStream.prototype = new stream(); 45405 var m2ts = { 45406 PAT_PID: 0x0000, 45407 MP2T_PACKET_LENGTH: MP2T_PACKET_LENGTH, 45408 TransportPacketStream: _TransportPacketStream, 45409 TransportParseStream: _TransportParseStream, 45410 ElementaryStream: _ElementaryStream, 45411 TimestampRolloverStream: TimestampRolloverStream$1, 45412 CaptionStream: captionStream.CaptionStream, 45413 Cea608Stream: captionStream.Cea608Stream, 45414 MetadataStream: metadataStream 45415 }; 45416 45417 for (var type in streamTypes) { 45418 if (streamTypes.hasOwnProperty(type)) { 45419 m2ts[type] = streamTypes[type]; 45420 } 45421 } 45422 45423 var m2ts_1 = m2ts; 45424 45425 var _AdtsStream; 45426 45427 var ADTS_SAMPLING_FREQUENCIES = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350]; 45428 /* 45429 * Accepts a ElementaryStream and emits data events with parsed 45430 * AAC Audio Frames of the individual packets. Input audio in ADTS 45431 * format is unpacked and re-emitted as AAC frames. 45432 * 45433 * @see http://wiki.multimedia.cx/index.php?title=ADTS 45434 * @see http://wiki.multimedia.cx/?title=Understanding_AAC 45435 */ 45436 45437 _AdtsStream = function AdtsStream() { 45438 var buffer; 45439 45440 _AdtsStream.prototype.init.call(this); 45441 45442 this.push = function (packet) { 45443 var i = 0, 45444 frameNum = 0, 45445 frameLength, 45446 protectionSkipBytes, 45447 frameEnd, 45448 oldBuffer, 45449 sampleCount, 45450 adtsFrameDuration; 45451 45452 if (packet.type !== 'audio') { 45453 // ignore non-audio data 45454 return; 45455 } // Prepend any data in the buffer to the input data so that we can parse 45456 // aac frames the cross a PES packet boundary 45457 45458 45459 if (buffer) { 45460 oldBuffer = buffer; 45461 buffer = new Uint8Array(oldBuffer.byteLength + packet.data.byteLength); 45462 buffer.set(oldBuffer); 45463 buffer.set(packet.data, oldBuffer.byteLength); 45464 } else { 45465 buffer = packet.data; 45466 } // unpack any ADTS frames which have been fully received 45467 // for details on the ADTS header, see http://wiki.multimedia.cx/index.php?title=ADTS 45468 45469 45470 while (i + 5 < buffer.length) { 45471 // Loook for the start of an ADTS header.. 45472 if (buffer[i] !== 0xFF || (buffer[i + 1] & 0xF6) !== 0xF0) { 45473 // If a valid header was not found, jump one forward and attempt to 45474 // find a valid ADTS header starting at the next byte 45475 i++; 45476 continue; 45477 } // The protection skip bit tells us if we have 2 bytes of CRC data at the 45478 // end of the ADTS header 45479 45480 45481 protectionSkipBytes = (~buffer[i + 1] & 0x01) * 2; // Frame length is a 13 bit integer starting 16 bits from the 45482 // end of the sync sequence 45483 45484 frameLength = (buffer[i + 3] & 0x03) << 11 | buffer[i + 4] << 3 | (buffer[i + 5] & 0xe0) >> 5; 45485 sampleCount = ((buffer[i + 6] & 0x03) + 1) * 1024; 45486 adtsFrameDuration = sampleCount * 90000 / ADTS_SAMPLING_FREQUENCIES[(buffer[i + 2] & 0x3c) >>> 2]; 45487 frameEnd = i + frameLength; // If we don't have enough data to actually finish this ADTS frame, return 45488 // and wait for more data 45489 45490 if (buffer.byteLength < frameEnd) { 45491 return; 45492 } // Otherwise, deliver the complete AAC frame 45493 45494 45495 this.trigger('data', { 45496 pts: packet.pts + frameNum * adtsFrameDuration, 45497 dts: packet.dts + frameNum * adtsFrameDuration, 45498 sampleCount: sampleCount, 45499 audioobjecttype: (buffer[i + 2] >>> 6 & 0x03) + 1, 45500 channelcount: (buffer[i + 2] & 1) << 2 | (buffer[i + 3] & 0xc0) >>> 6, 45501 samplerate: ADTS_SAMPLING_FREQUENCIES[(buffer[i + 2] & 0x3c) >>> 2], 45502 samplingfrequencyindex: (buffer[i + 2] & 0x3c) >>> 2, 45503 // assume ISO/IEC 14496-12 AudioSampleEntry default of 16 45504 samplesize: 16, 45505 data: buffer.subarray(i + 7 + protectionSkipBytes, frameEnd) 45506 }); // If the buffer is empty, clear it and return 45507 45508 if (buffer.byteLength === frameEnd) { 45509 buffer = undefined; 45510 return; 45511 } 45512 45513 frameNum++; // Remove the finished frame from the buffer and start the process again 45514 45515 buffer = buffer.subarray(frameEnd); 45516 } 45517 }; 45518 45519 this.flush = function () { 45520 this.trigger('done'); 45521 }; 45522 }; 45523 45524 _AdtsStream.prototype = new stream(); 45525 var adts = _AdtsStream; 45526 var ExpGolomb; 45527 /** 45528 * Parser for exponential Golomb codes, a variable-bitwidth number encoding 45529 * scheme used by h264. 45530 */ 45531 45532 ExpGolomb = function ExpGolomb(workingData) { 45533 var // the number of bytes left to examine in workingData 45534 workingBytesAvailable = workingData.byteLength, 45535 // the current word being examined 45536 workingWord = 0, 45537 // :uint 45538 // the number of bits left to examine in the current word 45539 workingBitsAvailable = 0; // :uint; 45540 // ():uint 45541 45542 this.length = function () { 45543 return 8 * workingBytesAvailable; 45544 }; // ():uint 45545 45546 45547 this.bitsAvailable = function () { 45548 return 8 * workingBytesAvailable + workingBitsAvailable; 45549 }; // ():void 45550 45551 45552 this.loadWord = function () { 45553 var position = workingData.byteLength - workingBytesAvailable, 45554 workingBytes = new Uint8Array(4), 45555 availableBytes = Math.min(4, workingBytesAvailable); 45556 45557 if (availableBytes === 0) { 45558 throw new Error('no bytes available'); 45559 } 45560 45561 workingBytes.set(workingData.subarray(position, position + availableBytes)); 45562 workingWord = new DataView(workingBytes.buffer).getUint32(0); // track the amount of workingData that has been processed 45563 45564 workingBitsAvailable = availableBytes * 8; 45565 workingBytesAvailable -= availableBytes; 45566 }; // (count:int):void 45567 45568 45569 this.skipBits = function (count) { 45570 var skipBytes; // :int 45571 45572 if (workingBitsAvailable > count) { 45573 workingWord <<= count; 45574 workingBitsAvailable -= count; 45575 } else { 45576 count -= workingBitsAvailable; 45577 skipBytes = Math.floor(count / 8); 45578 count -= skipBytes * 8; 45579 workingBytesAvailable -= skipBytes; 45580 this.loadWord(); 45581 workingWord <<= count; 45582 workingBitsAvailable -= count; 45583 } 45584 }; // (size:int):uint 45585 45586 45587 this.readBits = function (size) { 45588 var bits = Math.min(workingBitsAvailable, size), 45589 // :uint 45590 valu = workingWord >>> 32 - bits; // :uint 45591 // if size > 31, handle error 45592 45593 workingBitsAvailable -= bits; 45594 45595 if (workingBitsAvailable > 0) { 45596 workingWord <<= bits; 45597 } else if (workingBytesAvailable > 0) { 45598 this.loadWord(); 45599 } 45600 45601 bits = size - bits; 45602 45603 if (bits > 0) { 45604 return valu << bits | this.readBits(bits); 45605 } 45606 45607 return valu; 45608 }; // ():uint 45609 45610 45611 this.skipLeadingZeros = function () { 45612 var leadingZeroCount; // :uint 45613 45614 for (leadingZeroCount = 0; leadingZeroCount < workingBitsAvailable; ++leadingZeroCount) { 45615 if ((workingWord & 0x80000000 >>> leadingZeroCount) !== 0) { 45616 // the first bit of working word is 1 45617 workingWord <<= leadingZeroCount; 45618 workingBitsAvailable -= leadingZeroCount; 45619 return leadingZeroCount; 45620 } 45621 } // we exhausted workingWord and still have not found a 1 45622 45623 45624 this.loadWord(); 45625 return leadingZeroCount + this.skipLeadingZeros(); 45626 }; // ():void 45627 45628 45629 this.skipUnsignedExpGolomb = function () { 45630 this.skipBits(1 + this.skipLeadingZeros()); 45631 }; // ():void 45632 45633 45634 this.skipExpGolomb = function () { 45635 this.skipBits(1 + this.skipLeadingZeros()); 45636 }; // ():uint 45637 45638 45639 this.readUnsignedExpGolomb = function () { 45640 var clz = this.skipLeadingZeros(); // :uint 45641 45642 return this.readBits(clz + 1) - 1; 45643 }; // ():int 45644 45645 45646 this.readExpGolomb = function () { 45647 var valu = this.readUnsignedExpGolomb(); // :int 45648 45649 if (0x01 & valu) { 45650 // the number is odd if the low order bit is set 45651 return 1 + valu >>> 1; // add 1 to make it even, and divide by 2 45652 } 45653 45654 return -1 * (valu >>> 1); // divide by two then make it negative 45655 }; // Some convenience functions 45656 // :Boolean 45657 45658 45659 this.readBoolean = function () { 45660 return this.readBits(1) === 1; 45661 }; // ():int 45662 45663 45664 this.readUnsignedByte = function () { 45665 return this.readBits(8); 45666 }; 45667 45668 this.loadWord(); 45669 }; 45670 45671 var expGolomb = ExpGolomb; 45672 45673 var _H264Stream, _NalByteStream; 45674 45675 var PROFILES_WITH_OPTIONAL_SPS_DATA; 45676 /** 45677 * Accepts a NAL unit byte stream and unpacks the embedded NAL units. 45678 */ 45679 45680 _NalByteStream = function NalByteStream() { 45681 var syncPoint = 0, 45682 i, 45683 buffer; 45684 45685 _NalByteStream.prototype.init.call(this); 45686 /* 45687 * Scans a byte stream and triggers a data event with the NAL units found. 45688 * @param {Object} data Event received from H264Stream 45689 * @param {Uint8Array} data.data The h264 byte stream to be scanned 45690 * 45691 * @see H264Stream.push 45692 */ 45693 45694 45695 this.push = function (data) { 45696 var swapBuffer; 45697 45698 if (!buffer) { 45699 buffer = data.data; 45700 } else { 45701 swapBuffer = new Uint8Array(buffer.byteLength + data.data.byteLength); 45702 swapBuffer.set(buffer); 45703 swapBuffer.set(data.data, buffer.byteLength); 45704 buffer = swapBuffer; 45705 } // Rec. ITU-T H.264, Annex B 45706 // scan for NAL unit boundaries 45707 // a match looks like this: 45708 // 0 0 1 .. NAL .. 0 0 1 45709 // ^ sync point ^ i 45710 // or this: 45711 // 0 0 1 .. NAL .. 0 0 0 45712 // ^ sync point ^ i 45713 // advance the sync point to a NAL start, if necessary 45714 45715 45716 for (; syncPoint < buffer.byteLength - 3; syncPoint++) { 45717 if (buffer[syncPoint + 2] === 1) { 45718 // the sync point is properly aligned 45719 i = syncPoint + 5; 45720 break; 45721 } 45722 } 45723 45724 while (i < buffer.byteLength) { 45725 // look at the current byte to determine if we've hit the end of 45726 // a NAL unit boundary 45727 switch (buffer[i]) { 45728 case 0: 45729 // skip past non-sync sequences 45730 if (buffer[i - 1] !== 0) { 45731 i += 2; 45732 break; 45733 } else if (buffer[i - 2] !== 0) { 45734 i++; 45735 break; 45736 } // deliver the NAL unit if it isn't empty 45737 45738 45739 if (syncPoint + 3 !== i - 2) { 45740 this.trigger('data', buffer.subarray(syncPoint + 3, i - 2)); 45741 } // drop trailing zeroes 45742 45743 45744 do { 45745 i++; 45746 } while (buffer[i] !== 1 && i < buffer.length); 45747 45748 syncPoint = i - 2; 45749 i += 3; 45750 break; 45751 45752 case 1: 45753 // skip past non-sync sequences 45754 if (buffer[i - 1] !== 0 || buffer[i - 2] !== 0) { 45755 i += 3; 45756 break; 45757 } // deliver the NAL unit 45758 45759 45760 this.trigger('data', buffer.subarray(syncPoint + 3, i - 2)); 45761 syncPoint = i - 2; 45762 i += 3; 45763 break; 45764 45765 default: 45766 // the current byte isn't a one or zero, so it cannot be part 45767 // of a sync sequence 45768 i += 3; 45769 break; 45770 } 45771 } // filter out the NAL units that were delivered 45772 45773 45774 buffer = buffer.subarray(syncPoint); 45775 i -= syncPoint; 45776 syncPoint = 0; 45777 }; 45778 45779 this.flush = function () { 45780 // deliver the last buffered NAL unit 45781 if (buffer && buffer.byteLength > 3) { 45782 this.trigger('data', buffer.subarray(syncPoint + 3)); 45783 } // reset the stream state 45784 45785 45786 buffer = null; 45787 syncPoint = 0; 45788 this.trigger('done'); 45789 }; 45790 }; 45791 45792 _NalByteStream.prototype = new stream(); // values of profile_idc that indicate additional fields are included in the SPS 45793 // see Recommendation ITU-T H.264 (4/2013), 45794 // 7.3.2.1.1 Sequence parameter set data syntax 45795 45796 PROFILES_WITH_OPTIONAL_SPS_DATA = { 45797 100: true, 45798 110: true, 45799 122: true, 45800 244: true, 45801 44: true, 45802 83: true, 45803 86: true, 45804 118: true, 45805 128: true, 45806 138: true, 45807 139: true, 45808 134: true 45809 }; 45810 /** 45811 * Accepts input from a ElementaryStream and produces H.264 NAL unit data 45812 * events. 45813 */ 45814 45815 _H264Stream = function H264Stream() { 45816 var nalByteStream = new _NalByteStream(), 45817 self, 45818 trackId,
45819 currentPts, 45820 currentDts, 45821 discardEmulationPreventionBytes, 45822 readSequenceParameterSet, 45823 skipScalingList; 45824 45825 _H264Stream.prototype.init.call(this); 45826 45827 self = this; 45828 /* 45829 * Pushes a packet from a stream onto the NalByteStream 45830 * 45831 * @param {Object} packet - A packet received from a stream 45832 * @param {Uint8Array} packet.data - The raw bytes of the packet 45833 * @param {Number} packet.dts - Decode timestamp of the packet 45834 * @param {Number} packet.pts - Presentation timestamp of the packet 45835 * @param {Number} packet.trackId - The id of the h264 track this packet came from 45836 * @param {('video'|'audio')} packet.type - The type of packet 45837 * 45838 */ 45839 45840 this.push = function (packet) { 45841 if (packet.type !== 'video') { 45842 return; 45843 } 45844 45845 trackId = packet.trackId; 45846 currentPts = packet.pts; 45847 currentDts = packet.dts; 45848 nalByteStream.push(packet); 45849 }; 45850 /* 45851 * Identify NAL unit types and pass on the NALU, trackId, presentation and decode timestamps 45852 * for the NALUs to the next stream component. 45853 * Also, preprocess caption and sequence parameter NALUs. 45854 * 45855 * @param {Uint8Array} data - A NAL unit identified by `NalByteStream.push` 45856 * @see NalByteStream.push 45857 */ 45858 45859 45860 nalByteStream.on('data', function (data) { 45861 var event = { 45862 trackId: trackId,
vendor: 11,634 bytes, lines 45863-46210
45863 pts: currentPts, 45864 dts: currentDts, 45865 data: data 45866 }; 45867 45868 switch (data[0] & 0x1f) { 45869 case 0x05: 45870 event.nalUnitType = 'slice_layer_without_partitioning_rbsp_idr'; 45871 break; 45872 45873 case 0x06: 45874 event.nalUnitType = 'sei_rbsp'; 45875 event.escapedRBSP = discardEmulationPreventionBytes(data.subarray(1)); 45876 break; 45877 45878 case 0x07: 45879 event.nalUnitType = 'seq_parameter_set_rbsp'; 45880 event.escapedRBSP = discardEmulationPreventionBytes(data.subarray(1)); 45881 event.config = readSequenceParameterSet(event.escapedRBSP); 45882 break; 45883 45884 case 0x08: 45885 event.nalUnitType = 'pic_parameter_set_rbsp'; 45886 break; 45887 45888 case 0x09: 45889 event.nalUnitType = 'access_unit_delimiter_rbsp'; 45890 break; 45891 45892 default: 45893 break; 45894 } // This triggers data on the H264Stream 45895 45896 45897 self.trigger('data', event); 45898 }); 45899 nalByteStream.on('done', function () { 45900 self.trigger('done'); 45901 }); 45902 45903 this.flush = function () { 45904 nalByteStream.flush(); 45905 }; 45906 /** 45907 * Advance the ExpGolomb decoder past a scaling list. The scaling 45908 * list is optionally transmitted as part of a sequence parameter 45909 * set and is not relevant to transmuxing. 45910 * @param count {number} the number of entries in this scaling list 45911 * @param expGolombDecoder {object} an ExpGolomb pointed to the 45912 * start of a scaling list 45913 * @see Recommendation ITU-T H.264, Section 7.3.2.1.1.1 45914 */ 45915 45916 45917 skipScalingList = function skipScalingList(count, expGolombDecoder) { 45918 var lastScale = 8, 45919 nextScale = 8, 45920 j, 45921 deltaScale; 45922 45923 for (j = 0; j < count; j++) { 45924 if (nextScale !== 0) { 45925 deltaScale = expGolombDecoder.readExpGolomb(); 45926 nextScale = (lastScale + deltaScale + 256) % 256; 45927 } 45928 45929 lastScale = nextScale === 0 ? lastScale : nextScale; 45930 } 45931 }; 45932 /** 45933 * Expunge any "Emulation Prevention" bytes from a "Raw Byte 45934 * Sequence Payload" 45935 * @param data {Uint8Array} the bytes of a RBSP from a NAL 45936 * unit 45937 * @return {Uint8Array} the RBSP without any Emulation 45938 * Prevention Bytes 45939 */ 45940 45941 45942 discardEmulationPreventionBytes = function discardEmulationPreventionBytes(data) { 45943 var length = data.byteLength, 45944 emulationPreventionBytesPositions = [], 45945 i = 1, 45946 newLength, 45947 newData; // Find all `Emulation Prevention Bytes` 45948 45949 while (i < length - 2) { 45950 if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0x03) { 45951 emulationPreventionBytesPositions.push(i + 2); 45952 i += 2; 45953 } else { 45954 i++; 45955 } 45956 } // If no Emulation Prevention Bytes were found just return the original 45957 // array 45958 45959 45960 if (emulationPreventionBytesPositions.length === 0) { 45961 return data; 45962 } // Create a new array to hold the NAL unit data 45963 45964 45965 newLength = length - emulationPreventionBytesPositions.length; 45966 newData = new Uint8Array(newLength); 45967 var sourceIndex = 0; 45968 45969 for (i = 0; i < newLength; sourceIndex++, i++) { 45970 if (sourceIndex === emulationPreventionBytesPositions[0]) { 45971 // Skip this byte 45972 sourceIndex++; // Remove this position index 45973 45974 emulationPreventionBytesPositions.shift(); 45975 } 45976 45977 newData[i] = data[sourceIndex]; 45978 } 45979 45980 return newData; 45981 }; 45982 /** 45983 * Read a sequence parameter set and return some interesting video 45984 * properties. A sequence parameter set is the H264 metadata that 45985 * describes the properties of upcoming video frames. 45986 * @param data {Uint8Array} the bytes of a sequence parameter set 45987 * @return {object} an object with configuration parsed from the 45988 * sequence parameter set, including the dimensions of the 45989 * associated video frames. 45990 */ 45991 45992 45993 readSequenceParameterSet = function readSequenceParameterSet(data) { 45994 var frameCropLeftOffset = 0, 45995 frameCropRightOffset = 0, 45996 frameCropTopOffset = 0, 45997 frameCropBottomOffset = 0, 45998 sarScale = 1, 45999 expGolombDecoder, 46000 profileIdc, 46001 levelIdc, 46002 profileCompatibility, 46003 chromaFormatIdc, 46004 picOrderCntType, 46005 numRefFramesInPicOrderCntCycle, 46006 picWidthInMbsMinus1, 46007 picHeightInMapUnitsMinus1, 46008 frameMbsOnlyFlag, 46009 scalingListCount, 46010 sarRatio, 46011 aspectRatioIdc, 46012 i; 46013 expGolombDecoder = new expGolomb(data); 46014 profileIdc = expGolombDecoder.readUnsignedByte(); // profile_idc 46015 46016 profileCompatibility = expGolombDecoder.readUnsignedByte(); // constraint_set[0-5]_flag 46017 46018 levelIdc = expGolombDecoder.readUnsignedByte(); // level_idc u(8) 46019 46020 expGolombDecoder.skipUnsignedExpGolomb(); // seq_parameter_set_id 46021 // some profiles have more optional data we don't need 46022 46023 if (PROFILES_WITH_OPTIONAL_SPS_DATA[profileIdc]) { 46024 chromaFormatIdc = expGolombDecoder.readUnsignedExpGolomb(); 46025 46026 if (chromaFormatIdc === 3) { 46027 expGolombDecoder.skipBits(1); // separate_colour_plane_flag 46028 } 46029 46030 expGolombDecoder.skipUnsignedExpGolomb(); // bit_depth_luma_minus8 46031 46032 expGolombDecoder.skipUnsignedExpGolomb(); // bit_depth_chroma_minus8 46033 46034 expGolombDecoder.skipBits(1); // qpprime_y_zero_transform_bypass_flag 46035 46036 if (expGolombDecoder.readBoolean()) { 46037 // seq_scaling_matrix_present_flag 46038 scalingListCount = chromaFormatIdc !== 3 ? 8 : 12; 46039 46040 for (i = 0; i < scalingListCount; i++) { 46041 if (expGolombDecoder.readBoolean()) { 46042 // seq_scaling_list_present_flag[ i ] 46043 if (i < 6) { 46044 skipScalingList(16, expGolombDecoder); 46045 } else { 46046 skipScalingList(64, expGolombDecoder); 46047 } 46048 } 46049 } 46050 } 46051 } 46052 46053 expGolombDecoder.skipUnsignedExpGolomb(); // log2_max_frame_num_minus4 46054 46055 picOrderCntType = expGolombDecoder.readUnsignedExpGolomb(); 46056 46057 if (picOrderCntType === 0) { 46058 expGolombDecoder.readUnsignedExpGolomb(); // log2_max_pic_order_cnt_lsb_minus4 46059 } else if (picOrderCntType === 1) { 46060 expGolombDecoder.skipBits(1); // delta_pic_order_always_zero_flag 46061 46062 expGolombDecoder.skipExpGolomb(); // offset_for_non_ref_pic 46063 46064 expGolombDecoder.skipExpGolomb(); // offset_for_top_to_bottom_field 46065 46066 numRefFramesInPicOrderCntCycle = expGolombDecoder.readUnsignedExpGolomb(); 46067 46068 for (i = 0; i < numRefFramesInPicOrderCntCycle; i++) { 46069 expGolombDecoder.skipExpGolomb(); // offset_for_ref_frame[ i ] 46070 } 46071 } 46072 46073 expGolombDecoder.skipUnsignedExpGolomb(); // max_num_ref_frames 46074 46075 expGolombDecoder.skipBits(1); // gaps_in_frame_num_value_allowed_flag 46076 46077 picWidthInMbsMinus1 = expGolombDecoder.readUnsignedExpGolomb(); 46078 picHeightInMapUnitsMinus1 = expGolombDecoder.readUnsignedExpGolomb(); 46079 frameMbsOnlyFlag = expGolombDecoder.readBits(1); 46080 46081 if (frameMbsOnlyFlag === 0) { 46082 expGolombDecoder.skipBits(1); // mb_adaptive_frame_field_flag 46083 } 46084 46085 expGolombDecoder.skipBits(1); // direct_8x8_inference_flag 46086 46087 if (expGolombDecoder.readBoolean()) { 46088 // frame_cropping_flag 46089 frameCropLeftOffset = expGolombDecoder.readUnsignedExpGolomb(); 46090 frameCropRightOffset = expGolombDecoder.readUnsignedExpGolomb(); 46091 frameCropTopOffset = expGolombDecoder.readUnsignedExpGolomb(); 46092 frameCropBottomOffset = expGolombDecoder.readUnsignedExpGolomb(); 46093 } 46094 46095 if (expGolombDecoder.readBoolean()) { 46096 // vui_parameters_present_flag 46097 if (expGolombDecoder.readBoolean()) { 46098 // aspect_ratio_info_present_flag 46099 aspectRatioIdc = expGolombDecoder.readUnsignedByte(); 46100 46101 switch (aspectRatioIdc) { 46102 case 1: 46103 sarRatio = [1, 1]; 46104 break; 46105 46106 case 2: 46107 sarRatio = [12, 11]; 46108 break; 46109 46110 case 3: 46111 sarRatio = [10, 11]; 46112 break; 46113 46114 case 4: 46115 sarRatio = [16, 11]; 46116 break; 46117 46118 case 5: 46119 sarRatio = [40, 33]; 46120 break; 46121 46122 case 6: 46123 sarRatio = [24, 11]; 46124 break; 46125 46126 case 7: 46127 sarRatio = [20, 11]; 46128 break; 46129 46130 case 8: 46131 sarRatio = [32, 11]; 46132 break; 46133 46134 case 9: 46135 sarRatio = [80, 33]; 46136 break; 46137 46138 case 10: 46139 sarRatio = [18, 11]; 46140 break; 46141 46142 case 11: 46143 sarRatio = [15, 11]; 46144 break; 46145 46146 case 12: 46147 sarRatio = [64, 33]; 46148 break; 46149 46150 case 13: 46151 sarRatio = [160, 99]; 46152 break; 46153 46154 case 14: 46155 sarRatio = [4, 3]; 46156 break; 46157 46158 case 15: 46159 sarRatio = [3, 2]; 46160 break; 46161 46162 case 16: 46163 sarRatio = [2, 1]; 46164 break; 46165 46166 case 255: 46167 { 46168 sarRatio = [expGolombDecoder.readUnsignedByte() << 8 | expGolombDecoder.readUnsignedByte(), expGolombDecoder.readUnsignedByte() << 8 | expGolombDecoder.readUnsignedByte()]; 46169 break; 46170 } 46171 } 46172 46173 if (sarRatio) { 46174 sarScale = sarRatio[0] / sarRatio[1]; 46175 } 46176 } 46177 } 46178 46179 return { 46180 profileIdc: profileIdc, 46181 levelIdc: levelIdc, 46182 profileCompatibility: profileCompatibility, 46183 width: Math.ceil(((picWidthInMbsMinus1 + 1) * 16 - frameCropLeftOffset * 2 - frameCropRightOffset * 2) * sarScale), 46184 height: (2 - frameMbsOnlyFlag) * (picHeightInMapUnitsMinus1 + 1) * 16 - frameCropTopOffset * 2 - frameCropBottomOffset * 2 46185 }; 46186 }; 46187 }; 46188 46189 _H264Stream.prototype = new stream(); 46190 var h264 = { 46191 H264Stream: _H264Stream, 46192 NalByteStream: _NalByteStream 46193 }; 46194 /** 46195 * mux.js 46196 * 46197 * Copyright (c) 2016 Brightcove 46198 * All rights reserved. 46199 * 46200 * Utilities to detect basic properties and metadata about Aac data. 46201 */ 46202 46203 var ADTS_SAMPLING_FREQUENCIES$1 = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350]; 46204 46205 var isLikelyAacData = function isLikelyAacData(data) { 46206 if (data[0] === 'I'.charCodeAt(0) && data[1] === 'D'.charCodeAt(0) && data[2] === '3'.charCodeAt(0)) { 46207 return true; 46208 } 46209 46210 return false;
vendor: 7,306 bytes, lines 46211-46417
46211 }; 46212 46213 var parseSyncSafeInteger$1 = function parseSyncSafeInteger(data) { 46214 return data[0] << 21 | data[1] << 14 | data[2] << 7 | data[3]; 46215 }; // return a percent-encoded representation of the specified byte range 46216 // @see http://en.wikipedia.org/wiki/Percent-encoding 46217 46218 46219 var percentEncode$1 = function percentEncode(bytes, start, end) { 46220 var i, 46221 result = ''; 46222 46223 for (i = start; i < end; i++) { 46224 result += '%' + ('00' + bytes[i].toString(16)).slice(-2); 46225 } 46226 46227 return result; 46228 }; // return the string representation of the specified byte range, 46229 // interpreted as ISO-8859-1. 46230 46231 46232 var parseIso88591$1 = function parseIso88591(bytes, start, end) { 46233 return unescape(percentEncode$1(bytes, start, end)); // jshint ignore:line 46234 }; 46235 46236 var parseId3TagSize = function parseId3TagSize(header, byteIndex) { 46237 var returnSize = header[byteIndex + 6] << 21 | header[byteIndex + 7] << 14 | header[byteIndex + 8] << 7 | header[byteIndex + 9], 46238 flags = header[byteIndex + 5], 46239 footerPresent = (flags & 16) >> 4; 46240 46241 if (footerPresent) { 46242 return returnSize + 20; 46243 } 46244 46245 return returnSize + 10; 46246 }; 46247 46248 var parseAdtsSize = function parseAdtsSize(header, byteIndex) { 46249 var lowThree = (header[byteIndex + 5] & 0xE0) >> 5, 46250 middle = header[byteIndex + 4] << 3, 46251 highTwo = header[byteIndex + 3] & 0x3 << 11; 46252 return highTwo | middle | lowThree; 46253 }; 46254 46255 var parseType$1 = function parseType(header, byteIndex) { 46256 if (header[byteIndex] === 'I'.charCodeAt(0) && header[byteIndex + 1] === 'D'.charCodeAt(0) && header[byteIndex + 2] === '3'.charCodeAt(0)) { 46257 return 'timed-metadata'; 46258 } else if (header[byteIndex] & 0xff === 0xff && (header[byteIndex + 1] & 0xf0) === 0xf0) { 46259 return 'audio'; 46260 } 46261 46262 return null; 46263 }; 46264 46265 var parseSampleRate = function parseSampleRate(packet) { 46266 var i = 0; 46267 46268 while (i + 5 < packet.length) { 46269 if (packet[i] !== 0xFF || (packet[i + 1] & 0xF6) !== 0xF0) { 46270 // If a valid header was not found, jump one forward and attempt to 46271 // find a valid ADTS header starting at the next byte 46272 i++; 46273 continue; 46274 } 46275 46276 return ADTS_SAMPLING_FREQUENCIES$1[(packet[i + 2] & 0x3c) >>> 2]; 46277 } 46278 46279 return null; 46280 }; 46281 46282 var parseAacTimestamp = function parseAacTimestamp(packet) { 46283 var frameStart, frameSize, frame, frameHeader; // find the start of the first frame and the end of the tag 46284 46285 frameStart = 10; 46286 46287 if (packet[5] & 0x40) { 46288 // advance the frame start past the extended header 46289 frameStart += 4; // header size field 46290 46291 frameStart += parseSyncSafeInteger$1(packet.subarray(10, 14)); 46292 } // parse one or more ID3 frames 46293 // http://id3.org/id3v2.3.0#ID3v2_frame_overview 46294 46295 46296 do { 46297 // determine the number of bytes in this frame 46298 frameSize = parseSyncSafeInteger$1(packet.subarray(frameStart + 4, frameStart + 8)); 46299 46300 if (frameSize < 1) { 46301 return null; 46302 } 46303 46304 frameHeader = String.fromCharCode(packet[frameStart], packet[frameStart + 1], packet[frameStart + 2], packet[frameStart + 3]); 46305 46306 if (frameHeader === 'PRIV') { 46307 frame = packet.subarray(frameStart + 10, frameStart + frameSize + 10); 46308 46309 for (var i = 0; i < frame.byteLength; i++) { 46310 if (frame[i] === 0) { 46311 var owner = parseIso88591$1(frame, 0, i); 46312 46313 if (owner === 'com.apple.streaming.transportStreamTimestamp') { 46314 var d = frame.subarray(i + 1); 46315 var size = (d[3] & 0x01) << 30 | d[4] << 22 | d[5] << 14 | d[6] << 6 | d[7] >>> 2; 46316 size *= 4; 46317 size += d[7] & 0x03; 46318 return size; 46319 } 46320 46321 break; 46322 } 46323 } 46324 } 46325 46326 frameStart += 10; // advance past the frame header 46327 46328 frameStart += frameSize; // advance past the frame body 46329 } while (frameStart < packet.byteLength); 46330 46331 return null; 46332 }; 46333 46334 var utils = { 46335 isLikelyAacData: isLikelyAacData, 46336 parseId3TagSize: parseId3TagSize, 46337 parseAdtsSize: parseAdtsSize, 46338 parseType: parseType$1, 46339 parseSampleRate: parseSampleRate, 46340 parseAacTimestamp: parseAacTimestamp 46341 }; // Constants 46342 46343 var _AacStream; 46344 /** 46345 * Splits an incoming stream of binary data into ADTS and ID3 Frames. 46346 */ 46347 46348 46349 _AacStream = function AacStream() { 46350 var everything = new Uint8Array(), 46351 timeStamp = 0; 46352 46353 _AacStream.prototype.init.call(this); 46354 46355 this.setTimestamp = function (timestamp) { 46356 timeStamp = timestamp; 46357 }; 46358 46359 this.push = function (bytes) { 46360 var frameSize = 0, 46361 byteIndex = 0, 46362 bytesLeft, 46363 chunk, 46364 packet, 46365 tempLength; // If there are bytes remaining from the last segment, prepend them to the 46366 // bytes that were pushed in 46367 46368 if (everything.length) { 46369 tempLength = everything.length; 46370 everything = new Uint8Array(bytes.byteLength + tempLength); 46371 everything.set(everything.subarray(0, tempLength)); 46372 everything.set(bytes, tempLength); 46373 } else { 46374 everything = bytes; 46375 } 46376 46377 while (everything.length - byteIndex >= 3) { 46378 if (everything[byteIndex] === 'I'.charCodeAt(0) && everything[byteIndex + 1] === 'D'.charCodeAt(0) && everything[byteIndex + 2] === '3'.charCodeAt(0)) { 46379 // Exit early because we don't have enough to parse 46380 // the ID3 tag header 46381 if (everything.length - byteIndex < 10) { 46382 break; 46383 } // check framesize 46384 46385 46386 frameSize = utils.parseId3TagSize(everything, byteIndex); // Exit early if we don't have enough in the buffer 46387 // to emit a full packet 46388 // Add to byteIndex to support multiple ID3 tags in sequence 46389 46390 if (byteIndex + frameSize > everything.length) { 46391 break; 46392 } 46393 46394 chunk = { 46395 type: 'timed-metadata', 46396 data: everything.subarray(byteIndex, byteIndex + frameSize) 46397 }; 46398 this.trigger('data', chunk); 46399 byteIndex += frameSize; 46400 continue; 46401 } else if ((everything[byteIndex] & 0xff) === 0xff && (everything[byteIndex + 1] & 0xf0) === 0xf0) { 46402 // Exit early because we don't have enough to parse 46403 // the ADTS frame header 46404 if (everything.length - byteIndex < 7) { 46405 break; 46406 } 46407 46408 frameSize = utils.parseAdtsSize(everything, byteIndex); // Exit early if we don't have enough in the buffer 46409 // to emit a full packet 46410 46411 if (byteIndex + frameSize > everything.length) { 46412 break; 46413 } 46414 46415 packet = { 46416 type: 'audio', 46417 data: everything.subarray(byteIndex, byteIndex + frameSize),
vendor: 6,886 bytes, lines 46418-46599
46418 pts: timeStamp, 46419 dts: timeStamp 46420 }; 46421 this.trigger('data', packet); 46422 byteIndex += frameSize; 46423 continue; 46424 } 46425 46426 byteIndex++; 46427 } 46428 46429 bytesLeft = everything.length - byteIndex; 46430 46431 if (bytesLeft > 0) { 46432 everything = everything.subarray(byteIndex); 46433 } else { 46434 everything = new Uint8Array(); 46435 } 46436 }; 46437 }; 46438 46439 _AacStream.prototype = new stream(); 46440 var aac = _AacStream; 46441 var H264Stream = h264.H264Stream; 46442 var isLikelyAacData$1 = utils.isLikelyAacData; // constants 46443 46444 var AUDIO_PROPERTIES = ['audioobjecttype', 'channelcount', 'samplerate', 'samplingfrequencyindex', 'samplesize']; 46445 var VIDEO_PROPERTIES = ['width', 'height', 'profileIdc', 'levelIdc', 'profileCompatibility']; // object types 46446 46447 var _VideoSegmentStream, _AudioSegmentStream, _Transmuxer, _CoalesceStream; 46448 /** 46449 * Compare two arrays (even typed) for same-ness 46450 */ 46451 46452 46453 var arrayEquals = function arrayEquals(a, b) { 46454 var i; 46455 46456 if (a.length !== b.length) { 46457 return false; 46458 } // compare the value of each element in the array 46459 46460 46461 for (i = 0; i < a.length; i++) { 46462 if (a[i] !== b[i]) { 46463 return false; 46464 } 46465 } 46466 46467 return true; 46468 }; 46469 46470 var generateVideoSegmentTimingInfo = function generateVideoSegmentTimingInfo(baseMediaDecodeTime, startDts, startPts, endDts, endPts, prependedContentDuration) { 46471 var ptsOffsetFromDts = startPts - startDts, 46472 decodeDuration = endDts - startDts, 46473 presentationDuration = endPts - startPts; // The PTS and DTS values are based on the actual stream times from the segment, 46474 // however, the player time values will reflect a start from the baseMediaDecodeTime. 46475 // In order to provide relevant values for the player times, base timing info on the 46476 // baseMediaDecodeTime and the DTS and PTS durations of the segment. 46477 46478 return { 46479 start: { 46480 dts: baseMediaDecodeTime, 46481 pts: baseMediaDecodeTime + ptsOffsetFromDts 46482 }, 46483 end: { 46484 dts: baseMediaDecodeTime + decodeDuration, 46485 pts: baseMediaDecodeTime + presentationDuration 46486 }, 46487 prependedContentDuration: prependedContentDuration, 46488 baseMediaDecodeTime: baseMediaDecodeTime 46489 }; 46490 }; 46491 /** 46492 * Constructs a single-track, ISO BMFF media segment from AAC data 46493 * events. The output of this stream can be fed to a SourceBuffer 46494 * configured with a suitable initialization segment. 46495 * @param track {object} track metadata configuration 46496 * @param options {object} transmuxer options object 46497 * @param options.keepOriginalTimestamps {boolean} If true, keep the timestamps 46498 * in the source; false to adjust the first segment to start at 0. 46499 */ 46500 46501 46502 _AudioSegmentStream = function AudioSegmentStream(track, options) { 46503 var adtsFrames = [], 46504 sequenceNumber = 0, 46505 earliestAllowedDts = 0, 46506 audioAppendStartTs = 0, 46507 videoBaseMediaDecodeTime = Infinity; 46508 options = options || {}; 46509 46510 _AudioSegmentStream.prototype.init.call(this); 46511 46512 this.push = function (data) { 46513 trackDecodeInfo.collectDtsInfo(track, data); 46514 46515 if (track) { 46516 AUDIO_PROPERTIES.forEach(function (prop) { 46517 track[prop] = data[prop]; 46518 }); 46519 } // buffer audio data until end() is called 46520 46521 46522 adtsFrames.push(data); 46523 }; 46524 46525 this.setEarliestDts = function (earliestDts) { 46526 earliestAllowedDts = earliestDts - track.timelineStartInfo.baseMediaDecodeTime; 46527 }; 46528 46529 this.setVideoBaseMediaDecodeTime = function (baseMediaDecodeTime) { 46530 videoBaseMediaDecodeTime = baseMediaDecodeTime; 46531 }; 46532 46533 this.setAudioAppendStart = function (timestamp) { 46534 audioAppendStartTs = timestamp; 46535 }; 46536 46537 this.flush = function () { 46538 var frames, moof, mdat, boxes; // return early if no audio data has been observed 46539 46540 if (adtsFrames.length === 0) { 46541 this.trigger('done', 'AudioSegmentStream'); 46542 return; 46543 } 46544 46545 frames = audioFrameUtils.trimAdtsFramesByEarliestDts(adtsFrames, track, earliestAllowedDts); 46546 track.baseMediaDecodeTime = trackDecodeInfo.calculateTrackBaseMediaDecodeTime(track, options.keepOriginalTimestamps); 46547 audioFrameUtils.prefixWithSilence(track, frames, audioAppendStartTs, videoBaseMediaDecodeTime); // we have to build the index from byte locations to 46548 // samples (that is, adts frames) in the audio data 46549 46550 track.samples = audioFrameUtils.generateSampleTable(frames); // concatenate the audio data to constuct the mdat 46551 46552 mdat = mp4Generator.mdat(audioFrameUtils.concatenateFrameData(frames)); 46553 adtsFrames = []; 46554 moof = mp4Generator.moof(sequenceNumber, [track]); 46555 boxes = new Uint8Array(moof.byteLength + mdat.byteLength); // bump the sequence number for next time 46556 46557 sequenceNumber++; 46558 boxes.set(moof); 46559 boxes.set(mdat, moof.byteLength); 46560 trackDecodeInfo.clearDtsInfo(track); 46561 this.trigger('data', { 46562 track: track, 46563 boxes: boxes 46564 }); 46565 this.trigger('done', 'AudioSegmentStream'); 46566 }; 46567 }; 46568 46569 _AudioSegmentStream.prototype = new stream(); 46570 /** 46571 * Constructs a single-track, ISO BMFF media segment from H264 data 46572 * events. The output of this stream can be fed to a SourceBuffer 46573 * configured with a suitable initialization segment. 46574 * @param track {object} track metadata configuration 46575 * @param options {object} transmuxer options object 46576 * @param options.alignGopsAtEnd {boolean} If true, start from the end of the 46577 * gopsToAlignWith list when attempting to align gop pts 46578 * @param options.keepOriginalTimestamps {boolean} If true, keep the timestamps 46579 * in the source; false to adjust the first segment to start at 0. 46580 */ 46581 46582 _VideoSegmentStream = function VideoSegmentStream(track, options) { 46583 var sequenceNumber = 0, 46584 nalUnits = [], 46585 gopsToAlignWith = [], 46586 config, 46587 pps; 46588 options = options || {}; 46589 46590 _VideoSegmentStream.prototype.init.call(this); 46591 46592 delete track.minPTS; 46593 this.gopCache_ = []; 46594 /** 46595 * Constructs a ISO BMFF segment given H264 nalUnits 46596 * @param {Object} nalUnit A data event representing a nalUnit 46597 * @param {String} nalUnit.nalUnitType 46598 * @param {Object} nalUnit.config Properties for a mp4 track 46599 * @param {Uint8Array}
vendor: 11,611 bytes, lines 46599-46893
46599 nalUnit.data The nalUnit bytes 46600 * @see lib/codecs/h264.js 46601 **/ 46602 46603 this.push = function (nalUnit) { 46604 trackDecodeInfo.collectDtsInfo(track, nalUnit); // record the track config 46605 46606 if (nalUnit.nalUnitType === 'seq_parameter_set_rbsp' && !config) { 46607 config = nalUnit.config; 46608 track.sps = [nalUnit.data]; 46609 VIDEO_PROPERTIES.forEach(function (prop) { 46610 track[prop] = config[prop]; 46611 }, this); 46612 } 46613 46614 if (nalUnit.nalUnitType === 'pic_parameter_set_rbsp' && !pps) { 46615 pps = nalUnit.data; 46616 track.pps = [nalUnit.data]; 46617 } // buffer video until flush() is called 46618 46619 46620 nalUnits.push(nalUnit); 46621 }; 46622 /** 46623 * Pass constructed ISO BMFF track and boxes on to the 46624 * next stream in the pipeline 46625 **/ 46626 46627 46628 this.flush = function () { 46629 var frames, 46630 gopForFusion, 46631 gops, 46632 moof, 46633 mdat, 46634 boxes, 46635 prependedContentDuration = 0, 46636 firstGop, 46637 lastGop; // Throw away nalUnits at the start of the byte stream until 46638 // we find the first AUD 46639 46640 while (nalUnits.length) { 46641 if (nalUnits[0].nalUnitType === 'access_unit_delimiter_rbsp') { 46642 break; 46643 } 46644 46645 nalUnits.shift(); 46646 } // Return early if no video data has been observed 46647 46648 46649 if (nalUnits.length === 0) { 46650 this.resetStream_(); 46651 this.trigger('done', 'VideoSegmentStream'); 46652 return; 46653 } // Organize the raw nal-units into arrays that represent 46654 // higher-level constructs such as frames and gops 46655 // (group-of-pictures) 46656 46657 46658 frames = frameUtils.groupNalsIntoFrames(nalUnits); 46659 gops = frameUtils.groupFramesIntoGops(frames); // If the first frame of this fragment is not a keyframe we have 46660 // a problem since MSE (on Chrome) requires a leading keyframe. 46661 // 46662 // We have two approaches to repairing this situation: 46663 // 1) GOP-FUSION: 46664 // This is where we keep track of the GOPS (group-of-pictures) 46665 // from previous fragments and attempt to find one that we can 46666 // prepend to the current fragment in order to create a valid 46667 // fragment. 46668 // 2) KEYFRAME-PULLING: 46669 // Here we search for the first keyframe in the fragment and 46670 // throw away all the frames between the start of the fragment 46671 // and that keyframe. We then extend the duration and pull the 46672 // PTS of the keyframe forward so that it covers the time range 46673 // of the frames that were disposed of. 46674 // 46675 // #1 is far prefereable over #2 which can cause "stuttering" but 46676 // requires more things to be just right. 46677 46678 if (!gops[0][0].keyFrame) { 46679 // Search for a gop for fusion from our gopCache 46680 gopForFusion = this.getGopForFusion_(nalUnits[0], track); 46681 46682 if (gopForFusion) { 46683 // in order to provide more accurate timing information about the segment, save 46684 // the number of seconds prepended to the original segment due to GOP fusion 46685 prependedContentDuration = gopForFusion.duration; 46686 gops.unshift(gopForFusion); // Adjust Gops' metadata to account for the inclusion of the 46687 // new gop at the beginning 46688 46689 gops.byteLength += gopForFusion.byteLength; 46690 gops.nalCount += gopForFusion.nalCount; 46691 gops.pts = gopForFusion.pts; 46692 gops.dts = gopForFusion.dts; 46693 gops.duration += gopForFusion.duration; 46694 } else { 46695 // If we didn't find a candidate gop fall back to keyframe-pulling 46696 gops = frameUtils.extendFirstKeyFrame(gops); 46697 } 46698 } // Trim gops to align with gopsToAlignWith 46699 46700 46701 if (gopsToAlignWith.length) { 46702 var alignedGops; 46703 46704 if (options.alignGopsAtEnd) { 46705 alignedGops = this.alignGopsAtEnd_(gops); 46706 } else { 46707 alignedGops = this.alignGopsAtStart_(gops); 46708 } 46709 46710 if (!alignedGops) { 46711 // save all the nals in the last GOP into the gop cache 46712 this.gopCache_.unshift({ 46713 gop: gops.pop(), 46714 pps: track.pps, 46715 sps: track.sps 46716 }); // Keep a maximum of 6 GOPs in the cache 46717 46718 this.gopCache_.length = Math.min(6, this.gopCache_.length); // Clear nalUnits 46719 46720 nalUnits = []; // return early no gops can be aligned with desired gopsToAlignWith 46721 46722 this.resetStream_(); 46723 this.trigger('done', 'VideoSegmentStream'); 46724 return; 46725 } // Some gops were trimmed. clear dts info so minSegmentDts and pts are correct 46726 // when recalculated before sending off to CoalesceStream 46727 46728 46729 trackDecodeInfo.clearDtsInfo(track); 46730 gops = alignedGops; 46731 } 46732 46733 trackDecodeInfo.collectDtsInfo(track, gops); // First, we have to build the index from byte locations to 46734 // samples (that is, frames) in the video data 46735 46736 track.samples = frameUtils.generateSampleTable(gops); // Concatenate the video data and construct the mdat 46737 46738 mdat = mp4Generator.mdat(frameUtils.concatenateNalData(gops)); 46739 track.baseMediaDecodeTime = trackDecodeInfo.calculateTrackBaseMediaDecodeTime(track, options.keepOriginalTimestamps); 46740 this.trigger('processedGopsInfo', gops.map(function (gop) { 46741 return { 46742 pts: gop.pts, 46743 dts: gop.dts, 46744 byteLength: gop.byteLength 46745 }; 46746 })); 46747 firstGop = gops[0]; 46748 lastGop = gops[gops.length - 1]; 46749 this.trigger('segmentTimingInfo', generateVideoSegmentTimingInfo(track.baseMediaDecodeTime, firstGop.dts, firstGop.pts, lastGop.dts + lastGop.duration, lastGop.pts + lastGop.duration, prependedContentDuration)); // save all the nals in the last GOP into the gop cache 46750 46751 this.gopCache_.unshift({ 46752 gop: gops.pop(), 46753 pps: track.pps, 46754 sps: track.sps 46755 }); // Keep a maximum of 6 GOPs in the cache 46756 46757 this.gopCache_.length = Math.min(6, this.gopCache_.length); // Clear nalUnits 46758 46759 nalUnits = []; 46760 this.trigger('baseMediaDecodeTime', track.baseMediaDecodeTime); 46761 this.trigger('timelineStartInfo', track.timelineStartInfo); 46762 moof = mp4Generator.moof(sequenceNumber, [track]); // it would be great to allocate this array up front instead of 46763 // throwing away hundreds of media segment fragments 46764 46765 boxes = new Uint8Array(moof.byteLength + mdat.byteLength); // Bump the sequence number for next time 46766 46767 sequenceNumber++; 46768 boxes.set(moof); 46769 boxes.set(mdat, moof.byteLength); 46770 this.trigger('data', { 46771 track: track, 46772 boxes: boxes 46773 }); 46774 this.resetStream_(); // Continue with the flush process now 46775 46776 this.trigger('done', 'VideoSegmentStream'); 46777 }; 46778 46779 this.resetStream_ = function () { 46780 trackDecodeInfo.clearDtsInfo(track); // reset config and pps because they may differ across segments 46781 // for instance, when we are rendition switching 46782 46783 config = undefined; 46784 pps = undefined; 46785 }; // Search for a candidate Gop for gop-fusion from the gop cache and 46786 // return it or return null if no good candidate was found 46787 46788 46789 this.getGopForFusion_ = function (nalUnit) { 46790 var halfSecond = 45000, 46791 // Half-a-second in a 90khz clock 46792 allowableOverlap = 10000, 46793 // About 3 frames @ 30fps 46794 nearestDistance = Infinity, 46795 dtsDistance, 46796 nearestGopObj, 46797 currentGop, 46798 currentGopObj, 46799 i; // Search for the GOP nearest to the beginning of this nal unit 46800 46801 for (i = 0; i < this.gopCache_.length; i++) { 46802 currentGopObj = this.gopCache_[i]; 46803 currentGop = currentGopObj.gop; // Reject Gops with different SPS or PPS 46804 46805 if (!(track.pps && arrayEquals(track.pps[0], currentGopObj.pps[0])) || !(track.sps && arrayEquals(track.sps[0], currentGopObj.sps[0]))) { 46806 continue; 46807 } // Reject Gops that would require a negative baseMediaDecodeTime 46808 46809 46810 if (currentGop.dts < track.timelineStartInfo.dts) { 46811 continue; 46812 } // The distance between the end of the gop and the start of the nalUnit 46813 46814 46815 dtsDistance = nalUnit.dts - currentGop.dts - currentGop.duration; // Only consider GOPS that start before the nal unit and end within 46816 // a half-second of the nal unit 46817 46818 if (dtsDistance >= -allowableOverlap && dtsDistance <= halfSecond) { 46819 // Always use the closest GOP we found if there is more than 46820 // one candidate 46821 if (!nearestGopObj || nearestDistance > dtsDistance) { 46822 nearestGopObj = currentGopObj; 46823 nearestDistance = dtsDistance; 46824 } 46825 } 46826 } 46827 46828 if (nearestGopObj) { 46829 return nearestGopObj.gop; 46830 } 46831 46832 return null; 46833 }; // trim gop list to the first gop found that has a matching pts with a gop in the list 46834 // of gopsToAlignWith starting from the START of the list 46835 46836 46837 this.alignGopsAtStart_ = function (gops) { 46838 var alignIndex, gopIndex, align, gop, byteLength, nalCount, duration, alignedGops; 46839 byteLength = gops.byteLength; 46840 nalCount = gops.nalCount; 46841 duration = gops.duration; 46842 alignIndex = gopIndex = 0; 46843 46844 while (alignIndex < gopsToAlignWith.length && gopIndex < gops.length) { 46845 align = gopsToAlignWith[alignIndex]; 46846 gop = gops[gopIndex]; 46847 46848 if (align.pts === gop.pts) { 46849 break; 46850 } 46851 46852 if (gop.pts > align.pts) { 46853 // this current gop starts after the current gop we want to align on, so increment 46854 // align index 46855 alignIndex++; 46856 continue; 46857 } // current gop starts before the current gop we want to align on. so increment gop 46858 // index 46859 46860 46861 gopIndex++; 46862 byteLength -= gop.byteLength; 46863 nalCount -= gop.nalCount; 46864 duration -= gop.duration; 46865 } 46866 46867 if (gopIndex === 0) { 46868 // no gops to trim 46869 return gops; 46870 } 46871 46872 if (gopIndex === gops.length) { 46873 // all gops trimmed, skip appending all gops 46874 return null; 46875 } 46876 46877 alignedGops = gops.slice(gopIndex); 46878 alignedGops.byteLength = byteLength; 46879 alignedGops.duration = duration; 46880 alignedGops.nalCount = nalCount; 46881 alignedGops.pts = alignedGops[0].pts; 46882 alignedGops.dts = alignedGops[0].dts; 46883 return alignedGops; 46884 }; // trim gop list to the first gop found that has a matching pts with a gop in the list 46885 // of gopsToAlignWith starting from the END of the list 46886 46887 46888 this.alignGopsAtEnd_ = function (gops) { 46889 var alignIndex, gopIndex, align, gop, alignEndIndex, matchFound; 46890 alignIndex = gopsToAlignWith.length - 1; 46891 gopIndex = gops.length - 1; 46892 alignEndIndex = null; 46893 matchFound = false;
vendor: 46,629 bytes, lines 46894-48172
46894 46895 while (alignIndex >= 0 && gopIndex >= 0) { 46896 align = gopsToAlignWith[alignIndex]; 46897 gop = gops[gopIndex]; 46898 46899 if (align.pts === gop.pts) { 46900 matchFound = true; 46901 break; 46902 } 46903 46904 if (align.pts > gop.pts) { 46905 alignIndex--; 46906 continue; 46907 } 46908 46909 if (alignIndex === gopsToAlignWith.length - 1) { 46910 // gop.pts is greater than the last alignment candidate. If no match is found 46911 // by the end of this loop, we still want to append gops that come after this 46912 // point 46913 alignEndIndex = gopIndex; 46914 } 46915 46916 gopIndex--; 46917 } 46918 46919 if (!matchFound && alignEndIndex === null) { 46920 return null; 46921 } 46922 46923 var trimIndex; 46924 46925 if (matchFound) { 46926 trimIndex = gopIndex; 46927 } else { 46928 trimIndex = alignEndIndex; 46929 } 46930 46931 if (trimIndex === 0) { 46932 return gops; 46933 } 46934 46935 var alignedGops = gops.slice(trimIndex); 46936 var metadata = alignedGops.reduce(function (total, gop) { 46937 total.byteLength += gop.byteLength; 46938 total.duration += gop.duration; 46939 total.nalCount += gop.nalCount; 46940 return total; 46941 }, { 46942 byteLength: 0, 46943 duration: 0, 46944 nalCount: 0 46945 }); 46946 alignedGops.byteLength = metadata.byteLength; 46947 alignedGops.duration = metadata.duration; 46948 alignedGops.nalCount = metadata.nalCount; 46949 alignedGops.pts = alignedGops[0].pts; 46950 alignedGops.dts = alignedGops[0].dts; 46951 return alignedGops; 46952 }; 46953 46954 this.alignGopsWith = function (newGopsToAlignWith) { 46955 gopsToAlignWith = newGopsToAlignWith; 46956 }; 46957 }; 46958 46959 _VideoSegmentStream.prototype = new stream(); 46960 /** 46961 * A Stream that can combine multiple streams (ie. audio & video) 46962 * into a single output segment for MSE. Also supports audio-only 46963 * and video-only streams. 46964 * @param options {object} transmuxer options object 46965 * @param options.keepOriginalTimestamps {boolean} If true, keep the timestamps 46966 * in the source; false to adjust the first segment to start at media timeline start. 46967 */ 46968 46969 _CoalesceStream = function CoalesceStream(options, metadataStream) { 46970 // Number of Tracks per output segment 46971 // If greater than 1, we combine multiple 46972 // tracks into a single segment 46973 this.numberOfTracks = 0; 46974 this.metadataStream = metadataStream; 46975 options = options || {}; 46976 46977 if (typeof options.remux !== 'undefined') { 46978 this.remuxTracks = !!options.remux; 46979 } else { 46980 this.remuxTracks = true; 46981 } 46982 46983 if (typeof options.keepOriginalTimestamps === 'boolean') { 46984 this.keepOriginalTimestamps = options.keepOriginalTimestamps; 46985 } 46986 46987 this.pendingTracks = []; 46988 this.videoTrack = null; 46989 this.pendingBoxes = []; 46990 this.pendingCaptions = []; 46991 this.pendingMetadata = []; 46992 this.pendingBytes = 0; 46993 this.emittedTracks = 0; 46994 46995 _CoalesceStream.prototype.init.call(this); // Take output from multiple 46996 46997 46998 this.push = function (output) { 46999 // buffer incoming captions until the associated video segment 47000 // finishes 47001 if (output.text) { 47002 return this.pendingCaptions.push(output); 47003 } // buffer incoming id3 tags until the final flush 47004 47005 47006 if (output.frames) { 47007 return this.pendingMetadata.push(output); 47008 } // Add this track to the list of pending tracks and store 47009 // important information required for the construction of 47010 // the final segment 47011 47012 47013 this.pendingTracks.push(output.track); 47014 this.pendingBoxes.push(output.boxes); 47015 this.pendingBytes += output.boxes.byteLength; 47016 47017 if (output.track.type === 'video') { 47018 this.videoTrack = output.track; 47019 } 47020 47021 if (output.track.type === 'audio') { 47022 this.audioTrack = output.track; 47023 } 47024 }; 47025 }; 47026 47027 _CoalesceStream.prototype = new stream(); 47028 47029 _CoalesceStream.prototype.flush = function (flushSource) { 47030 var offset = 0, 47031 event = { 47032 captions: [], 47033 captionStreams: {}, 47034 metadata: [], 47035 info: {} 47036 }, 47037 caption, 47038 id3, 47039 initSegment, 47040 timelineStartPts = 0, 47041 i; 47042 47043 if (this.pendingTracks.length < this.numberOfTracks) { 47044 if (flushSource !== 'VideoSegmentStream' && flushSource !== 'AudioSegmentStream') { 47045 // Return because we haven't received a flush from a data-generating 47046 // portion of the segment (meaning that we have only recieved meta-data 47047 // or captions.) 47048 return; 47049 } else if (this.remuxTracks) { 47050 // Return until we have enough tracks from the pipeline to remux (if we 47051 // are remuxing audio and video into a single MP4) 47052 return; 47053 } else if (this.pendingTracks.length === 0) { 47054 // In the case where we receive a flush without any data having been 47055 // received we consider it an emitted track for the purposes of coalescing 47056 // `done` events. 47057 // We do this for the case where there is an audio and video track in the 47058 // segment but no audio data. (seen in several playlists with alternate 47059 // audio tracks and no audio present in the main TS segments.) 47060 this.emittedTracks++; 47061 47062 if (this.emittedTracks >= this.numberOfTracks) { 47063 this.trigger('done'); 47064 this.emittedTracks = 0; 47065 } 47066 47067 return; 47068 } 47069 } 47070 47071 if (this.videoTrack) { 47072 timelineStartPts = this.videoTrack.timelineStartInfo.pts; 47073 VIDEO_PROPERTIES.forEach(function (prop) { 47074 event.info[prop] = this.videoTrack[prop]; 47075 }, this); 47076 } else if (this.audioTrack) { 47077 timelineStartPts = this.audioTrack.timelineStartInfo.pts; 47078 AUDIO_PROPERTIES.forEach(function (prop) { 47079 event.info[prop] = this.audioTrack[prop]; 47080 }, this); 47081 } 47082 47083 if (this.pendingTracks.length === 1) { 47084 event.type = this.pendingTracks[0].type; 47085 } else { 47086 event.type = 'combined'; 47087 } 47088 47089 this.emittedTracks += this.pendingTracks.length; 47090 initSegment = mp4Generator.initSegment(this.pendingTracks); // Create a new typed array to hold the init segment 47091 47092 event.initSegment = new Uint8Array(initSegment.byteLength); // Create an init segment containing a moov 47093 // and track definitions 47094 47095 event.initSegment.set(initSegment); // Create a new typed array to hold the moof+mdats 47096 47097 event.data = new Uint8Array(this.pendingBytes); // Append each moof+mdat (one per track) together 47098 47099 for (i = 0; i < this.pendingBoxes.length; i++) { 47100 event.data.set(this.pendingBoxes[i], offset); 47101 offset += this.pendingBoxes[i].byteLength; 47102 } // Translate caption PTS times into second offsets to match the 47103 // video timeline for the segment, and add track info 47104 47105 47106 for (i = 0; i < this.pendingCaptions.length; i++) { 47107 caption = this.pendingCaptions[i]; 47108 caption.startTime = caption.startPts; 47109 47110 if (!this.keepOriginalTimestamps) { 47111 caption.startTime -= timelineStartPts; 47112 } 47113 47114 caption.startTime /= 90e3; 47115 caption.endTime = caption.endPts; 47116 47117 if (!this.keepOriginalTimestamps) { 47118 caption.endTime -= timelineStartPts; 47119 } 47120 47121 caption.endTime /= 90e3; 47122 event.captionStreams[caption.stream] = true; 47123 event.captions.push(caption); 47124 } // Translate ID3 frame PTS times into second offsets to match the 47125 // video timeline for the segment 47126 47127 47128 for (i = 0; i < this.pendingMetadata.length; i++) { 47129 id3 = this.pendingMetadata[i]; 47130 id3.cueTime = id3.pts; 47131 47132 if (!this.keepOriginalTimestamps) { 47133 id3.cueTime -= timelineStartPts; 47134 } 47135 47136 id3.cueTime /= 90e3; 47137 event.metadata.push(id3); 47138 } // We add this to every single emitted segment even though we only need 47139 // it for the first 47140 47141 47142 event.metadata.dispatchType = this.metadataStream.dispatchType; // Reset stream state 47143 47144 this.pendingTracks.length = 0; 47145 this.videoTrack = null; 47146 this.pendingBoxes.length = 0; 47147 this.pendingCaptions.length = 0; 47148 this.pendingBytes = 0; 47149 this.pendingMetadata.length = 0; // Emit the built segment 47150 47151 this.trigger('data', event); // Only emit `done` if all tracks have been flushed and emitted 47152 47153 if (this.emittedTracks >= this.numberOfTracks) { 47154 this.trigger('done'); 47155 this.emittedTracks = 0; 47156 } 47157 }; 47158 /** 47159 * A Stream that expects MP2T binary data as input and produces 47160 * corresponding media segments, suitable for use with Media Source 47161 * Extension (MSE) implementations that support the ISO BMFF byte 47162 * stream format, like Chrome. 47163 */ 47164 47165 47166 _Transmuxer = function Transmuxer(options) { 47167 var self = this, 47168 hasFlushed = true, 47169 videoTrack, 47170 audioTrack; 47171 47172 _Transmuxer.prototype.init.call(this); 47173 47174 options = options || {}; 47175 this.baseMediaDecodeTime = options.baseMediaDecodeTime || 0; 47176 this.transmuxPipeline_ = {}; 47177 47178 this.setupAacPipeline = function () { 47179 var pipeline = {}; 47180 this.transmuxPipeline_ = pipeline; 47181 pipeline.type = 'aac'; 47182 pipeline.metadataStream = new m2ts_1.MetadataStream(); // set up the parsing pipeline 47183 47184 pipeline.aacStream = new aac(); 47185 pipeline.audioTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('audio'); 47186 pipeline.timedMetadataTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('timed-metadata'); 47187 pipeline.adtsStream = new adts(); 47188 pipeline.coalesceStream = new _CoalesceStream(options, pipeline.metadataStream); 47189 pipeline.headOfPipeline = pipeline.aacStream; 47190 pipeline.aacStream.pipe(pipeline.audioTimestampRolloverStream).pipe(pipeline.adtsStream); 47191 pipeline.aacStream.pipe(pipeline.timedMetadataTimestampRolloverStream).pipe(pipeline.metadataStream).pipe(pipeline.coalesceStream); 47192 pipeline.metadataStream.on('timestamp', function (frame) { 47193 pipeline.aacStream.setTimestamp(frame.timeStamp); 47194 }); 47195 pipeline.aacStream.on('data', function (data) { 47196 if (data.type === 'timed-metadata' && !pipeline.audioSegmentStream) { 47197 audioTrack = audioTrack || { 47198 timelineStartInfo: { 47199 baseMediaDecodeTime: self.baseMediaDecodeTime 47200 }, 47201 codec: 'adts', 47202 type: 'audio' 47203 }; // hook up the audio segment stream to the first track with aac data 47204 47205 pipeline.coalesceStream.numberOfTracks++; 47206 pipeline.audioSegmentStream = new _AudioSegmentStream(audioTrack, options); // Set up the final part of the audio pipeline 47207 47208 pipeline.adtsStream.pipe(pipeline.audioSegmentStream).pipe(pipeline.coalesceStream); 47209 } 47210 }); // Re-emit any data coming from the coalesce stream to the outside world 47211 47212 pipeline.coalesceStream.on('data', this.trigger.bind(this, 'data')); // Let the consumer know we have finished flushing the entire pipeline 47213 47214 pipeline.coalesceStream.on('done', this.trigger.bind(this, 'done')); 47215 }; 47216 47217 this.setupTsPipeline = function () { 47218 var pipeline = {}; 47219 this.transmuxPipeline_ = pipeline; 47220 pipeline.type = 'ts'; 47221 pipeline.metadataStream = new m2ts_1.MetadataStream(); // set up the parsing pipeline 47222 47223 pipeline.packetStream = new m2ts_1.TransportPacketStream(); 47224 pipeline.parseStream = new m2ts_1.TransportParseStream(); 47225 pipeline.elementaryStream = new m2ts_1.ElementaryStream(); 47226 pipeline.videoTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('video'); 47227 pipeline.audioTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('audio'); 47228 pipeline.timedMetadataTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('timed-metadata'); 47229 pipeline.adtsStream = new adts(); 47230 pipeline.h264Stream = new H264Stream(); 47231 pipeline.captionStream = new m2ts_1.CaptionStream(); 47232 pipeline.coalesceStream = new _CoalesceStream(options, pipeline.metadataStream); 47233 pipeline.headOfPipeline = pipeline.packetStream; // disassemble MPEG2-TS packets into elementary streams 47234 47235 pipeline.packetStream.pipe(pipeline.parseStream).pipe(pipeline.elementaryStream); // !!THIS ORDER IS IMPORTANT!! 47236 // demux the streams 47237 47238 pipeline.elementaryStream.pipe(pipeline.videoTimestampRolloverStream).pipe(pipeline.h264Stream); 47239 pipeline.elementaryStream.pipe(pipeline.audioTimestampRolloverStream).pipe(pipeline.adtsStream); 47240 pipeline.elementaryStream.pipe(pipeline.timedMetadataTimestampRolloverStream).pipe(pipeline.metadataStream).pipe(pipeline.coalesceStream); // Hook up CEA-608/708 caption stream 47241 47242 pipeline.h264Stream.pipe(pipeline.captionStream).pipe(pipeline.coalesceStream); 47243 pipeline.elementaryStream.on('data', function (data) { 47244 var i; 47245 47246 if (data.type === 'metadata') { 47247 i = data.tracks.length; // scan the tracks listed in the metadata 47248 47249 while (i--) { 47250 if (!videoTrack && data.tracks[i].type === 'video') { 47251 videoTrack = data.tracks[i]; 47252 videoTrack.timelineStartInfo.baseMediaDecodeTime = self.baseMediaDecodeTime; 47253 } else if (!audioTrack && data.tracks[i].type === 'audio') { 47254 audioTrack = data.tracks[i]; 47255 audioTrack.timelineStartInfo.baseMediaDecodeTime = self.baseMediaDecodeTime; 47256 } 47257 } // hook up the video segment stream to the first track with h264 data 47258 47259 47260 if (videoTrack && !pipeline.videoSegmentStream) { 47261 pipeline.coalesceStream.numberOfTracks++; 47262 pipeline.videoSegmentStream = new _VideoSegmentStream(videoTrack, options); 47263 pipeline.videoSegmentStream.on('timelineStartInfo', function (timelineStartInfo) { 47264 // When video emits timelineStartInfo data after a flush, we forward that 47265 // info to the AudioSegmentStream, if it exists, because video timeline 47266 // data takes precedence. 47267 if (audioTrack) { 47268 audioTrack.timelineStartInfo = timelineStartInfo; // On the first segment we trim AAC frames that exist before the 47269 // very earliest DTS we have seen in video because Chrome will 47270 // interpret any video track with a baseMediaDecodeTime that is 47271 // non-zero as a gap. 47272 47273 pipeline.audioSegmentStream.setEarliestDts(timelineStartInfo.dts); 47274 } 47275 }); 47276 pipeline.videoSegmentStream.on('processedGopsInfo', self.trigger.bind(self, 'gopInfo')); 47277 pipeline.videoSegmentStream.on('segmentTimingInfo', self.trigger.bind(self, 'videoSegmentTimingInfo')); 47278 pipeline.videoSegmentStream.on('baseMediaDecodeTime', function (baseMediaDecodeTime) { 47279 if (audioTrack) { 47280 pipeline.audioSegmentStream.setVideoBaseMediaDecodeTime(baseMediaDecodeTime); 47281 } 47282 }); // Set up the final part of the video pipeline 47283 47284 pipeline.h264Stream.pipe(pipeline.videoSegmentStream).pipe(pipeline.coalesceStream); 47285 } 47286 47287 if (audioTrack && !pipeline.audioSegmentStream) { 47288 // hook up the audio segment stream to the first track with aac data 47289 pipeline.coalesceStream.numberOfTracks++; 47290 pipeline.audioSegmentStream = new _AudioSegmentStream(audioTrack, options); // Set up the final part of the audio pipeline 47291 47292 pipeline.adtsStream.pipe(pipeline.audioSegmentStream).pipe(pipeline.coalesceStream); 47293 } 47294 } 47295 }); // Re-emit any data coming from the coalesce stream to the outside world 47296 47297 pipeline.coalesceStream.on('data', this.trigger.bind(this, 'data')); // Let the consumer know we have finished flushing the entire pipeline 47298 47299 pipeline.coalesceStream.on('done', this.trigger.bind(this, 'done')); 47300 }; // hook up the segment streams once track metadata is delivered 47301 47302 47303 this.setBaseMediaDecodeTime = function (baseMediaDecodeTime) { 47304 var pipeline = this.transmuxPipeline_; 47305 47306 if (!options.keepOriginalTimestamps) { 47307 this.baseMediaDecodeTime = baseMediaDecodeTime; 47308 } 47309 47310 if (audioTrack) { 47311 audioTrack.timelineStartInfo.dts = undefined; 47312 audioTrack.timelineStartInfo.pts = undefined; 47313 trackDecodeInfo.clearDtsInfo(audioTrack); 47314 47315 if (!options.keepOriginalTimestamps) { 47316 audioTrack.timelineStartInfo.baseMediaDecodeTime = baseMediaDecodeTime; 47317 } 47318 47319 if (pipeline.audioTimestampRolloverStream) { 47320 pipeline.audioTimestampRolloverStream.discontinuity(); 47321 } 47322 } 47323 47324 if (videoTrack) { 47325 if (pipeline.videoSegmentStream) { 47326 pipeline.videoSegmentStream.gopCache_ = []; 47327 pipeline.videoTimestampRolloverStream.discontinuity(); 47328 } 47329 47330 videoTrack.timelineStartInfo.dts = undefined; 47331 videoTrack.timelineStartInfo.pts = undefined; 47332 trackDecodeInfo.clearDtsInfo(videoTrack); 47333 pipeline.captionStream.reset(); 47334 47335 if (!options.keepOriginalTimestamps) { 47336 videoTrack.timelineStartInfo.baseMediaDecodeTime = baseMediaDecodeTime; 47337 } 47338 } 47339 47340 if (pipeline.timedMetadataTimestampRolloverStream) { 47341 pipeline.timedMetadataTimestampRolloverStream.discontinuity(); 47342 } 47343 }; 47344 47345 this.setAudioAppendStart = function (timestamp) { 47346 if (audioTrack) { 47347 this.transmuxPipeline_.audioSegmentStream.setAudioAppendStart(timestamp); 47348 } 47349 }; 47350 47351 this.alignGopsWith = function (gopsToAlignWith) { 47352 if (videoTrack && this.transmuxPipeline_.videoSegmentStream) { 47353 this.transmuxPipeline_.videoSegmentStream.alignGopsWith(gopsToAlignWith); 47354 } 47355 }; // feed incoming data to the front of the parsing pipeline 47356 47357 47358 this.push = function (data) { 47359 if (hasFlushed) { 47360 var isAac = isLikelyAacData$1(data); 47361 47362 if (isAac && this.transmuxPipeline_.type !== 'aac') { 47363 this.setupAacPipeline(); 47364 } else if (!isAac && this.transmuxPipeline_.type !== 'ts') { 47365 this.setupTsPipeline(); 47366 } 47367 47368 hasFlushed = false; 47369 } 47370 47371 this.transmuxPipeline_.headOfPipeline.push(data); 47372 }; // flush any buffered data 47373 47374 47375 this.flush = function () { 47376 hasFlushed = true; // Start at the top of the pipeline and flush all pending work 47377 47378 this.transmuxPipeline_.headOfPipeline.flush(); 47379 }; // Caption data has to be reset when seeking outside buffered range 47380 47381 47382 this.resetCaptions = function () { 47383 if (this.transmuxPipeline_.captionStream) { 47384 this.transmuxPipeline_.captionStream.reset(); 47385 } 47386 }; 47387 }; 47388 47389 _Transmuxer.prototype = new stream(); 47390 var transmuxer = { 47391 Transmuxer: _Transmuxer, 47392 VideoSegmentStream: _VideoSegmentStream, 47393 AudioSegmentStream: _AudioSegmentStream, 47394 AUDIO_PROPERTIES: AUDIO_PROPERTIES, 47395 VIDEO_PROPERTIES: VIDEO_PROPERTIES, 47396 // exported for testing 47397 generateVideoSegmentTimingInfo: generateVideoSegmentTimingInfo 47398 }; 47399 47400 var inspectMp4, 47401 _textifyMp, 47402 parseType$2 = probe$$1.parseType, 47403 parseMp4Date = function parseMp4Date(seconds) { 47404 return new Date(seconds * 1000 - 2082844800000); 47405 }, 47406 parseSampleFlags = function parseSampleFlags(flags) { 47407 return { 47408 isLeading: (flags[0] & 0x0c) >>> 2, 47409 dependsOn: flags[0] & 0x03, 47410 isDependedOn: (flags[1] & 0xc0) >>> 6, 47411 hasRedundancy: (flags[1] & 0x30) >>> 4, 47412 paddingValue: (flags[1] & 0x0e) >>> 1, 47413 isNonSyncSample: flags[1] & 0x01, 47414 degradationPriority: flags[2] << 8 | flags[3] 47415 }; 47416 }, 47417 nalParse = function nalParse(avcStream) { 47418 var avcView = new DataView(avcStream.buffer, avcStream.byteOffset, avcStream.byteLength), 47419 result = [], 47420 i, 47421 length; 47422 47423 for (i = 0; i + 4 < avcStream.length; i += length) { 47424 length = avcView.getUint32(i); 47425 i += 4; // bail if this doesn't appear to be an H264 stream 47426 47427 if (length <= 0) { 47428 result.push('<span style=\'color:red;\'>MALFORMED DATA</span>'); 47429 continue; 47430 } 47431 47432 switch (avcStream[i] & 0x1F) { 47433 case 0x01: 47434 result.push('slice_layer_without_partitioning_rbsp'); 47435 break; 47436 47437 case 0x05: 47438 result.push('slice_layer_without_partitioning_rbsp_idr'); 47439 break; 47440 47441 case 0x06: 47442 result.push('sei_rbsp'); 47443 break; 47444 47445 case 0x07: 47446 result.push('seq_parameter_set_rbsp'); 47447 break; 47448 47449 case 0x08: 47450 result.push('pic_parameter_set_rbsp'); 47451 break; 47452 47453 case 0x09: 47454 result.push('access_unit_delimiter_rbsp'); 47455 break; 47456 47457 default: 47458 result.push('UNKNOWN NAL - ' + avcStream[i] & 0x1F); 47459 break; 47460 } 47461 } 47462 47463 return result; 47464 }, 47465 // registry of handlers for individual mp4 box types 47466 parse$$1 = { 47467 // codingname, not a first-class box type. stsd entries share the 47468 // same format as real boxes so the parsing infrastructure can be 47469 // shared 47470 avc1: function avc1(data) { 47471 var view = new DataView(data.buffer, data.byteOffset, data.byteLength); 47472 return { 47473 dataReferenceIndex: view.getUint16(6), 47474 width: view.getUint16(24), 47475 height: view.getUint16(26), 47476 horizresolution: view.getUint16(28) + view.getUint16(30) / 16, 47477 vertresolution: view.getUint16(32) + view.getUint16(34) / 16, 47478 frameCount: view.getUint16(40), 47479 depth: view.getUint16(74), 47480 config: inspectMp4(data.subarray(78, data.byteLength)) 47481 }; 47482 }, 47483 avcC: function avcC(data) { 47484 var view = new DataView(data.buffer, data.byteOffset, data.byteLength), 47485 result = { 47486 configurationVersion: data[0], 47487 avcProfileIndication: data[1], 47488 profileCompatibility: data[2], 47489 avcLevelIndication: data[3], 47490 lengthSizeMinusOne: data[4] & 0x03, 47491 sps: [], 47492 pps: [] 47493 }, 47494 numOfSequenceParameterSets = data[5] & 0x1f, 47495 numOfPictureParameterSets, 47496 nalSize, 47497 offset, 47498 i; // iterate past any SPSs 47499 47500 offset = 6; 47501 47502 for (i = 0; i < numOfSequenceParameterSets; i++) { 47503 nalSize = view.getUint16(offset); 47504 offset += 2; 47505 result.sps.push(new Uint8Array(data.subarray(offset, offset + nalSize))); 47506 offset += nalSize; 47507 } // iterate past any PPSs 47508 47509 47510 numOfPictureParameterSets = data[offset]; 47511 offset++; 47512 47513 for (i = 0; i < numOfPictureParameterSets; i++) { 47514 nalSize = view.getUint16(offset); 47515 offset += 2; 47516 result.pps.push(new Uint8Array(data.subarray(offset, offset + nalSize))); 47517 offset += nalSize; 47518 } 47519 47520 return result; 47521 }, 47522 btrt: function btrt(data) { 47523 var view = new DataView(data.buffer, data.byteOffset, data.byteLength); 47524 return { 47525 bufferSizeDB: view.getUint32(0), 47526 maxBitrate: view.getUint32(4), 47527 avgBitrate: view.getUint32(8) 47528 }; 47529 }, 47530 esds: function esds(data) { 47531 return { 47532 version: data[0], 47533 flags: new Uint8Array(data.subarray(1, 4)), 47534 esId: data[6] << 8 | data[7], 47535 streamPriority: data[8] & 0x1f, 47536 decoderConfig: { 47537 objectProfileIndication: data[11], 47538 streamType: data[12] >>> 2 & 0x3f, 47539 bufferSize: data[13] << 16 | data[14] << 8 | data[15], 47540 maxBitrate: data[16] << 24 | data[17] << 16 | data[18] << 8 | data[19], 47541 avgBitrate: data[20] << 24 | data[21] << 16 | data[22] << 8 | data[23], 47542 decoderConfigDescriptor: { 47543 tag: data[24], 47544 length: data[25], 47545 audioObjectType: data[26] >>> 3 & 0x1f, 47546 samplingFrequencyIndex: (data[26] & 0x07) << 1 | data[27] >>> 7 & 0x01, 47547 channelConfiguration: data[27] >>> 3 & 0x0f 47548 } 47549 } 47550 }; 47551 }, 47552 ftyp: function ftyp(data) { 47553 var view = new DataView(data.buffer, data.byteOffset, data.byteLength), 47554 result = { 47555 majorBrand: parseType$2(data.subarray(0, 4)), 47556 minorVersion: view.getUint32(4), 47557 compatibleBrands: [] 47558 }, 47559 i = 8; 47560 47561 while (i < data.byteLength) { 47562 result.compatibleBrands.push(parseType$2(data.subarray(i, i + 4))); 47563 i += 4; 47564 } 47565 47566 return result; 47567 }, 47568 dinf: function dinf(data) { 47569 return { 47570 boxes: inspectMp4(data) 47571 }; 47572 }, 47573 dref: function dref(data) { 47574 return { 47575 version: data[0], 47576 flags: new Uint8Array(data.subarray(1, 4)), 47577 dataReferences: inspectMp4(data.subarray(8)) 47578 }; 47579 }, 47580 hdlr: function hdlr(data) { 47581 var view = new DataView(data.buffer, data.byteOffset, data.byteLength), 47582 result = { 47583 version: view.getUint8(0), 47584 flags: new Uint8Array(data.subarray(1, 4)), 47585 handlerType: parseType$2(data.subarray(8, 12)), 47586 name: '' 47587 }, 47588 i = 8; // parse out the name field 47589 47590 for (i = 24; i < data.byteLength; i++) { 47591 if (data[i] === 0x00) { 47592 // the name field is null-terminated 47593 i++; 47594 break; 47595 } 47596 47597 result.name += String.fromCharCode(data[i]); 47598 } // decode UTF-8 to javascript's internal representation 47599 // see http://ecmanaut.blogspot.com/2006/07/encoding-decoding-utf8-in-javascript.html 47600 47601 47602 result.name = decodeURIComponent(escape(result.name)); 47603 return result; 47604 }, 47605 mdat: function mdat(data) { 47606 return { 47607 byteLength: data.byteLength, 47608 nals: nalParse(data) 47609 }; 47610 }, 47611 mdhd: function mdhd(data) { 47612 var view = new DataView(data.buffer, data.byteOffset, data.byteLength), 47613 i = 4, 47614 language, 47615 result = { 47616 version: view.getUint8(0), 47617 flags: new Uint8Array(data.subarray(1, 4)), 47618 language: '' 47619 }; 47620 47621 if (result.version === 1) { 47622 i += 4; 47623 result.creationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes 47624 47625 i += 8; 47626 result.modificationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes 47627 47628 i += 4; 47629 result.timescale = view.getUint32(i); 47630 i += 8; 47631 result.duration = view.getUint32(i); // truncating top 4 bytes 47632 } else { 47633 result.creationTime = parseMp4Date(view.getUint32(i)); 47634 i += 4; 47635 result.modificationTime = parseMp4Date(view.getUint32(i)); 47636 i += 4; 47637 result.timescale = view.getUint32(i); 47638 i += 4; 47639 result.duration = view.getUint32(i); 47640 } 47641 47642 i += 4; // language is stored as an ISO-639-2/T code in an array of three 5-bit fields 47643 // each field is the packed difference between its ASCII value and 0x60 47644 47645 language = view.getUint16(i); 47646 result.language += String.fromCharCode((language >> 10) + 0x60); 47647 result.language += String.fromCharCode(((language & 0x03e0) >> 5) + 0x60); 47648 result.language += String.fromCharCode((language & 0x1f) + 0x60); 47649 return result; 47650 }, 47651 mdia: function mdia(data) { 47652 return { 47653 boxes: inspectMp4(data) 47654 }; 47655 }, 47656 mfhd: function mfhd(data) { 47657 return { 47658 version: data[0], 47659 flags: new Uint8Array(data.subarray(1, 4)), 47660 sequenceNumber: data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7] 47661 }; 47662 }, 47663 minf: function minf(data) { 47664 return { 47665 boxes: inspectMp4(data) 47666 }; 47667 }, 47668 // codingname, not a first-class box type. stsd entries share the 47669 // same format as real boxes so the parsing infrastructure can be 47670 // shared 47671 mp4a: function mp4a(data) { 47672 var view = new DataView(data.buffer, data.byteOffset, data.byteLength), 47673 result = { 47674 // 6 bytes reserved 47675 dataReferenceIndex: view.getUint16(6), 47676 // 4 + 4 bytes reserved 47677 channelcount: view.getUint16(16), 47678 samplesize: view.getUint16(18), 47679 // 2 bytes pre_defined 47680 // 2 bytes reserved 47681 samplerate: view.getUint16(24) + view.getUint16(26) / 65536 47682 }; // if there are more bytes to process, assume this is an ISO/IEC 47683 // 14496-14 MP4AudioSampleEntry and parse the ESDBox 47684 47685 if (data.byteLength > 28) { 47686 result.streamDescriptor = inspectMp4(data.subarray(28))[0]; 47687 } 47688 47689 return result; 47690 }, 47691 moof: function moof(data) { 47692 return { 47693 boxes: inspectMp4(data) 47694 }; 47695 }, 47696 moov: function moov(data) { 47697 return { 47698 boxes: inspectMp4(data) 47699 }; 47700 }, 47701 mvex: function mvex(data) { 47702 return { 47703 boxes: inspectMp4(data) 47704 }; 47705 }, 47706 mvhd: function mvhd(data) { 47707 var view = new DataView(data.buffer, data.byteOffset, data.byteLength), 47708 i = 4, 47709 result = { 47710 version: view.getUint8(0), 47711 flags: new Uint8Array(data.subarray(1, 4)) 47712 }; 47713 47714 if (result.version === 1) { 47715 i += 4; 47716 result.creationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes 47717 47718 i += 8; 47719 result.modificationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes 47720 47721 i += 4; 47722 result.timescale = view.getUint32(i); 47723 i += 8; 47724 result.duration = view.getUint32(i); // truncating top 4 bytes 47725 } else { 47726 result.creationTime = parseMp4Date(view.getUint32(i)); 47727 i += 4; 47728 result.modificationTime = parseMp4Date(view.getUint32(i)); 47729 i += 4; 47730 result.timescale = view.getUint32(i); 47731 i += 4; 47732 result.duration = view.getUint32(i); 47733 } 47734 47735 i += 4; // convert fixed-point, base 16 back to a number 47736 47737 result.rate = view.getUint16(i) + view.getUint16(i + 2) / 16; 47738 i += 4; 47739 result.volume = view.getUint8(i) + view.getUint8(i + 1) / 8; 47740 i += 2; 47741 i += 2; 47742 i += 2 * 4; 47743 result.matrix = new Uint32Array(data.subarray(i, i + 9 * 4)); 47744 i += 9 * 4; 47745 i += 6 * 4; 47746 result.nextTrackId = view.getUint32(i); 47747 return result; 47748 }, 47749 pdin: function pdin(data) { 47750 var view = new DataView(data.buffer, data.byteOffset, data.byteLength); 47751 return { 47752 version: view.getUint8(0), 47753 flags: new Uint8Array(data.subarray(1, 4)), 47754 rate: view.getUint32(4), 47755 initialDelay: view.getUint32(8) 47756 }; 47757 }, 47758 sdtp: function sdtp(data) { 47759 var result = { 47760 version: data[0], 47761 flags: new Uint8Array(data.subarray(1, 4)), 47762 samples: [] 47763 }, 47764 i; 47765 47766 for (i = 4; i < data.byteLength; i++) { 47767 result.samples.push({ 47768 dependsOn: (data[i] & 0x30) >> 4, 47769 isDependedOn: (data[i] & 0x0c) >> 2, 47770 hasRedundancy: data[i] & 0x03 47771 }); 47772 } 47773 47774 return result; 47775 }, 47776 sidx: function sidx(data) { 47777 var view = new DataView(data.buffer, data.byteOffset, data.byteLength), 47778 result = { 47779 version: data[0], 47780 flags: new Uint8Array(data.subarray(1, 4)), 47781 references: [], 47782 referenceId: view.getUint32(4), 47783 timescale: view.getUint32(8), 47784 earliestPresentationTime: view.getUint32(12), 47785 firstOffset: view.getUint32(16) 47786 }, 47787 referenceCount = view.getUint16(22), 47788 i; 47789 47790 for (i = 24; referenceCount; i += 12, referenceCount--) { 47791 result.references.push({ 47792 referenceType: (data[i] & 0x80) >>> 7, 47793 referencedSize: view.getUint32(i) & 0x7FFFFFFF, 47794 subsegmentDuration: view.getUint32(i + 4), 47795 startsWithSap: !!(data[i + 8] & 0x80), 47796 sapType: (data[i + 8] & 0x70) >>> 4, 47797 sapDeltaTime: view.getUint32(i + 8) & 0x0FFFFFFF 47798 }); 47799 } 47800 47801 return result; 47802 }, 47803 smhd: function smhd(data) { 47804 return { 47805 version: data[0], 47806 flags: new Uint8Array(data.subarray(1, 4)), 47807 balance: data[4] + data[5] / 256 47808 }; 47809 }, 47810 stbl: function stbl(data) { 47811 return { 47812 boxes: inspectMp4(data) 47813 }; 47814 }, 47815 stco: function stco(data) { 47816 var view = new DataView(data.buffer, data.byteOffset, data.byteLength), 47817 result = { 47818 version: data[0], 47819 flags: new Uint8Array(data.subarray(1, 4)), 47820 chunkOffsets: [] 47821 }, 47822 entryCount = view.getUint32(4), 47823 i; 47824 47825 for (i = 8; entryCount; i += 4, entryCount--) { 47826 result.chunkOffsets.push(view.getUint32(i)); 47827 } 47828 47829 return result; 47830 }, 47831 stsc: function stsc(data) { 47832 var view = new DataView(data.buffer, data.byteOffset, data.byteLength), 47833 entryCount = view.getUint32(4), 47834 result = { 47835 version: data[0], 47836 flags: new Uint8Array(data.subarray(1, 4)), 47837 sampleToChunks: [] 47838 }, 47839 i; 47840 47841 for (i = 8; entryCount; i += 12, entryCount--) { 47842 result.sampleToChunks.push({ 47843 firstChunk: view.getUint32(i), 47844 samplesPerChunk: view.getUint32(i + 4), 47845 sampleDescriptionIndex: view.getUint32(i + 8) 47846 }); 47847 } 47848 47849 return result; 47850 }, 47851 stsd: function stsd(data) { 47852 return { 47853 version: data[0], 47854 flags: new Uint8Array(data.subarray(1, 4)), 47855 sampleDescriptions: inspectMp4(data.subarray(8)) 47856 }; 47857 }, 47858 stsz: function stsz(data) { 47859 var view = new DataView(data.buffer, data.byteOffset, data.byteLength), 47860 result = { 47861 version: data[0], 47862 flags: new Uint8Array(data.subarray(1, 4)), 47863 sampleSize: view.getUint32(4), 47864 entries: [] 47865 }, 47866 i; 47867 47868 for (i = 12; i < data.byteLength; i += 4) { 47869 result.entries.push(view.getUint32(i)); 47870 } 47871 47872 return result; 47873 }, 47874 stts: function stts(data) { 47875 var view = new DataView(data.buffer, data.byteOffset, data.byteLength), 47876 result = { 47877 version: data[0], 47878 flags: new Uint8Array(data.subarray(1, 4)), 47879 timeToSamples: [] 47880 }, 47881 entryCount = view.getUint32(4), 47882 i; 47883 47884 for (i = 8; entryCount; i += 8, entryCount--) { 47885 result.timeToSamples.push({ 47886 sampleCount: view.getUint32(i), 47887 sampleDelta: view.getUint32(i + 4) 47888 }); 47889 } 47890 47891 return result; 47892 }, 47893 styp: function styp(data) { 47894 return parse$$1.ftyp(data); 47895 }, 47896 tfdt: function tfdt(data) { 47897 var result = { 47898 version: data[0], 47899 flags: new Uint8Array(data.subarray(1, 4)), 47900 baseMediaDecodeTime: data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7] 47901 }; 47902 47903 if (result.version === 1) { 47904 result.baseMediaDecodeTime *= Math.pow(2, 32); 47905 result.baseMediaDecodeTime += data[8] << 24 | data[9] << 16 | data[10] << 8 | data[11]; 47906 } 47907 47908 return result; 47909 }, 47910 tfhd: function tfhd(data) { 47911 var view = new DataView(data.buffer, data.byteOffset, data.byteLength), 47912 result = { 47913 version: data[0], 47914 flags: new Uint8Array(data.subarray(1, 4)), 47915 trackId: view.getUint32(4) 47916 }, 47917 baseDataOffsetPresent = result.flags[2] & 0x01, 47918 sampleDescriptionIndexPresent = result.flags[2] & 0x02, 47919 defaultSampleDurationPresent = result.flags[2] & 0x08, 47920 defaultSampleSizePresent = result.flags[2] & 0x10, 47921 defaultSampleFlagsPresent = result.flags[2] & 0x20, 47922 durationIsEmpty = result.flags[0] & 0x010000, 47923 defaultBaseIsMoof = result.flags[0] & 0x020000, 47924 i; 47925 i = 8; 47926 47927 if (baseDataOffsetPresent) { 47928 i += 4; // truncate top 4 bytes 47929 // FIXME: should we read the full 64 bits? 47930 47931 result.baseDataOffset = view.getUint32(12); 47932 i += 4; 47933 } 47934 47935 if (sampleDescriptionIndexPresent) { 47936 result.sampleDescriptionIndex = view.getUint32(i); 47937 i += 4; 47938 } 47939 47940 if (defaultSampleDurationPresent) { 47941 result.defaultSampleDuration = view.getUint32(i); 47942 i += 4; 47943 } 47944 47945 if (defaultSampleSizePresent) { 47946 result.defaultSampleSize = view.getUint32(i); 47947 i += 4; 47948 } 47949 47950 if (defaultSampleFlagsPresent) { 47951 result.defaultSampleFlags = view.getUint32(i); 47952 } 47953 47954 if (durationIsEmpty) { 47955 result.durationIsEmpty = true; 47956 } 47957 47958 if (!baseDataOffsetPresent && defaultBaseIsMoof) { 47959 result.baseDataOffsetIsMoof = true; 47960 } 47961 47962 return result; 47963 }, 47964 tkhd: function tkhd(data) { 47965 var view = new DataView(data.buffer, data.byteOffset, data.byteLength), 47966 i = 4, 47967 result = { 47968 version: view.getUint8(0), 47969 flags: new Uint8Array(data.subarray(1, 4)) 47970 }; 47971 47972 if (result.version === 1) { 47973 i += 4; 47974 result.creationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes 47975 47976 i += 8; 47977 result.modificationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes 47978 47979 i += 4; 47980 result.trackId = view.getUint32(i); 47981 i += 4; 47982 i += 8; 47983 result.duration = view.getUint32(i); // truncating top 4 bytes 47984 } else { 47985 result.creationTime = parseMp4Date(view.getUint32(i)); 47986 i += 4; 47987 result.modificationTime = parseMp4Date(view.getUint32(i)); 47988 i += 4; 47989 result.trackId = view.getUint32(i); 47990 i += 4; 47991 i += 4; 47992 result.duration = view.getUint32(i); 47993 } 47994 47995 i += 4; 47996 i += 2 * 4; 47997 result.layer = view.getUint16(i); 47998 i += 2; 47999 result.alternateGroup = view.getUint16(i); 48000 i += 2; // convert fixed-point, base 16 back to a number 48001 48002 result.volume = view.getUint8(i) + view.getUint8(i + 1) / 8; 48003 i += 2; 48004 i += 2; 48005 result.matrix = new Uint32Array(data.subarray(i, i + 9 * 4)); 48006 i += 9 * 4; 48007 result.width = view.getUint16(i) + view.getUint16(i + 2) / 16; 48008 i += 4; 48009 result.height = view.getUint16(i) + view.getUint16(i + 2) / 16; 48010 return result; 48011 }, 48012 traf: function traf(data) { 48013 return { 48014 boxes: inspectMp4(data) 48015 }; 48016 }, 48017 trak: function trak(data) { 48018 return { 48019 boxes: inspectMp4(data) 48020 }; 48021 }, 48022 trex: function trex(data) { 48023 var view = new DataView(data.buffer, data.byteOffset, data.byteLength); 48024 return { 48025 version: data[0], 48026 flags: new Uint8Array(data.subarray(1, 4)), 48027 trackId: view.getUint32(4), 48028 defaultSampleDescriptionIndex: view.getUint32(8), 48029 defaultSampleDuration: view.getUint32(12), 48030 defaultSampleSize: view.getUint32(16), 48031 sampleDependsOn: data[20] & 0x03, 48032 sampleIsDependedOn: (data[21] & 0xc0) >> 6, 48033 sampleHasRedundancy: (data[21] & 0x30) >> 4, 48034 samplePaddingValue: (data[21] & 0x0e) >> 1, 48035 sampleIsDifferenceSample: !!(data[21] & 0x01), 48036 sampleDegradationPriority: view.getUint16(22) 48037 }; 48038 }, 48039 trun: function trun(data) { 48040 var result = { 48041 version: data[0], 48042 flags: new Uint8Array(data.subarray(1, 4)), 48043 samples: [] 48044 }, 48045 view = new DataView(data.buffer, data.byteOffset, data.byteLength), 48046 // Flag interpretation 48047 dataOffsetPresent = result.flags[2] & 0x01, 48048 // compare with 2nd byte of 0x1 48049 firstSampleFlagsPresent = result.flags[2] & 0x04, 48050 // compare with 2nd byte of 0x4 48051 sampleDurationPresent = result.flags[1] & 0x01, 48052 // compare with 2nd byte of 0x100 48053 sampleSizePresent = result.flags[1] & 0x02, 48054 // compare with 2nd byte of 0x200 48055 sampleFlagsPresent = result.flags[1] & 0x04, 48056 // compare with 2nd byte of 0x400 48057 sampleCompositionTimeOffsetPresent = result.flags[1] & 0x08, 48058 // compare with 2nd byte of 0x800 48059 sampleCount = view.getUint32(4), 48060 offset = 8, 48061 sample; 48062 48063 if (dataOffsetPresent) { 48064 // 32 bit signed integer 48065 result.dataOffset = view.getInt32(offset); 48066 offset += 4; 48067 } // Overrides the flags for the first sample only. The order of 48068 // optional values will be: duration, size, compositionTimeOffset 48069 48070 48071 if (firstSampleFlagsPresent && sampleCount) { 48072 sample = { 48073 flags: parseSampleFlags(data.subarray(offset, offset + 4)) 48074 }; 48075 offset += 4; 48076 48077 if (sampleDurationPresent) { 48078 sample.duration = view.getUint32(offset); 48079 offset += 4; 48080 } 48081 48082 if (sampleSizePresent) { 48083 sample.size = view.getUint32(offset); 48084 offset += 4; 48085 } 48086 48087 if (sampleCompositionTimeOffsetPresent) { 48088 // Note: this should be a signed int if version is 1 48089 sample.compositionTimeOffset = view.getUint32(offset); 48090 offset += 4; 48091 } 48092 48093 result.samples.push(sample); 48094 sampleCount--; 48095 } 48096 48097 while (sampleCount--) { 48098 sample = {}; 48099 48100 if (sampleDurationPresent) { 48101 sample.duration = view.getUint32(offset); 48102 offset += 4; 48103 } 48104 48105 if (sampleSizePresent) { 48106 sample.size = view.getUint32(offset); 48107 offset += 4; 48108 } 48109 48110 if (sampleFlagsPresent) { 48111 sample.flags = parseSampleFlags(data.subarray(offset, offset + 4)); 48112 offset += 4; 48113 } 48114 48115 if (sampleCompositionTimeOffsetPresent) { 48116 // Note: this should be a signed int if version is 1 48117 sample.compositionTimeOffset = view.getUint32(offset); 48118 offset += 4; 48119 } 48120 48121 result.samples.push(sample); 48122 } 48123 48124 return result; 48125 }, 48126 'url ': function url(data) { 48127 return { 48128 version: data[0], 48129 flags: new Uint8Array(data.subarray(1, 4)) 48130 }; 48131 }, 48132 vmhd: function vmhd(data) { 48133 var view = new DataView(data.buffer, data.byteOffset, data.byteLength); 48134 return { 48135 version: data[0], 48136 flags: new Uint8Array(data.subarray(1, 4)), 48137 graphicsmode: view.getUint16(4), 48138 opcolor: new Uint16Array([view.getUint16(6), view.getUint16(8), view.getUint16(10)]) 48139 }; 48140 } 48141 }; 48142 /** 48143 * Return a javascript array of box objects parsed from an ISO base 48144 * media file. 48145 * @param data {Uint8Array} the binary data of the media to be inspected 48146 * @return {array} a javascript array of potentially nested box objects 48147 */ 48148 48149 48150 inspectMp4 = function inspectMp4(data) { 48151 var i = 0, 48152 result = [], 48153 view, 48154 size, 48155 type, 48156 end, 48157 box; // Convert data from Uint8Array to ArrayBuffer, to follow Dataview API 48158 48159 var ab = new ArrayBuffer(data.length); 48160 var v = new Uint8Array(ab); 48161 48162 for (var z = 0; z < data.length; ++z) { 48163 v[z] = data[z]; 48164 } 48165 48166 view = new DataView(ab); 48167 48168 while (i < data.byteLength) { 48169 // parse box data 48170 size = view.getUint32(i); 48171 type = parseType$2(data.subarray(i + 4, i + 8)); 48172 end = size > 1 ? i + size : data.byteLength;
vendor: 4,410 bytes, lines 48172-48290
48172 // parse type-specific data 48173 48174 box = (parse$$1[type] || function (data) { 48175 return { 48176 data: data 48177 }; 48178 })(data.subarray(i + 8, end)); 48179 48180 box.size = size; 48181 box.type = type; // store this box and move to the next 48182 48183 result.push(box); 48184 i = end; 48185 } 48186 48187 return result; 48188 }; 48189 /** 48190 * Returns a textual representation of the javascript represtentation 48191 * of an MP4 file. You can use it as an alternative to 48192 * JSON.stringify() to compare inspected MP4s. 48193 * @param inspectedMp4 {array} the parsed array of boxes in an MP4 48194 * file 48195 * @param depth {number} (optional) the number of ancestor boxes of 48196 * the elements of inspectedMp4. Assumed to be zero if unspecified. 48197 * @return {string} a text representation of the parsed MP4 48198 */ 48199 48200 48201 _textifyMp = function textifyMp4(inspectedMp4, depth) { 48202 var indent; 48203 depth = depth || 0; 48204 indent = new Array(depth * 2 + 1).join(' '); // iterate over all the boxes 48205 48206 return inspectedMp4.map(function (box, index) { 48207 // list the box type first at the current indentation level 48208 return indent + box.type + '\n' + // the type is already included and handle child boxes separately 48209 Object.keys(box).filter(function (key) { 48210 return key !== 'type' && key !== 'boxes'; // output all the box properties 48211 }).map(function (key) { 48212 var prefix = indent + ' ' + key + ': ', 48213 value = box[key]; // print out raw bytes as hexademical 48214 48215 if (value instanceof Uint8Array || value instanceof Uint32Array) { 48216 var bytes = Array.prototype.slice.call(new Uint8Array(value.buffer, value.byteOffset, value.byteLength)).map(function (byte) { 48217 return ' ' + ('00' + byte.toString(16)).slice(-2); 48218 }).join('').match(/.{1,24}/g); 48219 48220 if (!bytes) { 48221 return prefix + '<>'; 48222 } 48223 48224 if (bytes.length === 1) { 48225 return prefix + '<' + bytes.join('').slice(1) + '>'; 48226 } 48227 48228 return prefix + '<\n' + bytes.map(function (line) { 48229 return indent + ' ' + line; 48230 }).join('\n') + '\n' + indent + ' >'; 48231 } // stringify generic objects 48232 48233 48234 return prefix + JSON.stringify(value, null, 2).split('\n').map(function (line, index) { 48235 if (index === 0) { 48236 return line; 48237 } 48238 48239 return indent + ' ' + line; 48240 }).join('\n'); 48241 }).join('\n') + ( // recursively textify the child boxes 48242 box.boxes ? '\n' + _textifyMp(box.boxes, depth + 1) : ''); 48243 }).join('\n'); 48244 }; 48245 48246 var mp4Inspector = { 48247 inspect: inspectMp4, 48248 textify: _textifyMp, 48249 parseTfdt: parse$$1.tfdt, 48250 parseHdlr: parse$$1.hdlr, 48251 parseTfhd: parse$$1.tfhd, 48252 parseTrun: parse$$1.trun, 48253 parseSidx: parse$$1.sidx 48254 }; 48255 var discardEmulationPreventionBytes$1 = captionPacketParser.discardEmulationPreventionBytes; 48256 var CaptionStream$1 = captionStream.CaptionStream; 48257 /** 48258 * Maps an offset in the mdat to a sample based on the the size of the samples. 48259 * Assumes that `parseSamples` has been called first. 48260 * 48261 * @param {Number} offset - The offset into the mdat 48262 * @param {Object[]} samples - An array of samples, parsed using `parseSamples` 48263 * @return {?Object} The matching sample, or null if no match was found. 48264 * 48265 * @see ISO-BMFF-12/2015, Section 8.8.8 48266 **/ 48267 48268 var mapToSample = function mapToSample(offset, samples) { 48269 var approximateOffset = offset; 48270 48271 for (var i = 0; i < samples.length; i++) { 48272 var sample = samples[i]; 48273 48274 if (approximateOffset < sample.size) { 48275 return sample; 48276 } 48277 48278 approximateOffset -= sample.size; 48279 } 48280 48281 return null; 48282 }; 48283 /** 48284 * Finds SEI nal units contained in a Media Data Box. 48285 * Assumes that `parseSamples` has been called first. 48286 * 48287 * @param {Uint8Array} avcStream - The bytes of the mdat 48288 * @param {Object[]} samples - The samples parsed out by `parseSamples` 48289 * @param {Number} trackId - The trackId of this video track 48290 * @return {Object[]}
vendor: 44,069 bytes, lines 48290-49619
48290 seiNals - the parsed SEI NALUs found. 48291 * The contents of the seiNal should match what is expected by 48292 * CaptionStream.push (nalUnitType, size, data, escapedRBSP, pts, dts) 48293 * 48294 * @see ISO-BMFF-12/2015, Section 8.1.1 48295 * @see Rec. ITU-T H.264, 7.3.2.3.1 48296 **/ 48297 48298 48299 var findSeiNals = function findSeiNals(avcStream, samples, trackId) { 48300 var avcView = new DataView(avcStream.buffer, avcStream.byteOffset, avcStream.byteLength), 48301 result = [], 48302 seiNal, 48303 i, 48304 length, 48305 lastMatchedSample; 48306 48307 for (i = 0; i + 4 < avcStream.length; i += length) { 48308 length = avcView.getUint32(i); 48309 i += 4; // Bail if this doesn't appear to be an H264 stream 48310 48311 if (length <= 0) { 48312 continue; 48313 } 48314 48315 switch (avcStream[i] & 0x1F) { 48316 case 0x06: 48317 var data = avcStream.subarray(i + 1, i + 1 + length); 48318 var matchingSample = mapToSample(i, samples); 48319 seiNal = { 48320 nalUnitType: 'sei_rbsp', 48321 size: length, 48322 data: data, 48323 escapedRBSP: discardEmulationPreventionBytes$1(data), 48324 trackId: trackId 48325 }; 48326 48327 if (matchingSample) { 48328 seiNal.pts = matchingSample.pts; 48329 seiNal.dts = matchingSample.dts; 48330 lastMatchedSample = matchingSample; 48331 } else { 48332 // If a matching sample cannot be found, use the last 48333 // sample's values as they should be as close as possible 48334 seiNal.pts = lastMatchedSample.pts; 48335 seiNal.dts = lastMatchedSample.dts; 48336 } 48337 48338 result.push(seiNal); 48339 break; 48340 48341 default: 48342 break; 48343 } 48344 } 48345 48346 return result; 48347 }; 48348 /** 48349 * Parses sample information out of Track Run Boxes and calculates 48350 * the absolute presentation and decode timestamps of each sample. 48351 * 48352 * @param {Array<Uint8Array>} truns - The Trun Run boxes to be parsed 48353 * @param {Number} baseMediaDecodeTime - base media decode time from tfdt 48354 @see ISO-BMFF-12/2015, Section 8.8.12 48355 * @param {Object} tfhd - The parsed Track Fragment Header 48356 * @see inspect.parseTfhd 48357 * @return {Object[]} the parsed samples 48358 * 48359 * @see ISO-BMFF-12/2015, Section 8.8.8 48360 **/ 48361 48362 48363 var parseSamples = function parseSamples(truns, baseMediaDecodeTime, tfhd) { 48364 var currentDts = baseMediaDecodeTime; 48365 var defaultSampleDuration = tfhd.defaultSampleDuration || 0; 48366 var defaultSampleSize = tfhd.defaultSampleSize || 0; 48367 var trackId = tfhd.trackId; 48368 var allSamples = []; 48369 truns.forEach(function (trun) { 48370 // Note: We currently do not parse the sample table as well 48371 // as the trun. It's possible some sources will require this. 48372 // moov > trak > mdia > minf > stbl 48373 var trackRun = mp4Inspector.parseTrun(trun); 48374 var samples = trackRun.samples; 48375 samples.forEach(function (sample) { 48376 if (sample.duration === undefined) { 48377 sample.duration = defaultSampleDuration; 48378 } 48379 48380 if (sample.size === undefined) { 48381 sample.size = defaultSampleSize; 48382 } 48383 48384 sample.trackId = trackId; 48385 sample.dts = currentDts; 48386 48387 if (sample.compositionTimeOffset === undefined) { 48388 sample.compositionTimeOffset = 0; 48389 } 48390 48391 sample.pts = currentDts + sample.compositionTimeOffset; 48392 currentDts += sample.duration; 48393 }); 48394 allSamples = allSamples.concat(samples); 48395 }); 48396 return allSamples; 48397 }; 48398 /** 48399 * Parses out caption nals from an FMP4 segment's video tracks. 48400 * 48401 * @param {Uint8Array} segment - The bytes of a single segment 48402 * @param {Number} videoTrackId - The trackId of a video track in the segment 48403 * @return {Object.<Number, Object[]>} A mapping of video trackId to 48404 * a list of seiNals found in that track 48405 **/ 48406 48407 48408 var parseCaptionNals = function parseCaptionNals(segment, videoTrackId) { 48409 // To get the samples 48410 var trafs = probe$$1.findBox(segment, ['moof', 'traf']); // To get SEI NAL units 48411 48412 var mdats = probe$$1.findBox(segment, ['mdat']); 48413 var captionNals = {}; 48414 var mdatTrafPairs = []; // Pair up each traf with a mdat as moofs and mdats are in pairs 48415 48416 mdats.forEach(function (mdat, index) { 48417 var matchingTraf = trafs[index]; 48418 mdatTrafPairs.push({ 48419 mdat: mdat, 48420 traf: matchingTraf 48421 }); 48422 }); 48423 mdatTrafPairs.forEach(function (pair) { 48424 var mdat = pair.mdat; 48425 var traf = pair.traf; 48426 var tfhd = probe$$1.findBox(traf, ['tfhd']); // Exactly 1 tfhd per traf 48427 48428 var headerInfo = mp4Inspector.parseTfhd(tfhd[0]); 48429 var trackId = headerInfo.trackId; 48430 var tfdt = probe$$1.findBox(traf, ['tfdt']); // Either 0 or 1 tfdt per traf 48431 48432 var baseMediaDecodeTime = tfdt.length > 0 ? mp4Inspector.parseTfdt(tfdt[0]).baseMediaDecodeTime : 0; 48433 var truns = probe$$1.findBox(traf, ['trun']); 48434 var samples; 48435 var seiNals; // Only parse video data for the chosen video track 48436 48437 if (videoTrackId === trackId && truns.length > 0) { 48438 samples = parseSamples(truns, baseMediaDecodeTime, headerInfo); 48439 seiNals = findSeiNals(mdat, samples, trackId); 48440 48441 if (!captionNals[trackId]) { 48442 captionNals[trackId] = []; 48443 } 48444 48445 captionNals[trackId] = captionNals[trackId].concat(seiNals); 48446 } 48447 }); 48448 return captionNals; 48449 }; 48450 /** 48451 * Parses out inband captions from an MP4 container and returns 48452 * caption objects that can be used by WebVTT and the TextTrack API. 48453 * @see https://developer.mozilla.org/en-US/docs/Web/API/VTTCue 48454 * @see https://developer.mozilla.org/en-US/docs/Web/API/TextTrack 48455 * Assumes that `probe.getVideoTrackIds` and `probe.timescale` have been called first 48456 * 48457 * @param {Uint8Array} segment - The fmp4 segment containing embedded captions 48458 * @param {Number} trackId - The id of the video track to parse 48459 * @param {Number} timescale - The timescale for the video track from the init segment 48460 * 48461 * @return {?Object[]} parsedCaptions - A list of captions or null if no video tracks 48462 * @return {Number} parsedCaptions[].startTime - The time to show the caption in seconds 48463 * @return {Number} parsedCaptions[].endTime - The time to stop showing the caption in seconds 48464 * @return {String} parsedCaptions[].text - The visible content of the caption 48465 **/ 48466 48467 48468 var parseEmbeddedCaptions = function parseEmbeddedCaptions(segment, trackId, timescale) { 48469 var seiNals; 48470 48471 if (!trackId) { 48472 return null; 48473 } 48474 48475 seiNals = parseCaptionNals(segment, trackId); 48476 return { 48477 seiNals: seiNals[trackId], 48478 timescale: timescale 48479 }; 48480 }; 48481 /** 48482 * Converts SEI NALUs into captions that can be used by video.js 48483 **/ 48484 48485 48486 var CaptionParser$$1 = function CaptionParser$$1() { 48487 var isInitialized = false; 48488 var captionStream$$1; // Stores segments seen before trackId and timescale are set 48489 48490 var segmentCache; // Stores video track ID of the track being parsed 48491 48492 var trackId; // Stores the timescale of the track being parsed 48493 48494 var timescale; // Stores captions parsed so far 48495 48496 var parsedCaptions; 48497 /** 48498 * A method to indicate whether a CaptionParser has been initalized 48499 * @returns {Boolean} 48500 **/ 48501 48502 this.isInitialized = function () { 48503 return isInitialized; 48504 }; 48505 /** 48506 * Initializes the underlying CaptionStream, SEI NAL parsing 48507 * and management, and caption collection 48508 **/ 48509 48510 48511 this.init = function () { 48512 captionStream$$1 = new CaptionStream$1(); 48513 isInitialized = true; // Collect dispatched captions 48514 48515 captionStream$$1.on('data', function (event) { 48516 // Convert to seconds in the source's timescale 48517 event.startTime = event.startPts / timescale; 48518 event.endTime = event.endPts / timescale; 48519 parsedCaptions.captions.push(event); 48520 parsedCaptions.captionStreams[event.stream] = true; 48521 }); 48522 }; 48523 /** 48524 * Determines if a new video track will be selected 48525 * or if the timescale changed 48526 * @return {Boolean} 48527 **/ 48528 48529 48530 this.isNewInit = function (videoTrackIds, timescales) { 48531 if (videoTrackIds && videoTrackIds.length === 0 || timescales && typeof timescales === 'object' && Object.keys(timescales).length === 0) { 48532 return false; 48533 } 48534 48535 return trackId !== videoTrackIds[0] || timescale !== timescales[trackId]; 48536 }; 48537 /** 48538 * Parses out SEI captions and interacts with underlying 48539 * CaptionStream to return dispatched captions 48540 * 48541 * @param {Uint8Array} segment - The fmp4 segment containing embedded captions 48542 * @param {Number[]} videoTrackIds - A list of video tracks found in the init segment 48543 * @param {Object.<Number, Number>} timescales - The timescales found in the init segment 48544 * @see parseEmbeddedCaptions 48545 * @see m2ts/caption-stream.js 48546 **/ 48547 48548 48549 this.parse = function (segment, videoTrackIds, timescales) { 48550 var parsedData; 48551 48552 if (!this.isInitialized()) { 48553 return null; // This is not likely to be a video segment 48554 } else if (!videoTrackIds || !timescales) { 48555 return null; 48556 } else if (this.isNewInit(videoTrackIds, timescales)) { 48557 // Use the first video track only as there is no 48558 // mechanism to switch to other video tracks 48559 trackId = videoTrackIds[0]; 48560 timescale = timescales[trackId]; // If an init segment has not been seen yet, hold onto segment 48561 // data until we have one 48562 } else if (!trackId || !timescale) { 48563 segmentCache.push(segment); 48564 return null; 48565 } // Now that a timescale and trackId is set, parse cached segments 48566 48567 48568 while (segmentCache.length > 0) { 48569 var cachedSegment = segmentCache.shift(); 48570 this.parse(cachedSegment, videoTrackIds, timescales); 48571 } 48572 48573 parsedData = parseEmbeddedCaptions(segment, trackId, timescale); 48574 48575 if (parsedData === null || !parsedData.seiNals) { 48576 return null; 48577 } 48578 48579 this.pushNals(parsedData.seiNals); // Force the parsed captions to be dispatched 48580 48581 this.flushStream(); 48582 return parsedCaptions; 48583 }; 48584 /** 48585 * Pushes SEI NALUs onto CaptionStream 48586 * @param {Object[]} nals - A list of SEI nals parsed using `parseCaptionNals` 48587 * Assumes that `parseCaptionNals` has been called first 48588 * @see m2ts/caption-stream.js 48589 **/ 48590 48591 48592 this.pushNals = function (nals) { 48593 if (!this.isInitialized() || !nals || nals.length === 0) { 48594 return null; 48595 } 48596 48597 nals.forEach(function (nal) { 48598 captionStream$$1.push(nal); 48599 }); 48600 }; 48601 /** 48602 * Flushes underlying CaptionStream to dispatch processed, displayable captions 48603 * @see m2ts/caption-stream.js 48604 **/ 48605 48606 48607 this.flushStream = function () { 48608 if (!this.isInitialized()) { 48609 return null; 48610 } 48611 48612 captionStream$$1.flush(); 48613 }; 48614 /** 48615 * Reset caption buckets for new data 48616 **/ 48617 48618 48619 this.clearParsedCaptions = function () { 48620 parsedCaptions.captions = []; 48621 parsedCaptions.captionStreams = {}; 48622 }; 48623 /** 48624 * Resets underlying CaptionStream 48625 * @see m2ts/caption-stream.js 48626 **/ 48627 48628 48629 this.resetCaptionStream = function () { 48630 if (!this.isInitialized()) { 48631 return null; 48632 } 48633 48634 captionStream$$1.reset(); 48635 }; 48636 /** 48637 * Convenience method to clear all captions flushed from the 48638 * CaptionStream and still being parsed 48639 * @see m2ts/caption-stream.js 48640 **/ 48641 48642 48643 this.clearAllCaptions = function () { 48644 this.clearParsedCaptions(); 48645 this.resetCaptionStream(); 48646 }; 48647 /** 48648 * Reset caption parser 48649 **/ 48650 48651 48652 this.reset = function () { 48653 segmentCache = []; 48654 trackId = null; 48655 timescale = null; 48656 48657 if (!parsedCaptions) { 48658 parsedCaptions = { 48659 captions: [], 48660 // CC1, CC2, CC3, CC4 48661 captionStreams: {} 48662 }; 48663 } else { 48664 this.clearParsedCaptions(); 48665 } 48666 48667 this.resetCaptionStream(); 48668 }; 48669 48670 this.reset(); 48671 }; 48672 48673 var captionParser = CaptionParser$$1; 48674 var mp4$$1 = { 48675 generator: mp4Generator, 48676 probe: probe$$1, 48677 Transmuxer: transmuxer.Transmuxer, 48678 AudioSegmentStream: transmuxer.AudioSegmentStream, 48679 VideoSegmentStream: transmuxer.VideoSegmentStream, 48680 CaptionParser: captionParser 48681 }; 48682 48683 var classCallCheck = function classCallCheck(instance, Constructor) { 48684 if (!(instance instanceof Constructor)) { 48685 throw new TypeError("Cannot call a class as a function"); 48686 } 48687 }; 48688 48689 var createClass = function () { 48690 function defineProperties(target, props) { 48691 for (var i = 0; i < props.length; i++) { 48692 var descriptor = props[i]; 48693 descriptor.enumerable = descriptor.enumerable || false; 48694 descriptor.configurable = true; 48695 if ("value" in descriptor) descriptor.writable = true; 48696 Object.defineProperty(target, descriptor.key, descriptor); 48697 } 48698 } 48699 48700 return function (Constructor, protoProps, staticProps) { 48701 if (protoProps) defineProperties(Constructor.prototype, protoProps); 48702 if (staticProps) defineProperties(Constructor, staticProps); 48703 return Constructor; 48704 }; 48705 }(); 48706 /** 48707 * @file transmuxer-worker.js 48708 */ 48709 48710 /** 48711 * Re-emits transmuxer events by converting them into messages to the 48712 * world outside the worker. 48713 * 48714 * @param {Object} transmuxer the transmuxer to wire events on 48715 * @private 48716 */ 48717 48718 48719 var wireTransmuxerEvents = function wireTransmuxerEvents(self, transmuxer) { 48720 transmuxer.on('data', function (segment) { 48721 // transfer ownership of the underlying ArrayBuffer 48722 // instead of doing a copy to save memory 48723 // ArrayBuffers are transferable but generic TypedArrays are not 48724 // @link https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers#Passing_data_by_transferring_ownership_(transferable_objects) 48725 var initArray = segment.initSegment; 48726 segment.initSegment = { 48727 data: initArray.buffer, 48728 byteOffset: initArray.byteOffset, 48729 byteLength: initArray.byteLength 48730 }; 48731 var typedArray = segment.data; 48732 segment.data = typedArray.buffer; 48733 self.postMessage({ 48734 action: 'data', 48735 segment: segment, 48736 byteOffset: typedArray.byteOffset, 48737 byteLength: typedArray.byteLength 48738 }, [segment.data]); 48739 }); 48740 48741 if (transmuxer.captionStream) { 48742 transmuxer.captionStream.on('data', function (caption) { 48743 self.postMessage({ 48744 action: 'caption', 48745 data: caption 48746 }); 48747 }); 48748 } 48749 48750 transmuxer.on('done', function (data) { 48751 self.postMessage({ 48752 action: 'done' 48753 }); 48754 }); 48755 transmuxer.on('gopInfo', function (gopInfo) { 48756 self.postMessage({ 48757 action: 'gopInfo', 48758 gopInfo: gopInfo 48759 }); 48760 }); 48761 transmuxer.on('videoSegmentTimingInfo', function (videoSegmentTimingInfo) { 48762 self.postMessage({ 48763 action: 'videoSegmentTimingInfo', 48764 videoSegmentTimingInfo: videoSegmentTimingInfo 48765 }); 48766 }); 48767 }; 48768 /** 48769 * All incoming messages route through this hash. If no function exists 48770 * to handle an incoming message, then we ignore the message. 48771 * 48772 * @class MessageHandlers 48773 * @param {Object} options the options to initialize with 48774 */ 48775 48776 48777 var MessageHandlers = function () { 48778 function MessageHandlers(self, options) { 48779 classCallCheck(this, MessageHandlers); 48780 this.options = options || {}; 48781 this.self = self; 48782 this.init(); 48783 } 48784 /** 48785 * initialize our web worker and wire all the events. 48786 */ 48787 48788 48789 createClass(MessageHandlers, [{ 48790 key: 'init', 48791 value: function init() { 48792 if (this.transmuxer) { 48793 this.transmuxer.dispose(); 48794 } 48795 48796 this.transmuxer = new mp4$$1.Transmuxer(this.options); 48797 wireTransmuxerEvents(this.self, this.transmuxer); 48798 } 48799 /** 48800 * Adds data (a ts segment) to the start of the transmuxer pipeline for 48801 * processing. 48802 * 48803 * @param {ArrayBuffer} data data to push into the muxer 48804 */ 48805 48806 }, { 48807 key: 'push', 48808 value: function push(data) { 48809 // Cast array buffer to correct type for transmuxer 48810 var segment = new Uint8Array(data.data, data.byteOffset, data.byteLength); 48811 this.transmuxer.push(segment); 48812 } 48813 /** 48814 * Recreate the transmuxer so that the next segment added via `push` 48815 * start with a fresh transmuxer. 48816 */ 48817 48818 }, { 48819 key: 'reset', 48820 value: function reset() { 48821 this.init(); 48822 } 48823 /** 48824 * Set the value that will be used as the `baseMediaDecodeTime` time for the 48825 * next segment pushed in. Subsequent segments will have their `baseMediaDecodeTime` 48826 * set relative to the first based on the PTS values. 48827 * 48828 * @param {Object} data used to set the timestamp offset in the muxer 48829 */ 48830 48831 }, { 48832 key: 'setTimestampOffset', 48833 value: function setTimestampOffset(data) { 48834 var timestampOffset = data.timestampOffset || 0; 48835 this.transmuxer.setBaseMediaDecodeTime(Math.round(timestampOffset * 90000)); 48836 } 48837 }, { 48838 key: 'setAudioAppendStart', 48839 value: function setAudioAppendStart(data) { 48840 this.transmuxer.setAudioAppendStart(Math.ceil(data.appendStart * 90000)); 48841 } 48842 /** 48843 * Forces the pipeline to finish processing the last segment and emit it's 48844 * results. 48845 * 48846 * @param {Object} data event data, not really used 48847 */ 48848 48849 }, { 48850 key: 'flush', 48851 value: function flush(data) { 48852 this.transmuxer.flush(); 48853 } 48854 }, { 48855 key: 'resetCaptions', 48856 value: function resetCaptions() { 48857 this.transmuxer.resetCaptions(); 48858 } 48859 }, { 48860 key: 'alignGopsWith', 48861 value: function alignGopsWith(data) { 48862 this.transmuxer.alignGopsWith(data.gopsToAlignWith.slice()); 48863 } 48864 }]); 48865 return MessageHandlers; 48866 }(); 48867 /** 48868 * Our web wroker interface so that things can talk to mux.js 48869 * that will be running in a web worker. the scope is passed to this by 48870 * webworkify. 48871 * 48872 * @param {Object} self the scope for the web worker 48873 */ 48874 48875 48876 var TransmuxerWorker = function TransmuxerWorker(self) { 48877 self.onmessage = function (event) { 48878 if (event.data.action === 'init' && event.data.options) { 48879 this.messageHandlers = new MessageHandlers(self, event.data.options); 48880 return; 48881 } 48882 48883 if (!this.messageHandlers) { 48884 this.messageHandlers = new MessageHandlers(self); 48885 } 48886 48887 if (event.data && event.data.action && event.data.action !== 'init') { 48888 if (this.messageHandlers[event.data.action]) { 48889 this.messageHandlers[event.data.action](event.data); 48890 } 48891 } 48892 }; 48893 }; 48894 48895 var transmuxerWorker = new TransmuxerWorker(self); 48896 return transmuxerWorker; 48897 }(); 48898 }); 48899 /** 48900 * @file - codecs.js - Handles tasks regarding codec strings such as translating them to 48901 * codec strings, or translating codec strings into objects that can be examined. 48902 */ 48903 // Default codec parameters if none were provided for video and/or audio 48904 48905 var defaultCodecs = { 48906 videoCodec: 'avc1', 48907 videoObjectTypeIndicator: '.4d400d', 48908 // AAC-LC 48909 audioProfile: '2' 48910 }; 48911 /** 48912 * Replace the old apple-style `avc1.<dd>.<dd>` codec string with the standard 48913 * `avc1.<hhhhhh>` 48914 * 48915 * @param {Array} codecs an array of codec strings to fix 48916 * @return {Array} the translated codec array 48917 * @private 48918 */ 48919 48920 var translateLegacyCodecs = function translateLegacyCodecs(codecs) { 48921 return codecs.map(function (codec) { 48922 return codec.replace(/avc1\.(\d+)\.(\d+)/i, function (orig, profile, avcLevel) { 48923 var profileHex = ('00' + Number(profile).toString(16)).slice(-2); 48924 var avcLevelHex = ('00' + Number(avcLevel).toString(16)).slice(-2); 48925 return 'avc1.' + profileHex + '00' + avcLevelHex; 48926 }); 48927 }); 48928 }; 48929 /** 48930 * Parses a codec string to retrieve the number of codecs specified, 48931 * the video codec and object type indicator, and the audio profile. 48932 */ 48933 48934 48935 var parseCodecs = function parseCodecs() { 48936 var codecs = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ''; 48937 var result = { 48938 codecCount: 0 48939 }; 48940 var parsed = void 0; 48941 result.codecCount = codecs.split(',').length; 48942 result.codecCount = result.codecCount || 2; // parse the video codec 48943 48944 parsed = /(^|\s|,)+(avc[13])([^ ,]*)/i.exec(codecs); 48945 48946 if (parsed) { 48947 result.videoCodec = parsed[2]; 48948 result.videoObjectTypeIndicator = parsed[3]; 48949 } // parse the last field of the audio codec 48950 48951 48952 result.audioProfile = /(^|\s|,)+mp4a.[0-9A-Fa-f]+\.([0-9A-Fa-f]+)/i.exec(codecs); 48953 result.audioProfile = result.audioProfile && result.audioProfile[2]; 48954 return result; 48955 }; 48956 /** 48957 * Replace codecs in the codec string with the old apple-style `avc1.<dd>.<dd>` to the 48958 * standard `avc1.<hhhhhh>`. 48959 * 48960 * @param codecString {String} the codec string 48961 * @return {String} the codec string with old apple-style codecs replaced 48962 * 48963 * @private 48964 */ 48965 48966 48967 var mapLegacyAvcCodecs = function mapLegacyAvcCodecs(codecString) { 48968 return codecString.replace(/avc1\.(\d+)\.(\d+)/i, function (match) { 48969 return translateLegacyCodecs([match])[0]; 48970 }); 48971 }; 48972 /** 48973 * Build a media mime-type string from a set of parameters 48974 * @param {String} type either 'audio' or 'video' 48975 * @param {String} container either 'mp2t' or 'mp4' 48976 * @param {Array} codecs an array of codec strings to add 48977 * @return {String} a valid media mime-type 48978 */ 48979 48980 48981 var makeMimeTypeString = function makeMimeTypeString(type, container, codecs) { 48982 // The codecs array is filtered so that falsey values are 48983 // dropped and don't cause Array#join to create spurious 48984 // commas 48985 return type + '/' + container + '; codecs="' + codecs.filter(function (c) { 48986 return !!c; 48987 }).join(', ') + '"'; 48988 }; 48989 /** 48990 * Returns the type container based on information in the playlist 48991 * @param {Playlist} media the current media playlist 48992 * @return {String} a valid media container type 48993 */ 48994 48995 48996 var getContainerType = function getContainerType(media) { 48997 // An initialization segment means the media playlist is an iframe 48998 // playlist or is using the mp4 container. We don't currently 48999 // support iframe playlists, so assume this is signalling mp4 49000 // fragments. 49001 if (media.segments && media.segments.length && media.segments[0].map) { 49002 return 'mp4'; 49003 } 49004 49005 return 'mp2t'; 49006 }; 49007 /** 49008 * Returns a set of codec strings parsed from the playlist or the default 49009 * codec strings if no codecs were specified in the playlist 49010 * @param {Playlist} media the current media playlist 49011 * @return {Object} an object with the video and audio codecs 49012 */ 49013 49014 49015 var getCodecs = function getCodecs(media) { 49016 // if the codecs were explicitly specified, use them instead of the 49017 // defaults 49018 var mediaAttributes = media.attributes || {}; 49019 49020 if (mediaAttributes.CODECS) { 49021 return parseCodecs(mediaAttributes.CODECS); 49022 } 49023 49024 return defaultCodecs; 49025 }; 49026 49027 var audioProfileFromDefault = function audioProfileFromDefault(master, audioGroupId) { 49028 if (!master.mediaGroups.AUDIO || !audioGroupId) { 49029 return null; 49030 } 49031 49032 var audioGroup = master.mediaGroups.AUDIO[audioGroupId]; 49033 49034 if (!audioGroup) { 49035 return null; 49036 } 49037 49038 for (var name in audioGroup) { 49039 var audioType = audioGroup[name]; 49040 49041 if (audioType.default && audioType.playlists) { 49042 // codec should be the same for all playlists within the audio type 49043 return parseCodecs(audioType.playlists[0].attributes.CODECS).audioProfile; 49044 } 49045 } 49046 49047 return null; 49048 }; 49049 /** 49050 * Calculates the MIME type strings for a working configuration of 49051 * SourceBuffers to play variant streams in a master playlist. If 49052 * there is no possible working configuration, an empty array will be 49053 * returned. 49054 * 49055 * @param master {Object} the m3u8 object for the master playlist 49056 * @param media {Object} the m3u8 object for the variant playlist 49057 * @return {Array} the MIME type strings. If the array has more than 49058 * one entry, the first element should be applied to the video 49059 * SourceBuffer and the second to the audio SourceBuffer. 49060 * 49061 * @private 49062 */ 49063 49064 49065 var mimeTypesForPlaylist = function mimeTypesForPlaylist(master, media) { 49066 var containerType = getContainerType(media); 49067 var codecInfo = getCodecs(media); 49068 var mediaAttributes = media.attributes || {}; // Default condition for a traditional HLS (no demuxed audio/video) 49069 49070 var isMuxed = true; 49071 var isMaat = false; 49072 49073 if (!media) { 49074 // Not enough information 49075 return []; 49076 } 49077 49078 if (master.mediaGroups.AUDIO && mediaAttributes.AUDIO) { 49079 var audioGroup = master.mediaGroups.AUDIO[mediaAttributes.AUDIO]; // Handle the case where we are in a multiple-audio track scenario 49080 49081 if (audioGroup) { 49082 isMaat = true; // Start with the everything demuxed then... 49083 49084 isMuxed = false; // ...check to see if any audio group tracks are muxed (ie. lacking a uri) 49085 49086 for (var groupId in audioGroup) { 49087 // either a uri is present (if the case of HLS and an external playlist), or 49088 // playlists is present (in the case of DASH where we don't have external audio 49089 // playlists) 49090 if (!audioGroup[groupId].uri && !audioGroup[groupId].playlists) { 49091 isMuxed = true; 49092 break; 49093 } 49094 } 49095 } 49096 } // HLS with multiple-audio tracks must always get an audio codec. 49097 // Put another way, there is no way to have a video-only multiple-audio HLS! 49098 49099 49100 if (isMaat && !codecInfo.audioProfile) { 49101 if (!isMuxed) { 49102 // It is possible for codecs to be specified on the audio media group playlist but 49103 // not on the rendition playlist. This is mostly the case for DASH, where audio and 49104 // video are always separate (and separately specified). 49105 codecInfo.audioProfile = audioProfileFromDefault(master, mediaAttributes.AUDIO); 49106 } 49107 49108 if (!codecInfo.audioProfile) { 49109 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)'); 49110 codecInfo.audioProfile = defaultCodecs.audioProfile; 49111 } 49112 } // Generate the final codec strings from the codec object generated above 49113 49114 49115 var codecStrings = {}; 49116 49117 if (codecInfo.videoCodec) { 49118 codecStrings.video = '' + codecInfo.videoCodec + codecInfo.videoObjectTypeIndicator; 49119 } 49120 49121 if (codecInfo.audioProfile) { 49122 codecStrings.audio = 'mp4a.40.' + codecInfo.audioProfile; 49123 } // Finally, make and return an array with proper mime-types depending on 49124 // the configuration 49125 49126 49127 var justAudio = makeMimeTypeString('audio', containerType, [codecStrings.audio]); 49128 var justVideo = makeMimeTypeString('video', containerType, [codecStrings.video]); 49129 var bothVideoAudio = makeMimeTypeString('video', containerType, [codecStrings.video, codecStrings.audio]); 49130 49131 if (isMaat) { 49132 if (!isMuxed && codecStrings.video) { 49133 return [justVideo, justAudio]; 49134 } 49135 49136 if (!isMuxed && !codecStrings.video) { 49137 // There is no muxed content and no video codec string, so this is an audio only 49138 // stream with alternate audio. 49139 return [justAudio, justAudio]; 49140 } // There exists the possiblity that this will return a `video/container` 49141 // mime-type for the first entry in the array even when there is only audio. 49142 // This doesn't appear to be a problem and simplifies the code. 49143 49144 49145 return [bothVideoAudio, justAudio]; 49146 } // If there is no video codec at all, always just return a single 49147 // audio/<container> mime-type 49148 49149 49150 if (!codecStrings.video) { 49151 return [justAudio]; 49152 } // When not using separate audio media groups, audio and video is 49153 // *always* muxed 49154 49155 49156 return [bothVideoAudio]; 49157 }; 49158 /** 49159 * Parse a content type header into a type and parameters 49160 * object 49161 * 49162 * @param {String} type the content type header 49163 * @return {Object} the parsed content-type 49164 * @private 49165 */ 49166 49167 49168 var parseContentType = function parseContentType(type) { 49169 var object = { 49170 type: '', 49171 parameters: {} 49172 }; 49173 var parameters = type.trim().split(';'); // first parameter should always be content-type 49174 49175 object.type = parameters.shift().trim(); 49176 parameters.forEach(function (parameter) { 49177 var pair = parameter.trim().split('='); 49178 49179 if (pair.length > 1) { 49180 var name = pair[0].replace(/"/g, '').trim(); 49181 var value = pair[1].replace(/"/g, '').trim(); 49182 object.parameters[name] = value; 49183 } 49184 }); 49185 return object; 49186 }; 49187 /** 49188 * Check if a codec string refers to an audio codec. 49189 * 49190 * @param {String} codec codec string to check 49191 * @return {Boolean} if this is an audio codec 49192 * @private 49193 */ 49194 49195 49196 var isAudioCodec = function isAudioCodec(codec) { 49197 return /mp4a\.\d+.\d+/i.test(codec); 49198 }; 49199 /** 49200 * Check if a codec string refers to a video codec. 49201 * 49202 * @param {String} codec codec string to check 49203 * @return {Boolean} if this is a video codec 49204 * @private 49205 */ 49206 49207 49208 var isVideoCodec = function isVideoCodec(codec) { 49209 return /avc1\.[\da-f]+/i.test(codec); 49210 }; 49211 /** 49212 * Returns a list of gops in the buffer that have a pts value of 3 seconds or more in 49213 * front of current time. 49214 * 49215 * @param {Array} buffer 49216 * The current buffer of gop information 49217 * @param {Number} currentTime 49218 * The current time 49219 * @param {Double} mapping 49220 * Offset to map display time to stream presentation time 49221 * @return {Array} 49222 * List of gops considered safe to append over 49223 */ 49224 49225 49226 var gopsSafeToAlignWith = function gopsSafeToAlignWith(buffer, currentTime, mapping) { 49227 if (typeof currentTime === 'undefined' || currentTime === null || !buffer.length) { 49228 return []; 49229 } // pts value for current time + 3 seconds to give a bit more wiggle room 49230 49231 49232 var currentTimePts = Math.ceil((currentTime - mapping + 3) * 90000); 49233 var i = void 0; 49234 49235 for (i = 0; i < buffer.length; i++) { 49236 if (buffer[i].pts > currentTimePts) { 49237 break; 49238 } 49239 } 49240 49241 return buffer.slice(i); 49242 }; 49243 /** 49244 * Appends gop information (timing and byteLength) received by the transmuxer for the 49245 * gops appended in the last call to appendBuffer 49246 * 49247 * @param {Array} buffer 49248 * The current buffer of gop information 49249 * @param {Array} gops 49250 * List of new gop information 49251 * @param {boolean} replace 49252 * If true, replace the buffer with the new gop information. If false, append the 49253 * new gop information to the buffer in the right location of time. 49254 * @return {Array} 49255 * Updated list of gop information 49256 */ 49257 49258 49259 var updateGopBuffer = function updateGopBuffer(buffer, gops, replace) { 49260 if (!gops.length) { 49261 return buffer; 49262 } 49263 49264 if (replace) { 49265 // If we are in safe append mode, then completely overwrite the gop buffer 49266 // with the most recent appeneded data. This will make sure that when appending 49267 // future segments, we only try to align with gops that are both ahead of current 49268 // time and in the last segment appended. 49269 return gops.slice(); 49270 } 49271 49272 var start = gops[0].pts; 49273 var i = 0; 49274 49275 for (i; i < buffer.length; i++) { 49276 if (buffer[i].pts >= start) { 49277 break; 49278 } 49279 } 49280 49281 return buffer.slice(0, i).concat(gops); 49282 }; 49283 /** 49284 * Removes gop information in buffer that overlaps with provided start and end 49285 * 49286 * @param {Array} buffer 49287 * The current buffer of gop information 49288 * @param {Double} start 49289 * position to start the remove at 49290 * @param {Double} end 49291 * position to end the remove at 49292 * @param {Double} mapping 49293 * Offset to map display time to stream presentation time 49294 */ 49295 49296 49297 var removeGopBuffer = function removeGopBuffer(buffer, start, end, mapping) { 49298 var startPts = Math.ceil((start - mapping) * 90000); 49299 var endPts = Math.ceil((end - mapping) * 90000); 49300 var updatedBuffer = buffer.slice(); 49301 var i = buffer.length; 49302 49303 while (i--) { 49304 if (buffer[i].pts <= endPts) { 49305 break; 49306 } 49307 } 49308 49309 if (i === -1) { 49310 // no removal because end of remove range is before start of buffer 49311 return updatedBuffer; 49312 } 49313 49314 var j = i + 1; 49315 49316 while (j--) { 49317 if (buffer[j].pts <= startPts) { 49318 break; 49319 } 49320 } // clamp remove range start to 0 index 49321 49322 49323 j = Math.max(j, 0); 49324 updatedBuffer.splice(j, i - j + 1); 49325 return updatedBuffer; 49326 }; 49327 49328 var buffered = function buffered(videoBuffer, audioBuffer, audioDisabled) { 49329 var start = null; 49330 var end = null; 49331 var arity = 0; 49332 var extents = []; 49333 var ranges = []; // neither buffer has been created yet 49334 49335 if (!videoBuffer && !audioBuffer) { 49336 return videojs$1.createTimeRange(); 49337 } // only one buffer is configured 49338 49339 49340 if (!videoBuffer) { 49341 return audioBuffer.buffered; 49342 } 49343 49344 if (!audioBuffer) { 49345 return videoBuffer.buffered; 49346 } // both buffers are configured 49347 49348 49349 if (audioDisabled) { 49350 return videoBuffer.buffered; 49351 } // both buffers are empty 49352 49353 49354 if (videoBuffer.buffered.length === 0 && audioBuffer.buffered.length === 0) { 49355 return videojs$1.createTimeRange(); 49356 } // Handle the case where we have both buffers and create an 49357 // intersection of the two 49358 49359 49360 var videoBuffered = videoBuffer.buffered; 49361 var audioBuffered = audioBuffer.buffered; 49362 var count = videoBuffered.length; // A) Gather up all start and end times 49363 49364 while (count--) { 49365 extents.push({ 49366 time: videoBuffered.start(count), 49367 type: 'start' 49368 }); 49369 extents.push({ 49370 time: videoBuffered.end(count), 49371 type: 'end' 49372 }); 49373 } 49374 49375 count = audioBuffered.length; 49376 49377 while (count--) { 49378 extents.push({ 49379 time: audioBuffered.start(count), 49380 type: 'start' 49381 }); 49382 extents.push({ 49383 time: audioBuffered.end(count), 49384 type: 'end' 49385 }); 49386 } // B) Sort them by time 49387 49388 49389 extents.sort(function (a, b) { 49390 return a.time - b.time; 49391 }); // C) Go along one by one incrementing arity for start and decrementing 49392 // arity for ends 49393 49394 for (count = 0; count < extents.length; count++) { 49395 if (extents[count].type === 'start') { 49396 arity++; // D) If arity is ever incremented to 2 we are entering an 49397 // overlapping range 49398 49399 if (arity === 2) { 49400 start = extents[count].time; 49401 } 49402 } else if (extents[count].type === 'end') { 49403 arity--; // E) If arity is ever decremented to 1 we leaving an 49404 // overlapping range 49405 49406 if (arity === 1) { 49407 end = extents[count].time; 49408 } 49409 } // F) Record overlapping ranges 49410 49411 49412 if (start !== null && end !== null) { 49413 ranges.push([start, end]); 49414 start = null; 49415 end = null; 49416 } 49417 } 49418 49419 return videojs$1.createTimeRanges(ranges); 49420 }; 49421 /** 49422 * @file virtual-source-buffer.js 49423 */ 49424 49425 49426 var ONE_SECOND_IN_TS$3 = 90000; // We create a wrapper around the SourceBuffer so that we can manage the 49427 // state of the `updating` property manually. We have to do this because 49428 // Firefox changes `updating` to false long before triggering `updateend` 49429 // events and that was causing strange problems in videojs-contrib-hls 49430 49431 var makeWrappedSourceBuffer = function makeWrappedSourceBuffer(mediaSource, mimeType) { 49432 var sourceBuffer = mediaSource.addSourceBuffer(mimeType); 49433 var wrapper = Object.create(null); 49434 wrapper.updating = false; 49435 wrapper.realBuffer_ = sourceBuffer; 49436 49437 var _loop = function _loop(key) { 49438 if (typeof sourceBuffer[key] === 'function') { 49439 wrapper[key] = function () { 49440 return sourceBuffer[key].apply(sourceBuffer, arguments); 49441 }; 49442 } else if (typeof wrapper[key] === 'undefined') { 49443 Object.defineProperty(wrapper, key, { 49444 get: function get$$1() { 49445 return sourceBuffer[key]; 49446 }, 49447 set: function set$$1(v) { 49448 return sourceBuffer[key] = v; 49449 } 49450 }); 49451 } 49452 }; 49453 49454 for (var key in sourceBuffer) { 49455 _loop(key); 49456 } 49457 49458 return wrapper; 49459 }; 49460 /** 49461 * VirtualSourceBuffers exist so that we can transmux non native formats 49462 * into a native format, but keep the same api as a native source buffer. 49463 * It creates a transmuxer, that works in its own thread (a web worker) and 49464 * that transmuxer muxes the data into a native format. VirtualSourceBuffer will 49465 * then send all of that data to the naive sourcebuffer so that it is 49466 * indestinguishable from a natively supported format. 49467 * 49468 * @param {HtmlMediaSource} mediaSource the parent mediaSource 49469 * @param {Array} codecs array of codecs that we will be dealing with 49470 * @class VirtualSourceBuffer 49471 * @extends video.js.EventTarget 49472 */ 49473 49474 49475 var VirtualSourceBuffer = function (_videojs$EventTarget) { 49476 inherits$1(VirtualSourceBuffer, _videojs$EventTarget); 49477 49478 function VirtualSourceBuffer(mediaSource, codecs) { 49479 classCallCheck$1(this, VirtualSourceBuffer); 49480 49481 var _this = possibleConstructorReturn$1(this, (VirtualSourceBuffer.__proto__ || Object.getPrototypeOf(VirtualSourceBuffer)).call(this, videojs$1.EventTarget)); 49482 49483 _this.timestampOffset_ = 0; 49484 _this.pendingBuffers_ = []; 49485 _this.bufferUpdating_ = false; 49486 _this.mediaSource_ = mediaSource; 49487 _this.codecs_ = codecs; 49488 _this.audioCodec_ = null; 49489 _this.videoCodec_ = null; 49490 _this.audioDisabled_ = false; 49491 _this.appendAudioInitSegment_ = true; 49492 _this.gopBuffer_ = []; 49493 _this.timeMapping_ = 0; 49494 _this.safeAppend_ = videojs$1.browser.IE_VERSION >= 11; 49495 var options = { 49496 remux: false, 49497 alignGopsAtEnd: _this.safeAppend_ 49498 }; 49499 49500 _this.codecs_.forEach(function (codec) { 49501 if (isAudioCodec(codec)) { 49502 _this.audioCodec_ = codec; 49503 } else if (isVideoCodec(codec)) { 49504 _this.videoCodec_ = codec; 49505 } 49506 }); // append muxed segments to their respective native buffers as 49507 // soon as they are available 49508 49509 49510 _this.transmuxer_ = new TransmuxWorker(); 49511 49512 _this.transmuxer_.postMessage({ 49513 action: 'init', 49514 options: options 49515 }); 49516 49517 _this.transmuxer_.onmessage = function (event) { 49518 if (event.data.action === 'data') { 49519 return _this.data_(event); 49520 } 49521 49522 if (event.data.action === 'done') { 49523 return _this.done_(event); 49524 } 49525 49526 if (event.data.action === 'gopInfo') { 49527 return _this.appendGopInfo_(event); 49528 } 49529 49530 if (event.data.action === 'videoSegmentTimingInfo') { 49531 return _this.videoSegmentTimingInfo_(event.data.videoSegmentTimingInfo); 49532 } 49533 }; // this timestampOffset is a property with the side-effect of resetting 49534 // baseMediaDecodeTime in the transmuxer on the setter 49535 49536 49537 Object.defineProperty(_this, 'timestampOffset', { 49538 get: function get$$1() { 49539 return this.timestampOffset_; 49540 }, 49541 set: function set$$1(val) { 49542 if (typeof val === 'number' && val >= 0) { 49543 this.timestampOffset_ = val; 49544 this.appendAudioInitSegment_ = true; // reset gop buffer on timestampoffset as this signals a change in timeline 49545 49546 this.gopBuffer_.length = 0; 49547 this.timeMapping_ = 0; // We have to tell the transmuxer to set the baseMediaDecodeTime to 49548 // the desired timestampOffset for the next segment 49549 49550 this.transmuxer_.postMessage({ 49551 action: 'setTimestampOffset', 49552 timestampOffset: val 49553 }); 49554 } 49555 } 49556 }); // setting the append window affects both source buffers 49557 49558 Object.defineProperty(_this, 'appendWindowStart', { 49559 get: function get$$1() { 49560 return (this.videoBuffer_ || this.audioBuffer_).appendWindowStart; 49561 }, 49562 set: function set$$1(start) { 49563 if (this.videoBuffer_) { 49564 this.videoBuffer_.appendWindowStart = start; 49565 } 49566 49567 if (this.audioBuffer_) { 49568 this.audioBuffer_.appendWindowStart = start; 49569 } 49570 } 49571 }); // this buffer is "updating" if either of its native buffers are 49572 49573 Object.defineProperty(_this, 'updating', { 49574 get: function get$$1() { 49575 return !!(this.bufferUpdating_ || !this.audioDisabled_ && this.audioBuffer_ && this.audioBuffer_.updating || this.videoBuffer_ && this.videoBuffer_.updating); 49576 } 49577 }); // the buffered property is the intersection of the buffered 49578 // ranges of the native source buffers 49579 49580 Object.defineProperty(_this, 'buffered', { 49581 get: function get$$1() { 49582 return buffered(this.videoBuffer_, this.audioBuffer_, this.audioDisabled_); 49583 } 49584 }); 49585 return _this; 49586 } 49587 /** 49588 * When we get a data event from the transmuxer 49589 * we call this function and handle the data that 49590 * was sent to us 49591 * 49592 * @private 49593 * @param {Event} event the data event from the transmuxer 49594 */ 49595 49596 49597 createClass$1(VirtualSourceBuffer, [{ 49598 key: 'data_', 49599 value: function data_(event) { 49600 var segment = event.data.segment; // Cast ArrayBuffer to TypedArray 49601 49602 segment.data = new Uint8Array(segment.data, event.data.byteOffset, event.data.byteLength); 49603 segment.initSegment = new Uint8Array(segment.initSegment.data, segment.initSegment.byteOffset, segment.initSegment.byteLength); 49604 createTextTracksIfNecessary(this, this.mediaSource_, segment); // Add the segments to the pendingBuffers array 49605 49606 this.pendingBuffers_.push(segment); 49607 return; 49608 } 49609 /** 49610 * When we get a done event from the transmuxer 49611 * we call this function and we process all 49612 * of the pending data that we have been saving in the 49613 * data_ function 49614 * 49615 * @private 49616 * @param {Event} event the done event from the transmuxer 49617 */ 49618 49619 }, {
49620 key: 'done_', 49621 value: function done_(event) { 49622 // Don't process and append data if the mediaSource is closed 49623 if (this.mediaSource_.readyState === 'closed') { 49624 this.pendingBuffers_.length = 0; 49625 return; 49626 } // All buffers should have been flushed from the muxer 49627 // start processing anything we have received 49628 49629 49630 this.processPendingSegments_(); 49631 return; 49632 } 49633 }, { 49634 key: 'videoSegmentTimingInfo_', 49635 value: function videoSegmentTimingInfo_(timingInfo) { 49636 var timingInfoInSeconds = { 49637 start: { 49638 decode: timingInfo.start.dts / ONE_SECOND_IN_TS$3, 49639 presentation: timingInfo.start.pts / ONE_SECOND_IN_TS$3 49640 }, 49641 end: { 49642 decode: timingInfo.end.dts / ONE_SECOND_IN_TS$3, 49643 presentation: timingInfo.end.pts / ONE_SECOND_IN_TS$3 49644 }, 49645 baseMediaDecodeTime: timingInfo.baseMediaDecodeTime / ONE_SECOND_IN_TS$3 49646 }; 49647 49648 if (timingInfo.prependedContentDuration) { 49649 timingInfoInSeconds.prependedContentDuration = timingInfo.prependedContentDuration / ONE_SECOND_IN_TS$3; 49650 } 49651 49652 this.trigger({ 49653 type: 'videoSegmentTimingInfo', 49654 videoSegmentTimingInfo: timingInfoInSeconds 49655 }); 49656 } 49657 /** 49658 * Create our internal native audio/video source buffers and add 49659 * event handlers to them with the following conditions: 49660 * 1. they do not already exist on the mediaSource 49661 * 2. this VSB has a codec for them 49662 * 49663 * @private 49664 */ 49665 49666 }, { 49667 key: 'createRealSourceBuffers_', 49668 value: function createRealSourceBuffers_() { 49669 var _this2 = this; 49670 49671 var types = ['audio', 'video']; 49672 types.forEach(function (type) { 49673 // Don't create a SourceBuffer of this type if we don't have a 49674 // codec for it 49675 if (!_this2[type + 'Codec_']) { 49676 return; 49677 } // Do nothing if a SourceBuffer of this type already exists 49678 49679 49680 if (_this2[type + 'Buffer_']) { 49681 return; 49682 } 49683 49684 var buffer = null; // If the mediasource already has a SourceBuffer for the codec 49685 // use that 49686 49687 if (_this2.mediaSource_[type + 'Buffer_']) { 49688 buffer = _this2.mediaSource_[type + 'Buffer_']; // In multiple audio track cases, the audio source buffer is disabled 49689 // on the main VirtualSourceBuffer by the HTMLMediaSource much earlier 49690 // than createRealSourceBuffers_ is called to create the second 49691 // VirtualSourceBuffer because that happens as a side-effect of 49692 // videojs-contrib-hls starting the audioSegmentLoader. As a result, 49693 // the audioBuffer is essentially "ownerless" and no one will toggle
vendor: 29,081 bytes, lines 49694-50511
49694 // the `updating` state back to false once the `updateend` event is received 49695 // 49696 // Setting `updating` to false manually will work around this 49697 // situation and allow work to continue 49698 49699 buffer.updating = false; 49700 } else { 49701 var codecProperty = type + 'Codec_'; 49702 var mimeType = type + '/mp4;codecs="' + _this2[codecProperty] + '"'; 49703 buffer = makeWrappedSourceBuffer(_this2.mediaSource_.nativeMediaSource_, mimeType); 49704 _this2.mediaSource_[type + 'Buffer_'] = buffer; 49705 } 49706 49707 _this2[type + 'Buffer_'] = buffer; // Wire up the events to the SourceBuffer 49708 49709 ['update', 'updatestart', 'updateend'].forEach(function (event) { 49710 buffer.addEventListener(event, function () { 49711 // if audio is disabled 49712 if (type === 'audio' && _this2.audioDisabled_) { 49713 return; 49714 } 49715 49716 if (event === 'updateend') { 49717 _this2[type + 'Buffer_'].updating = false; 49718 } 49719 49720 var shouldTrigger = types.every(function (t) { 49721 // skip checking audio's updating status if audio 49722 // is not enabled 49723 if (t === 'audio' && _this2.audioDisabled_) { 49724 return true; 49725 } // if the other type if updating we don't trigger 49726 49727 49728 if (type !== t && _this2[t + 'Buffer_'] && _this2[t + 'Buffer_'].updating) { 49729 return false; 49730 } 49731 49732 return true; 49733 }); 49734 49735 if (shouldTrigger) { 49736 return _this2.trigger(event); 49737 } 49738 }); 49739 }); 49740 }); 49741 } 49742 /** 49743 * Emulate the native mediasource function, but our function will 49744 * send all of the proposed segments to the transmuxer so that we 49745 * can transmux them before we append them to our internal 49746 * native source buffers in the correct format. 49747 * 49748 * @link https://developer.mozilla.org/en-US/docs/Web/API/SourceBuffer/appendBuffer 49749 * @param {Uint8Array} segment the segment to append to the buffer 49750 */ 49751 49752 }, { 49753 key: 'appendBuffer', 49754 value: function appendBuffer(segment) { 49755 // Start the internal "updating" state 49756 this.bufferUpdating_ = true; 49757 49758 if (this.audioBuffer_ && this.audioBuffer_.buffered.length) { 49759 var audioBuffered = this.audioBuffer_.buffered; 49760 this.transmuxer_.postMessage({ 49761 action: 'setAudioAppendStart', 49762 appendStart: audioBuffered.end(audioBuffered.length - 1) 49763 }); 49764 } 49765 49766 if (this.videoBuffer_) { 49767 this.transmuxer_.postMessage({ 49768 action: 'alignGopsWith', 49769 gopsToAlignWith: gopsSafeToAlignWith(this.gopBuffer_, this.mediaSource_.player_ ? this.mediaSource_.player_.currentTime() : null, this.timeMapping_) 49770 }); 49771 } 49772 49773 this.transmuxer_.postMessage({ 49774 action: 'push', 49775 // Send the typed-array of data as an ArrayBuffer so that 49776 // it can be sent as a "Transferable" and avoid the costly 49777 // memory copy 49778 data: segment.buffer, 49779 // To recreate the original typed-array, we need information 49780 // about what portion of the ArrayBuffer it was a view into 49781 byteOffset: segment.byteOffset, 49782 byteLength: segment.byteLength 49783 }, [segment.buffer]); 49784 this.transmuxer_.postMessage({ 49785 action: 'flush' 49786 }); 49787 } 49788 /** 49789 * Appends gop information (timing and byteLength) received by the transmuxer for the 49790 * gops appended in the last call to appendBuffer 49791 * 49792 * @param {Event} event 49793 * The gopInfo event from the transmuxer 49794 * @param {Array} event.data.gopInfo 49795 * List of gop info to append 49796 */ 49797 49798 }, { 49799 key: 'appendGopInfo_', 49800 value: function appendGopInfo_(event) { 49801 this.gopBuffer_ = updateGopBuffer(this.gopBuffer_, event.data.gopInfo, this.safeAppend_); 49802 } 49803 /** 49804 * Emulate the native mediasource function and remove parts 49805 * of the buffer from any of our internal buffers that exist 49806 * 49807 * @link https://developer.mozilla.org/en-US/docs/Web/API/SourceBuffer/remove 49808 * @param {Double} start position to start the remove at 49809 * @param {Double} end position to end the remove at 49810 */ 49811 49812 }, { 49813 key: 'remove', 49814 value: function remove(start, end) { 49815 if (this.videoBuffer_) { 49816 this.videoBuffer_.updating = true; 49817 this.videoBuffer_.remove(start, end); 49818 this.gopBuffer_ = removeGopBuffer(this.gopBuffer_, start, end, this.timeMapping_); 49819 } 49820 49821 if (!this.audioDisabled_ && this.audioBuffer_) { 49822 this.audioBuffer_.updating = true; 49823 this.audioBuffer_.remove(start, end); 49824 } // Remove Metadata Cues (id3) 49825 49826 49827 removeCuesFromTrack(start, end, this.metadataTrack_); // Remove Any Captions 49828 49829 if (this.inbandTextTracks_) { 49830 for (var track in this.inbandTextTracks_) { 49831 removeCuesFromTrack(start, end, this.inbandTextTracks_[track]); 49832 } 49833 } 49834 } 49835 /** 49836 * Process any segments that the muxer has output 49837 * Concatenate segments together based on type and append them into 49838 * their respective sourceBuffers 49839 * 49840 * @private 49841 */ 49842 49843 }, { 49844 key: 'processPendingSegments_', 49845 value: function processPendingSegments_() { 49846 var sortedSegments = { 49847 video: { 49848 segments: [], 49849 bytes: 0 49850 }, 49851 audio: { 49852 segments: [], 49853 bytes: 0 49854 }, 49855 captions: [], 49856 metadata: [] 49857 }; // Sort segments into separate video/audio arrays and 49858 // keep track of their total byte lengths 49859 49860 sortedSegments = this.pendingBuffers_.reduce(function (segmentObj, segment) { 49861 var type = segment.type; 49862 var data = segment.data; 49863 var initSegment = segment.initSegment; 49864 segmentObj[type].segments.push(data); 49865 segmentObj[type].bytes += data.byteLength; 49866 segmentObj[type].initSegment = initSegment; // Gather any captions into a single array 49867 49868 if (segment.captions) { 49869 segmentObj.captions = segmentObj.captions.concat(segment.captions); 49870 } 49871 49872 if (segment.info) { 49873 segmentObj[type].info = segment.info; 49874 } // Gather any metadata into a single array 49875 49876 49877 if (segment.metadata) { 49878 segmentObj.metadata = segmentObj.metadata.concat(segment.metadata); 49879 } 49880 49881 return segmentObj; 49882 }, sortedSegments); // Create the real source buffers if they don't exist by now since we 49883 // finally are sure what tracks are contained in the source 49884 49885 if (!this.videoBuffer_ && !this.audioBuffer_) { 49886 // Remove any codecs that may have been specified by default but 49887 // are no longer applicable now 49888 if (sortedSegments.video.bytes === 0) { 49889 this.videoCodec_ = null; 49890 } 49891 49892 if (sortedSegments.audio.bytes === 0) { 49893 this.audioCodec_ = null; 49894 } 49895 49896 this.createRealSourceBuffers_(); 49897 } 49898 49899 if (sortedSegments.audio.info) { 49900 this.mediaSource_.trigger({ 49901 type: 'audioinfo', 49902 info: sortedSegments.audio.info 49903 }); 49904 } 49905 49906 if (sortedSegments.video.info) { 49907 this.mediaSource_.trigger({ 49908 type: 'videoinfo', 49909 info: sortedSegments.video.info 49910 }); 49911 } 49912 49913 if (this.appendAudioInitSegment_) { 49914 if (!this.audioDisabled_ && this.audioBuffer_) { 49915 sortedSegments.audio.segments.unshift(sortedSegments.audio.initSegment); 49916 sortedSegments.audio.bytes += sortedSegments.audio.initSegment.byteLength; 49917 } 49918 49919 this.appendAudioInitSegment_ = false; 49920 } 49921 49922 var triggerUpdateend = false; // Merge multiple video and audio segments into one and append 49923 49924 if (this.videoBuffer_ && sortedSegments.video.bytes) { 49925 sortedSegments.video.segments.unshift(sortedSegments.video.initSegment); 49926 sortedSegments.video.bytes += sortedSegments.video.initSegment.byteLength; 49927 this.concatAndAppendSegments_(sortedSegments.video, this.videoBuffer_); 49928 } else if (this.videoBuffer_ && (this.audioDisabled_ || !this.audioBuffer_)) { 49929 // The transmuxer did not return any bytes of video, meaning it was all trimmed 49930 // for gop alignment. Since we have a video buffer and audio is disabled, updateend 49931 // will never be triggered by this source buffer, which will cause contrib-hls 49932 // to be stuck forever waiting for updateend. If audio is not disabled, updateend 49933 // will be triggered by the audio buffer, which will be sent upwards since the video 49934 // buffer will not be in an updating state. 49935 triggerUpdateend = true; 49936 } // Add text-track data for all 49937 49938 49939 addTextTrackData(this, sortedSegments.captions, sortedSegments.metadata); 49940 49941 if (!this.audioDisabled_ && this.audioBuffer_) { 49942 this.concatAndAppendSegments_(sortedSegments.audio, this.audioBuffer_); 49943 } 49944 49945 this.pendingBuffers_.length = 0; 49946 49947 if (triggerUpdateend) { 49948 this.trigger('updateend'); 49949 } // We are no longer in the internal "updating" state 49950 49951 49952 this.bufferUpdating_ = false; 49953 } 49954 /** 49955 * Combine all segments into a single Uint8Array and then append them 49956 * to the destination buffer 49957 * 49958 * @param {Object} segmentObj 49959 * @param {SourceBuffer} destinationBuffer native source buffer to append data to 49960 * @private 49961 */ 49962 49963 }, { 49964 key: 'concatAndAppendSegments_', 49965 value: function concatAndAppendSegments_(segmentObj, destinationBuffer) { 49966 var offset = 0; 49967 var tempBuffer = void 0; 49968 49969 if (segmentObj.bytes) { 49970 tempBuffer = new Uint8Array(segmentObj.bytes); // Combine the individual segments into one large typed-array 49971 49972 segmentObj.segments.forEach(function (segment) { 49973 tempBuffer.set(segment, offset); 49974 offset += segment.byteLength; 49975 }); 49976 49977 try { 49978 destinationBuffer.updating = true; 49979 destinationBuffer.appendBuffer(tempBuffer); 49980 } catch (error) { 49981 if (this.mediaSource_.player_) { 49982 this.mediaSource_.player_.error({ 49983 code: -3, 49984 type: 'APPEND_BUFFER_ERR', 49985 message: error.message, 49986 originalError: error 49987 }); 49988 } 49989 } 49990 } 49991 } 49992 /** 49993 * Emulate the native mediasource function. abort any soureBuffer 49994 * actions and throw out any un-appended data. 49995 * 49996 * @link https://developer.mozilla.org/en-US/docs/Web/API/SourceBuffer/abort 49997 */ 49998 49999 }, { 50000 key: 'abort', 50001 value: function abort() { 50002 if (this.videoBuffer_) { 50003 this.videoBuffer_.abort(); 50004 } 50005 50006 if (!this.audioDisabled_ && this.audioBuffer_) { 50007 this.audioBuffer_.abort(); 50008 } 50009 50010 if (this.transmuxer_) { 50011 this.transmuxer_.postMessage({ 50012 action: 'reset' 50013 }); 50014 } 50015 50016 this.pendingBuffers_.length = 0; 50017 this.bufferUpdating_ = false; 50018 } 50019 }]); 50020 return VirtualSourceBuffer; 50021 }(videojs$1.EventTarget); 50022 /** 50023 * @file html-media-source.js 50024 */ 50025 50026 /** 50027 * Our MediaSource implementation in HTML, mimics native 50028 * MediaSource where/if possible. 50029 * 50030 * @link https://developer.mozilla.org/en-US/docs/Web/API/MediaSource 50031 * @class HtmlMediaSource 50032 * @extends videojs.EventTarget 50033 */ 50034 50035 50036 var HtmlMediaSource = function (_videojs$EventTarget) { 50037 inherits$1(HtmlMediaSource, _videojs$EventTarget); 50038 50039 function HtmlMediaSource() { 50040 classCallCheck$1(this, HtmlMediaSource); 50041 50042 var _this = possibleConstructorReturn$1(this, (HtmlMediaSource.__proto__ || Object.getPrototypeOf(HtmlMediaSource)).call(this)); 50043 50044 var property = void 0; 50045 _this.nativeMediaSource_ = new window$1.MediaSource(); // delegate to the native MediaSource's methods by default 50046 50047 for (property in _this.nativeMediaSource_) { 50048 if (!(property in HtmlMediaSource.prototype) && typeof _this.nativeMediaSource_[property] === 'function') { 50049 _this[property] = _this.nativeMediaSource_[property].bind(_this.nativeMediaSource_); 50050 } 50051 } // emulate `duration` and `seekable` until seeking can be 50052 // handled uniformly for live streams 50053 // see https://github.com/w3c/media-source/issues/5 50054 50055 50056 _this.duration_ = NaN; 50057 Object.defineProperty(_this, 'duration', { 50058 get: function get$$1() { 50059 if (this.duration_ === Infinity) { 50060 return this.duration_; 50061 } 50062 50063 return this.nativeMediaSource_.duration; 50064 }, 50065 set: function set$$1(duration) { 50066 this.duration_ = duration; 50067 50068 if (duration !== Infinity) { 50069 this.nativeMediaSource_.duration = duration; 50070 return; 50071 } 50072 } 50073 }); 50074 Object.defineProperty(_this, 'seekable', { 50075 get: function get$$1() { 50076 if (this.duration_ === Infinity) { 50077 return videojs$1.createTimeRanges([[0, this.nativeMediaSource_.duration]]); 50078 } 50079 50080 return this.nativeMediaSource_.seekable; 50081 } 50082 }); 50083 Object.defineProperty(_this, 'readyState', { 50084 get: function get$$1() { 50085 return this.nativeMediaSource_.readyState; 50086 } 50087 }); 50088 Object.defineProperty(_this, 'activeSourceBuffers', { 50089 get: function get$$1() { 50090 return this.activeSourceBuffers_; 50091 } 50092 }); // the list of virtual and native SourceBuffers created by this 50093 // MediaSource 50094 50095 _this.sourceBuffers = []; 50096 _this.activeSourceBuffers_ = []; 50097 /** 50098 * update the list of active source buffers based upon various 50099 * imformation from HLS and video.js 50100 * 50101 * @private 50102 */ 50103 50104 _this.updateActiveSourceBuffers_ = function () { 50105 // Retain the reference but empty the array 50106 _this.activeSourceBuffers_.length = 0; // If there is only one source buffer, then it will always be active and audio will 50107 // be disabled based on the codec of the source buffer 50108 50109 if (_this.sourceBuffers.length === 1) { 50110 var sourceBuffer = _this.sourceBuffers[0]; 50111 sourceBuffer.appendAudioInitSegment_ = true; 50112 sourceBuffer.audioDisabled_ = !sourceBuffer.audioCodec_; 50113 50114 _this.activeSourceBuffers_.push(sourceBuffer); 50115 50116 return; 50117 } // There are 2 source buffers, a combined (possibly video only) source buffer and 50118 // and an audio only source buffer. 50119 // By default, the audio in the combined virtual source buffer is enabled 50120 // and the audio-only source buffer (if it exists) is disabled. 50121 50122 50123 var disableCombined = false; 50124 var disableAudioOnly = true; // TODO: maybe we can store the sourcebuffers on the track objects? 50125 // safari may do something like this 50126 50127 for (var i = 0; i < _this.player_.audioTracks().length; i++) { 50128 var track = _this.player_.audioTracks()[i]; 50129 50130 if (track.enabled && track.kind !== 'main') { 50131 // The enabled track is an alternate audio track so disable the audio in 50132 // the combined source buffer and enable the audio-only source buffer. 50133 disableCombined = true; 50134 disableAudioOnly = false; 50135 break; 50136 } 50137 } 50138 50139 _this.sourceBuffers.forEach(function (sourceBuffer, index) { 50140 /* eslinst-disable */ 50141 // TODO once codecs are required, we can switch to using the codecs to determine 50142 // what stream is the video stream, rather than relying on videoTracks 50143 50144 /* eslinst-enable */ 50145 sourceBuffer.appendAudioInitSegment_ = true; 50146 50147 if (sourceBuffer.videoCodec_ && sourceBuffer.audioCodec_) { 50148 // combined 50149 sourceBuffer.audioDisabled_ = disableCombined; 50150 } else if (sourceBuffer.videoCodec_ && !sourceBuffer.audioCodec_) { 50151 // If the "combined" source buffer is video only, then we do not want 50152 // disable the audio-only source buffer (this is mostly for demuxed 50153 // audio and video hls) 50154 sourceBuffer.audioDisabled_ = true; 50155 disableAudioOnly = false; 50156 } else if (!sourceBuffer.videoCodec_ && sourceBuffer.audioCodec_) { 50157 // audio only 50158 // In the case of audio only with alternate audio and disableAudioOnly is true 50159 // this means we want to disable the audio on the alternate audio sourcebuffer 50160 // but not the main "combined" source buffer. The "combined" source buffer is 50161 // always at index 0, so this ensures audio won't be disabled in both source 50162 // buffers. 50163 sourceBuffer.audioDisabled_ = index ? disableAudioOnly : !disableAudioOnly; 50164 50165 if (sourceBuffer.audioDisabled_) { 50166 return; 50167 } 50168 } 50169 50170 _this.activeSourceBuffers_.push(sourceBuffer); 50171 }); 50172 }; 50173 50174 _this.onPlayerMediachange_ = function () { 50175 _this.sourceBuffers.forEach(function (sourceBuffer) { 50176 sourceBuffer.appendAudioInitSegment_ = true; 50177 }); 50178 }; 50179 50180 _this.onHlsReset_ = function () { 50181 _this.sourceBuffers.forEach(function (sourceBuffer) { 50182 if (sourceBuffer.transmuxer_) { 50183 sourceBuffer.transmuxer_.postMessage({ 50184 action: 'resetCaptions' 50185 }); 50186 } 50187 }); 50188 }; 50189 50190 _this.onHlsSegmentTimeMapping_ = function (event) { 50191 _this.sourceBuffers.forEach(function (buffer) { 50192 return buffer.timeMapping_ = event.mapping; 50193 }); 50194 }; // Re-emit MediaSource events on the polyfill 50195 50196 50197 ['sourceopen', 'sourceclose', 'sourceended'].forEach(function (eventName) { 50198 this.nativeMediaSource_.addEventListener(eventName, this.trigger.bind(this)); 50199 }, _this); // capture the associated player when the MediaSource is 50200 // successfully attached 50201 50202 _this.on('sourceopen', function (event) { 50203 // Get the player this MediaSource is attached to 50204 var video = document.querySelector('[src="' + _this.url_ + '"]'); 50205 50206 if (!video) { 50207 return; 50208 } 50209 50210 _this.player_ = videojs$1(video.parentNode); 50211 50212 if (!_this.player_) { 50213 return; 50214 } // hls-reset is fired by videojs.Hls on to the tech after the main SegmentLoader 50215 // resets its state and flushes the buffer 50216 50217 50218 _this.player_.tech_.on('hls-reset', _this.onHlsReset_); // hls-segment-time-mapping is fired by videojs.Hls on to the tech after the main 50219 // SegmentLoader inspects an MTS segment and has an accurate stream to display 50220 // time mapping 50221 50222 50223 _this.player_.tech_.on('hls-segment-time-mapping', _this.onHlsSegmentTimeMapping_); 50224 50225 if (_this.player_.audioTracks && _this.player_.audioTracks()) { 50226 _this.player_.audioTracks().on('change', _this.updateActiveSourceBuffers_); 50227 50228 _this.player_.audioTracks().on('addtrack', _this.updateActiveSourceBuffers_); 50229 50230 _this.player_.audioTracks().on('removetrack', _this.updateActiveSourceBuffers_); 50231 } 50232 50233 _this.player_.on('mediachange', _this.onPlayerMediachange_); 50234 }); 50235 50236 _this.on('sourceended', function (event) { 50237 var duration = durationOfVideo(_this.duration); 50238 50239 for (var i = 0; i < _this.sourceBuffers.length; i++) { 50240 var sourcebuffer = _this.sourceBuffers[i]; 50241 var cues = sourcebuffer.metadataTrack_ && sourcebuffer.metadataTrack_.cues; 50242 50243 if (cues && cues.length) { 50244 cues[cues.length - 1].endTime = duration; 50245 } 50246 } 50247 }); // explicitly terminate any WebWorkers that were created 50248 // by SourceHandlers 50249 50250 50251 _this.on('sourceclose', function (event) { 50252 this.sourceBuffers.forEach(function (sourceBuffer) { 50253 if (sourceBuffer.transmuxer_) { 50254 sourceBuffer.transmuxer_.terminate(); 50255 } 50256 }); 50257 this.sourceBuffers.length = 0; 50258 50259 if (!this.player_) { 50260 return; 50261 } 50262 50263 if (this.player_.audioTracks && this.player_.audioTracks()) { 50264 this.player_.audioTracks().off('change', this.updateActiveSourceBuffers_); 50265 this.player_.audioTracks().off('addtrack', this.updateActiveSourceBuffers_); 50266 this.player_.audioTracks().off('removetrack', this.updateActiveSourceBuffers_); 50267 } // We can only change this if the player hasn't been disposed of yet 50268 // because `off` eventually tries to use the el_ property. If it has 50269 // been disposed of, then don't worry about it because there are no 50270 // event handlers left to unbind anyway 50271 50272 50273 if (this.player_.el_) { 50274 this.player_.off('mediachange', this.onPlayerMediachange_); 50275 } 50276 50277 if (this.player_.tech_ && this.player_.tech_.el_) { 50278 this.player_.tech_.off('hls-reset', this.onHlsReset_); 50279 this.player_.tech_.off('hls-segment-time-mapping', this.onHlsSegmentTimeMapping_); 50280 } 50281 }); 50282 50283 return _this; 50284 } 50285 /** 50286 * Add a range that that can now be seeked to. 50287 * 50288 * @param {Double} start where to start the addition 50289 * @param {Double} end where to end the addition 50290 * @private 50291 */ 50292 50293 50294 createClass$1(HtmlMediaSource, [{ 50295 key: 'addSeekableRange_', 50296 value: function addSeekableRange_(start, end) { 50297 var error = void 0; 50298 50299 if (this.duration !== Infinity) { 50300 error = new Error('MediaSource.addSeekableRange() can only be invoked ' + 'when the duration is Infinity'); 50301 error.name = 'InvalidStateError'; 50302 error.code = 11; 50303 throw error; 50304 } 50305 50306 if (end > this.nativeMediaSource_.duration || isNaN(this.nativeMediaSource_.duration)) { 50307 this.nativeMediaSource_.duration = end; 50308 } 50309 } 50310 /** 50311 * Add a source buffer to the media source. 50312 * 50313 * @link https://developer.mozilla.org/en-US/docs/Web/API/MediaSource/addSourceBuffer 50314 * @param {String} type the content-type of the content 50315 * @return {Object} the created source buffer 50316 */ 50317 50318 }, { 50319 key: 'addSourceBuffer', 50320 value: function addSourceBuffer(type) { 50321 var buffer = void 0; 50322 var parsedType = parseContentType(type); // Create a VirtualSourceBuffer to transmux MPEG-2 transport 50323 // stream segments into fragmented MP4s 50324 50325 if (/^(video|audio)\/mp2t$/i.test(parsedType.type)) { 50326 var codecs = []; 50327 50328 if (parsedType.parameters && parsedType.parameters.codecs) { 50329 codecs = parsedType.parameters.codecs.split(','); 50330 codecs = translateLegacyCodecs(codecs); 50331 codecs = codecs.filter(function (codec) { 50332 return isAudioCodec(codec) || isVideoCodec(codec); 50333 }); 50334 } 50335 50336 if (codecs.length === 0) { 50337 codecs = ['avc1.4d400d', 'mp4a.40.2']; 50338 } 50339 50340 buffer = new VirtualSourceBuffer(this, codecs); 50341 50342 if (this.sourceBuffers.length !== 0) { 50343 // If another VirtualSourceBuffer already exists, then we are creating a 50344 // SourceBuffer for an alternate audio track and therefore we know that 50345 // the source has both an audio and video track. 50346 // That means we should trigger the manual creation of the real 50347 // SourceBuffers instead of waiting for the transmuxer to return data 50348 this.sourceBuffers[0].createRealSourceBuffers_(); 50349 buffer.createRealSourceBuffers_(); // Automatically disable the audio on the first source buffer if 50350 // a second source buffer is ever created 50351 50352 this.sourceBuffers[0].audioDisabled_ = true; 50353 } 50354 } else { 50355 // delegate to the native implementation 50356 buffer = this.nativeMediaSource_.addSourceBuffer(type); 50357 } 50358 50359 this.sourceBuffers.push(buffer); 50360 return buffer; 50361 } 50362 }]); 50363 return HtmlMediaSource; 50364 }(videojs$1.EventTarget); 50365 /** 50366 * @file videojs-contrib-media-sources.js 50367 */ 50368 50369 50370 var urlCount = 0; // ------------ 50371 // Media Source 50372 // ------------ 50373 // store references to the media sources so they can be connected 50374 // to a video element (a swf object) 50375 // TODO: can we store this somewhere local to this module? 50376 50377 videojs$1.mediaSources = {}; 50378 /** 50379 * Provide a method for a swf object to notify JS that a 50380 * media source is now open. 50381 * 50382 * @param {String} msObjectURL string referencing the MSE Object URL 50383 * @param {String} swfId the swf id 50384 */ 50385 50386 var open = function open(msObjectURL, swfId) { 50387 var mediaSource = videojs$1.mediaSources[msObjectURL]; 50388 50389 if (mediaSource) { 50390 mediaSource.trigger({ 50391 type: 'sourceopen', 50392 swfId: swfId 50393 }); 50394 } else { 50395 throw new Error('Media Source not found (Video.js)'); 50396 } 50397 }; 50398 /** 50399 * Check to see if the native MediaSource object exists and supports 50400 * an MP4 container with both H.264 video and AAC-LC audio. 50401 * 50402 * @return {Boolean} if native media sources are supported 50403 */ 50404 50405 50406 var supportsNativeMediaSources = function supportsNativeMediaSources() { 50407 return !!window$1.MediaSource && !!window$1.MediaSource.isTypeSupported && window$1.MediaSource.isTypeSupported('video/mp4;codecs="avc1.4d400d,mp4a.40.2"'); 50408 }; 50409 /** 50410 * An emulation of the MediaSource API so that we can support 50411 * native and non-native functionality. returns an instance of 50412 * HtmlMediaSource. 50413 * 50414 * @link https://developer.mozilla.org/en-US/docs/Web/API/MediaSource/MediaSource 50415 */ 50416 50417 50418 var MediaSource = function MediaSource() { 50419 this.MediaSource = { 50420 open: open, 50421 supportsNativeMediaSources: supportsNativeMediaSources 50422 }; 50423 50424 if (supportsNativeMediaSources()) { 50425 return new HtmlMediaSource(); 50426 } 50427 50428 throw new Error('Cannot use create a virtual MediaSource for this video'); 50429 }; 50430 50431 MediaSource.open = open; 50432 MediaSource.supportsNativeMediaSources = supportsNativeMediaSources; 50433 /** 50434 * A wrapper around the native URL for our MSE object 50435 * implementation, this object is exposed under videojs.URL 50436 * 50437 * @link https://developer.mozilla.org/en-US/docs/Web/API/URL/URL 50438 */ 50439 50440 var URL$1 = { 50441 /** 50442 * A wrapper around the native createObjectURL for our objects. 50443 * This function maps a native or emulated mediaSource to a blob 50444 * url so that it can be loaded into video.js 50445 * 50446 * @link https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL 50447 * @param {MediaSource} object the object to create a blob url to 50448 */ 50449 createObjectURL: function createObjectURL(object) { 50450 var objectUrlPrefix = 'blob:vjs-media-source/'; 50451 var url = void 0; // use the native MediaSource to generate an object URL 50452 50453 if (object instanceof HtmlMediaSource) { 50454 url = window$1.URL.createObjectURL(object.nativeMediaSource_); 50455 object.url_ = url; 50456 return url; 50457 } // if the object isn't an emulated MediaSource, delegate to the 50458 // native implementation 50459 50460 50461 if (!(object instanceof HtmlMediaSource)) { 50462 url = window$1.URL.createObjectURL(object); 50463 object.url_ = url; 50464 return url; 50465 } // build a URL that can be used to map back to the emulated 50466 // MediaSource 50467 50468 50469 url = objectUrlPrefix + urlCount; 50470 urlCount++; // setup the mapping back to object 50471 50472 videojs$1.mediaSources[url] = object; 50473 return url; 50474 } 50475 }; 50476 videojs$1.MediaSource = MediaSource; 50477 videojs$1.URL = URL$1; 50478 var EventTarget$1$1 = videojs$1.EventTarget, 50479 mergeOptions$2 = videojs$1.mergeOptions; 50480 /** 50481 * Returns a new master manifest that is the result of merging an updated master manifest 50482 * into the original version. 50483 * 50484 * @param {Object} oldMaster 50485 * The old parsed mpd object 50486 * @param {Object} newMaster 50487 * The updated parsed mpd object 50488 * @return {Object} 50489 * A new object representing the original master manifest with the updated media 50490 * playlists merged in 50491 */ 50492 50493 var updateMaster$1 = function updateMaster$$1(oldMaster, newMaster) { 50494 var noChanges = void 0; 50495 var update = mergeOptions$2(oldMaster, { 50496 // These are top level properties that can be updated 50497 duration: newMaster.duration, 50498 minimumUpdatePeriod: newMaster.minimumUpdatePeriod 50499 }); // First update the playlists in playlist list 50500 50501 for (var i = 0; i < newMaster.playlists.length; i++) { 50502 var playlistUpdate = updateMaster(update, newMaster.playlists[i]); 50503 50504 if (playlistUpdate) { 50505 update = playlistUpdate; 50506 } else { 50507 noChanges = true; 50508 } 50509 } // Then update media group playlists 50510 50511
50512 forEachMediaGroup(newMaster, function (properties, type, group, label) { 50513 if (properties.playlists && properties.playlists.length) { 50514 var uri = properties.playlists[0].uri; 50515 50516 var _playlistUpdate = updateMaster(update, properties.playlists[0]); 50517 50518 if (_playlistUpdate) { 50519 update = _playlistUpdate; // update the playlist reference within media groups 50520 50521 update.mediaGroups[type][group][label].playlists[0] = update.playlists[uri]; 50522 noChanges = false; 50523 } 50524 } 50525 }); 50526 50527 if (noChanges) { 50528 return null; 50529 } 50530 50531 return update; 50532 }; 50533 50534 var DashPlaylistLoader = function (_EventTarget) { 50535 inherits$1(DashPlaylistLoader, _EventTarget); // DashPlaylistLoader must accept either a src url or a playlist because subsequent 50536 // playlist loader setups from media groups will expect to be able to pass a playlist 50537 // (since there aren't external URLs to media playlists with DASH) 50538 50539 function DashPlaylistLoader(srcUrlOrPlaylist, hls) { 50540 var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; 50541 var masterPlaylistLoader = arguments[3]; 50542 classCallCheck$1(this, DashPlaylistLoader); 50543 50544 var _this = possibleConstructorReturn$1(this, (DashPlaylistLoader.__proto__ || Object.getPrototypeOf(DashPlaylistLoader)).call(this)); 50545 50546 var _options$withCredenti = options.withCredentials, 50547 withCredentials = _options$withCredenti === undefined ? false : _options$withCredenti, 50548 _options$handleManife = options.handleManifestRedirects, 50549 handleManifestRedirects = _options$handleManife === undefined ? false : _options$handleManife; 50550 _this.hls_ = hls; 50551 _this.withCredentials = withCredentials; 50552 _this.handleManifestRedirects = handleManifestRedirects; 50553 50554 if (!srcUrlOrPlaylist) { 50555 throw new Error('A non-empty playlist URL or playlist is required'); 50556 } // event naming? 50557 50558 50559 _this.on('minimumUpdatePeriod', function () { 50560 _this.refreshXml_(); 50561 }); // live playlist staleness timeout 50562 50563 50564 _this.on('mediaupdatetimeout', function () { 50565 _this.refreshMedia_(); 50566 }); 50567 50568 _this.state = 'HAVE_NOTHING'; 50569 _this.loadedPlaylists_ = {}; // initialize the loader state 50570 // The masterPlaylistLoader will be created with a string 50571 50572 if (typeof srcUrlOrPlaylist === 'string') { 50573 _this.srcUrl = srcUrlOrPlaylist; 50574 return possibleConstructorReturn$1(_this); 50575 } 50576 50577 _this.setupChildLoader(masterPlaylistLoader, srcUrlOrPlaylist); 50578 50579 return _this; 50580 } 50581 50582 createClass$1(DashPlaylistLoader, [{ 50583 key: 'setupChildLoader', 50584 value: function setupChildLoader(masterPlaylistLoader, playlist) { 50585 this.masterPlaylistLoader_ = masterPlaylistLoader; 50586 this.childPlaylist_ = playlist; 50587 } 50588 }, { 50589 key: 'dispose', 50590 value: function dispose() { 50591 this.stopRequest(); 50592 this.loadedPlaylists_ = {}; 50593 window$1.clearTimeout(this.mediaUpdateTimeout); 50594 } 50595 }, { 50596 key: 'hasPendingRequest', 50597 value: function hasPendingRequest() { 50598 return this.request || this.mediaRequest_; 50599 } 50600 }, { 50601 key: 'stopRequest', 50602 value: function stopRequest() { 50603 if (this.request) { 50604 var oldRequest = this.request; 50605 this.request = null; 50606 oldRequest.onreadystatechange = null; 50607 oldRequest.abort(); 50608 } 50609 } 50610 }, { 50611 key: 'media', 50612 value: function media(playlist) { 50613 // getter 50614 if (!playlist) { 50615 return this.media_; 50616 } // setter 50617 50618 50619 if (this.state === 'HAVE_NOTHING') { 50620 throw new Error('Cannot switch media playlist from ' + this.state); 50621 } 50622 50623 var startingState = this.state; // find the playlist object if the target playlist has been specified by URI 50624 50625 if (typeof playlist === 'string') { 50626 if (!this.master.playlists[playlist]) { 50627 throw new Error('Unknown playlist URI: ' + playlist); 50628 } 50629 50630 playlist = this.master.playlists[playlist]; 50631 } 50632 50633 var mediaChange = !this.media_ || playlist.uri !== this.media_.uri; // switch to previously loaded playlists immediately 50634 50635 if (mediaChange && this.loadedPlaylists_[playlist.uri] && this.loadedPlaylists_[playlist.uri].endList) { 50636 this.state = 'HAVE_METADATA'; 50637 this.media_ = playlist; // trigger media change if the active media has been updated 50638 50639 if (mediaChange) { 50640 this.trigger('mediachanging'); 50641 this.trigger('mediachange'); 50642 } 50643 50644 return; 50645 } // switching to the active playlist is a no-op 50646 50647 50648 if (!mediaChange) { 50649 return; 50650 } // switching from an already loaded playlist 50651 50652 50653 if (this.media_) { 50654 this.trigger('mediachanging'); 50655 } // TODO: check for sidx here 50656 // Continue asynchronously if there is no sidx 50657 // wait one tick to allow haveMaster to run first on a child loader 50658 50659 50660 this.mediaRequest_ = window$1.setTimeout(this.haveMetadata.bind(this, { 50661 startingState: startingState, 50662 playlist: playlist 50663 }), 0); 50664 } 50665 }, {
vendor: 26,747 bytes, lines 50666-51502
50666 key: 'haveMetadata', 50667 value: function haveMetadata(_ref) { 50668 var startingState = _ref.startingState, 50669 playlist = _ref.playlist; 50670 this.state = 'HAVE_METADATA'; 50671 this.media_ = playlist; 50672 this.loadedPlaylists_[playlist.uri] = playlist; 50673 this.mediaRequest_ = null; // This will trigger loadedplaylist 50674 50675 this.refreshMedia_(); // fire loadedmetadata the first time a media playlist is loaded 50676 // to resolve setup of media groups 50677 50678 if (startingState === 'HAVE_MASTER') { 50679 this.trigger('loadedmetadata'); 50680 } else { 50681 // trigger media change if the active media has been updated 50682 this.trigger('mediachange'); 50683 } 50684 } 50685 }, { 50686 key: 'pause', 50687 value: function pause() { 50688 this.stopRequest(); 50689 window$1.clearTimeout(this.mediaUpdateTimeout); 50690 50691 if (this.state === 'HAVE_NOTHING') { 50692 // If we pause the loader before any data has been retrieved, its as if we never 50693 // started, so reset to an unstarted state. 50694 this.started = false; 50695 } 50696 } 50697 }, { 50698 key: 'load', 50699 value: function load(isFinalRendition) { 50700 var _this2 = this; 50701 50702 window$1.clearTimeout(this.mediaUpdateTimeout); 50703 var media = this.media(); 50704 50705 if (isFinalRendition) { 50706 var delay = media ? media.targetDuration / 2 * 1000 : 5 * 1000; 50707 this.mediaUpdateTimeout = window$1.setTimeout(function () { 50708 return _this2.load(); 50709 }, delay); 50710 return; 50711 } // because the playlists are internal to the manifest, load should either load the 50712 // main manifest, or do nothing but trigger an event 50713 50714 50715 if (!this.started) { 50716 this.start(); 50717 return; 50718 } 50719 50720 this.trigger('loadedplaylist'); 50721 } 50722 /** 50723 * Parses the master xml string and updates playlist uri references 50724 * 50725 * @return {Object} 50726 * The parsed mpd manifest object 50727 */ 50728 50729 }, { 50730 key: 'parseMasterXml', 50731 value: function parseMasterXml() { 50732 var master = parse(this.masterXml_, { 50733 manifestUri: this.srcUrl, 50734 clientOffset: this.clientOffset_ 50735 }); 50736 master.uri = this.srcUrl; // Set up phony URIs for the playlists since we won't have external URIs for DASH 50737 // but reference playlists by their URI throughout the project 50738 // TODO: Should we create the dummy uris in mpd-parser as well (leaning towards yes). 50739 50740 for (var i = 0; i < master.playlists.length; i++) { 50741 var phonyUri = 'placeholder-uri-' + i; 50742 master.playlists[i].uri = phonyUri; // set up by URI references 50743 50744 master.playlists[phonyUri] = master.playlists[i]; 50745 } // set up phony URIs for the media group playlists since we won't have external 50746 // URIs for DASH but reference playlists by their URI throughout the project 50747 50748 50749 forEachMediaGroup(master, function (properties, mediaType, groupKey, labelKey) { 50750 if (properties.playlists && properties.playlists.length) { 50751 var _phonyUri = 'placeholder-uri-' + mediaType + '-' + groupKey + '-' + labelKey; 50752 50753 properties.playlists[0].uri = _phonyUri; // setup URI references 50754 50755 master.playlists[_phonyUri] = properties.playlists[0]; 50756 } 50757 }); 50758 setupMediaPlaylists(master); 50759 resolveMediaGroupUris(master); 50760 return master; 50761 } 50762 }, { 50763 key: 'start', 50764 value: function start() { 50765 var _this3 = this; 50766 50767 this.started = true; // We don't need to request the master manifest again 50768 // Call this asynchronously to match the xhr request behavior below 50769 50770 if (this.masterPlaylistLoader_) { 50771 this.mediaRequest_ = window$1.setTimeout(this.haveMaster_.bind(this), 0); 50772 return; 50773 } // request the specified URL 50774 50775 50776 this.request = this.hls_.xhr({ 50777 uri: this.srcUrl, 50778 withCredentials: this.withCredentials 50779 }, function (error, req) { 50780 // disposed 50781 if (!_this3.request) { 50782 return; 50783 } // clear the loader's request reference 50784 50785 50786 _this3.request = null; 50787 50788 if (error) { 50789 _this3.error = { 50790 status: req.status, 50791 message: 'DASH playlist request error at URL: ' + _this3.srcUrl, 50792 responseText: req.responseText, 50793 // MEDIA_ERR_NETWORK 50794 code: 2 50795 }; 50796 50797 if (_this3.state === 'HAVE_NOTHING') { 50798 _this3.started = false; 50799 } 50800 50801 return _this3.trigger('error'); 50802 } 50803 50804 _this3.masterXml_ = req.responseText; 50805 50806 if (req.responseHeaders && req.responseHeaders.date) { 50807 _this3.masterLoaded_ = Date.parse(req.responseHeaders.date); 50808 } else { 50809 _this3.masterLoaded_ = Date.now(); 50810 } 50811 50812 _this3.srcUrl = resolveManifestRedirect(_this3.handleManifestRedirects, _this3.srcUrl, req); 50813 50814 _this3.syncClientServerClock_(_this3.onClientServerClockSync_.bind(_this3)); 50815 }); 50816 } 50817 /** 50818 * Parses the master xml for UTCTiming node to sync the client clock to the server 50819 * clock. If the UTCTiming node requires a HEAD or GET request, that request is made. 50820 * 50821 * @param {Function} done 50822 * Function to call when clock sync has completed 50823 */ 50824 50825 }, { 50826 key: 'syncClientServerClock_', 50827 value: function syncClientServerClock_(done) { 50828 var _this4 = this; 50829 50830 var utcTiming = parseUTCTiming(this.masterXml_); // No UTCTiming element found in the mpd. Use Date header from mpd request as the 50831 // server clock 50832 50833 if (utcTiming === null) { 50834 this.clientOffset_ = this.masterLoaded_ - Date.now(); 50835 return done(); 50836 } 50837 50838 if (utcTiming.method === 'DIRECT') { 50839 this.clientOffset_ = utcTiming.value - Date.now(); 50840 return done(); 50841 } 50842 50843 this.request = this.hls_.xhr({ 50844 uri: resolveUrl$1(this.srcUrl, utcTiming.value), 50845 method: utcTiming.method, 50846 withCredentials: this.withCredentials 50847 }, function (error, req) { 50848 // disposed 50849 if (!_this4.request) { 50850 return; 50851 } 50852 50853 if (error) { 50854 // sync request failed, fall back to using date header from mpd 50855 // TODO: log warning 50856 _this4.clientOffset_ = _this4.masterLoaded_ - Date.now(); 50857 return done(); 50858 } 50859 50860 var serverTime = void 0; 50861 50862 if (utcTiming.method === 'HEAD') { 50863 if (!req.responseHeaders || !req.responseHeaders.date) { 50864 // expected date header not preset, fall back to using date header from mpd 50865 // TODO: log warning 50866 serverTime = _this4.masterLoaded_; 50867 } else { 50868 serverTime = Date.parse(req.responseHeaders.date); 50869 } 50870 } else { 50871 serverTime = Date.parse(req.responseText); 50872 } 50873 50874 _this4.clientOffset_ = serverTime - Date.now(); 50875 done(); 50876 }); 50877 } 50878 }, { 50879 key: 'haveMaster_', 50880 value: function haveMaster_() { 50881 this.state = 'HAVE_MASTER'; // clear media request 50882 50883 this.mediaRequest_ = null; 50884 50885 if (!this.masterPlaylistLoader_) { 50886 this.master = this.parseMasterXml(); // We have the master playlist at this point, so 50887 // trigger this to allow MasterPlaylistController 50888 // to make an initial playlist selection 50889 50890 this.trigger('loadedplaylist'); 50891 } else if (!this.media_) { 50892 // no media playlist was specifically selected so select 50893 // the one the child playlist loader was created with 50894 this.media(this.childPlaylist_); 50895 } 50896 } 50897 /** 50898 * Handler for after client/server clock synchronization has happened. Sets up 50899 * xml refresh timer if specificed by the manifest. 50900 */ 50901 50902 }, { 50903 key: 'onClientServerClockSync_', 50904 value: function onClientServerClockSync_() { 50905 var _this5 = this; 50906 50907 this.haveMaster_(); 50908 50909 if (!this.hasPendingRequest() && !this.media_) { 50910 this.media(this.master.playlists[0]); 50911 } // TODO: minimumUpdatePeriod can have a value of 0. Currently the manifest will not 50912 // be refreshed when this is the case. The inter-op guide says that when the 50913 // minimumUpdatePeriod is 0, the manifest should outline all currently available 50914 // segments, but future segments may require an update. I think a good solution 50915 // would be to update the manifest at the same rate that the media playlists 50916 // are "refreshed", i.e. every targetDuration. 50917 50918 50919 if (this.master && this.master.minimumUpdatePeriod) { 50920 window$1.setTimeout(function () { 50921 _this5.trigger('minimumUpdatePeriod'); 50922 }, this.master.minimumUpdatePeriod); 50923 } 50924 } 50925 /** 50926 * Sends request to refresh the master xml and updates the parsed master manifest 50927 * TODO: Does the client offset need to be recalculated when the xml is refreshed? 50928 */ 50929 50930 }, { 50931 key: 'refreshXml_', 50932 value: function refreshXml_() { 50933 var _this6 = this; // The srcUrl here *may* need to pass through handleManifestsRedirects when 50934 // sidx is implemented 50935 50936 50937 this.request = this.hls_.xhr({ 50938 uri: this.srcUrl, 50939 withCredentials: this.withCredentials 50940 }, function (error, req) { 50941 // disposed 50942 if (!_this6.request) { 50943 return; 50944 } // clear the loader's request reference 50945 50946 50947 _this6.request = null; 50948 50949 if (error) { 50950 _this6.error = { 50951 status: req.status, 50952 message: 'DASH playlist request error at URL: ' + _this6.srcUrl, 50953 responseText: req.responseText, 50954 // MEDIA_ERR_NETWORK 50955 code: 2 50956 }; 50957 50958 if (_this6.state === 'HAVE_NOTHING') { 50959 _this6.started = false; 50960 } 50961 50962 return _this6.trigger('error'); 50963 } 50964 50965 _this6.masterXml_ = req.responseText; 50966 50967 var newMaster = _this6.parseMasterXml(); 50968 50969 var updatedMaster = updateMaster$1(_this6.master, newMaster); 50970 50971 if (updatedMaster) { 50972 _this6.master = updatedMaster; 50973 } 50974 50975 window$1.setTimeout(function () { 50976 _this6.trigger('minimumUpdatePeriod'); 50977 }, _this6.master.minimumUpdatePeriod); 50978 }); 50979 } 50980 /** 50981 * Refreshes the media playlist by re-parsing the master xml and updating playlist 50982 * references. If this is an alternate loader, the updated parsed manifest is retrieved 50983 * from the master loader. 50984 */ 50985 50986 }, { 50987 key: 'refreshMedia_', 50988 value: function refreshMedia_() { 50989 var _this7 = this; 50990 50991 var oldMaster = void 0; 50992 var newMaster = void 0; 50993 50994 if (this.masterPlaylistLoader_) { 50995 oldMaster = this.masterPlaylistLoader_.master; 50996 newMaster = this.masterPlaylistLoader_.parseMasterXml(); 50997 } else { 50998 oldMaster = this.master; 50999 newMaster = this.parseMasterXml(); 51000 } 51001 51002 var updatedMaster = updateMaster$1(oldMaster, newMaster); 51003 51004 if (updatedMaster) { 51005 if (this.masterPlaylistLoader_) { 51006 this.masterPlaylistLoader_.master = updatedMaster; 51007 } else { 51008 this.master = updatedMaster; 51009 } 51010 51011 this.media_ = updatedMaster.playlists[this.media_.uri]; 51012 } else { 51013 this.trigger('playlistunchanged'); 51014 } 51015 51016 if (!this.media().endList) { 51017 this.mediaUpdateTimeout = window$1.setTimeout(function () { 51018 _this7.trigger('mediaupdatetimeout'); 51019 }, refreshDelay(this.media(), !!updatedMaster)); 51020 } 51021 51022 this.trigger('loadedplaylist'); 51023 } 51024 }]); 51025 return DashPlaylistLoader; 51026 }(EventTarget$1$1); 51027 51028 var logger = function logger(source) { 51029 if (videojs$1.log.debug) { 51030 return videojs$1.log.debug.bind(videojs$1, 'VHS:', source + ' >'); 51031 } 51032 51033 return function () {}; 51034 }; 51035 51036 function noop$1() {} 51037 /** 51038 * @file source-updater.js 51039 */ 51040 51041 /** 51042 * A queue of callbacks to be serialized and applied when a 51043 * MediaSource and its associated SourceBuffers are not in the 51044 * updating state. It is used by the segment loader to update the 51045 * underlying SourceBuffers when new data is loaded, for instance. 51046 * 51047 * @class SourceUpdater 51048 * @param {MediaSource} mediaSource the MediaSource to create the 51049 * SourceBuffer from 51050 * @param {String} mimeType the desired MIME type of the underlying 51051 * SourceBuffer 51052 * @param {Object} sourceBufferEmitter an event emitter that fires when a source buffer is 51053 * added to the media source 51054 */ 51055 51056 51057 var SourceUpdater = function () { 51058 function SourceUpdater(mediaSource, mimeType, type, sourceBufferEmitter) { 51059 classCallCheck$1(this, SourceUpdater); 51060 this.callbacks_ = []; 51061 this.pendingCallback_ = null; 51062 this.timestampOffset_ = 0; 51063 this.mediaSource = mediaSource; 51064 this.processedAppend_ = false; 51065 this.type_ = type; 51066 this.mimeType_ = mimeType; 51067 this.logger_ = logger('SourceUpdater[' + type + '][' + mimeType + ']'); 51068 51069 if (mediaSource.readyState === 'closed') { 51070 mediaSource.addEventListener('sourceopen', this.createSourceBuffer_.bind(this, mimeType, sourceBufferEmitter)); 51071 } else { 51072 this.createSourceBuffer_(mimeType, sourceBufferEmitter); 51073 } 51074 } 51075 51076 createClass$1(SourceUpdater, [{ 51077 key: 'createSourceBuffer_', 51078 value: function createSourceBuffer_(mimeType, sourceBufferEmitter) { 51079 var _this = this; 51080 51081 this.sourceBuffer_ = this.mediaSource.addSourceBuffer(mimeType); 51082 this.logger_('created SourceBuffer'); 51083 51084 if (sourceBufferEmitter) { 51085 sourceBufferEmitter.trigger('sourcebufferadded'); 51086 51087 if (this.mediaSource.sourceBuffers.length < 2) { 51088 // There's another source buffer we must wait for before we can start updating 51089 // our own (or else we can get into a bad state, i.e., appending video/audio data 51090 // before the other video/audio source buffer is available and leading to a video 51091 // or audio only buffer). 51092 sourceBufferEmitter.on('sourcebufferadded', function () { 51093 _this.start_(); 51094 }); 51095 return; 51096 } 51097 } 51098 51099 this.start_(); 51100 } 51101 }, { 51102 key: 'start_', 51103 value: function start_() { 51104 var _this2 = this; 51105 51106 this.started_ = true; // run completion handlers and process callbacks as updateend 51107 // events fire 51108 51109 this.onUpdateendCallback_ = function () { 51110 var pendingCallback = _this2.pendingCallback_; 51111 _this2.pendingCallback_ = null; 51112 51113 _this2.logger_('buffered [' + printableRange(_this2.buffered()) + ']'); 51114 51115 if (pendingCallback) { 51116 pendingCallback(); 51117 } 51118 51119 _this2.runCallback_(); 51120 }; 51121 51122 this.sourceBuffer_.addEventListener('updateend', this.onUpdateendCallback_); 51123 this.runCallback_(); 51124 } 51125 /** 51126 * Aborts the current segment and resets the segment parser. 51127 * 51128 * @param {Function} done function to call when done 51129 * @see http://w3c.github.io/media-source/#widl-SourceBuffer-abort-void 51130 */ 51131 51132 }, { 51133 key: 'abort', 51134 value: function abort(done) { 51135 var _this3 = this; 51136 51137 if (this.processedAppend_) { 51138 this.queueCallback_(function () { 51139 _this3.sourceBuffer_.abort(); 51140 }, done); 51141 } 51142 } 51143 /** 51144 * Queue an update to append an ArrayBuffer. 51145 * 51146 * @param {ArrayBuffer} bytes 51147 * @param {Function} done the function to call when done 51148 * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-appendBuffer-void-ArrayBuffer-data 51149 */ 51150 51151 }, { 51152 key: 'appendBuffer', 51153 value: function appendBuffer(config, done) { 51154 var _this4 = this; 51155 51156 this.processedAppend_ = true; 51157 this.queueCallback_(function () { 51158 if (config.videoSegmentTimingInfoCallback) { 51159 _this4.sourceBuffer_.addEventListener('videoSegmentTimingInfo', config.videoSegmentTimingInfoCallback); 51160 } 51161 51162 _this4.sourceBuffer_.appendBuffer(config.bytes); 51163 }, function () { 51164 if (config.videoSegmentTimingInfoCallback) { 51165 _this4.sourceBuffer_.removeEventListener('videoSegmentTimingInfo', config.videoSegmentTimingInfoCallback); 51166 } 51167 51168 done(); 51169 }); 51170 } 51171 /** 51172 * Indicates what TimeRanges are buffered in the managed SourceBuffer. 51173 * 51174 * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-buffered 51175 */ 51176 51177 }, { 51178 key: 'buffered', 51179 value: function buffered() { 51180 if (!this.sourceBuffer_) { 51181 return videojs$1.createTimeRanges(); 51182 } 51183 51184 return this.sourceBuffer_.buffered; 51185 } 51186 /** 51187 * Queue an update to remove a time range from the buffer. 51188 * 51189 * @param {Number} start where to start the removal 51190 * @param {Number} end where to end the removal 51191 * @param {Function} [done=noop] optional callback to be executed when the remove 51192 * operation is complete 51193 * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-remove-void-double-start-unrestricted-double-end 51194 */ 51195 51196 }, { 51197 key: 'remove', 51198 value: function remove(start, end) { 51199 var _this5 = this; 51200 51201 var done = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : noop$1; 51202 51203 if (this.processedAppend_) { 51204 this.queueCallback_(function () { 51205 _this5.logger_('remove [' + start + ' => ' + end + ']'); 51206 51207 _this5.sourceBuffer_.remove(start, end); 51208 }, done); 51209 } 51210 } 51211 /** 51212 * Whether the underlying sourceBuffer is updating or not 51213 * 51214 * @return {Boolean} the updating status of the SourceBuffer 51215 */ 51216 51217 }, { 51218 key: 'updating', 51219 value: function updating() { 51220 // we are updating if the sourcebuffer is updating or 51221 return !this.sourceBuffer_ || this.sourceBuffer_.updating || // if we have a pending callback that is not our internal noop 51222 !!this.pendingCallback_ && this.pendingCallback_ !== noop$1; 51223 } 51224 /** 51225 * Set/get the timestampoffset on the SourceBuffer 51226 * 51227 * @return {Number} the timestamp offset 51228 */ 51229 51230 }, { 51231 key: 'timestampOffset', 51232 value: function timestampOffset(offset) { 51233 var _this6 = this; 51234 51235 if (typeof offset !== 'undefined') { 51236 this.queueCallback_(function () { 51237 _this6.sourceBuffer_.timestampOffset = offset; 51238 }); 51239 this.timestampOffset_ = offset; 51240 } 51241 51242 return this.timestampOffset_; 51243 } 51244 /** 51245 * Queue a callback to run 51246 */ 51247 51248 }, { 51249 key: 'queueCallback_', 51250 value: function queueCallback_(callback, done) { 51251 this.callbacks_.push([callback.bind(this), done]); 51252 this.runCallback_(); 51253 } 51254 /** 51255 * Run a queued callback 51256 */ 51257 51258 }, { 51259 key: 'runCallback_', 51260 value: function runCallback_() { 51261 var callbacks = void 0; 51262 51263 if (!this.updating() && this.callbacks_.length && this.started_) { 51264 callbacks = this.callbacks_.shift(); 51265 this.pendingCallback_ = callbacks[1]; 51266 callbacks[0](); 51267 } 51268 } 51269 /** 51270 * dispose of the source updater and the underlying sourceBuffer 51271 */ 51272 51273 }, { 51274 key: 'dispose', 51275 value: function dispose() { 51276 this.sourceBuffer_.removeEventListener('updateend', this.onUpdateendCallback_); 51277 51278 if (this.sourceBuffer_ && this.mediaSource.readyState === 'open') { 51279 this.sourceBuffer_.abort(); 51280 } 51281 } 51282 }]); 51283 return SourceUpdater; 51284 }(); 51285 51286 var Config = { 51287 GOAL_BUFFER_LENGTH: 30, 51288 MAX_GOAL_BUFFER_LENGTH: 60, 51289 GOAL_BUFFER_LENGTH_RATE: 1, 51290 // 0.5 MB/s 51291 INITIAL_BANDWIDTH: 4194304, 51292 // A fudge factor to apply to advertised playlist bitrates to account for 51293 // temporary flucations in client bandwidth 51294 BANDWIDTH_VARIANCE: 1.2, 51295 // How much of the buffer must be filled before we consider upswitching 51296 BUFFER_LOW_WATER_LINE: 0, 51297 MAX_BUFFER_LOW_WATER_LINE: 30, 51298 BUFFER_LOW_WATER_LINE_RATE: 1 51299 }; 51300 var REQUEST_ERRORS = { 51301 FAILURE: 2, 51302 TIMEOUT: -101, 51303 ABORTED: -102 51304 }; 51305 /** 51306 * Turns segment byterange into a string suitable for use in 51307 * HTTP Range requests 51308 * 51309 * @param {Object} byterange - an object with two values defining the start and end 51310 * of a byte-range 51311 */ 51312 51313 var byterangeStr = function byterangeStr(byterange) { 51314 var byterangeStart = void 0; 51315 var byterangeEnd = void 0; // `byterangeEnd` is one less than `offset + length` because the HTTP range 51316 // header uses inclusive ranges 51317 51318 byterangeEnd = byterange.offset + byterange.length - 1; 51319 byterangeStart = byterange.offset; 51320 return 'bytes=' + byterangeStart + '-' + byterangeEnd; 51321 }; 51322 /** 51323 * Defines headers for use in the xhr request for a particular segment. 51324 * 51325 * @param {Object} segment - a simplified copy of the segmentInfo object 51326 * from SegmentLoader 51327 */ 51328 51329 51330 var segmentXhrHeaders = function segmentXhrHeaders(segment) { 51331 var headers = {}; 51332 51333 if (segment.byterange) { 51334 headers.Range = byterangeStr(segment.byterange); 51335 } 51336 51337 return headers; 51338 }; 51339 /** 51340 * Abort all requests 51341 * 51342 * @param {Object} activeXhrs - an object that tracks all XHR requests 51343 */ 51344 51345 51346 var abortAll = function abortAll(activeXhrs) { 51347 activeXhrs.forEach(function (xhr) { 51348 xhr.abort(); 51349 }); 51350 }; 51351 /** 51352 * Gather important bandwidth stats once a request has completed 51353 * 51354 * @param {Object} request - the XHR request from which to gather stats 51355 */ 51356 51357 51358 var getRequestStats = function getRequestStats(request) { 51359 return { 51360 bandwidth: request.bandwidth, 51361 bytesReceived: request.bytesReceived || 0, 51362 roundTripTime: request.roundTripTime || 0 51363 }; 51364 }; 51365 /** 51366 * If possible gather bandwidth stats as a request is in 51367 * progress 51368 * 51369 * @param {Event} progressEvent - an event object from an XHR's progress event 51370 */ 51371 51372 51373 var getProgressStats = function getProgressStats(progressEvent) { 51374 var request = progressEvent.target; 51375 var roundTripTime = Date.now() - request.requestTime; 51376 var stats = { 51377 bandwidth: Infinity, 51378 bytesReceived: 0, 51379 roundTripTime: roundTripTime || 0 51380 }; 51381 stats.bytesReceived = progressEvent.loaded; // This can result in Infinity if stats.roundTripTime is 0 but that is ok 51382 // because we should only use bandwidth stats on progress to determine when 51383 // abort a request early due to insufficient bandwidth 51384 51385 stats.bandwidth = Math.floor(stats.bytesReceived / stats.roundTripTime * 8 * 1000); 51386 return stats; 51387 }; 51388 /** 51389 * Handle all error conditions in one place and return an object 51390 * with all the information 51391 * 51392 * @param {Error|null} error - if non-null signals an error occured with the XHR 51393 * @param {Object} request - the XHR request that possibly generated the error 51394 */ 51395 51396 51397 var handleErrors = function handleErrors(error, request) { 51398 if (request.timedout) { 51399 return { 51400 status: request.status, 51401 message: 'HLS request timed-out at URL: ' + request.uri, 51402 code: REQUEST_ERRORS.TIMEOUT, 51403 xhr: request 51404 }; 51405 } 51406 51407 if (request.aborted) { 51408 return { 51409 status: request.status, 51410 message: 'HLS request aborted at URL: ' + request.uri, 51411 code: REQUEST_ERRORS.ABORTED, 51412 xhr: request 51413 }; 51414 } 51415 51416 if (error) { 51417 return { 51418 status: request.status, 51419 message: 'HLS request errored at URL: ' + request.uri, 51420 code: REQUEST_ERRORS.FAILURE, 51421 xhr: request 51422 }; 51423 } 51424 51425 return null; 51426 }; 51427 /** 51428 * Handle responses for key data and convert the key data to the correct format 51429 * for the decryption step later 51430 * 51431 * @param {Object} segment - a simplified copy of the segmentInfo object 51432 * from SegmentLoader 51433 * @param {Function} finishProcessingFn - a callback to execute to continue processing 51434 * this request 51435 */ 51436 51437 51438 var handleKeyResponse = function handleKeyResponse(segment, finishProcessingFn) { 51439 return function (error, request) { 51440 var response = request.response; 51441 var errorObj = handleErrors(error, request); 51442 51443 if (errorObj) { 51444 return finishProcessingFn(errorObj, segment); 51445 } 51446 51447 if (response.byteLength !== 16) { 51448 return finishProcessingFn({ 51449 status: request.status, 51450 message: 'Invalid HLS key at URL: ' + request.uri, 51451 code: REQUEST_ERRORS.FAILURE, 51452 xhr: request 51453 }, segment); 51454 } 51455 51456 var view = new DataView(response); 51457 segment.key.bytes = new Uint32Array([view.getUint32(0), view.getUint32(4), view.getUint32(8), view.getUint32(12)]); 51458 return finishProcessingFn(null, segment); 51459 }; 51460 }; 51461 /** 51462 * Handle init-segment responses 51463 * 51464 * @param {Object} segment - a simplified copy of the segmentInfo object 51465 * from SegmentLoader 51466 * @param {Function} finishProcessingFn - a callback to execute to continue processing 51467 * this request 51468 */ 51469 51470 51471 var handleInitSegmentResponse = function handleInitSegmentResponse(segment, captionParser, finishProcessingFn) { 51472 return function (error, request) { 51473 var response = request.response; 51474 var errorObj = handleErrors(error, request); 51475 51476 if (errorObj) { 51477 return finishProcessingFn(errorObj, segment); 51478 } // stop processing if received empty content 51479 51480 51481 if (response.byteLength === 0) { 51482 return finishProcessingFn({ 51483 status: request.status, 51484 message: 'Empty HLS segment content at URL: ' + request.uri, 51485 code: REQUEST_ERRORS.FAILURE, 51486 xhr: request 51487 }, segment); 51488 } 51489 51490 segment.map.bytes = new Uint8Array(request.response); // Initialize CaptionParser if it hasn't been yet 51491 51492 if (!captionParser.isInitialized()) { 51493 captionParser.init(); 51494 } 51495 51496 segment.map.timescales = probe.timescale(segment.map.bytes); 51497 segment.map.videoTrackIds = probe.videoTrackIds(segment.map.bytes); 51498 return finishProcessingFn(null, segment); 51499 }; 51500 }; 51501 /** 51502 * Response handler for segment-requests being sure to set the correct
51503 * property depending on whether the segment is encryped or not 51504 * Also records and keeps track of stats that are used for ABR purposes 51505 * 51506 * @param {Object} segment - a simplified copy of the segmentInfo object 51507 * from SegmentLoader 51508 * @param {Function} finishProcessingFn - a callback to execute to continue processing 51509 * this request 51510 */ 51511 51512 51513 var handleSegmentResponse = function handleSegmentResponse(segment, captionParser, finishProcessingFn) { 51514 return function (error, request) { 51515 var response = request.response; 51516 var errorObj = handleErrors(error, request); 51517 var parsed = void 0; 51518 51519 if (errorObj) { 51520 return finishProcessingFn(errorObj, segment); 51521 } // stop processing if received empty content 51522 51523 51524 if (response.byteLength === 0) { 51525 return finishProcessingFn({ 51526 status: request.status, 51527 message: 'Empty HLS segment content at URL: ' + request.uri, 51528 code: REQUEST_ERRORS.FAILURE, 51529 xhr: request 51530 }, segment); 51531 } 51532 51533 segment.stats = getRequestStats(request); 51534 51535 if (segment.key) { 51536 segment.encryptedBytes = new Uint8Array(request.response); 51537 } else { 51538 segment.bytes = new Uint8Array(request.response); 51539 } // This is likely an FMP4 and has the init segment. 51540 // Run through the CaptionParser in case there are captions. 51541 51542 51543 if (segment.map && segment.map.bytes) { 51544 // Initialize CaptionParser if it hasn't been yet 51545 if (!captionParser.isInitialized()) { 51546 captionParser.init(); 51547 } 51548 51549 parsed = captionParser.parse(segment.bytes, segment.map.videoTrackIds, segment.map.timescales); 51550 51551 if (parsed && parsed.captions) { 51552 segment.captionStreams = parsed.captionStreams; 51553 segment.fmp4Captions = parsed.captions; 51554 } 51555 } 51556 51557 return finishProcessingFn(null, segment); 51558 }; 51559 }; 51560 /** 51561 * Decrypt the segment via the decryption web worker 51562 * 51563 * @param {WebWorker} decrypter - a WebWorker interface to AES-128 decryption routines 51564 * @param {Object} segment - a simplified copy of the segmentInfo object 51565 * from SegmentLoader 51566 * @param {Function} doneFn - a callback that is executed after decryption has completed 51567 */ 51568 51569 51570 var decryptSegment = function decryptSegment(decrypter, segment, doneFn) { 51571 var decryptionHandler = function decryptionHandler(event) { 51572 if (event.data.source === segment.requestId) { 51573 decrypter.removeEventListener('message', decryptionHandler); 51574 var decrypted = event.data.decrypted; 51575 segment.bytes = new Uint8Array(decrypted.bytes, decrypted.byteOffset, decrypted.byteLength); 51576 return doneFn(null, segment); 51577 } 51578 }; 51579 51580 decrypter.addEventListener('message', decryptionHandler); // this is an encrypted segment 51581 // incrementally decrypt the segment 51582 51583 decrypter.postMessage(createTransferableMessage({ 51584 source: segment.requestId, 51585 encrypted: segment.encryptedBytes, 51586 key: segment.key.bytes, 51587 iv: segment.key.iv 51588 }), [segment.encryptedBytes.buffer, segment.key.bytes.buffer]); 51589 }; 51590 /** 51591 * This function waits for all XHRs to finish (with either success or failure) 51592 * before continueing processing via it's callback. The function gathers errors 51593 * from each request into a single errors array so that the error status for 51594 * each request can be examined later. 51595 * 51596 * @param {Object} activeXhrs - an object that tracks all XHR requests 51597 * @param {WebWorker} decrypter - a WebWorker interface to AES-128 decryption routines 51598 * @param {Function} doneFn - a callback that is executed after all resources have been 51599 * downloaded and any decryption completed 51600 */ 51601 51602 51603 var waitForCompletion = function waitForCompletion(activeXhrs, decrypter, doneFn) { 51604 var count = 0; 51605 var didError = false; 51606 return function (error, segment) { 51607 if (didError) { 51608 return; 51609 } 51610 51611 if (error) { 51612 didError = true; // If there are errors, we have to abort any outstanding requests 51613 51614 abortAll(activeXhrs); // Even though the requests above are aborted, and in theory we could wait until we 51615 // handle the aborted events from those requests, there are some cases where we may 51616 // never get an aborted event. For instance, if the network connection is lost and
vendor: 203,709 bytes, lines 51617-57234
51617 // there were two requests, the first may have triggered an error immediately, while 51618 // the second request remains unsent. In that case, the aborted algorithm will not 51619 // trigger an abort: see https://xhr.spec.whatwg.org/#the-abort()-method 51620 // 51621 // We also can't rely on the ready state of the XHR, since the request that 51622 // triggered the connection error may also show as a ready state of 0 (unsent). 51623 // Therefore, we have to finish this group of requests immediately after the first 51624 // seen error. 51625 51626 return doneFn(error, segment); 51627 } 51628 51629 count += 1; 51630 51631 if (count === activeXhrs.length) { 51632 // Keep track of when *all* of the requests have completed 51633 segment.endOfAllRequests = Date.now(); 51634 51635 if (segment.encryptedBytes) { 51636 return decryptSegment(decrypter, segment, doneFn); 51637 } // Otherwise, everything is ready just continue 51638 51639 51640 return doneFn(null, segment); 51641 } 51642 }; 51643 }; 51644 /** 51645 * Simple progress event callback handler that gathers some stats before 51646 * executing a provided callback with the `segment` object 51647 * 51648 * @param {Object} segment - a simplified copy of the segmentInfo object 51649 * from SegmentLoader 51650 * @param {Function} progressFn - a callback that is executed each time a progress event 51651 * is received 51652 * @param {Event} event - the progress event object from XMLHttpRequest 51653 */ 51654 51655 51656 var handleProgress = function handleProgress(segment, progressFn) { 51657 return function (event) { 51658 segment.stats = videojs$1.mergeOptions(segment.stats, getProgressStats(event)); // record the time that we receive the first byte of data 51659 51660 if (!segment.stats.firstBytesReceivedAt && segment.stats.bytesReceived) { 51661 segment.stats.firstBytesReceivedAt = Date.now(); 51662 } 51663 51664 return progressFn(event, segment); 51665 }; 51666 }; 51667 /** 51668 * Load all resources and does any processing necessary for a media-segment 51669 * 51670 * Features: 51671 * decrypts the media-segment if it has a key uri and an iv 51672 * aborts *all* requests if *any* one request fails 51673 * 51674 * The segment object, at minimum, has the following format: 51675 * { 51676 * resolvedUri: String, 51677 * [byterange]: { 51678 * offset: Number, 51679 * length: Number 51680 * }, 51681 * [key]: { 51682 * resolvedUri: String 51683 * [byterange]: { 51684 * offset: Number, 51685 * length: Number 51686 * }, 51687 * iv: { 51688 * bytes: Uint32Array 51689 * } 51690 * }, 51691 * [map]: { 51692 * resolvedUri: String, 51693 * [byterange]: { 51694 * offset: Number, 51695 * length: Number 51696 * }, 51697 * [bytes]: Uint8Array 51698 * } 51699 * } 51700 * ...where [name] denotes optional properties 51701 * 51702 * @param {Function} xhr - an instance of the xhr wrapper in xhr.js 51703 * @param {Object} xhrOptions - the base options to provide to all xhr requests 51704 * @param {WebWorker} decryptionWorker - a WebWorker interface to AES-128 51705 * decryption routines 51706 * @param {Object} segment - a simplified copy of the segmentInfo object 51707 * from SegmentLoader 51708 * @param {Function} progressFn - a callback that receives progress events from the main 51709 * segment's xhr request 51710 * @param {Function} doneFn - a callback that is executed only once all requests have 51711 * succeeded or failed 51712 * @returns {Function} a function that, when invoked, immediately aborts all 51713 * outstanding requests 51714 */ 51715 51716 51717 var mediaSegmentRequest = function mediaSegmentRequest(xhr, xhrOptions, decryptionWorker, captionParser, segment, progressFn, doneFn) { 51718 var activeXhrs = []; 51719 var finishProcessingFn = waitForCompletion(activeXhrs, decryptionWorker, doneFn); // optionally, request the decryption key 51720 51721 if (segment.key) { 51722 var keyRequestOptions = videojs$1.mergeOptions(xhrOptions, { 51723 uri: segment.key.resolvedUri, 51724 responseType: 'arraybuffer' 51725 }); 51726 var keyRequestCallback = handleKeyResponse(segment, finishProcessingFn); 51727 var keyXhr = xhr(keyRequestOptions, keyRequestCallback); 51728 activeXhrs.push(keyXhr); 51729 } // optionally, request the associated media init segment 51730 51731 51732 if (segment.map && !segment.map.bytes) { 51733 var initSegmentOptions = videojs$1.mergeOptions(xhrOptions, { 51734 uri: segment.map.resolvedUri, 51735 responseType: 'arraybuffer', 51736 headers: segmentXhrHeaders(segment.map) 51737 }); 51738 var initSegmentRequestCallback = handleInitSegmentResponse(segment, captionParser, finishProcessingFn); 51739 var initSegmentXhr = xhr(initSegmentOptions, initSegmentRequestCallback); 51740 activeXhrs.push(initSegmentXhr); 51741 } 51742 51743 var segmentRequestOptions = videojs$1.mergeOptions(xhrOptions, { 51744 uri: segment.resolvedUri, 51745 responseType: 'arraybuffer', 51746 headers: segmentXhrHeaders(segment) 51747 }); 51748 var segmentRequestCallback = handleSegmentResponse(segment, captionParser, finishProcessingFn); 51749 var segmentXhr = xhr(segmentRequestOptions, segmentRequestCallback); 51750 segmentXhr.addEventListener('progress', handleProgress(segment, progressFn)); 51751 activeXhrs.push(segmentXhr); 51752 return function () { 51753 return abortAll(activeXhrs); 51754 }; 51755 }; // Utilities 51756 51757 /** 51758 * Returns the CSS value for the specified property on an element 51759 * using `getComputedStyle`. Firefox has a long-standing issue where 51760 * getComputedStyle() may return null when running in an iframe with 51761 * `display: none`. 51762 * 51763 * @see https://bugzilla.mozilla.org/show_bug.cgi?id=548397 51764 * @param {HTMLElement} el the htmlelement to work on 51765 * @param {string} the proprety to get the style for 51766 */ 51767 51768 51769 var safeGetComputedStyle = function safeGetComputedStyle(el, property) { 51770 var result = void 0; 51771 51772 if (!el) { 51773 return ''; 51774 } 51775 51776 result = window$1.getComputedStyle(el); 51777 51778 if (!result) { 51779 return ''; 51780 } 51781 51782 return result[property]; 51783 }; 51784 /** 51785 * Resuable stable sort function 51786 * 51787 * @param {Playlists} array 51788 * @param {Function} sortFn Different comparators 51789 * @function stableSort 51790 */ 51791 51792 51793 var stableSort = function stableSort(array, sortFn) { 51794 var newArray = array.slice(); 51795 array.sort(function (left, right) { 51796 var cmp = sortFn(left, right); 51797 51798 if (cmp === 0) { 51799 return newArray.indexOf(left) - newArray.indexOf(right); 51800 } 51801 51802 return cmp; 51803 }); 51804 }; 51805 /** 51806 * A comparator function to sort two playlist object by bandwidth. 51807 * 51808 * @param {Object} left a media playlist object 51809 * @param {Object} right a media playlist object 51810 * @return {Number} Greater than zero if the bandwidth attribute of 51811 * left is greater than the corresponding attribute of right. Less 51812 * than zero if the bandwidth of right is greater than left and 51813 * exactly zero if the two are equal. 51814 */ 51815 51816 51817 var comparePlaylistBandwidth = function comparePlaylistBandwidth(left, right) { 51818 var leftBandwidth = void 0; 51819 var rightBandwidth = void 0; 51820 51821 if (left.attributes.BANDWIDTH) { 51822 leftBandwidth = left.attributes.BANDWIDTH; 51823 } 51824 51825 leftBandwidth = leftBandwidth || window$1.Number.MAX_VALUE; 51826 51827 if (right.attributes.BANDWIDTH) { 51828 rightBandwidth = right.attributes.BANDWIDTH; 51829 } 51830 51831 rightBandwidth = rightBandwidth || window$1.Number.MAX_VALUE; 51832 return leftBandwidth - rightBandwidth; 51833 }; 51834 /** 51835 * A comparator function to sort two playlist object by resolution (width). 51836 * @param {Object} left a media playlist object 51837 * @param {Object} right a media playlist object 51838 * @return {Number} Greater than zero if the resolution.width attribute of 51839 * left is greater than the corresponding attribute of right. Less 51840 * than zero if the resolution.width of right is greater than left and 51841 * exactly zero if the two are equal. 51842 */ 51843 51844 51845 var comparePlaylistResolution = function comparePlaylistResolution(left, right) { 51846 var leftWidth = void 0; 51847 var rightWidth = void 0; 51848 51849 if (left.attributes.RESOLUTION && left.attributes.RESOLUTION.width) { 51850 leftWidth = left.attributes.RESOLUTION.width; 51851 } 51852 51853 leftWidth = leftWidth || window$1.Number.MAX_VALUE; 51854 51855 if (right.attributes.RESOLUTION && right.attributes.RESOLUTION.width) { 51856 rightWidth = right.attributes.RESOLUTION.width; 51857 } 51858 51859 rightWidth = rightWidth || window$1.Number.MAX_VALUE; // NOTE - Fallback to bandwidth sort as appropriate in cases where multiple renditions 51860 // have the same media dimensions/ resolution 51861 51862 if (leftWidth === rightWidth && left.attributes.BANDWIDTH && right.attributes.BANDWIDTH) { 51863 return left.attributes.BANDWIDTH - right.attributes.BANDWIDTH; 51864 } 51865 51866 return leftWidth - rightWidth; 51867 }; 51868 /** 51869 * Chooses the appropriate media playlist based on bandwidth and player size 51870 * 51871 * @param {Object} master 51872 * Object representation of the master manifest 51873 * @param {Number} playerBandwidth 51874 * Current calculated bandwidth of the player 51875 * @param {Number} playerWidth 51876 * Current width of the player element 51877 * @param {Number} playerHeight 51878 * Current height of the player element 51879 * @param {Boolean} limitRenditionByPlayerDimensions 51880 * True if the player width and height should be used during the selection, false otherwise 51881 * @return {Playlist} the highest bitrate playlist less than the 51882 * currently detected bandwidth, accounting for some amount of 51883 * bandwidth variance 51884 */ 51885 51886 51887 var simpleSelector = function simpleSelector(master, playerBandwidth, playerWidth, playerHeight, limitRenditionByPlayerDimensions) { 51888 // convert the playlists to an intermediary representation to make comparisons easier 51889 var sortedPlaylistReps = master.playlists.map(function (playlist) { 51890 var width = void 0; 51891 var height = void 0; 51892 var bandwidth = void 0; 51893 width = playlist.attributes.RESOLUTION && playlist.attributes.RESOLUTION.width; 51894 height = playlist.attributes.RESOLUTION && playlist.attributes.RESOLUTION.height; 51895 bandwidth = playlist.attributes.BANDWIDTH; 51896 bandwidth = bandwidth || window$1.Number.MAX_VALUE; 51897 return { 51898 bandwidth: bandwidth, 51899 width: width, 51900 height: height, 51901 playlist: playlist 51902 }; 51903 }); 51904 stableSort(sortedPlaylistReps, function (left, right) { 51905 return left.bandwidth - right.bandwidth; 51906 }); // filter out any playlists that have been excluded due to 51907 // incompatible configurations 51908 51909 sortedPlaylistReps = sortedPlaylistReps.filter(function (rep) { 51910 return !Playlist.isIncompatible(rep.playlist); 51911 }); // filter out any playlists that have been disabled manually through the representations 51912 // api or blacklisted temporarily due to playback errors. 51913 51914 var enabledPlaylistReps = sortedPlaylistReps.filter(function (rep) { 51915 return Playlist.isEnabled(rep.playlist); 51916 }); 51917 51918 if (!enabledPlaylistReps.length) { 51919 // if there are no enabled playlists, then they have all been blacklisted or disabled 51920 // by the user through the representations api. In this case, ignore blacklisting and 51921 // fallback to what the user wants by using playlists the user has not disabled. 51922 enabledPlaylistReps = sortedPlaylistReps.filter(function (rep) { 51923 return !Playlist.isDisabled(rep.playlist); 51924 }); 51925 } // filter out any variant that has greater effective bitrate 51926 // than the current estimated bandwidth 51927 51928 51929 var bandwidthPlaylistReps = enabledPlaylistReps.filter(function (rep) { 51930 return rep.bandwidth * Config.BANDWIDTH_VARIANCE < playerBandwidth; 51931 }); 51932 var highestRemainingBandwidthRep = bandwidthPlaylistReps[bandwidthPlaylistReps.length - 1]; // get all of the renditions with the same (highest) bandwidth 51933 // and then taking the very first element 51934 51935 var bandwidthBestRep = bandwidthPlaylistReps.filter(function (rep) { 51936 return rep.bandwidth === highestRemainingBandwidthRep.bandwidth; 51937 })[0]; // if we're not going to limit renditions by player size, make an early decision. 51938 51939 if (limitRenditionByPlayerDimensions === false) { 51940 var _chosenRep = bandwidthBestRep || enabledPlaylistReps[0] || sortedPlaylistReps[0]; 51941 51942 return _chosenRep ? _chosenRep.playlist : null; 51943 } // filter out playlists without resolution information 51944 51945 51946 var haveResolution = bandwidthPlaylistReps.filter(function (rep) { 51947 return rep.width && rep.height; 51948 }); // sort variants by resolution 51949 51950 stableSort(haveResolution, function (left, right) { 51951 return left.width - right.width; 51952 }); // if we have the exact resolution as the player use it 51953 51954 var resolutionBestRepList = haveResolution.filter(function (rep) { 51955 return rep.width === playerWidth && rep.height === playerHeight; 51956 }); 51957 highestRemainingBandwidthRep = resolutionBestRepList[resolutionBestRepList.length - 1]; // ensure that we pick the highest bandwidth variant that have exact resolution 51958 51959 var resolutionBestRep = resolutionBestRepList.filter(function (rep) { 51960 return rep.bandwidth === highestRemainingBandwidthRep.bandwidth; 51961 })[0]; 51962 var resolutionPlusOneList = void 0; 51963 var resolutionPlusOneSmallest = void 0; 51964 var resolutionPlusOneRep = void 0; // find the smallest variant that is larger than the player 51965 // if there is no match of exact resolution 51966 51967 if (!resolutionBestRep) { 51968 resolutionPlusOneList = haveResolution.filter(function (rep) { 51969 return rep.width > playerWidth || rep.height > playerHeight; 51970 }); // find all the variants have the same smallest resolution 51971 51972 resolutionPlusOneSmallest = resolutionPlusOneList.filter(function (rep) { 51973 return rep.width === resolutionPlusOneList[0].width && rep.height === resolutionPlusOneList[0].height; 51974 }); // ensure that we also pick the highest bandwidth variant that 51975 // is just-larger-than the video player 51976 51977 highestRemainingBandwidthRep = resolutionPlusOneSmallest[resolutionPlusOneSmallest.length - 1]; 51978 resolutionPlusOneRep = resolutionPlusOneSmallest.filter(function (rep) { 51979 return rep.bandwidth === highestRemainingBandwidthRep.bandwidth; 51980 })[0]; 51981 } // fallback chain of variants 51982 51983 51984 var chosenRep = resolutionPlusOneRep || resolutionBestRep || bandwidthBestRep || enabledPlaylistReps[0] || sortedPlaylistReps[0]; 51985 return chosenRep ? chosenRep.playlist : null; 51986 }; // Playlist Selectors 51987 51988 /** 51989 * Chooses the appropriate media playlist based on the most recent 51990 * bandwidth estimate and the player size. 51991 * 51992 * Expects to be called within the context of an instance of HlsHandler 51993 * 51994 * @return {Playlist} the highest bitrate playlist less than the 51995 * currently detected bandwidth, accounting for some amount of 51996 * bandwidth variance 51997 */ 51998 51999 52000 var lastBandwidthSelector = function lastBandwidthSelector() { 52001 return simpleSelector(this.playlists.master, this.systemBandwidth, parseInt(safeGetComputedStyle(this.tech_.el(), 'width'), 10), parseInt(safeGetComputedStyle(this.tech_.el(), 'height'), 10), this.limitRenditionByPlayerDimensions); 52002 }; 52003 /** 52004 * Chooses the appropriate media playlist based on the potential to rebuffer 52005 * 52006 * @param {Object} settings 52007 * Object of information required to use this selector 52008 * @param {Object} settings.master 52009 * Object representation of the master manifest 52010 * @param {Number} settings.currentTime 52011 * The current time of the player 52012 * @param {Number} settings.bandwidth 52013 * Current measured bandwidth 52014 * @param {Number} settings.duration 52015 * Duration of the media 52016 * @param {Number} settings.segmentDuration 52017 * Segment duration to be used in round trip time calculations 52018 * @param {Number} settings.timeUntilRebuffer 52019 * Time left in seconds until the player has to rebuffer 52020 * @param {Number} settings.currentTimeline 52021 * The current timeline segments are being loaded from 52022 * @param {SyncController} settings.syncController 52023 * SyncController for determining if we have a sync point for a given playlist 52024 * @return {Object|null} 52025 * {Object} return.playlist 52026 * The highest bandwidth playlist with the least amount of rebuffering 52027 * {Number} return.rebufferingImpact 52028 * The amount of time in seconds switching to this playlist will rebuffer. A 52029 * negative value means that switching will cause zero rebuffering. 52030 */ 52031 52032 52033 var minRebufferMaxBandwidthSelector = function minRebufferMaxBandwidthSelector(settings) { 52034 var master = settings.master, 52035 currentTime = settings.currentTime, 52036 bandwidth = settings.bandwidth, 52037 duration$$1 = settings.duration, 52038 segmentDuration = settings.segmentDuration, 52039 timeUntilRebuffer = settings.timeUntilRebuffer, 52040 currentTimeline = settings.currentTimeline, 52041 syncController = settings.syncController; // filter out any playlists that have been excluded due to 52042 // incompatible configurations 52043 52044 var compatiblePlaylists = master.playlists.filter(function (playlist) { 52045 return !Playlist.isIncompatible(playlist); 52046 }); // filter out any playlists that have been disabled manually through the representations 52047 // api or blacklisted temporarily due to playback errors. 52048 52049 var enabledPlaylists = compatiblePlaylists.filter(Playlist.isEnabled); 52050 52051 if (!enabledPlaylists.length) { 52052 // if there are no enabled playlists, then they have all been blacklisted or disabled 52053 // by the user through the representations api. In this case, ignore blacklisting and 52054 // fallback to what the user wants by using playlists the user has not disabled. 52055 enabledPlaylists = compatiblePlaylists.filter(function (playlist) { 52056 return !Playlist.isDisabled(playlist); 52057 }); 52058 } 52059 52060 var bandwidthPlaylists = enabledPlaylists.filter(Playlist.hasAttribute.bind(null, 'BANDWIDTH')); 52061 var rebufferingEstimates = bandwidthPlaylists.map(function (playlist) { 52062 var syncPoint = syncController.getSyncPoint(playlist, duration$$1, currentTimeline, currentTime); // If there is no sync point for this playlist, switching to it will require a 52063 // sync request first. This will double the request time 52064 52065 var numRequests = syncPoint ? 1 : 2; 52066 var requestTimeEstimate = Playlist.estimateSegmentRequestTime(segmentDuration, bandwidth, playlist); 52067 var rebufferingImpact = requestTimeEstimate * numRequests - timeUntilRebuffer; 52068 return { 52069 playlist: playlist, 52070 rebufferingImpact: rebufferingImpact 52071 }; 52072 }); 52073 var noRebufferingPlaylists = rebufferingEstimates.filter(function (estimate) { 52074 return estimate.rebufferingImpact <= 0; 52075 }); // Sort by bandwidth DESC 52076 52077 stableSort(noRebufferingPlaylists, function (a, b) { 52078 return comparePlaylistBandwidth(b.playlist, a.playlist); 52079 }); 52080 52081 if (noRebufferingPlaylists.length) { 52082 return noRebufferingPlaylists[0]; 52083 } 52084 52085 stableSort(rebufferingEstimates, function (a, b) { 52086 return a.rebufferingImpact - b.rebufferingImpact; 52087 }); 52088 return rebufferingEstimates[0] || null; 52089 }; 52090 /** 52091 * Chooses the appropriate media playlist, which in this case is the lowest bitrate 52092 * one with video. If no renditions with video exist, return the lowest audio rendition. 52093 * 52094 * Expects to be called within the context of an instance of HlsHandler 52095 * 52096 * @return {Object|null} 52097 * {Object} return.playlist 52098 * The lowest bitrate playlist that contains a video codec. If no such rendition 52099 * exists pick the lowest audio rendition. 52100 */ 52101 52102 52103 var lowestBitrateCompatibleVariantSelector = function lowestBitrateCompatibleVariantSelector() { 52104 // filter out any playlists that have been excluded due to 52105 // incompatible configurations or playback errors 52106 var playlists = this.playlists.master.playlists.filter(Playlist.isEnabled); // Sort ascending by bitrate 52107 52108 stableSort(playlists, function (a, b) { 52109 return comparePlaylistBandwidth(a, b); 52110 }); // Parse and assume that playlists with no video codec have no video 52111 // (this is not necessarily true, although it is generally true). 52112 // 52113 // If an entire manifest has no valid videos everything will get filtered 52114 // out. 52115 52116 var playlistsWithVideo = playlists.filter(function (playlist) { 52117 return parseCodecs(playlist.attributes.CODECS).videoCodec; 52118 }); 52119 return playlistsWithVideo[0] || null; 52120 }; 52121 /** 52122 * Create captions text tracks on video.js if they do not exist 52123 * 52124 * @param {Object} inbandTextTracks a reference to current inbandTextTracks 52125 * @param {Object} tech the video.js tech 52126 * @param {Object} captionStreams the caption streams to create 52127 * @private 52128 */ 52129 52130 52131 var createCaptionsTrackIfNotExists = function createCaptionsTrackIfNotExists(inbandTextTracks, tech, captionStreams) { 52132 for (var trackId in captionStreams) { 52133 if (!inbandTextTracks[trackId]) { 52134 tech.trigger({ 52135 type: 'usage', 52136 name: 'hls-608' 52137 }); 52138 var track = tech.textTracks().getTrackById(trackId); 52139 52140 if (track) { 52141 // Resuse an existing track with a CC# id because this was 52142 // very likely created by videojs-contrib-hls from information 52143 // in the m3u8 for us to use 52144 inbandTextTracks[trackId] = track; 52145 } else { 52146 // Otherwise, create a track with the default `CC#` label and 52147 // without a language 52148 inbandTextTracks[trackId] = tech.addRemoteTextTrack({ 52149 kind: 'captions', 52150 id: trackId, 52151 label: trackId 52152 }, false).track; 52153 } 52154 } 52155 } 52156 }; 52157 52158 var addCaptionData = function addCaptionData(_ref) { 52159 var inbandTextTracks = _ref.inbandTextTracks, 52160 captionArray = _ref.captionArray, 52161 timestampOffset = _ref.timestampOffset; 52162 52163 if (!captionArray) { 52164 return; 52165 } 52166 52167 var Cue = window.WebKitDataCue || window.VTTCue; 52168 captionArray.forEach(function (caption) { 52169 var track = caption.stream; 52170 var startTime = caption.startTime; 52171 var endTime = caption.endTime; 52172 52173 if (!inbandTextTracks[track]) { 52174 return; 52175 } 52176 52177 startTime += timestampOffset; 52178 endTime += timestampOffset; 52179 inbandTextTracks[track].addCue(new Cue(startTime, endTime, caption.text)); 52180 }); 52181 }; 52182 /** 52183 * @file segment-loader.js 52184 */ 52185 // in ms 52186 52187 52188 var CHECK_BUFFER_DELAY = 500; 52189 /** 52190 * Determines if we should call endOfStream on the media source based 52191 * on the state of the buffer or if appened segment was the final 52192 * segment in the playlist. 52193 * 52194 * @param {Object} playlist a media playlist object 52195 * @param {Object} mediaSource the MediaSource object 52196 * @param {Number} segmentIndex the index of segment we last appended 52197 * @returns {Boolean} do we need to call endOfStream on the MediaSource 52198 */ 52199 52200 var detectEndOfStream = function detectEndOfStream(playlist, mediaSource, segmentIndex) { 52201 if (!playlist || !mediaSource) { 52202 return false; 52203 } 52204 52205 var segments = playlist.segments; // determine a few boolean values to help make the branch below easier 52206 // to read 52207 52208 var appendedLastSegment = segmentIndex === segments.length; // if we've buffered to the end of the video, we need to call endOfStream 52209 // so that MediaSources can trigger the `ended` event when it runs out of 52210 // buffered data instead of waiting for me 52211 52212 return playlist.endList && mediaSource.readyState === 'open' && appendedLastSegment; 52213 }; 52214 52215 var finite = function finite(num) { 52216 return typeof num === 'number' && isFinite(num); 52217 }; 52218 52219 var illegalMediaSwitch = function illegalMediaSwitch(loaderType, startingMedia, newSegmentMedia) { 52220 // Although these checks should most likely cover non 'main' types, for now it narrows 52221 // the scope of our checks. 52222 if (loaderType !== 'main' || !startingMedia || !newSegmentMedia) { 52223 return null; 52224 } 52225 52226 if (!newSegmentMedia.containsAudio && !newSegmentMedia.containsVideo) { 52227 return 'Neither audio nor video found in segment.'; 52228 } 52229 52230 if (startingMedia.containsVideo && !newSegmentMedia.containsVideo) { 52231 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.'; 52232 } 52233 52234 if (!startingMedia.containsVideo && newSegmentMedia.containsVideo) { 52235 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.'; 52236 } 52237 52238 return null; 52239 }; 52240 /** 52241 * Calculates a time value that is safe to remove from the back buffer without interupting 52242 * playback. 52243 * 52244 * @param {TimeRange} seekable 52245 * The current seekable range 52246 * @param {Number} currentTime 52247 * The current time of the player 52248 * @param {Number} targetDuration 52249 * The target duration of the current playlist 52250 * @return {Number} 52251 * Time that is safe to remove from the back buffer without interupting playback 52252 */ 52253 52254 52255 var safeBackBufferTrimTime = function safeBackBufferTrimTime(seekable$$1, currentTime, targetDuration) { 52256 var removeToTime = void 0; 52257 52258 if (seekable$$1.length && seekable$$1.start(0) > 0 && seekable$$1.start(0) < currentTime) { 52259 // If we have a seekable range use that as the limit for what can be removed safely 52260 removeToTime = seekable$$1.start(0); 52261 } else { 52262 // otherwise remove anything older than 30 seconds before the current play head 52263 removeToTime = currentTime - 30; 52264 } // Don't allow removing from the buffer within target duration of current time 52265 // to avoid the possibility of removing the GOP currently being played which could 52266 // cause playback stalls. 52267 52268 52269 return Math.min(removeToTime, currentTime - targetDuration); 52270 }; 52271 52272 var segmentInfoString = function segmentInfoString(segmentInfo) { 52273 var _segmentInfo$segment = segmentInfo.segment, 52274 start = _segmentInfo$segment.start, 52275 end = _segmentInfo$segment.end, 52276 _segmentInfo$playlist = segmentInfo.playlist, 52277 seq = _segmentInfo$playlist.mediaSequence, 52278 id = _segmentInfo$playlist.id, 52279 _segmentInfo$playlist2 = _segmentInfo$playlist.segments, 52280 segments = _segmentInfo$playlist2 === undefined ? [] : _segmentInfo$playlist2, 52281 index = segmentInfo.mediaIndex, 52282 timeline = segmentInfo.timeline; 52283 return ['appending [' + index + '] of [' + seq + ', ' + (seq + segments.length) + '] from playlist [' + id + ']', '[' + start + ' => ' + end + '] in timeline [' + timeline + ']'].join(' '); 52284 }; 52285 /** 52286 * An object that manages segment loading and appending. 52287 * 52288 * @class SegmentLoader 52289 * @param {Object} options required and optional options 52290 * @extends videojs.EventTarget 52291 */ 52292 52293 52294 var SegmentLoader = function (_videojs$EventTarget) { 52295 inherits$1(SegmentLoader, _videojs$EventTarget); 52296 52297 function SegmentLoader(settings) { 52298 classCallCheck$1(this, SegmentLoader); // check pre-conditions 52299 52300 var _this = possibleConstructorReturn$1(this, (SegmentLoader.__proto__ || Object.getPrototypeOf(SegmentLoader)).call(this)); 52301 52302 if (!settings) { 52303 throw new TypeError('Initialization settings are required'); 52304 } 52305 52306 if (typeof settings.currentTime !== 'function') { 52307 throw new TypeError('No currentTime getter specified'); 52308 } 52309 52310 if (!settings.mediaSource) { 52311 throw new TypeError('No MediaSource specified'); 52312 } // public properties 52313 52314 52315 _this.bandwidth = settings.bandwidth; 52316 _this.throughput = { 52317 rate: 0, 52318 count: 0 52319 }; 52320 _this.roundTrip = NaN; 52321 52322 _this.resetStats_(); 52323 52324 _this.mediaIndex = null; // private settings 52325 52326 _this.hasPlayed_ = settings.hasPlayed; 52327 _this.currentTime_ = settings.currentTime; 52328 _this.seekable_ = settings.seekable; 52329 _this.seeking_ = settings.seeking; 52330 _this.duration_ = settings.duration; 52331 _this.mediaSource_ = settings.mediaSource; 52332 _this.hls_ = settings.hls; 52333 _this.loaderType_ = settings.loaderType; 52334 _this.startingMedia_ = void 0; 52335 _this.segmentMetadataTrack_ = settings.segmentMetadataTrack; 52336 _this.goalBufferLength_ = settings.goalBufferLength; 52337 _this.sourceType_ = settings.sourceType; 52338 _this.inbandTextTracks_ = settings.inbandTextTracks; 52339 _this.state_ = 'INIT'; // private instance variables 52340 52341 _this.checkBufferTimeout_ = null; 52342 _this.error_ = void 0; 52343 _this.currentTimeline_ = -1; 52344 _this.pendingSegment_ = null; 52345 _this.mimeType_ = null; 52346 _this.sourceUpdater_ = null; 52347 _this.xhrOptions_ = null; // Fragmented mp4 playback 52348 52349 _this.activeInitSegmentId_ = null; 52350 _this.initSegments_ = {}; // Fmp4 CaptionParser 52351 52352 _this.captionParser_ = new mp4_6(); 52353 _this.decrypter_ = settings.decrypter; // Manages the tracking and generation of sync-points, mappings 52354 // between a time in the display time and a segment index within 52355 // a playlist 52356 52357 _this.syncController_ = settings.syncController; 52358 _this.syncPoint_ = { 52359 segmentIndex: 0, 52360 time: 0 52361 }; 52362 52363 _this.syncController_.on('syncinfoupdate', function () { 52364 return _this.trigger('syncinfoupdate'); 52365 }); 52366 52367 _this.mediaSource_.addEventListener('sourceopen', function () { 52368 return _this.ended_ = false; 52369 }); // ...for determining the fetch location 52370 52371 52372 _this.fetchAtBuffer_ = false; 52373 _this.logger_ = logger('SegmentLoader[' + _this.loaderType_ + ']'); 52374 Object.defineProperty(_this, 'state', { 52375 get: function get$$1() { 52376 return this.state_; 52377 }, 52378 set: function set$$1(newState) { 52379 if (newState !== this.state_) { 52380 this.logger_(this.state_ + ' -> ' + newState); 52381 this.state_ = newState; 52382 } 52383 } 52384 }); 52385 return _this; 52386 } 52387 /** 52388 * reset all of our media stats 52389 * 52390 * @private 52391 */ 52392 52393 52394 createClass$1(SegmentLoader, [{ 52395 key: 'resetStats_', 52396 value: function resetStats_() { 52397 this.mediaBytesTransferred = 0; 52398 this.mediaRequests = 0; 52399 this.mediaRequestsAborted = 0; 52400 this.mediaRequestsTimedout = 0; 52401 this.mediaRequestsErrored = 0; 52402 this.mediaTransferDuration = 0; 52403 this.mediaSecondsLoaded = 0; 52404 } 52405 /** 52406 * dispose of the SegmentLoader and reset to the default state 52407 */ 52408 52409 }, { 52410 key: 'dispose', 52411 value: function dispose() { 52412 this.state = 'DISPOSED'; 52413 this.pause(); 52414 this.abort_(); 52415 52416 if (this.sourceUpdater_) { 52417 this.sourceUpdater_.dispose(); 52418 } 52419 52420 this.resetStats_(); 52421 this.captionParser_.reset(); 52422 } 52423 /** 52424 * abort anything that is currently doing on with the SegmentLoader 52425 * and reset to a default state 52426 */ 52427 52428 }, { 52429 key: 'abort', 52430 value: function abort() { 52431 if (this.state !== 'WAITING') { 52432 if (this.pendingSegment_) { 52433 this.pendingSegment_ = null; 52434 } 52435 52436 return; 52437 } 52438 52439 this.abort_(); // We aborted the requests we were waiting on, so reset the loader's state to READY 52440 // since we are no longer "waiting" on any requests. XHR callback is not always run 52441 // when the request is aborted. This will prevent the loader from being stuck in the 52442 // WAITING state indefinitely. 52443 52444 this.state = 'READY'; // don't wait for buffer check timeouts to begin fetching the 52445 // next segment 52446 52447 if (!this.paused()) { 52448 this.monitorBuffer_(); 52449 } 52450 } 52451 /** 52452 * abort all pending xhr requests and null any pending segements 52453 * 52454 * @private 52455 */ 52456 52457 }, { 52458 key: 'abort_', 52459 value: function abort_() { 52460 if (this.pendingSegment_) { 52461 this.pendingSegment_.abortRequests(); 52462 } // clear out the segment being processed 52463 52464 52465 this.pendingSegment_ = null; 52466 } 52467 /** 52468 * set an error on the segment loader and null out any pending segements 52469 * 52470 * @param {Error} error the error to set on the SegmentLoader 52471 * @return {Error} the error that was set or that is currently set 52472 */ 52473 52474 }, { 52475 key: 'error', 52476 value: function error(_error) { 52477 if (typeof _error !== 'undefined') { 52478 this.error_ = _error; 52479 } 52480 52481 this.pendingSegment_ = null; 52482 return this.error_; 52483 } 52484 }, { 52485 key: 'endOfStream', 52486 value: function endOfStream() { 52487 this.ended_ = true; 52488 this.pause(); 52489 this.trigger('ended'); 52490 } 52491 /** 52492 * Indicates which time ranges are buffered 52493 * 52494 * @return {TimeRange} 52495 * TimeRange object representing the current buffered ranges 52496 */ 52497 52498 }, { 52499 key: 'buffered_', 52500 value: function buffered_() { 52501 if (!this.sourceUpdater_) { 52502 return videojs$1.createTimeRanges(); 52503 } 52504 52505 return this.sourceUpdater_.buffered(); 52506 } 52507 /** 52508 * Gets and sets init segment for the provided map 52509 * 52510 * @param {Object} map 52511 * The map object representing the init segment to get or set 52512 * @param {Boolean=} set 52513 * If true, the init segment for the provided map should be saved 52514 * @return {Object} 52515 * map object for desired init segment 52516 */ 52517 52518 }, { 52519 key: 'initSegment', 52520 value: function initSegment(map) { 52521 var set$$1 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; 52522 52523 if (!map) { 52524 return null; 52525 } 52526 52527 var id = initSegmentId(map); 52528 var storedMap = this.initSegments_[id]; 52529 52530 if (set$$1 && !storedMap && map.bytes) { 52531 this.initSegments_[id] = storedMap = { 52532 resolvedUri: map.resolvedUri, 52533 byterange: map.byterange, 52534 bytes: map.bytes, 52535 timescales: map.timescales, 52536 videoTrackIds: map.videoTrackIds 52537 }; 52538 } 52539 52540 return storedMap || map; 52541 } 52542 /** 52543 * Returns true if all configuration required for loading is present, otherwise false. 52544 * 52545 * @return {Boolean} True if the all configuration is ready for loading 52546 * @private 52547 */ 52548 52549 }, { 52550 key: 'couldBeginLoading_', 52551 value: function couldBeginLoading_() { 52552 return this.playlist_ && ( // the source updater is created when init_ is called, so either having a 52553 // source updater or being in the INIT state with a mimeType is enough 52554 // to say we have all the needed configuration to start loading. 52555 this.sourceUpdater_ || this.mimeType_ && this.state === 'INIT') && !this.paused(); 52556 } 52557 /** 52558 * load a playlist and start to fill the buffer 52559 */ 52560 52561 }, { 52562 key: 'load', 52563 value: function load() { 52564 // un-pause 52565 this.monitorBuffer_(); // if we don't have a playlist yet, keep waiting for one to be 52566 // specified 52567 52568 if (!this.playlist_) { 52569 return; 52570 } // not sure if this is the best place for this 52571 52572 52573 this.syncController_.setDateTimeMapping(this.playlist_); // if all the configuration is ready, initialize and begin loading 52574 52575 if (this.state === 'INIT' && this.couldBeginLoading_()) { 52576 return this.init_(); 52577 } // if we're in the middle of processing a segment already, don't 52578 // kick off an additional segment request 52579 52580 52581 if (!this.couldBeginLoading_() || this.state !== 'READY' && this.state !== 'INIT') { 52582 return; 52583 } 52584 52585 this.state = 'READY'; 52586 } 52587 /** 52588 * Once all the starting parameters have been specified, begin 52589 * operation. This method should only be invoked from the INIT 52590 * state. 52591 * 52592 * @private 52593 */ 52594 52595 }, { 52596 key: 'init_', 52597 value: function init_() { 52598 this.state = 'READY'; 52599 this.sourceUpdater_ = new SourceUpdater(this.mediaSource_, this.mimeType_, this.loaderType_, this.sourceBufferEmitter_); 52600 this.resetEverything(); 52601 return this.monitorBuffer_(); 52602 } 52603 /** 52604 * set a playlist on the segment loader 52605 * 52606 * @param {PlaylistLoader} media the playlist to set on the segment loader 52607 */ 52608 52609 }, { 52610 key: 'playlist', 52611 value: function playlist(newPlaylist) { 52612 var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 52613 52614 if (!newPlaylist) { 52615 return; 52616 } 52617 52618 var oldPlaylist = this.playlist_; 52619 var segmentInfo = this.pendingSegment_; 52620 this.playlist_ = newPlaylist; 52621 this.xhrOptions_ = options; // when we haven't started playing yet, the start of a live playlist 52622 // is always our zero-time so force a sync update each time the playlist 52623 // is refreshed from the server 52624 52625 if (!this.hasPlayed_()) { 52626 newPlaylist.syncInfo = { 52627 mediaSequence: newPlaylist.mediaSequence, 52628 time: 0 52629 }; 52630 } 52631 52632 var oldId = null; 52633 52634 if (oldPlaylist) { 52635 if (oldPlaylist.id) { 52636 oldId = oldPlaylist.id; 52637 } else if (oldPlaylist.uri) { 52638 oldId = oldPlaylist.uri; 52639 } 52640 } 52641 52642 this.logger_('playlist update [' + oldId + ' => ' + (newPlaylist.id || newPlaylist.uri) + ']'); // in VOD, this is always a rendition switch (or we updated our syncInfo above) 52643 // in LIVE, we always want to update with new playlists (including refreshes) 52644 52645 this.trigger('syncinfoupdate'); // if we were unpaused but waiting for a playlist, start 52646 // buffering now 52647 52648 if (this.state === 'INIT' && this.couldBeginLoading_()) { 52649 return this.init_(); 52650 } 52651 52652 if (!oldPlaylist || oldPlaylist.uri !== newPlaylist.uri) { 52653 if (this.mediaIndex !== null) { 52654 // we must "resync" the segment loader when we switch renditions and 52655 // the segment loader is already synced to the previous rendition 52656 this.resyncLoader(); 52657 } // the rest of this function depends on `oldPlaylist` being defined 52658 52659 52660 return; 52661 } // we reloaded the same playlist so we are in a live scenario 52662 // and we will likely need to adjust the mediaIndex 52663 52664 52665 var mediaSequenceDiff = newPlaylist.mediaSequence - oldPlaylist.mediaSequence; 52666 this.logger_('live window shift [' + mediaSequenceDiff + ']'); // update the mediaIndex on the SegmentLoader 52667 // this is important because we can abort a request and this value must be 52668 // equal to the last appended mediaIndex 52669 52670 if (this.mediaIndex !== null) { 52671 this.mediaIndex -= mediaSequenceDiff; 52672 } // update the mediaIndex on the SegmentInfo object 52673 // this is important because we will update this.mediaIndex with this value 52674 // in `handleUpdateEnd_` after the segment has been successfully appended 52675 52676 52677 if (segmentInfo) { 52678 segmentInfo.mediaIndex -= mediaSequenceDiff; // we need to update the referenced segment so that timing information is 52679 // saved for the new playlist's segment, however, if the segment fell off the 52680 // playlist, we can leave the old reference and just lose the timing info 52681 52682 if (segmentInfo.mediaIndex >= 0) { 52683 segmentInfo.segment = newPlaylist.segments[segmentInfo.mediaIndex]; 52684 } 52685 } 52686 52687 this.syncController_.saveExpiredSegmentInfo(oldPlaylist, newPlaylist); 52688 } 52689 /** 52690 * Prevent the loader from fetching additional segments. If there 52691 * is a segment request outstanding, it will finish processing 52692 * before the loader halts. A segment loader can be unpaused by 52693 * calling load(). 52694 */ 52695 52696 }, { 52697 key: 'pause', 52698 value: function pause() { 52699 if (this.checkBufferTimeout_) { 52700 window$1.clearTimeout(this.checkBufferTimeout_); 52701 this.checkBufferTimeout_ = null; 52702 } 52703 } 52704 /** 52705 * Returns whether the segment loader is fetching additional 52706 * segments when given the opportunity. This property can be 52707 * modified through calls to pause() and load(). 52708 */ 52709 52710 }, { 52711 key: 'paused', 52712 value: function paused() { 52713 return this.checkBufferTimeout_ === null; 52714 } 52715 /** 52716 * create/set the following mimetype on the SourceBuffer through a 52717 * SourceUpdater 52718 * 52719 * @param {String} mimeType the mime type string to use 52720 * @param {Object} sourceBufferEmitter an event emitter that fires when a source buffer 52721 * is added to the media source 52722 */ 52723 52724 }, { 52725 key: 'mimeType', 52726 value: function mimeType(_mimeType, sourceBufferEmitter) { 52727 if (this.mimeType_) { 52728 return; 52729 } 52730 52731 this.mimeType_ = _mimeType; 52732 this.sourceBufferEmitter_ = sourceBufferEmitter; // if we were unpaused but waiting for a sourceUpdater, start 52733 // buffering now 52734 52735 if (this.state === 'INIT' && this.couldBeginLoading_()) { 52736 this.init_(); 52737 } 52738 } 52739 /** 52740 * Delete all the buffered data and reset the SegmentLoader 52741 * @param {Function} [done] an optional callback to be executed when the remove 52742 * operation is complete 52743 */ 52744 52745 }, { 52746 key: 'resetEverything', 52747 value: function resetEverything(done) { 52748 this.ended_ = false; 52749 this.resetLoader(); 52750 this.remove(0, this.duration_(), done); // clears fmp4 captions 52751 52752 this.captionParser_.clearAllCaptions(); 52753 this.trigger('reseteverything'); 52754 } 52755 /** 52756 * Force the SegmentLoader to resync and start loading around the currentTime instead 52757 * of starting at the end of the buffer 52758 * 52759 * Useful for fast quality changes 52760 */ 52761 52762 }, { 52763 key: 'resetLoader', 52764 value: function resetLoader() { 52765 this.fetchAtBuffer_ = false; 52766 this.resyncLoader(); 52767 } 52768 /** 52769 * Force the SegmentLoader to restart synchronization and make a conservative guess 52770 * before returning to the simple walk-forward method 52771 */ 52772 52773 }, { 52774 key: 'resyncLoader', 52775 value: function resyncLoader() { 52776 this.mediaIndex = null; 52777 this.syncPoint_ = null; 52778 this.abort(); 52779 } 52780 /** 52781 * Remove any data in the source buffer between start and end times 52782 * @param {Number} start - the start time of the region to remove from the buffer 52783 * @param {Number} end - the end time of the region to remove from the buffer 52784 * @param {Function} [done] - an optional callback to be executed when the remove 52785 * operation is complete 52786 */ 52787 52788 }, { 52789 key: 'remove', 52790 value: function remove(start, end, done) { 52791 if (this.sourceUpdater_) { 52792 this.sourceUpdater_.remove(start, end, done); 52793 } 52794 52795 removeCuesFromTrack(start, end, this.segmentMetadataTrack_); 52796 52797 if (this.inbandTextTracks_) { 52798 for (var id in this.inbandTextTracks_) { 52799 removeCuesFromTrack(start, end, this.inbandTextTracks_[id]); 52800 } 52801 } 52802 } 52803 /** 52804 * (re-)schedule monitorBufferTick_ to run as soon as possible 52805 * 52806 * @private 52807 */ 52808 52809 }, { 52810 key: 'monitorBuffer_', 52811 value: function monitorBuffer_() { 52812 if (this.checkBufferTimeout_) { 52813 window$1.clearTimeout(this.checkBufferTimeout_); 52814 } 52815 52816 this.checkBufferTimeout_ = window$1.setTimeout(this.monitorBufferTick_.bind(this), 1); 52817 } 52818 /** 52819 * As long as the SegmentLoader is in the READY state, periodically 52820 * invoke fillBuffer_(). 52821 * 52822 * @private 52823 */ 52824 52825 }, { 52826 key: 'monitorBufferTick_', 52827 value: function monitorBufferTick_() { 52828 if (this.state === 'READY') { 52829 this.fillBuffer_(); 52830 } 52831 52832 if (this.checkBufferTimeout_) { 52833 window$1.clearTimeout(this.checkBufferTimeout_); 52834 } 52835 52836 this.checkBufferTimeout_ = window$1.setTimeout(this.monitorBufferTick_.bind(this), CHECK_BUFFER_DELAY); 52837 } 52838 /** 52839 * fill the buffer with segements unless the sourceBuffers are 52840 * currently updating 52841 * 52842 * Note: this function should only ever be called by monitorBuffer_ 52843 * and never directly 52844 * 52845 * @private 52846 */ 52847 52848 }, { 52849 key: 'fillBuffer_', 52850 value: function fillBuffer_() { 52851 if (this.sourceUpdater_.updating()) { 52852 return; 52853 } 52854 52855 if (!this.syncPoint_) { 52856 this.syncPoint_ = this.syncController_.getSyncPoint(this.playlist_, this.duration_(), this.currentTimeline_, this.currentTime_()); 52857 } // see if we need to begin loading immediately 52858 52859 52860 var segmentInfo = this.checkBuffer_(this.buffered_(), this.playlist_, this.mediaIndex, this.hasPlayed_(), this.currentTime_(), this.syncPoint_); 52861 52862 if (!segmentInfo) { 52863 return; 52864 } 52865 52866 if (this.isEndOfStream_(segmentInfo.mediaIndex)) { 52867 this.endOfStream(); 52868 return; 52869 } 52870 52871 if (segmentInfo.mediaIndex === this.playlist_.segments.length - 1 && this.mediaSource_.readyState === 'ended' && !this.seeking_()) { 52872 return; 52873 } // We will need to change timestampOffset of the sourceBuffer if either of 52874 // the following conditions are true: 52875 // - The segment.timeline !== this.currentTimeline 52876 // (we are crossing a discontinuity somehow) 52877 // - The "timestampOffset" for the start of this segment is less than 52878 // the currently set timestampOffset 52879 // Also, clear captions if we are crossing a discontinuity boundary 52880 52881 52882 if (segmentInfo.timeline !== this.currentTimeline_ || segmentInfo.startOfSegment !== null && segmentInfo.startOfSegment < this.sourceUpdater_.timestampOffset()) { 52883 this.syncController_.reset(); 52884 segmentInfo.timestampOffset = segmentInfo.startOfSegment; 52885 this.captionParser_.clearAllCaptions(); 52886 } 52887 52888 this.loadSegment_(segmentInfo); 52889 } 52890 /** 52891 * Determines if this segment loader is at the end of it's stream. 52892 * 52893 * @param {Number} mediaIndex the index of segment we last appended 52894 * @param {Object} [playlist=this.playlist_] a media playlist object 52895 * @returns {Boolean} true if at end of stream, false otherwise. 52896 */ 52897 52898 }, { 52899 key: 'isEndOfStream_', 52900 value: function isEndOfStream_(mediaIndex) { 52901 var playlist = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.playlist_; 52902 return detectEndOfStream(playlist, this.mediaSource_, mediaIndex) && !this.sourceUpdater_.updating(); 52903 } 52904 /** 52905 * Determines what segment request should be made, given current playback 52906 * state. 52907 * 52908 * @param {TimeRanges} buffered - the state of the buffer 52909 * @param {Object} playlist - the playlist object to fetch segments from 52910 * @param {Number} mediaIndex - the previous mediaIndex fetched or null 52911 * @param {Boolean} hasPlayed - a flag indicating whether we have played or not 52912 * @param {Number} currentTime - the playback position in seconds 52913 * @param {Object} syncPoint - a segment info object that describes the 52914 * @returns {Object} a segment request object that describes the segment to load 52915 */ 52916 52917 }, { 52918 key: 'checkBuffer_', 52919 value: function checkBuffer_(buffered, playlist, mediaIndex, hasPlayed, currentTime, syncPoint) { 52920 var lastBufferedEnd = 0; 52921 var startOfSegment = void 0; 52922 52923 if (buffered.length) { 52924 lastBufferedEnd = buffered.end(buffered.length - 1); 52925 } 52926 52927 var bufferedTime = Math.max(0, lastBufferedEnd - currentTime); 52928 52929 if (!playlist.segments.length) { 52930 return null; 52931 } // if there is plenty of content buffered, and the video has 52932 // been played before relax for awhile 52933 52934 52935 if (bufferedTime >= this.goalBufferLength_()) { 52936 return null; 52937 } // if the video has not yet played once, and we already have 52938 // one segment downloaded do nothing 52939 52940 52941 if (!hasPlayed && bufferedTime >= 1) { 52942 return null; 52943 } // When the syncPoint is null, there is no way of determining a good 52944 // conservative segment index to fetch from 52945 // The best thing to do here is to get the kind of sync-point data by 52946 // making a request 52947 52948 52949 if (syncPoint === null) { 52950 mediaIndex = this.getSyncSegmentCandidate_(playlist); 52951 return this.generateSegmentInfo_(playlist, mediaIndex, null, true); 52952 } // Under normal playback conditions fetching is a simple walk forward 52953 52954 52955 if (mediaIndex !== null) { 52956 var segment = playlist.segments[mediaIndex]; 52957 52958 if (segment && segment.end) { 52959 startOfSegment = segment.end; 52960 } else { 52961 startOfSegment = lastBufferedEnd; 52962 } 52963 52964 return this.generateSegmentInfo_(playlist, mediaIndex + 1, startOfSegment, false); 52965 } // There is a sync-point but the lack of a mediaIndex indicates that 52966 // we need to make a good conservative guess about which segment to 52967 // fetch 52968 52969 52970 if (this.fetchAtBuffer_) { 52971 // Find the segment containing the end of the buffer 52972 var mediaSourceInfo = Playlist.getMediaInfoForTime(playlist, lastBufferedEnd, syncPoint.segmentIndex, syncPoint.time); 52973 mediaIndex = mediaSourceInfo.mediaIndex; 52974 startOfSegment = mediaSourceInfo.startTime; 52975 } else { 52976 // Find the segment containing currentTime 52977 var _mediaSourceInfo = Playlist.getMediaInfoForTime(playlist, currentTime, syncPoint.segmentIndex, syncPoint.time); 52978 52979 mediaIndex = _mediaSourceInfo.mediaIndex; 52980 startOfSegment = _mediaSourceInfo.startTime; 52981 } 52982 52983 return this.generateSegmentInfo_(playlist, mediaIndex, startOfSegment, false); 52984 } 52985 /** 52986 * The segment loader has no recourse except to fetch a segment in the 52987 * current playlist and use the internal timestamps in that segment to 52988 * generate a syncPoint. This function returns a good candidate index 52989 * for that process. 52990 * 52991 * @param {Object} playlist - the playlist object to look for a 52992 * @returns {Number} An index of a segment from the playlist to load 52993 */ 52994 52995 }, { 52996 key: 'getSyncSegmentCandidate_', 52997 value: function getSyncSegmentCandidate_(playlist) { 52998 var _this2 = this; 52999 53000 if (this.currentTimeline_ === -1) { 53001 return 0; 53002 } 53003 53004 var segmentIndexArray = playlist.segments.map(function (s, i) { 53005 return { 53006 timeline: s.timeline, 53007 segmentIndex: i 53008 }; 53009 }).filter(function (s) { 53010 return s.timeline === _this2.currentTimeline_; 53011 }); 53012 53013 if (segmentIndexArray.length) { 53014 return segmentIndexArray[Math.min(segmentIndexArray.length - 1, 1)].segmentIndex; 53015 } 53016 53017 return Math.max(playlist.segments.length - 1, 0); 53018 } 53019 }, { 53020 key: 'generateSegmentInfo_', 53021 value: function generateSegmentInfo_(playlist, mediaIndex, startOfSegment, isSyncRequest) { 53022 if (mediaIndex < 0 || mediaIndex >= playlist.segments.length) { 53023 return null; 53024 } 53025 53026 var segment = playlist.segments[mediaIndex]; 53027 return { 53028 requestId: 'segment-loader-' + Math.random(), 53029 // resolve the segment URL relative to the playlist 53030 uri: segment.resolvedUri, 53031 // the segment's mediaIndex at the time it was requested 53032 mediaIndex: mediaIndex, 53033 // whether or not to update the SegmentLoader's state with this 53034 // segment's mediaIndex 53035 isSyncRequest: isSyncRequest, 53036 startOfSegment: startOfSegment, 53037 // the segment's playlist 53038 playlist: playlist, 53039 // unencrypted bytes of the segment 53040 bytes: null, 53041 // when a key is defined for this segment, the encrypted bytes 53042 encryptedBytes: null, 53043 // The target timestampOffset for this segment when we append it 53044 // to the source buffer 53045 timestampOffset: null, 53046 // The timeline that the segment is in 53047 timeline: segment.timeline, 53048 // The expected duration of the segment in seconds 53049 duration: segment.duration, 53050 // retain the segment in case the playlist updates while doing an async process 53051 segment: segment 53052 }; 53053 } 53054 /** 53055 * Determines if the network has enough bandwidth to complete the current segment 53056 * request in a timely manner. If not, the request will be aborted early and bandwidth 53057 * updated to trigger a playlist switch. 53058 * 53059 * @param {Object} stats 53060 * Object containing stats about the request timing and size 53061 * @return {Boolean} True if the request was aborted, false otherwise 53062 * @private 53063 */ 53064 53065 }, { 53066 key: 'abortRequestEarly_', 53067 value: function abortRequestEarly_(stats) { 53068 if (this.hls_.tech_.paused() || // Don't abort if the current playlist is on the lowestEnabledRendition 53069 // TODO: Replace using timeout with a boolean indicating whether this playlist is 53070 // the lowestEnabledRendition. 53071 !this.xhrOptions_.timeout || // Don't abort if we have no bandwidth information to estimate segment sizes 53072 !this.playlist_.attributes.BANDWIDTH) { 53073 return false; 53074 } // Wait at least 1 second since the first byte of data has been received before 53075 // using the calculated bandwidth from the progress event to allow the bitrate 53076 // to stabilize 53077 53078 53079 if (Date.now() - (stats.firstBytesReceivedAt || Date.now()) < 1000) { 53080 return false; 53081 } 53082 53083 var currentTime = this.currentTime_(); 53084 var measuredBandwidth = stats.bandwidth; 53085 var segmentDuration = this.pendingSegment_.duration; 53086 var requestTimeRemaining = Playlist.estimateSegmentRequestTime(segmentDuration, measuredBandwidth, this.playlist_, stats.bytesReceived); // Subtract 1 from the timeUntilRebuffer so we still consider an early abort 53087 // if we are only left with less than 1 second when the request completes. 53088 // A negative timeUntilRebuffering indicates we are already rebuffering 53089 53090 var timeUntilRebuffer$$1 = timeUntilRebuffer(this.buffered_(), currentTime, this.hls_.tech_.playbackRate()) - 1; // Only consider aborting early if the estimated time to finish the download 53091 // is larger than the estimated time until the player runs out of forward buffer 53092 53093 if (requestTimeRemaining <= timeUntilRebuffer$$1) { 53094 return false; 53095 } 53096 53097 var switchCandidate = minRebufferMaxBandwidthSelector({ 53098 master: this.hls_.playlists.master, 53099 currentTime: currentTime, 53100 bandwidth: measuredBandwidth, 53101 duration: this.duration_(), 53102 segmentDuration: segmentDuration, 53103 timeUntilRebuffer: timeUntilRebuffer$$1, 53104 currentTimeline: this.currentTimeline_, 53105 syncController: this.syncController_ 53106 }); 53107 53108 if (!switchCandidate) { 53109 return; 53110 } 53111 53112 var rebufferingImpact = requestTimeRemaining - timeUntilRebuffer$$1; 53113 var timeSavedBySwitching = rebufferingImpact - switchCandidate.rebufferingImpact; 53114 var minimumTimeSaving = 0.5; // If we are already rebuffering, increase the amount of variance we add to the 53115 // potential round trip time of the new request so that we are not too aggressive 53116 // with switching to a playlist that might save us a fraction of a second. 53117 53118 if (timeUntilRebuffer$$1 <= TIME_FUDGE_FACTOR) { 53119 minimumTimeSaving = 1; 53120 } 53121 53122 if (!switchCandidate.playlist || switchCandidate.playlist.uri === this.playlist_.uri || timeSavedBySwitching < minimumTimeSaving) { 53123 return false; 53124 } // set the bandwidth to that of the desired playlist being sure to scale by 53125 // BANDWIDTH_VARIANCE and add one so the playlist selector does not exclude it 53126 // don't trigger a bandwidthupdate as the bandwidth is artifial 53127 53128 53129 this.bandwidth = switchCandidate.playlist.attributes.BANDWIDTH * Config.BANDWIDTH_VARIANCE + 1; 53130 this.abort(); 53131 this.trigger('earlyabort'); 53132 return true; 53133 } 53134 /** 53135 * XHR `progress` event handler 53136 * 53137 * @param {Event} 53138 * The XHR `progress` event 53139 * @param {Object} simpleSegment 53140 * A simplified segment object copy 53141 * @private 53142 */ 53143 53144 }, { 53145 key: 'handleProgress_', 53146 value: function handleProgress_(event, simpleSegment) { 53147 if (!this.pendingSegment_ || simpleSegment.requestId !== this.pendingSegment_.requestId || this.abortRequestEarly_(simpleSegment.stats)) { 53148 return; 53149 } 53150 53151 this.trigger('progress'); 53152 } 53153 /** 53154 * load a specific segment from a request into the buffer 53155 * 53156 * @private 53157 */ 53158 53159 }, { 53160 key: 'loadSegment_', 53161 value: function loadSegment_(segmentInfo) { 53162 this.state = 'WAITING'; 53163 this.pendingSegment_ = segmentInfo; 53164 this.trimBackBuffer_(segmentInfo); 53165 segmentInfo.abortRequests = mediaSegmentRequest(this.hls_.xhr, this.xhrOptions_, this.decrypter_, this.captionParser_, this.createSimplifiedSegmentObj_(segmentInfo), // progress callback 53166 this.handleProgress_.bind(this), this.segmentRequestFinished_.bind(this)); 53167 } 53168 /** 53169 * trim the back buffer so that we don't have too much data 53170 * in the source buffer 53171 * 53172 * @private 53173 * 53174 * @param {Object} segmentInfo - the current segment 53175 */ 53176 53177 }, { 53178 key: 'trimBackBuffer_', 53179 value: function trimBackBuffer_(segmentInfo) { 53180 var removeToTime = safeBackBufferTrimTime(this.seekable_(), this.currentTime_(), this.playlist_.targetDuration || 10); // Chrome has a hard limit of 150MB of 53181 // buffer and a very conservative "garbage collector" 53182 // We manually clear out the old buffer to ensure 53183 // we don't trigger the QuotaExceeded error 53184 // on the source buffer during subsequent appends 53185 53186 if (removeToTime > 0) { 53187 this.remove(0, removeToTime); 53188 } 53189 } 53190 /** 53191 * created a simplified copy of the segment object with just the 53192 * information necessary to perform the XHR and decryption 53193 * 53194 * @private 53195 * 53196 * @param {Object} segmentInfo - the current segment 53197 * @returns {Object} a simplified segment object copy 53198 */ 53199 53200 }, { 53201 key: 'createSimplifiedSegmentObj_', 53202 value: function createSimplifiedSegmentObj_(segmentInfo) { 53203 var segment = segmentInfo.segment; 53204 var simpleSegment = { 53205 resolvedUri: segment.resolvedUri, 53206 byterange: segment.byterange, 53207 requestId: segmentInfo.requestId 53208 }; 53209 53210 if (segment.key) { 53211 // if the media sequence is greater than 2^32, the IV will be incorrect 53212 // assuming 10s segments, that would be about 1300 years 53213 var iv = segment.key.iv || new Uint32Array([0, 0, 0, segmentInfo.mediaIndex + segmentInfo.playlist.mediaSequence]); 53214 simpleSegment.key = { 53215 resolvedUri: segment.key.resolvedUri, 53216 iv: iv 53217 }; 53218 } 53219 53220 if (segment.map) { 53221 simpleSegment.map = this.initSegment(segment.map); 53222 } 53223 53224 return simpleSegment; 53225 } 53226 /** 53227 * Handle the callback from the segmentRequest function and set the 53228 * associated SegmentLoader state and errors if necessary 53229 * 53230 * @private 53231 */ 53232 53233 }, { 53234 key: 'segmentRequestFinished_', 53235 value: function segmentRequestFinished_(error, simpleSegment) { 53236 // every request counts as a media request even if it has been aborted 53237 // or canceled due to a timeout 53238 this.mediaRequests += 1; 53239 53240 if (simpleSegment.stats) { 53241 this.mediaBytesTransferred += simpleSegment.stats.bytesReceived; 53242 this.mediaTransferDuration += simpleSegment.stats.roundTripTime; 53243 } // The request was aborted and the SegmentLoader has already been reset 53244 53245 53246 if (!this.pendingSegment_) { 53247 this.mediaRequestsAborted += 1; 53248 return; 53249 } // the request was aborted and the SegmentLoader has already started 53250 // another request. this can happen when the timeout for an aborted 53251 // request triggers due to a limitation in the XHR library 53252 // do not count this as any sort of request or we risk double-counting 53253 53254 53255 if (simpleSegment.requestId !== this.pendingSegment_.requestId) { 53256 return; 53257 } // an error occurred from the active pendingSegment_ so reset everything 53258 53259 53260 if (error) { 53261 this.pendingSegment_ = null; 53262 this.state = 'READY'; // the requests were aborted just record the aborted stat and exit 53263 // this is not a true error condition and nothing corrective needs 53264 // to be done 53265 53266 if (error.code === REQUEST_ERRORS.ABORTED) { 53267 this.mediaRequestsAborted += 1; 53268 return; 53269 } 53270 53271 this.pause(); // the error is really just that at least one of the requests timed-out 53272 // set the bandwidth to a very low value and trigger an ABR switch to 53273 // take emergency action 53274 53275 if (error.code === REQUEST_ERRORS.TIMEOUT) { 53276 this.mediaRequestsTimedout += 1; 53277 this.bandwidth = 1; 53278 this.roundTrip = NaN; 53279 this.trigger('bandwidthupdate'); 53280 return; 53281 } // if control-flow has arrived here, then the error is real 53282 // emit an error event to blacklist the current playlist 53283 53284 53285 this.mediaRequestsErrored += 1; 53286 this.error(error); 53287 this.trigger('error'); 53288 return; 53289 } // the response was a success so set any bandwidth stats the request 53290 // generated for ABR purposes 53291 53292 53293 this.bandwidth = simpleSegment.stats.bandwidth; 53294 this.roundTrip = simpleSegment.stats.roundTripTime; // if this request included an initialization segment, save that data 53295 // to the initSegment cache 53296 53297 if (simpleSegment.map) { 53298 simpleSegment.map = this.initSegment(simpleSegment.map, true); 53299 } 53300 53301 this.processSegmentResponse_(simpleSegment); 53302 } 53303 /** 53304 * Move any important data from the simplified segment object 53305 * back to the real segment object for future phases 53306 * 53307 * @private 53308 */ 53309 53310 }, { 53311 key: 'processSegmentResponse_', 53312 value: function processSegmentResponse_(simpleSegment) { 53313 var segmentInfo = this.pendingSegment_; 53314 segmentInfo.bytes = simpleSegment.bytes; 53315 53316 if (simpleSegment.map) { 53317 segmentInfo.segment.map.bytes = simpleSegment.map.bytes; 53318 } 53319 53320 segmentInfo.endOfAllRequests = simpleSegment.endOfAllRequests; // This has fmp4 captions, add them to text tracks 53321 53322 if (simpleSegment.fmp4Captions) { 53323 createCaptionsTrackIfNotExists(this.inbandTextTracks_, this.hls_.tech_, simpleSegment.captionStreams); 53324 addCaptionData({ 53325 inbandTextTracks: this.inbandTextTracks_, 53326 captionArray: simpleSegment.fmp4Captions, 53327 // fmp4s will not have a timestamp offset 53328 timestampOffset: 0 53329 }); // Reset stored captions since we added parsed 53330 // captions to a text track at this point 53331 53332 this.captionParser_.clearParsedCaptions(); 53333 } 53334 53335 this.handleSegment_(); 53336 } 53337 /** 53338 * append a decrypted segement to the SourceBuffer through a SourceUpdater 53339 * 53340 * @private 53341 */ 53342 53343 }, { 53344 key: 'handleSegment_', 53345 value: function handleSegment_() { 53346 var _this3 = this; 53347 53348 if (!this.pendingSegment_) { 53349 this.state = 'READY'; 53350 return; 53351 } 53352 53353 var segmentInfo = this.pendingSegment_; 53354 var segment = segmentInfo.segment; 53355 var timingInfo = this.syncController_.probeSegmentInfo(segmentInfo); // When we have our first timing info, determine what media types this loader is 53356 // dealing with. Although we're maintaining extra state, it helps to preserve the 53357 // separation of segment loader from the actual source buffers. 53358 53359 if (typeof this.startingMedia_ === 'undefined' && timingInfo && ( // Guard against cases where we're not getting timing info at all until we are 53360 // certain that all streams will provide it. 53361 timingInfo.containsAudio || timingInfo.containsVideo)) { 53362 this.startingMedia_ = { 53363 containsAudio: timingInfo.containsAudio, 53364 containsVideo: timingInfo.containsVideo 53365 }; 53366 } 53367 53368 var illegalMediaSwitchError = illegalMediaSwitch(this.loaderType_, this.startingMedia_, timingInfo); 53369 53370 if (illegalMediaSwitchError) { 53371 this.error({ 53372 message: illegalMediaSwitchError, 53373 blacklistDuration: Infinity 53374 }); 53375 this.trigger('error'); 53376 return; 53377 } 53378 53379 if (segmentInfo.isSyncRequest) { 53380 this.trigger('syncinfoupdate'); 53381 this.pendingSegment_ = null; 53382 this.state = 'READY'; 53383 return; 53384 } 53385 53386 if (segmentInfo.timestampOffset !== null && segmentInfo.timestampOffset !== this.sourceUpdater_.timestampOffset()) { 53387 this.sourceUpdater_.timestampOffset(segmentInfo.timestampOffset); // fired when a timestamp offset is set in HLS (can also identify discontinuities) 53388 53389 this.trigger('timestampoffset'); 53390 } 53391 53392 var timelineMapping = this.syncController_.mappingForTimeline(segmentInfo.timeline); 53393 53394 if (timelineMapping !== null) { 53395 this.trigger({ 53396 type: 'segmenttimemapping', 53397 mapping: timelineMapping 53398 }); 53399 } 53400 53401 this.state = 'APPENDING'; // if the media initialization segment is changing, append it 53402 // before the content segment 53403 53404 if (segment.map) { 53405 var initId = initSegmentId(segment.map); 53406 53407 if (!this.activeInitSegmentId_ || this.activeInitSegmentId_ !== initId) { 53408 var initSegment = this.initSegment(segment.map); 53409 this.sourceUpdater_.appendBuffer({ 53410 bytes: initSegment.bytes 53411 }, function () { 53412 _this3.activeInitSegmentId_ = initId; 53413 }); 53414 } 53415 } 53416 53417 segmentInfo.byteLength = segmentInfo.bytes.byteLength; 53418 53419 if (typeof segment.start === 'number' && typeof segment.end === 'number') { 53420 this.mediaSecondsLoaded += segment.end - segment.start; 53421 } else { 53422 this.mediaSecondsLoaded += segment.duration; 53423 } 53424 53425 this.logger_(segmentInfoString(segmentInfo)); 53426 this.sourceUpdater_.appendBuffer({ 53427 bytes: segmentInfo.bytes, 53428 videoSegmentTimingInfoCallback: this.handleVideoSegmentTimingInfo_.bind(this, segmentInfo.requestId) 53429 }, this.handleUpdateEnd_.bind(this)); 53430 } 53431 }, { 53432 key: 'handleVideoSegmentTimingInfo_', 53433 value: function handleVideoSegmentTimingInfo_(requestId, event) { 53434 if (!this.pendingSegment_ || requestId !== this.pendingSegment_.requestId) { 53435 return; 53436 } 53437 53438 var segment = this.pendingSegment_.segment; 53439 53440 if (!segment.videoTimingInfo) { 53441 segment.videoTimingInfo = {}; 53442 } 53443 53444 segment.videoTimingInfo.transmuxerPrependedSeconds = event.videoSegmentTimingInfo.prependedContentDuration || 0; 53445 segment.videoTimingInfo.transmuxedPresentationStart = event.videoSegmentTimingInfo.start.presentation; 53446 segment.videoTimingInfo.transmuxedPresentationEnd = event.videoSegmentTimingInfo.end.presentation; // mainly used as a reference for debugging 53447 53448 segment.videoTimingInfo.baseMediaDecodeTime = event.videoSegmentTimingInfo.baseMediaDecodeTime; 53449 } 53450 /** 53451 * callback to run when appendBuffer is finished. detects if we are 53452 * in a good state to do things with the data we got, or if we need 53453 * to wait for more 53454 * 53455 * @private 53456 */ 53457 53458 }, { 53459 key: 'handleUpdateEnd_', 53460 value: function handleUpdateEnd_() { 53461 if (!this.pendingSegment_) { 53462 this.state = 'READY'; 53463 53464 if (!this.paused()) { 53465 this.monitorBuffer_(); 53466 } 53467 53468 return; 53469 } 53470 53471 var segmentInfo = this.pendingSegment_; 53472 var segment = segmentInfo.segment; 53473 var isWalkingForward = this.mediaIndex !== null; 53474 this.pendingSegment_ = null; 53475 this.recordThroughput_(segmentInfo); 53476 this.addSegmentMetadataCue_(segmentInfo); 53477 this.state = 'READY'; 53478 this.mediaIndex = segmentInfo.mediaIndex; 53479 this.fetchAtBuffer_ = true; 53480 this.currentTimeline_ = segmentInfo.timeline; // We must update the syncinfo to recalculate the seekable range before 53481 // the following conditional otherwise it may consider this a bad "guess" 53482 // and attempt to resync when the post-update seekable window and live 53483 // point would mean that this was the perfect segment to fetch 53484 53485 this.trigger('syncinfoupdate'); // If we previously appended a segment that ends more than 3 targetDurations before 53486 // the currentTime_ that means that our conservative guess was too conservative. 53487 // In that case, reset the loader state so that we try to use any information gained 53488 // from the previous request to create a new, more accurate, sync-point. 53489 53490 if (segment.end && this.currentTime_() - segment.end > segmentInfo.playlist.targetDuration * 3) { 53491 this.resetEverything(); 53492 return; 53493 } // Don't do a rendition switch unless we have enough time to get a sync segment 53494 // and conservatively guess 53495 53496 53497 if (isWalkingForward) { 53498 this.trigger('bandwidthupdate'); 53499 } 53500 53501 this.trigger('progress'); // any time an update finishes and the last segment is in the 53502 // buffer, end the stream. this ensures the "ended" event will 53503 // fire if playback reaches that point. 53504 53505 if (this.isEndOfStream_(segmentInfo.mediaIndex + 1, segmentInfo.playlist)) { 53506 this.endOfStream(); 53507 } 53508 53509 if (!this.paused()) { 53510 this.monitorBuffer_(); 53511 } 53512 } 53513 /** 53514 * Records the current throughput of the decrypt, transmux, and append 53515 * portion of the semgment pipeline. `throughput.rate` is a the cumulative 53516 * moving average of the throughput. `throughput.count` is the number of 53517 * data points in the average. 53518 * 53519 * @private 53520 * @param {Object} segmentInfo the object returned by loadSegment 53521 */ 53522 53523 }, { 53524 key: 'recordThroughput_', 53525 value: function recordThroughput_(segmentInfo) { 53526 var rate = this.throughput.rate; // Add one to the time to ensure that we don't accidentally attempt to divide 53527 // by zero in the case where the throughput is ridiculously high 53528 53529 var segmentProcessingTime = Date.now() - segmentInfo.endOfAllRequests + 1; // Multiply by 8000 to convert from bytes/millisecond to bits/second 53530 53531 var segmentProcessingThroughput = Math.floor(segmentInfo.byteLength / segmentProcessingTime * 8 * 1000); // This is just a cumulative moving average calculation: 53532 // newAvg = oldAvg + (sample - oldAvg) / (sampleCount + 1) 53533 53534 this.throughput.rate += (segmentProcessingThroughput - rate) / ++this.throughput.count; 53535 } 53536 /** 53537 * Adds a cue to the segment-metadata track with some metadata information about the 53538 * segment 53539 * 53540 * @private 53541 * @param {Object} segmentInfo 53542 * the object returned by loadSegment 53543 * @method addSegmentMetadataCue_ 53544 */ 53545 53546 }, { 53547 key: 'addSegmentMetadataCue_', 53548 value: function addSegmentMetadataCue_(segmentInfo) { 53549 if (!this.segmentMetadataTrack_) { 53550 return; 53551 } 53552 53553 var segment = segmentInfo.segment; 53554 var start = segment.start; 53555 var end = segment.end; // Do not try adding the cue if the start and end times are invalid. 53556 53557 if (!finite(start) || !finite(end)) { 53558 return; 53559 } 53560 53561 removeCuesFromTrack(start, end, this.segmentMetadataTrack_); 53562 var Cue = window$1.WebKitDataCue || window$1.VTTCue; 53563 var value = { 53564 custom: segment.custom, 53565 dateTimeObject: segment.dateTimeObject, 53566 dateTimeString: segment.dateTimeString, 53567 bandwidth: segmentInfo.playlist.attributes.BANDWIDTH, 53568 resolution: segmentInfo.playlist.attributes.RESOLUTION, 53569 codecs: segmentInfo.playlist.attributes.CODECS, 53570 byteLength: segmentInfo.byteLength, 53571 uri: segmentInfo.uri, 53572 timeline: segmentInfo.timeline, 53573 playlist: segmentInfo.playlist.uri, 53574 start: start, 53575 end: end 53576 }; 53577 var data = JSON.stringify(value); 53578 var cue = new Cue(start, end, data); // Attach the metadata to the value property of the cue to keep consistency between 53579 // the differences of WebKitDataCue in safari and VTTCue in other browsers 53580 53581 cue.value = value; 53582 this.segmentMetadataTrack_.addCue(cue); 53583 } 53584 }]); 53585 return SegmentLoader; 53586 }(videojs$1.EventTarget); 53587 53588 var uint8ToUtf8 = function uint8ToUtf8(uintArray) { 53589 return decodeURIComponent(escape(String.fromCharCode.apply(null, uintArray))); 53590 }; 53591 /** 53592 * @file vtt-segment-loader.js 53593 */ 53594 53595 53596 var VTT_LINE_TERMINATORS = new Uint8Array('\n\n'.split('').map(function (char) { 53597 return char.charCodeAt(0); 53598 })); 53599 /** 53600 * An object that manages segment loading and appending. 53601 * 53602 * @class VTTSegmentLoader 53603 * @param {Object} options required and optional options 53604 * @extends videojs.EventTarget 53605 */ 53606 53607 var VTTSegmentLoader = function (_SegmentLoader) { 53608 inherits$1(VTTSegmentLoader, _SegmentLoader); 53609 53610 function VTTSegmentLoader(settings) { 53611 var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 53612 classCallCheck$1(this, VTTSegmentLoader); // SegmentLoader requires a MediaSource be specified or it will throw an error; 53613 // however, VTTSegmentLoader has no need of a media source, so delete the reference 53614 53615 var _this = possibleConstructorReturn$1(this, (VTTSegmentLoader.__proto__ || Object.getPrototypeOf(VTTSegmentLoader)).call(this, settings, options)); 53616 53617 _this.mediaSource_ = null; 53618 _this.subtitlesTrack_ = null; 53619 return _this; 53620 } 53621 /** 53622 * Indicates which time ranges are buffered 53623 * 53624 * @return {TimeRange} 53625 * TimeRange object representing the current buffered ranges 53626 */ 53627 53628 53629 createClass$1(VTTSegmentLoader, [{ 53630 key: 'buffered_', 53631 value: function buffered_() { 53632 if (!this.subtitlesTrack_ || !this.subtitlesTrack_.cues.length) { 53633 return videojs$1.createTimeRanges(); 53634 } 53635 53636 var cues = this.subtitlesTrack_.cues; 53637 var start = cues[0].startTime; 53638 var end = cues[cues.length - 1].startTime; 53639 return videojs$1.createTimeRanges([[start, end]]); 53640 } 53641 /** 53642 * Gets and sets init segment for the provided map 53643 * 53644 * @param {Object} map 53645 * The map object representing the init segment to get or set 53646 * @param {Boolean=} set 53647 * If true, the init segment for the provided map should be saved 53648 * @return {Object} 53649 * map object for desired init segment 53650 */ 53651 53652 }, { 53653 key: 'initSegment', 53654 value: function initSegment(map) { 53655 var set$$1 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; 53656 53657 if (!map) { 53658 return null; 53659 } 53660 53661 var id = initSegmentId(map); 53662 var storedMap = this.initSegments_[id]; 53663 53664 if (set$$1 && !storedMap && map.bytes) { 53665 // append WebVTT line terminators to the media initialization segment if it exists 53666 // to follow the WebVTT spec (https://w3c.github.io/webvtt/#file-structure) that 53667 // requires two or more WebVTT line terminators between the WebVTT header and the 53668 // rest of the file 53669 var combinedByteLength = VTT_LINE_TERMINATORS.byteLength + map.bytes.byteLength; 53670 var combinedSegment = new Uint8Array(combinedByteLength); 53671 combinedSegment.set(map.bytes); 53672 combinedSegment.set(VTT_LINE_TERMINATORS, map.bytes.byteLength); 53673 this.initSegments_[id] = storedMap = { 53674 resolvedUri: map.resolvedUri, 53675 byterange: map.byterange, 53676 bytes: combinedSegment 53677 }; 53678 } 53679 53680 return storedMap || map; 53681 } 53682 /** 53683 * Returns true if all configuration required for loading is present, otherwise false. 53684 * 53685 * @return {Boolean} True if the all configuration is ready for loading 53686 * @private 53687 */ 53688 53689 }, { 53690 key: 'couldBeginLoading_', 53691 value: function couldBeginLoading_() { 53692 return this.playlist_ && this.subtitlesTrack_ && !this.paused(); 53693 } 53694 /** 53695 * Once all the starting parameters have been specified, begin 53696 * operation. This method should only be invoked from the INIT 53697 * state. 53698 * 53699 * @private 53700 */ 53701 53702 }, { 53703 key: 'init_', 53704 value: function init_() { 53705 this.state = 'READY'; 53706 this.resetEverything(); 53707 return this.monitorBuffer_(); 53708 } 53709 /** 53710 * Set a subtitle track on the segment loader to add subtitles to 53711 * 53712 * @param {TextTrack=} track 53713 * The text track to add loaded subtitles to 53714 * @return {TextTrack} 53715 * Returns the subtitles track 53716 */ 53717 53718 }, { 53719 key: 'track', 53720 value: function track(_track) { 53721 if (typeof _track === 'undefined') { 53722 return this.subtitlesTrack_; 53723 } 53724 53725 this.subtitlesTrack_ = _track; // if we were unpaused but waiting for a sourceUpdater, start 53726 // buffering now 53727 53728 if (this.state === 'INIT' && this.couldBeginLoading_()) { 53729 this.init_(); 53730 } 53731 53732 return this.subtitlesTrack_; 53733 } 53734 /** 53735 * Remove any data in the source buffer between start and end times 53736 * @param {Number} start - the start time of the region to remove from the buffer 53737 * @param {Number} end - the end time of the region to remove from the buffer 53738 */ 53739 53740 }, { 53741 key: 'remove', 53742 value: function remove(start, end) { 53743 removeCuesFromTrack(start, end, this.subtitlesTrack_); 53744 } 53745 /** 53746 * fill the buffer with segements unless the sourceBuffers are 53747 * currently updating 53748 * 53749 * Note: this function should only ever be called by monitorBuffer_ 53750 * and never directly 53751 * 53752 * @private 53753 */ 53754 53755 }, { 53756 key: 'fillBuffer_', 53757 value: function fillBuffer_() { 53758 var _this2 = this; 53759 53760 if (!this.syncPoint_) { 53761 this.syncPoint_ = this.syncController_.getSyncPoint(this.playlist_, this.duration_(), this.currentTimeline_, this.currentTime_()); 53762 } // see if we need to begin loading immediately 53763 53764 53765 var segmentInfo = this.checkBuffer_(this.buffered_(), this.playlist_, this.mediaIndex, this.hasPlayed_(), this.currentTime_(), this.syncPoint_); 53766 segmentInfo = this.skipEmptySegments_(segmentInfo); 53767 53768 if (!segmentInfo) { 53769 return; 53770 } 53771 53772 if (this.syncController_.timestampOffsetForTimeline(segmentInfo.timeline) === null) { 53773 // We don't have the timestamp offset that we need to sync subtitles. 53774 // Rerun on a timestamp offset or user interaction. 53775 var checkTimestampOffset = function checkTimestampOffset() { 53776 _this2.state = 'READY'; 53777 53778 if (!_this2.paused()) { 53779 // if not paused, queue a buffer check as soon as possible 53780 _this2.monitorBuffer_(); 53781 } 53782 }; 53783 53784 this.syncController_.one('timestampoffset', checkTimestampOffset); 53785 this.state = 'WAITING_ON_TIMELINE'; 53786 return; 53787 } 53788 53789 this.loadSegment_(segmentInfo); 53790 } 53791 /** 53792 * Prevents the segment loader from requesting segments we know contain no subtitles 53793 * by walking forward until we find the next segment that we don't know whether it is 53794 * empty or not. 53795 * 53796 * @param {Object} segmentInfo 53797 * a segment info object that describes the current segment 53798 * @return {Object} 53799 * a segment info object that describes the current segment 53800 */ 53801 53802 }, { 53803 key: 'skipEmptySegments_', 53804 value: function skipEmptySegments_(segmentInfo) { 53805 while (segmentInfo && segmentInfo.segment.empty) { 53806 segmentInfo = this.generateSegmentInfo_(segmentInfo.playlist, segmentInfo.mediaIndex + 1, segmentInfo.startOfSegment + segmentInfo.duration, segmentInfo.isSyncRequest); 53807 } 53808 53809 return segmentInfo; 53810 } 53811 /** 53812 * append a decrypted segement to the SourceBuffer through a SourceUpdater 53813 * 53814 * @private 53815 */ 53816 53817 }, { 53818 key: 'handleSegment_', 53819 value: function handleSegment_() { 53820 var _this3 = this; 53821 53822 if (!this.pendingSegment_ || !this.subtitlesTrack_) { 53823 this.state = 'READY'; 53824 return; 53825 } 53826 53827 this.state = 'APPENDING'; 53828 var segmentInfo = this.pendingSegment_; 53829 var segment = segmentInfo.segment; // Make sure that vttjs has loaded, otherwise, wait till it finished loading 53830 53831 if (typeof window$1.WebVTT !== 'function' && this.subtitlesTrack_ && this.subtitlesTrack_.tech_) { 53832 var loadHandler = function loadHandler() { 53833 _this3.handleSegment_(); 53834 }; 53835 53836 this.state = 'WAITING_ON_VTTJS'; 53837 this.subtitlesTrack_.tech_.one('vttjsloaded', loadHandler); 53838 this.subtitlesTrack_.tech_.one('vttjserror', function () { 53839 _this3.subtitlesTrack_.tech_.off('vttjsloaded', loadHandler); 53840 53841 _this3.error({ 53842 message: 'Error loading vtt.js' 53843 }); 53844 53845 _this3.state = 'READY'; 53846 53847 _this3.pause(); 53848 53849 _this3.trigger('error'); 53850 }); 53851 return; 53852 } 53853 53854 segment.requested = true; 53855 53856 try { 53857 this.parseVTTCues_(segmentInfo); 53858 } catch (e) { 53859 this.error({ 53860 message: e.message 53861 }); 53862 this.state = 'READY'; 53863 this.pause(); 53864 return this.trigger('error'); 53865 } 53866 53867 this.updateTimeMapping_(segmentInfo, this.syncController_.timelines[segmentInfo.timeline], this.playlist_); 53868 53869 if (segmentInfo.isSyncRequest) { 53870 this.trigger('syncinfoupdate'); 53871 this.pendingSegment_ = null; 53872 this.state = 'READY'; 53873 return; 53874 } 53875 53876 segmentInfo.byteLength = segmentInfo.bytes.byteLength; 53877 this.mediaSecondsLoaded += segment.duration; 53878 53879 if (segmentInfo.cues.length) { 53880 // remove any overlapping cues to prevent doubling 53881 this.remove(segmentInfo.cues[0].endTime, segmentInfo.cues[segmentInfo.cues.length - 1].endTime); 53882 } 53883 53884 segmentInfo.cues.forEach(function (cue) { 53885 _this3.subtitlesTrack_.addCue(cue); 53886 }); 53887 this.handleUpdateEnd_(); 53888 } 53889 /** 53890 * Uses the WebVTT parser to parse the segment response 53891 * 53892 * @param {Object} segmentInfo 53893 * a segment info object that describes the current segment 53894 * @private 53895 */ 53896 53897 }, { 53898 key: 'parseVTTCues_', 53899 value: function parseVTTCues_(segmentInfo) { 53900 var decoder = void 0; 53901 var decodeBytesToString = false; 53902 53903 if (typeof window$1.TextDecoder === 'function') { 53904 decoder = new window$1.TextDecoder('utf8'); 53905 } else { 53906 decoder = window$1.WebVTT.StringDecoder(); 53907 decodeBytesToString = true; 53908 } 53909 53910 var parser = new window$1.WebVTT.Parser(window$1, window$1.vttjs, decoder); 53911 segmentInfo.cues = []; 53912 segmentInfo.timestampmap = { 53913 MPEGTS: 0, 53914 LOCAL: 0 53915 }; 53916 parser.oncue = segmentInfo.cues.push.bind(segmentInfo.cues); 53917 53918 parser.ontimestampmap = function (map) { 53919 return segmentInfo.timestampmap = map; 53920 }; 53921 53922 parser.onparsingerror = function (error) { 53923 videojs$1.log.warn('Error encountered when parsing cues: ' + error.message); 53924 }; 53925 53926 if (segmentInfo.segment.map) { 53927 var mapData = segmentInfo.segment.map.bytes; 53928 53929 if (decodeBytesToString) { 53930 mapData = uint8ToUtf8(mapData); 53931 } 53932 53933 parser.parse(mapData); 53934 } 53935 53936 var segmentData = segmentInfo.bytes; 53937 53938 if (decodeBytesToString) { 53939 segmentData = uint8ToUtf8(segmentData); 53940 } 53941 53942 parser.parse(segmentData); 53943 parser.flush(); 53944 } 53945 /** 53946 * Updates the start and end times of any cues parsed by the WebVTT parser using 53947 * the information parsed from the X-TIMESTAMP-MAP header and a TS to media time mapping 53948 * from the SyncController 53949 * 53950 * @param {Object} segmentInfo 53951 * a segment info object that describes the current segment 53952 * @param {Object} mappingObj 53953 * object containing a mapping from TS to media time 53954 * @param {Object} playlist 53955 * the playlist object containing the segment 53956 * @private 53957 */ 53958 53959 }, { 53960 key: 'updateTimeMapping_', 53961 value: function updateTimeMapping_(segmentInfo, mappingObj, playlist) { 53962 var segment = segmentInfo.segment; 53963 53964 if (!mappingObj) { 53965 // If the sync controller does not have a mapping of TS to Media Time for the 53966 // timeline, then we don't have enough information to update the cue 53967 // start/end times 53968 return; 53969 } 53970 53971 if (!segmentInfo.cues.length) { 53972 // If there are no cues, we also do not have enough information to figure out 53973 // segment timing. Mark that the segment contains no cues so we don't re-request 53974 // an empty segment. 53975 segment.empty = true; 53976 return; 53977 } 53978 53979 var timestampmap = segmentInfo.timestampmap; 53980 var diff = timestampmap.MPEGTS / 90000 - timestampmap.LOCAL + mappingObj.mapping; 53981 segmentInfo.cues.forEach(function (cue) { 53982 // First convert cue time to TS time using the timestamp-map provided within the vtt 53983 cue.startTime += diff; 53984 cue.endTime += diff; 53985 }); 53986 53987 if (!playlist.syncInfo) { 53988 var firstStart = segmentInfo.cues[0].startTime; 53989 var lastStart = segmentInfo.cues[segmentInfo.cues.length - 1].startTime; 53990 playlist.syncInfo = { 53991 mediaSequence: playlist.mediaSequence + segmentInfo.mediaIndex, 53992 time: Math.min(firstStart, lastStart - segment.duration) 53993 }; 53994 } 53995 } 53996 }]); 53997 return VTTSegmentLoader; 53998 }(SegmentLoader); 53999 /** 54000 * @file ad-cue-tags.js 54001 */ 54002 54003 /** 54004 * Searches for an ad cue that overlaps with the given mediaTime 54005 */ 54006 54007 54008 var findAdCue = function findAdCue(track, mediaTime) { 54009 var cues = track.cues; 54010 54011 for (var i = 0; i < cues.length; i++) { 54012 var cue = cues[i]; 54013 54014 if (mediaTime >= cue.adStartTime && mediaTime <= cue.adEndTime) { 54015 return cue; 54016 } 54017 } 54018 54019 return null; 54020 }; 54021 54022 var updateAdCues = function updateAdCues(media, track) { 54023 var offset = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0; 54024 54025 if (!media.segments) { 54026 return; 54027 } 54028 54029 var mediaTime = offset; 54030 var cue = void 0; 54031 54032 for (var i = 0; i < media.segments.length; i++) { 54033 var segment = media.segments[i]; 54034 54035 if (!cue) { 54036 // Since the cues will span for at least the segment duration, adding a fudge 54037 // factor of half segment duration will prevent duplicate cues from being 54038 // created when timing info is not exact (e.g. cue start time initialized 54039 // at 10.006677, but next call mediaTime is 10.003332 ) 54040 cue = findAdCue(track, mediaTime + segment.duration / 2); 54041 } 54042 54043 if (cue) { 54044 if ('cueIn' in segment) { 54045 // Found a CUE-IN so end the cue 54046 cue.endTime = mediaTime; 54047 cue.adEndTime = mediaTime; 54048 mediaTime += segment.duration; 54049 cue = null; 54050 continue; 54051 } 54052 54053 if (mediaTime < cue.endTime) { 54054 // Already processed this mediaTime for this cue 54055 mediaTime += segment.duration; 54056 continue; 54057 } // otherwise extend cue until a CUE-IN is found 54058 54059 54060 cue.endTime += segment.duration; 54061 } else { 54062 if ('cueOut' in segment) { 54063 cue = new window$1.VTTCue(mediaTime, mediaTime + segment.duration, segment.cueOut); 54064 cue.adStartTime = mediaTime; // Assumes tag format to be 54065 // #EXT-X-CUE-OUT:30 54066 54067 cue.adEndTime = mediaTime + parseFloat(segment.cueOut); 54068 track.addCue(cue); 54069 } 54070 54071 if ('cueOutCont' in segment) { 54072 // Entered into the middle of an ad cue 54073 var adOffset = void 0; 54074 var adTotal = void 0; // Assumes tag formate to be 54075 // #EXT-X-CUE-OUT-CONT:10/30 54076 54077 var _segment$cueOutCont$s = segment.cueOutCont.split('/').map(parseFloat); 54078 54079 var _segment$cueOutCont$s2 = slicedToArray(_segment$cueOutCont$s, 2); 54080 54081 adOffset = _segment$cueOutCont$s2[0]; 54082 adTotal = _segment$cueOutCont$s2[1]; 54083 cue = new window$1.VTTCue(mediaTime, mediaTime + segment.duration, ''); 54084 cue.adStartTime = mediaTime - adOffset; 54085 cue.adEndTime = cue.adStartTime + adTotal; 54086 track.addCue(cue); 54087 } 54088 } 54089 54090 mediaTime += segment.duration; 54091 } 54092 }; 54093 /** 54094 * @file sync-controller.js 54095 */ 54096 54097 54098 var tsprobe = tsInspector.inspect; 54099 var syncPointStrategies = [// Stategy "VOD": Handle the VOD-case where the sync-point is *always* 54100 // the equivalence display-time 0 === segment-index 0 54101 { 54102 name: 'VOD', 54103 run: function run(syncController, playlist, duration$$1, currentTimeline, currentTime) { 54104 if (duration$$1 !== Infinity) { 54105 var syncPoint = { 54106 time: 0, 54107 segmentIndex: 0 54108 }; 54109 return syncPoint; 54110 } 54111 54112 return null; 54113 } 54114 }, // Stategy "ProgramDateTime": We have a program-date-time tag in this playlist 54115 { 54116 name: 'ProgramDateTime', 54117 run: function run(syncController, playlist, duration$$1, currentTimeline, currentTime) { 54118 if (!syncController.datetimeToDisplayTime) { 54119 return null; 54120 } 54121 54122 var segments = playlist.segments || []; 54123 var syncPoint = null; 54124 var lastDistance = null; 54125 currentTime = currentTime || 0; 54126 54127 for (var i = 0; i < segments.length; i++) { 54128 var segment = segments[i]; 54129 54130 if (segment.dateTimeObject) { 54131 var segmentTime = segment.dateTimeObject.getTime() / 1000; 54132 var segmentStart = segmentTime + syncController.datetimeToDisplayTime; 54133 var distance = Math.abs(currentTime - segmentStart); // Once the distance begins to increase, we have passed 54134 // currentTime and can stop looking for better candidates 54135 54136 if (lastDistance !== null && lastDistance < distance) { 54137 break; 54138 } 54139 54140 lastDistance = distance; 54141 syncPoint = { 54142 time: segmentStart, 54143 segmentIndex: i 54144 }; 54145 } 54146 } 54147 54148 return syncPoint; 54149 } 54150 }, // Stategy "Segment": We have a known time mapping for a timeline and a 54151 // segment in the current timeline with timing data 54152 { 54153 name: 'Segment', 54154 run: function run(syncController, playlist, duration$$1, currentTimeline, currentTime) { 54155 var segments = playlist.segments || []; 54156 var syncPoint = null; 54157 var lastDistance = null; 54158 currentTime = currentTime || 0; 54159 54160 for (var i = 0; i < segments.length; i++) { 54161 var segment = segments[i]; 54162 54163 if (segment.timeline === currentTimeline && typeof segment.start !== 'undefined') { 54164 var distance = Math.abs(currentTime - segment.start); // Once the distance begins to increase, we have passed 54165 // currentTime and can stop looking for better candidates 54166 54167 if (lastDistance !== null && lastDistance < distance) { 54168 break; 54169 } 54170 54171 if (!syncPoint || lastDistance === null || lastDistance >= distance) { 54172 lastDistance = distance; 54173 syncPoint = { 54174 time: segment.start, 54175 segmentIndex: i 54176 }; 54177 } 54178 } 54179 } 54180 54181 return syncPoint; 54182 } 54183 }, // Stategy "Discontinuity": We have a discontinuity with a known 54184 // display-time 54185 { 54186 name: 'Discontinuity', 54187 run: function run(syncController, playlist, duration$$1, currentTimeline, currentTime) { 54188 var syncPoint = null; 54189 currentTime = currentTime || 0; 54190 54191 if (playlist.discontinuityStarts && playlist.discontinuityStarts.length) { 54192 var lastDistance = null; 54193 54194 for (var i = 0; i < playlist.discontinuityStarts.length; i++) { 54195 var segmentIndex = playlist.discontinuityStarts[i]; 54196 var discontinuity = playlist.discontinuitySequence + i + 1; 54197 var discontinuitySync = syncController.discontinuities[discontinuity]; 54198 54199 if (discontinuitySync) { 54200 var distance = Math.abs(currentTime - discontinuitySync.time); // Once the distance begins to increase, we have passed 54201 // currentTime and can stop looking for better candidates 54202 54203 if (lastDistance !== null && lastDistance < distance) { 54204 break; 54205 } 54206 54207 if (!syncPoint || lastDistance === null || lastDistance >= distance) { 54208 lastDistance = distance; 54209 syncPoint = { 54210 time: discontinuitySync.time, 54211 segmentIndex: segmentIndex 54212 }; 54213 } 54214 } 54215 } 54216 } 54217 54218 return syncPoint; 54219 } 54220 }, // Stategy "Playlist": We have a playlist with a known mapping of 54221 // segment index to display time 54222 { 54223 name: 'Playlist', 54224 run: function run(syncController, playlist, duration$$1, currentTimeline, currentTime) { 54225 if (playlist.syncInfo) { 54226 var syncPoint = { 54227 time: playlist.syncInfo.time, 54228 segmentIndex: playlist.syncInfo.mediaSequence - playlist.mediaSequence 54229 }; 54230 return syncPoint; 54231 } 54232 54233 return null; 54234 } 54235 }]; 54236 54237 var SyncController = function (_videojs$EventTarget) { 54238 inherits$1(SyncController, _videojs$EventTarget); 54239 54240 function SyncController() { 54241 classCallCheck$1(this, SyncController); // Segment Loader state variables... 54242 // ...for synching across variants 54243 54244 var _this = possibleConstructorReturn$1(this, (SyncController.__proto__ || Object.getPrototypeOf(SyncController)).call(this)); 54245 54246 _this.inspectCache_ = undefined; // ...for synching across variants 54247 54248 _this.timelines = []; 54249 _this.discontinuities = []; 54250 _this.datetimeToDisplayTime = null; 54251 _this.logger_ = logger('SyncController'); 54252 return _this; 54253 } 54254 /** 54255 * Find a sync-point for the playlist specified 54256 * 54257 * A sync-point is defined as a known mapping from display-time to 54258 * a segment-index in the current playlist. 54259 * 54260 * @param {Playlist} playlist 54261 * The playlist that needs a sync-point 54262 * @param {Number} duration 54263 * Duration of the MediaSource (Infinite if playing a live source) 54264 * @param {Number} currentTimeline 54265 * The last timeline from which a segment was loaded 54266 * @returns {Object} 54267 * A sync-point object 54268 */ 54269 54270 54271 createClass$1(SyncController, [{ 54272 key: 'getSyncPoint', 54273 value: function getSyncPoint(playlist, duration$$1, currentTimeline, currentTime) { 54274 var syncPoints = this.runStrategies_(playlist, duration$$1, currentTimeline, currentTime); 54275 54276 if (!syncPoints.length) { 54277 // Signal that we need to attempt to get a sync-point manually 54278 // by fetching a segment in the playlist and constructing 54279 // a sync-point from that information 54280 return null; 54281 } // Now find the sync-point that is closest to the currentTime because 54282 // that should result in the most accurate guess about which segment 54283 // to fetch 54284 54285 54286 return this.selectSyncPoint_(syncPoints, { 54287 key: 'time', 54288 value: currentTime 54289 }); 54290 } 54291 /** 54292 * Calculate the amount of time that has expired off the playlist during playback 54293 * 54294 * @param {Playlist} playlist 54295 * Playlist object to calculate expired from 54296 * @param {Number} duration 54297 * Duration of the MediaSource (Infinity if playling a live source) 54298 * @returns {Number|null} 54299 * The amount of time that has expired off the playlist during playback. Null 54300 * if no sync-points for the playlist can be found. 54301 */ 54302 54303 }, { 54304 key: 'getExpiredTime', 54305 value: function getExpiredTime(playlist, duration$$1) { 54306 if (!playlist || !playlist.segments) { 54307 return null; 54308 } 54309 54310 var syncPoints = this.runStrategies_(playlist, duration$$1, playlist.discontinuitySequence, 0); // Without sync-points, there is not enough information to determine the expired time 54311 54312 if (!syncPoints.length) { 54313 return null; 54314 } 54315 54316 var syncPoint = this.selectSyncPoint_(syncPoints, { 54317 key: 'segmentIndex', 54318 value: 0 54319 }); // If the sync-point is beyond the start of the playlist, we want to subtract the 54320 // duration from index 0 to syncPoint.segmentIndex instead of adding. 54321 54322 if (syncPoint.segmentIndex > 0) { 54323 syncPoint.time *= -1; 54324 } 54325 54326 return Math.abs(syncPoint.time + sumDurations(playlist, syncPoint.segmentIndex, 0)); 54327 } 54328 /** 54329 * Runs each sync-point strategy and returns a list of sync-points returned by the 54330 * strategies 54331 * 54332 * @private 54333 * @param {Playlist} playlist 54334 * The playlist that needs a sync-point 54335 * @param {Number} duration 54336 * Duration of the MediaSource (Infinity if playing a live source) 54337 * @param {Number} currentTimeline 54338 * The last timeline from which a segment was loaded 54339 * @returns {Array} 54340 * A list of sync-point objects 54341 */ 54342 54343 }, { 54344 key: 'runStrategies_', 54345 value: function runStrategies_(playlist, duration$$1, currentTimeline, currentTime) { 54346 var syncPoints = []; // Try to find a sync-point in by utilizing various strategies... 54347 54348 for (var i = 0; i < syncPointStrategies.length; i++) { 54349 var strategy = syncPointStrategies[i]; 54350 var syncPoint = strategy.run(this, playlist, duration$$1, currentTimeline, currentTime); 54351 54352 if (syncPoint) { 54353 syncPoint.strategy = strategy.name; 54354 syncPoints.push({ 54355 strategy: strategy.name, 54356 syncPoint: syncPoint 54357 }); 54358 } 54359 } 54360 54361 return syncPoints; 54362 } 54363 /** 54364 * Selects the sync-point nearest the specified target 54365 * 54366 * @private 54367 * @param {Array} syncPoints 54368 * List of sync-points to select from 54369 * @param {Object} target 54370 * Object specifying the property and value we are targeting 54371 * @param {String} target.key 54372 * Specifies the property to target. Must be either 'time' or 'segmentIndex' 54373 * @param {Number} target.value 54374 * The value to target for the specified key. 54375 * @returns {Object} 54376 * The sync-point nearest the target 54377 */ 54378 54379 }, { 54380 key: 'selectSyncPoint_', 54381 value: function selectSyncPoint_(syncPoints, target) { 54382 var bestSyncPoint = syncPoints[0].syncPoint; 54383 var bestDistance = Math.abs(syncPoints[0].syncPoint[target.key] - target.value); 54384 var bestStrategy = syncPoints[0].strategy; 54385 54386 for (var i = 1; i < syncPoints.length; i++) { 54387 var newDistance = Math.abs(syncPoints[i].syncPoint[target.key] - target.value); 54388 54389 if (newDistance < bestDistance) { 54390 bestDistance = newDistance; 54391 bestSyncPoint = syncPoints[i].syncPoint; 54392 bestStrategy = syncPoints[i].strategy; 54393 } 54394 } 54395 54396 this.logger_('syncPoint for [' + target.key + ': ' + target.value + '] chosen with strategy' + (' [' + bestStrategy + ']: [time:' + bestSyncPoint.time + ',') + (' segmentIndex:' + bestSyncPoint.segmentIndex + ']')); 54397 return bestSyncPoint; 54398 } 54399 /** 54400 * Save any meta-data present on the segments when segments leave 54401 * the live window to the playlist to allow for synchronization at the 54402 * playlist level later. 54403 * 54404 * @param {Playlist} oldPlaylist - The previous active playlist 54405 * @param {Playlist} newPlaylist - The updated and most current playlist 54406 */ 54407 54408 }, { 54409 key: 'saveExpiredSegmentInfo', 54410 value: function saveExpiredSegmentInfo(oldPlaylist, newPlaylist) { 54411 var mediaSequenceDiff = newPlaylist.mediaSequence - oldPlaylist.mediaSequence; // When a segment expires from the playlist and it has a start time 54412 // save that information as a possible sync-point reference in future 54413 54414 for (var i = mediaSequenceDiff - 1; i >= 0; i--) { 54415 var lastRemovedSegment = oldPlaylist.segments[i]; 54416 54417 if (lastRemovedSegment && typeof lastRemovedSegment.start !== 'undefined') { 54418 newPlaylist.syncInfo = { 54419 mediaSequence: oldPlaylist.mediaSequence + i, 54420 time: lastRemovedSegment.start 54421 }; 54422 this.logger_('playlist refresh sync: [time:' + newPlaylist.syncInfo.time + ',' + (' mediaSequence: ' + newPlaylist.syncInfo.mediaSequence + ']')); 54423 this.trigger('syncinfoupdate'); 54424 break; 54425 } 54426 } 54427 } 54428 /** 54429 * Save the mapping from playlist's ProgramDateTime to display. This should 54430 * only ever happen once at the start of playback. 54431 * 54432 * @param {Playlist} playlist - The currently active playlist 54433 */ 54434 54435 }, { 54436 key: 'setDateTimeMapping', 54437 value: function setDateTimeMapping(playlist) { 54438 if (!this.datetimeToDisplayTime && playlist.segments && playlist.segments.length && playlist.segments[0].dateTimeObject) { 54439 var playlistTimestamp = playlist.segments[0].dateTimeObject.getTime() / 1000; 54440 this.datetimeToDisplayTime = -playlistTimestamp; 54441 } 54442 } 54443 /** 54444 * Reset the state of the inspection cache when we do a rendition 54445 * switch 54446 */ 54447 54448 }, { 54449 key: 'reset', 54450 value: function reset() { 54451 this.inspectCache_ = undefined; 54452 } 54453 /** 54454 * Probe or inspect a fmp4 or an mpeg2-ts segment to determine the start 54455 * and end of the segment in it's internal "media time". Used to generate 54456 * mappings from that internal "media time" to the display time that is 54457 * shown on the player. 54458 * 54459 * @param {SegmentInfo} segmentInfo - The current active request information 54460 */ 54461 54462 }, { 54463 key: 'probeSegmentInfo', 54464 value: function probeSegmentInfo(segmentInfo) { 54465 var segment = segmentInfo.segment; 54466 var playlist = segmentInfo.playlist; 54467 var timingInfo = void 0; 54468 54469 if (segment.map) { 54470 timingInfo = this.probeMp4Segment_(segmentInfo); 54471 } else { 54472 timingInfo = this.probeTsSegment_(segmentInfo); 54473 } 54474 54475 if (timingInfo) { 54476 if (this.calculateSegmentTimeMapping_(segmentInfo, timingInfo)) { 54477 this.saveDiscontinuitySyncInfo_(segmentInfo); // If the playlist does not have sync information yet, record that information 54478 // now with segment timing information 54479 54480 if (!playlist.syncInfo) { 54481 playlist.syncInfo = { 54482 mediaSequence: playlist.mediaSequence + segmentInfo.mediaIndex, 54483 time: segment.start 54484 }; 54485 } 54486 } 54487 } 54488 54489 return timingInfo; 54490 } 54491 /** 54492 * Probe an fmp4 or an mpeg2-ts segment to determine the start of the segment 54493 * in it's internal "media time". 54494 * 54495 * @private 54496 * @param {SegmentInfo} segmentInfo - The current active request information 54497 * @return {object} The start and end time of the current segment in "media time" 54498 */ 54499 54500 }, { 54501 key: 'probeMp4Segment_', 54502 value: function probeMp4Segment_(segmentInfo) { 54503 var segment = segmentInfo.segment; 54504 var timescales = probe.timescale(segment.map.bytes); 54505 var startTime = probe.startTime(timescales, segmentInfo.bytes); 54506 54507 if (segmentInfo.timestampOffset !== null) { 54508 segmentInfo.timestampOffset -= startTime; 54509 } 54510 54511 return { 54512 start: startTime, 54513 end: startTime + segment.duration 54514 }; 54515 } 54516 /** 54517 * Probe an mpeg2-ts segment to determine the start and end of the segment 54518 * in it's internal "media time". 54519 * 54520 * @private 54521 * @param {SegmentInfo} segmentInfo - The current active request information 54522 * @return {object} The start and end time of the current segment in "media time" 54523 */ 54524 54525 }, { 54526 key: 'probeTsSegment_', 54527 value: function probeTsSegment_(segmentInfo) { 54528 var timeInfo = tsprobe(segmentInfo.bytes, this.inspectCache_); 54529 var segmentStartTime = void 0; 54530 var segmentEndTime = void 0; 54531 54532 if (!timeInfo) { 54533 return null; 54534 } 54535 54536 if (timeInfo.video && timeInfo.video.length === 2) { 54537 this.inspectCache_ = timeInfo.video[1].dts; 54538 segmentStartTime = timeInfo.video[0].dtsTime; 54539 segmentEndTime = timeInfo.video[1].dtsTime; 54540 } else if (timeInfo.audio && timeInfo.audio.length === 2) { 54541 this.inspectCache_ = timeInfo.audio[1].dts; 54542 segmentStartTime = timeInfo.audio[0].dtsTime; 54543 segmentEndTime = timeInfo.audio[1].dtsTime; 54544 } 54545 54546 var probedInfo = { 54547 start: segmentStartTime, 54548 end: segmentEndTime, 54549 containsVideo: timeInfo.video && timeInfo.video.length === 2, 54550 containsAudio: timeInfo.audio && timeInfo.audio.length === 2 54551 }; 54552 return probedInfo; 54553 } 54554 }, { 54555 key: 'timestampOffsetForTimeline', 54556 value: function timestampOffsetForTimeline(timeline) { 54557 if (typeof this.timelines[timeline] === 'undefined') { 54558 return null; 54559 } 54560 54561 return this.timelines[timeline].time; 54562 } 54563 }, { 54564 key: 'mappingForTimeline', 54565 value: function mappingForTimeline(timeline) { 54566 if (typeof this.timelines[timeline] === 'undefined') { 54567 return null; 54568 } 54569 54570 return this.timelines[timeline].mapping; 54571 } 54572 /** 54573 * Use the "media time" for a segment to generate a mapping to "display time" and 54574 * save that display time to the segment. 54575 * 54576 * @private 54577 * @param {SegmentInfo} segmentInfo 54578 * The current active request information 54579 * @param {object} timingInfo 54580 * The start and end time of the current segment in "media time" 54581 * @returns {Boolean} 54582 * Returns false if segment time mapping could not be calculated 54583 */ 54584 54585 }, { 54586 key: 'calculateSegmentTimeMapping_', 54587 value: function calculateSegmentTimeMapping_(segmentInfo, timingInfo) { 54588 var segment = segmentInfo.segment; 54589 var mappingObj = this.timelines[segmentInfo.timeline]; 54590 54591 if (segmentInfo.timestampOffset !== null) { 54592 mappingObj = { 54593 time: segmentInfo.startOfSegment, 54594 mapping: segmentInfo.startOfSegment - timingInfo.start 54595 }; 54596 this.timelines[segmentInfo.timeline] = mappingObj; 54597 this.trigger('timestampoffset'); 54598 this.logger_('time mapping for timeline ' + segmentInfo.timeline + ': ' + ('[time: ' + mappingObj.time + '] [mapping: ' + mappingObj.mapping + ']')); 54599 segment.start = segmentInfo.startOfSegment; 54600 segment.end = timingInfo.end + mappingObj.mapping; 54601 } else if (mappingObj) { 54602 segment.start = timingInfo.start + mappingObj.mapping; 54603 segment.end = timingInfo.end + mappingObj.mapping; 54604 } else { 54605 return false; 54606 } 54607 54608 return true; 54609 } 54610 /** 54611 * Each time we have discontinuity in the playlist, attempt to calculate the location 54612 * in display of the start of the discontinuity and save that. We also save an accuracy 54613 * value so that we save values with the most accuracy (closest to 0.) 54614 * 54615 * @private 54616 * @param {SegmentInfo} segmentInfo - The current active request information 54617 */ 54618 54619 }, { 54620 key: 'saveDiscontinuitySyncInfo_', 54621 value: function saveDiscontinuitySyncInfo_(segmentInfo) { 54622 var playlist = segmentInfo.playlist; 54623 var segment = segmentInfo.segment; // If the current segment is a discontinuity then we know exactly where 54624 // the start of the range and it's accuracy is 0 (greater accuracy values 54625 // mean more approximation) 54626 54627 if (segment.discontinuity) { 54628 this.discontinuities[segment.timeline] = { 54629 time: segment.start, 54630 accuracy: 0 54631 }; 54632 } else if (playlist.discontinuityStarts && playlist.discontinuityStarts.length) { 54633 // Search for future discontinuities that we can provide better timing 54634 // information for and save that information for sync purposes 54635 for (var i = 0; i < playlist.discontinuityStarts.length; i++) { 54636 var segmentIndex = playlist.discontinuityStarts[i]; 54637 var discontinuity = playlist.discontinuitySequence + i + 1; 54638 var mediaIndexDiff = segmentIndex - segmentInfo.mediaIndex; 54639 var accuracy = Math.abs(mediaIndexDiff); 54640 54641 if (!this.discontinuities[discontinuity] || this.discontinuities[discontinuity].accuracy > accuracy) { 54642 var time = void 0; 54643 54644 if (mediaIndexDiff < 0) { 54645 time = segment.start - sumDurations(playlist, segmentInfo.mediaIndex, segmentIndex); 54646 } else { 54647 time = segment.end + sumDurations(playlist, segmentInfo.mediaIndex + 1, segmentIndex); 54648 } 54649 54650 this.discontinuities[discontinuity] = { 54651 time: time, 54652 accuracy: accuracy 54653 }; 54654 } 54655 } 54656 } 54657 } 54658 }]); 54659 return SyncController; 54660 }(videojs$1.EventTarget); 54661 54662 var Decrypter$1 = new shimWorker("./decrypter-worker.worker.js", function (window, document$$1) { 54663 var self = this; 54664 54665 var decrypterWorker = function () { 54666 /* 54667 * pkcs7.pad 54668 * https://github.com/brightcove/pkcs7 54669 * 54670 * Copyright (c) 2014 Brightcove 54671 * Licensed under the apache2 license. 54672 */ 54673 54674 /** 54675 * Returns the subarray of a Uint8Array without PKCS#7 padding. 54676 * @param padded {Uint8Array} unencrypted bytes that have been padded 54677 * @return {Uint8Array} the unpadded bytes 54678 * @see http://tools.ietf.org/html/rfc5652 54679 */ 54680 function unpad(padded) { 54681 return padded.subarray(0, padded.byteLength - padded[padded.byteLength - 1]); 54682 } 54683 54684 var classCallCheck = function classCallCheck(instance, Constructor) { 54685 if (!(instance instanceof Constructor)) { 54686 throw new TypeError("Cannot call a class as a function"); 54687 } 54688 }; 54689 54690 var createClass = function () { 54691 function defineProperties(target, props) { 54692 for (var i = 0; i < props.length; i++) { 54693 var descriptor = props[i]; 54694 descriptor.enumerable = descriptor.enumerable || false; 54695 descriptor.configurable = true; 54696 if ("value" in descriptor) descriptor.writable = true; 54697 Object.defineProperty(target, descriptor.key, descriptor); 54698 } 54699 } 54700 54701 return function (Constructor, protoProps, staticProps) { 54702 if (protoProps) defineProperties(Constructor.prototype, protoProps); 54703 if (staticProps) defineProperties(Constructor, staticProps); 54704 return Constructor; 54705 }; 54706 }(); 54707 54708 var inherits = function inherits(subClass, superClass) { 54709 if (typeof superClass !== "function" && superClass !== null) { 54710 throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); 54711 } 54712 54713 subClass.prototype = Object.create(superClass && superClass.prototype, { 54714 constructor: { 54715 value: subClass, 54716 enumerable: false, 54717 writable: true, 54718 configurable: true 54719 } 54720 }); 54721 if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; 54722 }; 54723 54724 var possibleConstructorReturn = function possibleConstructorReturn(self, call) { 54725 if (!self) { 54726 throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); 54727 } 54728 54729 return call && (typeof call === "object" || typeof call === "function") ? call : self; 54730 }; 54731 /** 54732 * @file aes.js 54733 * 54734 * This file contains an adaptation of the AES decryption algorithm 54735 * from the Standford Javascript Cryptography Library. That work is 54736 * covered by the following copyright and permissions notice: 54737 * 54738 * Copyright 2009-2010 Emily Stark, Mike Hamburg, Dan Boneh. 54739 * All rights reserved. 54740 * 54741 * Redistribution and use in source and binary forms, with or without 54742 * modification, are permitted provided that the following conditions are 54743 * met: 54744 * 54745 * 1. Redistributions of source code must retain the above copyright 54746 * notice, this list of conditions and the following disclaimer. 54747 * 54748 * 2. Redistributions in binary form must reproduce the above 54749 * copyright notice, this list of conditions and the following 54750 * disclaimer in the documentation and/or other materials provided 54751 * with the distribution. 54752 * 54753 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR 54754 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 54755 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 54756 * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> OR CONTRIBUTORS BE 54757 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 54758 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 54759 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 54760 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 54761 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 54762 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN 54763 * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 54764 * 54765 * The views and conclusions contained in the software and documentation 54766 * are those of the authors and should not be interpreted as representing 54767 * official policies, either expressed or implied, of the authors. 54768 */ 54769 54770 /** 54771 * Expand the S-box tables. 54772 * 54773 * @private 54774 */ 54775 54776 54777 var precompute = function precompute() { 54778 var tables = [[[], [], [], [], []], [[], [], [], [], []]]; 54779 var encTable = tables[0]; 54780 var decTable = tables[1]; 54781 var sbox = encTable[4]; 54782 var sboxInv = decTable[4]; 54783 var i = void 0; 54784 var x = void 0; 54785 var xInv = void 0; 54786 var d = []; 54787 var th = []; 54788 var x2 = void 0; 54789 var x4 = void 0; 54790 var x8 = void 0; 54791 var s = void 0; 54792 var tEnc = void 0; 54793 var tDec = void 0; // Compute double and third tables 54794 54795 for (i = 0; i < 256; i++) { 54796 th[(d[i] = i << 1 ^ (i >> 7) * 283) ^ i] = i; 54797 } 54798 54799 for (x = xInv = 0; !sbox[x]; x ^= x2 || 1, xInv = th[xInv] || 1) { 54800 // Compute sbox 54801 s = xInv ^ xInv << 1 ^ xInv << 2 ^ xInv << 3 ^ xInv << 4; 54802 s = s >> 8 ^ s & 255 ^ 99; 54803 sbox[x] = s; 54804 sboxInv[s] = x; // Compute MixColumns 54805 54806 x8 = d[x4 = d[x2 = d[x]]]; 54807 tDec = x8 * 0x1010101 ^ x4 * 0x10001 ^ x2 * 0x101 ^ x * 0x1010100; 54808 tEnc = d[s] * 0x101 ^ s * 0x1010100; 54809 54810 for (i = 0; i < 4; i++) { 54811 encTable[i][x] = tEnc = tEnc << 24 ^ tEnc >>> 8; 54812 decTable[i][s] = tDec = tDec << 24 ^ tDec >>> 8; 54813 } 54814 } // Compactify. Considerable speedup on Firefox. 54815 54816 54817 for (i = 0; i < 5; i++) { 54818 encTable[i] = encTable[i].slice(0); 54819 decTable[i] = decTable[i].slice(0); 54820 } 54821 54822 return tables; 54823 }; 54824 54825 var aesTables = null; 54826 /** 54827 * Schedule out an AES key for both encryption and decryption. This 54828 * is a low-level class. Use a cipher mode to do bulk encryption. 54829 * 54830 * @class AES 54831 * @param key {Array} The key as an array of 4, 6 or 8 words. 54832 */ 54833 54834 var AES = function () { 54835 function AES(key) { 54836 classCallCheck(this, AES); 54837 /** 54838 * The expanded S-box and inverse S-box tables. These will be computed 54839 * on the client so that we don't have to send them down the wire. 54840 * 54841 * There are two tables, _tables[0] is for encryption and 54842 * _tables[1] is for decryption. 54843 * 54844 * The first 4 sub-tables are the expanded S-box with MixColumns. The 54845 * last (_tables[01][4]) is the S-box itself. 54846 * 54847 * @private 54848 */ 54849 // if we have yet to precompute the S-box tables 54850 // do so now 54851 54852 if (!aesTables) { 54853 aesTables = precompute(); 54854 } // then make a copy of that object for use 54855 54856 54857 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()]]; 54858 var i = void 0; 54859 var j = void 0; 54860 var tmp = void 0; 54861 var encKey = void 0; 54862 var decKey = void 0; 54863 var sbox = this._tables[0][4]; 54864 var decTable = this._tables[1]; 54865 var keyLen = key.length; 54866 var rcon = 1; 54867 54868 if (keyLen !== 4 && keyLen !== 6 && keyLen !== 8) { 54869 throw new Error('Invalid aes key size'); 54870 } 54871 54872 encKey = key.slice(0); 54873 decKey = []; 54874 this._key = [encKey, decKey]; // schedule encryption keys 54875 54876 for (i = keyLen; i < 4 * keyLen + 28; i++) { 54877 tmp = encKey[i - 1]; // apply sbox 54878 54879 if (i % keyLen === 0 || keyLen === 8 && i % keyLen === 4) { 54880 tmp = sbox[tmp >>> 24] << 24 ^ sbox[tmp >> 16 & 255] << 16 ^ sbox[tmp >> 8 & 255] << 8 ^ sbox[tmp & 255]; // shift rows and add rcon 54881 54882 if (i % keyLen === 0) { 54883 tmp = tmp << 8 ^ tmp >>> 24 ^ rcon << 24; 54884 rcon = rcon << 1 ^ (rcon >> 7) * 283; 54885 } 54886 } 54887 54888 encKey[i] = encKey[i - keyLen] ^ tmp; 54889 } // schedule decryption keys 54890 54891 54892 for (j = 0; i; j++, i--) { 54893 tmp = encKey[j & 3 ? i : i - 4]; 54894 54895 if (i <= 4 || j < 4) { 54896 decKey[j] = tmp; 54897 } else { 54898 decKey[j] = decTable[0][sbox[tmp >>> 24]] ^ decTable[1][sbox[tmp >> 16 & 255]] ^ decTable[2][sbox[tmp >> 8 & 255]] ^ decTable[3][sbox[tmp & 255]]; 54899 } 54900 } 54901 } 54902 /** 54903 * Decrypt 16 bytes, specified as four 32-bit words. 54904 * 54905 * @param {Number} encrypted0 the first word to decrypt 54906 * @param {Number} encrypted1 the second word to decrypt 54907 * @param {Number} encrypted2 the third word to decrypt 54908 * @param {Number} encrypted3 the fourth word to decrypt 54909 * @param {Int32Array} out the array to write the decrypted words 54910 * into 54911 * @param {Number} offset the offset into the output array to start 54912 * writing results 54913 * @return {Array} The plaintext. 54914 */ 54915 54916 54917 AES.prototype.decrypt = function decrypt$$1(encrypted0, encrypted1, encrypted2, encrypted3, out, offset) { 54918 var key = this._key[1]; // state variables a,b,c,d are loaded with pre-whitened data 54919 54920 var a = encrypted0 ^ key[0]; 54921 var b = encrypted3 ^ key[1]; 54922 var c = encrypted2 ^ key[2]; 54923 var d = encrypted1 ^ key[3]; 54924 var a2 = void 0; 54925 var b2 = void 0; 54926 var c2 = void 0; // key.length === 2 ? 54927 54928 var nInnerRounds = key.length / 4 - 2; 54929 var i = void 0; 54930 var kIndex = 4; 54931 var table = this._tables[1]; // load up the tables 54932 54933 var table0 = table[0]; 54934 var table1 = table[1]; 54935 var table2 = table[2]; 54936 var table3 = table[3]; 54937 var sbox = table[4]; // Inner rounds. Cribbed from OpenSSL. 54938 54939 for (i = 0; i < nInnerRounds; i++) { 54940 a2 = table0[a >>> 24] ^ table1[b >> 16 & 255] ^ table2[c >> 8 & 255] ^ table3[d & 255] ^ key[kIndex]; 54941 b2 = table0[b >>> 24] ^ table1[c >> 16 & 255] ^ table2[d >> 8 & 255] ^ table3[a & 255] ^ key[kIndex + 1]; 54942 c2 = table0[c >>> 24] ^ table1[d >> 16 & 255] ^ table2[a >> 8 & 255] ^ table3[b & 255] ^ key[kIndex + 2]; 54943 d = table0[d >>> 24] ^ table1[a >> 16 & 255] ^ table2[b >> 8 & 255] ^ table3[c & 255] ^ key[kIndex + 3]; 54944 kIndex += 4; 54945 a = a2; 54946 b = b2; 54947 c = c2; 54948 } // Last round. 54949 54950 54951 for (i = 0; i < 4; i++) { 54952 out[(3 & -i) + offset] = sbox[a >>> 24] << 24 ^ sbox[b >> 16 & 255] << 16 ^ sbox[c >> 8 & 255] << 8 ^ sbox[d & 255] ^ key[kIndex++]; 54953 a2 = a; 54954 a = b; 54955 b = c; 54956 c = d; 54957 d = a2; 54958 } 54959 }; 54960 54961 return AES; 54962 }(); 54963 /** 54964 * @file stream.js 54965 */ 54966 54967 /** 54968 * A lightweight readable stream implemention that handles event dispatching. 54969 * 54970 * @class Stream 54971 */ 54972 54973 54974 var Stream = function () { 54975 function Stream() { 54976 classCallCheck(this, Stream); 54977 this.listeners = {}; 54978 } 54979 /** 54980 * Add a listener for a specified event type. 54981 * 54982 * @param {String} type the event name 54983 * @param {Function} listener the callback to be invoked when an event of 54984 * the specified type occurs 54985 */ 54986 54987 54988 Stream.prototype.on = function on(type, listener) { 54989 if (!this.listeners[type]) { 54990 this.listeners[type] = []; 54991 } 54992 54993 this.listeners[type].push(listener); 54994 }; 54995 /** 54996 * Remove a listener for a specified event type. 54997 * 54998 * @param {String} type the event name 54999 * @param {Function} listener a function previously registered for this 55000 * type of event through `on` 55001 * @return {Boolean} if we could turn it off or not 55002 */ 55003 55004 55005 Stream.prototype.off = function off(type, listener) { 55006 if (!this.listeners[type]) { 55007 return false; 55008 } 55009 55010 var index = this.listeners[type].indexOf(listener); 55011 this.listeners[type].splice(index, 1); 55012 return index > -1; 55013 }; 55014 /** 55015 * Trigger an event of the specified type on this stream. Any additional 55016 * arguments to this function are passed as parameters to event listeners. 55017 * 55018 * @param {String} type the event name 55019 */ 55020 55021 55022 Stream.prototype.trigger = function trigger(type) { 55023 var callbacks = this.listeners[type]; 55024 55025 if (!callbacks) { 55026 return; 55027 } // Slicing the arguments on every invocation of this method 55028 // can add a significant amount of overhead. Avoid the 55029 // intermediate object creation for the common case of a 55030 // single callback argument 55031 55032 55033 if (arguments.length === 2) { 55034 var length = callbacks.length; 55035 55036 for (var i = 0; i < length; ++i) { 55037 callbacks[i].call(this, arguments[1]); 55038 } 55039 } else { 55040 var args = Array.prototype.slice.call(arguments, 1); 55041 var _length = callbacks.length; 55042 55043 for (var _i = 0; _i < _length; ++_i) { 55044 callbacks[_i].apply(this, args); 55045 } 55046 } 55047 }; 55048 /** 55049 * Destroys the stream and cleans up. 55050 */ 55051 55052 55053 Stream.prototype.dispose = function dispose() { 55054 this.listeners = {}; 55055 }; 55056 /** 55057 * Forwards all `data` events on this stream to the destination stream. The 55058 * destination stream should provide a method `push` to receive the data 55059 * events as they arrive. 55060 * 55061 * @param {Stream} destination the stream that will receive all `data` events 55062 * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options 55063 */ 55064 55065 55066 Stream.prototype.pipe = function pipe(destination) { 55067 this.on('data', function (data) { 55068 destination.push(data); 55069 }); 55070 }; 55071 55072 return Stream; 55073 }(); 55074 /** 55075 * @file async-stream.js 55076 */ 55077 55078 /** 55079 * A wrapper around the Stream class to use setTiemout 55080 * and run stream "jobs" Asynchronously 55081 * 55082 * @class AsyncStream 55083 * @extends Stream 55084 */ 55085 55086 55087 var AsyncStream$$1 = function (_Stream) { 55088 inherits(AsyncStream$$1, _Stream); 55089 55090 function AsyncStream$$1() { 55091 classCallCheck(this, AsyncStream$$1); 55092 55093 var _this = possibleConstructorReturn(this, _Stream.call(this, Stream)); 55094 55095 _this.jobs = []; 55096 _this.delay = 1; 55097 _this.timeout_ = null; 55098 return _this; 55099 } 55100 /** 55101 * process an async job 55102 * 55103 * @private 55104 */ 55105 55106 55107 AsyncStream$$1.prototype.processJob_ = function processJob_() { 55108 this.jobs.shift()(); 55109 55110 if (this.jobs.length) { 55111 this.timeout_ = setTimeout(this.processJob_.bind(this), this.delay); 55112 } else { 55113 this.timeout_ = null; 55114 } 55115 }; 55116 /** 55117 * push a job into the stream 55118 * 55119 * @param {Function} job the job to push into the stream 55120 */ 55121 55122 55123 AsyncStream$$1.prototype.push = function push(job) { 55124 this.jobs.push(job); 55125 55126 if (!this.timeout_) { 55127 this.timeout_ = setTimeout(this.processJob_.bind(this), this.delay); 55128 } 55129 }; 55130 55131 return AsyncStream$$1; 55132 }(Stream); 55133 /** 55134 * @file decrypter.js 55135 * 55136 * An asynchronous implementation of AES-128 CBC decryption with 55137 * PKCS#7 padding. 55138 */ 55139 55140 /** 55141 * Convert network-order (big-endian) bytes into their little-endian 55142 * representation. 55143 */ 55144 55145 55146 var ntoh = function ntoh(word) { 55147 return word << 24 | (word & 0xff00) << 8 | (word & 0xff0000) >> 8 | word >>> 24; 55148 }; 55149 /** 55150 * Decrypt bytes using AES-128 with CBC and PKCS#7 padding. 55151 * 55152 * @param {Uint8Array} encrypted the encrypted bytes 55153 * @param {Uint32Array} key the bytes of the decryption key 55154 * @param {Uint32Array} initVector the initialization vector (IV) to 55155 * use for the first round of CBC. 55156 * @return {Uint8Array} the decrypted bytes 55157 * 55158 * @see http://en.wikipedia.org/wiki/Advanced_Encryption_Standard 55159 * @see http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_Block_Chaining_.28CBC.29 55160 * @see https://tools.ietf.org/html/rfc2315 55161 */ 55162 55163 55164 var decrypt$$1 = function decrypt$$1(encrypted, key, initVector) { 55165 // word-level access to the encrypted bytes 55166 var encrypted32 = new Int32Array(encrypted.buffer, encrypted.byteOffset, encrypted.byteLength >> 2); 55167 var decipher = new AES(Array.prototype.slice.call(key)); // byte and word-level access for the decrypted output 55168 55169 var decrypted = new Uint8Array(encrypted.byteLength); 55170 var decrypted32 = new Int32Array(decrypted.buffer); // temporary variables for working with the IV, encrypted, and 55171 // decrypted data 55172 55173 var init0 = void 0; 55174 var init1 = void 0; 55175 var init2 = void 0; 55176 var init3 = void 0; 55177 var encrypted0 = void 0; 55178 var encrypted1 = void 0; 55179 var encrypted2 = void 0; 55180 var encrypted3 = void 0; // iteration variable 55181 55182 var wordIx = void 0; // pull out the words of the IV to ensure we don't modify the 55183 // passed-in reference and easier access 55184 55185 init0 = initVector[0]; 55186 init1 = initVector[1]; 55187 init2 = initVector[2]; 55188 init3 = initVector[3]; // decrypt four word sequences, applying cipher-block chaining (CBC) 55189 // to each decrypted block 55190 55191 for (wordIx = 0; wordIx < encrypted32.length; wordIx += 4) { 55192 // convert big-endian (network order) words into little-endian 55193 // (javascript order) 55194 encrypted0 = ntoh(encrypted32[wordIx]); 55195 encrypted1 = ntoh(encrypted32[wordIx + 1]); 55196 encrypted2 = ntoh(encrypted32[wordIx + 2]); 55197 encrypted3 = ntoh(encrypted32[wordIx + 3]); // decrypt the block 55198 55199 decipher.decrypt(encrypted0, encrypted1, encrypted2, encrypted3, decrypted32, wordIx); // XOR with the IV, and restore network byte-order to obtain the 55200 // plaintext 55201 55202 decrypted32[wordIx] = ntoh(decrypted32[wordIx] ^ init0); 55203 decrypted32[wordIx + 1] = ntoh(decrypted32[wordIx + 1] ^ init1); 55204 decrypted32[wordIx + 2] = ntoh(decrypted32[wordIx + 2] ^ init2); 55205 decrypted32[wordIx + 3] = ntoh(decrypted32[wordIx + 3] ^ init3); // setup the IV for the next round 55206 55207 init0 = encrypted0; 55208 init1 = encrypted1; 55209 init2 = encrypted2; 55210 init3 = encrypted3; 55211 } 55212 55213 return decrypted; 55214 }; 55215 /** 55216 * The `Decrypter` class that manages decryption of AES 55217 * data through `AsyncStream` objects and the `decrypt` 55218 * function 55219 * 55220 * @param {Uint8Array} encrypted the encrypted bytes 55221 * @param {Uint32Array} key the bytes of the decryption key 55222 * @param {Uint32Array} initVector the initialization vector (IV) to 55223 * @param {Function} done the function to run when done 55224 * @class Decrypter 55225 */ 55226 55227 55228 var Decrypter$$1 = function () { 55229 function Decrypter$$1(encrypted, key, initVector, done) { 55230 classCallCheck(this, Decrypter$$1); 55231 var step = Decrypter$$1.STEP; 55232 var encrypted32 = new Int32Array(encrypted.buffer); 55233 var decrypted = new Uint8Array(encrypted.byteLength); 55234 var i = 0; 55235 this.asyncStream_ = new AsyncStream$$1(); // split up the encryption job and do the individual chunks asynchronously 55236 55237 this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step), key, initVector, decrypted)); 55238 55239 for (i = step; i < encrypted32.length; i += step) { 55240 initVector = new Uint32Array([ntoh(encrypted32[i - 4]), ntoh(encrypted32[i - 3]), ntoh(encrypted32[i - 2]), ntoh(encrypted32[i - 1])]); 55241 this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step), key, initVector, decrypted)); 55242 } // invoke the done() callback when everything is finished 55243 55244 55245 this.asyncStream_.push(function () { 55246 // remove pkcs#7 padding from the decrypted bytes 55247 done(null, unpad(decrypted)); 55248 }); 55249 } 55250 /** 55251 * a getter for step the maximum number of bytes to process at one time 55252 * 55253 * @return {Number} the value of step 32000 55254 */ 55255 55256 /** 55257 * @private 55258 */ 55259 55260 55261 Decrypter$$1.prototype.decryptChunk_ = function decryptChunk_(encrypted, key, initVector, decrypted) { 55262 return function () { 55263 var bytes = decrypt$$1(encrypted, key, initVector); 55264 decrypted.set(bytes, encrypted.byteOffset); 55265 }; 55266 }; 55267 55268 createClass(Decrypter$$1, null, [{ 55269 key: 'STEP', 55270 get: function get$$1() { 55271 // 4 * 8000; 55272 return 32000; 55273 } 55274 }]); 55275 return Decrypter$$1; 55276 }(); 55277 /** 55278 * @file bin-utils.js 55279 */ 55280 55281 /** 55282 * Creates an object for sending to a web worker modifying properties that are TypedArrays 55283 * into a new object with seperated properties for the buffer, byteOffset, and byteLength. 55284 * 55285 * @param {Object} message 55286 * Object of properties and values to send to the web worker 55287 * @return {Object} 55288 * Modified message with TypedArray values expanded 55289 * @function createTransferableMessage 55290 */ 55291 55292 55293 var createTransferableMessage = function createTransferableMessage(message) { 55294 var transferable = {}; 55295 Object.keys(message).forEach(function (key) { 55296 var value = message[key]; 55297 55298 if (ArrayBuffer.isView(value)) { 55299 transferable[key] = { 55300 bytes: value.buffer, 55301 byteOffset: value.byteOffset, 55302 byteLength: value.byteLength 55303 }; 55304 } else { 55305 transferable[key] = value; 55306 } 55307 }); 55308 return transferable; 55309 }; 55310 /** 55311 * Our web worker interface so that things can talk to aes-decrypter 55312 * that will be running in a web worker. the scope is passed to this by 55313 * webworkify. 55314 * 55315 * @param {Object} self 55316 * the scope for the web worker 55317 */ 55318 55319 55320 var DecrypterWorker = function DecrypterWorker(self) { 55321 self.onmessage = function (event) { 55322 var data = event.data; 55323 var encrypted = new Uint8Array(data.encrypted.bytes, data.encrypted.byteOffset, data.encrypted.byteLength); 55324 var key = new Uint32Array(data.key.bytes, data.key.byteOffset, data.key.byteLength / 4); 55325 var iv = new Uint32Array(data.iv.bytes, data.iv.byteOffset, data.iv.byteLength / 4); 55326 /* eslint-disable no-new, handle-callback-err */ 55327 55328 new Decrypter$$1(encrypted, key, iv, function (err, bytes) { 55329 self.postMessage(createTransferableMessage({ 55330 source: data.source, 55331 decrypted: bytes 55332 }), [bytes.buffer]); 55333 }); 55334 /* eslint-enable */ 55335 }; 55336 }; 55337 55338 var decrypterWorker = new DecrypterWorker(self); 55339 return decrypterWorker; 55340 }(); 55341 }); 55342 /** 55343 * Convert the properties of an HLS track into an audioTrackKind. 55344 * 55345 * @private 55346 */ 55347 55348 var audioTrackKind_ = function audioTrackKind_(properties) { 55349 var kind = properties.default ? 'main' : 'alternative'; 55350 55351 if (properties.characteristics && properties.characteristics.indexOf('public.accessibility.describes-video') >= 0) { 55352 kind = 'main-desc'; 55353 } 55354 55355 return kind; 55356 }; 55357 /** 55358 * Pause provided segment loader and playlist loader if active 55359 * 55360 * @param {SegmentLoader} segmentLoader 55361 * SegmentLoader to pause 55362 * @param {Object} mediaType 55363 * Active media type 55364 * @function stopLoaders 55365 */ 55366 55367 55368 var stopLoaders = function stopLoaders(segmentLoader, mediaType) { 55369 segmentLoader.abort(); 55370 segmentLoader.pause(); 55371 55372 if (mediaType && mediaType.activePlaylistLoader) { 55373 mediaType.activePlaylistLoader.pause(); 55374 mediaType.activePlaylistLoader = null; 55375 } 55376 }; 55377 /** 55378 * Start loading provided segment loader and playlist loader 55379 * 55380 * @param {PlaylistLoader} playlistLoader 55381 * PlaylistLoader to start loading 55382 * @param {Object} mediaType 55383 * Active media type 55384 * @function startLoaders 55385 */ 55386 55387 55388 var startLoaders = function startLoaders(playlistLoader, mediaType) { 55389 // Segment loader will be started after `loadedmetadata` or `loadedplaylist` from the 55390 // playlist loader 55391 mediaType.activePlaylistLoader = playlistLoader; 55392 playlistLoader.load(); 55393 }; 55394 /** 55395 * Returns a function to be called when the media group changes. It performs a 55396 * non-destructive (preserve the buffer) resync of the SegmentLoader. This is because a 55397 * change of group is merely a rendition switch of the same content at another encoding, 55398 * rather than a change of content, such as switching audio from English to Spanish. 55399 * 55400 * @param {String} type 55401 * MediaGroup type 55402 * @param {Object} settings 55403 * Object containing required information for media groups 55404 * @return {Function} 55405 * Handler for a non-destructive resync of SegmentLoader when the active media 55406 * group changes. 55407 * @function onGroupChanged 55408 */ 55409 55410 55411 var onGroupChanged = function onGroupChanged(type, settings) { 55412 return function () { 55413 var _settings$segmentLoad = settings.segmentLoaders, 55414 segmentLoader = _settings$segmentLoad[type], 55415 mainSegmentLoader = _settings$segmentLoad.main, 55416 mediaType = settings.mediaTypes[type]; 55417 var activeTrack = mediaType.activeTrack(); 55418 var activeGroup = mediaType.activeGroup(activeTrack); 55419 var previousActiveLoader = mediaType.activePlaylistLoader; 55420 stopLoaders(segmentLoader, mediaType); 55421 55422 if (!activeGroup) { 55423 // there is no group active 55424 return; 55425 } 55426 55427 if (!activeGroup.playlistLoader) { 55428 if (previousActiveLoader) { 55429 // The previous group had a playlist loader but the new active group does not 55430 // this means we are switching from demuxed to muxed audio. In this case we want to 55431 // do a destructive reset of the main segment loader and not restart the audio 55432 // loaders. 55433 mainSegmentLoader.resetEverything(); 55434 } 55435 55436 return; 55437 } // Non-destructive resync 55438 55439 55440 segmentLoader.resyncLoader(); 55441 startLoaders(activeGroup.playlistLoader, mediaType); 55442 }; 55443 }; 55444 /** 55445 * Returns a function to be called when the media track changes. It performs a 55446 * destructive reset of the SegmentLoader to ensure we start loading as close to 55447 * currentTime as possible. 55448 * 55449 * @param {String} type 55450 * MediaGroup type 55451 * @param {Object} settings 55452 * Object containing required information for media groups 55453 * @return {Function} 55454 * Handler for a destructive reset of SegmentLoader when the active media 55455 * track changes. 55456 * @function onTrackChanged 55457 */ 55458 55459 55460 var onTrackChanged = function onTrackChanged(type, settings) { 55461 return function () { 55462 var _settings$segmentLoad2 = settings.segmentLoaders, 55463 segmentLoader = _settings$segmentLoad2[type], 55464 mainSegmentLoader = _settings$segmentLoad2.main, 55465 mediaType = settings.mediaTypes[type]; 55466 var activeTrack = mediaType.activeTrack(); 55467 var activeGroup = mediaType.activeGroup(activeTrack); 55468 var previousActiveLoader = mediaType.activePlaylistLoader; 55469 stopLoaders(segmentLoader, mediaType); 55470 55471 if (!activeGroup) { 55472 // there is no group active so we do not want to restart loaders 55473 return; 55474 } 55475 55476 if (!activeGroup.playlistLoader) { 55477 // when switching from demuxed audio/video to muxed audio/video (noted by no playlist 55478 // loader for the audio group), we want to do a destructive reset of the main segment 55479 // loader and not restart the audio loaders 55480 mainSegmentLoader.resetEverything(); 55481 return; 55482 } 55483 55484 if (previousActiveLoader === activeGroup.playlistLoader) { 55485 // Nothing has actually changed. This can happen because track change events can fire 55486 // multiple times for a "single" change. One for enabling the new active track, and 55487 // one for disabling the track that was active 55488 startLoaders(activeGroup.playlistLoader, mediaType); 55489 return; 55490 } 55491 55492 if (segmentLoader.track) { 55493 // For WebVTT, set the new text track in the segmentloader 55494 segmentLoader.track(activeTrack); 55495 } // destructive reset 55496 55497 55498 segmentLoader.resetEverything(); 55499 startLoaders(activeGroup.playlistLoader, mediaType); 55500 }; 55501 }; 55502 55503 var onError = { 55504 /** 55505 * Returns a function to be called when a SegmentLoader or PlaylistLoader encounters 55506 * an error. 55507 * 55508 * @param {String} type 55509 * MediaGroup type 55510 * @param {Object} settings 55511 * Object containing required information for media groups 55512 * @return {Function} 55513 * Error handler. Logs warning (or error if the playlist is blacklisted) to 55514 * console and switches back to default audio track. 55515 * @function onError.AUDIO 55516 */ 55517 AUDIO: function AUDIO(type, settings) { 55518 return function () { 55519 var segmentLoader = settings.segmentLoaders[type], 55520 mediaType = settings.mediaTypes[type], 55521 blacklistCurrentPlaylist = settings.blacklistCurrentPlaylist; 55522 stopLoaders(segmentLoader, mediaType); // switch back to default audio track 55523 55524 var activeTrack = mediaType.activeTrack(); 55525 var activeGroup = mediaType.activeGroup(); 55526 var id = (activeGroup.filter(function (group) { 55527 return group.default; 55528 })[0] || activeGroup[0]).id; 55529 var defaultTrack = mediaType.tracks[id]; 55530 55531 if (activeTrack === defaultTrack) { 55532 // Default track encountered an error. All we can do now is blacklist the current 55533 // rendition and hope another will switch audio groups 55534 blacklistCurrentPlaylist({ 55535 message: 'Problem encountered loading the default audio track.' 55536 }); 55537 return; 55538 } 55539 55540 videojs$1.log.warn('Problem encountered loading the alternate audio track.' + 'Switching back to default.'); 55541 55542 for (var trackId in mediaType.tracks) { 55543 mediaType.tracks[trackId].enabled = mediaType.tracks[trackId] === defaultTrack; 55544 } 55545 55546 mediaType.onTrackChanged(); 55547 }; 55548 }, 55549 55550 /** 55551 * Returns a function to be called when a SegmentLoader or PlaylistLoader encounters 55552 * an error. 55553 * 55554 * @param {String} type 55555 * MediaGroup type 55556 * @param {Object} settings 55557 * Object containing required information for media groups 55558 * @return {Function} 55559 * Error handler. Logs warning to console and disables the active subtitle track 55560 * @function onError.SUBTITLES 55561 */ 55562 SUBTITLES: function SUBTITLES(type, settings) { 55563 return function () { 55564 var segmentLoader = settings.segmentLoaders[type], 55565 mediaType = settings.mediaTypes[type]; 55566 videojs$1.log.warn('Problem encountered loading the subtitle track.' + 'Disabling subtitle track.'); 55567 stopLoaders(segmentLoader, mediaType); 55568 var track = mediaType.activeTrack(); 55569 55570 if (track) { 55571 track.mode = 'disabled'; 55572 } 55573 55574 mediaType.onTrackChanged(); 55575 }; 55576 } 55577 }; 55578 var setupListeners = { 55579 /** 55580 * Setup event listeners for audio playlist loader 55581 * 55582 * @param {String} type 55583 * MediaGroup type 55584 * @param {PlaylistLoader|null} playlistLoader 55585 * PlaylistLoader to register listeners on 55586 * @param {Object} settings 55587 * Object containing required information for media groups 55588 * @function setupListeners.AUDIO 55589 */ 55590 AUDIO: function AUDIO(type, playlistLoader, settings) { 55591 if (!playlistLoader) { 55592 // no playlist loader means audio will be muxed with the video 55593 return; 55594 } 55595 55596 var tech = settings.tech, 55597 requestOptions = settings.requestOptions, 55598 segmentLoader = settings.segmentLoaders[type]; 55599 playlistLoader.on('loadedmetadata', function () { 55600 var media = playlistLoader.media(); 55601 segmentLoader.playlist(media, requestOptions); // if the video is already playing, or if this isn't a live video and preload 55602 // permits, start downloading segments 55603 55604 if (!tech.paused() || media.endList && tech.preload() !== 'none') { 55605 segmentLoader.load(); 55606 } 55607 }); 55608 playlistLoader.on('loadedplaylist', function () { 55609 segmentLoader.playlist(playlistLoader.media(), requestOptions); // If the player isn't paused, ensure that the segment loader is running 55610 55611 if (!tech.paused()) { 55612 segmentLoader.load(); 55613 } 55614 }); 55615 playlistLoader.on('error', onError[type](type, settings)); 55616 }, 55617 55618 /** 55619 * Setup event listeners for subtitle playlist loader 55620 * 55621 * @param {String} type 55622 * MediaGroup type 55623 * @param {PlaylistLoader|null} playlistLoader 55624 * PlaylistLoader to register listeners on 55625 * @param {Object} settings 55626 * Object containing required information for media groups 55627 * @function setupListeners.SUBTITLES 55628 */ 55629 SUBTITLES: function SUBTITLES(type, playlistLoader, settings) { 55630 var tech = settings.tech, 55631 requestOptions = settings.requestOptions, 55632 segmentLoader = settings.segmentLoaders[type], 55633 mediaType = settings.mediaTypes[type]; 55634 playlistLoader.on('loadedmetadata', function () { 55635 var media = playlistLoader.media(); 55636 segmentLoader.playlist(media, requestOptions); 55637 segmentLoader.track(mediaType.activeTrack()); // if the video is already playing, or if this isn't a live video and preload 55638 // permits, start downloading segments 55639 55640 if (!tech.paused() || media.endList && tech.preload() !== 'none') { 55641 segmentLoader.load(); 55642 } 55643 }); 55644 playlistLoader.on('loadedplaylist', function () { 55645 segmentLoader.playlist(playlistLoader.media(), requestOptions); // If the player isn't paused, ensure that the segment loader is running 55646 55647 if (!tech.paused()) { 55648 segmentLoader.load(); 55649 } 55650 }); 55651 playlistLoader.on('error', onError[type](type, settings)); 55652 } 55653 }; 55654 55655 var byGroupId = function byGroupId(type, groupId) { 55656 return function (playlist) { 55657 return playlist.attributes[type] === groupId; 55658 }; 55659 }; 55660 55661 var byResolvedUri = function byResolvedUri(resolvedUri) { 55662 return function (playlist) { 55663 return playlist.resolvedUri === resolvedUri; 55664 }; 55665 }; 55666 55667 var initialize = { 55668 /** 55669 * Setup PlaylistLoaders and AudioTracks for the audio groups 55670 * 55671 * @param {String} type 55672 * MediaGroup type 55673 * @param {Object} settings 55674 * Object containing required information for media groups 55675 * @function initialize.AUDIO 55676 */ 55677 'AUDIO': function AUDIO(type, settings) { 55678 var hls = settings.hls, 55679 sourceType = settings.sourceType, 55680 segmentLoader = settings.segmentLoaders[type], 55681 requestOptions = settings.requestOptions, 55682 _settings$master = settings.master, 55683 mediaGroups = _settings$master.mediaGroups, 55684 playlists = _settings$master.playlists, 55685 _settings$mediaTypes$ = settings.mediaTypes[type], 55686 groups = _settings$mediaTypes$.groups, 55687 tracks = _settings$mediaTypes$.tracks, 55688 masterPlaylistLoader = settings.masterPlaylistLoader; // force a default if we have none 55689 55690 if (!mediaGroups[type] || Object.keys(mediaGroups[type]).length === 0) { 55691 mediaGroups[type] = { 55692 main: { 55693 default: { 55694 default: true 55695 } 55696 } 55697 }; 55698 } 55699 55700 for (var groupId in mediaGroups[type]) { 55701 if (!groups[groupId]) { 55702 groups[groupId] = []; 55703 } // List of playlists that have an AUDIO attribute value matching the current 55704 // group ID 55705 55706 55707 var groupPlaylists = playlists.filter(byGroupId(type, groupId)); 55708 55709 for (var variantLabel in mediaGroups[type][groupId]) { 55710 var properties = mediaGroups[type][groupId][variantLabel]; // List of playlists for the current group ID that have a matching uri with 55711 // this alternate audio variant 55712 55713 var matchingPlaylists = groupPlaylists.filter(byResolvedUri(properties.resolvedUri)); 55714 55715 if (matchingPlaylists.length) { 55716 // If there is a playlist that has the same uri as this audio variant, assume 55717 // that the playlist is audio only. We delete the resolvedUri property here 55718 // to prevent a playlist loader from being created so that we don't have 55719 // both the main and audio segment loaders loading the same audio segments 55720 // from the same playlist. 55721 delete properties.resolvedUri; 55722 } 55723 55724 var playlistLoader = void 0; 55725 55726 if (properties.resolvedUri) { 55727 playlistLoader = new PlaylistLoader(properties.resolvedUri, hls, requestOptions); 55728 } else if (properties.playlists && sourceType === 'dash') { 55729 playlistLoader = new DashPlaylistLoader(properties.playlists[0], hls, requestOptions, masterPlaylistLoader); 55730 } else { 55731 // no resolvedUri means the audio is muxed with the video when using this 55732 // audio track 55733 playlistLoader = null; 55734 } 55735 55736 properties = videojs$1.mergeOptions({ 55737 id: variantLabel, 55738 playlistLoader: playlistLoader 55739 }, properties); 55740 setupListeners[type](type, properties.playlistLoader, settings); 55741 groups[groupId].push(properties); 55742 55743 if (typeof tracks[variantLabel] === 'undefined') { 55744 var track = new videojs$1.AudioTrack({ 55745 id: variantLabel, 55746 kind: audioTrackKind_(properties), 55747 enabled: false, 55748 language: properties.language, 55749 default: properties.default, 55750 label: variantLabel 55751 }); 55752 tracks[variantLabel] = track; 55753 } 55754 } 55755 } // setup single error event handler for the segment loader 55756 55757 55758 segmentLoader.on('error', onError[type](type, settings)); 55759 }, 55760 55761 /** 55762 * Setup PlaylistLoaders and TextTracks for the subtitle groups 55763 * 55764 * @param {String} type 55765 * MediaGroup type 55766 * @param {Object} settings 55767 * Object containing required information for media groups 55768 * @function initialize.SUBTITLES 55769 */ 55770 'SUBTITLES': function SUBTITLES(type, settings) { 55771 var tech = settings.tech, 55772 hls = settings.hls, 55773 sourceType = settings.sourceType, 55774 segmentLoader = settings.segmentLoaders[type], 55775 requestOptions = settings.requestOptions, 55776 mediaGroups = settings.master.mediaGroups, 55777 _settings$mediaTypes$2 = settings.mediaTypes[type], 55778 groups = _settings$mediaTypes$2.groups, 55779 tracks = _settings$mediaTypes$2.tracks, 55780 masterPlaylistLoader = settings.masterPlaylistLoader; 55781 55782 for (var groupId in mediaGroups[type]) { 55783 if (!groups[groupId]) { 55784 groups[groupId] = []; 55785 } 55786 55787 for (var variantLabel in mediaGroups[type][groupId]) { 55788 if (mediaGroups[type][groupId][variantLabel].forced) { 55789 // Subtitle playlists with the forced attribute are not selectable in Safari. 55790 // According to Apple's HLS Authoring Specification: 55791 // If content has forced subtitles and regular subtitles in a given language, 55792 // the regular subtitles track in that language MUST contain both the forced 55793 // subtitles and the regular subtitles for that language. 55794 // Because of this requirement and that Safari does not add forced subtitles, 55795 // forced subtitles are skipped here to maintain consistent experience across 55796 // all platforms 55797 continue; 55798 } 55799 55800 var properties = mediaGroups[type][groupId][variantLabel]; 55801 var playlistLoader = void 0; 55802 55803 if (sourceType === 'hls') { 55804 playlistLoader = new PlaylistLoader(properties.resolvedUri, hls, requestOptions); 55805 } else if (sourceType === 'dash') { 55806 playlistLoader = new DashPlaylistLoader(properties.playlists[0], hls, requestOptions, masterPlaylistLoader); 55807 } 55808 55809 properties = videojs$1.mergeOptions({ 55810 id: variantLabel, 55811 playlistLoader: playlistLoader 55812 }, properties); 55813 setupListeners[type](type, properties.playlistLoader, settings); 55814 groups[groupId].push(properties); 55815 55816 if (typeof tracks[variantLabel] === 'undefined') { 55817 var track = tech.addRemoteTextTrack({ 55818 id: variantLabel, 55819 kind: 'subtitles', 55820 default: properties.default && properties.autoselect, 55821 language: properties.language, 55822 label: variantLabel 55823 }, false).track; 55824 tracks[variantLabel] = track; 55825 } 55826 } 55827 } // setup single error event handler for the segment loader 55828 55829 55830 segmentLoader.on('error', onError[type](type, settings)); 55831 }, 55832 55833 /** 55834 * Setup TextTracks for the closed-caption groups 55835 * 55836 * @param {String} type 55837 * MediaGroup type 55838 * @param {Object} settings 55839 * Object containing required information for media groups 55840 * @function initialize['CLOSED-CAPTIONS'] 55841 */ 55842 'CLOSED-CAPTIONS': function CLOSEDCAPTIONS(type, settings) { 55843 var tech = settings.tech, 55844 mediaGroups = settings.master.mediaGroups, 55845 _settings$mediaTypes$3 = settings.mediaTypes[type], 55846 groups = _settings$mediaTypes$3.groups, 55847 tracks = _settings$mediaTypes$3.tracks; 55848 55849 for (var groupId in mediaGroups[type]) { 55850 if (!groups[groupId]) { 55851 groups[groupId] = []; 55852 } 55853 55854 for (var variantLabel in mediaGroups[type][groupId]) { 55855 var properties = mediaGroups[type][groupId][variantLabel]; // We only support CEA608 captions for now, so ignore anything that 55856 // doesn't use a CCx INSTREAM-ID 55857 55858 if (!properties.instreamId.match(/CC\d/)) { 55859 continue; 55860 } // No PlaylistLoader is required for Closed-Captions because the captions are 55861 // embedded within the video stream 55862 55863 55864 groups[groupId].push(videojs$1.mergeOptions({ 55865 id: variantLabel 55866 }, properties)); 55867 55868 if (typeof tracks[variantLabel] === 'undefined') { 55869 var track = tech.addRemoteTextTrack({ 55870 id: properties.instreamId, 55871 kind: 'captions', 55872 default: properties.default && properties.autoselect, 55873 language: properties.language, 55874 label: variantLabel 55875 }, false).track; 55876 tracks[variantLabel] = track; 55877 } 55878 } 55879 } 55880 } 55881 }; 55882 /** 55883 * Returns a function used to get the active group of the provided type 55884 * 55885 * @param {String} type 55886 * MediaGroup type 55887 * @param {Object} settings 55888 * Object containing required information for media groups 55889 * @return {Function} 55890 * Function that returns the active media group for the provided type. Takes an 55891 * optional parameter {TextTrack} track. If no track is provided, a list of all 55892 * variants in the group, otherwise the variant corresponding to the provided 55893 * track is returned. 55894 * @function activeGroup 55895 */ 55896 55897 var activeGroup = function activeGroup(type, settings) { 55898 return function (track) { 55899 var masterPlaylistLoader = settings.masterPlaylistLoader, 55900 groups = settings.mediaTypes[type].groups; 55901 var media = masterPlaylistLoader.media(); 55902 55903 if (!media) { 55904 return null; 55905 } 55906 55907 var variants = null; 55908 55909 if (media.attributes[type]) { 55910 variants = groups[media.attributes[type]]; 55911 } 55912 55913 variants = variants || groups.main; 55914 55915 if (typeof track === 'undefined') { 55916 return variants; 55917 } 55918 55919 if (track === null) { 55920 // An active track was specified so a corresponding group is expected. track === null 55921 // means no track is currently active so there is no corresponding group 55922 return null; 55923 } 55924 55925 return variants.filter(function (props) { 55926 return props.id === track.id; 55927 })[0] || null; 55928 }; 55929 }; 55930 55931 var activeTrack = { 55932 /** 55933 * Returns a function used to get the active track of type provided 55934 * 55935 * @param {String} type 55936 * MediaGroup type 55937 * @param {Object} settings 55938 * Object containing required information for media groups 55939 * @return {Function} 55940 * Function that returns the active media track for the provided type. Returns 55941 * null if no track is active 55942 * @function activeTrack.AUDIO 55943 */ 55944 AUDIO: function AUDIO(type, settings) { 55945 return function () { 55946 var tracks = settings.mediaTypes[type].tracks; 55947 55948 for (var id in tracks) { 55949 if (tracks[id].enabled) { 55950 return tracks[id]; 55951 } 55952 } 55953 55954 return null; 55955 }; 55956 }, 55957 55958 /** 55959 * Returns a function used to get the active track of type provided 55960 * 55961 * @param {String} type 55962 * MediaGroup type 55963 * @param {Object} settings 55964 * Object containing required information for media groups 55965 * @return {Function} 55966 * Function that returns the active media track for the provided type. Returns 55967 * null if no track is active 55968 * @function activeTrack.SUBTITLES 55969 */ 55970 SUBTITLES: function SUBTITLES(type, settings) { 55971 return function () { 55972 var tracks = settings.mediaTypes[type].tracks; 55973 55974 for (var id in tracks) { 55975 if (tracks[id].mode === 'showing') { 55976 return tracks[id]; 55977 } 55978 } 55979 55980 return null; 55981 }; 55982 } 55983 }; 55984 /** 55985 * Setup PlaylistLoaders and Tracks for media groups (Audio, Subtitles, 55986 * Closed-Captions) specified in the master manifest. 55987 * 55988 * @param {Object} settings 55989 * Object containing required information for setting up the media groups 55990 * @param {SegmentLoader} settings.segmentLoaders.AUDIO 55991 * Audio segment loader 55992 * @param {SegmentLoader} settings.segmentLoaders.SUBTITLES 55993 * Subtitle segment loader 55994 * @param {SegmentLoader} settings.segmentLoaders.main 55995 * Main segment loader 55996 * @param {Tech} settings.tech 55997 * The tech of the player 55998 * @param {Object} settings.requestOptions 55999 * XHR request options used by the segment loaders 56000 * @param {PlaylistLoader} settings.masterPlaylistLoader 56001 * PlaylistLoader for the master source 56002 * @param {HlsHandler} settings.hls 56003 * HLS SourceHandler 56004 * @param {Object} settings.master 56005 * The parsed master manifest 56006 * @param {Object} settings.mediaTypes 56007 * Object to store the loaders, tracks, and utility methods for each media type 56008 * @param {Function} settings.blacklistCurrentPlaylist 56009 * Blacklists the current rendition and forces a rendition switch. 56010 * @function setupMediaGroups 56011 */ 56012 56013 var setupMediaGroups = function setupMediaGroups(settings) { 56014 ['AUDIO', 'SUBTITLES', 'CLOSED-CAPTIONS'].forEach(function (type) { 56015 initialize[type](type, settings); 56016 }); 56017 var mediaTypes = settings.mediaTypes, 56018 masterPlaylistLoader = settings.masterPlaylistLoader, 56019 tech = settings.tech, 56020 hls = settings.hls; // setup active group and track getters and change event handlers 56021 56022 ['AUDIO', 'SUBTITLES'].forEach(function (type) { 56023 mediaTypes[type].activeGroup = activeGroup(type, settings); 56024 mediaTypes[type].activeTrack = activeTrack[type](type, settings); 56025 mediaTypes[type].onGroupChanged = onGroupChanged(type, settings); 56026 mediaTypes[type].onTrackChanged = onTrackChanged(type, settings); 56027 }); // DO NOT enable the default subtitle or caption track. 56028 // DO enable the default audio track 56029 56030 var audioGroup = mediaTypes.AUDIO.activeGroup(); 56031 var groupId = (audioGroup.filter(function (group) { 56032 return group.default; 56033 })[0] || audioGroup[0]).id; 56034 mediaTypes.AUDIO.tracks[groupId].enabled = true; 56035 mediaTypes.AUDIO.onTrackChanged(); 56036 masterPlaylistLoader.on('mediachange', function () { 56037 ['AUDIO', 'SUBTITLES'].forEach(function (type) { 56038 return mediaTypes[type].onGroupChanged(); 56039 }); 56040 }); // custom audio track change event handler for usage event 56041 56042 var onAudioTrackChanged = function onAudioTrackChanged() { 56043 mediaTypes.AUDIO.onTrackChanged(); 56044 tech.trigger({ 56045 type: 'usage', 56046 name: 'hls-audio-change' 56047 }); 56048 }; 56049 56050 tech.audioTracks().addEventListener('change', onAudioTrackChanged); 56051 tech.remoteTextTracks().addEventListener('change', mediaTypes.SUBTITLES.onTrackChanged); 56052 hls.on('dispose', function () { 56053 tech.audioTracks().removeEventListener('change', onAudioTrackChanged); 56054 tech.remoteTextTracks().removeEventListener('change', mediaTypes.SUBTITLES.onTrackChanged); 56055 }); // clear existing audio tracks and add the ones we just created 56056 56057 tech.clearTracks('audio'); 56058 56059 for (var id in mediaTypes.AUDIO.tracks) { 56060 tech.audioTracks().addTrack(mediaTypes.AUDIO.tracks[id]); 56061 } 56062 }; 56063 /** 56064 * Creates skeleton object used to store the loaders, tracks, and utility methods for each 56065 * media type 56066 * 56067 * @return {Object} 56068 * Object to store the loaders, tracks, and utility methods for each media type 56069 * @function createMediaTypes 56070 */ 56071 56072 56073 var createMediaTypes = function createMediaTypes() { 56074 var mediaTypes = {}; 56075 ['AUDIO', 'SUBTITLES', 'CLOSED-CAPTIONS'].forEach(function (type) { 56076 mediaTypes[type] = { 56077 groups: {}, 56078 tracks: {}, 56079 activePlaylistLoader: null, 56080 activeGroup: noop$1, 56081 activeTrack: noop$1, 56082 onGroupChanged: noop$1, 56083 onTrackChanged: noop$1 56084 }; 56085 }); 56086 return mediaTypes; 56087 }; 56088 /** 56089 * @file master-playlist-controller.js 56090 */ 56091 56092 56093 var ABORT_EARLY_BLACKLIST_SECONDS = 60 * 2; 56094 var Hls = void 0; // SegmentLoader stats that need to have each loader's 56095 // values summed to calculate the final value 56096 56097 var loaderStats = ['mediaRequests', 'mediaRequestsAborted', 'mediaRequestsTimedout', 'mediaRequestsErrored', 'mediaTransferDuration', 'mediaBytesTransferred']; 56098 56099 var sumLoaderStat = function sumLoaderStat(stat) { 56100 return this.audioSegmentLoader_[stat] + this.mainSegmentLoader_[stat]; 56101 }; 56102 /** 56103 * the master playlist controller controller all interactons 56104 * between playlists and segmentloaders. At this time this mainly 56105 * involves a master playlist and a series of audio playlists 56106 * if they are available 56107 * 56108 * @class MasterPlaylistController 56109 * @extends videojs.EventTarget 56110 */ 56111 56112 56113 var MasterPlaylistController = function (_videojs$EventTarget) { 56114 inherits$1(MasterPlaylistController, _videojs$EventTarget); 56115 56116 function MasterPlaylistController(options) { 56117 classCallCheck$1(this, MasterPlaylistController); 56118 56119 var _this = possibleConstructorReturn$1(this, (MasterPlaylistController.__proto__ || Object.getPrototypeOf(MasterPlaylistController)).call(this)); 56120 56121 var url = options.url, 56122 handleManifestRedirects = options.handleManifestRedirects, 56123 withCredentials = options.withCredentials, 56124 tech = options.tech, 56125 bandwidth = options.bandwidth, 56126 externHls = options.externHls, 56127 useCueTags = options.useCueTags, 56128 blacklistDuration = options.blacklistDuration, 56129 enableLowInitialPlaylist = options.enableLowInitialPlaylist, 56130 sourceType = options.sourceType, 56131 seekTo = options.seekTo; 56132 56133 if (!url) { 56134 throw new Error('A non-empty playlist URL is required'); 56135 } 56136 56137 Hls = externHls; 56138 _this.withCredentials = withCredentials; 56139 _this.tech_ = tech; 56140 _this.hls_ = tech.hls; 56141 _this.seekTo_ = seekTo; 56142 _this.sourceType_ = sourceType; 56143 _this.useCueTags_ = useCueTags; 56144 _this.blacklistDuration = blacklistDuration; 56145 _this.enableLowInitialPlaylist = enableLowInitialPlaylist; 56146 56147 if (_this.useCueTags_) { 56148 _this.cueTagsTrack_ = _this.tech_.addTextTrack('metadata', 'ad-cues'); 56149 _this.cueTagsTrack_.inBandMetadataTrackDispatchType = ''; 56150 } 56151 56152 _this.requestOptions_ = { 56153 withCredentials: withCredentials, 56154 handleManifestRedirects: handleManifestRedirects, 56155 timeout: null 56156 }; 56157 _this.mediaTypes_ = createMediaTypes(); 56158 _this.mediaSource = new videojs$1.MediaSource(); // load the media source into the player 56159 56160 _this.mediaSource.addEventListener('sourceopen', _this.handleSourceOpen_.bind(_this)); 56161 56162 _this.seekable_ = videojs$1.createTimeRanges(); 56163 56164 _this.hasPlayed_ = function () { 56165 return false; 56166 }; 56167 56168 _this.syncController_ = new SyncController(options); 56169 _this.segmentMetadataTrack_ = tech.addRemoteTextTrack({ 56170 kind: 'metadata', 56171 label: 'segment-metadata' 56172 }, false).track; 56173 _this.decrypter_ = new Decrypter$1(); 56174 _this.inbandTextTracks_ = {}; 56175 var segmentLoaderSettings = { 56176 hls: _this.hls_, 56177 mediaSource: _this.mediaSource, 56178 currentTime: _this.tech_.currentTime.bind(_this.tech_), 56179 seekable: function seekable$$1() { 56180 return _this.seekable(); 56181 }, 56182 seeking: function seeking() { 56183 return _this.tech_.seeking(); 56184 }, 56185 duration: function duration$$1() { 56186 return _this.mediaSource.duration; 56187 }, 56188 hasPlayed: function hasPlayed() { 56189 return _this.hasPlayed_(); 56190 }, 56191 goalBufferLength: function goalBufferLength() { 56192 return _this.goalBufferLength(); 56193 }, 56194 bandwidth: bandwidth, 56195 syncController: _this.syncController_, 56196 decrypter: _this.decrypter_, 56197 sourceType: _this.sourceType_, 56198 inbandTextTracks: _this.inbandTextTracks_ 56199 }; 56200 _this.masterPlaylistLoader_ = _this.sourceType_ === 'dash' ? new DashPlaylistLoader(url, _this.hls_, _this.requestOptions_) : new PlaylistLoader(url, _this.hls_, _this.requestOptions_); 56201 56202 _this.setupMasterPlaylistLoaderListeners_(); // setup segment loaders 56203 // combined audio/video or just video when alternate audio track is selected 56204 56205 56206 _this.mainSegmentLoader_ = new SegmentLoader(videojs$1.mergeOptions(segmentLoaderSettings, { 56207 segmentMetadataTrack: _this.segmentMetadataTrack_, 56208 loaderType: 'main' 56209 }), options); // alternate audio track 56210 56211 _this.audioSegmentLoader_ = new SegmentLoader(videojs$1.mergeOptions(segmentLoaderSettings, { 56212 loaderType: 'audio' 56213 }), options); 56214 _this.subtitleSegmentLoader_ = new VTTSegmentLoader(videojs$1.mergeOptions(segmentLoaderSettings, { 56215 loaderType: 'vtt' 56216 }), options); 56217 56218 _this.setupSegmentLoaderListeners_(); // Create SegmentLoader stat-getters 56219 56220 56221 loaderStats.forEach(function (stat) { 56222 _this[stat + '_'] = sumLoaderStat.bind(_this, stat); 56223 }); 56224 _this.logger_ = logger('MPC'); 56225 56226 _this.masterPlaylistLoader_.load(); 56227 56228 return _this; 56229 } 56230 /** 56231 * Register event handlers on the master playlist loader. A helper 56232 * function for construction time. 56233 * 56234 * @private 56235 */ 56236 56237 56238 createClass$1(MasterPlaylistController, [{ 56239 key: 'setupMasterPlaylistLoaderListeners_', 56240 value: function setupMasterPlaylistLoaderListeners_() { 56241 var _this2 = this; 56242 56243 this.masterPlaylistLoader_.on('loadedmetadata', function () { 56244 var media = _this2.masterPlaylistLoader_.media(); 56245 56246 var requestTimeout = media.targetDuration * 1.5 * 1000; // If we don't have any more available playlists, we don't want to 56247 // timeout the request. 56248 56249 if (isLowestEnabledRendition(_this2.masterPlaylistLoader_.master, _this2.masterPlaylistLoader_.media())) { 56250 _this2.requestOptions_.timeout = 0; 56251 } else { 56252 _this2.requestOptions_.timeout = requestTimeout; 56253 } // if this isn't a live video and preload permits, start 56254 // downloading segments 56255 56256 56257 if (media.endList && _this2.tech_.preload() !== 'none') { 56258 _this2.mainSegmentLoader_.playlist(media, _this2.requestOptions_); 56259 56260 _this2.mainSegmentLoader_.load(); 56261 } 56262 56263 setupMediaGroups({ 56264 sourceType: _this2.sourceType_, 56265 segmentLoaders: { 56266 AUDIO: _this2.audioSegmentLoader_, 56267 SUBTITLES: _this2.subtitleSegmentLoader_, 56268 main: _this2.mainSegmentLoader_ 56269 }, 56270 tech: _this2.tech_, 56271 requestOptions: _this2.requestOptions_, 56272 masterPlaylistLoader: _this2.masterPlaylistLoader_, 56273 hls: _this2.hls_, 56274 master: _this2.master(), 56275 mediaTypes: _this2.mediaTypes_, 56276 blacklistCurrentPlaylist: _this2.blacklistCurrentPlaylist.bind(_this2) 56277 }); 56278 56279 _this2.triggerPresenceUsage_(_this2.master(), media); 56280 56281 try { 56282 _this2.setupSourceBuffers_(); 56283 } catch (e) { 56284 videojs$1.log.warn('Failed to create SourceBuffers', e); 56285 return _this2.mediaSource.endOfStream('decode'); 56286 } 56287 56288 _this2.setupFirstPlay(); 56289 56290 if (!_this2.mediaTypes_.AUDIO.activePlaylistLoader || _this2.mediaTypes_.AUDIO.activePlaylistLoader.media()) { 56291 _this2.trigger('selectedinitialmedia'); 56292 } else { 56293 // We must wait for the active audio playlist loader to 56294 // finish setting up before triggering this event so the 56295 // representations API and EME setup is correct 56296 _this2.mediaTypes_.AUDIO.activePlaylistLoader.one('loadedmetadata', function () { 56297 _this2.trigger('selectedinitialmedia'); 56298 }); 56299 } 56300 }); 56301 this.masterPlaylistLoader_.on('loadedplaylist', function () { 56302 var updatedPlaylist = _this2.masterPlaylistLoader_.media(); 56303 56304 if (!updatedPlaylist) { 56305 // blacklist any variants that are not supported by the browser before selecting 56306 // an initial media as the playlist selectors do not consider browser support 56307 _this2.excludeUnsupportedVariants_(); 56308 56309 var selectedMedia = void 0; 56310 56311 if (_this2.enableLowInitialPlaylist) { 56312 selectedMedia = _this2.selectInitialPlaylist(); 56313 } 56314 56315 if (!selectedMedia) { 56316 selectedMedia = _this2.selectPlaylist(); 56317 } 56318 56319 _this2.initialMedia_ = selectedMedia; 56320 56321 _this2.masterPlaylistLoader_.media(_this2.initialMedia_); 56322 56323 return; 56324 } 56325 56326 if (_this2.useCueTags_) { 56327 _this2.updateAdCues_(updatedPlaylist); 56328 } // TODO: Create a new event on the PlaylistLoader that signals 56329 // that the segments have changed in some way and use that to 56330 // update the SegmentLoader instead of doing it twice here and 56331 // on `mediachange` 56332 56333 56334 _this2.mainSegmentLoader_.playlist(updatedPlaylist, _this2.requestOptions_); 56335 56336 _this2.updateDuration(); // If the player isn't paused, ensure that the segment loader is running, 56337 // as it is possible that it was temporarily stopped while waiting for 56338 // a playlist (e.g., in case the playlist errored and we re-requested it). 56339 56340 56341 if (!_this2.tech_.paused()) { 56342 _this2.mainSegmentLoader_.load(); 56343 56344 if (_this2.audioSegmentLoader_) { 56345 _this2.audioSegmentLoader_.load(); 56346 } 56347 } 56348 56349 if (!updatedPlaylist.endList) { 56350 var addSeekableRange = function addSeekableRange() { 56351 var seekable$$1 = _this2.seekable(); 56352 56353 if (seekable$$1.length !== 0) { 56354 _this2.mediaSource.addSeekableRange_(seekable$$1.start(0), seekable$$1.end(0)); 56355 } 56356 }; 56357 56358 if (_this2.duration() !== Infinity) { 56359 var onDurationchange = function onDurationchange() { 56360 if (_this2.duration() === Infinity) { 56361 addSeekableRange(); 56362 } else { 56363 _this2.tech_.one('durationchange', onDurationchange); 56364 } 56365 }; 56366 56367 _this2.tech_.one('durationchange', onDurationchange); 56368 } else { 56369 addSeekableRange(); 56370 } 56371 } 56372 }); 56373 this.masterPlaylistLoader_.on('error', function () { 56374 _this2.blacklistCurrentPlaylist(_this2.masterPlaylistLoader_.error); 56375 }); 56376 this.masterPlaylistLoader_.on('mediachanging', function () { 56377 _this2.mainSegmentLoader_.abort(); 56378 56379 _this2.mainSegmentLoader_.pause(); 56380 }); 56381 this.masterPlaylistLoader_.on('mediachange', function () { 56382 var media = _this2.masterPlaylistLoader_.media(); 56383 56384 var requestTimeout = media.targetDuration * 1.5 * 1000; // If we don't have any more available playlists, we don't want to 56385 // timeout the request. 56386 56387 if (isLowestEnabledRendition(_this2.masterPlaylistLoader_.master, _this2.masterPlaylistLoader_.media())) { 56388 _this2.requestOptions_.timeout = 0; 56389 } else { 56390 _this2.requestOptions_.timeout = requestTimeout; 56391 } // TODO: Create a new event on the PlaylistLoader that signals 56392 // that the segments have changed in some way and use that to 56393 // update the SegmentLoader instead of doing it twice here and 56394 // on `loadedplaylist` 56395 56396 56397 _this2.mainSegmentLoader_.playlist(media, _this2.requestOptions_); 56398 56399 _this2.mainSegmentLoader_.load(); 56400 56401 _this2.tech_.trigger({ 56402 type: 'mediachange', 56403 bubbles: true 56404 }); 56405 }); 56406 this.masterPlaylistLoader_.on('playlistunchanged', function () { 56407 var updatedPlaylist = _this2.masterPlaylistLoader_.media(); 56408 56409 var playlistOutdated = _this2.stuckAtPlaylistEnd_(updatedPlaylist); 56410 56411 if (playlistOutdated) { 56412 // Playlist has stopped updating and we're stuck at its end. Try to 56413 // blacklist it and switch to another playlist in the hope that that 56414 // one is updating (and give the player a chance to re-adjust to the 56415 // safe live point). 56416 _this2.blacklistCurrentPlaylist({ 56417 message: 'Playlist no longer updating.' 56418 }); // useful for monitoring QoS 56419 56420 56421 _this2.tech_.trigger('playliststuck'); 56422 } 56423 }); 56424 this.masterPlaylistLoader_.on('renditiondisabled', function () { 56425 _this2.tech_.trigger({ 56426 type: 'usage', 56427 name: 'hls-rendition-disabled' 56428 }); 56429 }); 56430 this.masterPlaylistLoader_.on('renditionenabled', function () { 56431 _this2.tech_.trigger({ 56432 type: 'usage', 56433 name: 'hls-rendition-enabled' 56434 }); 56435 }); 56436 } 56437 /** 56438 * A helper function for triggerring presence usage events once per source 56439 * 56440 * @private 56441 */ 56442 56443 }, { 56444 key: 'triggerPresenceUsage_', 56445 value: function triggerPresenceUsage_(master, media) { 56446 var mediaGroups = master.mediaGroups || {}; 56447 var defaultDemuxed = true; 56448 var audioGroupKeys = Object.keys(mediaGroups.AUDIO); 56449 56450 for (var mediaGroup in mediaGroups.AUDIO) { 56451 for (var label in mediaGroups.AUDIO[mediaGroup]) { 56452 var properties = mediaGroups.AUDIO[mediaGroup][label]; 56453 56454 if (!properties.uri) { 56455 defaultDemuxed = false; 56456 } 56457 } 56458 } 56459 56460 if (defaultDemuxed) { 56461 this.tech_.trigger({ 56462 type: 'usage', 56463 name: 'hls-demuxed' 56464 }); 56465 } 56466 56467 if (Object.keys(mediaGroups.SUBTITLES).length) { 56468 this.tech_.trigger({ 56469 type: 'usage', 56470 name: 'hls-webvtt' 56471 }); 56472 } 56473 56474 if (Hls.Playlist.isAes(media)) { 56475 this.tech_.trigger({ 56476 type: 'usage', 56477 name: 'hls-aes' 56478 }); 56479 } 56480 56481 if (Hls.Playlist.isFmp4(media)) { 56482 this.tech_.trigger({ 56483 type: 'usage', 56484 name: 'hls-fmp4' 56485 }); 56486 } 56487 56488 if (audioGroupKeys.length && Object.keys(mediaGroups.AUDIO[audioGroupKeys[0]]).length > 1) { 56489 this.tech_.trigger({ 56490 type: 'usage', 56491 name: 'hls-alternate-audio' 56492 }); 56493 } 56494 56495 if (this.useCueTags_) { 56496 this.tech_.trigger({ 56497 type: 'usage', 56498 name: 'hls-playlist-cue-tags' 56499 }); 56500 } 56501 } 56502 /** 56503 * Register event handlers on the segment loaders. A helper function 56504 * for construction time. 56505 * 56506 * @private 56507 */ 56508 56509 }, { 56510 key: 'setupSegmentLoaderListeners_', 56511 value: function setupSegmentLoaderListeners_() { 56512 var _this3 = this; 56513 56514 this.mainSegmentLoader_.on('bandwidthupdate', function () { 56515 var nextPlaylist = _this3.selectPlaylist(); 56516 56517 var currentPlaylist = _this3.masterPlaylistLoader_.media(); 56518 56519 var buffered = _this3.tech_.buffered(); 56520 56521 var forwardBuffer = buffered.length ? buffered.end(buffered.length - 1) - _this3.tech_.currentTime() : 0; 56522 56523 var bufferLowWaterLine = _this3.bufferLowWaterLine(); // If the playlist is live, then we want to not take low water line into account. 56524 // This is because in LIVE, the player plays 3 segments from the end of the 56525 // playlist, and if `BUFFER_LOW_WATER_LINE` is greater than the duration availble 56526 // in those segments, a viewer will never experience a rendition upswitch. 56527 56528 56529 if (!currentPlaylist.endList || // For the same reason as LIVE, we ignore the low water line when the VOD 56530 // duration is below the max potential low water line 56531 _this3.duration() < Config.MAX_BUFFER_LOW_WATER_LINE || // we want to switch down to lower resolutions quickly to continue playback, but 56532 nextPlaylist.attributes.BANDWIDTH < currentPlaylist.attributes.BANDWIDTH || // ensure we have some buffer before we switch up to prevent us running out of 56533 // buffer while loading a higher rendition. 56534 forwardBuffer >= bufferLowWaterLine) { 56535 _this3.masterPlaylistLoader_.media(nextPlaylist); 56536 } 56537 56538 _this3.tech_.trigger('bandwidthupdate'); 56539 }); 56540 this.mainSegmentLoader_.on('progress', function () { 56541 _this3.trigger('progress'); 56542 }); 56543 this.mainSegmentLoader_.on('error', function () { 56544 _this3.blacklistCurrentPlaylist(_this3.mainSegmentLoader_.error()); 56545 }); 56546 this.mainSegmentLoader_.on('syncinfoupdate', function () { 56547 _this3.onSyncInfoUpdate_(); 56548 }); 56549 this.mainSegmentLoader_.on('timestampoffset', function () { 56550 _this3.tech_.trigger({ 56551 type: 'usage', 56552 name: 'hls-timestamp-offset' 56553 }); 56554 }); 56555 this.audioSegmentLoader_.on('syncinfoupdate', function () { 56556 _this3.onSyncInfoUpdate_(); 56557 }); 56558 this.mainSegmentLoader_.on('ended', function () { 56559 _this3.onEndOfStream(); 56560 }); 56561 this.mainSegmentLoader_.on('earlyabort', function () { 56562 _this3.blacklistCurrentPlaylist({ 56563 message: 'Aborted early because there isn\'t enough bandwidth to complete the ' + 'request without rebuffering.' 56564 }, ABORT_EARLY_BLACKLIST_SECONDS); 56565 }); 56566 this.mainSegmentLoader_.on('reseteverything', function () { 56567 // If playing an MTS stream, a videojs.MediaSource is listening for 56568 // hls-reset to reset caption parsing state in the transmuxer 56569 _this3.tech_.trigger('hls-reset'); 56570 }); 56571 this.mainSegmentLoader_.on('segmenttimemapping', function (event) { 56572 // If playing an MTS stream in html, a videojs.MediaSource is listening for 56573 // hls-segment-time-mapping update its internal mapping of stream to display time 56574 _this3.tech_.trigger({ 56575 type: 'hls-segment-time-mapping', 56576 mapping: event.mapping 56577 }); 56578 }); 56579 this.audioSegmentLoader_.on('ended', function () { 56580 _this3.onEndOfStream(); 56581 }); 56582 } 56583 }, { 56584 key: 'mediaSecondsLoaded_', 56585 value: function mediaSecondsLoaded_() { 56586 return Math.max(this.audioSegmentLoader_.mediaSecondsLoaded + this.mainSegmentLoader_.mediaSecondsLoaded); 56587 } 56588 /** 56589 * Call load on our SegmentLoaders 56590 */ 56591 56592 }, { 56593 key: 'load', 56594 value: function load() { 56595 this.mainSegmentLoader_.load(); 56596 56597 if (this.mediaTypes_.AUDIO.activePlaylistLoader) { 56598 this.audioSegmentLoader_.load(); 56599 } 56600 56601 if (this.mediaTypes_.SUBTITLES.activePlaylistLoader) { 56602 this.subtitleSegmentLoader_.load(); 56603 } 56604 } 56605 /** 56606 * Re-tune playback quality level for the current player 56607 * conditions without performing destructive actions, like 56608 * removing already buffered content 56609 * 56610 * @private 56611 */ 56612 56613 }, { 56614 key: 'smoothQualityChange_', 56615 value: function smoothQualityChange_() { 56616 var media = this.selectPlaylist(); 56617 56618 if (media !== this.masterPlaylistLoader_.media()) { 56619 this.masterPlaylistLoader_.media(media); 56620 this.mainSegmentLoader_.resetLoader(); // don't need to reset audio as it is reset when media changes 56621 } 56622 } 56623 /** 56624 * Re-tune playback quality level for the current player 56625 * conditions. This method will perform destructive actions like removing 56626 * already buffered content in order to readjust the currently active 56627 * playlist quickly. This is good for manual quality changes 56628 * 56629 * @private 56630 */ 56631 56632 }, { 56633 key: 'fastQualityChange_', 56634 value: function fastQualityChange_() { 56635 var _this4 = this; 56636 56637 var media = this.selectPlaylist(); 56638 56639 if (media === this.masterPlaylistLoader_.media()) { 56640 return; 56641 } 56642 56643 this.masterPlaylistLoader_.media(media); // Delete all buffered data to allow an immediate quality switch, then seek to give 56644 // the browser a kick to remove any cached frames from the previous rendtion (.04 seconds 56645 // ahead is roughly the minimum that will accomplish this across a variety of content 56646 // in IE and Edge, but seeking in place is sufficient on all other browsers) 56647 // Edge/IE bug: https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/14600375/ 56648 // Chrome bug: https://bugs.chromium.org/p/chromium/issues/detail?id=651904 56649 56650 this.mainSegmentLoader_.resetEverything(function () { 56651 // Since this is not a typical seek, we avoid the seekTo method which can cause segments 56652 // from the previously enabled rendition to load before the new playlist has finished loading 56653 if (videojs$1.browser.IE_VERSION || videojs$1.browser.IS_EDGE) { 56654 _this4.tech_.setCurrentTime(_this4.tech_.currentTime() + 0.04); 56655 } else { 56656 _this4.tech_.setCurrentTime(_this4.tech_.currentTime()); 56657 } 56658 }); // don't need to reset audio as it is reset when media changes 56659 } 56660 /** 56661 * Begin playback. 56662 */ 56663 56664 }, { 56665 key: 'play', 56666 value: function play() { 56667 if (this.setupFirstPlay()) { 56668 return; 56669 } 56670 56671 if (this.tech_.ended()) { 56672 this.seekTo_(0); 56673 } 56674 56675 if (this.hasPlayed_()) { 56676 this.load(); 56677 } 56678 56679 var seekable$$1 = this.tech_.seekable(); // if the viewer has paused and we fell out of the live window, 56680 // seek forward to the live point 56681 56682 if (this.tech_.duration() === Infinity) { 56683 if (this.tech_.currentTime() < seekable$$1.start(0)) { 56684 return this.seekTo_(seekable$$1.end(seekable$$1.length - 1)); 56685 } 56686 } 56687 } 56688 /** 56689 * Seek to the latest media position if this is a live video and the 56690 * player and video are loaded and initialized. 56691 */ 56692 56693 }, { 56694 key: 'setupFirstPlay', 56695 value: function setupFirstPlay() { 56696 var _this5 = this; 56697 56698 var media = this.masterPlaylistLoader_.media(); // Check that everything is ready to begin buffering for the first call to play 56699 // If 1) there is no active media 56700 // 2) the player is paused 56701 // 3) the first play has already been setup 56702 // then exit early 56703 56704 if (!media || this.tech_.paused() || this.hasPlayed_()) { 56705 return false; 56706 } // when the video is a live stream 56707 56708 56709 if (!media.endList) { 56710 var seekable$$1 = this.seekable(); 56711 56712 if (!seekable$$1.length) { 56713 // without a seekable range, the player cannot seek to begin buffering at the live 56714 // point 56715 return false; 56716 } 56717 56718 if (videojs$1.browser.IE_VERSION && this.tech_.readyState() === 0) { 56719 // IE11 throws an InvalidStateError if you try to set currentTime while the 56720 // readyState is 0, so it must be delayed until the tech fires loadedmetadata. 56721 this.tech_.one('loadedmetadata', function () { 56722 _this5.trigger('firstplay'); 56723 56724 _this5.seekTo_(seekable$$1.end(0)); 56725 56726 _this5.hasPlayed_ = function () { 56727 return true; 56728 }; 56729 }); 56730 return false; 56731 } // trigger firstplay to inform the source handler to ignore the next seek event 56732 56733 56734 this.trigger('firstplay'); // seek to the live point 56735 56736 this.seekTo_(seekable$$1.end(0)); 56737 } 56738 56739 this.hasPlayed_ = function () { 56740 return true; 56741 }; // we can begin loading now that everything is ready 56742 56743 56744 this.load(); 56745 return true; 56746 } 56747 /** 56748 * handle the sourceopen event on the MediaSource 56749 * 56750 * @private 56751 */ 56752 56753 }, { 56754 key: 'handleSourceOpen_', 56755 value: function handleSourceOpen_() { 56756 // Only attempt to create the source buffer if none already exist. 56757 // handleSourceOpen is also called when we are "re-opening" a source buffer 56758 // after `endOfStream` has been called (in response to a seek for instance) 56759 try { 56760 this.setupSourceBuffers_(); 56761 } catch (e) { 56762 videojs$1.log.warn('Failed to create Source Buffers', e); 56763 return this.mediaSource.endOfStream('decode'); 56764 } // if autoplay is enabled, begin playback. This is duplicative of 56765 // code in video.js but is required because play() must be invoked 56766 // *after* the media source has opened. 56767 56768 56769 if (this.tech_.autoplay()) { 56770 var playPromise = this.tech_.play(); // Catch/silence error when a pause interrupts a play request 56771 // on browsers which return a promise 56772 56773 if (typeof playPromise !== 'undefined' && typeof playPromise.then === 'function') { 56774 playPromise.then(null, function (e) {}); 56775 } 56776 } 56777 56778 this.trigger('sourceopen'); 56779 } 56780 /** 56781 * Calls endOfStream on the media source when all active stream types have called 56782 * endOfStream 56783 * 56784 * @param {string} streamType 56785 * Stream type of the segment loader that called endOfStream 56786 * @private 56787 */ 56788 56789 }, { 56790 key: 'onEndOfStream', 56791 value: function onEndOfStream() { 56792 var isEndOfStream = this.mainSegmentLoader_.ended_; 56793 56794 if (this.mediaTypes_.AUDIO.activePlaylistLoader) { 56795 // if the audio playlist loader exists, then alternate audio is active 56796 if (!this.mainSegmentLoader_.startingMedia_ || this.mainSegmentLoader_.startingMedia_.containsVideo) { 56797 // if we do not know if the main segment loader contains video yet or if we 56798 // definitively know the main segment loader contains video, then we need to wait 56799 // for both main and audio segment loaders to call endOfStream 56800 isEndOfStream = isEndOfStream && this.audioSegmentLoader_.ended_; 56801 } else { 56802 // otherwise just rely on the audio loader 56803 isEndOfStream = this.audioSegmentLoader_.ended_; 56804 } 56805 } 56806 56807 if (!isEndOfStream) { 56808 return; 56809 } 56810 56811 this.logger_('calling mediaSource.endOfStream()'); // on chrome calling endOfStream can sometimes cause an exception, 56812 // even when the media source is in a valid state. 56813 56814 try { 56815 this.mediaSource.endOfStream(); 56816 } catch (e) { 56817 videojs$1.log.warn('Failed to call media source endOfStream', e); 56818 } 56819 } 56820 /** 56821 * Check if a playlist has stopped being updated 56822 * @param {Object} playlist the media playlist object 56823 * @return {boolean} whether the playlist has stopped being updated or not 56824 */ 56825 56826 }, { 56827 key: 'stuckAtPlaylistEnd_', 56828 value: function stuckAtPlaylistEnd_(playlist) { 56829 var seekable$$1 = this.seekable(); 56830 56831 if (!seekable$$1.length) { 56832 // playlist doesn't have enough information to determine whether we are stuck 56833 return false; 56834 } 56835 56836 var expired = this.syncController_.getExpiredTime(playlist, this.mediaSource.duration); 56837 56838 if (expired === null) { 56839 return false; 56840 } // does not use the safe live end to calculate playlist end, since we 56841 // don't want to say we are stuck while there is still content 56842 56843 56844 var absolutePlaylistEnd = Hls.Playlist.playlistEnd(playlist, expired); 56845 var currentTime = this.tech_.currentTime(); 56846 var buffered = this.tech_.buffered(); 56847 56848 if (!buffered.length) { 56849 // return true if the playhead reached the absolute end of the playlist 56850 return absolutePlaylistEnd - currentTime <= SAFE_TIME_DELTA; 56851 } 56852 56853 var bufferedEnd = buffered.end(buffered.length - 1); // return true if there is too little buffer left and buffer has reached absolute 56854 // end of playlist 56855 56856 return bufferedEnd - currentTime <= SAFE_TIME_DELTA && absolutePlaylistEnd - bufferedEnd <= SAFE_TIME_DELTA; 56857 } 56858 /** 56859 * Blacklists a playlist when an error occurs for a set amount of time 56860 * making it unavailable for selection by the rendition selection algorithm 56861 * and then forces a new playlist (rendition) selection. 56862 * 56863 * @param {Object=} error an optional error that may include the playlist 56864 * to blacklist 56865 * @param {Number=} blacklistDuration an optional number of seconds to blacklist the 56866 * playlist 56867 */ 56868 56869 }, { 56870 key: 'blacklistCurrentPlaylist', 56871 value: function blacklistCurrentPlaylist() { 56872 var error = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; 56873 var blacklistDuration = arguments[1]; 56874 var currentPlaylist = void 0; 56875 var nextPlaylist = void 0; // If the `error` was generated by the playlist loader, it will contain 56876 // the playlist we were trying to load (but failed) and that should be 56877 // blacklisted instead of the currently selected playlist which is likely 56878 // out-of-date in this scenario 56879 56880 currentPlaylist = error.playlist || this.masterPlaylistLoader_.media(); 56881 blacklistDuration = blacklistDuration || error.blacklistDuration || this.blacklistDuration; // If there is no current playlist, then an error occurred while we were 56882 // trying to load the master OR while we were disposing of the tech 56883 56884 if (!currentPlaylist) { 56885 this.error = error; 56886 56887 try { 56888 return this.mediaSource.endOfStream('network'); 56889 } catch (e) { 56890 return this.trigger('error'); 56891 } 56892 } 56893 56894 var isFinalRendition = this.masterPlaylistLoader_.master.playlists.filter(isEnabled).length === 1; 56895 56896 if (isFinalRendition) { 56897 // Never blacklisting this playlist because it's final rendition 56898 videojs$1.log.warn('Problem encountered with the current ' + 'HLS playlist. Trying again since it is the final playlist.'); 56899 this.tech_.trigger('retryplaylist'); 56900 return this.masterPlaylistLoader_.load(isFinalRendition); 56901 } // Blacklist this playlist 56902 56903 56904 currentPlaylist.excludeUntil = Date.now() + blacklistDuration * 1000; 56905 this.tech_.trigger('blacklistplaylist'); 56906 this.tech_.trigger({ 56907 type: 'usage', 56908 name: 'hls-rendition-blacklisted' 56909 }); // Select a new playlist 56910 56911 nextPlaylist = this.selectPlaylist(); 56912 videojs$1.log.warn('Problem encountered with the current HLS playlist.' + (error.message ? ' ' + error.message : '') + ' Switching to another playlist.'); 56913 return this.masterPlaylistLoader_.media(nextPlaylist); 56914 } 56915 /** 56916 * Pause all segment loaders 56917 */ 56918 56919 }, { 56920 key: 'pauseLoading', 56921 value: function pauseLoading() { 56922 this.mainSegmentLoader_.pause(); 56923 56924 if (this.mediaTypes_.AUDIO.activePlaylistLoader) { 56925 this.audioSegmentLoader_.pause(); 56926 } 56927 56928 if (this.mediaTypes_.SUBTITLES.activePlaylistLoader) { 56929 this.subtitleSegmentLoader_.pause(); 56930 } 56931 } 56932 /** 56933 * set the current time on all segment loaders 56934 * 56935 * @param {TimeRange} currentTime the current time to set 56936 * @return {TimeRange} the current time 56937 */ 56938 56939 }, { 56940 key: 'setCurrentTime', 56941 value: function setCurrentTime(currentTime) { 56942 var buffered = findRange(this.tech_.buffered(), currentTime); 56943 56944 if (!(this.masterPlaylistLoader_ && this.masterPlaylistLoader_.media())) { 56945 // return immediately if the metadata is not ready yet 56946 return 0; 56947 } // it's clearly an edge-case but don't thrown an error if asked to 56948 // seek within an empty playlist 56949 56950 56951 if (!this.masterPlaylistLoader_.media().segments) { 56952 return 0; 56953 } // In flash playback, the segment loaders should be reset on every seek, even 56954 // in buffer seeks. If the seek location is already buffered, continue buffering as 56955 // usual 56956 // TODO: redo this comment 56957 56958 56959 if (buffered && buffered.length) { 56960 return currentTime; 56961 } // cancel outstanding requests so we begin buffering at the new 56962 // location 56963 56964 56965 this.mainSegmentLoader_.resetEverything(); 56966 this.mainSegmentLoader_.abort(); 56967 56968 if (this.mediaTypes_.AUDIO.activePlaylistLoader) { 56969 this.audioSegmentLoader_.resetEverything(); 56970 this.audioSegmentLoader_.abort(); 56971 } 56972 56973 if (this.mediaTypes_.SUBTITLES.activePlaylistLoader) { 56974 this.subtitleSegmentLoader_.resetEverything(); 56975 this.subtitleSegmentLoader_.abort(); 56976 } // start segment loader loading in case they are paused 56977 56978 56979 this.load(); 56980 } 56981 /** 56982 * get the current duration 56983 * 56984 * @return {TimeRange} the duration 56985 */ 56986 56987 }, { 56988 key: 'duration', 56989 value: function duration$$1() { 56990 if (!this.masterPlaylistLoader_) { 56991 return 0; 56992 } 56993 56994 if (this.mediaSource) { 56995 return this.mediaSource.duration; 56996 } 56997 56998 return Hls.Playlist.duration(this.masterPlaylistLoader_.media()); 56999 } 57000 /** 57001 * check the seekable range 57002 * 57003 * @return {TimeRange} the seekable range 57004 */ 57005 57006 }, { 57007 key: 'seekable', 57008 value: function seekable$$1() { 57009 return this.seekable_; 57010 } 57011 }, { 57012 key: 'onSyncInfoUpdate_', 57013 value: function onSyncInfoUpdate_() { 57014 var mainSeekable = void 0; 57015 var audioSeekable = void 0; 57016 57017 if (!this.masterPlaylistLoader_) { 57018 return; 57019 } 57020 57021 var media = this.masterPlaylistLoader_.media(); 57022 57023 if (!media) { 57024 return; 57025 } 57026 57027 var expired = this.syncController_.getExpiredTime(media, this.mediaSource.duration); 57028 57029 if (expired === null) { 57030 // not enough information to update seekable 57031 return; 57032 } 57033 57034 mainSeekable = Hls.Playlist.seekable(media, expired); 57035 57036 if (mainSeekable.length === 0) { 57037 return; 57038 } 57039 57040 if (this.mediaTypes_.AUDIO.activePlaylistLoader) { 57041 media = this.mediaTypes_.AUDIO.activePlaylistLoader.media(); 57042 expired = this.syncController_.getExpiredTime(media, this.mediaSource.duration); 57043 57044 if (expired === null) { 57045 return; 57046 } 57047 57048 audioSeekable = Hls.Playlist.seekable(media, expired); 57049 57050 if (audioSeekable.length === 0) { 57051 return; 57052 } 57053 } 57054 57055 var oldEnd = void 0; 57056 var oldStart = void 0; 57057 57058 if (this.seekable_ && this.seekable_.length) { 57059 oldEnd = this.seekable_.end(0); 57060 oldStart = this.seekable_.start(0); 57061 } 57062 57063 if (!audioSeekable) { 57064 // seekable has been calculated based on buffering video data so it 57065 // can be returned directly 57066 this.seekable_ = mainSeekable; 57067 } else if (audioSeekable.start(0) > mainSeekable.end(0) || mainSeekable.start(0) > audioSeekable.end(0)) { 57068 // seekables are pretty far off, rely on main 57069 this.seekable_ = mainSeekable; 57070 } else { 57071 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)]]); 57072 } // seekable is the same as last time 57073 57074 57075 if (this.seekable_ && this.seekable_.length) { 57076 if (this.seekable_.end(0) === oldEnd && this.seekable_.start(0) === oldStart) { 57077 return; 57078 } 57079 } 57080 57081 this.logger_('seekable updated [' + printableRange(this.seekable_) + ']'); 57082 this.tech_.trigger('seekablechanged'); 57083 } 57084 /** 57085 * Update the player duration 57086 */ 57087 57088 }, { 57089 key: 'updateDuration', 57090 value: function updateDuration() { 57091 var _this6 = this; 57092 57093 var oldDuration = this.mediaSource.duration; 57094 var newDuration = Hls.Playlist.duration(this.masterPlaylistLoader_.media()); 57095 var buffered = this.tech_.buffered(); 57096 57097 var setDuration = function setDuration() { 57098 // on firefox setting the duration may sometimes cause an exception 57099 // even if the media source is open and source buffers are not 57100 // updating, something about the media source being in an invalid state. 57101 _this6.logger_('Setting duration from ' + _this6.mediaSource.duration + ' => ' + newDuration); 57102 57103 try { 57104 _this6.mediaSource.duration = newDuration; 57105 } catch (e) { 57106 videojs$1.log.warn('Failed to set media source duration', e); 57107 } 57108 57109 _this6.tech_.trigger('durationchange'); 57110 57111 _this6.mediaSource.removeEventListener('sourceopen', setDuration); 57112 }; 57113 57114 if (buffered.length > 0) { 57115 newDuration = Math.max(newDuration, buffered.end(buffered.length - 1)); 57116 } // if the duration has changed, invalidate the cached value 57117 57118 57119 if (oldDuration !== newDuration) { 57120 // update the duration 57121 if (this.mediaSource.readyState !== 'open') { 57122 this.mediaSource.addEventListener('sourceopen', setDuration); 57123 } else { 57124 setDuration(); 57125 } 57126 } 57127 } 57128 /** 57129 * dispose of the MasterPlaylistController and everything 57130 * that it controls 57131 */ 57132 57133 }, { 57134 key: 'dispose', 57135 value: function dispose() { 57136 var _this7 = this; 57137 57138 this.decrypter_.terminate(); 57139 this.masterPlaylistLoader_.dispose(); 57140 this.mainSegmentLoader_.dispose(); 57141 ['AUDIO', 'SUBTITLES'].forEach(function (type) { 57142 var groups = _this7.mediaTypes_[type].groups; 57143 57144 for (var id in groups) { 57145 groups[id].forEach(function (group) { 57146 if (group.playlistLoader) { 57147 group.playlistLoader.dispose(); 57148 } 57149 }); 57150 } 57151 }); 57152 this.audioSegmentLoader_.dispose(); 57153 this.subtitleSegmentLoader_.dispose(); 57154 } 57155 /** 57156 * return the master playlist object if we have one 57157 * 57158 * @return {Object} the master playlist object that we parsed 57159 */ 57160 57161 }, { 57162 key: 'master', 57163 value: function master() { 57164 return this.masterPlaylistLoader_.master; 57165 } 57166 /** 57167 * return the currently selected playlist 57168 * 57169 * @return {Object} the currently selected playlist object that we parsed 57170 */ 57171 57172 }, { 57173 key: 'media', 57174 value: function media() { 57175 // playlist loader will not return media if it has not been fully loaded 57176 return this.masterPlaylistLoader_.media() || this.initialMedia_; 57177 } 57178 /** 57179 * setup our internal source buffers on our segment Loaders 57180 * 57181 * @private 57182 */ 57183 57184 }, { 57185 key: 'setupSourceBuffers_', 57186 value: function setupSourceBuffers_() { 57187 var media = this.masterPlaylistLoader_.media(); 57188 var mimeTypes = void 0; // wait until a media playlist is available and the Media Source is 57189 // attached 57190 57191 if (!media || this.mediaSource.readyState !== 'open') { 57192 return; 57193 } 57194 57195 mimeTypes = mimeTypesForPlaylist(this.masterPlaylistLoader_.master, media); 57196 57197 if (mimeTypes.length < 1) { 57198 this.error = 'No compatible SourceBuffer configuration for the variant stream:' + media.resolvedUri; 57199 return this.mediaSource.endOfStream('decode'); 57200 } 57201 57202 this.configureLoaderMimeTypes_(mimeTypes); // exclude any incompatible variant streams from future playlist 57203 // selection 57204 57205 this.excludeIncompatibleVariants_(media); 57206 } 57207 }, { 57208 key: 'configureLoaderMimeTypes_', 57209 value: function configureLoaderMimeTypes_(mimeTypes) { 57210 // If the content is demuxed, we can't start appending segments to a source buffer 57211 // until both source buffers are set up, or else the browser may not let us add the 57212 // second source buffer (it will assume we are playing either audio only or video 57213 // only). 57214 var sourceBufferEmitter = // If there is more than one mime type 57215 mimeTypes.length > 1 && // and the first mime type does not have muxed video and audio 57216 mimeTypes[0].indexOf(',') === -1 && // and the two mime types are different (they can be the same in the case of audio 57217 // only with alternate audio) 57218 mimeTypes[0] !== mimeTypes[1] ? // then we want to wait on the second source buffer 57219 new videojs$1.EventTarget() : // otherwise there is no need to wait as the content is either audio only, 57220 // video only, or muxed content. 57221 null; 57222 this.mainSegmentLoader_.mimeType(mimeTypes[0], sourceBufferEmitter); 57223 57224 if (mimeTypes[1]) { 57225 this.audioSegmentLoader_.mimeType(mimeTypes[1], sourceBufferEmitter); 57226 } 57227 } 57228 /** 57229 * Blacklists playlists with codecs that are unsupported by the browser. 57230 */ 57231 57232 }, { 57233 key: 'excludeUnsupportedVariants_', 57234 value: function excludeUnsupportedVariants_() {
57235 this.master().playlists.forEach(function (variant) { 57236 if (variant.attributes.CODECS && window$1.MediaSource && window$1.MediaSource.isTypeSupported && !window$1.MediaSource.isTypeSupported('video/mp4; codecs="' + mapLegacyAvcCodecs(variant.attributes.CODECS) + '"')) { 57237 variant.excludeUntil = Infinity; 57238 } 57239 }); 57240 } 57241 /** 57242 * Blacklist playlists that are known to be codec or 57243 * stream-incompatible with the SourceBuffer configuration. For 57244 * instance, Media Source Extensions would cause the video element to 57245 * stall waiting for video data if you switched from a variant with 57246 * video and audio to an audio-only one. 57247 * 57248 * @param {Object} media a media playlist compatible with the current 57249 * set of SourceBuffers. Variants in the current master playlist that 57250 * do not appear to have compatible codec or stream configurations 57251 * will be excluded from the default playlist selection algorithm 57252 * indefinitely. 57253 * @private 57254 */ 57255 57256 }, { 57257 key: 'excludeIncompatibleVariants_', 57258 value: function excludeIncompatibleVariants_(media) { 57259 var codecCount = 2; 57260 var videoCodec = null; 57261 var codecs = void 0; 57262 57263 if (media.attributes.CODECS) { 57264 codecs = parseCodecs(media.attributes.CODECS); 57265 videoCodec = codecs.videoCodec; 57266 codecCount = codecs.codecCount; 57267 } 57268
vendor: 32,178 bytes, lines 57269-58192
57269 this.master().playlists.forEach(function (variant) { 57270 var variantCodecs = { 57271 codecCount: 2, 57272 videoCodec: null 57273 }; 57274 57275 if (variant.attributes.CODECS) { 57276 variantCodecs = parseCodecs(variant.attributes.CODECS); 57277 } // if the streams differ in the presence or absence of audio or 57278 // video, they are incompatible 57279 57280 57281 if (variantCodecs.codecCount !== codecCount) { 57282 variant.excludeUntil = Infinity; 57283 } // if h.264 is specified on the current playlist, some flavor of 57284 // it must be specified on all compatible variants 57285 57286 57287 if (variantCodecs.videoCodec !== videoCodec) { 57288 variant.excludeUntil = Infinity; 57289 } 57290 }); 57291 } 57292 }, { 57293 key: 'updateAdCues_', 57294 value: function updateAdCues_(media) { 57295 var offset = 0; 57296 var seekable$$1 = this.seekable(); 57297 57298 if (seekable$$1.length) { 57299 offset = seekable$$1.start(0); 57300 } 57301 57302 updateAdCues(media, this.cueTagsTrack_, offset); 57303 } 57304 /** 57305 * Calculates the desired forward buffer length based on current time 57306 * 57307 * @return {Number} Desired forward buffer length in seconds 57308 */ 57309 57310 }, { 57311 key: 'goalBufferLength', 57312 value: function goalBufferLength() { 57313 var currentTime = this.tech_.currentTime(); 57314 var initial = Config.GOAL_BUFFER_LENGTH; 57315 var rate = Config.GOAL_BUFFER_LENGTH_RATE; 57316 var max = Math.max(initial, Config.MAX_GOAL_BUFFER_LENGTH); 57317 return Math.min(initial + currentTime * rate, max); 57318 } 57319 /** 57320 * Calculates the desired buffer low water line based on current time 57321 * 57322 * @return {Number} Desired buffer low water line in seconds 57323 */ 57324 57325 }, { 57326 key: 'bufferLowWaterLine', 57327 value: function bufferLowWaterLine() { 57328 var currentTime = this.tech_.currentTime(); 57329 var initial = Config.BUFFER_LOW_WATER_LINE; 57330 var rate = Config.BUFFER_LOW_WATER_LINE_RATE; 57331 var max = Math.max(initial, Config.MAX_BUFFER_LOW_WATER_LINE); 57332 return Math.min(initial + currentTime * rate, max); 57333 } 57334 }]); 57335 return MasterPlaylistController; 57336 }(videojs$1.EventTarget); 57337 /** 57338 * Returns a function that acts as the Enable/disable playlist function. 57339 * 57340 * @param {PlaylistLoader} loader - The master playlist loader 57341 * @param {String} playlistUri - uri of the playlist 57342 * @param {Function} changePlaylistFn - A function to be called after a 57343 * playlist's enabled-state has been changed. Will NOT be called if a 57344 * playlist's enabled-state is unchanged 57345 * @param {Boolean=} enable - Value to set the playlist enabled-state to 57346 * or if undefined returns the current enabled-state for the playlist 57347 * @return {Function} Function for setting/getting enabled 57348 */ 57349 57350 57351 var enableFunction = function enableFunction(loader, playlistUri, changePlaylistFn) { 57352 return function (enable) { 57353 var playlist = loader.master.playlists[playlistUri]; 57354 var incompatible = isIncompatible(playlist); 57355 var currentlyEnabled = isEnabled(playlist); 57356 57357 if (typeof enable === 'undefined') { 57358 return currentlyEnabled; 57359 } 57360 57361 if (enable) { 57362 delete playlist.disabled; 57363 } else { 57364 playlist.disabled = true; 57365 } 57366 57367 if (enable !== currentlyEnabled && !incompatible) { 57368 // Ensure the outside world knows about our changes 57369 changePlaylistFn(); 57370 57371 if (enable) { 57372 loader.trigger('renditionenabled'); 57373 } else { 57374 loader.trigger('renditiondisabled'); 57375 } 57376 } 57377 57378 return enable; 57379 }; 57380 }; 57381 /** 57382 * The representation object encapsulates the publicly visible information 57383 * in a media playlist along with a setter/getter-type function (enabled) 57384 * for changing the enabled-state of a particular playlist entry 57385 * 57386 * @class Representation 57387 */ 57388 57389 57390 var Representation = function Representation(hlsHandler, playlist, id) { 57391 classCallCheck$1(this, Representation); 57392 var mpc = hlsHandler.masterPlaylistController_, 57393 smoothQualityChange = hlsHandler.options_.smoothQualityChange; // Get a reference to a bound version of the quality change function 57394 57395 var changeType = smoothQualityChange ? 'smooth' : 'fast'; 57396 var qualityChangeFunction = mpc[changeType + 'QualityChange_'].bind(mpc); // some playlist attributes are optional 57397 57398 if (playlist.attributes.RESOLUTION) { 57399 var resolution = playlist.attributes.RESOLUTION; 57400 this.width = resolution.width; 57401 this.height = resolution.height; 57402 } 57403 57404 this.bandwidth = playlist.attributes.BANDWIDTH; // The id is simply the ordinality of the media playlist 57405 // within the master playlist 57406 57407 this.id = id; // Partially-apply the enableFunction to create a playlist- 57408 // specific variant 57409 57410 this.enabled = enableFunction(hlsHandler.playlists, playlist.uri, qualityChangeFunction); 57411 }; 57412 /** 57413 * A mixin function that adds the `representations` api to an instance 57414 * of the HlsHandler class 57415 * @param {HlsHandler} hlsHandler - An instance of HlsHandler to add the 57416 * representation API into 57417 */ 57418 57419 57420 var renditionSelectionMixin = function renditionSelectionMixin(hlsHandler) { 57421 var playlists = hlsHandler.playlists; // Add a single API-specific function to the HlsHandler instance 57422 57423 hlsHandler.representations = function () { 57424 return playlists.master.playlists.filter(function (media) { 57425 return !isIncompatible(media); 57426 }).map(function (e, i) { 57427 return new Representation(hlsHandler, e, e.uri); 57428 }); 57429 }; 57430 }; 57431 /** 57432 * @file playback-watcher.js 57433 * 57434 * Playback starts, and now my watch begins. It shall not end until my death. I shall 57435 * take no wait, hold no uncleared timeouts, father no bad seeks. I shall wear no crowns 57436 * and win no glory. I shall live and die at my post. I am the corrector of the underflow. 57437 * I am the watcher of gaps. I am the shield that guards the realms of seekable. I pledge 57438 * my life and honor to the Playback Watch, for this Player and all the Players to come. 57439 */ 57440 // Set of events that reset the playback-watcher time check logic and clear the timeout 57441 57442 57443 var timerCancelEvents = ['seeking', 'seeked', 'pause', 'playing', 'error']; 57444 /** 57445 * @class PlaybackWatcher 57446 */ 57447 57448 var PlaybackWatcher = function () { 57449 /** 57450 * Represents an PlaybackWatcher object. 57451 * @constructor 57452 * @param {object} options an object that includes the tech and settings 57453 */ 57454 function PlaybackWatcher(options) { 57455 var _this = this; 57456 57457 classCallCheck$1(this, PlaybackWatcher); 57458 this.tech_ = options.tech; 57459 this.seekable = options.seekable; 57460 this.seekTo = options.seekTo; 57461 this.allowSeeksWithinUnsafeLiveWindow = options.allowSeeksWithinUnsafeLiveWindow; 57462 this.media = options.media; 57463 this.consecutiveUpdates = 0; 57464 this.lastRecordedTime = null; 57465 this.timer_ = null; 57466 this.checkCurrentTimeTimeout_ = null; 57467 this.logger_ = logger('PlaybackWatcher'); 57468 this.logger_('initialize'); 57469 57470 var canPlayHandler = function canPlayHandler() { 57471 return _this.monitorCurrentTime_(); 57472 }; 57473 57474 var waitingHandler = function waitingHandler() { 57475 return _this.techWaiting_(); 57476 }; 57477 57478 var cancelTimerHandler = function cancelTimerHandler() { 57479 return _this.cancelTimer_(); 57480 }; 57481 57482 var fixesBadSeeksHandler = function fixesBadSeeksHandler() { 57483 return _this.fixesBadSeeks_(); 57484 }; 57485 57486 this.tech_.on('seekablechanged', fixesBadSeeksHandler); 57487 this.tech_.on('waiting', waitingHandler); 57488 this.tech_.on(timerCancelEvents, cancelTimerHandler); 57489 this.tech_.on('canplay', canPlayHandler); // Define the dispose function to clean up our events 57490 57491 this.dispose = function () { 57492 _this.logger_('dispose'); 57493 57494 _this.tech_.off('seekablechanged', fixesBadSeeksHandler); 57495 57496 _this.tech_.off('waiting', waitingHandler); 57497 57498 _this.tech_.off(timerCancelEvents, cancelTimerHandler); 57499 57500 _this.tech_.off('canplay', canPlayHandler); 57501 57502 if (_this.checkCurrentTimeTimeout_) { 57503 window$1.clearTimeout(_this.checkCurrentTimeTimeout_); 57504 } 57505 57506 _this.cancelTimer_(); 57507 }; 57508 } 57509 /** 57510 * Periodically check current time to see if playback stopped 57511 * 57512 * @private 57513 */ 57514 57515 57516 createClass$1(PlaybackWatcher, [{ 57517 key: 'monitorCurrentTime_', 57518 value: function monitorCurrentTime_() { 57519 this.checkCurrentTime_(); 57520 57521 if (this.checkCurrentTimeTimeout_) { 57522 window$1.clearTimeout(this.checkCurrentTimeTimeout_); 57523 } // 42 = 24 fps // 250 is what Webkit uses // FF uses 15 57524 57525 57526 this.checkCurrentTimeTimeout_ = window$1.setTimeout(this.monitorCurrentTime_.bind(this), 250); 57527 } 57528 /** 57529 * The purpose of this function is to emulate the "waiting" event on 57530 * browsers that do not emit it when they are waiting for more 57531 * data to continue playback 57532 * 57533 * @private 57534 */ 57535 57536 }, { 57537 key: 'checkCurrentTime_', 57538 value: function checkCurrentTime_() { 57539 if (this.tech_.seeking() && this.fixesBadSeeks_()) { 57540 this.consecutiveUpdates = 0; 57541 this.lastRecordedTime = this.tech_.currentTime(); 57542 return; 57543 } 57544 57545 if (this.tech_.paused() || this.tech_.seeking()) { 57546 return; 57547 } 57548 57549 var currentTime = this.tech_.currentTime(); 57550 var buffered = this.tech_.buffered(); 57551 57552 if (this.lastRecordedTime === currentTime && (!buffered.length || currentTime + SAFE_TIME_DELTA >= buffered.end(buffered.length - 1))) { 57553 // If current time is at the end of the final buffered region, then any playback 57554 // stall is most likely caused by buffering in a low bandwidth environment. The tech 57555 // should fire a `waiting` event in this scenario, but due to browser and tech 57556 // inconsistencies. Calling `techWaiting_` here allows us to simulate 57557 // responding to a native `waiting` event when the tech fails to emit one. 57558 return this.techWaiting_(); 57559 } 57560 57561 if (this.consecutiveUpdates >= 5 && currentTime === this.lastRecordedTime) { 57562 this.consecutiveUpdates++; 57563 this.waiting_(); 57564 } else if (currentTime === this.lastRecordedTime) { 57565 this.consecutiveUpdates++; 57566 } else { 57567 this.consecutiveUpdates = 0; 57568 this.lastRecordedTime = currentTime; 57569 } 57570 } 57571 /** 57572 * Cancels any pending timers and resets the 'timeupdate' mechanism 57573 * designed to detect that we are stalled 57574 * 57575 * @private 57576 */ 57577 57578 }, { 57579 key: 'cancelTimer_', 57580 value: function cancelTimer_() { 57581 this.consecutiveUpdates = 0; 57582 57583 if (this.timer_) { 57584 this.logger_('cancelTimer_'); 57585 clearTimeout(this.timer_); 57586 } 57587 57588 this.timer_ = null; 57589 } 57590 /** 57591 * Fixes situations where there's a bad seek 57592 * 57593 * @return {Boolean} whether an action was taken to fix the seek 57594 * @private 57595 */ 57596 57597 }, { 57598 key: 'fixesBadSeeks_', 57599 value: function fixesBadSeeks_() { 57600 var seeking = this.tech_.seeking(); 57601 57602 if (!seeking) { 57603 return false; 57604 } 57605 57606 var seekable = this.seekable(); 57607 var currentTime = this.tech_.currentTime(); 57608 var isAfterSeekableRange = this.afterSeekableWindow_(seekable, currentTime, this.media(), this.allowSeeksWithinUnsafeLiveWindow); 57609 var seekTo = void 0; 57610 57611 if (isAfterSeekableRange) { 57612 var seekableEnd = seekable.end(seekable.length - 1); // sync to live point (if VOD, our seekable was updated and we're simply adjusting) 57613 57614 seekTo = seekableEnd; 57615 } 57616 57617 if (this.beforeSeekableWindow_(seekable, currentTime)) { 57618 var seekableStart = seekable.start(0); // sync to the beginning of the live window 57619 // provide a buffer of .1 seconds to handle rounding/imprecise numbers 57620 57621 seekTo = seekableStart + SAFE_TIME_DELTA; 57622 } 57623 57624 if (typeof seekTo !== 'undefined') { 57625 this.logger_('Trying to seek outside of seekable at time ' + currentTime + ' with ' + ('seekable range ' + printableRange(seekable) + '. Seeking to ') + (seekTo + '.')); 57626 this.seekTo(seekTo); 57627 return true; 57628 } 57629 57630 return false; 57631 } 57632 /** 57633 * Handler for situations when we determine the player is waiting. 57634 * 57635 * @private 57636 */ 57637 57638 }, { 57639 key: 'waiting_', 57640 value: function waiting_() { 57641 if (this.techWaiting_()) { 57642 return; 57643 } // All tech waiting checks failed. Use last resort correction 57644 57645 57646 var currentTime = this.tech_.currentTime(); 57647 var buffered = this.tech_.buffered(); 57648 var currentRange = findRange(buffered, currentTime); // Sometimes the player can stall for unknown reasons within a contiguous buffered 57649 // region with no indication that anything is amiss (seen in Firefox). Seeking to 57650 // currentTime is usually enough to kickstart the player. This checks that the player 57651 // is currently within a buffered region before attempting a corrective seek. 57652 // Chrome does not appear to continue `timeupdate` events after a `waiting` event 57653 // until there is ~ 3 seconds of forward buffer available. PlaybackWatcher should also 57654 // make sure there is ~3 seconds of forward buffer before taking any corrective action 57655 // to avoid triggering an `unknownwaiting` event when the network is slow. 57656 57657 if (currentRange.length && currentTime + 3 <= currentRange.end(0)) { 57658 this.cancelTimer_(); 57659 this.seekTo(currentTime); 57660 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 57661 57662 this.tech_.trigger({ 57663 type: 'usage', 57664 name: 'hls-unknown-waiting' 57665 }); 57666 return; 57667 } 57668 } 57669 /** 57670 * Handler for situations when the tech fires a `waiting` event 57671 * 57672 * @return {Boolean} 57673 * True if an action (or none) was needed to correct the waiting. False if no 57674 * checks passed 57675 * @private 57676 */ 57677 57678 }, { 57679 key: 'techWaiting_', 57680 value: function techWaiting_() { 57681 var seekable = this.seekable(); 57682 var currentTime = this.tech_.currentTime(); 57683 57684 if (this.tech_.seeking() && this.fixesBadSeeks_()) { 57685 // Tech is seeking or bad seek fixed, no action needed 57686 return true; 57687 } 57688 57689 if (this.tech_.seeking() || this.timer_ !== null) { 57690 // Tech is seeking or already waiting on another action, no action needed 57691 return true; 57692 } 57693 57694 if (this.beforeSeekableWindow_(seekable, currentTime)) { 57695 var livePoint = seekable.end(seekable.length - 1); 57696 this.logger_('Fell out of live window at time ' + currentTime + '. Seeking to ' + ('live point (seekable end) ' + livePoint)); 57697 this.cancelTimer_(); 57698 this.seekTo(livePoint); // live window resyncs may be useful for monitoring QoS 57699 57700 this.tech_.trigger({ 57701 type: 'usage', 57702 name: 'hls-live-resync' 57703 }); 57704 return true; 57705 } 57706 57707 var buffered = this.tech_.buffered(); 57708 var nextRange = findNextRange(buffered, currentTime); 57709 57710 if (this.videoUnderflow_(nextRange, buffered, currentTime)) { 57711 // Even though the video underflowed and was stuck in a gap, the audio overplayed 57712 // the gap, leading currentTime into a buffered range. Seeking to currentTime 57713 // allows the video to catch up to the audio position without losing any audio 57714 // (only suffering ~3 seconds of frozen video and a pause in audio playback). 57715 this.cancelTimer_(); 57716 this.seekTo(currentTime); // video underflow may be useful for monitoring QoS 57717 57718 this.tech_.trigger({ 57719 type: 'usage', 57720 name: 'hls-video-underflow' 57721 }); 57722 return true; 57723 } // check for gap 57724 57725 57726 if (nextRange.length > 0) { 57727 var difference = nextRange.start(0) - currentTime; 57728 this.logger_('Stopped at ' + currentTime + ', setting timer for ' + difference + ', seeking ' + ('to ' + nextRange.start(0))); 57729 this.timer_ = setTimeout(this.skipTheGap_.bind(this), difference * 1000, currentTime); 57730 return true; 57731 } // All checks failed. Returning false to indicate failure to correct waiting 57732 57733 57734 return false; 57735 } 57736 }, { 57737 key: 'afterSeekableWindow_', 57738 value: function afterSeekableWindow_(seekable, currentTime, playlist) { 57739 var allowSeeksWithinUnsafeLiveWindow = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false; 57740 57741 if (!seekable.length) { 57742 // we can't make a solid case if there's no seekable, default to false 57743 return false; 57744 } 57745 57746 var allowedEnd = seekable.end(seekable.length - 1) + SAFE_TIME_DELTA; 57747 var isLive = !playlist.endList; 57748 57749 if (isLive && allowSeeksWithinUnsafeLiveWindow) { 57750 allowedEnd = seekable.end(seekable.length - 1) + playlist.targetDuration * 3; 57751 } 57752 57753 if (currentTime > allowedEnd) { 57754 return true; 57755 } 57756 57757 return false; 57758 } 57759 }, { 57760 key: 'beforeSeekableWindow_', 57761 value: function beforeSeekableWindow_(seekable, currentTime) { 57762 if (seekable.length && // can't fall before 0 and 0 seekable start identifies VOD stream 57763 seekable.start(0) > 0 && currentTime < seekable.start(0) - SAFE_TIME_DELTA) { 57764 return true; 57765 } 57766 57767 return false; 57768 } 57769 }, { 57770 key: 'videoUnderflow_', 57771 value: function videoUnderflow_(nextRange, buffered, currentTime) { 57772 if (nextRange.length === 0) { 57773 // Even if there is no available next range, there is still a possibility we are 57774 // stuck in a gap due to video underflow. 57775 var gap = this.gapFromVideoUnderflow_(buffered, currentTime); 57776 57777 if (gap) { 57778 this.logger_('Encountered a gap in video from ' + gap.start + ' to ' + gap.end + '. ' + ('Seeking to current time ' + currentTime)); 57779 return true; 57780 } 57781 } 57782 57783 return false; 57784 } 57785 /** 57786 * Timer callback. If playback still has not proceeded, then we seek 57787 * to the start of the next buffered region. 57788 * 57789 * @private 57790 */ 57791 57792 }, { 57793 key: 'skipTheGap_', 57794 value: function skipTheGap_(scheduledCurrentTime) { 57795 var buffered = this.tech_.buffered(); 57796 var currentTime = this.tech_.currentTime(); 57797 var nextRange = findNextRange(buffered, currentTime); 57798 this.cancelTimer_(); 57799 57800 if (nextRange.length === 0 || currentTime !== scheduledCurrentTime) { 57801 return; 57802 } 57803 57804 this.logger_('skipTheGap_:', 'currentTime:', currentTime, 'scheduled currentTime:', scheduledCurrentTime, 'nextRange start:', nextRange.start(0)); // only seek if we still have not played 57805 57806 this.seekTo(nextRange.start(0) + TIME_FUDGE_FACTOR); 57807 this.tech_.trigger({ 57808 type: 'usage', 57809 name: 'hls-gap-skip' 57810 }); 57811 } 57812 }, { 57813 key: 'gapFromVideoUnderflow_', 57814 value: function gapFromVideoUnderflow_(buffered, currentTime) { 57815 // At least in Chrome, if there is a gap in the video buffer, the audio will continue 57816 // playing for ~3 seconds after the video gap starts. This is done to account for 57817 // video buffer underflow/underrun (note that this is not done when there is audio 57818 // buffer underflow/underrun -- in that case the video will stop as soon as it 57819 // encounters the gap, as audio stalls are more noticeable/jarring to a user than 57820 // video stalls). The player's time will reflect the playthrough of audio, so the 57821 // time will appear as if we are in a buffered region, even if we are stuck in a 57822 // "gap." 57823 // 57824 // Example: 57825 // video buffer: 0 => 10.1, 10.2 => 20 57826 // audio buffer: 0 => 20 57827 // overall buffer: 0 => 10.1, 10.2 => 20 57828 // current time: 13 57829 // 57830 // Chrome's video froze at 10 seconds, where the video buffer encountered the gap, 57831 // however, the audio continued playing until it reached ~3 seconds past the gap 57832 // (13 seconds), at which point it stops as well. Since current time is past the 57833 // gap, findNextRange will return no ranges. 57834 // 57835 // To check for this issue, we see if there is a gap that starts somewhere within 57836 // a 3 second range (3 seconds +/- 1 second) back from our current time. 57837 var gaps = findGaps(buffered); 57838 57839 for (var i = 0; i < gaps.length; i++) { 57840 var start = gaps.start(i); 57841 var end = gaps.end(i); // gap is starts no more than 4 seconds back 57842 57843 if (currentTime - start < 4 && currentTime - start > 2) { 57844 return { 57845 start: start, 57846 end: end 57847 }; 57848 } 57849 } 57850 57851 return null; 57852 } 57853 }]); 57854 return PlaybackWatcher; 57855 }(); 57856 57857 var defaultOptions = { 57858 errorInterval: 30, 57859 getSource: function getSource(next) { 57860 var tech = this.tech({ 57861 IWillNotUseThisInPlugins: true 57862 }); 57863 var sourceObj = tech.currentSource_; 57864 return next(sourceObj); 57865 } 57866 }; 57867 /** 57868 * Main entry point for the plugin 57869 * 57870 * @param {Player} player a reference to a videojs Player instance 57871 * @param {Object} [options] an object with plugin options 57872 * @private 57873 */ 57874 57875 var initPlugin = function initPlugin(player, options) { 57876 var lastCalled = 0; 57877 var seekTo = 0; 57878 var localOptions = videojs$1.mergeOptions(defaultOptions, options); 57879 player.ready(function () { 57880 player.trigger({ 57881 type: 'usage', 57882 name: 'hls-error-reload-initialized' 57883 }); 57884 }); 57885 /** 57886 * Player modifications to perform that must wait until `loadedmetadata` 57887 * has been triggered 57888 * 57889 * @private 57890 */ 57891 57892 var loadedMetadataHandler = function loadedMetadataHandler() { 57893 if (seekTo) { 57894 player.currentTime(seekTo); 57895 } 57896 }; 57897 /** 57898 * Set the source on the player element, play, and seek if necessary 57899 * 57900 * @param {Object} sourceObj An object specifying the source url and mime-type to play 57901 * @private 57902 */ 57903 57904 57905 var setSource = function setSource(sourceObj) { 57906 if (sourceObj === null || sourceObj === undefined) { 57907 return; 57908 } 57909 57910 seekTo = player.duration() !== Infinity && player.currentTime() || 0; 57911 player.one('loadedmetadata', loadedMetadataHandler); 57912 player.src(sourceObj); 57913 player.trigger({ 57914 type: 'usage', 57915 name: 'hls-error-reload' 57916 }); 57917 player.play(); 57918 }; 57919 /** 57920 * Attempt to get a source from either the built-in getSource function 57921 * or a custom function provided via the options 57922 * 57923 * @private 57924 */ 57925 57926 57927 var errorHandler = function errorHandler() { 57928 // Do not attempt to reload the source if a source-reload occurred before 57929 // 'errorInterval' time has elapsed since the last source-reload 57930 if (Date.now() - lastCalled < localOptions.errorInterval * 1000) { 57931 player.trigger({ 57932 type: 'usage', 57933 name: 'hls-error-reload-canceled' 57934 }); 57935 return; 57936 } 57937 57938 if (!localOptions.getSource || typeof localOptions.getSource !== 'function') { 57939 videojs$1.log.error('ERROR: reloadSourceOnError - The option getSource must be a function!'); 57940 return; 57941 } 57942 57943 lastCalled = Date.now(); 57944 return localOptions.getSource.call(player, setSource); 57945 }; 57946 /** 57947 * Unbind any event handlers that were bound by the plugin 57948 * 57949 * @private 57950 */ 57951 57952 57953 var cleanupEvents = function cleanupEvents() { 57954 player.off('loadedmetadata', loadedMetadataHandler); 57955 player.off('error', errorHandler); 57956 player.off('dispose', cleanupEvents); 57957 }; 57958 /** 57959 * Cleanup before re-initializing the plugin 57960 * 57961 * @param {Object} [newOptions] an object with plugin options 57962 * @private 57963 */ 57964 57965 57966 var reinitPlugin = function reinitPlugin(newOptions) { 57967 cleanupEvents(); 57968 initPlugin(player, newOptions); 57969 }; 57970 57971 player.on('error', errorHandler); 57972 player.on('dispose', cleanupEvents); // Overwrite the plugin function so that we can correctly cleanup before 57973 // initializing the plugin 57974 57975 player.reloadSourceOnError = reinitPlugin; 57976 }; 57977 /** 57978 * Reload the source when an error is detected as long as there 57979 * wasn't an error previously within the last 30 seconds 57980 * 57981 * @param {Object} [options] an object with plugin options 57982 */ 57983 57984 57985 var reloadSourceOnError = function reloadSourceOnError(options) { 57986 initPlugin(this, options); 57987 }; 57988 57989 var version$3 = "1.9.3"; // since VHS handles HLS and DASH (and in the future, more types), use * to capture all 57990 57991 videojs$1.use('*', function (player) { 57992 return { 57993 setSource: function setSource(srcObj, next) { 57994 // pass null as the first argument to indicate that the source is not rejected 57995 next(null, srcObj); 57996 }, 57997 // VHS needs to know when seeks happen. For external seeks (generated at the player 57998 // level), this middleware will capture the action. For internal seeks (generated at 57999 // the tech level), we use a wrapped function so that we can handle it on our own 58000 // (specified elsewhere). 58001 setCurrentTime: function setCurrentTime(time) { 58002 if (player.vhs && player.currentSource().src === player.vhs.source_.src) { 58003 player.vhs.setCurrentTime(time); 58004 } 58005 58006 return time; 58007 }, 58008 // Sync VHS after play requests. 58009 // This specifically handles replay where the order of actions is 58010 // play, video element will seek to 0 (skipping the setCurrentTime middleware) 58011 // then triggers a play event. 58012 play: function play() { 58013 if (player.vhs && player.currentSource().src === player.vhs.source_.src) { 58014 player.vhs.setCurrentTime(player.tech_.currentTime()); 58015 } 58016 } 58017 }; 58018 }); 58019 /** 58020 * @file videojs-http-streaming.js 58021 * 58022 * The main file for the HLS project. 58023 * License: https://github.com/videojs/videojs-http-streaming/blob/master/LICENSE 58024 */ 58025 58026 var Hls$1 = { 58027 PlaylistLoader: PlaylistLoader, 58028 Playlist: Playlist, 58029 Decrypter: Decrypter, 58030 AsyncStream: AsyncStream, 58031 decrypt: decrypt, 58032 utils: utils$1, 58033 STANDARD_PLAYLIST_SELECTOR: lastBandwidthSelector, 58034 INITIAL_PLAYLIST_SELECTOR: lowestBitrateCompatibleVariantSelector, 58035 comparePlaylistBandwidth: comparePlaylistBandwidth, 58036 comparePlaylistResolution: comparePlaylistResolution, 58037 xhr: xhrFactory() 58038 }; // Define getter/setters for config properites 58039 58040 ['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) { 58041 Object.defineProperty(Hls$1, prop, { 58042 get: function get$$1() { 58043 videojs$1.log.warn('using Hls.' + prop + ' is UNSAFE be sure you know what you are doing'); 58044 return Config[prop]; 58045 }, 58046 set: function set$$1(value) { 58047 videojs$1.log.warn('using Hls.' + prop + ' is UNSAFE be sure you know what you are doing'); 58048 58049 if (typeof value !== 'number' || value < 0) { 58050 videojs$1.log.warn('value of Hls.' + prop + ' must be greater than or equal to 0'); 58051 return; 58052 } 58053 58054 Config[prop] = value; 58055 } 58056 }); 58057 }); 58058 var LOCAL_STORAGE_KEY$1 = 'videojs-vhs'; 58059 58060 var simpleTypeFromSourceType = function simpleTypeFromSourceType(type) { 58061 var mpegurlRE = /^(audio|video|application)\/(x-|vnd\.apple\.)?mpegurl/i; 58062 58063 if (mpegurlRE.test(type)) { 58064 return 'hls'; 58065 } 58066 58067 var dashRE = /^application\/dash\+xml/i; 58068 58069 if (dashRE.test(type)) { 58070 return 'dash'; 58071 } 58072 58073 return null; 58074 }; 58075 /** 58076 * Updates the selectedIndex of the QualityLevelList when a mediachange happens in hls. 58077 * 58078 * @param {QualityLevelList} qualityLevels The QualityLevelList to update. 58079 * @param {PlaylistLoader} playlistLoader PlaylistLoader containing the new media info. 58080 * @function handleHlsMediaChange 58081 */ 58082 58083 58084 var handleHlsMediaChange = function handleHlsMediaChange(qualityLevels, playlistLoader) { 58085 var newPlaylist = playlistLoader.media(); 58086 var selectedIndex = -1; 58087 58088 for (var i = 0; i < qualityLevels.length; i++) { 58089 if (qualityLevels[i].id === newPlaylist.uri) { 58090 selectedIndex = i; 58091 break; 58092 } 58093 } 58094 58095 qualityLevels.selectedIndex_ = selectedIndex; 58096 qualityLevels.trigger({ 58097 selectedIndex: selectedIndex, 58098 type: 'change' 58099 }); 58100 }; 58101 /** 58102 * Adds quality levels to list once playlist metadata is available 58103 * 58104 * @param {QualityLevelList} qualityLevels The QualityLevelList to attach events to. 58105 * @param {Object} hls Hls object to listen to for media events. 58106 * @function handleHlsLoadedMetadata 58107 */ 58108 58109 58110 var handleHlsLoadedMetadata = function handleHlsLoadedMetadata(qualityLevels, hls) { 58111 hls.representations().forEach(function (rep) { 58112 qualityLevels.addQualityLevel(rep); 58113 }); 58114 handleHlsMediaChange(qualityLevels, hls.playlists); 58115 }; // HLS is a source handler, not a tech. Make sure attempts to use it 58116 // as one do not cause exceptions. 58117 58118 58119 Hls$1.canPlaySource = function () { 58120 return videojs$1.log.warn('HLS is no longer a tech. Please remove it from ' + 'your player\'s techOrder.'); 58121 }; 58122 58123 var emeKeySystems = function emeKeySystems(keySystemOptions, videoPlaylist, audioPlaylist) { 58124 if (!keySystemOptions) { 58125 return keySystemOptions; 58126 } // upsert the content types based on the selected playlist 58127 58128 58129 var keySystemContentTypes = {}; 58130 58131 for (var keySystem in keySystemOptions) { 58132 keySystemContentTypes[keySystem] = { 58133 audioContentType: 'audio/mp4; codecs="' + audioPlaylist.attributes.CODECS + '"', 58134 videoContentType: 'video/mp4; codecs="' + videoPlaylist.attributes.CODECS + '"' 58135 }; 58136 58137 if (videoPlaylist.contentProtection && videoPlaylist.contentProtection[keySystem] && videoPlaylist.contentProtection[keySystem].pssh) { 58138 keySystemContentTypes[keySystem].pssh = videoPlaylist.contentProtection[keySystem].pssh; 58139 } // videojs-contrib-eme accepts the option of specifying: 'com.some.cdm': 'url' 58140 // so we need to prevent overwriting the URL entirely 58141 58142 58143 if (typeof keySystemOptions[keySystem] === 'string') { 58144 keySystemContentTypes[keySystem].url = keySystemOptions[keySystem]; 58145 } 58146 } 58147 58148 return videojs$1.mergeOptions(keySystemOptions, keySystemContentTypes); 58149 }; 58150 58151 var setupEmeOptions = function setupEmeOptions(hlsHandler) { 58152 if (hlsHandler.options_.sourceType !== 'dash') { 58153 return; 58154 } 58155 58156 var player = videojs$1.players[hlsHandler.tech_.options_.playerId]; 58157 58158 if (player.eme) { 58159 var sourceOptions = emeKeySystems(hlsHandler.source_.keySystems, hlsHandler.playlists.media(), hlsHandler.masterPlaylistController_.mediaTypes_.AUDIO.activePlaylistLoader.media()); 58160 58161 if (sourceOptions) { 58162 player.currentSource().keySystems = sourceOptions; // works around https://bugs.chromium.org/p/chromium/issues/detail?id=895449 58163 58164 if (player.eme.initializeMediaKeys) { 58165 player.eme.initializeMediaKeys(); 58166 } 58167 } 58168 } 58169 }; 58170 58171 var getVhsLocalStorage = function getVhsLocalStorage() { 58172 if (!window.localStorage) { 58173 return null; 58174 } 58175 58176 var storedObject = window.localStorage.getItem(LOCAL_STORAGE_KEY$1); 58177 58178 if (!storedObject) { 58179 return null; 58180 } 58181 58182 try { 58183 return JSON.parse(storedObject); 58184 } catch (e) { 58185 // someone may have tampered with the value 58186 return null; 58187 } 58188 }; 58189 58190 var updateVhsLocalStorage = function updateVhsLocalStorage(options) { 58191 if (!window.localStorage) { 58192 return false;
58193 } 58194 58195 var objectToStore = getVhsLocalStorage(); 58196 objectToStore = objectToStore ? videojs$1.mergeOptions(objectToStore, options) : options; 58197 58198 try { 58199 window.localStorage.setItem(LOCAL_STORAGE_KEY$1, JSON.stringify(objectToStore)); 58200 } catch (e) { 58201 // Throws if storage is full (e.g., always on iOS 5+ Safari private mode, where 58202 // storage is set to 0). 58203 // https://developer.mozilla.org/en-US/docs/Web/API/Storage/setItem#Exceptions 58204 // No need to perform any operation. 58205 return false; 58206 } 58207 58208 return objectToStore; 58209 }; 58210 /** 58211 * Whether the browser has built-in HLS support. 58212 */ 58213 58214 58215 Hls$1.supportsNativeHls = function () { 58216 var video = document.createElement('video'); // native HLS is definitely not supported if HTML5 video isn't 58217 58218 if (!videojs$1.getTech('Html5').isSupported()) { 58219 return false; 58220 } // HLS manifests can go by many mime-types 58221 58222 58223 var canPlay = [// Apple santioned 58224 'application/vnd.apple.mpegurl', // Apple sanctioned for backwards compatibility 58225 'audio/mpegurl', // Very common 58226 'audio/x-mpegurl', // Very common
vendor: 7,654 bytes, lines 58227-58418
58227 'application/x-mpegurl', // Included for completeness 58228 'video/x-mpegurl', 'video/mpegurl', 'application/mpegurl']; 58229 return canPlay.some(function (canItPlay) { 58230 return /maybe|probably/i.test(video.canPlayType(canItPlay)); 58231 }); 58232 }(); 58233 58234 Hls$1.supportsNativeDash = function () { 58235 if (!videojs$1.getTech('Html5').isSupported()) { 58236 return false; 58237 } 58238 58239 return /maybe|probably/i.test(document.createElement('video').canPlayType('application/dash+xml')); 58240 }(); 58241 58242 Hls$1.supportsTypeNatively = function (type) { 58243 if (type === 'hls') { 58244 return Hls$1.supportsNativeHls; 58245 } 58246 58247 if (type === 'dash') { 58248 return Hls$1.supportsNativeDash; 58249 } 58250 58251 return false; 58252 }; 58253 /** 58254 * HLS is a source handler, not a tech. Make sure attempts to use it 58255 * as one do not cause exceptions. 58256 */ 58257 58258 58259 Hls$1.isSupported = function () { 58260 return videojs$1.log.warn('HLS is no longer a tech. Please remove it from ' + 'your player\'s techOrder.'); 58261 }; 58262 58263 var Component$1 = videojs$1.getComponent('Component'); 58264 /** 58265 * The Hls Handler object, where we orchestrate all of the parts 58266 * of HLS to interact with video.js 58267 * 58268 * @class HlsHandler 58269 * @extends videojs.Component 58270 * @param {Object} source the soruce object 58271 * @param {Tech} tech the parent tech object 58272 * @param {Object} options optional and required options 58273 */ 58274 58275 var HlsHandler = function (_Component) { 58276 inherits$1(HlsHandler, _Component); 58277 58278 function HlsHandler(source, tech, options) { 58279 classCallCheck$1(this, HlsHandler); // tech.player() is deprecated but setup a reference to HLS for 58280 // backwards-compatibility 58281 58282 var _this = possibleConstructorReturn$1(this, (HlsHandler.__proto__ || Object.getPrototypeOf(HlsHandler)).call(this, tech, options.hls)); 58283 58284 if (tech.options_ && tech.options_.playerId) { 58285 var _player = videojs$1(tech.options_.playerId); 58286 58287 if (!_player.hasOwnProperty('hls')) { 58288 Object.defineProperty(_player, 'hls', { 58289 get: function get$$1() { 58290 videojs$1.log.warn('player.hls is deprecated. Use player.tech().hls instead.'); 58291 tech.trigger({ 58292 type: 'usage', 58293 name: 'hls-player-access' 58294 }); 58295 return _this; 58296 }, 58297 configurable: true 58298 }); 58299 } // Set up a reference to the HlsHandler from player.vhs. This allows users to start 58300 // migrating from player.tech_.hls... to player.vhs... for API access. Although this 58301 // isn't the most appropriate form of reference for video.js (since all APIs should 58302 // be provided through core video.js), it is a common pattern for plugins, and vhs 58303 // will act accordingly. 58304 58305 58306 _player.vhs = _this; // deprecated, for backwards compatibility 58307 58308 _player.dash = _this; 58309 _this.player_ = _player; 58310 } 58311 58312 _this.tech_ = tech; 58313 _this.source_ = source; 58314 _this.stats = {}; 58315 58316 _this.setOptions_(); 58317 58318 if (_this.options_.overrideNative && tech.overrideNativeAudioTracks && tech.overrideNativeVideoTracks) { 58319 tech.overrideNativeAudioTracks(true); 58320 tech.overrideNativeVideoTracks(true); 58321 } else if (_this.options_.overrideNative && (tech.featuresNativeVideoTracks || tech.featuresNativeAudioTracks)) { 58322 // overriding native HLS only works if audio tracks have been emulated 58323 // error early if we're misconfigured 58324 throw new Error('Overriding native HLS requires emulated tracks. ' + 'See https://git.io/vMpjB'); 58325 } // listen for fullscreenchange events for this player so that we 58326 // can adjust our quality selection quickly 58327 58328 58329 _this.on(document, ['fullscreenchange', 'webkitfullscreenchange', 'mozfullscreenchange', 'MSFullscreenChange'], function (event) { 58330 var fullscreenElement = document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement; 58331 58332 if (fullscreenElement && fullscreenElement.contains(_this.tech_.el())) { 58333 _this.masterPlaylistController_.smoothQualityChange_(); 58334 } 58335 }); // Handle seeking when looping - middleware doesn't handle this seek event from the tech 58336 58337 58338 _this.on(_this.tech_, 'seeking', function () { 58339 if (this.tech_.currentTime() === 0 && this.tech_.player_.loop()) { 58340 this.setCurrentTime(0); 58341 } 58342 }); 58343 58344 _this.on(_this.tech_, 'error', function () { 58345 if (this.masterPlaylistController_) { 58346 this.masterPlaylistController_.pauseLoading(); 58347 } 58348 }); 58349 58350 _this.on(_this.tech_, 'play', _this.play); 58351 58352 return _this; 58353 } 58354 58355 createClass$1(HlsHandler, [{ 58356 key: 'setOptions_', 58357 value: function setOptions_() { 58358 var _this2 = this; // defaults 58359 58360 58361 this.options_.withCredentials = this.options_.withCredentials || false; 58362 this.options_.handleManifestRedirects = this.options_.handleManifestRedirects || false; 58363 this.options_.limitRenditionByPlayerDimensions = this.options_.limitRenditionByPlayerDimensions === false ? false : true; 58364 this.options_.smoothQualityChange = this.options_.smoothQualityChange || false; 58365 this.options_.useBandwidthFromLocalStorage = typeof this.source_.useBandwidthFromLocalStorage !== 'undefined' ? this.source_.useBandwidthFromLocalStorage : this.options_.useBandwidthFromLocalStorage || false; 58366 this.options_.customTagParsers = this.options_.customTagParsers || []; 58367 this.options_.customTagMappers = this.options_.customTagMappers || []; 58368 58369 if (typeof this.options_.blacklistDuration !== 'number') { 58370 this.options_.blacklistDuration = 5 * 60; 58371 } 58372 58373 if (typeof this.options_.bandwidth !== 'number') { 58374 if (this.options_.useBandwidthFromLocalStorage) { 58375 var storedObject = getVhsLocalStorage(); 58376 58377 if (storedObject && storedObject.bandwidth) { 58378 this.options_.bandwidth = storedObject.bandwidth; 58379 this.tech_.trigger({ 58380 type: 'usage', 58381 name: 'hls-bandwidth-from-local-storage' 58382 }); 58383 } 58384 58385 if (storedObject && storedObject.throughput) { 58386 this.options_.throughput = storedObject.throughput; 58387 this.tech_.trigger({ 58388 type: 'usage', 58389 name: 'hls-throughput-from-local-storage' 58390 }); 58391 } 58392 } 58393 } // if bandwidth was not set by options or pulled from local storage, start playlist 58394 // selection at a reasonable bandwidth 58395 58396 58397 if (typeof this.options_.bandwidth !== 'number') { 58398 this.options_.bandwidth = Config.INITIAL_BANDWIDTH; 58399 } // If the bandwidth number is unchanged from the initial setting 58400 // then this takes precedence over the enableLowInitialPlaylist option 58401 58402 58403 this.options_.enableLowInitialPlaylist = this.options_.enableLowInitialPlaylist && this.options_.bandwidth === Config.INITIAL_BANDWIDTH; // grab options passed to player.src 58404 58405 ['withCredentials', 'limitRenditionByPlayerDimensions', 'bandwidth', 'smoothQualityChange', 'customTagParsers', 'customTagMappers', 'handleManifestRedirects'].forEach(function (option) { 58406 if (typeof _this2.source_[option] !== 'undefined') { 58407 _this2.options_[option] = _this2.source_[option]; 58408 } 58409 }); 58410 this.limitRenditionByPlayerDimensions = this.options_.limitRenditionByPlayerDimensions; 58411 } 58412 /** 58413 * called when player.src gets called, handle a new source 58414 * 58415 * @param {Object} src the source object to handle 58416 */ 58417 58418 }, {
vendor: 9,926 bytes, lines 58419-58686
58419 key: 'src', 58420 value: function src(_src, type) { 58421 var _this3 = this; // do nothing if the src is falsey 58422 58423 58424 if (!_src) { 58425 return; 58426 } 58427 58428 this.setOptions_(); // add master playlist controller options 58429 58430 this.options_.url = this.source_.src; 58431 this.options_.tech = this.tech_; 58432 this.options_.externHls = Hls$1; 58433 this.options_.sourceType = simpleTypeFromSourceType(type); // Whenever we seek internally, we should update both the tech and call our own 58434 // setCurrentTime function. This is needed because "seeking" events aren't always 58435 // reliable. External seeks (via the player object) are handled via middleware. 58436 58437 this.options_.seekTo = function (time) { 58438 _this3.tech_.setCurrentTime(time); 58439 58440 _this3.setCurrentTime(time); 58441 }; 58442 58443 this.masterPlaylistController_ = new MasterPlaylistController(this.options_); 58444 this.playbackWatcher_ = new PlaybackWatcher(videojs$1.mergeOptions(this.options_, { 58445 seekable: function seekable$$1() { 58446 return _this3.seekable(); 58447 }, 58448 media: function media() { 58449 return _this3.masterPlaylistController_.media(); 58450 } 58451 })); 58452 this.masterPlaylistController_.on('error', function () { 58453 var player = videojs$1.players[_this3.tech_.options_.playerId]; 58454 player.error(_this3.masterPlaylistController_.error); 58455 }); // `this` in selectPlaylist should be the HlsHandler for backwards 58456 // compatibility with < v2 58457 58458 this.masterPlaylistController_.selectPlaylist = this.selectPlaylist ? this.selectPlaylist.bind(this) : Hls$1.STANDARD_PLAYLIST_SELECTOR.bind(this); 58459 this.masterPlaylistController_.selectInitialPlaylist = Hls$1.INITIAL_PLAYLIST_SELECTOR.bind(this); // re-expose some internal objects for backwards compatibility with < v2 58460 58461 this.playlists = this.masterPlaylistController_.masterPlaylistLoader_; 58462 this.mediaSource = this.masterPlaylistController_.mediaSource; // Proxy assignment of some properties to the master playlist 58463 // controller. Using a custom property for backwards compatibility 58464 // with < v2 58465 58466 Object.defineProperties(this, { 58467 selectPlaylist: { 58468 get: function get$$1() { 58469 return this.masterPlaylistController_.selectPlaylist; 58470 }, 58471 set: function set$$1(selectPlaylist) { 58472 this.masterPlaylistController_.selectPlaylist = selectPlaylist.bind(this); 58473 } 58474 }, 58475 throughput: { 58476 get: function get$$1() { 58477 return this.masterPlaylistController_.mainSegmentLoader_.throughput.rate; 58478 }, 58479 set: function set$$1(throughput) { 58480 this.masterPlaylistController_.mainSegmentLoader_.throughput.rate = throughput; // By setting `count` to 1 the throughput value becomes the starting value 58481 // for the cumulative average 58482 58483 this.masterPlaylistController_.mainSegmentLoader_.throughput.count = 1; 58484 } 58485 }, 58486 bandwidth: { 58487 get: function get$$1() { 58488 return this.masterPlaylistController_.mainSegmentLoader_.bandwidth; 58489 }, 58490 set: function set$$1(bandwidth) { 58491 this.masterPlaylistController_.mainSegmentLoader_.bandwidth = bandwidth; // setting the bandwidth manually resets the throughput counter 58492 // `count` is set to zero that current value of `rate` isn't included 58493 // in the cumulative average 58494 58495 this.masterPlaylistController_.mainSegmentLoader_.throughput = { 58496 rate: 0, 58497 count: 0 58498 }; 58499 } 58500 }, 58501 58502 /** 58503 * `systemBandwidth` is a combination of two serial processes bit-rates. The first 58504 * is the network bitrate provided by `bandwidth` and the second is the bitrate of 58505 * the entire process after that - decryption, transmuxing, and appending - provided 58506 * by `throughput`. 58507 * 58508 * Since the two process are serial, the overall system bandwidth is given by: 58509 * sysBandwidth = 1 / (1 / bandwidth + 1 / throughput) 58510 */ 58511 systemBandwidth: { 58512 get: function get$$1() { 58513 var invBandwidth = 1 / (this.bandwidth || 1); 58514 var invThroughput = void 0; 58515 58516 if (this.throughput > 0) { 58517 invThroughput = 1 / this.throughput; 58518 } else { 58519 invThroughput = 0; 58520 } 58521 58522 var systemBitrate = Math.floor(1 / (invBandwidth + invThroughput)); 58523 return systemBitrate; 58524 }, 58525 set: function set$$1() { 58526 videojs$1.log.error('The "systemBandwidth" property is read-only'); 58527 } 58528 } 58529 }); 58530 58531 if (this.options_.bandwidth) { 58532 this.bandwidth = this.options_.bandwidth; 58533 } 58534 58535 if (this.options_.throughput) { 58536 this.throughput = this.options_.throughput; 58537 } 58538 58539 Object.defineProperties(this.stats, { 58540 bandwidth: { 58541 get: function get$$1() { 58542 return _this3.bandwidth || 0; 58543 }, 58544 enumerable: true 58545 }, 58546 mediaRequests: { 58547 get: function get$$1() { 58548 return _this3.masterPlaylistController_.mediaRequests_() || 0; 58549 }, 58550 enumerable: true 58551 }, 58552 mediaRequestsAborted: { 58553 get: function get$$1() { 58554 return _this3.masterPlaylistController_.mediaRequestsAborted_() || 0; 58555 }, 58556 enumerable: true 58557 }, 58558 mediaRequestsTimedout: { 58559 get: function get$$1() { 58560 return _this3.masterPlaylistController_.mediaRequestsTimedout_() || 0; 58561 }, 58562 enumerable: true 58563 }, 58564 mediaRequestsErrored: { 58565 get: function get$$1() { 58566 return _this3.masterPlaylistController_.mediaRequestsErrored_() || 0; 58567 }, 58568 enumerable: true 58569 }, 58570 mediaTransferDuration: { 58571 get: function get$$1() { 58572 return _this3.masterPlaylistController_.mediaTransferDuration_() || 0; 58573 }, 58574 enumerable: true 58575 }, 58576 mediaBytesTransferred: { 58577 get: function get$$1() { 58578 return _this3.masterPlaylistController_.mediaBytesTransferred_() || 0; 58579 }, 58580 enumerable: true 58581 }, 58582 mediaSecondsLoaded: { 58583 get: function get$$1() { 58584 return _this3.masterPlaylistController_.mediaSecondsLoaded_() || 0; 58585 }, 58586 enumerable: true 58587 }, 58588 buffered: { 58589 get: function get$$1() { 58590 return timeRangesToArray(_this3.tech_.buffered()); 58591 }, 58592 enumerable: true 58593 }, 58594 currentTime: { 58595 get: function get$$1() { 58596 return _this3.tech_.currentTime(); 58597 }, 58598 enumerable: true 58599 }, 58600 currentSource: { 58601 get: function get$$1() { 58602 return _this3.tech_.currentSource_; 58603 }, 58604 enumerable: true 58605 }, 58606 currentTech: { 58607 get: function get$$1() { 58608 return _this3.tech_.name_; 58609 }, 58610 enumerable: true 58611 }, 58612 duration: { 58613 get: function get$$1() { 58614 return _this3.tech_.duration(); 58615 }, 58616 enumerable: true 58617 }, 58618 master: { 58619 get: function get$$1() { 58620 return _this3.playlists.master; 58621 }, 58622 enumerable: true 58623 }, 58624 playerDimensions: { 58625 get: function get$$1() { 58626 return _this3.tech_.currentDimensions(); 58627 }, 58628 enumerable: true 58629 }, 58630 seekable: { 58631 get: function get$$1() { 58632 return timeRangesToArray(_this3.tech_.seekable()); 58633 }, 58634 enumerable: true 58635 }, 58636 timestamp: { 58637 get: function get$$1() { 58638 return Date.now(); 58639 }, 58640 enumerable: true 58641 }, 58642 videoPlaybackQuality: { 58643 get: function get$$1() { 58644 return _this3.tech_.getVideoPlaybackQuality(); 58645 }, 58646 enumerable: true 58647 } 58648 }); 58649 this.tech_.one('canplay', this.masterPlaylistController_.setupFirstPlay.bind(this.masterPlaylistController_)); 58650 this.tech_.on('bandwidthupdate', function () { 58651 if (_this3.options_.useBandwidthFromLocalStorage) { 58652 updateVhsLocalStorage({ 58653 bandwidth: _this3.bandwidth, 58654 throughput: Math.round(_this3.throughput) 58655 }); 58656 } 58657 }); 58658 this.masterPlaylistController_.on('selectedinitialmedia', function () { 58659 // Add the manual rendition mix-in to HlsHandler 58660 renditionSelectionMixin(_this3); 58661 setupEmeOptions(_this3); 58662 }); // the bandwidth of the primary segment loader is our best 58663 // estimate of overall bandwidth 58664 58665 this.on(this.masterPlaylistController_, 'progress', function () { 58666 this.tech_.trigger('progress'); 58667 }); 58668 this.tech_.ready(function () { 58669 return _this3.setupQualityLevels_(); 58670 }); // do nothing if the tech has been disposed already 58671 // this can occur if someone sets the src in player.ready(), for instance 58672 58673 if (!this.tech_.el()) { 58674 return; 58675 } 58676 58677 this.tech_.src(videojs$1.URL.createObjectURL(this.masterPlaylistController_.mediaSource)); 58678 } 58679 /** 58680 * Initializes the quality levels and sets listeners to update them. 58681 * 58682 * @method setupQualityLevels_ 58683 * @private 58684 */ 58685 58686 }, {
58687 key: 'setupQualityLevels_', 58688 value: function setupQualityLevels_() { 58689 var _this4 = this; 58690 58691 var player = videojs$1.players[this.tech_.options_.playerId]; 58692 58693 if (player && player.qualityLevels) { 58694 this.qualityLevels_ = player.qualityLevels(); 58695 this.masterPlaylistController_.on('selectedinitialmedia', function () { 58696 handleHlsLoadedMetadata(_this4.qualityLevels_, _this4); 58697 }); 58698 this.playlists.on('mediachange', function () { 58699 handleHlsMediaChange(_this4.qualityLevels_, _this4.playlists); 58700 }); 58701 } 58702 } 58703 /** 58704 * Begin playing the video. 58705 */ 58706 58707 }, { 58708 key: 'play', 58709 value: function play() { 58710 this.masterPlaylistController_.play(); 58711 } 58712 /** 58713 * a wrapper around the function in MasterPlaylistController 58714 */ 58715 58716 }, { 58717 key: 'setCurrentTime', 58718 value: function setCurrentTime(currentTime) { 58719 this.masterPlaylistController_.setCurrentTime(currentTime); 58720 } 58721 /** 58722 * a wrapper around the function in MasterPlaylistController 58723 */ 58724 58725 }, { 58726 key: 'duration', 58727 value: function duration$$1() { 58728 return this.masterPlaylistController_.duration(); 58729 } 58730 /** 58731 * a wrapper around the function in MasterPlaylistController 58732 */ 58733 58734 }, { 58735 key: 'seekable', 58736 value: function seekable$$1() { 58737 return this.masterPlaylistController_.seekable(); 58738 } 58739 /** 58740 * Abort all outstanding work and cleanup. 58741 */ 58742 58743 }, { 58744 key: 'dispose', 58745 value: function dispose() { 58746 if (this.playbackWatcher_) { 58747 this.playbackWatcher_.dispose(); 58748 } 58749 58750 if (this.masterPlaylistController_) { 58751 this.masterPlaylistController_.dispose(); 58752 } 58753
58754 if (this.qualityLevels_) { 58755 this.qualityLevels_.dispose(); 58756 } 58757 58758 if (this.player_) { 58759 delete this.player_.vhs; 58760 delete this.player_.dash; 58761 delete this.player_.hls; 58762 } 58763 58764 if (this.tech_ && this.tech_.hls) { 58765 delete this.tech_.hls; 58766 } 58767 58768 get$1(HlsHandler.prototype.__proto__ || Object.getPrototypeOf(HlsHandler.prototype), 'dispose', this).call(this); 58769 } 58770 }, { 58771 key: 'convertToProgramTime', 58772 value: function convertToProgramTime(time, callback) { 58773 return getProgramTime({ 58774 playlist: this.masterPlaylistController_.media(), 58775 time: time, 58776 callback: callback 58777 }); 58778 } // the player must be playing before calling this 58779 58780 }, { 58781 key: 'seekToProgramTime', 58782 value: function seekToProgramTime$$1(programTime, callback) { 58783 var pauseAfterSeek = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true; 58784 var retryCount = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 2; 58785 return seekToProgramTime({ 58786 programTime: programTime, 58787 playlist: this.masterPlaylistController_.media(), 58788 retryCount: retryCount, 58789 pauseAfterSeek: pauseAfterSeek, 58790 seekTo: this.options_.seekTo, 58791 tech: this.options_.tech, 58792 callback: callback 58793 }); 58794 } 58795 }]); 58796 return HlsHandler; 58797 }(Component$1); 58798 /** 58799 * The Source Handler object, which informs video.js what additional 58800 * MIME types are supported and sets up playback. It is registered 58801 * automatically to the appropriate tech based on the capabilities of 58802 * the browser it is running in. It is not necessary to use or modify 58803 * this object in normal usage. 58804 */ 58805 58806 58807 var HlsSourceHandler = { 58808 name: 'videojs-http-streaming', 58809 VERSION: version$3, 58810 canHandleSource: function canHandleSource(srcObj) { 58811 var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 58812 var localOptions = videojs$1.mergeOptions(videojs$1.options, options); 58813 return HlsSourceHandler.canPlayType(srcObj.type, localOptions); 58814 }, 58815 handleSource: function handleSource(source, tech) { 58816 var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; 58817 var localOptions = videojs$1.mergeOptions(videojs$1.options, options); 58818 tech.hls = new HlsHandler(source, tech, localOptions); 58819 tech.hls.xhr = xhrFactory(); 58820 tech.hls.src(source.src, source.type); 58821 return tech.hls; 58822 }, 58823 canPlayType: function canPlayType(type) { 58824 var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 58825 58826 var _videojs$mergeOptions = videojs$1.mergeOptions(videojs$1.options, options), 58827 overrideNative = _videojs$mergeOptions.hls.overrideNative; 58828 58829 var supportedType = simpleTypeFromSourceType(type); 58830 var canUseMsePlayback = supportedType && (!Hls$1.supportsTypeNatively(supportedType) || overrideNative); 58831 return canUseMsePlayback ? 'maybe' : ''; 58832 } 58833 }; 58834 58835 if (typeof videojs$1.MediaSource === 'undefined' || typeof videojs$1.URL === 'undefined') { 58836 videojs$1.MediaSource = MediaSource; 58837 videojs$1.URL = URL$1; 58838 } // register source handlers with the appropriate techs 58839 58840 58841 if (MediaSource.supportsNativeMediaSources()) { 58842 videojs$1.getTech('Html5').registerSourceHandler(HlsSourceHandler, 0); 58843 } 58844 58845 videojs$1.HlsHandler = HlsHandler; 58846 videojs$1.HlsSourceHandler = HlsSourceHandler; 58847 videojs$1.Hls = Hls$1; 58848 58849 if (!videojs$1.use) { 58850 videojs$1.registerComponent('Hls', Hls$1); 58851 } 58852 58853 videojs$1.options.hls = videojs$1.options.hls || {}; 58854 58855 if (videojs$1.registerPlugin) { 58856 videojs$1.registerPlugin('reloadSourceOnError', reloadSourceOnError); 58857 } else { 58858 videojs$1.plugin('reloadSourceOnError', reloadSourceOnError); 58859 } 58860 58861 return videojs$1; 58862 58863})); 58864 58865!function(){!function(a){var b=a&&a.videojs;b&&(b.CDN_VERSION="7.5.5")}(window)}();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.