vendor: 15,927 bytes, lines 1-623
1/** 2 * @license 3 * Video.js 6.12.0 <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() : 15 typeof define === 'function' && define.amd ? define(factory) : 16 (global.videojs = factory()); 17}(this, (function () { 18 19var version = "6.12.0"; 20 21var commonjsGlobal = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}; 22 23 24 25 26 27function createCommonjsModule(fn, module) { 28 return module = { exports: {} }, fn(module, module.exports), module.exports; 29} 30 31var win; 32 33if (typeof window !== "undefined") { 34 win = window; 35} else if (typeof commonjsGlobal !== "undefined") { 36 win = commonjsGlobal; 37} else if (typeof self !== "undefined"){ 38 win = self; 39} else { 40 win = {}; 41} 42 43var window_1 = win; 44 45var empty = {}; 46 47 48var empty$1 = (Object.freeze || Object)({ 49 'default': empty 50}); 51 52var minDoc = ( empty$1 && empty ) || empty$1; 53 54var topLevel = typeof commonjsGlobal !== 'undefined' ? commonjsGlobal : 55 typeof window !== 'undefined' ? window : {}; 56 57 58var doccy; 59 60if (typeof document !== 'undefined') { 61 doccy = document; 62} else { 63 doccy = topLevel['__GLOBAL_DOCUMENT_CACHE@4']; 64 65 if (!doccy) { 66 doccy = topLevel['__GLOBAL_DOCUMENT_CACHE@4'] = minDoc; 67 } 68} 69 70var document_1 = doccy; 71 72/** 73 * @file browser.js 74 * @module browser 75 */ 76var USER_AGENT = window_1.navigator && window_1.navigator.userAgent || ''; 77var webkitVersionMap = /AppleWebKit\/([\d.]+)/i.exec(USER_AGENT); 78var appleWebkitVersion = webkitVersionMap ? parseFloat(webkitVersionMap.pop()) : null; 79 80/* 81 * Device is an iPhone 82 * 83 * @type {Boolean} 84 * @constant 85 * @private 86 */ 87var IS_IPAD = /iPad/i.test(USER_AGENT); 88 89// The Facebook app's UIWebView identifies as both an iPhone and iPad, so 90// to identify iPhones, we need to exclude iPads. 91// http://artsy.github.io/blog/2012/10/18/the-perils-of-ios-user-agent-sniffing/ 92var IS_IPHONE = /iPhone/i.test(USER_AGENT) && !IS_IPAD; 93var IS_IPOD = /iPod/i.test(USER_AGENT); 94var IS_IOS = IS_IPHONE || IS_IPAD || IS_IPOD; 95 96var IOS_VERSION = function () { 97 var match = USER_AGENT.match(/OS (\d+)_/i); 98 99 if (match && match[1]) { 100 return match[1]; 101 } 102 return null; 103}(); 104 105var IS_ANDROID = /Android/i.test(USER_AGENT); 106var ANDROID_VERSION = function () { 107 // This matches Android Major.Minor.Patch versions 108 // ANDROID_VERSION is Major.Minor as a Number, if Minor isn't available, then only Major is returned 109 var match = USER_AGENT.match(/Android (\d+)(?:\.(\d+))?(?:\.(\d+))*/i); 110 111 if (!match) { 112 return null; 113 } 114 115 var major = match[1] && parseFloat(match[1]); 116 var minor = match[2] && parseFloat(match[2]); 117 118 if (major && minor) { 119 return parseFloat(match[1] + '.' + match[2]); 120 } else if (major) { 121 return major; 122 } 123 return null; 124}(); 125 126// Old Android is defined as Version older than 2.3, and requiring a webkit version of the android browser 127var IS_OLD_ANDROID = IS_ANDROID && /webkit/i.test(USER_AGENT) && ANDROID_VERSION < 2.3; 128var IS_NATIVE_ANDROID = IS_ANDROID && ANDROID_VERSION < 5 && appleWebkitVersion < 537; 129 130var IS_FIREFOX = /Firefox/i.test(USER_AGENT); 131var IS_EDGE = /Edge/i.test(USER_AGENT); 132var IS_CHROME = !IS_EDGE && (/Chrome/i.test(USER_AGENT) || /CriOS/i.test(USER_AGENT)); 133var CHROME_VERSION = function () { 134 var match = USER_AGENT.match(/(Chrome|CriOS)\/(\d+)/); 135 136 if (match && match[2]) { 137 return parseFloat(match[2]); 138 } 139 return null; 140}(); 141var IS_IE8 = /MSIE\s8\.0/.test(USER_AGENT); 142var IE_VERSION = function () { 143 var result = /MSIE\s(\d+)\.\d/.exec(USER_AGENT); 144 var version = result && parseFloat(result[1]); 145 146 if (!version && /Trident\/7.0/i.test(USER_AGENT) && /rv:11.0/.test(USER_AGENT)) { 147 // IE 11 has a different user agent string than other IE versions 148 version = 11.0; 149 } 150 151 return version; 152}(); 153 154var IS_SAFARI = /Safari/i.test(USER_AGENT) && !IS_CHROME && !IS_ANDROID && !IS_EDGE; 155var IS_ANY_SAFARI = (IS_SAFARI || IS_IOS) && !IS_CHROME; 156 157var TOUCH_ENABLED = isReal() && ('ontouchstart' in window_1 || window_1.navigator.maxTouchPoints || window_1.DocumentTouch && window_1.document instanceof window_1.DocumentTouch); 158 159var BACKGROUND_SIZE_SUPPORTED = isReal() && 'backgroundSize' in window_1.document.createElement('video').style; 160 161var browser = (Object.freeze || Object)({ 162 IS_IPAD: IS_IPAD, 163 IS_IPHONE: IS_IPHONE, 164 IS_IPOD: IS_IPOD, 165 IS_IOS: IS_IOS, 166 IOS_VERSION: IOS_VERSION, 167 IS_ANDROID: IS_ANDROID, 168 ANDROID_VERSION: ANDROID_VERSION, 169 IS_OLD_ANDROID: IS_OLD_ANDROID, 170 IS_NATIVE_ANDROID: IS_NATIVE_ANDROID, 171 IS_FIREFOX: IS_FIREFOX, 172 IS_EDGE: IS_EDGE, 173 IS_CHROME: IS_CHROME, 174 CHROME_VERSION: CHROME_VERSION, 175 IS_IE8: IS_IE8, 176 IE_VERSION: IE_VERSION, 177 IS_SAFARI: IS_SAFARI, 178 IS_ANY_SAFARI: IS_ANY_SAFARI, 179 TOUCH_ENABLED: TOUCH_ENABLED, 180 BACKGROUND_SIZE_SUPPORTED: BACKGROUND_SIZE_SUPPORTED 181}); 182 183var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { 184 return typeof obj; 185} : function (obj) { 186 return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; 187}; 188 189 190 191 192 193 194 195 196 197 198 199var classCallCheck = function (instance, Constructor) { 200 if (!(instance instanceof Constructor)) { 201 throw new TypeError("Cannot call a class as a function"); 202 } 203}; 204 205 206 207 208 209 210 211 212 213 214 215var inherits = function (subClass, superClass) { 216 if (typeof superClass !== "function" && superClass !== null) { 217 throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); 218 } 219 220 subClass.prototype = Object.create(superClass && superClass.prototype, { 221 constructor: { 222 value: subClass, 223 enumerable: false, 224 writable: true, 225 configurable: true 226 } 227 }); 228 if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; 229}; 230 231 232 233 234 235 236 237 238 239 240 241var possibleConstructorReturn = function (self, call) { 242 if (!self) { 243 throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); 244 } 245 246 return call && (typeof call === "object" || typeof call === "function") ? call : self; 247}; 248 249 250 251 252 253 254 255 256 257 258 259var taggedTemplateLiteralLoose = function (strings, raw) { 260 strings.raw = raw; 261 return strings; 262}; 263 264/** 265 * @file obj.js 266 * @module obj 267 */ 268 269/** 270 * @callback obj:EachCallback 271 * 272 * @param {Mixed} value 273 * The current key for the object that is being iterated over. 274 * 275 * @param {string} key 276 * The current key-value for object that is being iterated over 277 */ 278 279/** 280 * @callback obj:ReduceCallback 281 * 282 * @param {Mixed} accum 283 * The value that is accumulating over the reduce loop. 284 * 285 * @param {Mixed} value 286 * The current key for the object that is being iterated over. 287 * 288 * @param {string} key 289 * The current key-value for object that is being iterated over 290 * 291 * @return {Mixed} 292 * The new accumulated value. 293 */ 294var toString = Object.prototype.toString; 295 296/** 297 * Get the keys of an Object 298 * 299 * @param {Object} 300 * The Object to get the keys from 301 * 302 * @return {string[]} 303 * An array of the keys from the object. Returns an empty array if the 304 * object passed in was invalid or had no keys. 305 * 306 * @private 307 */ 308var keys = function keys(object) { 309 return isObject(object) ? Object.keys(object) : []; 310}; 311 312/** 313 * Array-like iteration for objects. 314 * 315 * @param {Object} object 316 * The object to iterate over 317 * 318 * @param {obj:EachCallback} fn 319 * The callback function which is called for each key in the object. 320 */ 321function each(object, fn) { 322 keys(object).forEach(function (key) { 323 return fn(object[key], key); 324 }); 325} 326 327/** 328 * Array-like reduce for objects. 329 * 330 * @param {Object} object 331 * The Object that you want to reduce. 332 * 333 * @param {Function} fn 334 * A callback function which is called for each key in the object. It 335 * receives the accumulated value and the per-iteration value and key 336 * as arguments. 337 * 338 * @param {Mixed} [initial = 0] 339 * Starting value 340 * 341 * @return {Mixed} 342 * The final accumulated value. 343 */ 344function reduce(object, fn) { 345 var initial = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0; 346 347 return keys(object).reduce(function (accum, key) { 348 return fn(accum, object[key], key); 349 }, initial); 350} 351 352/** 353 * Object.assign-style object shallow merge/extend. 354 * 355 * @param {Object} target 356 * @param {Object} ...sources 357 * @return {Object} 358 */ 359function assign(target) { 360 for (var _len = arguments.length, sources = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { 361 sources[_key - 1] = arguments[_key]; 362 } 363 364 if (Object.assign) { 365 return Object.assign.apply(Object, [target].concat(sources)); 366 } 367 368 sources.forEach(function (source) { 369 if (!source) { 370 return; 371 } 372 373 each(source, function (value, key) { 374 target[key] = value; 375 }); 376 }); 377 378 return target; 379} 380 381/** 382 * Returns whether a value is an object of any kind - including DOM nodes, 383 * arrays, regular expressions, etc. Not functions, though. 384 * 385 * This avoids the gotcha where using `typeof` on a `null` value 386 * results in `'object'`. 387 * 388 * @param {Object} value 389 * @return {Boolean} 390 */ 391function isObject(value) { 392 return !!value && (typeof value === 'undefined' ? 'undefined' : _typeof(value)) === 'object'; 393} 394 395/** 396 * Returns whether an object appears to be a "plain" object - that is, a 397 * direct instance of `Object`. 398 * 399 * @param {Object} value 400 * @return {Boolean} 401 */ 402function isPlain(value) { 403 return isObject(value) && toString.call(value) === '[object Object]' && value.constructor === Object; 404} 405 406/** 407 * @file log.js 408 * @module log 409 */ 410var log = void 0; 411 412// This is the private tracking variable for logging level. 413var level = 'info'; 414 415// This is the private tracking variable for the logging history. 416var history = []; 417 418/** 419 * Log messages to the console and history based on the type of message 420 * 421 * @private 422 * @param {string} type 423 * The name of the console method to use. 424 * 425 * @param {Array} args 426 * The arguments to be passed to the matching console method. 427 * 428 * @param {boolean} [stringify] 429 * By default, only old IEs should get console argument stringification, 430 * but this is exposed as a parameter to facilitate testing. 431 */ 432var logByType = function logByType(type, args) { 433 var stringify = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : !!IE_VERSION && IE_VERSION < 11; 434 435 var lvl = log.levels[level]; 436 var lvlRegExp = new RegExp('^(' + lvl + ')$'); 437 438 if (type !== 'log') { 439 440 // Add the type to the front of the message when it's not "log". 441 args.unshift(type.toUpperCase() + ':'); 442 } 443 444 // Add a clone of the args at this point to history. 445 if (history) { 446 history.push([].concat(args)); 447 } 448 449 // Add console prefix after adding to history. 450 args.unshift('VIDEOJS:'); 451 452 // If there's no console then don't try to output messages, but they will 453 // still be stored in history. 454 if (!window_1.console) { 455 return; 456 } 457 458 // Was setting these once outside of this function, but containing them 459 // in the function makes it easier to test cases where console doesn't exist 460 // when the module is executed. 461 var fn = window_1.console[type]; 462 463 if (!fn && type === 'debug') { 464 // Certain browsers don't have support for console.debug. For those, we 465 // should default to the closest comparable log. 466 fn = window_1.console.info || window_1.console.log; 467 } 468 469 // Bail out if there's no console or if this type is not allowed by the 470 // current logging level. 471 if (!fn || !lvl || !lvlRegExp.test(type)) { 472 return; 473 } 474 475 // IEs previous to 11 log objects uselessly as "[object Object]"; so, JSONify 476 // objects and arrays for those less-capable browsers. 477 if (stringify) { 478 args = args.map(function (a) { 479 if (isObject(a) || Array.isArray(a)) { 480 try { 481 return JSON.stringify(a); 482 } catch (x) { 483 return String(a); 484 } 485 } 486 487 // Cast to string before joining, so we get null and undefined explicitly 488 // included in output (as we would in a modern console). 489 return String(a); 490 }).join(' '); 491 } 492 493 // Old IE versions do not allow .apply() for console methods (they are 494 // reported as objects rather than functions). 495 if (!fn.apply) { 496 fn(args); 497 } else { 498 fn[Array.isArray(args) ? 'apply' : 'call'](window_1.console, args); 499 } 500}; 501 502/** 503 * Logs plain debug messages. Similar to `console.log`. 504 * 505 * @class 506 * @param {Mixed[]} args 507 * One or more messages or objects that should be logged. 508 */ 509log = function log() { 510 for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { 511 args[_key] = arguments[_key]; 512 } 513 514 logByType('log', args); 515}; 516 517/** 518 * Enumeration of available logging levels, where the keys are the level names 519 * and the values are `|`-separated strings containing logging methods allowed 520 * in that logging level. These strings are used to create a regular expression 521 * matching the function name being called. 522 * 523 * Levels provided by video.js are: 524 * 525 * - `off`: Matches no calls. Any value that can be cast to `false` will have 526 * this effect. The most restrictive. 527 * - `all`: Matches only Video.js-provided functions (`debug`, `log`, 528 * `log.warn`, and `log.error`). 529 * - `debug`: Matches `log.debug`, `log`, `log.warn`, and `log.error` calls. 530 * - `info` (default): Matches `log`, `log.warn`, and `log.error` calls. 531 * - `warn`: Matches `log.warn` and `log.error` calls. 532 * - `error`: Matches only `log.error` calls. 533 * 534 * @type {Object} 535 */ 536log.levels = { 537 all: 'debug|log|warn|error', 538 off: '', 539 debug: 'debug|log|warn|error', 540 info: 'log|warn|error', 541 warn: 'warn|error', 542 error: 'error', 543 DEFAULT: level 544}; 545 546/** 547 * Get or set the current logging level. If a string matching a key from 548 * {@link log.levels} is provided, acts as a setter. Regardless of argument, 549 * returns the current logging level. 550 * 551 * @param {string} [lvl] 552 * Pass to set a new logging level. 553 * 554 * @return {string} 555 * The current logging level. 556 */ 557log.level = function (lvl) { 558 if (typeof lvl === 'string') { 559 if (!log.levels.hasOwnProperty(lvl)) { 560 throw new Error('"' + lvl + '" in not a valid log level'); 561 } 562 level = lvl; 563 } 564 return level; 565}; 566 567/** 568 * Returns an array containing everything that has been logged to the history. 569 * 570 * This array is a shallow clone of the internal history record. However, its 571 * contents are _not_ cloned; so, mutating objects inside this array will 572 * mutate them in history. 573 * 574 * @return {Array} 575 */ 576log.history = function () { 577 return history ? [].concat(history) : []; 578}; 579 580/** 581 * Clears the internal history tracking, but does not prevent further history 582 * tracking. 583 */ 584log.history.clear = function () { 585 if (history) { 586 history.length = 0; 587 } 588}; 589 590/** 591 * Disable history tracking if it is currently enabled. 592 */ 593log.history.disable = function () { 594 if (history !== null) { 595 history.length = 0; 596 history = null; 597 } 598}; 599 600/** 601 * Enable history tracking if it is currently disabled. 602 */ 603log.history.enable = function () { 604 if (history === null) { 605 history = []; 606 } 607}; 608 609/** 610 * Logs error messages. Similar to `console.error`. 611 * 612 * @param {Mixed[]} args 613 * One or more messages or objects that should be logged as an error 614 */ 615log.error = function () { 616 for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { 617 args[_key2] = arguments[_key2]; 618 } 619 620 return logByType('error', args); 621}; 622 623/**
624 * Logs warning messages. Similar to `console.warn`. 625 * 626 * @param {Mixed[]} args 627 * One or more messages or objects that should be logged as a warning. 628 */ 629log.warn = function () { 630 for (var _len3 = arguments.length, args = Array(_len3), _key3 = 0; _key3 < _len3; _key3++) { 631 args[_key3] = arguments[_key3]; 632 } 633 634 return logByType('warn', args); 635}; 636 637/** 638 * Logs debug messages. Similar to `console.debug`, but may also act as a comparable 639 * log if `console.debug` is not available 640 * 641 * @param {Mixed[]} args 642 * One or more messages or objects that should be logged as debug. 643 */ 644log.debug = function () { 645 for (var _len4 = arguments.length, args = Array(_len4), _key4 = 0; _key4 < _len4; _key4++) { 646 args[_key4] = arguments[_key4]; 647 } 648 649 return logByType('debug', args); 650}; 651 652var log$1 = log; 653 654function clean (s) { 655 return s.replace(/\n\r?\s*/g, '') 656} 657 658 659var tsml = function tsml (sa) { 660 var s = '' 661 , i = 0; 662 663 for (; i < arguments.length; i++) 664 s += clean(sa[i]) + (arguments[i + 1] || ''); 665 666 return s 667}; 668 669/** 670 * @file computed-style.js 671 * @module computed-style 672 */ 673/** 674 * A safe getComputedStyle with an IE8 fallback. 675 * 676 * This is needed because in Firefox, if the player is loaded in an iframe with 677 * `display:none`, then `getComputedStyle` returns `null`, so, we do a null-check to 678 * make sure that the player doesn't break in these cases. 679 * 680 * @param {Element} el 681 * The element you want the computed style of 682 * 683 * @param {string} prop 684 * The property name you want 685 * 686 * @see https://bugzilla.mozilla.org/show_bug.cgi?id=548397 687 * 688 * @static 689 * @const 690 */ 691function computedStyle(el, prop) { 692 if (!el || !prop) { 693 return ''; 694 } 695 696 if (typeof window_1.getComputedStyle === 'function') { 697 var cs = window_1.getComputedStyle(el); 698 699 return cs ? cs[prop] : ''; 700 } 701 702 return el.currentStyle[prop] || ''; 703} 704 705var _templateObject = 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 ', '.'], ['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 ', '.']); 706 707/** 708 * @file dom.js 709 * @module dom 710 */ 711/** 712 * Detect if a value is a string with any non-whitespace characters. 713 * 714 * @param {string} str 715 * The string to check 716 * 717 * @return {boolean} 718 * - True if the string is non-blank 719 * - False otherwise 720 * 721 */ 722function isNonBlankString(str) { 723 return typeof str === 'string' && /\S/.test(str); 724} 725 726/** 727 * Throws an error if the passed string has whitespace. This is used by 728 * class methods to be relatively consistent with the classList API. 729 * 730 * @param {string} str 731 * The string to check for whitespace. 732 * 733 * @throws {Error} 734 * Throws an error if there is whitespace in the string. 735 * 736 */ 737function throwIfWhitespace(str) { 738 if (/\s/.test(str)) { 739 throw new Error('class has illegal whitespace characters'); 740 } 741} 742 743/** 744 * Produce a regular expression for matching a className within an elements className. 745 * 746 * @param {string} className 747 * The className to generate the RegExp for. 748 * 749 * @return {RegExp} 750 * The RegExp that will check for a specific `className` in an elements 751 * className. 752 */ 753function classRegExp(className) { 754 return new RegExp('(^|\\s)' + className + '($|\\s)'); 755} 756 757/** 758 * Whether the current DOM interface appears to be real. 759 * 760 * @return {Boolean} 761 */ 762function isReal() { 763 return ( 764 765 // Both document and window will never be undefined thanks to `global`. 766 document_1 === window_1.document && 767 768 // In IE < 9, DOM methods return "object" as their type, so all we can 769 // confidently check is that it exists. 770 typeof document_1.createElement !== 'undefined' 771 ); 772} 773 774/** 775 * Determines, via duck typing, whether or not a value is a DOM element. 776 * 777 * @param {Mixed} value 778 * The thing to check 779 * 780 * @return {boolean} 781 * - True if it is a DOM element 782 * - False otherwise 783 */ 784function isEl(value) { 785 return isObject(value) && value.nodeType === 1; 786} 787 788/** 789 * Determines if the current DOM is embedded in an iframe. 790 * 791 * @return {boolean} 792 * 793 */ 794function isInFrame() { 795 796 // We need a try/catch here because Safari will throw errors when attempting 797 // to get either `parent` or `self` 798 try { 799 return window_1.parent !== window_1.self; 800 } catch (x) { 801 return true; 802 } 803} 804 805/** 806 * Creates functions to query the DOM using a given method. 807 * 808 * @param {string} method 809 * The method to create the query with. 810 * 811 * @return {Function} 812 * The query method 813 */ 814function createQuerier(method) { 815 return function (selector, context) { 816 if (!isNonBlankString(selector)) { 817 return document_1[method](null); 818 } 819 if (isNonBlankString(context)) { 820 context = document_1.querySelector(context); 821 } 822 823 var ctx = isEl(context) ? context : document_1; 824 825 return ctx[method] && ctx[method](selector); 826 }; 827} 828 829/** 830 * Creates an element and applies properties. 831 * 832 * @param {string} [tagName='div'] 833 * Name of tag to be created. 834 * 835 * @param {Object} [properties={}] 836 * Element properties to be applied. 837 * 838 * @param {Object} [attributes={}] 839 * Element attributes to be applied. 840 * 841 * @param {String|Element|TextNode|Array|Function} [content] 842 * Contents for the element (see: {@link dom:normalizeContent}) 843 * 844 * @return {Element} 845 * The element that was created. 846 */ 847function createEl() { 848 var tagName = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'div'; 849 var properties = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 850 var attributes = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; 851 var content = arguments[3]; 852 853 var el = document_1.createElement(tagName); 854
vendor: 4,609 bytes, lines 855-1031
855 Object.getOwnPropertyNames(properties).forEach(function (propName) { 856 var val = properties[propName]; 857 858 // See #2176 859 // We originally were accepting both properties and attributes in the 860 // same object, but that doesn't work so well. 861 if (propName.indexOf('aria-') !== -1 || propName === 'role' || propName === 'type') { 862 log$1.warn(tsml(_templateObject, propName, val)); 863 el.setAttribute(propName, val); 864 865 // Handle textContent since it's not supported everywhere and we have a 866 // method for it. 867 } else if (propName === 'textContent') { 868 textContent(el, val); 869 } else { 870 el[propName] = val; 871 } 872 }); 873 874 Object.getOwnPropertyNames(attributes).forEach(function (attrName) { 875 el.setAttribute(attrName, attributes[attrName]); 876 }); 877 878 if (content) { 879 appendContent(el, content); 880 } 881 882 return el; 883} 884 885/** 886 * Injects text into an element, replacing any existing contents entirely. 887 * 888 * @param {Element} el 889 * The element to add text content into 890 * 891 * @param {string} text 892 * The text content to add. 893 * 894 * @return {Element} 895 * The element with added text content. 896 */ 897function textContent(el, text) { 898 if (typeof el.textContent === 'undefined') { 899 el.innerText = text; 900 } else { 901 el.textContent = text; 902 } 903 return el; 904} 905 906/** 907 * Insert an element as the first child node of another 908 * 909 * @param {Element} child 910 * Element to insert 911 * 912 * @param {Element} parent 913 * Element to insert child into 914 */ 915function prependTo(child, parent) { 916 if (parent.firstChild) { 917 parent.insertBefore(child, parent.firstChild); 918 } else { 919 parent.appendChild(child); 920 } 921} 922 923/** 924 * Check if an element has a CSS class 925 * 926 * @param {Element} element 927 * Element to check 928 * 929 * @param {string} classToCheck 930 * Class name to check for 931 * 932 * @return {boolean} 933 * - True if the element had the class 934 * - False otherwise. 935 * 936 * @throws {Error} 937 * Throws an error if `classToCheck` has white space. 938 */ 939function hasClass(element, classToCheck) { 940 throwIfWhitespace(classToCheck); 941 if (element.classList) { 942 return element.classList.contains(classToCheck); 943 } 944 return classRegExp(classToCheck).test(element.className); 945} 946 947/** 948 * Add a CSS class name to an element 949 * 950 * @param {Element} element 951 * Element to add class name to. 952 * 953 * @param {string} classToAdd 954 * Class name to add. 955 * 956 * @return {Element} 957 * The dom element with the added class name. 958 */ 959function addClass(element, classToAdd) { 960 if (element.classList) { 961 element.classList.add(classToAdd); 962 963 // Don't need to `throwIfWhitespace` here because `hasElClass` will do it 964 // in the case of classList not being supported. 965 } else if (!hasClass(element, classToAdd)) { 966 element.className = (element.className + ' ' + classToAdd).trim(); 967 } 968 969 return element; 970} 971 972/** 973 * Remove a CSS class name from an element 974 * 975 * @param {Element} element 976 * Element to remove a class name from. 977 * 978 * @param {string} classToRemove 979 * Class name to remove 980 * 981 * @return {Element} 982 * The dom element with class name removed. 983 */ 984function removeClass(element, classToRemove) { 985 if (element.classList) { 986 element.classList.remove(classToRemove); 987 } else { 988 throwIfWhitespace(classToRemove); 989 element.className = element.className.split(/\s+/).filter(function (c) { 990 return c !== classToRemove; 991 }).join(' '); 992 } 993 994 return element; 995} 996 997/** 998 * The callback definition for toggleElClass. 999 * 1000 * @callback Dom~PredicateCallback 1001 * @param {Element} element 1002 * The DOM element of the Component. 1003 * 1004 * @param {string} classToToggle 1005 * The `className` that wants to be toggled 1006 * 1007 * @return {boolean|undefined} 1008 * - If true the `classToToggle` will get added to `element`. 1009 * - If false the `classToToggle` will get removed from `element`. 1010 * - If undefined this callback will be ignored 1011 */ 1012 1013/** 1014 * Adds or removes a CSS class name on an element depending on an optional 1015 * condition or the presence/absence of the class name. 1016 * 1017 * @param {Element} element 1018 * The element to toggle a class name on. 1019 * 1020 * @param {string} classToToggle 1021 * The class that should be toggled 1022 * 1023 * @param {boolean|PredicateCallback} [predicate] 1024 * See the return value for {@link Dom~PredicateCallback} 1025 * 1026 * @return {Element} 1027 * The element with a class that has been toggled. 1028 */ 1029function toggleClass(element, classToToggle, predicate) { 1030 1031 // This CANNOT use `classList` internally because IE does not support the
vendor: 5,424 bytes, lines 1032-1240
1032 // second parameter to the `classList.toggle()` method! Which is fine because 1033 // `classList` will be used by the add/remove functions. 1034 var has = hasClass(element, classToToggle); 1035 1036 if (typeof predicate === 'function') { 1037 predicate = predicate(element, classToToggle); 1038 } 1039 1040 if (typeof predicate !== 'boolean') { 1041 predicate = !has; 1042 } 1043 1044 // If the necessary class operation matches the current state of the 1045 // element, no action is required. 1046 if (predicate === has) { 1047 return; 1048 } 1049 1050 if (predicate) { 1051 addClass(element, classToToggle); 1052 } else { 1053 removeClass(element, classToToggle); 1054 } 1055 1056 return element; 1057} 1058 1059/** 1060 * Apply attributes to an HTML element. 1061 * 1062 * @param {Element} el 1063 * Element to add attributes to. 1064 * 1065 * @param {Object} [attributes] 1066 * Attributes to be applied. 1067 */ 1068function setAttributes(el, attributes) { 1069 Object.getOwnPropertyNames(attributes).forEach(function (attrName) { 1070 var attrValue = attributes[attrName]; 1071 1072 if (attrValue === null || typeof attrValue === 'undefined' || attrValue === false) { 1073 el.removeAttribute(attrName); 1074 } else { 1075 el.setAttribute(attrName, attrValue === true ? '' : attrValue); 1076 } 1077 }); 1078} 1079 1080/** 1081 * Get an element's attribute values, as defined on the HTML tag 1082 * Attributes are not the same as properties. They're defined on the tag 1083 * or with setAttribute (which shouldn't be used with HTML) 1084 * This will return true or false for boolean attributes. 1085 * 1086 * @param {Element} tag 1087 * Element from which to get tag attributes. 1088 * 1089 * @return {Object} 1090 * All attributes of the element. 1091 */ 1092function getAttributes(tag) { 1093 var obj = {}; 1094 1095 // known boolean attributes 1096 // we can check for matching boolean properties, but older browsers 1097 // won't know about HTML5 boolean attributes that we still read from 1098 var knownBooleans = ',' + 'autoplay,controls,playsinline,loop,muted,default,defaultMuted' + ','; 1099 1100 if (tag && tag.attributes && tag.attributes.length > 0) { 1101 var attrs = tag.attributes; 1102 1103 for (var i = attrs.length - 1; i >= 0; i--) { 1104 var attrName = attrs[i].name; 1105 var attrVal = attrs[i].value; 1106 1107 // check for known booleans 1108 // the matching element property will return a value for typeof 1109 if (typeof tag[attrName] === 'boolean' || knownBooleans.indexOf(',' + attrName + ',') !== -1) { 1110 // the value of an included boolean attribute is typically an empty 1111 // string ('') which would equal false if we just check for a false value. 1112 // we also don't want support bad code like autoplay='false' 1113 attrVal = attrVal !== null ? true : false; 1114 } 1115 1116 obj[attrName] = attrVal; 1117 } 1118 } 1119 1120 return obj; 1121} 1122 1123/** 1124 * Get the value of an element's attribute 1125 * 1126 * @param {Element} el 1127 * A DOM element 1128 * 1129 * @param {string} attribute 1130 * Attribute to get the value of 1131 * 1132 * @return {string} 1133 * value of the attribute 1134 */ 1135function getAttribute(el, attribute) { 1136 return el.getAttribute(attribute); 1137} 1138 1139/** 1140 * Set the value of an element's attribute 1141 * 1142 * @param {Element} el 1143 * A DOM element 1144 * 1145 * @param {string} attribute 1146 * Attribute to set 1147 * 1148 * @param {string} value 1149 * Value to set the attribute to 1150 */ 1151function setAttribute(el, attribute, value) { 1152 el.setAttribute(attribute, value); 1153} 1154 1155/** 1156 * Remove an element's attribute 1157 * 1158 * @param {Element} el 1159 * A DOM element 1160 * 1161 * @param {string} attribute 1162 * Attribute to remove 1163 */ 1164function removeAttribute(el, attribute) { 1165 el.removeAttribute(attribute); 1166} 1167 1168/** 1169 * Attempt to block the ability to select text while dragging controls 1170 */ 1171function blockTextSelection() { 1172 document_1.body.focus(); 1173 document_1.onselectstart = function () { 1174 return false; 1175 }; 1176} 1177 1178/** 1179 * Turn off text selection blocking 1180 */ 1181function unblockTextSelection() { 1182 document_1.onselectstart = function () { 1183 return true; 1184 }; 1185} 1186 1187/** 1188 * Identical to the native `getBoundingClientRect` function, but ensures that 1189 * the method is supported at all (it is in all browsers we claim to support) 1190 * and that the element is in the DOM before continuing. 1191 * 1192 * This wrapper function also shims properties which are not provided by some 1193 * older browsers (namely, IE8). 1194 * 1195 * Additionally, some browsers do not support adding properties to a 1196 * `ClientRect`/`DOMRect` object; so, we shallow-copy it with the standard 1197 * properties (except `x` and `y` which are not widely supported). This helps 1198 * avoid implementations where keys are non-enumerable. 1199 * 1200 * @param {Element} el 1201 * Element whose `ClientRect` we want to calculate. 1202 * 1203 * @return {Object|undefined} 1204 * Always returns a plain 1205 */ 1206function getBoundingClientRect(el) { 1207 if (el && el.getBoundingClientRect && el.parentNode) { 1208 var rect = el.getBoundingClientRect(); 1209 var result = {}; 1210 1211 ['bottom', 'height', 'left', 'right', 'top', 'width'].forEach(function (k) { 1212 if (rect[k] !== undefined) { 1213 result[k] = rect[k]; 1214 } 1215 }); 1216 1217 if (!result.height) { 1218 result.height = parseFloat(computedStyle(el, 'height')); 1219 } 1220 1221 if (!result.width) { 1222 result.width = parseFloat(computedStyle(el, 'width')); 1223 } 1224 1225 return result; 1226 } 1227} 1228 1229/** 1230 * The postion of a DOM element on the page. 1231 * 1232 * @typedef {Object} module:dom~Position 1233 * 1234 * @property {number} left 1235 * Pixels to the left 1236 * 1237 * @property {number} top 1238 * Pixels on top 1239 */ 1240
vendor: 4,060 bytes, lines 1241-1395
1241/** 1242 * Offset Left. 1243 * getBoundingClientRect technique from 1244 * John Resig 1245 * 1246 * @see http://ejohn.org/blog/getboundingclientrect-is-awesome/ 1247 * 1248 * @param {Element} el 1249 * Element from which to get offset 1250 * 1251 * @return {module:dom~Position} 1252 * The position of the element that was passed in. 1253 */ 1254function findPosition(el) { 1255 var box = void 0; 1256 1257 if (el.getBoundingClientRect && el.parentNode) { 1258 box = el.getBoundingClientRect(); 1259 } 1260 1261 if (!box) { 1262 return { 1263 left: 0, 1264 top: 0 1265 }; 1266 } 1267 1268 var docEl = document_1.documentElement; 1269 var body = document_1.body; 1270 1271 var clientLeft = docEl.clientLeft || body.clientLeft || 0; 1272 var scrollLeft = window_1.pageXOffset || body.scrollLeft; 1273 var left = box.left + scrollLeft - clientLeft; 1274 1275 var clientTop = docEl.clientTop || body.clientTop || 0; 1276 var scrollTop = window_1.pageYOffset || body.scrollTop; 1277 var top = box.top + scrollTop - clientTop; 1278 1279 // Android sometimes returns slightly off decimal values, so need to round 1280 return { 1281 left: Math.round(left), 1282 top: Math.round(top) 1283 }; 1284} 1285 1286/** 1287 * x and y coordinates for a dom element or mouse pointer 1288 * 1289 * @typedef {Object} Dom~Coordinates 1290 * 1291 * @property {number} x 1292 * x coordinate in pixels 1293 * 1294 * @property {number} y 1295 * y coordinate in pixels 1296 */ 1297 1298/** 1299 * Get pointer position in element 1300 * Returns an object with x and y coordinates. 1301 * The base on the coordinates are the bottom left of the element. 1302 * 1303 * @param {Element} el 1304 * Element on which to get the pointer position on 1305 * 1306 * @param {EventTarget~Event} event 1307 * Event object 1308 * 1309 * @return {Dom~Coordinates} 1310 * A Coordinates object corresponding to the mouse position. 1311 * 1312 */ 1313function getPointerPosition(el, event) { 1314 var position = {}; 1315 var box = findPosition(el); 1316 var boxW = el.offsetWidth; 1317 var boxH = el.offsetHeight; 1318 1319 var boxY = box.top; 1320 var boxX = box.left; 1321 var pageY = event.pageY; 1322 var pageX = event.pageX; 1323 1324 if (event.changedTouches) { 1325 pageX = event.changedTouches[0].pageX; 1326 pageY = event.changedTouches[0].pageY; 1327 } 1328 1329 position.y = Math.max(0, Math.min(1, (boxY - pageY + boxH) / boxH)); 1330 position.x = Math.max(0, Math.min(1, (pageX - boxX) / boxW)); 1331 1332 return position; 1333} 1334 1335/** 1336 * Determines, via duck typing, whether or not a value is a text node. 1337 * 1338 * @param {Mixed} value 1339 * Check if this value is a text node. 1340 * 1341 * @return {boolean} 1342 * - True if it is a text node 1343 * - False otherwise 1344 */ 1345function isTextNode(value) { 1346 return isObject(value) && value.nodeType === 3; 1347} 1348 1349/** 1350 * Empties the contents of an element. 1351 * 1352 * @param {Element} el 1353 * The element to empty children from 1354 * 1355 * @return {Element} 1356 * The element with no children 1357 */ 1358function emptyEl(el) { 1359 while (el.firstChild) { 1360 el.removeChild(el.firstChild); 1361 } 1362 return el; 1363} 1364 1365/** 1366 * Normalizes content for eventual insertion into the DOM. 1367 * 1368 * This allows a wide range of content definition methods, but protects 1369 * from falling into the trap of simply writing to `innerHTML`, which is 1370 * an XSS concern. 1371 * 1372 * The content for an element can be passed in multiple types and 1373 * combinations, whose behavior is as follows: 1374 * 1375 * @param {String|Element|TextNode|Array|Function} content 1376 * - String: Normalized into a text node. 1377 * - Element/TextNode: Passed through. 1378 * - Array: A one-dimensional array of strings, elements, nodes, or functions 1379 * (which return single strings, elements, or nodes). 1380 * - Function: If the sole argument, is expected to produce a string, element, 1381 * node, or array as defined above. 1382 * 1383 * @return {Array} 1384 * All of the content that was passed in normalized. 1385 */ 1386function normalizeContent(content) { 1387 1388 // First, invoke content if it is a function. If it produces an array, 1389 // that needs to happen before normalization. 1390 if (typeof content === 'function') { 1391 content = content(); 1392 } 1393 1394 // Next up, normalize to an array, so one or many items can be normalized, 1395 // filtered, and returned.
vendor: 4,108 bytes, lines 1396-1536
1396 return (Array.isArray(content) ? content : [content]).map(function (value) { 1397 1398 // First, invoke value if it is a function to produce a new value, 1399 // which will be subsequently normalized to a Node of some kind. 1400 if (typeof value === 'function') { 1401 value = value(); 1402 } 1403 1404 if (isEl(value) || isTextNode(value)) { 1405 return value; 1406 } 1407 1408 if (typeof value === 'string' && /\S/.test(value)) { 1409 return document_1.createTextNode(value); 1410 } 1411 }).filter(function (value) { 1412 return value; 1413 }); 1414} 1415 1416/** 1417 * Normalizes and appends content to an element. 1418 * 1419 * @param {Element} el 1420 * Element to append normalized content to. 1421 * 1422 * 1423 * @param {String|Element|TextNode|Array|Function} content 1424 * See the `content` argument of {@link dom:normalizeContent} 1425 * 1426 * @return {Element} 1427 * The element with appended normalized content. 1428 */ 1429function appendContent(el, content) { 1430 normalizeContent(content).forEach(function (node) { 1431 return el.appendChild(node); 1432 }); 1433 return el; 1434} 1435 1436/** 1437 * Normalizes and inserts content into an element; this is identical to 1438 * `appendContent()`, except it empties the element first. 1439 * 1440 * @param {Element} el 1441 * Element to insert normalized content into. 1442 * 1443 * @param {String|Element|TextNode|Array|Function} content 1444 * See the `content` argument of {@link dom:normalizeContent} 1445 * 1446 * @return {Element} 1447 * The element with inserted normalized content. 1448 * 1449 */ 1450function insertContent(el, content) { 1451 return appendContent(emptyEl(el), content); 1452} 1453 1454/** 1455 * Check if event was a single left click 1456 * 1457 * @param {EventTarget~Event} event 1458 * Event object 1459 * 1460 * @return {boolean} 1461 * - True if a left click 1462 * - False if not a left click 1463 */ 1464function isSingleLeftClick(event) { 1465 // Note: if you create something draggable, be sure to 1466 // call it on both `mousedown` and `mousemove` event, 1467 // otherwise `mousedown` should be enough for a button 1468 1469 if (event.button === undefined && event.buttons === undefined) { 1470 // Why do we need `buttons` ? 1471 // Because, middle mouse sometimes have this: 1472 // e.button === 0 and e.buttons === 4 1473 // Furthermore, we want to prevent combination click, something like 1474 // HOLD middlemouse then left click, that would be 1475 // e.button === 0, e.buttons === 5 1476 // just `button` is not gonna work 1477 1478 // Alright, then what this block does ? 1479 // this is for chrome `simulate mobile devices` 1480 // I want to support this as well 1481 1482 return true; 1483 } 1484 1485 if (event.button === 0 && event.buttons === undefined) { 1486 // Touch screen, sometimes on some specific device, `buttons` 1487 // doesn't have anything (safari on ios, blackberry...) 1488 1489 return true; 1490 } 1491 1492 if (IE_VERSION === 9) { 1493 // Ignore IE9 1494 1495 return true; 1496 } 1497 1498 if (event.button !== 0 || event.buttons !== 1) { 1499 // This is the reason we have those if else block above 1500 // if any special case we can catch and let it slide 1501 // we do it above, when get to here, this definitely 1502 // is-not-left-click 1503 1504 return false; 1505 } 1506 1507 return true; 1508} 1509 1510/** 1511 * Finds a single DOM element matching `selector` within the optional 1512 * `context` of another DOM element (defaulting to `document`). 1513 * 1514 * @param {string} selector 1515 * A valid CSS selector, which will be passed to `querySelector`. 1516 * 1517 * @param {Element|String} [context=document] 1518 * A DOM element within which to query. Can also be a selector 1519 * string in which case the first matching element will be used 1520 * as context. If missing (or no element matches selector), falls 1521 * back to `document`. 1522 * 1523 * @return {Element|null} 1524 * The element that was found or null. 1525 */ 1526var $ = createQuerier('querySelector'); 1527 1528/** 1529 * Finds a all DOM elements matching `selector` within the optional 1530 * `context` of another DOM element (defaulting to `document`). 1531 * 1532 * @param {string} selector 1533 * A valid CSS selector, which will be passed to `querySelectorAll`. 1534 * 1535 * @param {Element|String} [context=document] 1536 * A DOM element within which to query. Can also be a selector
vendor: 5,002 bytes, lines 1537-1751
1537 * string in which case the first matching element will be used 1538 * as context. If missing (or no element matches selector), falls 1539 * back to `document`. 1540 * 1541 * @return {NodeList} 1542 * A element list of elements that were found. Will be empty if none were found. 1543 * 1544 */ 1545var $$ = createQuerier('querySelectorAll'); 1546 1547 1548 1549var Dom = (Object.freeze || Object)({ 1550 isReal: isReal, 1551 isEl: isEl, 1552 isInFrame: isInFrame, 1553 createEl: createEl, 1554 textContent: textContent, 1555 prependTo: prependTo, 1556 hasClass: hasClass, 1557 addClass: addClass, 1558 removeClass: removeClass, 1559 toggleClass: toggleClass, 1560 setAttributes: setAttributes, 1561 getAttributes: getAttributes, 1562 getAttribute: getAttribute, 1563 setAttribute: setAttribute, 1564 removeAttribute: removeAttribute, 1565 blockTextSelection: blockTextSelection, 1566 unblockTextSelection: unblockTextSelection, 1567 getBoundingClientRect: getBoundingClientRect, 1568 findPosition: findPosition, 1569 getPointerPosition: getPointerPosition, 1570 isTextNode: isTextNode, 1571 emptyEl: emptyEl, 1572 normalizeContent: normalizeContent, 1573 appendContent: appendContent, 1574 insertContent: insertContent, 1575 isSingleLeftClick: isSingleLeftClick, 1576 $: $, 1577 $$: $$ 1578}); 1579 1580/** 1581 * @file guid.js 1582 * @module guid 1583 */ 1584 1585/** 1586 * Unique ID for an element or function 1587 * @type {Number} 1588 */ 1589var _guid = 1; 1590 1591/** 1592 * Get a unique auto-incrementing ID by number that has not been returned before. 1593 * 1594 * @return {number} 1595 * A new unique ID. 1596 */ 1597function newGUID() { 1598 return _guid++; 1599} 1600 1601/** 1602 * @file dom-data.js 1603 * @module dom-data 1604 */ 1605/** 1606 * Element Data Store. 1607 * 1608 * Allows for binding data to an element without putting it directly on the 1609 * element. Ex. Event listeners are stored here. 1610 * (also from jsninja.com, slightly modified and updated for closure compiler) 1611 * 1612 * @type {Object} 1613 * @private 1614 */ 1615var elData = {}; 1616 1617/* 1618 * Unique attribute name to store an element's guid in 1619 * 1620 * @type {String} 1621 * @constant 1622 * @private 1623 */ 1624var elIdAttr = 'vdata' + new Date().getTime(); 1625 1626/** 1627 * Returns the cache object where data for an element is stored 1628 * 1629 * @param {Element} el 1630 * Element to store data for. 1631 * 1632 * @return {Object} 1633 * The cache object for that el that was passed in. 1634 */ 1635function getData(el) { 1636 var id = el[elIdAttr]; 1637 1638 if (!id) { 1639 id = el[elIdAttr] = newGUID(); 1640 } 1641 1642 if (!elData[id]) { 1643 elData[id] = {}; 1644 } 1645 1646 return elData[id]; 1647} 1648 1649/** 1650 * Returns whether or not an element has cached data 1651 * 1652 * @param {Element} el 1653 * Check if this element has cached data. 1654 * 1655 * @return {boolean} 1656 * - True if the DOM element has cached data. 1657 * - False otherwise. 1658 */ 1659function hasData(el) { 1660 var id = el[elIdAttr]; 1661 1662 if (!id) { 1663 return false; 1664 } 1665 1666 return !!Object.getOwnPropertyNames(elData[id]).length; 1667} 1668 1669/** 1670 * Delete data for the element from the cache and the guid attr from getElementById 1671 * 1672 * @param {Element} el 1673 * Remove cached data for this element. 1674 */ 1675function removeData(el) { 1676 var id = el[elIdAttr]; 1677 1678 if (!id) { 1679 return; 1680 } 1681 1682 // Remove all stored data 1683 delete elData[id]; 1684 1685 // Remove the elIdAttr property from the DOM node 1686 try { 1687 delete el[elIdAttr]; 1688 } catch (e) { 1689 if (el.removeAttribute) { 1690 el.removeAttribute(elIdAttr); 1691 } else { 1692 // IE doesn't appear to support removeAttribute on the document element 1693 el[elIdAttr] = null; 1694 } 1695 } 1696} 1697 1698/** 1699 * @file events.js. An Event System (John Resig - Secrets of a JS Ninja http://jsninja.com/) 1700 * (Original book version wasn't completely usable, so fixed some things and made Closure Compiler compatible) 1701 * This should work very similarly to jQuery's events, however it's based off the book version which isn't as 1702 * robust as jquery's, so there's probably some differences. 1703 * 1704 * @module events 1705 */ 1706 1707/** 1708 * Clean up the listener cache and dispatchers 1709 * 1710 * @param {Element|Object} elem 1711 * Element to clean up 1712 * 1713 * @param {string} type 1714 * Type of event to clean up 1715 */ 1716function _cleanUpEvents(elem, type) { 1717 var data = getData(elem); 1718 1719 // Remove the events of a particular type if there are none left 1720 if (data.handlers[type].length === 0) { 1721 delete data.handlers[type]; 1722 // data.handlers[type] = null; 1723 // Setting to null was causing an error with data.handlers 1724 1725 // Remove the meta-handler from the element 1726 if (elem.removeEventListener) { 1727 elem.removeEventListener(type, data.dispatcher, false); 1728 } else if (elem.detachEvent) { 1729 elem.detachEvent('on' + type, data.dispatcher); 1730 } 1731 } 1732 1733 // Remove the events object if there are no types left 1734 if (Object.getOwnPropertyNames(data.handlers).length <= 0) { 1735 delete data.handlers; 1736 delete data.dispatcher; 1737 delete data.disabled; 1738 } 1739 1740 // Finally remove the element data if there is no data left 1741 if (Object.getOwnPropertyNames(data).length === 0) { 1742 removeData(elem); 1743 } 1744} 1745 1746/** 1747 * Loops through an array of event types and calls the requested method for each type. 1748 * 1749 * @param {Function} fn 1750 * The event method we want to use. 1751 *
vendor: 4,387 bytes, lines 1752-1886
1752 * @param {Element|Object} elem 1753 * Element or object to bind listeners to 1754 * 1755 * @param {string} type 1756 * Type of event to bind to. 1757 * 1758 * @param {EventTarget~EventListener} callback 1759 * Event listener. 1760 */ 1761function _handleMultipleEvents(fn, elem, types, callback) { 1762 types.forEach(function (type) { 1763 // Call the event method for each one of the types 1764 fn(elem, type, callback); 1765 }); 1766} 1767 1768/** 1769 * Fix a native event to have standard property values 1770 * 1771 * @param {Object} event 1772 * Event object to fix. 1773 * 1774 * @return {Object} 1775 * Fixed event object. 1776 */ 1777function fixEvent(event) { 1778 1779 function returnTrue() { 1780 return true; 1781 } 1782 1783 function returnFalse() { 1784 return false; 1785 } 1786 1787 // Test if fixing up is needed 1788 // Used to check if !event.stopPropagation instead of isPropagationStopped 1789 // But native events return true for stopPropagation, but don't have 1790 // other expected methods like isPropagationStopped. Seems to be a problem 1791 // with the Javascript Ninja code. So we're just overriding all events now. 1792 if (!event || !event.isPropagationStopped) { 1793 var old = event || window_1.event; 1794 1795 event = {}; 1796 // Clone the old object so that we can modify the values event = {}; 1797 // IE8 Doesn't like when you mess with native event properties 1798 // Firefox returns false for event.hasOwnProperty('type') and other props 1799 // which makes copying more difficult. 1800 // TODO: Probably best to create a whitelist of event props 1801 for (var key in old) { 1802 // Safari 6.0.3 warns you if you try to copy deprecated layerX/Y 1803 // Chrome warns you if you try to copy deprecated keyboardEvent.keyLocation 1804 // and webkitMovementX/Y 1805 if (key !== 'layerX' && key !== 'layerY' && key !== 'keyLocation' && key !== 'webkitMovementX' && key !== 'webkitMovementY') { 1806 // Chrome 32+ warns if you try to copy deprecated returnValue, but 1807 // we still want to if preventDefault isn't supported (IE8). 1808 if (!(key === 'returnValue' && old.preventDefault)) { 1809 event[key] = old[key]; 1810 } 1811 } 1812 } 1813 1814 // The event occurred on this element 1815 if (!event.target) { 1816 event.target = event.srcElement || document_1; 1817 } 1818 1819 // Handle which other element the event is related to 1820 if (!event.relatedTarget) { 1821 event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement; 1822 } 1823 1824 // Stop the default browser action 1825 event.preventDefault = function () { 1826 if (old.preventDefault) { 1827 old.preventDefault(); 1828 } 1829 event.returnValue = false; 1830 old.returnValue = false; 1831 event.defaultPrevented = true; 1832 }; 1833 1834 event.defaultPrevented = false; 1835 1836 // Stop the event from bubbling 1837 event.stopPropagation = function () { 1838 if (old.stopPropagation) { 1839 old.stopPropagation(); 1840 } 1841 event.cancelBubble = true; 1842 old.cancelBubble = true; 1843 event.isPropagationStopped = returnTrue; 1844 }; 1845 1846 event.isPropagationStopped = returnFalse; 1847 1848 // Stop the event from bubbling and executing other handlers 1849 event.stopImmediatePropagation = function () { 1850 if (old.stopImmediatePropagation) { 1851 old.stopImmediatePropagation(); 1852 } 1853 event.isImmediatePropagationStopped = returnTrue; 1854 event.stopPropagation(); 1855 }; 1856 1857 event.isImmediatePropagationStopped = returnFalse; 1858 1859 // Handle mouse position 1860 if (event.clientX !== null && event.clientX !== undefined) { 1861 var doc = document_1.documentElement; 1862 var body = document_1.body; 1863 1864 event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0); 1865 event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0); 1866 } 1867 1868 // Handle key presses 1869 event.which = event.charCode || event.keyCode; 1870 1871 // Fix button for mouse clicks: 1872 // 0 == left; 1 == middle; 2 == right 1873 if (event.button !== null && event.button !== undefined) { 1874 1875 // The following is disabled because it does not pass videojs-standard 1876 // and... yikes. 1877 /* eslint-disable */ 1878 event.button = event.button & 1 ? 0 : event.button & 4 ? 1 : event.button & 2 ? 2 : 0; 1879 /* eslint-enable */ 1880 } 1881 } 1882 1883 // Returns fixed-up instance 1884 return event; 1885} 1886
vendor: 4,088 bytes, lines 1887-2068
1887/** 1888 * Whether passive event listeners are supported 1889 */ 1890var _supportsPassive = false; 1891 1892(function () { 1893 try { 1894 var opts = Object.defineProperty({}, 'passive', { 1895 get: function get() { 1896 _supportsPassive = true; 1897 } 1898 }); 1899 1900 window_1.addEventListener('test', null, opts); 1901 window_1.removeEventListener('test', null, opts); 1902 } catch (e) { 1903 // disregard 1904 } 1905})(); 1906 1907/** 1908 * Touch events Chrome expects to be passive 1909 */ 1910var passiveEvents = ['touchstart', 'touchmove']; 1911 1912/** 1913 * Add an event listener to element 1914 * It stores the handler function in a separate cache object 1915 * and adds a generic handler to the element's event, 1916 * along with a unique id (guid) to the element. 1917 * 1918 * @param {Element|Object} elem 1919 * Element or object to bind listeners to 1920 * 1921 * @param {string|string[]} type 1922 * Type of event to bind to. 1923 * 1924 * @param {EventTarget~EventListener} fn 1925 * Event listener. 1926 */ 1927function on(elem, type, fn) { 1928 if (Array.isArray(type)) { 1929 return _handleMultipleEvents(on, elem, type, fn); 1930 } 1931 1932 var data = getData(elem); 1933 1934 // We need a place to store all our handler data 1935 if (!data.handlers) { 1936 data.handlers = {}; 1937 } 1938 1939 if (!data.handlers[type]) { 1940 data.handlers[type] = []; 1941 } 1942 1943 if (!fn.guid) { 1944 fn.guid = newGUID(); 1945 } 1946 1947 data.handlers[type].push(fn); 1948 1949 if (!data.dispatcher) { 1950 data.disabled = false; 1951 1952 data.dispatcher = function (event, hash) { 1953 1954 if (data.disabled) { 1955 return; 1956 } 1957 1958 event = fixEvent(event); 1959 1960 var handlers = data.handlers[event.type]; 1961 1962 if (handlers) { 1963 // Copy handlers so if handlers are added/removed during the process it doesn't throw everything off. 1964 var handlersCopy = handlers.slice(0); 1965 1966 for (var m = 0, n = handlersCopy.length; m < n; m++) { 1967 if (event.isImmediatePropagationStopped()) { 1968 break; 1969 } else { 1970 try { 1971 handlersCopy[m].call(elem, event, hash); 1972 } catch (e) { 1973 log$1.error(e); 1974 } 1975 } 1976 } 1977 } 1978 }; 1979 } 1980 1981 if (data.handlers[type].length === 1) { 1982 if (elem.addEventListener) { 1983 var options = false; 1984 1985 if (_supportsPassive && passiveEvents.indexOf(type) > -1) { 1986 options = { passive: true }; 1987 } 1988 elem.addEventListener(type, data.dispatcher, options); 1989 } else if (elem.attachEvent) { 1990 elem.attachEvent('on' + type, data.dispatcher); 1991 } 1992 } 1993} 1994 1995/** 1996 * Removes event listeners from an element 1997 * 1998 * @param {Element|Object} elem 1999 * Object to remove listeners from. 2000 * 2001 * @param {string|string[]} [type] 2002 * Type of listener to remove. Don't include to remove all events from element. 2003 * 2004 * @param {EventTarget~EventListener} [fn] 2005 * Specific listener to remove. Don't include to remove listeners for an event 2006 * type. 2007 */ 2008function off(elem, type, fn) { 2009 // Don't want to add a cache object through getElData if not needed 2010 if (!hasData(elem)) { 2011 return; 2012 } 2013 2014 var data = getData(elem); 2015 2016 // If no events exist, nothing to unbind 2017 if (!data.handlers) { 2018 return; 2019 } 2020 2021 if (Array.isArray(type)) { 2022 return _handleMultipleEvents(off, elem, type, fn); 2023 } 2024 2025 // Utility function 2026 var removeType = function removeType(el, t) { 2027 data.handlers[t] = []; 2028 _cleanUpEvents(el, t); 2029 }; 2030 2031 // Are we removing all bound events? 2032 if (type === undefined) { 2033 for (var t in data.handlers) { 2034 if (Object.prototype.hasOwnProperty.call(data.handlers || {}, t)) { 2035 removeType(elem, t); 2036 } 2037 } 2038 return; 2039 } 2040 2041 var handlers = data.handlers[type]; 2042 2043 // If no handlers exist, nothing to unbind 2044 if (!handlers) { 2045 return; 2046 } 2047 2048 // If no listener was provided, remove all listeners for type 2049 if (!fn) { 2050 removeType(elem, type); 2051 return; 2052 } 2053 2054 // We're only removing a single handler 2055 if (fn.guid) { 2056 for (var n = 0; n < handlers.length; n++) { 2057 if (handlers[n].guid === fn.guid) { 2058 handlers.splice(n--, 1); 2059 } 2060 } 2061 } 2062 2063 _cleanUpEvents(elem, type); 2064} 2065 2066/** 2067 * Trigger an event for an element 2068 *
2069 * @param {Element|Object} elem 2070 * Element to trigger an event on 2071 * 2072 * @param {EventTarget~Event|string} event 2073 * A string (the type) or an event object with a type attribute 2074 * 2075 * @param {Object} [hash] 2076 * data hash to pass along with the event 2077 * 2078 * @return {boolean|undefined} 2079 * - Returns the opposite of `defaultPrevented` if default was prevented 2080 * - Otherwise returns undefined 2081 */ 2082function trigger(elem, event, hash) { 2083 // Fetches element data and a reference to the parent (for bubbling). 2084 // Don't want to add a data object to cache for every parent, 2085 // so checking hasElData first. 2086 var elemData = hasData(elem) ? getData(elem) : {}; 2087 var parent = elem.parentNode || elem.ownerDocument; 2088 // type = event.type || event, 2089 // handler; 2090 2091 // If an event name was passed as a string, creates an event out of it 2092 if (typeof event === 'string') { 2093 event = { type: event, target: elem }; 2094 } else if (!event.target) { 2095 event.target = elem; 2096 } 2097 2098 // Normalizes the event properties. 2099 event = fixEvent(event); 2100 2101 // If the passed element has a dispatcher, executes the established handlers. 2102 if (elemData.dispatcher) { 2103 elemData.dispatcher.call(elem, event, hash); 2104 } 2105 2106 // Unless explicitly stopped or the event does not bubble (e.g. media events) 2107 // recursively calls this function to bubble the event up the DOM. 2108 if (parent && !event.isPropagationStopped() && event.bubbles === true) { 2109 trigger.call(null, parent, event, hash); 2110 2111 // If at the top of the DOM, triggers the default action unless disabled. 2112 } else if (!parent && !event.defaultPrevented) { 2113 var targetData = getData(event.target); 2114 2115 // Checks if the target has a default action for this event. 2116 if (event.target[event.type]) { 2117 // Temporarily disables event dispatching on the target as we have already executed the handler. 2118 targetData.disabled = true; 2119 // Executes the default action. 2120 if (typeof event.target[event.type] === 'function') { 2121 event.target[event.type](); 2122 } 2123 // Re-enables event dispatching. 2124 targetData.disabled = false; 2125 } 2126 } 2127 2128 // Inform the triggerer if the default was prevented by returning false 2129 return !event.defaultPrevented; 2130} 2131 2132/** 2133 * Trigger a listener only once for an event 2134 * 2135 * @param {Element|Object} elem 2136 * Element or object to bind to. 2137 * 2138 * @param {string|string[]} type 2139 * Name/type of event 2140 * 2141 * @param {Event~EventListener} fn 2142 * Event Listener function 2143 */ 2144function one(elem, type, fn) { 2145 if (Array.isArray(type)) { 2146 return _handleMultipleEvents(one, elem, type, fn); 2147 } 2148 var func = function func() { 2149 off(elem, type, func); 2150 fn.apply(this, arguments); 2151 }; 2152 2153 // copy the guid to the new function so it can removed using the original function's ID 2154 func.guid = fn.guid = fn.guid || newGUID(); 2155 on(elem, type, func); 2156} 2157 2158var Events = (Object.freeze || Object)({ 2159 fixEvent: fixEvent, 2160 on: on, 2161 off: off, 2162 trigger: trigger, 2163 one: one 2164}); 2165 2166/** 2167 * @file setup.js - Functions for setting up a player without 2168 * user interaction based on the data-setup `attribute` of the video tag. 2169 * 2170 * @module setup 2171 */ 2172var _windowLoaded = false; 2173var videojs$2 = void 0; 2174 2175/** 2176 * Set up any tags that have a data-setup `attribute` when the player is started. 2177 */ 2178var autoSetup = function autoSetup() { 2179 2180 // Protect against breakage in non-browser environments and check global autoSetup option. 2181 if (!isReal() || videojs$2.options.autoSetup === false) { 2182 return; 2183 } 2184 2185 // One day, when we stop supporting IE8, go back to this, but in the meantime...*hack hack hack* 2186 // var vids = Array.prototype.slice.call(document.getElementsByTagName('video')); 2187 // var audios = Array.prototype.slice.call(document.getElementsByTagName('audio')); 2188 // var mediaEls = vids.concat(audios); 2189 2190 // Because IE8 doesn't support calling slice on a node list, we need to loop 2191 // through each list of elements to build up a new, combined list of elements. 2192 var vids = document_1.getElementsByTagName('video'); 2193 var audios = document_1.getElementsByTagName('audio'); 2194 var divs = document_1.getElementsByTagName('video-js'); 2195 var mediaEls = []; 2196 2197 if (vids && vids.length > 0) { 2198 for (var i = 0, e = vids.length; i < e; i++) { 2199 mediaEls.push(vids[i]); 2200 } 2201 } 2202 2203 if (audios && audios.length > 0) { 2204 for (var _i = 0, _e = audios.length; _i < _e; _i++) { 2205 mediaEls.push(audios[_i]); 2206 } 2207 } 2208 2209 if (divs && divs.length > 0) { 2210 for (var _i2 = 0, _e2 = divs.length; _i2 < _e2; _i2++) { 2211 mediaEls.push(divs[_i2]); 2212 } 2213 } 2214 2215 // Check if any media elements exist 2216 if (mediaEls && mediaEls.length > 0) { 2217 2218 for (var _i3 = 0, _e3 = mediaEls.length; _i3 < _e3; _i3++) { 2219 var mediaEl = mediaEls[_i3]; 2220 2221 // Check if element exists, has getAttribute func. 2222 // IE seems to consider typeof el.getAttribute == 'object' instead of 2223 // 'function' like expected, at least when loading the player immediately. 2224 if (mediaEl && mediaEl.getAttribute) { 2225 2226 // Make sure this player hasn't already been set up. 2227 if (mediaEl.player === undefined) { 2228 var options = mediaEl.getAttribute('data-setup'); 2229 2230 // Check if data-setup attr exists. 2231 // We only auto-setup if they've added the data-setup attr. 2232 if (options !== null) { 2233 // Create new video.js instance. 2234 videojs$2(mediaEl); 2235 } 2236 } 2237 2238 // If getAttribute isn't defined, we need to wait for the DOM. 2239 } else { 2240 autoSetupTimeout(1); 2241 break; 2242 } 2243 } 2244 2245 // No videos were found, so keep looping unless page is finished loading. 2246 } else if (!_windowLoaded) { 2247 autoSetupTimeout(1); 2248 } 2249}; 2250 2251/** 2252 * Wait until the page is loaded before running autoSetup. This will be called in 2253 * autoSetup if `hasLoaded` returns false. 2254 * 2255 * @param {number} wait 2256 * How long to wait in ms 2257 * 2258 * @param {module:videojs} [vjs] 2259 * The videojs library function 2260 */ 2261function autoSetupTimeout(wait, vjs) { 2262 if (vjs) { 2263 videojs$2 = vjs; 2264 } 2265 2266 window_1.setTimeout(autoSetup, wait); 2267} 2268 2269if (isReal() && document_1.readyState === 'complete') { 2270 _windowLoaded = true; 2271} else { 2272 /** 2273 * Listen for the load event on window, and set _windowLoaded to true. 2274 * 2275 * @listens load 2276 */ 2277 one(window_1, 'load', function () { 2278 _windowLoaded = true; 2279 }); 2280} 2281 2282/** 2283 * @file stylesheet.js 2284 * @module stylesheet 2285 */ 2286/** 2287 * Create a DOM syle element given a className for it. 2288 * 2289 * @param {string} className 2290 * The className to add to the created style element. 2291 * 2292 * @return {Element} 2293 * The element that was created. 2294 */ 2295var createStyleElement = function createStyleElement(className) { 2296 var style = document_1.createElement('style'); 2297 2298 style.className = className; 2299 2300 return style; 2301}; 2302 2303/** 2304 * Add text to a DOM element. 2305 * 2306 * @param {Element} el 2307 * The Element to add text content to. 2308 * 2309 * @param {string} content 2310 * The text to add to the element. 2311 */ 2312var setTextContent = function setTextContent(el, content) { 2313 if (el.styleSheet) { 2314 el.styleSheet.cssText = content; 2315 } else { 2316 el.textContent = content; 2317 } 2318}; 2319 2320/** 2321 * @file fn.js 2322 * @module fn 2323 */ 2324/**
2325 * Bind (a.k.a proxy or Context). A simple method for changing the context of a function 2326 * It also stores a unique id on the function so it can be easily removed from events. 2327 * 2328 * @param {Mixed} context 2329 * The object to bind as scope. 2330 * 2331 * @param {Function} fn 2332 * The function to be bound to a scope. 2333 * 2334 * @param {number} [uid] 2335 * An optional unique ID for the function to be set 2336 * 2337 * @return {Function} 2338 * The new function that will be bound into the context given 2339 */ 2340var bind = function bind(context, fn, uid) { 2341 // Make sure the function has a unique ID 2342 if (!fn.guid) { 2343 fn.guid = newGUID(); 2344 } 2345 2346 // Create the new function that changes the context 2347 var bound = function bound() { 2348 return fn.apply(context, arguments); 2349 }; 2350 2351 // Allow for the ability to individualize this function 2352 // Needed in the case where multiple objects might share the same prototype 2353 // IF both items add an event listener with the same function, then you try to remove just one 2354 // it will remove both because they both have the same guid. 2355 // when using this, you need to use the bind method when you remove the listener as well. 2356 // currently used in text tracks 2357 bound.guid = uid ? uid + '_' + fn.guid : fn.guid; 2358 2359 return bound; 2360}; 2361 2362/** 2363 * Wraps the given function, `fn`, with a new function that only invokes `fn` 2364 * at most once per every `wait` milliseconds. 2365 * 2366 * @param {Function} fn 2367 * The function to be throttled. 2368 * 2369 * @param {Number} wait 2370 * The number of milliseconds by which to throttle. 2371 * 2372 * @return {Function} 2373 */ 2374var throttle = function throttle(fn, wait) { 2375 var last = Date.now(); 2376 2377 var throttled = function throttled() { 2378 var now = Date.now(); 2379 2380 if (now - last >= wait) { 2381 fn.apply(undefined, arguments); 2382 last = now; 2383 } 2384 }; 2385 2386 return throttled; 2387}; 2388 2389/** 2390 * Creates a debounced function that delays invoking `func` until after `wait` 2391 * milliseconds have elapsed since the last time the debounced function was 2392 * invoked. 2393 * 2394 * Inspired by lodash and underscore implementations. 2395 * 2396 * @param {Function} func 2397 * The function to wrap with debounce behavior. 2398 * 2399 * @param {number} wait 2400 * The number of milliseconds to wait after the last invocation. 2401 * 2402 * @param {boolean} [immediate] 2403 * Whether or not to invoke the function immediately upon creation. 2404 * 2405 * @param {Object} [context=window] 2406 * The "context" in which the debounced function should debounce. For 2407 * example, if this function should be tied to a Video.js player, 2408 * the player can be passed here. Alternatively, defaults to the 2409 * global `window` object. 2410 * 2411 * @return {Function} 2412 * A debounced function. 2413 */ 2414var debounce = function debounce(func, wait, immediate) { 2415 var context = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : window_1; 2416 2417 var timeout = void 0; 2418 2419 /* eslint-disable consistent-this */ 2420 return function () { 2421 var self = this; 2422 var args = arguments; 2423 2424 var _later = function later() { 2425 timeout = null; 2426 _later = null; 2427 if (!immediate) { 2428 func.apply(self, args); 2429 } 2430 }; 2431 2432 if (!timeout && immediate) { 2433 func.apply(self, args); 2434 } 2435 2436 context.clearTimeout(timeout); 2437 timeout = context.setTimeout(_later, wait); 2438 }; 2439 /* eslint-enable consistent-this */ 2440}; 2441 2442/** 2443 * @file src/js/event-target.js 2444 */ 2445/** 2446 * `EventTarget` is a class that can have the same API as the DOM `EventTarget`. It 2447 * adds shorthand functions that wrap around lengthy functions. For example: 2448 * the `on` function is a wrapper around `addEventListener`. 2449 * 2450 * @see [EventTarget Spec]{@link https://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-EventTarget} 2451 * @class EventTarget 2452 */ 2453var EventTarget = function EventTarget() {}; 2454 2455/** 2456 * A Custom DOM event. 2457 * 2458 * @typedef {Object} EventTarget~Event 2459 * @see [Properties]{@link https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent} 2460 */ 2461 2462/** 2463 * All event listeners should follow the following format. 2464 * 2465 * @callback EventTarget~EventListener 2466 * @this {EventTarget} 2467 * 2468 * @param {EventTarget~Event} event 2469 * the event that triggered this function 2470 * 2471 * @param {Object} [hash] 2472 * hash of data sent during the event 2473 */ 2474 2475/**
vendor: 8,182 bytes, lines 2476-2763
2476 * An object containing event names as keys and booleans as values. 2477 * 2478 * > NOTE: If an event name is set to a true value here {@link EventTarget#trigger} 2479 * will have extra functionality. See that function for more information. 2480 * 2481 * @property EventTarget.prototype.allowedEvents_ 2482 * @private 2483 */ 2484EventTarget.prototype.allowedEvents_ = {}; 2485 2486/** 2487 * Adds an `event listener` to an instance of an `EventTarget`. An `event listener` is a 2488 * function that will get called when an event with a certain name gets triggered. 2489 * 2490 * @param {string|string[]} type 2491 * An event name or an array of event names. 2492 * 2493 * @param {EventTarget~EventListener} fn 2494 * The function to call with `EventTarget`s 2495 */ 2496EventTarget.prototype.on = function (type, fn) { 2497 // Remove the addEventListener alias before calling Events.on 2498 // so we don't get into an infinite type loop 2499 var ael = this.addEventListener; 2500 2501 this.addEventListener = function () {}; 2502 on(this, type, fn); 2503 this.addEventListener = ael; 2504}; 2505 2506/** 2507 * An alias of {@link EventTarget#on}. Allows `EventTarget` to mimic 2508 * the standard DOM API. 2509 * 2510 * @function 2511 * @see {@link EventTarget#on} 2512 */ 2513EventTarget.prototype.addEventListener = EventTarget.prototype.on; 2514 2515/** 2516 * Removes an `event listener` for a specific event from an instance of `EventTarget`. 2517 * This makes it so that the `event listener` will no longer get called when the 2518 * named event happens. 2519 * 2520 * @param {string|string[]} type 2521 * An event name or an array of event names. 2522 * 2523 * @param {EventTarget~EventListener} fn 2524 * The function to remove. 2525 */ 2526EventTarget.prototype.off = function (type, fn) { 2527 off(this, type, fn); 2528}; 2529 2530/** 2531 * An alias of {@link EventTarget#off}. Allows `EventTarget` to mimic 2532 * the standard DOM API. 2533 * 2534 * @function 2535 * @see {@link EventTarget#off} 2536 */ 2537EventTarget.prototype.removeEventListener = EventTarget.prototype.off; 2538 2539/** 2540 * This function will add an `event listener` that gets triggered only once. After the 2541 * first trigger it will get removed. This is like adding an `event listener` 2542 * with {@link EventTarget#on} that calls {@link EventTarget#off} on itself. 2543 * 2544 * @param {string|string[]} type 2545 * An event name or an array of event names. 2546 * 2547 * @param {EventTarget~EventListener} fn 2548 * The function to be called once for each event name. 2549 */ 2550EventTarget.prototype.one = function (type, fn) { 2551 // Remove the addEventListener alialing Events.on 2552 // so we don't get into an infinite type loop 2553 var ael = this.addEventListener; 2554 2555 this.addEventListener = function () {}; 2556 one(this, type, fn); 2557 this.addEventListener = ael; 2558}; 2559 2560/** 2561 * This function causes an event to happen. This will then cause any `event listeners` 2562 * that are waiting for that event, to get called. If there are no `event listeners` 2563 * for an event then nothing will happen. 2564 * 2565 * If the name of the `Event` that is being triggered is in `EventTarget.allowedEvents_`. 2566 * Trigger will also call the `on` + `uppercaseEventName` function. 2567 * 2568 * Example: 2569 * 'click' is in `EventTarget.allowedEvents_`, so, trigger will attempt to call 2570 * `onClick` if it exists. 2571 * 2572 * @param {string|EventTarget~Event|Object} event 2573 * The name of the event, an `Event`, or an object with a key of type set to 2574 * an event name. 2575 */ 2576EventTarget.prototype.trigger = function (event) { 2577 var type = event.type || event; 2578 2579 if (typeof event === 'string') { 2580 event = { type: type }; 2581 } 2582 event = fixEvent(event); 2583 2584 if (this.allowedEvents_[type] && this['on' + type]) { 2585 this['on' + type](event); 2586 } 2587 2588 trigger(this, event); 2589}; 2590 2591/** 2592 * An alias of {@link EventTarget#trigger}. Allows `EventTarget` to mimic 2593 * the standard DOM API. 2594 * 2595 * @function 2596 * @see {@link EventTarget#trigger} 2597 */ 2598EventTarget.prototype.dispatchEvent = EventTarget.prototype.trigger; 2599 2600/** 2601 * @file mixins/evented.js 2602 * @module evented 2603 */ 2604/** 2605 * Returns whether or not an object has had the evented mixin applied. 2606 * 2607 * @param {Object} object 2608 * An object to test. 2609 * 2610 * @return {boolean} 2611 * Whether or not the object appears to be evented. 2612 */ 2613var isEvented = function isEvented(object) { 2614 return object instanceof EventTarget || !!object.eventBusEl_ && ['on', 'one', 'off', 'trigger'].every(function (k) { 2615 return typeof object[k] === 'function'; 2616 }); 2617}; 2618 2619/** 2620 * Whether a value is a valid event type - non-empty string or array. 2621 * 2622 * @private 2623 * @param {string|Array} type 2624 * The type value to test. 2625 * 2626 * @return {boolean} 2627 * Whether or not the type is a valid event type. 2628 */ 2629var isValidEventType = function isValidEventType(type) { 2630 return ( 2631 // The regex here verifies that the `type` contains at least one non- 2632 // whitespace character. 2633 typeof type === 'string' && /\S/.test(type) || Array.isArray(type) && !!type.length 2634 ); 2635}; 2636 2637/** 2638 * Validates a value to determine if it is a valid event target. Throws if not. 2639 * 2640 * @private 2641 * @throws {Error} 2642 * If the target does not appear to be a valid event target. 2643 * 2644 * @param {Object} target 2645 * The object to test. 2646 */ 2647var validateTarget = function validateTarget(target) { 2648 if (!target.nodeName && !isEvented(target)) { 2649 throw new Error('Invalid target; must be a DOM node or evented object.'); 2650 } 2651}; 2652 2653/** 2654 * Validates a value to determine if it is a valid event target. Throws if not. 2655 * 2656 * @private 2657 * @throws {Error} 2658 * If the type does not appear to be a valid event type. 2659 * 2660 * @param {string|Array} type 2661 * The type to test. 2662 */ 2663var validateEventType = function validateEventType(type) { 2664 if (!isValidEventType(type)) { 2665 throw new Error('Invalid event type; must be a non-empty string or array.'); 2666 } 2667}; 2668 2669/** 2670 * Validates a value to determine if it is a valid listener. Throws if not. 2671 * 2672 * @private 2673 * @throws {Error} 2674 * If the listener is not a function. 2675 * 2676 * @param {Function} listener 2677 * The listener to test. 2678 */ 2679var validateListener = function validateListener(listener) { 2680 if (typeof listener !== 'function') { 2681 throw new Error('Invalid listener; must be a function.'); 2682 } 2683}; 2684 2685/** 2686 * Takes an array of arguments given to `on()` or `one()`, validates them, and 2687 * normalizes them into an object. 2688 * 2689 * @private 2690 * @param {Object} self 2691 * The evented object on which `on()` or `one()` was called. This 2692 * object will be bound as the `this` value for the listener. 2693 * 2694 * @param {Array} args 2695 * An array of arguments passed to `on()` or `one()`. 2696 * 2697 * @return {Object} 2698 * An object containing useful values for `on()` or `one()` calls. 2699 */ 2700var normalizeListenArgs = function normalizeListenArgs(self, args) { 2701 2702 // If the number of arguments is less than 3, the target is always the 2703 // evented object itself. 2704 var isTargetingSelf = args.length < 3 || args[0] === self || args[0] === self.eventBusEl_; 2705 var target = void 0; 2706 var type = void 0; 2707 var listener = void 0; 2708 2709 if (isTargetingSelf) { 2710 target = self.eventBusEl_; 2711 2712 // Deal with cases where we got 3 arguments, but we are still listening to 2713 // the evented object itself. 2714 if (args.length >= 3) { 2715 args.shift(); 2716 } 2717 2718 type = args[0]; 2719 listener = args[1]; 2720 } else { 2721 target = args[0]; 2722 type = args[1]; 2723 listener = args[2]; 2724 } 2725 2726 validateTarget(target); 2727 validateEventType(type); 2728 validateListener(listener); 2729 2730 listener = bind(self, listener); 2731 2732 return { isTargetingSelf: isTargetingSelf, target: target, type: type, listener: listener }; 2733}; 2734 2735/** 2736 * Adds the listener to the event type(s) on the target, normalizing for 2737 * the type of target. 2738 * 2739 * @private 2740 * @param {Element|Object} target 2741 * A DOM node or evented object. 2742 * 2743 * @param {string} method 2744 * The event binding method to use ("on" or "one"). 2745 * 2746 * @param {string|Array} type 2747 * One or more event type(s). 2748 * 2749 * @param {Function} listener 2750 * A listener function. 2751 */ 2752var listen = function listen(target, method, type, listener) { 2753 validateTarget(target); 2754 2755 if (target.nodeName) { 2756 Events[method](target, type, listener); 2757 } else { 2758 target[method](type, listener); 2759 } 2760}; 2761 2762/** 2763 * Contains methods that provide event capabilit
vendor: 8,396 bytes, lines 2763-3005
2763es to an object which is passed 2764 * to {@link module:evented|evented}. 2765 * 2766 * @mixin EventedMixin 2767 */ 2768var EventedMixin = { 2769 2770 /** 2771 * Add a listener to an event (or events) on this object or another evented 2772 * object. 2773 * 2774 * @param {string|Array|Element|Object} targetOrType 2775 * If this is a string or array, it represents the event type(s) 2776 * that will trigger the listener. 2777 * 2778 * Another evented object can be passed here instead, which will 2779 * cause the listener to listen for events on _that_ object. 2780 * 2781 * In either case, the listener's `this` value will be bound to 2782 * this object. 2783 * 2784 * @param {string|Array|Function} typeOrListener 2785 * If the first argument was a string or array, this should be the 2786 * listener function. Otherwise, this is a string or array of event 2787 * type(s). 2788 * 2789 * @param {Function} [listener] 2790 * If the first argument was another evented object, this will be 2791 * the listener function. 2792 */ 2793 on: function on$$1() { 2794 var _this = this; 2795 2796 for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { 2797 args[_key] = arguments[_key]; 2798 } 2799 2800 var _normalizeListenArgs = normalizeListenArgs(this, args), 2801 isTargetingSelf = _normalizeListenArgs.isTargetingSelf, 2802 target = _normalizeListenArgs.target, 2803 type = _normalizeListenArgs.type, 2804 listener = _normalizeListenArgs.listener; 2805 2806 listen(target, 'on', type, listener); 2807 2808 // If this object is listening to another evented object. 2809 if (!isTargetingSelf) { 2810 2811 // If this object is disposed, remove the listener. 2812 var removeListenerOnDispose = function removeListenerOnDispose() { 2813 return _this.off(target, type, listener); 2814 }; 2815 2816 // Use the same function ID as the listener so we can remove it later it 2817 // using the ID of the original listener. 2818 removeListenerOnDispose.guid = listener.guid; 2819 2820 // Add a listener to the target's dispose event as well. This ensures 2821 // that if the target is disposed BEFORE this object, we remove the 2822 // removal listener that was just added. Otherwise, we create a memory leak. 2823 var removeRemoverOnTargetDispose = function removeRemoverOnTargetDispose() { 2824 return _this.off('dispose', removeListenerOnDispose); 2825 }; 2826 2827 // Use the same function ID as the listener so we can remove it later 2828 // it using the ID of the original listener. 2829 removeRemoverOnTargetDispose.guid = listener.guid; 2830 2831 listen(this, 'on', 'dispose', removeListenerOnDispose); 2832 listen(target, 'on', 'dispose', removeRemoverOnTargetDispose); 2833 } 2834 }, 2835 2836 2837 /** 2838 * Add a listener to an event (or events) on this object or another evented 2839 * object. The listener will only be called once and then removed. 2840 * 2841 * @param {string|Array|Element|Object} targetOrType 2842 * If this is a string or array, it represents the event type(s) 2843 * that will trigger the listener. 2844 * 2845 * Another evented object can be passed here instead, which will 2846 * cause the listener to listen for events on _that_ object. 2847 * 2848 * In either case, the listener's `this` value will be bound to 2849 * this object. 2850 * 2851 * @param {string|Array|Function} typeOrListener 2852 * If the first argument was a string or array, this should be the 2853 * listener function. Otherwise, this is a string or array of event 2854 * type(s). 2855 * 2856 * @param {Function} [listener] 2857 * If the first argument was another evented object, this will be 2858 * the listener function. 2859 */ 2860 one: function one$$1() { 2861 var _this2 = this; 2862 2863 for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { 2864 args[_key2] = arguments[_key2]; 2865 } 2866 2867 var _normalizeListenArgs2 = normalizeListenArgs(this, args), 2868 isTargetingSelf = _normalizeListenArgs2.isTargetingSelf, 2869 target = _normalizeListenArgs2.target, 2870 type = _normalizeListenArgs2.type, 2871 listener = _normalizeListenArgs2.listener; 2872 2873 // Targeting this evented object. 2874 2875 2876 if (isTargetingSelf) { 2877 listen(target, 'one', type, listener); 2878 2879 // Targeting another evented object. 2880 } else { 2881 var wrapper = function wrapper() { 2882 for (var _len3 = arguments.length, largs = Array(_len3), _key3 = 0; _key3 < _len3; _key3++) { 2883 largs[_key3] = arguments[_key3]; 2884 } 2885 2886 _this2.off(target, type, wrapper); 2887 listener.apply(null, largs); 2888 }; 2889 2890 // Use the same function ID as the listener so we can remove it later 2891 // it using the ID of the original listener. 2892 wrapper.guid = listener.guid; 2893 listen(target, 'one', type, wrapper); 2894 } 2895 }, 2896 2897 2898 /** 2899 * Removes listener(s) from event(s) on an evented object. 2900 * 2901 * @param {string|Array|Element|Object} [targetOrType] 2902 * If this is a string or array, it represents the event type(s). 2903 * 2904 * Another evented object can be passed here instead, in which case 2905 * ALL 3 arguments are _required_. 2906 * 2907 * @param {string|Array|Function} [typeOrListener] 2908 * If the first argument was a string or array, this may be the 2909 * listener function. Otherwise, this is a string or array of event 2910 * type(s). 2911 * 2912 * @param {Function} [listener] 2913 * If the first argument was another evented object, this will be 2914 * the listener function; otherwise, _all_ listeners bound to the 2915 * event type(s) will be removed. 2916 */ 2917 off: function off$$1(targetOrType, typeOrListener, listener) { 2918 2919 // Targeting this evented object. 2920 if (!targetOrType || isValidEventType(targetOrType)) { 2921 off(this.eventBusEl_, targetOrType, typeOrListener); 2922 2923 // Targeting another evented object. 2924 } else { 2925 var target = targetOrType; 2926 var type = typeOrListener; 2927 2928 // Fail fast and in a meaningful way! 2929 validateTarget(target); 2930 validateEventType(type); 2931 validateListener(listener); 2932 2933 // Ensure there's at least a guid, even if the function hasn't been used 2934 listener = bind(this, listener); 2935 2936 // Remove the dispose listener on this evented object, which was given 2937 // the same guid as the event listener in on(). 2938 this.off('dispose', listener); 2939 2940 if (target.nodeName) { 2941 off(target, type, listener); 2942 off(target, 'dispose', listener); 2943 } else if (isEvented(target)) { 2944 target.off(type, listener); 2945 target.off('dispose', listener); 2946 } 2947 } 2948 }, 2949 2950 2951 /** 2952 * Fire an event on this evented object, causing its listeners to be called. 2953 * 2954 * @param {string|Object} event 2955 * An event type or an object with a type property. 2956 * 2957 * @param {Object} [hash] 2958 * An additional object to pass along to listeners. 2959 * 2960 * @returns {boolean} 2961 * Whether or not the default behavior was prevented. 2962 */ 2963 trigger: function trigger$$1(event, hash) { 2964 return trigger(this.eventBusEl_, event, hash); 2965 } 2966}; 2967 2968/** 2969 * Applies {@link module:evented~EventedMixin|EventedMixin} to a target object. 2970 * 2971 * @param {Object} target 2972 * The object to which to add event methods. 2973 * 2974 * @param {Object} [options={}] 2975 * Options for customizing the mixin behavior. 2976 * 2977 * @param {String} [options.eventBusKey] 2978 * By default, adds a `eventBusEl_` DOM element to the target object, 2979 * which is used as an event bus. If the target object already has a 2980 * DOM element that should be used, pass its key here. 2981 * 2982 * @return {Object} 2983 * The target object. 2984 */ 2985function evented(target) { 2986 var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 2987 var eventBusKey = options.eventBusKey; 2988 2989 // Set or create the eventBusEl_. 2990 2991 if (eventBusKey) { 2992 if (!target[eventBusKey].nodeName) { 2993 throw new Error('The eventBusKey "' + eventBusKey + '" does not refer to an element.'); 2994 } 2995 target.eventBusEl_ = target[eventBusKey]; 2996 } else { 2997 target.eventBusEl_ = createEl('span', { className: 'vjs-event-bus' }); 2998 } 2999 3000 assign(target, EventedMixin); 3001 3002 // When any evented object is disposed, it removes all its listeners. 3003 target.on('dispose', function () { 3004 target.off(); 3005
vendor: 4,465 bytes, lines 3005-3176
3005window_1.setTimeout(function () { 3006 target.eventBusEl_ = null; 3007 }, 0); 3008 }); 3009 3010 return target; 3011} 3012 3013/** 3014 * @file mixins/stateful.js 3015 * @module stateful 3016 */ 3017/** 3018 * Contains methods that provide statefulness to an object which is passed 3019 * to {@link module:stateful}. 3020 * 3021 * @mixin StatefulMixin 3022 */ 3023var StatefulMixin = { 3024 3025 /** 3026 * A hash containing arbitrary keys and values representing the state of 3027 * the object. 3028 * 3029 * @type {Object} 3030 */ 3031 state: {}, 3032 3033 /** 3034 * Set the state of an object by mutating its 3035 * {@link module:stateful~StatefulMixin.state|state} object in place. 3036 * 3037 * @fires module:stateful~StatefulMixin#statechanged 3038 * @param {Object|Function} stateUpdates 3039 * A new set of properties to shallow-merge into the plugin state. 3040 * Can be a plain object or a function returning a plain object. 3041 * 3042 * @returns {Object|undefined} 3043 * An object containing changes that occurred. If no changes 3044 * occurred, returns `undefined`. 3045 */ 3046 setState: function setState(stateUpdates) { 3047 var _this = this; 3048 3049 // Support providing the `stateUpdates` state as a function. 3050 if (typeof stateUpdates === 'function') { 3051 stateUpdates = stateUpdates(); 3052 } 3053 3054 var changes = void 0; 3055 3056 each(stateUpdates, function (value, key) { 3057 3058 // Record the change if the value is different from what's in the 3059 // current state. 3060 if (_this.state[key] !== value) { 3061 changes = changes || {}; 3062 changes[key] = { 3063 from: _this.state[key], 3064 to: value 3065 }; 3066 } 3067 3068 _this.state[key] = value; 3069 }); 3070 3071 // Only trigger "statechange" if there were changes AND we have a trigger 3072 // function. This allows us to not require that the target object be an 3073 // evented object. 3074 if (changes && isEvented(this)) { 3075 3076 /** 3077 * An event triggered on an object that is both 3078 * {@link module:stateful|stateful} and {@link module:evented|evented} 3079 * indicating that its state has changed. 3080 * 3081 * @event module:stateful~StatefulMixin#statechanged 3082 * @type {Object} 3083 * @property {Object} changes 3084 * A hash containing the properties that were changed and 3085 * the values they were changed `from` and `to`. 3086 */ 3087 this.trigger({ 3088 changes: changes, 3089 type: 'statechanged' 3090 }); 3091 } 3092 3093 return changes; 3094 } 3095}; 3096 3097/** 3098 * Applies {@link module:stateful~StatefulMixin|StatefulMixin} to a target 3099 * object. 3100 * 3101 * If the target object is {@link module:evented|evented} and has a 3102 * `handleStateChanged` method, that method will be automatically bound to the 3103 * `statechanged` event on itself. 3104 * 3105 * @param {Object} target 3106 * The object to be made stateful. 3107 * 3108 * @param {Object} [defaultState] 3109 * A default set of properties to populate the newly-stateful object's 3110 * `state` property. 3111 * 3112 * @returns {Object} 3113 * Returns the `target`. 3114 */ 3115function stateful(target, defaultState) { 3116 assign(target, StatefulMixin); 3117 3118 // This happens after the mixing-in because we need to replace the `state` 3119 // added in that step. 3120 target.state = assign({}, target.state, defaultState); 3121 3122 // Auto-bind the `handleStateChanged` method of the target object if it exists. 3123 if (typeof target.handleStateChanged === 'function' && isEvented(target)) { 3124 target.on('statechanged', target.handleStateChanged); 3125 } 3126 3127 return target; 3128} 3129 3130/** 3131 * @file to-title-case.js 3132 * @module to-title-case 3133 */ 3134 3135/** 3136 * Uppercase the first letter of a string. 3137 * 3138 * @param {string} string 3139 * String to be uppercased 3140 * 3141 * @return {string} 3142 * The string with an uppercased first letter 3143 */ 3144function toTitleCase(string) { 3145 if (typeof string !== 'string') { 3146 return string; 3147 } 3148 3149 return string.charAt(0).toUpperCase() + string.slice(1); 3150} 3151 3152/** 3153 * Compares the TitleCase versions of the two strings for equality. 3154 * 3155 * @param {string} str1 3156 * The first string to compare 3157 * 3158 * @param {string} str2 3159 * The second string to compare 3160 * 3161 * @return {boolean} 3162 * Whether the TitleCase versions of the strings are equal 3163 */ 3164function titleCaseEquals(str1, str2) { 3165 return toTitleCase(str1) === toTitleCase(str2); 3166} 3167 3168/** 3169 * @file merge-options.js 3170 * @module merge-options 3171 */ 3172/** 3173 * Deep-merge one or more options objects, recursively merging **only** plain 3174 * object properties. 3175 * 3176 * @param {Object[]} sources
3177 * One or more objects to merge into a new object. 3178 * 3179 * @returns {Object} 3180 * A new object that is the merged result of all sources. 3181 */ 3182function mergeOptions() { 3183 var result = {}; 3184 3185 for (var _len = arguments.length, sources = Array(_len), _key = 0; _key < _len; _key++) { 3186 sources[_key] = arguments[_key]; 3187 } 3188 3189 sources.forEach(function (source) { 3190 if (!source) { 3191 return; 3192 } 3193 3194 each(source, function (value, key) { 3195 if (!isPlain(value)) { 3196 result[key] = value; 3197 return; 3198 } 3199 3200 if (!isPlain(result[key])) { 3201 result[key] = {}; 3202 } 3203 3204 result[key] = mergeOptions(result[key], value); 3205 }); 3206 }); 3207 3208 return result; 3209} 3210 3211/** 3212 * Player Component - Base class for all UI objects 3213 * 3214 * @file component.js 3215 */
vendor: 30,502 bytes, lines 3215-4258
3215 3216/** 3217 * Base class for all UI Components. 3218 * Components are UI objects which represent both a javascript object and an element 3219 * in the DOM. They can be children of other components, and can have 3220 * children themselves. 3221 * 3222 * Components can also use methods from {@link EventTarget} 3223 */ 3224 3225var Component = function () { 3226 3227 /** 3228 * A callback that is called when a component is ready. Does not have any 3229 * paramters and any callback value will be ignored. 3230 * 3231 * @callback Component~ReadyCallback 3232 * @this Component 3233 */ 3234 3235 /** 3236 * Creates an instance of this class. 3237 * 3238 * @param {Player} player 3239 * The `Player` that this class should be attached to. 3240 * 3241 * @param {Object} [options] 3242 * The key/value store of player options. 3243 * 3244 * @param {Object[]} [options.children] 3245 * An array of children objects to intialize this component with. Children objects have 3246 * a name property that will be used if more than one component of the same type needs to be 3247 * added. 3248 * 3249 * @param {Component~ReadyCallback} [ready] 3250 * Function that gets called when the `Component` is ready. 3251 */ 3252 function Component(player, options, ready) { 3253 classCallCheck(this, Component); 3254 3255 3256 // The component might be the player itself and we can't pass `this` to super 3257 if (!player && this.play) { 3258 this.player_ = player = this; // eslint-disable-line 3259 } else { 3260 this.player_ = player; 3261 } 3262 3263 // Make a copy of prototype.options_ to protect against overriding defaults 3264 this.options_ = mergeOptions({}, this.options_); 3265 3266 // Updated options with supplied options 3267 options = this.options_ = mergeOptions(this.options_, options); 3268 3269 // Get ID from options or options element if one is supplied 3270 this.id_ = options.id || options.el && options.el.id; 3271 3272 // If there was no ID from the options, generate one 3273 if (!this.id_) { 3274 // Don't require the player ID function in the case of mock players 3275 var id = player && player.id && player.id() || 'no_player'; 3276 3277 this.id_ = id + '_component_' + newGUID(); 3278 } 3279 3280 this.name_ = options.name || null; 3281 3282 // Create element if one wasn't provided in options 3283 if (options.el) { 3284 this.el_ = options.el; 3285 } else if (options.createEl !== false) { 3286 this.el_ = this.createEl(); 3287 } 3288 3289 // if evented is anything except false, we want to mixin in evented 3290 if (options.evented !== false) { 3291 // Make this an evented object and use `el_`, if available, as its event bus 3292 evented(this, { eventBusKey: this.el_ ? 'el_' : null }); 3293 } 3294 stateful(this, this.constructor.defaultState); 3295 3296 this.children_ = []; 3297 this.childIndex_ = {}; 3298 this.childNameIndex_ = {}; 3299 3300 // Add any child components in options 3301 if (options.initChildren !== false) { 3302 this.initChildren(); 3303 } 3304 3305 this.ready(ready); 3306 // Don't want to trigger ready here or it will before init is actually 3307 // finished for all children that run this constructor 3308 3309 if (options.reportTouchActivity !== false) { 3310 this.enableTouchActivity(); 3311 } 3312 } 3313 3314 /** 3315 * Dispose of the `Component` and all child components. 3316 * 3317 * @fires Component#dispose 3318 */ 3319 3320 3321 Component.prototype.dispose = function dispose() { 3322 3323 /** 3324 * Triggered when a `Component` is disposed. 3325 * 3326 * @event Component#dispose 3327 * @type {EventTarget~Event} 3328 * 3329 * @property {boolean} [bubbles=false] 3330 * set to false so that the close event does not 3331 * bubble up 3332 */ 3333 this.trigger({ type: 'dispose', bubbles: false }); 3334 3335 // Dispose all children. 3336 if (this.children_) { 3337 for (var i = this.children_.length - 1; i >= 0; i--) { 3338 if (this.children_[i].dispose) { 3339 this.children_[i].dispose(); 3340 } 3341 } 3342 } 3343 3344 // Delete child references 3345 this.children_ = null; 3346 this.childIndex_ = null; 3347 this.childNameIndex_ = null; 3348 3349 if (this.el_) { 3350 // Remove element from DOM 3351 if (this.el_.parentNode) { 3352 this.el_.parentNode.removeChild(this.el_); 3353 } 3354 3355 removeData(this.el_); 3356 this.el_ = null; 3357 } 3358 3359 // remove reference to the player after disposing of the element 3360 this.player_ = null; 3361 }; 3362 3363 /** 3364 * Return the {@link Player} that the `Component` has attached to. 3365 * 3366 * @return {Player} 3367 * The player that this `Component` has attached to. 3368 */ 3369 3370 3371 Component.prototype.player = function player() { 3372 return this.player_; 3373 }; 3374 3375 /** 3376 * Deep merge of options objects with new options. 3377 * > Note: When both `obj` and `options` contain properties whose values are objects. 3378 * The two properties get merged using {@link module:mergeOptions} 3379 * 3380 * @param {Object} obj 3381 * The object that contains new options. 3382 * 3383 * @return {Object} 3384 * A new object of `this.options_` and `obj` merged together. 3385 * 3386 * @deprecated since version 5 3387 */ 3388 3389 3390 Component.prototype.options = function options(obj) { 3391 log$1.warn('this.options() has been deprecated and will be moved to the constructor in 6.0'); 3392 3393 if (!obj) { 3394 return this.options_; 3395 } 3396 3397 this.options_ = mergeOptions(this.options_, obj); 3398 return this.options_; 3399 }; 3400 3401 /** 3402 * Get the `Component`s DOM element 3403 * 3404 * @return {Element} 3405 * The DOM element for this `Component`. 3406 */ 3407 3408 3409 Component.prototype.el = function el() { 3410 return this.el_; 3411 }; 3412 3413 /** 3414 * Create the `Component`s DOM element. 3415 * 3416 * @param {string} [tagName] 3417 * Element's DOM node type. e.g. 'div' 3418 * 3419 * @param {Object} [properties] 3420 * An object of properties that should be set. 3421 * 3422 * @param {Object} [attributes] 3423 * An object of attributes that should be set. 3424 * 3425 * @return {Element} 3426 * The element that gets created. 3427 */ 3428 3429 3430 Component.prototype.createEl = function createEl$$1(tagName, properties, attributes) { 3431 return createEl(tagName, properties, attributes); 3432 }; 3433 3434 /** 3435 * Localize a string given the string in english. 3436 * 3437 * If tokens are provided, it'll try and run a simple token replacement on the provided string. 3438 * The tokens it looks for look like `{1}` with the index being 1-indexed into the tokens array. 3439 * 3440 * If a `defaultValue` is provided, it'll use that over `string`, 3441 * if a value isn't found in provided language files. 3442 * This is useful if you want to have a descriptive key for token replacement 3443 * but have a succinct localized string and not require `en.json` to be included. 3444 * 3445 * Currently, it is used for the progress bar timing. 3446 * ```js 3447 * { 3448 * "progress bar timing: currentTime={1} duration={2}": "{1} of {2}" 3449 * } 3450 * ``` 3451 * It is then used like so: 3452 * ```js 3453 * this.localize('progress bar timing: currentTime={1} duration{2}', 3454 * [this.player_.currentTime(), this.player_.duration()], 3455 * '{1} of {2}'); 3456 * ``` 3457 * 3458 * Which outputs something like: `01:23 of 24:56`. 3459 * 3460 * 3461 * @param {string} string 3462 * The string to localize and the key to lookup in the language files. 3463 * @param {string[]} [tokens] 3464 * If the current item has token replacements, provide the tokens here. 3465 * @param {string} [defaultValue] 3466 * Defaults to `string`. Can be a default value to use for token replacement 3467 * if the lookup key is needed to be separate. 3468 * 3469 * @return {string} 3470 * The localized string or if no localization exists the english string. 3471 */ 3472 3473 3474 Component.prototype.localize = function localize(string, tokens) { 3475 var defaultValue = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : string; 3476 3477 var code = this.player_.language && this.player_.language(); 3478 var languages = this.player_.languages && this.player_.languages(); 3479 var language = languages && languages[code]; 3480 var primaryCode = code && code.split('-')[0]; 3481 var primaryLang = languages && languages[primaryCode]; 3482 3483 var localizedString = defaultValue; 3484 3485 if (language && language[string]) { 3486 localizedString = language[string]; 3487 } else if (primaryLang && primaryLang[string]) { 3488 localizedString = primaryLang[string]; 3489 } 3490 3491 if (tokens) { 3492 localizedString = localizedString.replace(/\{(\d+)\}/g, function (match, index) { 3493 var value = tokens[index - 1]; 3494 var ret = value; 3495 3496 if (typeof value === 'undefined') { 3497 ret = match; 3498 } 3499 3500 return ret; 3501 }); 3502 } 3503 3504 return localizedString; 3505 }; 3506 3507 /** 3508 * Return the `Component`s DOM element. This is where children get inserted. 3509 * This will usually be the the same as the element returned in {@link Component#el}. 3510 * 3511 * @return {Element} 3512 * The content element for this `Component`. 3513 */ 3514 3515 3516 Component.prototype.contentEl = function contentEl() { 3517 return this.contentEl_ || this.el_; 3518 }; 3519 3520 /** 3521 * Get this `Component`s ID 3522 * 3523 * @return {string} 3524 * The id of this `Component` 3525 */ 3526 3527 3528 Component.prototype.id = function id() { 3529 return this.id_; 3530 }; 3531 3532 /** 3533 * Get the `Component`s name. The name gets used to reference the `Component` 3534 * and is set during registration. 3535 * 3536 * @return {string} 3537 * The name of this `Component`. 3538 */ 3539 3540 3541 Component.prototype.name = function name() { 3542 return this.name_; 3543 }; 3544 3545 /** 3546 * Get an array of all child components 3547 * 3548 * @return {Array} 3549 * The children 3550 */ 3551 3552 3553 Component.prototype.children = function children() { 3554 return this.children_; 3555 }; 3556 3557 /** 3558 * Returns the child `Component` with the given `id`. 3559 * 3560 * @param {string} id 3561 * The id of the child `Component` to get. 3562 * 3563 * @return {Component|undefined} 3564 * The child `Component` with the given `id` or undefined. 3565 */ 3566 3567 3568 Component.prototype.getChildById = function getChildById(id) { 3569 return this.childIndex_[id]; 3570 }; 3571 3572 /** 3573 * Returns the child `Component` with the given `name`. 3574 * 3575 * @param {string} name 3576 * The name of the child `Component` to get. 3577 * 3578 * @return {Component|undefined} 3579 * The child `Component` with the given `name` or undefined. 3580 */ 3581 3582 3583 Component.prototype.getChild = function getChild(name) { 3584 if (!name) { 3585 return; 3586 } 3587 3588 name = toTitleCase(name); 3589 3590 return this.childNameIndex_[name]; 3591 }; 3592 3593 /** 3594 * Add a child `Component` inside the current `Component`. 3595 * 3596 * 3597 * @param {string|Component} child 3598 * The name or instance of a child to add. 3599 * 3600 * @param {Object} [options={}] 3601 * The key/value store of options that will get passed to children of 3602 * the child. 3603 * 3604 * @param {number} [index=this.children_.length] 3605 * The index to attempt to add a child into. 3606 * 3607 * @return {Component} 3608 * The `Component` that gets added as a child. When using a string the 3609 * `Component` will get created by this process. 3610 */ 3611 3612 3613 Component.prototype.addChild = function addChild(child) { 3614 var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 3615 var index = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : this.children_.length; 3616 3617 var component = void 0; 3618 var componentName = void 0; 3619 3620 // If child is a string, create component with options 3621 if (typeof child === 'string') { 3622 componentName = toTitleCase(child); 3623 3624 var componentClassName = options.componentClass || componentName; 3625 3626 // Set name through options 3627 options.name = componentName; 3628 3629 // Create a new object & element for this controls set 3630 // If there's no .player_, this is a player 3631 var ComponentClass = Component.getComponent(componentClassName); 3632 3633 if (!ComponentClass) { 3634 throw new Error('Component ' + componentClassName + ' does not exist'); 3635 } 3636 3637 // data stored directly on the videojs object may be 3638 // misidentified as a component to retain 3639 // backwards-compatibility with 4.x. check to make sure the 3640 // component class can be instantiated. 3641 if (typeof ComponentClass !== 'function') { 3642 return null; 3643 } 3644 3645 component = new ComponentClass(this.player_ || this, options); 3646 3647 // child is a component instance 3648 } else { 3649 component = child; 3650 } 3651 3652 this.children_.splice(index, 0, component); 3653 3654 if (typeof component.id === 'function') { 3655 this.childIndex_[component.id()] = component; 3656 } 3657 3658 // If a name wasn't used to create the component, check if we can use the 3659 // name function of the component 3660 componentName = componentName || component.name && toTitleCase(component.name()); 3661 3662 if (componentName) { 3663 this.childNameIndex_[componentName] = component; 3664 } 3665 3666 // Add the UI object's element to the container div (box) 3667 // Having an element is not required 3668 if (typeof component.el === 'function' && component.el()) { 3669 var childNodes = this.contentEl().children; 3670 var refNode = childNodes[index] || null; 3671 3672 this.contentEl().insertBefore(component.el(), refNode); 3673 } 3674 3675 // Return so it can stored on parent object if desired. 3676 return component; 3677 }; 3678 3679 /** 3680 * Remove a child `Component` from this `Component`s list of children. Also removes 3681 * the child `Component`s element from this `Component`s element. 3682 * 3683 * @param {Component} component 3684 * The child `Component` to remove. 3685 */ 3686 3687 3688 Component.prototype.removeChild = function removeChild(component) { 3689 if (typeof component === 'string') { 3690 component = this.getChild(component); 3691 } 3692 3693 if (!component || !this.children_) { 3694 return; 3695 } 3696 3697 var childFound = false; 3698 3699 for (var i = this.children_.length - 1; i >= 0; i--) { 3700 if (this.children_[i] === component) { 3701 childFound = true; 3702 this.children_.splice(i, 1); 3703 break; 3704 } 3705 } 3706 3707 if (!childFound) { 3708 return; 3709 } 3710 3711 this.childIndex_[component.id()] = null; 3712 this.childNameIndex_[component.name()] = null; 3713 3714 var compEl = component.el(); 3715 3716 if (compEl && compEl.parentNode === this.contentEl()) { 3717 this.contentEl().removeChild(component.el()); 3718 } 3719 }; 3720 3721 /** 3722 * Add and initialize default child `Component`s based upon options. 3723 */ 3724 3725 3726 Component.prototype.initChildren = function initChildren() { 3727 var _this = this; 3728 3729 var children = this.options_.children; 3730 3731 if (children) { 3732 // `this` is `parent` 3733 var parentOptions = this.options_; 3734 3735 var handleAdd = function handleAdd(child) { 3736 var name = child.name; 3737 var opts = child.opts; 3738 3739 // Allow options for children to be set at the parent options 3740 // e.g. videojs(id, { controlBar: false }); 3741 // instead of videojs(id, { children: { controlBar: false }); 3742 if (parentOptions[name] !== undefined) { 3743 opts = parentOptions[name]; 3744 } 3745 3746 // Allow for disabling default components 3747 // e.g. options['children']['posterImage'] = false 3748 if (opts === false) { 3749 return; 3750 } 3751 3752 // Allow options to be passed as a simple boolean if no configuration 3753 // is necessary. 3754 if (opts === true) { 3755 opts = {}; 3756 } 3757 3758 // We also want to pass the original player options 3759 // to each component as well so they don't need to 3760 // reach back into the player for options later. 3761 opts.playerOptions = _this.options_.playerOptions; 3762 3763 // Create and add the child component. 3764 // Add a direct reference to the child by name on the parent instance. 3765 // If two of the same component are used, different names should be supplied 3766 // for each 3767 var newChild = _this.addChild(name, opts); 3768 3769 if (newChild) { 3770 _this[name] = newChild; 3771 } 3772 }; 3773 3774 // Allow for an array of children details to passed in the options 3775 var workingChildren = void 0; 3776 var Tech = Component.getComponent('Tech'); 3777 3778 if (Array.isArray(children)) { 3779 workingChildren = children; 3780 } else { 3781 workingChildren = Object.keys(children); 3782 } 3783 3784 workingChildren 3785 // children that are in this.options_ but also in workingChildren would 3786 // give us extra children we do not want. So, we want to filter them out. 3787 .concat(Object.keys(this.options_).filter(function (child) { 3788 return !workingChildren.some(function (wchild) { 3789 if (typeof wchild === 'string') { 3790 return child === wchild; 3791 } 3792 return child === wchild.name; 3793 }); 3794 })).map(function (child) { 3795 var name = void 0; 3796 var opts = void 0; 3797 3798 if (typeof child === 'string') { 3799 name = child; 3800 opts = children[name] || _this.options_[name] || {}; 3801 } else { 3802 name = child.name; 3803 opts = child; 3804 } 3805 3806 return { name: name, opts: opts }; 3807 }).filter(function (child) { 3808 // we have to make sure that child.name isn't in the techOrder since 3809 // techs are registerd as Components but can't aren't compatible 3810 // See https://github.com/videojs/video.js/issues/2772 3811 var c = Component.getComponent(child.opts.componentClass || toTitleCase(child.name)); 3812 3813 return c && !Tech.isTech(c); 3814 }).forEach(handleAdd); 3815 } 3816 }; 3817 3818 /** 3819 * Builds the default DOM class name. Should be overriden by sub-components. 3820 * 3821 * @return {string} 3822 * The DOM class name for this object. 3823 * 3824 * @abstract 3825 */ 3826 3827 3828 Component.prototype.buildCSSClass = function buildCSSClass() { 3829 // Child classes can include a function that does: 3830 // return 'CLASS NAME' + this._super(); 3831 return ''; 3832 }; 3833 3834 /** 3835 * Bind a listener to the component's ready state. 3836 * Different from event listeners in that if the ready event has already happened 3837 * it will trigger the function immediately. 3838 * 3839 * @return {Component} 3840 * Returns itself; method can be chained. 3841 */ 3842 3843 3844 Component.prototype.ready = function ready(fn) { 3845 var sync = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; 3846 3847 if (!fn) { 3848 return; 3849 } 3850 3851 if (!this.isReady_) { 3852 this.readyQueue_ = this.readyQueue_ || []; 3853 this.readyQueue_.push(fn); 3854 return; 3855 } 3856 3857 if (sync) { 3858 fn.call(this); 3859 } else { 3860 // Call the function asynchronously by default for consistency 3861 this.setTimeout(fn, 1); 3862 } 3863 }; 3864 3865 /** 3866 * Trigger all the ready listeners for this `Component`. 3867 * 3868 * @fires Component#ready 3869 */ 3870 3871 3872 Component.prototype.triggerReady = function triggerReady() { 3873 this.isReady_ = true; 3874 3875 // Ensure ready is triggered asynchronously 3876 this.setTimeout(function () { 3877 var readyQueue = this.readyQueue_; 3878 3879 // Reset Ready Queue 3880 this.readyQueue_ = []; 3881 3882 if (readyQueue && readyQueue.length > 0) { 3883 readyQueue.forEach(function (fn) { 3884 fn.call(this); 3885 }, this); 3886 } 3887 3888 // Allow for using event listeners also 3889 /** 3890 * Triggered when a `Component` is ready. 3891 * 3892 * @event Component#ready 3893 * @type {EventTarget~Event} 3894 */ 3895 this.trigger('ready'); 3896 }, 1); 3897 }; 3898 3899 /** 3900 * Find a single DOM element matching a `selector`. This can be within the `Component`s 3901 * `contentEl()` or another custom context. 3902 * 3903 * @param {string} selector 3904 * A valid CSS selector, which will be passed to `querySelector`. 3905 * 3906 * @param {Element|string} [context=this.contentEl()] 3907 * A DOM element within which to query. Can also be a selector string in 3908 * which case the first matching element will get used as context. If 3909 * missing `this.contentEl()` gets used. If `this.contentEl()` returns 3910 * nothing it falls back to `document`. 3911 * 3912 * @return {Element|null} 3913 * the dom element that was found, or null 3914 * 3915 * @see [Information on CSS Selectors](https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Getting_Started/Selectors) 3916 */ 3917 3918 3919 Component.prototype.$ = function $$$1(selector, context) { 3920 return $(selector, context || this.contentEl()); 3921 }; 3922 3923 /** 3924 * Finds all DOM element matching a `selector`. This can be within the `Component`s 3925 * `contentEl()` or another custom context. 3926 * 3927 * @param {string} selector 3928 * A valid CSS selector, which will be passed to `querySelectorAll`. 3929 * 3930 * @param {Element|string} [context=this.contentEl()] 3931 * A DOM element within which to query. Can also be a selector string in 3932 * which case the first matching element will get used as context. If 3933 * missing `this.contentEl()` gets used. If `this.contentEl()` returns 3934 * nothing it falls back to `document`. 3935 * 3936 * @return {NodeList} 3937 * a list of dom elements that were found 3938 * 3939 * @see [Information on CSS Selectors](https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Getting_Started/Selectors) 3940 */ 3941 3942 3943 Component.prototype.$$ = function $$$$1(selector, context) { 3944 return $$(selector, context || this.contentEl()); 3945 }; 3946 3947 /** 3948 * Check if a component's element has a CSS class name. 3949 * 3950 * @param {string} classToCheck 3951 * CSS class name to check. 3952 * 3953 * @return {boolean} 3954 * - True if the `Component` has the class. 3955 * - False if the `Component` does not have the class` 3956 */ 3957 3958 3959 Component.prototype.hasClass = function hasClass$$1(classToCheck) { 3960 return hasClass(this.el_, classToCheck); 3961 }; 3962 3963 /** 3964 * Add a CSS class name to the `Component`s element. 3965 * 3966 * @param {string} classToAdd 3967 * CSS class name to add 3968 */ 3969 3970 3971 Component.prototype.addClass = function addClass$$1(classToAdd) { 3972 addClass(this.el_, classToAdd); 3973 }; 3974 3975 /** 3976 * Remove a CSS class name from the `Component`s element. 3977 * 3978 * @param {string} classToRemove 3979 * CSS class name to remove 3980 */ 3981 3982 3983 Component.prototype.removeClass = function removeClass$$1(classToRemove) { 3984 removeClass(this.el_, classToRemove); 3985 }; 3986 3987 /** 3988 * Add or remove a CSS class name from the component's element. 3989 * - `classToToggle` gets added when {@link Component#hasClass} would return false. 3990 * - `classToToggle` gets removed when {@link Component#hasClass} would return true. 3991 * 3992 * @param {string} classToToggle 3993 * The class to add or remove based on (@link Component#hasClass} 3994 * 3995 * @param {boolean|Dom~predicate} [predicate] 3996 * An {@link Dom~predicate} function or a boolean 3997 */ 3998 3999 4000 Component.prototype.toggleClass = function toggleClass$$1(classToToggle, predicate) { 4001 toggleClass(this.el_, classToToggle, predicate); 4002 }; 4003 4004 /** 4005 * Show the `Component`s element if it is hidden by removing the 4006 * 'vjs-hidden' class name from it. 4007 */ 4008 4009 4010 Component.prototype.show = function show() { 4011 this.removeClass('vjs-hidden'); 4012 }; 4013 4014 /** 4015 * Hide the `Component`s element if it is currently showing by adding the 4016 * 'vjs-hidden` class name to it. 4017 */ 4018 4019 4020 Component.prototype.hide = function hide() { 4021 this.addClass('vjs-hidden'); 4022 }; 4023 4024 /** 4025 * Lock a `Component`s element in its visible state by adding the 'vjs-lock-showing' 4026 * class name to it. Used during fadeIn/fadeOut. 4027 * 4028 * @private 4029 */ 4030 4031 4032 Component.prototype.lockShowing = function lockShowing() { 4033 this.addClass('vjs-lock-showing'); 4034 }; 4035 4036 /** 4037 * Unlock a `Component`s element from its visible state by removing the 'vjs-lock-showing' 4038 * class name from it. Used during fadeIn/fadeOut. 4039 * 4040 * @private 4041 */ 4042 4043 4044 Component.prototype.unlockShowing = function unlockShowing() { 4045 this.removeClass('vjs-lock-showing'); 4046 }; 4047 4048 /** 4049 * Get the value of an attribute on the `Component`s element. 4050 * 4051 * @param {string} attribute 4052 * Name of the attribute to get the value from. 4053 * 4054 * @return {string|null} 4055 * - The value of the attribute that was asked for. 4056 * - Can be an empty string on some browsers if the attribute does not exist 4057 * or has no value 4058 * - Most browsers will return null if the attibute does not exist or has 4059 * no value. 4060 * 4061 * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/getAttribute} 4062 */ 4063 4064 4065 Component.prototype.getAttribute = function getAttribute$$1(attribute) { 4066 return getAttribute(this.el_, attribute); 4067 }; 4068 4069 /** 4070 * Set the value of an attribute on the `Component`'s element 4071 * 4072 * @param {string} attribute 4073 * Name of the attribute to set. 4074 * 4075 * @param {string} value 4076 * Value to set the attribute to. 4077 * 4078 * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/setAttribute} 4079 */ 4080 4081 4082 Component.prototype.setAttribute = function setAttribute$$1(attribute, value) { 4083 setAttribute(this.el_, attribute, value); 4084 }; 4085 4086 /** 4087 * Remove an attribute from the `Component`s element. 4088 * 4089 * @param {string} attribute 4090 * Name of the attribute to remove. 4091 * 4092 * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/removeAttribute} 4093 */ 4094 4095 4096 Component.prototype.removeAttribute = function removeAttribute$$1(attribute) { 4097 removeAttribute(this.el_, attribute); 4098 }; 4099 4100 /** 4101 * Get or set the width of the component based upon the CSS styles. 4102 * See {@link Component#dimension} for more detailed information. 4103 * 4104 * @param {number|string} [num] 4105 * The width that you want to set postfixed with '%', 'px' or nothing. 4106 * 4107 * @param {boolean} [skipListeners] 4108 * Skip the componentresize event trigger 4109 * 4110 * @return {number|string} 4111 * The width when getting, zero if there is no width. Can be a string 4112 * postpixed with '%' or 'px'. 4113 */ 4114 4115 4116 Component.prototype.width = function width(num, skipListeners) { 4117 return this.dimension('width', num, skipListeners); 4118 }; 4119 4120 /** 4121 * Get or set the height of the component based upon the CSS styles. 4122 * See {@link Component#dimension} for more detailed information. 4123 * 4124 * @param {number|string} [num] 4125 * The height that you want to set postfixed with '%', 'px' or nothing. 4126 * 4127 * @param {boolean} [skipListeners] 4128 * Skip the componentresize event trigger 4129 * 4130 * @return {number|string} 4131 * The width when getting, zero if there is no width. Can be a string 4132 * postpixed with '%' or 'px'. 4133 */ 4134 4135 4136 Component.prototype.height = function height(num, skipListeners) { 4137 return this.dimension('height', num, skipListeners); 4138 }; 4139 4140 /** 4141 * Set both the width and height of the `Component` element at the same time. 4142 * 4143 * @param {number|string} width 4144 * Width to set the `Component`s element to. 4145 * 4146 * @param {number|string} height 4147 * Height to set the `Component`s element to. 4148 */ 4149 4150 4151 Component.prototype.dimensions = function dimensions(width, height) { 4152 // Skip componentresize listeners on width for optimization 4153 this.width(width, true); 4154 this.height(height); 4155 }; 4156 4157 /** 4158 * Get or set width or height of the `Component` element. This is the shared code 4159 * for the {@link Component#width} and {@link Component#height}. 4160 * 4161 * Things to know: 4162 * - If the width or height in an number this will return the number postfixed with 'px'. 4163 * - If the width/height is a percent this will return the percent postfixed with '%' 4164 * - Hidden elements have a width of 0 with `window.getComputedStyle`. This function 4165 * defaults to the `Component`s `style.width` and falls back to `window.getComputedStyle`. 4166 * See [this]{@link http://www.foliotek.com/devblog/getting-the-width-of-a-hidden-element-with-jquery-using-width/} 4167 * for more information 4168 * - If you want the computed style of the component, use {@link Component#currentWidth} 4169 * and {@link {Component#currentHeight} 4170 * 4171 * @fires Component#componentresize 4172 * 4173 * @param {string} widthOrHeight 4174 8 'width' or 'height' 4175 * 4176 * @param {number|string} [num] 4177 8 New dimension 4178 * 4179 * @param {boolean} [skipListeners] 4180 * Skip componentresize event trigger 4181 * 4182 * @return {number} 4183 * The dimension when getting or 0 if unset 4184 */ 4185 4186 4187 Component.prototype.dimension = function dimension(widthOrHeight, num, skipListeners) { 4188 if (num !== undefined) { 4189 // Set to zero if null or literally NaN (NaN !== NaN) 4190 if (num === null || num !== num) { 4191 num = 0; 4192 } 4193 4194 // Check if using css width/height (% or px) and adjust 4195 if (('' + num).indexOf('%') !== -1 || ('' + num).indexOf('px') !== -1) { 4196 this.el_.style[widthOrHeight] = num; 4197 } else if (num === 'auto') { 4198 this.el_.style[widthOrHeight] = ''; 4199 } else { 4200 this.el_.style[widthOrHeight] = num + 'px'; 4201 } 4202 4203 // skipListeners allows us to avoid triggering the resize event when setting both width and height 4204 if (!skipListeners) { 4205 /** 4206 * Triggered when a component is resized. 4207 * 4208 * @event Component#componentresize 4209 * @type {EventTarget~Event} 4210 */ 4211 this.trigger('componentresize'); 4212 } 4213 4214 return; 4215 } 4216 4217 // Not setting a value, so getting it 4218 // Make sure element exists 4219 if (!this.el_) { 4220 return 0; 4221 } 4222 4223 // Get dimension value from style 4224 var val = this.el_.style[widthOrHeight]; 4225 var pxIndex = val.indexOf('px'); 4226 4227 if (pxIndex !== -1) { 4228 // Return the pixel value with no 'px' 4229 return parseInt(val.slice(0, pxIndex), 10); 4230 } 4231 4232 // No px so using % or no style was set, so falling back to offsetWidth/height 4233 // If component has display:none, offset will return 0 4234 // TODO: handle display:none and no dimension style using px 4235 return parseInt(this.el_['offset' + toTitleCase(widthOrHeight)], 10); 4236 }; 4237 4238 /** 4239 * Get the width or the height of the `Component` elements computed style. Uses 4240 * `window.getComputedStyle`. 4241 * 4242 * @param {string} widthOrHeight 4243 * A string containing 'width' or 'height'. Whichever one you want to get. 4244 * 4245 * @return {number} 4246 * The dimension that gets asked for or 0 if nothing was set 4247 * for that dimension. 4248 */ 4249 4250 4251 Component.prototype.currentDimension = function currentDimension(widthOrHeight) { 4252 var computedWidthOrHeight = 0; 4253 4254 if (widthOrHeight !== 'width' && widthOrHeight !== 'height') { 4255 throw new Error('currentDimension only accepts width or height value'); 4256 } 4257 4258 if (typeof
vendor: 11,811 bytes, lines 4258-4607
4258window_1.getComputedStyle === 'function') { 4259 var computedStyle = window_1.getComputedStyle(this.el_); 4260 4261 computedWidthOrHeight = computedStyle.getPropertyValue(widthOrHeight) || computedStyle[widthOrHeight]; 4262 } 4263 4264 // remove 'px' from variable and parse as integer 4265 computedWidthOrHeight = parseFloat(computedWidthOrHeight); 4266 4267 // if the computed value is still 0, it's possible that the browser is lying 4268 // and we want to check the offset values. 4269 // This code also runs on IE8 and wherever getComputedStyle doesn't exist. 4270 if (computedWidthOrHeight === 0) { 4271 var rule = 'offset' + toTitleCase(widthOrHeight); 4272 4273 computedWidthOrHeight = this.el_[rule]; 4274 } 4275 4276 return computedWidthOrHeight; 4277 }; 4278 4279 /** 4280 * An object that contains width and height values of the `Component`s 4281 * computed style. Uses `window.getComputedStyle`. 4282 * 4283 * @typedef {Object} Component~DimensionObject 4284 * 4285 * @property {number} width 4286 * The width of the `Component`s computed style. 4287 * 4288 * @property {number} height 4289 * The height of the `Component`s computed style. 4290 */ 4291 4292 /** 4293 * Get an object that contains width and height values of the `Component`s 4294 * computed style. 4295 * 4296 * @return {Component~DimensionObject} 4297 * The dimensions of the components element 4298 */ 4299 4300 4301 Component.prototype.currentDimensions = function currentDimensions() { 4302 return { 4303 width: this.currentDimension('width'), 4304 height: this.currentDimension('height') 4305 }; 4306 }; 4307 4308 /** 4309 * Get the width of the `Component`s computed style. Uses `window.getComputedStyle`. 4310 * 4311 * @return {number} width 4312 * The width of the `Component`s computed style. 4313 */ 4314 4315 4316 Component.prototype.currentWidth = function currentWidth() { 4317 return this.currentDimension('width'); 4318 }; 4319 4320 /** 4321 * Get the height of the `Component`s computed style. Uses `window.getComputedStyle`. 4322 * 4323 * @return {number} height 4324 * The height of the `Component`s computed style. 4325 */ 4326 4327 4328 Component.prototype.currentHeight = function currentHeight() { 4329 return this.currentDimension('height'); 4330 }; 4331 4332 /** 4333 * Set the focus to this component 4334 */ 4335 4336 4337 Component.prototype.focus = function focus() { 4338 this.el_.focus(); 4339 }; 4340 4341 /** 4342 * Remove the focus from this component 4343 */ 4344 4345 4346 Component.prototype.blur = function blur() { 4347 this.el_.blur(); 4348 }; 4349 4350 /** 4351 * Emit a 'tap' events when touch event support gets detected. This gets used to 4352 * support toggling the controls through a tap on the video. They get enabled 4353 * because every sub-component would have extra overhead otherwise. 4354 * 4355 * @private 4356 * @fires Component#tap 4357 * @listens Component#touchstart 4358 * @listens Component#touchmove 4359 * @listens Component#touchleave 4360 * @listens Component#touchcancel 4361 * @listens Component#touchend 4362 */ 4363 4364 4365 Component.prototype.emitTapEvents = function emitTapEvents() { 4366 // Track the start time so we can determine how long the touch lasted 4367 var touchStart = 0; 4368 var firstTouch = null; 4369 4370 // Maximum movement allowed during a touch event to still be considered a tap 4371 // Other popular libs use anywhere from 2 (hammer.js) to 15, 4372 // so 10 seems like a nice, round number. 4373 var tapMovementThreshold = 10; 4374 4375 // The maximum length a touch can be while still being considered a tap 4376 var touchTimeThreshold = 200; 4377 4378 var couldBeTap = void 0; 4379 4380 this.on('touchstart', function (event) { 4381 // If more than one finger, don't consider treating this as a click 4382 if (event.touches.length === 1) { 4383 // Copy pageX/pageY from the object 4384 firstTouch = { 4385 pageX: event.touches[0].pageX, 4386 pageY: event.touches[0].pageY 4387 }; 4388 // Record start time so we can detect a tap vs. "touch and hold" 4389 touchStart = new Date().getTime(); 4390 // Reset couldBeTap tracking 4391 couldBeTap = true; 4392 } 4393 }); 4394 4395 this.on('touchmove', function (event) { 4396 // If more than one finger, don't consider treating this as a click 4397 if (event.touches.length > 1) { 4398 couldBeTap = false; 4399 } else if (firstTouch) { 4400 // Some devices will throw touchmoves for all but the slightest of taps. 4401 // So, if we moved only a small distance, this could still be a tap 4402 var xdiff = event.touches[0].pageX - firstTouch.pageX; 4403 var ydiff = event.touches[0].pageY - firstTouch.pageY; 4404 var touchDistance = Math.sqrt(xdiff * xdiff + ydiff * ydiff); 4405 4406 if (touchDistance > tapMovementThreshold) { 4407 couldBeTap = false; 4408 } 4409 } 4410 }); 4411 4412 var noTap = function noTap() { 4413 couldBeTap = false; 4414 }; 4415 4416 // TODO: Listen to the original target. http://youtu.be/DujfpXOKUp8?t=13m8s 4417 this.on('touchleave', noTap); 4418 this.on('touchcancel', noTap); 4419 4420 // When the touch ends, measure how long it took and trigger the appropriate 4421 // event 4422 this.on('touchend', function (event) { 4423 firstTouch = null; 4424 // Proceed only if the touchmove/leave/cancel event didn't happen 4425 if (couldBeTap === true) { 4426 // Measure how long the touch lasted 4427 var touchTime = new Date().getTime() - touchStart; 4428 4429 // Make sure the touch was less than the threshold to be considered a tap 4430 if (touchTime < touchTimeThreshold) { 4431 // Don't let browser turn this into a click 4432 event.preventDefault(); 4433 /** 4434 * Triggered when a `Component` is tapped. 4435 * 4436 * @event Component#tap 4437 * @type {EventTarget~Event} 4438 */ 4439 this.trigger('tap'); 4440 // It may be good to copy the touchend event object and change the 4441 // type to tap, if the other event properties aren't exact after 4442 // Events.fixEvent runs (e.g. event.target) 4443 } 4444 } 4445 }); 4446 }; 4447 4448 /** 4449 * This function reports user activity whenever touch events happen. This can get 4450 * turned off by any sub-components that wants touch events to act another way. 4451 * 4452 * Report user touch activity when touch events occur. User activity gets used to 4453 * determine when controls should show/hide. It is simple when it comes to mouse 4454 * events, because any mouse event should show the controls. So we capture mouse 4455 * events that bubble up to the player and report activity when that happens. 4456 * With touch events it isn't as easy as `touchstart` and `touchend` toggle player 4457 * controls. So touch events can't help us at the player level either. 4458 * 4459 * User activity gets checked asynchronously. So what could happen is a tap event 4460 * on the video turns the controls off. Then the `touchend` event bubbles up to 4461 * the player. Which, if it reported user activity, would turn the controls right 4462 * back on. We also don't want to completely block touch events from bubbling up. 4463 * Furthermore a `touchmove` event and anything other than a tap, should not turn 4464 * controls back on. 4465 * 4466 * @listens Component#touchstart 4467 * @listens Component#touchmove 4468 * @listens Component#touchend 4469 * @listens Component#touchcancel 4470 */ 4471 4472 4473 Component.prototype.enableTouchActivity = function enableTouchActivity() { 4474 // Don't continue if the root player doesn't support reporting user activity 4475 if (!this.player() || !this.player().reportUserActivity) { 4476 return; 4477 } 4478 4479 // listener for reporting that the user is active 4480 var report = bind(this.player(), this.player().reportUserActivity); 4481 4482 var touchHolding = void 0; 4483 4484 this.on('touchstart', function () { 4485 report(); 4486 // For as long as the they are touching the device or have their mouse down, 4487 // we consider them active even if they're not moving their finger or mouse. 4488 // So we want to continue to update that they are active 4489 this.clearInterval(touchHolding); 4490 // report at the same interval as activityCheck 4491 touchHolding = this.setInterval(report, 250); 4492 }); 4493 4494 var touchEnd = function touchEnd(event) { 4495 report(); 4496 // stop the interval that maintains activity if the touch is holding 4497 this.clearInterval(touchHolding); 4498 }; 4499 4500 this.on('touchmove', report); 4501 this.on('touchend', touchEnd); 4502 this.on('touchcancel', touchEnd); 4503 }; 4504 4505 /** 4506 * A callback that has no parameters and is bound into `Component`s context. 4507 * 4508 * @callback Component~GenericCallback 4509 * @this Component 4510 */ 4511 4512 /** 4513 * Creates a function that runs after an `x` millisecond timeout. This function is a 4514 * wrapper around `window.setTimeout`. There are a few reasons to use this one 4515 * instead though: 4516 * 1. It gets cleared via {@link Component#clearTimeout} when 4517 * {@link Component#dispose} gets called. 4518 * 2. The function callback will gets turned into a {@link Component~GenericCallback} 4519 * 4520 * > Note: You can't use `window.clearTimeout` on the id returned by this function. This 4521 * will cause its dispose listener not to get cleaned up! Please use 4522 * {@link Component#clearTimeout} or {@link Component#dispose} instead. 4523 * 4524 * @param {Component~GenericCallback} fn 4525 * The function that will be run after `timeout`. 4526 * 4527 * @param {number} timeout 4528 * Timeout in milliseconds to delay before executing the specified function. 4529 * 4530 * @return {number} 4531 * Returns a timeout ID that gets used to identify the timeout. It can also 4532 * get used in {@link Component#clearTimeout} to clear the timeout that 4533 * was set. 4534 * 4535 * @listens Component#dispose 4536 * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setTimeout} 4537 */ 4538 4539 4540 Component.prototype.setTimeout = function setTimeout(fn, timeout) { 4541 var _this2 = this; 4542 4543 // declare as variables so they are properly available in timeout function 4544 // eslint-disable-next-line 4545 var timeoutId, disposeFn; 4546 4547 fn = bind(this, fn); 4548 4549 timeoutId = window_1.setTimeout(function () { 4550 _this2.off('dispose', disposeFn); 4551 fn(); 4552 }, timeout); 4553 4554 disposeFn = function disposeFn() { 4555 return _this2.clearTimeout(timeoutId); 4556 }; 4557 4558 disposeFn.guid = 'vjs-timeout-' + timeoutId; 4559 4560 this.on('dispose', disposeFn); 4561 4562 return timeoutId; 4563 }; 4564 4565 /** 4566 * Clears a timeout that gets created via `window.setTimeout` or 4567 * {@link Component#setTimeout}. If you set a timeout via {@link Component#setTimeout} 4568 * use this function instead of `window.clearTimout`. If you don't your dispose 4569 * listener will not get cleaned up until {@link Component#dispose}! 4570 * 4571 * @param {number} timeoutId 4572 * The id of the timeout to clear. The return value of 4573 * {@link Component#setTimeout} or `window.setTimeout`. 4574 * 4575 * @return {number} 4576 * Returns the timeout id that was cleared. 4577 * 4578 * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/clearTimeout} 4579 */ 4580 4581 4582 Component.prototype.clearTimeout = function clearTimeout(timeoutId) { 4583 window_1.clearTimeout(timeoutId); 4584 4585 var disposeFn = function disposeFn() {}; 4586 4587 disposeFn.guid = 'vjs-timeout-' + timeoutId; 4588 4589 this.off('dispose', disposeFn); 4590 4591 return timeoutId; 4592 }; 4593 4594 /** 4595 * Creates a function that gets run every `x` milliseconds. This function is a wrapper 4596 * around `window.setInterval`. There are a few reasons to use this one instead though. 4597 * 1. It gets cleared via {@link Component#clearInterval} when 4598 * {@link Component#dispose} gets called. 4599 * 2. The function callback will be a {@link Component~GenericCallback} 4600 * 4601 * @param {Component~GenericCallback} fn 4602 * The function to run every `x` seconds. 4603 * 4604 * @param {number} interval 4605 * Execute the specified function every `x` milliseconds. 4606 * 4607 * @return {number}
4608 * Returns an id that can be used to identify the interval. It can also be be used in 4609 * {@link Component#clearInterval} to clear the interval. 4610 * 4611 * @listens Component#dispose 4612 * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setInterval} 4613 */ 4614 4615 4616 Component.prototype.setInterval = function setInterval(fn, interval) { 4617 var _this3 = this; 4618 4619 fn = bind(this, fn); 4620 4621 var intervalId = window_1.setInterval(fn, interval); 4622 4623 var disposeFn = function disposeFn() { 4624 return _this3.clearInterval(intervalId); 4625 }; 4626 4627 disposeFn.guid = 'vjs-interval-' + intervalId; 4628 4629 this.on('dispose', disposeFn); 4630 4631 return intervalId; 4632 }; 4633 4634 /** 4635 * Clears an interval that gets created via `window.setInterval` or 4636 * {@link Component#setInterval}. If you set an inteval via {@link Component#setInterval} 4637 * use this function instead of `window.clearInterval`. If you don't your dispose 4638 * listener will not get cleaned up until {@link Component#dispose}! 4639 * 4640 * @param {number} intervalId 4641 * The id of the interval to clear. The return value of 4642 * {@link Component#setInterval} or `window.setInterval`. 4643 * 4644 * @return {number} 4645 * Returns the interval id that was cleared. 4646 * 4647 * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/clearInterval} 4648 */ 4649 4650 4651 Component.prototype.clearInterval = function clearInterval(intervalId) { 4652 window_1.clearInterval(intervalId); 4653 4654 var disposeFn = function disposeFn() {}; 4655 4656 disposeFn.guid = 'vjs-interval-' + intervalId; 4657 4658 this.off('dispose', disposeFn); 4659 4660 return intervalId; 4661 }; 4662 4663 /** 4664 * Queues up a callback to be passed to requestAnimationFrame (rAF), but 4665 * with a few extra bonuses: 4666 * 4667 * - Supports browsers that do not support rAF by falling back to 4668 * {@link Component#setTimeout}. 4669 * 4670 * - The callback is turned into a {@link Component~GenericCallback} (i.e. 4671 * bound to the component). 4672 * 4673 * - Automatic cancellation of the rAF callback is handled if the component 4674 * is disposed before it is called. 4675 * 4676 * @param {Component~GenericCallback} fn 4677 * A function that will be bound to this component and executed just 4678 * before the browser's next repaint. 4679 * 4680 * @return {number} 4681 * Returns an rAF ID that gets used to identify the timeout. It can 4682 * also be used in {@link Component#cancelAnimationFrame} to cancel 4683 * the animation frame callback. 4684 * 4685 * @listens Component#dispose 4686 * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame} 4687 */ 4688 4689 4690 Component.prototype.requestAnimationFrame = function requestAnimationFrame(fn) { 4691 var _this4 = this; 4692 4693 // declare as variables so they are properly available in rAF function 4694 // eslint-disable-next-line 4695 var id, disposeFn; 4696 4697 if (this.supportsRaf_) { 4698 fn = bind(this, fn); 4699 4700 id = window_1.requestAnimationFrame(function () { 4701 _this4.off('dispose', disposeFn); 4702 fn(); 4703 }); 4704 4705 disposeFn = function disposeFn() { 4706 return _this4.cancelAnimationFrame(id); 4707 }; 4708 4709 disposeFn.guid = 'vjs-raf-' + id; 4710 this.on('dispose', disposeFn); 4711 4712 return id; 4713 } 4714 4715 // Fall back to using a timer. 4716 return this.setTimeout(fn, 1000 / 60); 4717 }; 4718 4719 /** 4720 * Cancels a queued callback passed to {@link Component#requestAnimationFrame} 4721 * (rAF). 4722 * 4723 * If you queue an rAF callback via {@link Component#requestAnimationFrame}, 4724 * use this function instead of `window.cancelAnimationFrame`. If you don't, 4725 * your dispose listener will not get cleaned up until {@link Component#dispose}! 4726 * 4727 * @param {number} id 4728 * The rAF ID to clear. The return value of {@link Component#requestAnimationFrame}. 4729 * 4730 * @return {number} 4731 * Returns the rAF ID that was cleared. 4732 * 4733 * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/window/cancelAnimationFrame} 4734 */ 4735 4736 4737 Component.prototype.cancelAnimationFrame = function cancelAnimationFrame(id) { 4738 if (this.supportsRaf_) { 4739 window_1.cancelAnimationFrame(id); 4740 4741 var disposeFn = function disposeFn() {}; 4742 4743 disposeFn.guid = 'vjs-raf-' + id; 4744 4745 this.off('dispose', disposeFn); 4746 4747 return id; 4748 } 4749 4750 // Fall back to using a timer. 4751 return this.clearTimeout(id); 4752 }; 4753 4754 /** 4755 * Register a `Component` with `videojs` given the name and the component. 4756 * 4757 * > NOTE: {@link Tech}s should not be registered as a `Component`. {@link Tech}s 4758 * should be registered using {@link Tech.registerTech} or
4759 * {@link videojs:videojs.registerTech}. 4760 * 4761 * > NOTE: This function can also be seen on videojs as 4762 * {@link videojs:videojs.registerComponent}. 4763 * 4764 * @param {string} name 4765 * The name of the `Component` to register. 4766 * 4767 * @param {Component} ComponentToRegister 4768 * The `Component` class to register. 4769 * 4770 * @return {Component} 4771 * The `Component` that was registered. 4772 */ 4773 4774 4775 Component.registerComponent = function registerComponent(name, ComponentToRegister) { 4776 if (typeof name !== 'string' || !name) { 4777 throw new Error('Illegal component name, "' + name + '"; must be a non-empty string.'); 4778 } 4779 4780 var Tech = Component.getComponent('Tech'); 4781 4782 // We need to make sure this check is only done if Tech has been registered. 4783 var isTech = Tech && Tech.isTech(ComponentToRegister); 4784 var isComp = Component === ComponentToRegister || Component.prototype.isPrototypeOf(ComponentToRegister.prototype); 4785 4786 if (isTech || !isComp) { 4787 var reason = void 0; 4788 4789 if (isTech) { 4790 reason = 'techs must be registered using Tech.registerTech()'; 4791 } else { 4792 reason = 'must be a Component subclass'; 4793 } 4794 4795 throw new Error('Illegal component, "' + name + '"; ' + reason + '.'); 4796 } 4797 4798 name = toTitleCase(name); 4799 4800 if (!Component.components_) { 4801 Component.components_ = {}; 4802 } 4803 4804 var Player = Component.getComponent('Player'); 4805 4806 if (name === 'Player' && Player && Player.players) { 4807 var players = Player.players; 4808 var playerNames = Object.keys(players); 4809 4810 // If we have players that were disposed, then their name will still be 4811 // in Players.players. So, we must loop through and verify that the value 4812 // for each item is not null. This allows registration of the Player component 4813 // after all players have been disposed or before any were created. 4814 if (players && playerNames.length > 0 && playerNames.map(function (pname) { 4815 return players[pname]; 4816 }).every(Boolean)) { 4817 throw new Error('Can not register Player component after player has been created.'); 4818 } 4819 } 4820 4821 Component.components_[name] = ComponentToRegister; 4822 4823 return ComponentToRegister; 4824 }; 4825 4826 /** 4827 * Get a `Component` based on the name it was registered with. 4828 * 4829 * @param {string} name 4830 * The Name of the component to get. 4831 * 4832 * @return {Component} 4833 * The `Component` that got registered under the given name. 4834 * 4835 * @deprecated In `videojs` 6 this will not return `Component`s that were not 4836 * registered using {@link Component.registerComponent}. Currently we 4837 * check the global `videojs` object for a `Component` name and 4838 * return that if it exists. 4839 */ 4840 4841 4842 Component.getComponent = function getComponent(name) { 4843 if (!name) { 4844 return; 4845 } 4846 4847 name = toTitleCase(name); 4848 4849 if (Component.components_ && Component.components_[name]) { 4850 return Component.components_[name]; 4851 } 4852 }; 4853 4854 return Component; 4855}(); 4856 4857/** 4858 * Whether or not this component supports `requestAnimationFrame`. 4859 * 4860 * This is exposed primarily for testing purposes. 4861 * 4862 * @private 4863 * @type {Boolean} 4864 */ 4865 4866 4867Component.prototype.supportsRaf_ = typeof window_1.requestAnimationFrame === 'function' && typeof window_1.cancelAnimationFrame === 'function'; 4868 4869Component.registerComponent('Component', Component); 4870 4871/** 4872 * @file time-ranges.js 4873 * @module time-ranges 4874 */ 4875 4876/** 4877 * Returns the time for the specified index at the start or end 4878 * of a TimeRange object. 4879 * 4880 * @function time-ranges:indexFunction 4881 * 4882 * @param {number} [index=0] 4883 * The range number to return the time for. 4884 * 4885 * @return {number} 4886 * The time that offset at the specified index. 4887 * 4888 * @depricated index must be set to a value, in the future this will throw an error. 4889 */ 4890 4891/** 4892 * An object that contains ranges of time for various reasons. 4893 * 4894 * @typedef {Object} TimeRange 4895 * 4896 * @property {number} length 4897 * The number of time ranges represented by this Object 4898 * 4899 * @property {time-ranges:indexFunction} start 4900 * Returns the time offset at which a specified time range begins. 4901 * 4902 * @property {time-ranges:indexFunction} end 4903 * Returns the time offset at which a specified time range ends. 4904 * 4905 * @see https://developer.mozilla.org/en-US/docs/Web/API/TimeRanges 4906 */ 4907
vendor: 6,234 bytes, lines 4908-5119
4908/** 4909 * Check if any of the time ranges are over the maximum index. 4910 * 4911 * @param {string} fnName 4912 * The function name to use for logging 4913 * 4914 * @param {number} index 4915 * The index to check 4916 * 4917 * @param {number} maxIndex 4918 * The maximum possible index 4919 * 4920 * @throws {Error} if the timeRanges provided are over the maxIndex 4921 */ 4922function rangeCheck(fnName, index, maxIndex) { 4923 if (typeof index !== 'number' || index < 0 || index > maxIndex) { 4924 throw new Error('Failed to execute \'' + fnName + '\' on \'TimeRanges\': The index provided (' + index + ') is non-numeric or out of bounds (0-' + maxIndex + ').'); 4925 } 4926} 4927 4928/** 4929 * Get the time for the specified index at the start or end 4930 * of a TimeRange object. 4931 * 4932 * @param {string} fnName 4933 * The function name to use for logging 4934 * 4935 * @param {string} valueIndex 4936 * The proprety that should be used to get the time. should be 'start' or 'end' 4937 * 4938 * @param {Array} ranges 4939 * An array of time ranges 4940 * 4941 * @param {Array} [rangeIndex=0] 4942 * The index to start the search at 4943 * 4944 * @return {number} 4945 * The time that offset at the specified index. 4946 * 4947 * 4948 * @depricated rangeIndex must be set to a value, in the future this will throw an error. 4949 * @throws {Error} if rangeIndex is more than the length of ranges 4950 */ 4951function getRange(fnName, valueIndex, ranges, rangeIndex) { 4952 rangeCheck(fnName, rangeIndex, ranges.length - 1); 4953 return ranges[rangeIndex][valueIndex]; 4954} 4955 4956/** 4957 * Create a time range object given ranges of time. 4958 * 4959 * @param {Array} [ranges] 4960 * An array of time ranges. 4961 */ 4962function createTimeRangesObj(ranges) { 4963 if (ranges === undefined || ranges.length === 0) { 4964 return { 4965 length: 0, 4966 start: function start() { 4967 throw new Error('This TimeRanges object is empty'); 4968 }, 4969 end: function end() { 4970 throw new Error('This TimeRanges object is empty'); 4971 } 4972 }; 4973 } 4974 return { 4975 length: ranges.length, 4976 start: getRange.bind(null, 'start', 0, ranges), 4977 end: getRange.bind(null, 'end', 1, ranges) 4978 }; 4979} 4980 4981/** 4982 * Should create a fake `TimeRange` object which mimics an HTML5 time range instance. 4983 * 4984 * @param {number|Array} start 4985 * The start of a single range or an array of ranges 4986 * 4987 * @param {number} end 4988 * The end of a single range. 4989 * 4990 * @private 4991 */ 4992function createTimeRanges(start, end) { 4993 if (Array.isArray(start)) { 4994 return createTimeRangesObj(start); 4995 } else if (start === undefined || end === undefined) { 4996 return createTimeRangesObj(); 4997 } 4998 return createTimeRangesObj([[start, end]]); 4999} 5000 5001/** 5002 * @file buffer.js 5003 * @module buffer 5004 */ 5005/** 5006 * Compute the percentage of the media that has been buffered. 5007 * 5008 * @param {TimeRange} buffered 5009 * The current `TimeRange` object representing buffered time ranges 5010 * 5011 * @param {number} duration 5012 * Total duration of the media 5013 * 5014 * @return {number} 5015 * Percent buffered of the total duration in decimal form. 5016 */ 5017function bufferedPercent(buffered, duration) { 5018 var bufferedDuration = 0; 5019 var start = void 0; 5020 var end = void 0; 5021 5022 if (!duration) { 5023 return 0; 5024 } 5025 5026 if (!buffered || !buffered.length) { 5027 buffered = createTimeRanges(0, 0); 5028 } 5029 5030 for (var i = 0; i < buffered.length; i++) { 5031 start = buffered.start(i); 5032 end = buffered.end(i); 5033 5034 // buffered end can be bigger than duration by a very small fraction 5035 if (end > duration) { 5036 end = duration; 5037 } 5038 5039 bufferedDuration += end - start; 5040 } 5041 5042 return bufferedDuration / duration; 5043} 5044 5045/** 5046 * @file fullscreen-api.js 5047 * @module fullscreen-api 5048 * @private 5049 */ 5050/** 5051 * Store the browser-specific methods for the fullscreen API. 5052 * 5053 * @type {Object} 5054 * @see [Specification]{@link https://fullscreen.spec.whatwg.org} 5055 * @see [Map Approach From Screenfull.js]{@link https://github.com/sindresorhus/screenfull.js} 5056 */ 5057var FullscreenApi = {}; 5058 5059// browser API methods 5060var apiMap = [['requestFullscreen', 'exitFullscreen', 'fullscreenElement', 'fullscreenEnabled', 'fullscreenchange', 'fullscreenerror'], 5061// WebKit 5062['webkitRequestFullscreen', 'webkitExitFullscreen', 'webkitFullscreenElement', 'webkitFullscreenEnabled', 'webkitfullscreenchange', 'webkitfullscreenerror'], 5063// Old WebKit (Safari 5.1) 5064['webkitRequestFullScreen', 'webkitCancelFullScreen', 'webkitCurrentFullScreenElement', 'webkitCancelFullScreen', 'webkitfullscreenchange', 'webkitfullscreenerror'], 5065// Mozilla 5066['mozRequestFullScreen', 'mozCancelFullScreen', 'mozFullScreenElement', 'mozFullScreenEnabled', 'mozfullscreenchange', 'mozfullscreenerror'], 5067// Microsoft 5068['msRequestFullscreen', 'msExitFullscreen', 'msFullscreenElement', 'msFullscreenEnabled', 'MSFullscreenChange', 'MSFullscreenError']]; 5069 5070var specApi = apiMap[0]; 5071var browserApi = void 0; 5072 5073// determine the supported set of functions 5074for (var i = 0; i < apiMap.length; i++) { 5075 // check for exitFullscreen function 5076 if (apiMap[i][1] in document_1) { 5077 browserApi = apiMap[i]; 5078 break; 5079 } 5080} 5081 5082// map the browser API names to the spec API names 5083if (browserApi) { 5084 for (var _i = 0; _i < browserApi.length; _i++) { 5085 FullscreenApi[specApi[_i]] = browserApi[_i]; 5086 } 5087} 5088 5089/** 5090 * @file media-error.js 5091 */ 5092/** 5093 * A Custom `MediaError` class which mimics the standard HTML5 `MediaError` class. 5094 * 5095 * @param {number|string|Object|MediaError} value 5096 * This can be of multiple types: 5097 * - number: should be a standard error code 5098 * - string: an error message (the code will be 0) 5099 * - Object: arbitrary properties 5100 * - `MediaError` (native): used to populate a video.js `MediaError` object 5101 * - `MediaError` (video.js): will return itself if it's already a 5102 * video.js `MediaError` object. 5103 * 5104 * @see [MediaError Spec]{@link https://dev.w3.org/html5/spec-author-view/video.html#mediaerror} 5105 * @see [Encrypted MediaError Spec]{@link https://www.w3.org/TR/2013/WD-encrypted-media-20130510/#error-codes} 5106 * 5107 * @class MediaError 5108 */ 5109function MediaError(value) { 5110 5111 // Allow redundant calls to this constructor to avoid having `instanceof` 5112 // checks peppered around the code. 5113 if (value instanceof MediaError) { 5114 return value; 5115 } 5116 5117 if (typeof value === 'number') { 5118 this.code = value; 5119 } else if (typeof value === 'string') {
vendor: 4,783 bytes, lines 5120-5284
5120 // default code is zero, so this is a custom error 5121 this.message = value; 5122 } else if (isObject(value)) { 5123 5124 // We assign the `code` property manually because native `MediaError` objects 5125 // do not expose it as an own/enumerable property of the object. 5126 if (typeof value.code === 'number') { 5127 this.code = value.code; 5128 } 5129 5130 assign(this, value); 5131 } 5132 5133 if (!this.message) { 5134 this.message = MediaError.defaultMessages[this.code] || ''; 5135 } 5136} 5137 5138/** 5139 * The error code that refers two one of the defined `MediaError` types 5140 * 5141 * @type {Number} 5142 */ 5143MediaError.prototype.code = 0; 5144 5145/** 5146 * An optional message that to show with the error. Message is not part of the HTML5 5147 * video spec but allows for more informative custom errors. 5148 * 5149 * @type {String} 5150 */ 5151MediaError.prototype.message = ''; 5152 5153/** 5154 * An optional status code that can be set by plugins to allow even more detail about 5155 * the error. For example a plugin might provide a specific HTTP status code and an 5156 * error message for that code. Then when the plugin gets that error this class will 5157 * know how to display an error message for it. This allows a custom message to show 5158 * up on the `Player` error overlay. 5159 * 5160 * @type {Array} 5161 */ 5162MediaError.prototype.status = null; 5163 5164/** 5165 * Errors indexed by the W3C standard. The order **CANNOT CHANGE**! See the 5166 * specification listed under {@link MediaError} for more information. 5167 * 5168 * @enum {array} 5169 * @readonly 5170 * @property {string} 0 - MEDIA_ERR_CUSTOM 5171 * @property {string} 1 - MEDIA_ERR_CUSTOM 5172 * @property {string} 2 - MEDIA_ERR_ABORTED 5173 * @property {string} 3 - MEDIA_ERR_NETWORK 5174 * @property {string} 4 - MEDIA_ERR_SRC_NOT_SUPPORTED 5175 * @property {string} 5 - MEDIA_ERR_ENCRYPTED 5176 */ 5177MediaError.errorTypes = ['MEDIA_ERR_CUSTOM', 'MEDIA_ERR_ABORTED', 'MEDIA_ERR_NETWORK', 'MEDIA_ERR_DECODE', 'MEDIA_ERR_SRC_NOT_SUPPORTED', 'MEDIA_ERR_ENCRYPTED']; 5178 5179/** 5180 * The default `MediaError` messages based on the {@link MediaError.errorTypes}. 5181 * 5182 * @type {Array} 5183 * @constant 5184 */ 5185MediaError.defaultMessages = { 5186 1: 'You aborted the media playback', 5187 2: 'A network error caused the media download to fail part-way.', 5188 3: 'The media playback was aborted due to a corruption problem or because the media used features your browser did not support.', 5189 4: 'The media could not be loaded, either because the server or network failed or because the format is not supported.', 5190 5: 'The media is encrypted and we do not have the keys to decrypt it.' 5191}; 5192 5193// Add types as properties on MediaError 5194// e.g. MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED = 4; 5195for (var errNum = 0; errNum < MediaError.errorTypes.length; errNum++) { 5196 MediaError[MediaError.errorTypes[errNum]] = errNum; 5197 // values should be accessible on both the class and instance 5198 MediaError.prototype[MediaError.errorTypes[errNum]] = errNum; 5199} 5200 5201var tuple = SafeParseTuple; 5202 5203function SafeParseTuple(obj, reviver) { 5204 var json; 5205 var error = null; 5206 5207 try { 5208 json = JSON.parse(obj, reviver); 5209 } catch (err) { 5210 error = err; 5211 } 5212 5213 return [error, json] 5214} 5215 5216/** 5217 * Returns whether an object is `Promise`-like (i.e. has a `then` method). 5218 * 5219 * @param {Object} value 5220 * An object that may or may not be `Promise`-like. 5221 * 5222 * @return {Boolean} 5223 * Whether or not the object is `Promise`-like. 5224 */ 5225function isPromise(value) { 5226 return value !== undefined && value !== null && typeof value.then === 'function'; 5227} 5228 5229/** 5230 * Silence a Promise-like object. 5231 * 5232 * This is useful for avoiding non-harmful, but potentially confusing "uncaught 5233 * play promise" rejection error messages. 5234 * 5235 * @param {Object} value 5236 * An object that may or may not be `Promise`-like. 5237 */ 5238function silencePromise(value) { 5239 if (isPromise(value)) { 5240 value.then(null, function (e) {}); 5241 } 5242} 5243 5244/** 5245 * @file text-track-list-converter.js Utilities for capturing text track state and 5246 * re-creating tracks based on a capture. 5247 * 5248 * @module text-track-list-converter 5249 */ 5250 5251/** 5252 * Examine a single {@link TextTrack} and return a JSON-compatible javascript object that 5253 * represents the {@link TextTrack}'s state. 5254 * 5255 * @param {TextTrack} track 5256 * The text track to query. 5257 * 5258 * @return {Object} 5259 * A serializable javascript representation of the TextTrack. 5260 * @private 5261 */ 5262var trackToJson_ = function trackToJson_(track) { 5263 var ret = ['kind', 'label', 'language', 'id', 'inBandMetadataTrackDispatchType', 'mode', 'src'].reduce(function (acc, prop, i) { 5264 5265 if (track[prop]) { 5266 acc[prop] = track[prop]; 5267 } 5268 5269 return acc; 5270 }, { 5271 cues: track.cues && Array.prototype.map.call(track.cues, function (cue) { 5272 return { 5273 startTime: cue.startTime, 5274 endTime: cue.endTime, 5275 text: cue.text, 5276 id: cue.id 5277 }; 5278 }) 5279 }); 5280 5281 return ret; 5282}; 5283 5284/**
5285 * Examine a {@link Tech} and return a JSON-compatible javascript array that represents the 5286 * state of all {@link TextTrack}s currently configured. The return array is compatible with 5287 * {@link text-track-list-converter:jsonToTextTracks}. 5288 * 5289 * @param {Tech} tech 5290 * The tech object to query 5291 * 5292 * @return {Array} 5293 * A serializable javascript representation of the {@link Tech}s 5294 * {@link TextTrackList}. 5295 */ 5296var textTracksToJson = function textTracksToJson(tech) { 5297 5298 var trackEls = tech.$$('track'); 5299 5300 var trackObjs = Array.prototype.map.call(trackEls, function (t) { 5301 return t.track; 5302 }); 5303 var tracks = Array.prototype.map.call(trackEls, function (trackEl) { 5304 var json = trackToJson_(trackEl.track); 5305 5306 if (trackEl.src) { 5307 json.src = trackEl.src; 5308 } 5309 return json; 5310 }); 5311 5312 return tracks.concat(Array.prototype.filter.call(tech.textTracks(), function (track) { 5313 return trackObjs.indexOf(track) === -1; 5314 }).map(trackToJson_)); 5315}; 5316 5317/** 5318 * Create a set of remote {@link TextTrack}s on a {@link Tech} based on an array of javascript 5319 * object {@link TextTrack} representations. 5320 * 5321 * @param {Array} json 5322 * An array of `TextTrack` representation objects, like those that would be 5323 * produced by `textTracksToJson`. 5324 * 5325 * @param {Tech} tech 5326 * The `Tech` to create the `TextTrack`s on. 5327 */ 5328var jsonToTextTracks = function jsonToTextTracks(json, tech) { 5329 json.forEach(function (track) { 5330 var addedTrack = tech.addRemoteTextTrack(track).track; 5331 5332 if (!track.src && track.cues) { 5333 track.cues.forEach(function (cue) { 5334 return addedTrack.addCue(cue); 5335 }); 5336 } 5337 }); 5338 5339 return tech.textTracks(); 5340}; 5341 5342var textTrackConverter = { textTracksToJson: textTracksToJson, jsonToTextTracks: jsonToTextTracks, trackToJson_: trackToJson_ }; 5343 5344/** 5345 * @file modal-dialog.js 5346 */
vendor: 12,535 bytes, lines 5346-5805
5346 5347var MODAL_CLASS_NAME = 'vjs-modal-dialog'; 5348var ESC = 27; 5349 5350/** 5351 * The `ModalDialog` displays over the video and its controls, which blocks 5352 * interaction with the player until it is closed. 5353 * 5354 * Modal dialogs include a "Close" button and will close when that button 5355 * is activated - or when ESC is pressed anywhere. 5356 * 5357 * @extends Component 5358 */ 5359 5360var ModalDialog = function (_Component) { 5361 inherits(ModalDialog, _Component); 5362 5363 /** 5364 * Create an instance of this class. 5365 * 5366 * @param {Player} player 5367 * The `Player` that this class should be attached to. 5368 * 5369 * @param {Object} [options] 5370 * The key/value store of player options. 5371 * 5372 * @param {Mixed} [options.content=undefined] 5373 * Provide customized content for this modal. 5374 * 5375 * @param {string} [options.description] 5376 * A text description for the modal, primarily for accessibility. 5377 * 5378 * @param {boolean} [options.fillAlways=false] 5379 * Normally, modals are automatically filled only the first time 5380 * they open. This tells the modal to refresh its content 5381 * every time it opens. 5382 * 5383 * @param {string} [options.label] 5384 * A text label for the modal, primarily for accessibility. 5385 * 5386 * @param {boolean} [options.temporary=true] 5387 * If `true`, the modal can only be opened once; it will be 5388 * disposed as soon as it's closed. 5389 * 5390 * @param {boolean} [options.uncloseable=false] 5391 * If `true`, the user will not be able to close the modal 5392 * through the UI in the normal ways. Programmatic closing is 5393 * still possible. 5394 */ 5395 function ModalDialog(player, options) { 5396 classCallCheck(this, ModalDialog); 5397 5398 var _this = possibleConstructorReturn(this, _Component.call(this, player, options)); 5399 5400 _this.opened_ = _this.hasBeenOpened_ = _this.hasBeenFilled_ = false; 5401 5402 _this.closeable(!_this.options_.uncloseable); 5403 _this.content(_this.options_.content); 5404 5405 // Make sure the contentEl is defined AFTER any children are initialized 5406 // because we only want the contents of the modal in the contentEl 5407 // (not the UI elements like the close button). 5408 _this.contentEl_ = createEl('div', { 5409 className: MODAL_CLASS_NAME + '-content' 5410 }, { 5411 role: 'document' 5412 }); 5413 5414 _this.descEl_ = createEl('p', { 5415 className: MODAL_CLASS_NAME + '-description vjs-control-text', 5416 id: _this.el().getAttribute('aria-describedby') 5417 }); 5418 5419 textContent(_this.descEl_, _this.description()); 5420 _this.el_.appendChild(_this.descEl_); 5421 _this.el_.appendChild(_this.contentEl_); 5422 return _this; 5423 } 5424 5425 /** 5426 * Create the `ModalDialog`'s DOM element 5427 * 5428 * @return {Element} 5429 * The DOM element that gets created. 5430 */ 5431 5432 5433 ModalDialog.prototype.createEl = function createEl$$1() { 5434 return _Component.prototype.createEl.call(this, 'div', { 5435 className: this.buildCSSClass(), 5436 tabIndex: -1 5437 }, { 5438 'aria-describedby': this.id() + '_description', 5439 'aria-hidden': 'true', 5440 'aria-label': this.label(), 5441 'role': 'dialog' 5442 }); 5443 }; 5444 5445 ModalDialog.prototype.dispose = function dispose() { 5446 this.contentEl_ = null; 5447 this.descEl_ = null; 5448 this.previouslyActiveEl_ = null; 5449 5450 _Component.prototype.dispose.call(this); 5451 }; 5452 5453 /** 5454 * Builds the default DOM `className`. 5455 * 5456 * @return {string} 5457 * The DOM `className` for this object. 5458 */ 5459 5460 5461 ModalDialog.prototype.buildCSSClass = function buildCSSClass() { 5462 return MODAL_CLASS_NAME + ' vjs-hidden ' + _Component.prototype.buildCSSClass.call(this); 5463 }; 5464 5465 /** 5466 * Handles `keydown` events on the document, looking for ESC, which closes 5467 * the modal. 5468 * 5469 * @param {EventTarget~Event} e 5470 * The keypress that triggered this event. 5471 * 5472 * @listens keydown 5473 */ 5474 5475 5476 ModalDialog.prototype.handleKeyPress = function handleKeyPress(e) { 5477 if (e.which === ESC && this.closeable()) { 5478 this.close(); 5479 } 5480 }; 5481 5482 /** 5483 * Returns the label string for this modal. Primarily used for accessibility. 5484 * 5485 * @return {string} 5486 * the localized or raw label of this modal. 5487 */ 5488 5489 5490 ModalDialog.prototype.label = function label() { 5491 return this.localize(this.options_.label || 'Modal Window'); 5492 }; 5493 5494 /** 5495 * Returns the description string for this modal. Primarily used for 5496 * accessibility. 5497 * 5498 * @return {string} 5499 * The localized or raw description of this modal. 5500 */ 5501 5502 5503 ModalDialog.prototype.description = function description() { 5504 var desc = this.options_.description || this.localize('This is a modal window.'); 5505 5506 // Append a universal closeability message if the modal is closeable. 5507 if (this.closeable()) { 5508 desc += ' ' + this.localize('This modal can be closed by pressing the Escape key or activating the close button.'); 5509 } 5510 5511 return desc; 5512 }; 5513 5514 /** 5515 * Opens the modal. 5516 * 5517 * @fires ModalDialog#beforemodalopen 5518 * @fires ModalDialog#modalopen 5519 */ 5520 5521 5522 ModalDialog.prototype.open = function open() { 5523 if (!this.opened_) { 5524 var player = this.player(); 5525 5526 /** 5527 * Fired just before a `ModalDialog` is opened. 5528 * 5529 * @event ModalDialog#beforemodalopen 5530 * @type {EventTarget~Event} 5531 */ 5532 this.trigger('beforemodalopen'); 5533 this.opened_ = true; 5534 5535 // Fill content if the modal has never opened before and 5536 // never been filled. 5537 if (this.options_.fillAlways || !this.hasBeenOpened_ && !this.hasBeenFilled_) { 5538 this.fill(); 5539 } 5540 5541 // If the player was playing, pause it and take note of its previously 5542 // playing state. 5543 this.wasPlaying_ = !player.paused(); 5544 5545 if (this.options_.pauseOnOpen && this.wasPlaying_) { 5546 player.pause(); 5547 } 5548 5549 if (this.closeable()) { 5550 this.on(this.el_.ownerDocument, 'keydown', bind(this, this.handleKeyPress)); 5551 } 5552 5553 // Hide controls and note if they were enabled. 5554 this.hadControls_ = player.controls(); 5555 player.controls(false); 5556 5557 this.show(); 5558 this.conditionalFocus_(); 5559 this.el().setAttribute('aria-hidden', 'false'); 5560 5561 /** 5562 * Fired just after a `ModalDialog` is opened. 5563 * 5564 * @event ModalDialog#modalopen 5565 * @type {EventTarget~Event} 5566 */ 5567 this.trigger('modalopen'); 5568 this.hasBeenOpened_ = true; 5569 } 5570 }; 5571 5572 /** 5573 * If the `ModalDialog` is currently open or closed. 5574 * 5575 * @param {boolean} [value] 5576 * If given, it will open (`true`) or close (`false`) the modal. 5577 * 5578 * @return {boolean} 5579 * the current open state of the modaldialog 5580 */ 5581 5582 5583 ModalDialog.prototype.opened = function opened(value) { 5584 if (typeof value === 'boolean') { 5585 this[value ? 'open' : 'close'](); 5586 } 5587 return this.opened_; 5588 }; 5589 5590 /** 5591 * Closes the modal, does nothing if the `ModalDialog` is 5592 * not open. 5593 * 5594 * @fires ModalDialog#beforemodalclose 5595 * @fires ModalDialog#modalclose 5596 */ 5597 5598 5599 ModalDialog.prototype.close = function close() { 5600 if (!this.opened_) { 5601 return; 5602 } 5603 var player = this.player(); 5604 5605 /** 5606 * Fired just before a `ModalDialog` is closed. 5607 * 5608 * @event ModalDialog#beforemodalclose 5609 * @type {EventTarget~Event} 5610 */ 5611 this.trigger('beforemodalclose'); 5612 this.opened_ = false; 5613 5614 if (this.wasPlaying_ && this.options_.pauseOnOpen) { 5615 player.play(); 5616 } 5617 5618 if (this.closeable()) { 5619 this.off(this.el_.ownerDocument, 'keydown', bind(this, this.handleKeyPress)); 5620 } 5621 5622 if (this.hadControls_) { 5623 player.controls(true); 5624 } 5625 5626 this.hide(); 5627 this.el().setAttribute('aria-hidden', 'true'); 5628 5629 /** 5630 * Fired just after a `ModalDialog` is closed. 5631 * 5632 * @event ModalDialog#modalclose 5633 * @type {EventTarget~Event} 5634 */ 5635 this.trigger('modalclose'); 5636 this.conditionalBlur_(); 5637 5638 if (this.options_.temporary) { 5639 this.dispose(); 5640 } 5641 }; 5642 5643 /** 5644 * Check to see if the `ModalDialog` is closeable via the UI. 5645 * 5646 * @param {boolean} [value] 5647 * If given as a boolean, it will set the `closeable` option. 5648 * 5649 * @return {boolean} 5650 * Returns the final value of the closable option. 5651 */ 5652 5653 5654 ModalDialog.prototype.closeable = function closeable(value) { 5655 if (typeof value === 'boolean') { 5656 var closeable = this.closeable_ = !!value; 5657 var close = this.getChild('closeButton'); 5658 5659 // If this is being made closeable and has no close button, add one. 5660 if (closeable && !close) { 5661 5662 // The close button should be a child of the modal - not its 5663 // content element, so temporarily change the content element. 5664 var temp = this.contentEl_; 5665 5666 this.contentEl_ = this.el_; 5667 close = this.addChild('closeButton', { controlText: 'Close Modal Dialog' }); 5668 this.contentEl_ = temp; 5669 this.on(close, 'close', this.close); 5670 } 5671 5672 // If this is being made uncloseable and has a close button, remove it. 5673 if (!closeable && close) { 5674 this.off(close, 'close', this.close); 5675 this.removeChild(close); 5676 close.dispose(); 5677 } 5678 } 5679 return this.closeable_; 5680 }; 5681 5682 /** 5683 * Fill the modal's content element with the modal's "content" option. 5684 * The content element will be emptied before this change takes place. 5685 */ 5686 5687 5688 ModalDialog.prototype.fill = function fill() { 5689 this.fillWith(this.content()); 5690 }; 5691 5692 /** 5693 * Fill the modal's content element with arbitrary content. 5694 * The content element will be emptied before this change takes place. 5695 * 5696 * @fires ModalDialog#beforemodalfill 5697 * @fires ModalDialog#modalfill 5698 * 5699 * @param {Mixed} [content] 5700 * The same rules apply to this as apply to the `content` option. 5701 */ 5702 5703 5704 ModalDialog.prototype.fillWith = function fillWith(content) { 5705 var contentEl = this.contentEl(); 5706 var parentEl = contentEl.parentNode; 5707 var nextSiblingEl = contentEl.nextSibling; 5708 5709 /** 5710 * Fired just before a `ModalDialog` is filled with content. 5711 * 5712 * @event ModalDialog#beforemodalfill 5713 * @type {EventTarget~Event} 5714 */ 5715 this.trigger('beforemodalfill'); 5716 this.hasBeenFilled_ = true; 5717 5718 // Detach the content element from the DOM before performing 5719 // manipulation to avoid modifying the live DOM multiple times. 5720 parentEl.removeChild(contentEl); 5721 this.empty(); 5722 insertContent(contentEl, content); 5723 /** 5724 * Fired just after a `ModalDialog` is filled with content. 5725 * 5726 * @event ModalDialog#modalfill 5727 * @type {EventTarget~Event} 5728 */ 5729 this.trigger('modalfill'); 5730 5731 // Re-inject the re-filled content element. 5732 if (nextSiblingEl) { 5733 parentEl.insertBefore(contentEl, nextSiblingEl); 5734 } else { 5735 parentEl.appendChild(contentEl); 5736 } 5737 5738 // make sure that the close button is last in the dialog DOM 5739 var closeButton = this.getChild('closeButton'); 5740 5741 if (closeButton) { 5742 parentEl.appendChild(closeButton.el_); 5743 } 5744 }; 5745 5746 /** 5747 * Empties the content element. This happens anytime the modal is filled. 5748 * 5749 * @fires ModalDialog#beforemodalempty 5750 * @fires ModalDialog#modalempty 5751 */ 5752 5753 5754 ModalDialog.prototype.empty = function empty() { 5755 /** 5756 * Fired just before a `ModalDialog` is emptied. 5757 * 5758 * @event ModalDialog#beforemodalempty 5759 * @type {EventTarget~Event} 5760 */ 5761 this.trigger('beforemodalempty'); 5762 emptyEl(this.contentEl()); 5763 5764 /** 5765 * Fired just after a `ModalDialog` is emptied. 5766 * 5767 * @event ModalDialog#modalempty 5768 * @type {EventTarget~Event} 5769 */ 5770 this.trigger('modalempty'); 5771 }; 5772 5773 /** 5774 * Gets or sets the modal content, which gets normalized before being 5775 * rendered into the DOM. 5776 * 5777 * This does not update the DOM or fill the modal, but it is called during 5778 * that process. 5779 * 5780 * @param {Mixed} [value] 5781 * If defined, sets the internal content value to be used on the 5782 * next call(s) to `fill`. This value is normalized before being 5783 * inserted. To "clear" the internal content value, pass `null`. 5784 * 5785 * @return {Mixed} 5786 * The current content of the modal dialog 5787 */ 5788 5789 5790 ModalDialog.prototype.content = function content(value) { 5791 if (typeof value !== 'undefined') { 5792 this.content_ = value; 5793 } 5794 return this.content_; 5795 }; 5796 5797 /** 5798 * conditionally focus the modal dialog if focus was previously on the player. 5799 * 5800 * @private 5801 */ 5802 5803 5804 ModalDialog.prototype.conditionalFocus_ = function conditionalFocus_() { 5805 var activeEl =
vendor: 1,619 bytes, lines 5805-5878
5805document_1.activeElement; 5806 var playerEl = this.player_.el_; 5807 5808 this.previouslyActiveEl_ = null; 5809 5810 if (playerEl.contains(activeEl) || playerEl === activeEl) { 5811 this.previouslyActiveEl_ = activeEl; 5812 5813 this.focus(); 5814 5815 this.on(document_1, 'keydown', this.handleKeyDown); 5816 } 5817 }; 5818 5819 /** 5820 * conditionally blur the element and refocus the last focused element 5821 * 5822 * @private 5823 */ 5824 5825 5826 ModalDialog.prototype.conditionalBlur_ = function conditionalBlur_() { 5827 if (this.previouslyActiveEl_) { 5828 this.previouslyActiveEl_.focus(); 5829 this.previouslyActiveEl_ = null; 5830 } 5831 5832 this.off(document_1, 'keydown', this.handleKeyDown); 5833 }; 5834 5835 /** 5836 * Keydown handler. Attached when modal is focused. 5837 * 5838 * @listens keydown 5839 */ 5840 5841 5842 ModalDialog.prototype.handleKeyDown = function handleKeyDown(event) { 5843 // exit early if it isn't a tab key 5844 if (event.which !== 9) { 5845 return; 5846 } 5847 5848 var focusableEls = this.focusableEls_(); 5849 var activeEl = this.el_.querySelector(':focus'); 5850 var focusIndex = void 0; 5851 5852 for (var i = 0; i < focusableEls.length; i++) { 5853 if (activeEl === focusableEls[i]) { 5854 focusIndex = i; 5855 break; 5856 } 5857 } 5858 5859 if (document_1.activeElement === this.el_) { 5860 focusIndex = 0; 5861 } 5862 5863 if (event.shiftKey && focusIndex === 0) { 5864 focusableEls[focusableEls.length - 1].focus(); 5865 event.preventDefault(); 5866 } else if (!event.shiftKey && focusIndex === focusableEls.length - 1) { 5867 focusableEls[0].focus(); 5868 event.preventDefault(); 5869 } 5870 }; 5871 5872 /** 5873 * get all focusable elements 5874 * 5875 * @private 5876 */ 5877 5878
vendor: 6,242 bytes, lines 5879-6132
5879 ModalDialog.prototype.focusableEls_ = function focusableEls_() { 5880 var allChildren = this.el_.querySelectorAll('*'); 5881 5882 return Array.prototype.filter.call(allChildren, function (child) { 5883 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'); 5884 }); 5885 }; 5886 5887 return ModalDialog; 5888}(Component); 5889 5890/** 5891 * Default options for `ModalDialog` default options. 5892 * 5893 * @type {Object} 5894 * @private 5895 */ 5896 5897 5898ModalDialog.prototype.options_ = { 5899 pauseOnOpen: true, 5900 temporary: true 5901}; 5902 5903Component.registerComponent('ModalDialog', ModalDialog); 5904 5905/** 5906 * @file track-list.js 5907 */ 5908/** 5909 * Common functionaliy between {@link TextTrackList}, {@link AudioTrackList}, and 5910 * {@link VideoTrackList} 5911 * 5912 * @extends EventTarget 5913 */ 5914 5915var TrackList = function (_EventTarget) { 5916 inherits(TrackList, _EventTarget); 5917 5918 /** 5919 * Create an instance of this class 5920 * 5921 * @param {Track[]} tracks 5922 * A list of tracks to initialize the list with. 5923 * 5924 * @param {Object} [list] 5925 * The child object with inheritance done manually for ie8. 5926 * 5927 * @abstract 5928 */ 5929 function TrackList() { 5930 var tracks = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; 5931 5932 var _ret; 5933 5934 var list = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null; 5935 classCallCheck(this, TrackList); 5936 5937 var _this = possibleConstructorReturn(this, _EventTarget.call(this)); 5938 5939 if (!list) { 5940 list = _this; // eslint-disable-line 5941 if (IS_IE8) { 5942 list = document_1.createElement('custom'); 5943 for (var prop in TrackList.prototype) { 5944 if (prop !== 'constructor') { 5945 list[prop] = TrackList.prototype[prop]; 5946 } 5947 } 5948 } 5949 } 5950 5951 list.tracks_ = []; 5952 5953 /** 5954 * @memberof TrackList 5955 * @member {number} length 5956 * The current number of `Track`s in the this Trackist. 5957 * @instance 5958 */ 5959 Object.defineProperty(list, 'length', { 5960 get: function get$$1() { 5961 return this.tracks_.length; 5962 } 5963 }); 5964 5965 for (var i = 0; i < tracks.length; i++) { 5966 list.addTrack(tracks[i]); 5967 } 5968 5969 // must return the object, as for ie8 it will not be this 5970 // but a reference to a document object 5971 return _ret = list, possibleConstructorReturn(_this, _ret); 5972 } 5973 5974 /** 5975 * Add a {@link Track} to the `TrackList` 5976 * 5977 * @param {Track} track 5978 * The audio, video, or text track to add to the list. 5979 * 5980 * @fires TrackList#addtrack 5981 */ 5982 5983 5984 TrackList.prototype.addTrack = function addTrack(track) { 5985 var index = this.tracks_.length; 5986 5987 if (!('' + index in this)) { 5988 Object.defineProperty(this, index, { 5989 get: function get$$1() { 5990 return this.tracks_[index]; 5991 } 5992 }); 5993 } 5994 5995 // Do not add duplicate tracks 5996 if (this.tracks_.indexOf(track) === -1) { 5997 this.tracks_.push(track); 5998 /** 5999 * Triggered when a track is added to a track list. 6000 * 6001 * @event TrackList#addtrack 6002 * @type {EventTarget~Event} 6003 * @property {Track} track 6004 * A reference to track that was added. 6005 */ 6006 this.trigger({ 6007 track: track, 6008 type: 'addtrack' 6009 }); 6010 } 6011 }; 6012 6013 /** 6014 * Remove a {@link Track} from the `TrackList` 6015 * 6016 * @param {Track} rtrack 6017 * The audio, video, or text track to remove from the list. 6018 * 6019 * @fires TrackList#removetrack 6020 */ 6021 6022 6023 TrackList.prototype.removeTrack = function removeTrack(rtrack) { 6024 var track = void 0; 6025 6026 for (var i = 0, l = this.length; i < l; i++) { 6027 if (this[i] === rtrack) { 6028 track = this[i]; 6029 if (track.off) { 6030 track.off(); 6031 } 6032 6033 this.tracks_.splice(i, 1); 6034 6035 break; 6036 } 6037 } 6038 6039 if (!track) { 6040 return; 6041 } 6042 6043 /** 6044 * Triggered when a track is removed from track list. 6045 * 6046 * @event TrackList#removetrack 6047 * @type {EventTarget~Event} 6048 * @property {Track} track 6049 * A reference to track that was removed. 6050 */ 6051 this.trigger({ 6052 track: track, 6053 type: 'removetrack' 6054 }); 6055 }; 6056 6057 /** 6058 * Get a Track from the TrackList by a tracks id 6059 * 6060 * @param {String} id - the id of the track to get 6061 * @method getTrackById 6062 * @return {Track} 6063 * @private 6064 */ 6065 6066 6067 TrackList.prototype.getTrackById = function getTrackById(id) { 6068 var result = null; 6069 6070 for (var i = 0, l = this.length; i < l; i++) { 6071 var track = this[i]; 6072 6073 if (track.id === id) { 6074 result = track; 6075 break; 6076 } 6077 } 6078 6079 return result; 6080 }; 6081 6082 return TrackList; 6083}(EventTarget); 6084 6085/** 6086 * Triggered when a different track is selected/enabled. 6087 * 6088 * @event TrackList#change 6089 * @type {EventTarget~Event} 6090 */ 6091 6092/** 6093 * Events that can be called with on + eventName. See {@link EventHandler}. 6094 * 6095 * @property {Object} TrackList#allowedEvents_ 6096 * @private 6097 */ 6098 6099 6100TrackList.prototype.allowedEvents_ = { 6101 change: 'change', 6102 addtrack: 'addtrack', 6103 removetrack: 'removetrack' 6104}; 6105 6106// emulate attribute EventHandler support to allow for feature detection 6107for (var event in TrackList.prototype.allowedEvents_) { 6108 TrackList.prototype['on' + event] = null; 6109} 6110 6111/** 6112 * @file audio-track-list.js 6113 */ 6114/** 6115 * Anywhere we call this function we diverge from the spec 6116 * as we only support one enabled audiotrack at a time 6117 * 6118 * @param {AudioTrackList} list 6119 * list to work on 6120 * 6121 * @param {AudioTrack} track 6122 * The track to skip 6123 * 6124 * @private 6125 */ 6126var disableOthers = function disableOthers(list, track) { 6127 for (var i = 0; i < list.length; i++) { 6128 if (!Object.keys(list[i]).length || track.id === list[i].id) { 6129 continue; 6130 } 6131 // another audio track is enabled, disable it 6132 list[i].enabled = false;
6133 } 6134}; 6135 6136/** 6137 * The current list of {@link AudioTrack} for a media file. 6138 * 6139 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotracklist} 6140 * @extends TrackList 6141 */ 6142 6143var AudioTrackList = function (_TrackList) { 6144 inherits(AudioTrackList, _TrackList); 6145 6146 /** 6147 * Create an instance of this class. 6148 * 6149 * @param {AudioTrack[]} [tracks=[]] 6150 * A list of `AudioTrack` to instantiate the list with. 6151 */ 6152 function AudioTrackList() { 6153 var _this, _ret; 6154 6155 var tracks = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; 6156 classCallCheck(this, AudioTrackList); 6157 6158 var list = void 0; 6159 6160 // make sure only 1 track is enabled 6161 // sorted from last index to first index 6162 for (var i = tracks.length - 1; i >= 0; i--) { 6163 if (tracks[i].enabled) { 6164 disableOthers(tracks, tracks[i]); 6165 break; 6166 } 6167 } 6168 6169 // IE8 forces us to implement inheritance ourselves 6170 // as it does not support Object.defineProperty properly 6171 if (IS_IE8) { 6172 list = document_1.createElement('custom'); 6173 for (var prop in TrackList.prototype) { 6174 if (prop !== 'constructor') { 6175 list[prop] = TrackList.prototype[prop]; 6176 } 6177 } 6178 for (var _prop in AudioTrackList.prototype) { 6179 if (_prop !== 'constructor') { 6180 list[_prop] = AudioTrackList.prototype[_prop]; 6181 } 6182 } 6183 } 6184
6185 list = (_this = possibleConstructorReturn(this, _TrackList.call(this, tracks, list)), _this); 6186 list.changing_ = false; 6187 6188 return _ret = list, possibleConstructorReturn(_this, _ret); 6189 } 6190 6191 /** 6192 * Add an {@link AudioTrack} to the `AudioTrackList`. 6193 * 6194 * @param {AudioTrack} track 6195 * The AudioTrack to add to the list 6196 * 6197 * @fires TrackList#addtrack 6198 */ 6199 6200 6201 AudioTrackList.prototype.addTrack = function addTrack(track) { 6202 var _this2 = this; 6203 6204 if (track.enabled) { 6205 disableOthers(this, track); 6206 } 6207 6208 _TrackList.prototype.addTrack.call(this, track); 6209 // native tracks don't have this 6210 if (!track.addEventListener) { 6211 return; 6212 } 6213 6214 /** 6215 * @listens AudioTrack#enabledchange 6216 * @fires TrackList#change 6217 */ 6218 track.addEventListener('enabledchange', function () { 6219 // when we are disabling other tracks (since we don't support 6220 // more than one track at a time) we will set changing_ 6221 // to true so that we don't trigger additional change events 6222 if (_this2.changing_) { 6223 return; 6224 } 6225 _this2.changing_ = true; 6226 disableOthers(_this2, track); 6227 _this2.changing_ = false; 6228 _this2.trigger('change'); 6229 }); 6230 }; 6231 6232 return AudioTrackList; 6233}(TrackList); 6234 6235/** 6236 * @file video-track-list.js 6237 */ 6238/** 6239 * Un-select all other {@link VideoTrack}s that are selected. 6240 * 6241 * @param {VideoTrackList} list 6242 * list to work on 6243 * 6244 * @param {VideoTrack} track 6245 * The track to skip 6246 * 6247 * @private 6248 */ 6249var disableOthers$1 = function disableOthers(list, track) { 6250 for (var i = 0; i < list.length; i++) { 6251 if (!Object.keys(list[i]).length || track.id === list[i].id) { 6252 continue; 6253 } 6254 // another video track is enabled, disable it 6255 list[i].selected = false; 6256 } 6257}; 6258 6259/** 6260 * The current list of {@link VideoTrack} for a video. 6261 * 6262 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#videotracklist} 6263 * @extends TrackList 6264 */ 6265 6266var VideoTrackList = function (_TrackList) { 6267 inherits(VideoTrackList, _TrackList); 6268 6269 /** 6270 * Create an instance of this class. 6271 * 6272 * @param {VideoTrack[]} [tracks=[]] 6273 * A list of `VideoTrack` to instantiate the list with. 6274 */ 6275 function VideoTrackList() { 6276 var _this, _ret; 6277 6278 var tracks = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; 6279 classCallCheck(this, VideoTrackList); 6280 6281 var list = void 0; 6282 6283 // make sure only 1 track is enabled 6284 // sorted from last index to first index 6285 for (var i = tracks.length - 1; i >= 0; i--) { 6286 if (tracks[i].selected) { 6287 disableOthers$1(tracks, tracks[i]); 6288 break; 6289 } 6290 } 6291 6292 // IE8 forces us to implement inheritance ourselves 6293 // as it does not support Object.defineProperty properly 6294 if (IS_IE8) { 6295 list = document_1.createElement('custom'); 6296 for (var prop in TrackList.prototype) { 6297 if (prop !== 'constructor') { 6298 list[prop] = TrackList.prototype[prop]; 6299 } 6300 } 6301 for (var _prop in VideoTrackList.prototype) { 6302 if (_prop !== 'constructor') { 6303 list[_prop] = VideoTrackList.prototype[_prop]; 6304 } 6305 } 6306 } 6307
6308 list = (_this = possibleConstructorReturn(this, _TrackList.call(this, tracks, list)), _this); 6309 list.changing_ = false; 6310 6311 /** 6312 * @member {number} VideoTrackList#selectedIndex 6313 * The current index of the selected {@link VideoTrack`}. 6314 */ 6315 Object.defineProperty(list, 'selectedIndex', { 6316 get: function get$$1() { 6317 for (var _i = 0; _i < this.length; _i++) { 6318 if (this[_i].selected) { 6319 return _i; 6320 } 6321 } 6322 return -1; 6323 }, 6324 set: function set$$1() {} 6325 }); 6326 6327 return _ret = list, possibleConstructorReturn(_this, _ret); 6328 } 6329 6330 /** 6331 * Add a {@link VideoTrack} to the `VideoTrackList`. 6332 * 6333 * @param {VideoTrack} track 6334 * The VideoTrack to add to the list 6335 * 6336 * @fires TrackList#addtrack 6337 */ 6338 6339 6340 VideoTrackList.prototype.addTrack = function addTrack(track) { 6341 var _this2 = this; 6342 6343 if (track.selected) { 6344 disableOthers$1(this, track); 6345 } 6346 6347 _TrackList.prototype.addTrack.call(this, track); 6348 // native tracks don't have this 6349 if (!track.addEventListener) { 6350 return; 6351 } 6352 6353 /** 6354 * @listens VideoTrack#selectedchange 6355 * @fires TrackList#change 6356 */ 6357 track.addEventListener('selectedchange', function () { 6358 if (_this2.changing_) { 6359 return; 6360 } 6361 _this2.changing_ = true; 6362 disableOthers$1(_this2, track); 6363 _this2.changing_ = false; 6364 _this2.trigger('change'); 6365 }); 6366 }; 6367 6368 return VideoTrackList; 6369}(TrackList); 6370 6371/** 6372 * @file text-track-list.js 6373 */ 6374/** 6375 * The current list of {@link TextTrack} for a media file. 6376 * 6377 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttracklist} 6378 * @extends TrackList 6379 */ 6380 6381var TextTrackList = function (_TrackList) { 6382 inherits(TextTrackList, _TrackList); 6383 6384 /** 6385 * Create an instance of this class. 6386 * 6387 * @param {TextTrack[]} [tracks=[]] 6388 * A list of `TextTrack` to instantiate the list with. 6389 */ 6390 function TextTrackList() { 6391 var _this, _ret; 6392 6393 var tracks = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; 6394 classCallCheck(this, TextTrackList); 6395 6396 var list = void 0; 6397 6398 // IE8 forces us to implement inheritance ourselves 6399 // as it does not support Object.defineProperty properly 6400 if (IS_IE8) { 6401 list = document_1.createElement('custom'); 6402 for (var prop in TrackList.prototype) { 6403 if (prop !== 'constructor') { 6404 list[prop] = TrackList.prototype[prop]; 6405 } 6406 } 6407 for (var _prop in TextTrackList.prototype) { 6408 if (_prop !== 'constructor') { 6409 list[_prop] = TextTrackList.prototype[_prop]; 6410 } 6411 } 6412 } 6413
vendor: 7,767 bytes, lines 6414-6748
6414 list = (_this = possibleConstructorReturn(this, _TrackList.call(this, tracks, list)), _this); 6415 return _ret = list, possibleConstructorReturn(_this, _ret); 6416 } 6417 6418 /** 6419 * Add a {@link TextTrack} to the `TextTrackList` 6420 * 6421 * @param {TextTrack} track 6422 * The text track to add to the list. 6423 * 6424 * @fires TrackList#addtrack 6425 */ 6426 6427 6428 TextTrackList.prototype.addTrack = function addTrack(track) { 6429 _TrackList.prototype.addTrack.call(this, track); 6430 6431 /** 6432 * @listens TextTrack#modechange 6433 * @fires TrackList#change 6434 */ 6435 track.addEventListener('modechange', bind(this, function () { 6436 this.trigger('change'); 6437 })); 6438 6439 var nonLanguageTextTrackKind = ['metadata', 'chapters']; 6440 6441 if (nonLanguageTextTrackKind.indexOf(track.kind) === -1) { 6442 track.addEventListener('modechange', bind(this, function () { 6443 this.trigger('selectedlanguagechange'); 6444 })); 6445 } 6446 }; 6447 6448 return TextTrackList; 6449}(TrackList); 6450 6451/** 6452 * @file html-track-element-list.js 6453 */ 6454 6455/** 6456 * The current list of {@link HtmlTrackElement}s. 6457 */ 6458 6459var HtmlTrackElementList = function () { 6460 6461 /** 6462 * Create an instance of this class. 6463 * 6464 * @param {HtmlTrackElement[]} [tracks=[]] 6465 * A list of `HtmlTrackElement` to instantiate the list with. 6466 */ 6467 function HtmlTrackElementList() { 6468 var trackElements = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; 6469 classCallCheck(this, HtmlTrackElementList); 6470 6471 var list = this; // eslint-disable-line 6472 6473 if (IS_IE8) { 6474 list = document_1.createElement('custom'); 6475 6476 for (var prop in HtmlTrackElementList.prototype) { 6477 if (prop !== 'constructor') { 6478 list[prop] = HtmlTrackElementList.prototype[prop]; 6479 } 6480 } 6481 } 6482 6483 list.trackElements_ = []; 6484 6485 /** 6486 * @memberof HtmlTrackElementList 6487 * @member {number} length 6488 * The current number of `Track`s in the this Trackist. 6489 * @instance 6490 */ 6491 Object.defineProperty(list, 'length', { 6492 get: function get$$1() { 6493 return this.trackElements_.length; 6494 } 6495 }); 6496 6497 for (var i = 0, length = trackElements.length; i < length; i++) { 6498 list.addTrackElement_(trackElements[i]); 6499 } 6500 6501 if (IS_IE8) { 6502 return list; 6503 } 6504 } 6505 6506 /** 6507 * Add an {@link HtmlTrackElement} to the `HtmlTrackElementList` 6508 * 6509 * @param {HtmlTrackElement} trackElement 6510 * The track element to add to the list. 6511 * 6512 * @private 6513 */ 6514 6515 6516 HtmlTrackElementList.prototype.addTrackElement_ = function addTrackElement_(trackElement) { 6517 var index = this.trackElements_.length; 6518 6519 if (!('' + index in this)) { 6520 Object.defineProperty(this, index, { 6521 get: function get$$1() { 6522 return this.trackElements_[index]; 6523 } 6524 }); 6525 } 6526 6527 // Do not add duplicate elements 6528 if (this.trackElements_.indexOf(trackElement) === -1) { 6529 this.trackElements_.push(trackElement); 6530 } 6531 }; 6532 6533 /** 6534 * Get an {@link HtmlTrackElement} from the `HtmlTrackElementList` given an 6535 * {@link TextTrack}. 6536 * 6537 * @param {TextTrack} track 6538 * The track associated with a track element. 6539 * 6540 * @return {HtmlTrackElement|undefined} 6541 * The track element that was found or undefined. 6542 * 6543 * @private 6544 */ 6545 6546 6547 HtmlTrackElementList.prototype.getTrackElementByTrack_ = function getTrackElementByTrack_(track) { 6548 var trackElement_ = void 0; 6549 6550 for (var i = 0, length = this.trackElements_.length; i < length; i++) { 6551 if (track === this.trackElements_[i].track) { 6552 trackElement_ = this.trackElements_[i]; 6553 6554 break; 6555 } 6556 } 6557 6558 return trackElement_; 6559 }; 6560 6561 /** 6562 * Remove a {@link HtmlTrackElement} from the `HtmlTrackElementList` 6563 * 6564 * @param {HtmlTrackElement} trackElement 6565 * The track element to remove from the list. 6566 * 6567 * @private 6568 */ 6569 6570 6571 HtmlTrackElementList.prototype.removeTrackElement_ = function removeTrackElement_(trackElement) { 6572 for (var i = 0, length = this.trackElements_.length; i < length; i++) { 6573 if (trackElement === this.trackElements_[i]) { 6574 this.trackElements_.splice(i, 1); 6575 6576 break; 6577 } 6578 } 6579 }; 6580 6581 return HtmlTrackElementList; 6582}(); 6583 6584/** 6585 * @file text-track-cue-list.js 6586 */ 6587/** 6588 * @typedef {Object} TextTrackCueList~TextTrackCue 6589 * 6590 * @property {string} id 6591 * The unique id for this text track cue 6592 * 6593 * @property {number} startTime 6594 * The start time for this text track cue 6595 * 6596 * @property {number} endTime 6597 * The end time for this text track cue 6598 * 6599 * @property {boolean} pauseOnExit 6600 * Pause when the end time is reached if true. 6601 * 6602 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackcue} 6603 */ 6604 6605/** 6606 * A List of TextTrackCues. 6607 * 6608 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackcuelist} 6609 */ 6610 6611var TextTrackCueList = function () { 6612 6613 /** 6614 * Create an instance of this class.. 6615 * 6616 * @param {Array} cues 6617 * A list of cues to be initialized with 6618 */ 6619 function TextTrackCueList(cues) { 6620 classCallCheck(this, TextTrackCueList); 6621 6622 var list = this; // eslint-disable-line 6623 6624 if (IS_IE8) { 6625 list = document_1.createElement('custom'); 6626 6627 for (var prop in TextTrackCueList.prototype) { 6628 if (prop !== 'constructor') { 6629 list[prop] = TextTrackCueList.prototype[prop]; 6630 } 6631 } 6632 } 6633 6634 TextTrackCueList.prototype.setCues_.call(list, cues); 6635 6636 /** 6637 * @memberof TextTrackCueList 6638 * @member {number} length 6639 * The current number of `TextTrackCue`s in the TextTrackCueList. 6640 * @instance 6641 */ 6642 Object.defineProperty(list, 'length', { 6643 get: function get$$1() { 6644 return this.length_; 6645 } 6646 }); 6647 6648 if (IS_IE8) { 6649 return list; 6650 } 6651 } 6652 6653 /** 6654 * A setter for cues in this list. Creates getters 6655 * an an index for the cues. 6656 * 6657 * @param {Array} cues 6658 * An array of cues to set 6659 * 6660 * @private 6661 */ 6662 6663 6664 TextTrackCueList.prototype.setCues_ = function setCues_(cues) { 6665 var oldLength = this.length || 0; 6666 var i = 0; 6667 var l = cues.length; 6668 6669 this.cues_ = cues; 6670 this.length_ = cues.length; 6671 6672 var defineProp = function defineProp(index) { 6673 if (!('' + index in this)) { 6674 Object.defineProperty(this, '' + index, { 6675 get: function get$$1() { 6676 return this.cues_[index]; 6677 } 6678 }); 6679 } 6680 }; 6681 6682 if (oldLength < l) { 6683 i = oldLength; 6684 6685 for (; i < l; i++) { 6686 defineProp.call(this, i); 6687 } 6688 } 6689 }; 6690 6691 /** 6692 * Get a `TextTrackCue` that is currently in the `TextTrackCueList` by id. 6693 * 6694 * @param {string} id 6695 * The id of the cue that should be searched for. 6696 * 6697 * @return {TextTrackCueList~TextTrackCue|null} 6698 * A single cue or null if none was found. 6699 */ 6700 6701 6702 TextTrackCueList.prototype.getCueById = function getCueById(id) { 6703 var result = null; 6704 6705 for (var i = 0, l = this.length; i < l; i++) { 6706 var cue = this[i]; 6707 6708 if (cue.id === id) { 6709 result = cue; 6710 break; 6711 } 6712 } 6713 6714 return result; 6715 }; 6716 6717 return TextTrackCueList; 6718}(); 6719 6720/** 6721 * @file track-kinds.js 6722 */ 6723 6724/** 6725 * All possible `VideoTrackKind`s 6726 * 6727 * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-videotrack-kind 6728 * @typedef VideoTrack~Kind 6729 * @enum 6730 */ 6731var VideoTrackKind = { 6732 alternative: 'alternative', 6733 captions: 'captions', 6734 main: 'main', 6735 sign: 'sign', 6736 subtitles: 'subtitles', 6737 commentary: 'commentary' 6738}; 6739 6740/** 6741 * All possible `AudioTrackKind`s 6742 * 6743 * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-audiotrack-kind 6744 * @typedef AudioTrack~Kind 6745 * @enum 6746 */ 6747var AudioTrackKind = { 6748 'alternative': 'alternative',
6749 'descriptions': 'descriptions', 6750 'main': 'main', 6751 'main-desc': 'main-desc', 6752 'translation': 'translation', 6753 'commentary': 'commentary' 6754}; 6755 6756/** 6757 * All possible `TextTrackKind`s 6758 * 6759 * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-texttrack-kind 6760 * @typedef TextTrack~Kind 6761 * @enum 6762 */ 6763var TextTrackKind = { 6764 subtitles: 'subtitles', 6765 captions: 'captions', 6766 descriptions: 'descriptions', 6767 chapters: 'chapters', 6768 metadata: 'metadata' 6769}; 6770 6771/** 6772 * All possible `TextTrackMode`s 6773 * 6774 * @see https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackmode 6775 * @typedef TextTrack~Mode 6776 * @enum 6777 */ 6778var TextTrackMode = { 6779 disabled: 'disabled', 6780 hidden: 'hidden', 6781 showing: 'showing' 6782}; 6783 6784/** 6785 * @file track.js 6786 */ 6787/** 6788 * A Track class that contains all of the common functionality for {@link AudioTrack}, 6789 * {@link VideoTrack}, and {@link TextTrack}. 6790 * 6791 * > Note: This class should not be used directly 6792 * 6793 * @see {@link https://html.spec.whatwg.org/multipage/embedded-content.html} 6794 * @extends EventTarget 6795 * @abstract 6796 */ 6797 6798var Track = function (_EventTarget) { 6799 inherits(Track, _EventTarget); 6800 6801 /** 6802 * Create an instance of this class. 6803 * 6804 * @param {Object} [options={}] 6805 * Object of option names and values 6806 * 6807 * @param {string} [options.kind=''] 6808 * A valid kind for the track type you are creating. 6809 * 6810 * @param {string} [options.id='vjs_track_' + Guid.newGUID()] 6811 * A unique id for this AudioTrack. 6812 * 6813 * @param {string} [options.label=''] 6814 * The menu label for this track. 6815 * 6816 * @param {string} [options.language=''] 6817 * A valid two character language code. 6818 * 6819 * @abstract 6820 */ 6821 function Track() { 6822 var _ret; 6823 6824 var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; 6825 classCallCheck(this, Track); 6826 6827 var _this = possibleConstructorReturn(this, _EventTarget.call(this)); 6828 6829 var track = _this; // eslint-disable-line 6830 6831 if (IS_IE8) { 6832 track = document_1.createElement('custom'); 6833 for (var prop in Track.prototype) { 6834 if (prop !== 'constructor') { 6835 track[prop] = Track.prototype[prop]; 6836 } 6837 } 6838 } 6839 6840 var trackProps = { 6841 id: options.id || 'vjs_track_' + newGUID(), 6842 kind: options.kind || '', 6843 label: options.label || '', 6844 language: options.language || '' 6845 }; 6846 6847 /** 6848 * @memberof Track 6849 * @member {string} id 6850 * The id of this track. Cannot be changed after creation. 6851 * @instance 6852 * 6853 * @readonly 6854 */ 6855 6856 /** 6857 * @memberof Track 6858 * @member {string} kind 6859 * The kind of track that this is. Cannot be changed after creation. 6860 * @instance 6861 * 6862 * @readonly 6863 */ 6864 6865 /** 6866 * @memberof Track 6867 * @member {string} label 6868 * The label of this track. Cannot be changed after creation. 6869 * @instance 6870 * 6871 * @readonly 6872 */ 6873 6874 /** 6875 * @memberof Track 6876 * @member {string} language 6877 * The two letter language code for this track. Cannot be changed after 6878 * creation. 6879 * @instance 6880 * 6881 * @readonly 6882 */ 6883 6884 var _loop = function _loop(key) { 6885 Object.defineProperty(track, key, { 6886 get: function get$$1() { 6887 return trackProps[key]; 6888 }, 6889 set: function set$$1() {} 6890 }); 6891 }; 6892 6893 for (var key in trackProps) { 6894 _loop(key); 6895 } 6896 6897 return _ret = track, possibleConstructorReturn(_this, _ret); 6898 } 6899 6900 return Track; 6901}(EventTarget); 6902 6903/** 6904 * @file url.js 6905 * @module url 6906 */ 6907/** 6908 * @typedef {Object} url:URLObject 6909 * 6910 * @property {string} protocol 6911 * The protocol of the url that was parsed. 6912 * 6913 * @property {string} hostname 6914 * The hostname of the url that was parsed. 6915 * 6916 * @property {string} port 6917 * The port of the url that was parsed. 6918 * 6919 * @property {string} pathname 6920 * The pathname of the url that was parsed. 6921 * 6922 * @property {string} search 6923 * The search query of the url that was parsed. 6924 * 6925 * @property {string} hash 6926 * The hash of the url that was parsed. 6927 * 6928 * @property {string} host 6929 * The host of the url that was parsed. 6930 */ 6931 6932/** 6933 * Resolve and parse the elements of a URL. 6934 * 6935 * @param {String} url 6936 * The url to parse 6937 * 6938 * @return {url:URLObject} 6939 * An object of url details 6940 */ 6941var parseUrl = function parseUrl(url) { 6942 var props = ['protocol', 'hostname', 'port', 'pathname', 'search', 'hash', 'host']; 6943 6944 // add the url to an anchor and let the browser parse the URL 6945 var a = document_1.createElement('a'); 6946 6947 a.href = url; 6948 6949 // IE8 (and 9?) Fix
vendor: 4,164 bytes, lines 6950-7104
6950 // ie8 doesn't parse the URL correctly until the anchor is actually 6951 // added to the body, and an innerHTML is needed to trigger the parsing 6952 var addToBody = a.host === '' && a.protocol !== 'file:'; 6953 var div = void 0; 6954 6955 if (addToBody) { 6956 div = document_1.createElement('div'); 6957 div.innerHTML = '<a href="' + url + '"></a>'; 6958 a = div.firstChild; 6959 // prevent the div from affecting layout 6960 div.setAttribute('style', 'display:none; position:absolute;'); 6961 document_1.body.appendChild(div); 6962 } 6963 6964 // Copy the specific URL properties to a new object 6965 // This is also needed for IE8 because the anchor loses its 6966 // properties when it's removed from the dom 6967 var details = {}; 6968 6969 for (var i = 0; i < props.length; i++) { 6970 details[props[i]] = a[props[i]]; 6971 } 6972 6973 // IE9 adds the port to the host property unlike everyone else. If 6974 // a port identifier is added for standard ports, strip it. 6975 if (details.protocol === 'http:') { 6976 details.host = details.host.replace(/:80$/, ''); 6977 } 6978 6979 if (details.protocol === 'https:') { 6980 details.host = details.host.replace(/:443$/, ''); 6981 } 6982 6983 if (!details.protocol) { 6984 details.protocol = window_1.location.protocol; 6985 } 6986 6987 if (addToBody) { 6988 document_1.body.removeChild(div); 6989 } 6990 6991 return details; 6992}; 6993 6994/** 6995 * Get absolute version of relative URL. Used to tell flash correct URL. 6996 * 6997 * 6998 * @param {string} url 6999 * URL to make absolute 7000 * 7001 * @return {string} 7002 * Absolute URL 7003 * 7004 * @see http://stackoverflow.com/questions/470832/getting-an-absolute-url-from-a-relative-one-ie6-issue 7005 */ 7006var getAbsoluteURL = function getAbsoluteURL(url) { 7007 // Check if absolute URL 7008 if (!url.match(/^https?:\/\//)) { 7009 // Convert to absolute URL. Flash hosted off-site needs an absolute URL. 7010 var div = document_1.createElement('div'); 7011 7012 div.innerHTML = '<a href="' + url + '">x</a>'; 7013 url = div.firstChild.href; 7014 } 7015 7016 return url; 7017}; 7018 7019/** 7020 * Returns the extension of the passed file name. It will return an empty string 7021 * if passed an invalid path. 7022 * 7023 * @param {string} path 7024 * The fileName path like '/path/to/file.mp4' 7025 * 7026 * @returns {string} 7027 * The extension in lower case or an empty string if no 7028 * extension could be found. 7029 */ 7030var getFileExtension = function getFileExtension(path) { 7031 if (typeof path === 'string') { 7032 var splitPathRe = /^(\/?)([\s\S]*?)((?:\.{1,2}|[^\/]+?)(\.([^\.\/\?]+)))(?:[\/]*|[\?].*)$/i; 7033 var pathParts = splitPathRe.exec(path); 7034 7035 if (pathParts) { 7036 return pathParts.pop().toLowerCase(); 7037 } 7038 } 7039 7040 return ''; 7041}; 7042 7043/** 7044 * Returns whether the url passed is a cross domain request or not. 7045 * 7046 * @param {string} url 7047 * The url to check. 7048 * 7049 * @return {boolean} 7050 * Whether it is a cross domain request or not. 7051 */ 7052var isCrossOrigin = function isCrossOrigin(url) { 7053 var winLoc = window_1.location; 7054 var urlInfo = parseUrl(url); 7055 7056 // IE8 protocol relative urls will return ':' for protocol 7057 var srcProtocol = urlInfo.protocol === ':' ? winLoc.protocol : urlInfo.protocol; 7058 7059 // Check if url is for another domain/origin 7060 // IE8 doesn't know location.origin, so we won't rely on it here 7061 var crossOrigin = srcProtocol + urlInfo.host !== winLoc.protocol + winLoc.host; 7062 7063 return crossOrigin; 7064}; 7065 7066var Url = (Object.freeze || Object)({ 7067 parseUrl: parseUrl, 7068 getAbsoluteURL: getAbsoluteURL, 7069 getFileExtension: getFileExtension, 7070 isCrossOrigin: isCrossOrigin 7071}); 7072 7073var isFunction_1 = isFunction; 7074 7075var toString$1 = Object.prototype.toString; 7076 7077function isFunction (fn) { 7078 var string = toString$1.call(fn); 7079 return string === '[object Function]' || 7080 (typeof fn === 'function' && string !== '[object RegExp]') || 7081 (typeof window !== 'undefined' && 7082 // IE8 and below 7083 (fn === window.setTimeout || 7084 fn === window.alert || 7085 fn === window.confirm || 7086 fn === window.prompt)) 7087} 7088 7089var trim_1 = createCommonjsModule(function (module, exports) { 7090exports = module.exports = trim; 7091 7092function trim(str){ 7093 return str.replace(/^\s*|\s*$/g, ''); 7094} 7095 7096exports.left = function(str){ 7097 return str.replace(/^\s*/, ''); 7098}; 7099 7100exports.right = function(str){ 7101 return str.replace(/\s*$/, ''); 7102}; 7103}); 7104
7105var forEach_1 = forEach; 7106 7107var toString$2 = Object.prototype.toString; 7108var hasOwnProperty = Object.prototype.hasOwnProperty; 7109 7110function forEach(list, iterator, context) { 7111 if (!isFunction_1(iterator)) { 7112 throw new TypeError('iterator must be a function') 7113 } 7114 7115 if (arguments.length < 3) { 7116 context = this; 7117 } 7118 7119 if (toString$2.call(list) === '[object Array]') 7120 forEachArray$1(list, iterator, context); 7121 else if (typeof list === 'string') 7122 forEachString(list, iterator, context); 7123 else 7124 forEachObject(list, iterator, context); 7125} 7126 7127function forEachArray$1(array, iterator, context) { 7128 for (var i = 0, len = array.length; i < len; i++) { 7129 if (hasOwnProperty.call(array, i)) { 7130 iterator.call(context, array[i], i, array); 7131 } 7132 } 7133} 7134 7135function forEachString(string, iterator, context) { 7136 for (var i = 0, len = string.length; i < len; i++) { 7137 // no such thing as a sparse string. 7138 iterator.call(context, string.charAt(i), i, string); 7139 } 7140} 7141 7142function forEachObject(object, iterator, context) { 7143 for (var k in object) { 7144 if (hasOwnProperty.call(object, k)) { 7145 iterator.call(context, object[k], k, object); 7146 } 7147 } 7148} 7149 7150var isArray = function(arg) { 7151 return Object.prototype.toString.call(arg) === '[object Array]'; 7152 }; 7153 7154var parseHeaders = function (headers) { 7155 if (!headers) 7156 return {} 7157 7158 var result = {}; 7159 7160 forEach_1( 7161 trim_1(headers).split('\n') 7162 , function (row) { 7163 var index = row.indexOf(':') 7164 , key = trim_1(row.slice(0, index)).toLowerCase() 7165 , value = trim_1(row.slice(index + 1)); 7166 7167 if (typeof(result[key]) === 'undefined') { 7168 result[key] = value; 7169 } else if (isArray(result[key])) { 7170 result[key].push(value); 7171 } else { 7172 result[key] = [ result[key], value ]; 7173 } 7174 } 7175 ); 7176 7177 return result 7178}; 7179 7180var immutable = extend; 7181 7182var hasOwnProperty$1 = Object.prototype.hasOwnProperty; 7183 7184function extend() { 7185 var target = {}; 7186 7187 for (var i = 0; i < arguments.length; i++) { 7188 var source = arguments[i]; 7189 7190 for (var key in source) { 7191 if (hasOwnProperty$1.call(source, key)) { 7192 target[key] = source[key]; 7193 } 7194 } 7195 } 7196 7197 return target 7198} 7199 7200var xhr = createXHR; 7201createXHR.XMLHttpRequest = window_1.XMLHttpRequest || noop; 7202createXHR.XDomainRequest = "withCredentials" in (new createXHR.XMLHttpRequest()) ? createXHR.XMLHttpRequest : window_1.XDomainRequest; 7203 7204forEachArray(["get", "put", "post", "patch", "head", "delete"], function(method) { 7205 createXHR[method === "delete" ? "del" : method] = function(uri, options, callback) { 7206 options = initParams(uri, options, callback); 7207 options.method = method.toUpperCase(); 7208 return _createXHR(options) 7209 }; 7210}); 7211 7212function forEachArray(array, iterator) { 7213 for (var i = 0; i < array.length; i++) { 7214 iterator(array[i]); 7215 } 7216} 7217 7218function isEmpty(obj){ 7219 for(var i in obj){ 7220 if(obj.hasOwnProperty(i)) return false 7221 } 7222 return true 7223} 7224 7225function initParams(uri, options, callback) { 7226 var params = uri; 7227 7228 if (isFunction_1(options)) { 7229 callback = options; 7230 if (typeof uri === "string") { 7231 params = {uri:uri}; 7232 } 7233 } else { 7234 params = immutable(options, {uri: uri}); 7235 } 7236 7237 params.callback = callback; 7238 return params 7239} 7240 7241function createXHR(uri, options, callback) { 7242 options = initParams(uri, options, callback); 7243 return _createXHR(options) 7244} 7245 7246function _createXHR(options) { 7247 if(typeof options.callback === "undefined"){ 7248 throw new Error("callback argument missing") 7249 } 7250 7251 var called = false; 7252 var callback = function cbOnce(err, response, body){ 7253 if(!called){ 7254 called = true; 7255 options.callback(err, response, body); 7256 } 7257 }; 7258 7259 function readystatechange() { 7260 if (xhr.readyState === 4) { 7261 setTimeout(loadFunc, 0); 7262 } 7263 } 7264 7265 function getBody() { 7266 // Chrome with requestType=blob throws errors arround when even testing access to responseText 7267 var body = undefined; 7268 7269 if (xhr.response) { 7270 body = xhr.response; 7271 } else { 7272 body = xhr.responseText || getXml(xhr); 7273 } 7274 7275 if (isJson) { 7276 try { 7277 body = JSON.parse(body); 7278 } catch (e) {} 7279 } 7280 7281 return body 7282 } 7283 7284 function errorFunc(evt) { 7285 clearTimeout(timeoutTimer); 7286 if(!(evt instanceof Error)){ 7287 evt = new Error("" + (evt || "Unknown XMLHttpRequest Error") ); 7288 } 7289 evt.statusCode = 0; 7290 return callback(evt, failureResponse) 7291 } 7292 7293 // will load the data & process the response in a special response object 7294 function loadFunc() { 7295 if (aborted) return 7296 var status; 7297 clearTimeout(timeoutTimer); 7298 if(options.useXDR && xhr.status===undefined) { 7299 //IE8 CORS GET successful response doesn't have a status field, but body is fine 7300 status = 200; 7301 } else { 7302 status = (xhr.status === 1223 ? 204 : xhr.status); 7303 } 7304 var response = failureResponse; 7305 var err = null; 7306 7307 if (status !== 0){ 7308 response = { 7309 body: getBody(), 7310 statusCode: status, 7311 method: method, 7312 headers: {},
vendor: 4,867 bytes, lines 7313-7473
7313 url: uri, 7314 rawRequest: xhr 7315 }; 7316 if(xhr.getAllResponseHeaders){ //remember xhr can in fact be XDR for CORS in IE 7317 response.headers = parseHeaders(xhr.getAllResponseHeaders()); 7318 } 7319 } else { 7320 err = new Error("Internal XMLHttpRequest Error"); 7321 } 7322 return callback(err, response, response.body) 7323 } 7324 7325 var xhr = options.xhr || null; 7326 7327 if (!xhr) { 7328 if (options.cors || options.useXDR) { 7329 xhr = new createXHR.XDomainRequest(); 7330 }else{ 7331 xhr = new createXHR.XMLHttpRequest(); 7332 } 7333 } 7334 7335 var key; 7336 var aborted; 7337 var uri = xhr.url = options.uri || options.url; 7338 var method = xhr.method = options.method || "GET"; 7339 var body = options.body || options.data; 7340 var headers = xhr.headers = options.headers || {}; 7341 var sync = !!options.sync; 7342 var isJson = false; 7343 var timeoutTimer; 7344 var failureResponse = { 7345 body: undefined, 7346 headers: {}, 7347 statusCode: 0, 7348 method: method, 7349 url: uri, 7350 rawRequest: xhr 7351 }; 7352 7353 if ("json" in options && options.json !== false) { 7354 isJson = true; 7355 headers["accept"] || headers["Accept"] || (headers["Accept"] = "application/json"); //Don't override existing accept header declared by user 7356 if (method !== "GET" && method !== "HEAD") { 7357 headers["content-type"] || headers["Content-Type"] || (headers["Content-Type"] = "application/json"); //Don't override existing accept header declared by user 7358 body = JSON.stringify(options.json === true ? body : options.json); 7359 } 7360 } 7361 7362 xhr.onreadystatechange = readystatechange; 7363 xhr.onload = loadFunc; 7364 xhr.onerror = errorFunc; 7365 // IE9 must have onprogress be set to a unique function. 7366 xhr.onprogress = function () { 7367 // IE must die 7368 }; 7369 xhr.onabort = function(){ 7370 aborted = true; 7371 }; 7372 xhr.ontimeout = errorFunc; 7373 xhr.open(method, uri, !sync, options.username, options.password); 7374 //has to be after open 7375 if(!sync) { 7376 xhr.withCredentials = !!options.withCredentials; 7377 } 7378 // Cannot set timeout with sync request 7379 // not setting timeout on the xhr object, because of old webkits etc. not handling that correctly 7380 // both npm's request and jquery 1.x use this kind of timeout, so this is being consistent 7381 if (!sync && options.timeout > 0 ) { 7382 timeoutTimer = setTimeout(function(){ 7383 if (aborted) return 7384 aborted = true;//IE9 may still call readystatechange 7385 xhr.abort("timeout"); 7386 var e = new Error("XMLHttpRequest timeout"); 7387 e.code = "ETIMEDOUT"; 7388 errorFunc(e); 7389 }, options.timeout ); 7390 } 7391 7392 if (xhr.setRequestHeader) { 7393 for(key in headers){ 7394 if(headers.hasOwnProperty(key)){ 7395 xhr.setRequestHeader(key, headers[key]); 7396 } 7397 } 7398 } else if (options.headers && !isEmpty(options.headers)) { 7399 throw new Error("Headers cannot be set on an XDomainRequest object") 7400 } 7401 7402 if ("responseType" in options) { 7403 xhr.responseType = options.responseType; 7404 } 7405 7406 if ("beforeSend" in options && 7407 typeof options.beforeSend === "function" 7408 ) { 7409 options.beforeSend(xhr); 7410 } 7411 7412 // Microsoft Edge browser sends "undefined" when send is called with undefined value. 7413 // XMLHttpRequest spec says to pass null as body to indicate no body 7414 // See https://github.com/naugtur/xhr/issues/100. 7415 xhr.send(body || null); 7416 7417 return xhr 7418 7419 7420} 7421 7422function getXml(xhr) { 7423 if (xhr.responseType === "document") { 7424 return xhr.responseXML 7425 } 7426 var firefoxBugTakenEffect = xhr.responseXML && xhr.responseXML.documentElement.nodeName === "parsererror"; 7427 if (xhr.responseType === "" && !firefoxBugTakenEffect) { 7428 return xhr.responseXML 7429 } 7430 7431 return null 7432} 7433 7434function noop() {} 7435 7436/** 7437 * @file text-track.js 7438 */ 7439/** 7440 * Takes a webvtt file contents and parses it into cues 7441 * 7442 * @param {string} srcContent 7443 * webVTT file contents 7444 * 7445 * @param {TextTrack} track 7446 * TextTrack to add cues to. Cues come from the srcContent. 7447 * 7448 * @private 7449 */ 7450var parseCues = function parseCues(srcContent, track) { 7451 var parser = new window_1.WebVTT.Parser(window_1, window_1.vttjs, window_1.WebVTT.StringDecoder()); 7452 var errors = []; 7453 7454 parser.oncue = function (cue) { 7455 track.addCue(cue); 7456 }; 7457 7458 parser.onparsingerror = function (error) { 7459 errors.push(error); 7460 }; 7461 7462 parser.onflush = function () { 7463 track.trigger({ 7464 type: 'loadeddata', 7465 target: track 7466 }); 7467 }; 7468 7469 parser.parse(srcContent); 7470 if (errors.length > 0) { 7471 if (window_1.console && window_1.console.groupCollapsed) { 7472 window_1.console.groupCollapsed('Text Track parsing errors for ' + track.src); 7473 }
7474 errors.forEach(function (error) { 7475 return log$1.error(error); 7476 }); 7477 if (window_1.console && window_1.console.groupEnd) { 7478 window_1.console.groupEnd(); 7479 } 7480 } 7481 7482 parser.flush(); 7483}; 7484 7485/** 7486 * Load a `TextTrack` from a specifed url. 7487 * 7488 * @param {string} src 7489 * Url to load track from. 7490 * 7491 * @param {TextTrack} track 7492 * Track to add cues to. Comes from the content at the end of `url`. 7493 * 7494 * @private 7495 */ 7496var loadTrack = function loadTrack(src, track) { 7497 var opts = { 7498 uri: src 7499 }; 7500 var crossOrigin = isCrossOrigin(src); 7501 7502 if (crossOrigin) { 7503 opts.cors = crossOrigin; 7504 } 7505 7506 xhr(opts, bind(this, function (err, response, responseBody) { 7507 if (err) { 7508 return log$1.error(err, response); 7509 } 7510 7511 track.loaded_ = true; 7512 7513 // Make sure that vttjs has loaded, otherwise, wait till it finished loading 7514 // NOTE: this is only used for the alt/video.novtt.js build 7515 if (typeof window_1.WebVTT !== 'function') { 7516 if (track.tech_) { 7517 var loadHandler = function loadHandler() { 7518 return parseCues(responseBody, track); 7519 }; 7520 7521 track.tech_.on('vttjsloaded', loadHandler); 7522 track.tech_.on('vttjserror', function () { 7523 log$1.error('vttjs failed to load, stopping trying to process ' + track.src); 7524 track.tech_.off('vttjsloaded', loadHandler); 7525 }); 7526 } 7527 } else { 7528 parseCues(responseBody, track); 7529 } 7530 })); 7531}; 7532 7533/** 7534 * A representation of a single `TextTrack`. 7535 * 7536 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrack} 7537 * @extends Track 7538 */ 7539 7540var TextTrack = function (_Track) { 7541 inherits(TextTrack, _Track); 7542 7543 /** 7544 * Create an instance of this class. 7545 * 7546 * @param {Object} options={} 7547 * Object of option names and values 7548 * 7549 * @param {Tech} options.tech 7550 * A reference to the tech that owns this TextTrack. 7551 * 7552 * @param {TextTrack~Kind} [options.kind='subtitles'] 7553 * A valid text track kind. 7554 * 7555 * @param {TextTrack~Mode} [options.mode='disabled'] 7556 * A valid text track mode. 7557 * 7558 * @param {string} [options.id='vjs_track_' + Guid.newGUID()] 7559 * A unique id for this TextTrack. 7560 * 7561 * @param {string} [options.label=''] 7562 * The menu label for this track. 7563 * 7564 * @param {string} [options.language=''] 7565 * A valid two character language code. 7566 * 7567 * @param {string} [options.srclang=''] 7568 * A valid two character language code. An alternative, but deprioritized 7569 * vesion of `options.language` 7570 * 7571 * @param {string} [options.src] 7572 * A url to TextTrack cues. 7573 * 7574 * @param {boolean} [options.default] 7575 * If this track should default to on or off. 7576 */ 7577 function TextTrack() { 7578 var _this, _ret; 7579 7580 var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; 7581 classCallCheck(this, TextTrack); 7582 7583 if (!options.tech) { 7584 throw new Error('A tech was not provided.'); 7585 } 7586 7587 var settings = mergeOptions(options, { 7588 kind: TextTrackKind[options.kind] || 'subtitles', 7589 language: options.language || options.srclang || '' 7590 }); 7591 var mode = TextTrackMode[settings.mode] || 'disabled'; 7592 var default_ = settings['default']; 7593 7594 if (settings.kind === 'metadata' || settings.kind === 'chapters') { 7595 mode = 'hidden'; 7596 } 7597 // on IE8 this will be a document element 7598 // for every other browser this will be a normal object 7599 var tt = (_this = possibleConstructorReturn(this, _Track.call(this, settings)), _this); 7600 7601 tt.tech_ = settings.tech; 7602 7603 if (IS_IE8) { 7604 for (var prop in TextTrack.prototype) { 7605 if (prop !== 'constructor') { 7606 tt[prop] = TextTrack.prototype[prop]; 7607 } 7608 } 7609 } 7610 7611 tt.cues_ = []; 7612 tt.activeCues_ = []; 7613 7614 var cues = new TextTrackCueList(tt.cues_); 7615 var activeCues = new TextTrackCueList(tt.activeCues_); 7616 var changed = false; 7617 var timeupdateHandler = bind(tt, function () { 7618 7619 // Accessing this.activeCues for the side-effects of updating itself 7620 // due to it's nature as a getter function. Do not remove or cues will 7621 // stop updating!
vendor: 6,824 bytes, lines 7622-7885
7622 // Use the setter to prevent deletion from uglify (pure_getters rule) 7623 this.activeCues = this.activeCues; 7624 if (changed) { 7625 this.trigger('cuechange'); 7626 changed = false; 7627 } 7628 }); 7629 7630 if (mode !== 'disabled') { 7631 tt.tech_.ready(function () { 7632 tt.tech_.on('timeupdate', timeupdateHandler); 7633 }, true); 7634 } 7635 7636 /** 7637 * @memberof TextTrack 7638 * @member {boolean} default 7639 * If this track was set to be on or off by default. Cannot be changed after 7640 * creation. 7641 * @instance 7642 * 7643 * @readonly 7644 */ 7645 Object.defineProperty(tt, 'default', { 7646 get: function get$$1() { 7647 return default_; 7648 }, 7649 set: function set$$1() {} 7650 }); 7651 7652 /** 7653 * @memberof TextTrack 7654 * @member {string} mode 7655 * Set the mode of this TextTrack to a valid {@link TextTrack~Mode}. Will 7656 * not be set if setting to an invalid mode. 7657 * @instance 7658 * 7659 * @fires TextTrack#modechange 7660 */ 7661 Object.defineProperty(tt, 'mode', { 7662 get: function get$$1() { 7663 return mode; 7664 }, 7665 set: function set$$1(newMode) { 7666 var _this2 = this; 7667 7668 if (!TextTrackMode[newMode]) { 7669 return; 7670 } 7671 mode = newMode; 7672 if (mode === 'showing') { 7673 7674 this.tech_.ready(function () { 7675 _this2.tech_.on('timeupdate', timeupdateHandler); 7676 }, true); 7677 } 7678 /** 7679 * An event that fires when mode changes on this track. This allows 7680 * the TextTrackList that holds this track to act accordingly. 7681 * 7682 * > Note: This is not part of the spec! 7683 * 7684 * @event TextTrack#modechange 7685 * @type {EventTarget~Event} 7686 */ 7687 this.trigger('modechange'); 7688 } 7689 }); 7690 7691 /** 7692 * @memberof TextTrack 7693 * @member {TextTrackCueList} cues 7694 * The text track cue list for this TextTrack. 7695 * @instance 7696 */ 7697 Object.defineProperty(tt, 'cues', { 7698 get: function get$$1() { 7699 if (!this.loaded_) { 7700 return null; 7701 } 7702 7703 return cues; 7704 }, 7705 set: function set$$1() {} 7706 }); 7707 7708 /** 7709 * @memberof TextTrack 7710 * @member {TextTrackCueList} activeCues 7711 * The list text track cues that are currently active for this TextTrack. 7712 * @instance 7713 */ 7714 Object.defineProperty(tt, 'activeCues', { 7715 get: function get$$1() { 7716 if (!this.loaded_) { 7717 return null; 7718 } 7719 7720 // nothing to do 7721 if (this.cues.length === 0) { 7722 return activeCues; 7723 } 7724 7725 var ct = this.tech_.currentTime(); 7726 var active = []; 7727 7728 for (var i = 0, l = this.cues.length; i < l; i++) { 7729 var cue = this.cues[i]; 7730 7731 if (cue.startTime <= ct && cue.endTime >= ct) { 7732 active.push(cue); 7733 } else if (cue.startTime === cue.endTime && cue.startTime <= ct && cue.startTime + 0.5 >= ct) { 7734 active.push(cue); 7735 } 7736 } 7737 7738 changed = false; 7739 7740 if (active.length !== this.activeCues_.length) { 7741 changed = true; 7742 } else { 7743 for (var _i = 0; _i < active.length; _i++) { 7744 if (this.activeCues_.indexOf(active[_i]) === -1) { 7745 changed = true; 7746 } 7747 } 7748 } 7749 7750 this.activeCues_ = active; 7751 activeCues.setCues_(this.activeCues_); 7752 7753 return activeCues; 7754 }, 7755 7756 7757 // /!\ Keep this setter empty (see the timeupdate handler above) 7758 set: function set$$1() {} 7759 }); 7760 7761 if (settings.src) { 7762 tt.src = settings.src; 7763 loadTrack(settings.src, tt); 7764 } else { 7765 tt.loaded_ = true; 7766 } 7767 7768 return _ret = tt, possibleConstructorReturn(_this, _ret); 7769 } 7770 7771 /** 7772 * Add a cue to the internal list of cues. 7773 * 7774 * @param {TextTrack~Cue} cue 7775 * The cue to add to our internal list 7776 */ 7777 7778 7779 TextTrack.prototype.addCue = function addCue(originalCue) { 7780 var cue = originalCue; 7781 7782 if (window_1.vttjs && !(originalCue instanceof window_1.vttjs.VTTCue)) { 7783 cue = new window_1.vttjs.VTTCue(originalCue.startTime, originalCue.endTime, originalCue.text); 7784 7785 for (var prop in originalCue) { 7786 if (!(prop in cue)) { 7787 cue[prop] = originalCue[prop]; 7788 } 7789 } 7790 7791 // make sure that `id` is copied over 7792 cue.id = originalCue.id; 7793 cue.originalCue_ = originalCue; 7794 } 7795 7796 var tracks = this.tech_.textTracks(); 7797 7798 for (var i = 0; i < tracks.length; i++) { 7799 if (tracks[i] !== this) { 7800 tracks[i].removeCue(cue); 7801 } 7802 } 7803 7804 this.cues_.push(cue); 7805 this.cues.setCues_(this.cues_); 7806 }; 7807 7808 /** 7809 * Remove a cue from our internal list 7810 * 7811 * @param {TextTrack~Cue} removeCue 7812 * The cue to remove from our internal list 7813 */ 7814 7815 7816 TextTrack.prototype.removeCue = function removeCue(_removeCue) { 7817 var i = this.cues_.length; 7818 7819 while (i--) { 7820 var cue = this.cues_[i]; 7821 7822 if (cue === _removeCue || cue.originalCue_ && cue.originalCue_ === _removeCue) { 7823 this.cues_.splice(i, 1); 7824 this.cues.setCues_(this.cues_); 7825 break; 7826 } 7827 } 7828 }; 7829 7830 return TextTrack; 7831}(Track); 7832 7833/** 7834 * cuechange - One or more cues in the track have become active or stopped being active. 7835 */ 7836 7837 7838TextTrack.prototype.allowedEvents_ = { 7839 cuechange: 'cuechange' 7840}; 7841 7842/** 7843 * A representation of a single `AudioTrack`. If it is part of an {@link AudioTrackList} 7844 * only one `AudioTrack` in the list will be enabled at a time. 7845 * 7846 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotrack} 7847 * @extends Track 7848 */ 7849 7850var AudioTrack = function (_Track) { 7851 inherits(AudioTrack, _Track); 7852 7853 /** 7854 * Create an instance of this class. 7855 * 7856 * @param {Object} [options={}] 7857 * Object of option names and values 7858 * 7859 * @param {AudioTrack~Kind} [options.kind=''] 7860 * A valid audio track kind 7861 * 7862 * @param {string} [options.id='vjs_track_' + Guid.newGUID()] 7863 * A unique id for this AudioTrack. 7864 * 7865 * @param {string} [options.label=''] 7866 * The menu label for this track. 7867 * 7868 * @param {string} [options.language=''] 7869 * A valid two character language code. 7870 * 7871 * @param {boolean} [options.enabled] 7872 * If this track is the one that is currently playing. If this track is part of 7873 * an {@link AudioTrackList}, only one {@link AudioTrack} will be enabled. 7874 */ 7875 function AudioTrack() { 7876 var _this, _ret; 7877 7878 var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; 7879 classCallCheck(this, AudioTrack); 7880 7881 var settings = mergeOptions(options, { 7882 kind: AudioTrackKind[options.kind] || '' 7883 }); 7884 // on IE8 this will be a document element 7885 // for every other browser this will be a normal object
7886 var track = (_this = possibleConstructorReturn(this, _Track.call(this, settings)), _this); 7887 var enabled = false; 7888 7889 if (IS_IE8) { 7890 for (var prop in AudioTrack.prototype) { 7891 if (prop !== 'constructor') { 7892 track[prop] = AudioTrack.prototype[prop]; 7893 } 7894 } 7895 } 7896 /** 7897 * @memberof AudioTrack 7898 * @member {boolean} enabled 7899 * If this `AudioTrack` is enabled or not. When setting this will 7900 * fire {@link AudioTrack#enabledchange} if the state of enabled is changed. 7901 * @instance 7902 * 7903 * @fires VideoTrack#selectedchange 7904 */ 7905 Object.defineProperty(track, 'enabled', { 7906 get: function get$$1() { 7907 return enabled; 7908 }, 7909 set: function set$$1(newEnabled) { 7910 // an invalid or unchanged value 7911 if (typeof newEnabled !== 'boolean' || newEnabled === enabled) { 7912 return; 7913 } 7914 enabled = newEnabled; 7915 7916 /** 7917 * An event that fires when enabled changes on this track. This allows 7918 * the AudioTrackList that holds this track to act accordingly. 7919 * 7920 * > Note: This is not part of the spec! Native tracks will do 7921 * this internally without an event. 7922 * 7923 * @event AudioTrack#enabledchange 7924 * @type {EventTarget~Event} 7925 */ 7926 this.trigger('enabledchange'); 7927 } 7928 }); 7929 7930 // if the user sets this track to selected then 7931 // set selected to that true value otherwise 7932 // we keep it false 7933 if (settings.enabled) { 7934 track.enabled = settings.enabled; 7935 } 7936 track.loaded_ = true; 7937 7938 return _ret = track, possibleConstructorReturn(_this, _ret); 7939 } 7940 7941 return AudioTrack; 7942}(Track); 7943 7944/** 7945 * A representation of a single `VideoTrack`. 7946 * 7947 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#videotrack} 7948 * @extends Track 7949 */ 7950 7951var VideoTrack = function (_Track) { 7952 inherits(VideoTrack, _Track); 7953 7954 /** 7955 * Create an instance of this class. 7956 * 7957 * @param {Object} [options={}] 7958 * Object of option names and values 7959 * 7960 * @param {string} [options.kind=''] 7961 * A valid {@link VideoTrack~Kind} 7962 * 7963 * @param {string} [options.id='vjs_track_' + Guid.newGUID()] 7964 * A unique id for this AudioTrack. 7965 * 7966 * @param {string} [options.label=''] 7967 * The menu label for this track. 7968 * 7969 * @param {string} [options.language=''] 7970 * A valid two character language code. 7971 * 7972 * @param {boolean} [options.selected] 7973 * If this track is the one that is currently playing. 7974 */ 7975 function VideoTrack() { 7976 var _this, _ret; 7977 7978 var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; 7979 classCallCheck(this, VideoTrack); 7980 7981 var settings = mergeOptions(options, { 7982 kind: VideoTrackKind[options.kind] || '' 7983 }); 7984 7985 // on IE8 this will be a document element 7986 // for every other browser this will be a normal object 7987 var track = (_this = possibleConstructorReturn(this, _Track.call(this, settings)), _this); 7988 var selected = false; 7989 7990 if (IS_IE8) { 7991 for (var prop in VideoTrack.prototype) { 7992 if (prop !== 'constructor') { 7993 track[prop] = VideoTrack.prototype[prop]; 7994 } 7995 } 7996 } 7997 7998 /** 7999 * @memberof VideoTrack 8000 * @member {boolean} selected 8001 * If this `VideoTrack` is selected or not. When setting this will 8002 * fire {@link VideoTrack#selectedchange} if the state of selected changed. 8003 * @instance 8004 * 8005 * @fires VideoTrack#selectedchange 8006 */ 8007 Object.defineProperty(track, 'selected', { 8008 get: function get$$1() { 8009 return selected; 8010 }, 8011 set: function set$$1(newSelected) { 8012 // an invalid or unchanged value 8013 if (typeof newSelected !== 'boolean' || newSelected === selected) { 8014 return; 8015 } 8016 selected = newSelected; 8017 8018 /** 8019 * An event that fires when selected changes on this track. This allows 8020 * the VideoTrackList that holds this track to act accordingly. 8021 * 8022 * > Note: This is not part of the spec! Native tracks will do 8023 * this internally without an event. 8024 * 8025 * @event VideoTrack#selectedchange 8026 * @type {EventTarget~Event} 8027 */ 8028 this.trigger('selectedchange'); 8029 } 8030 }); 8031 8032 // if the user sets this track to selected then 8033 // set selected to that true value otherwise 8034 // we keep it false 8035 if (settings.selected) { 8036 track.selected = settings.selected; 8037 } 8038 8039 return _ret = track, possibleConstructorReturn(_this, _ret); 8040 } 8041 8042 return VideoTrack; 8043}(Track); 8044 8045/** 8046 * @file html-track-element.js 8047 */ 8048 8049/** 8050 * @memberof HTMLTrackElement 8051 * @typedef {HTMLTrackElement~ReadyState} 8052 * @enum {number} 8053 */ 8054var NONE = 0; 8055var LOADING = 1; 8056var LOADED = 2; 8057var ERROR = 3; 8058 8059/** 8060 * A single track represented in the DOM. 8061 * 8062 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#htmltrackelement}
8063 * @extends EventTarget 8064 */ 8065 8066var HTMLTrackElement = function (_EventTarget) { 8067 inherits(HTMLTrackElement, _EventTarget); 8068 8069 /** 8070 * Create an instance of this class. 8071 * 8072 * @param {Object} options={} 8073 * Object of option names and values 8074 * 8075 * @param {Tech} options.tech 8076 * A reference to the tech that owns this HTMLTrackElement. 8077 * 8078 * @param {TextTrack~Kind} [options.kind='subtitles'] 8079 * A valid text track kind. 8080 * 8081 * @param {TextTrack~Mode} [options.mode='disabled'] 8082 * A valid text track mode. 8083 * 8084 * @param {string} [options.id='vjs_track_' + Guid.newGUID()] 8085 * A unique id for this TextTrack. 8086 * 8087 * @param {string} [options.label=''] 8088 * The menu label for this track. 8089 * 8090 * @param {string} [options.language=''] 8091 * A valid two character language code. 8092 * 8093 * @param {string} [options.srclang=''] 8094 * A valid two character language code. An alternative, but deprioritized 8095 * vesion of `options.language` 8096 * 8097 * @param {string} [options.src] 8098 * A url to TextTrack cues. 8099 * 8100 * @param {boolean} [options.default] 8101 * If this track should default to on or off. 8102 */ 8103 function HTMLTrackElement() { 8104 var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; 8105 classCallCheck(this, HTMLTrackElement); 8106 8107 var _this = possibleConstructorReturn(this, _EventTarget.call(this)); 8108 8109 var readyState = void 0; 8110 var trackElement = _this; // eslint-disable-line 8111 8112 if (IS_IE8) { 8113 trackElement = document_1.createElement('custom'); 8114 8115 for (var prop in HTMLTrackElement.prototype) { 8116 if (prop !== 'constructor') { 8117 trackElement[prop] = HTMLTrackElement.prototype[prop]; 8118 } 8119 } 8120 } 8121 8122 var track = new TextTrack(options); 8123 8124 trackElement.kind = track.kind; 8125 trackElement.src = track.src; 8126 trackElement.srclang = track.language; 8127 trackElement.label = track.label; 8128 trackElement['default'] = track['default']; 8129 8130 /** 8131 * @memberof HTMLTrackElement 8132 * @member {HTMLTrackElement~ReadyState} readyState 8133 * The current ready state of the track element. 8134 * @instance 8135 */ 8136 Object.defineProperty(trackElement, 'readyState', { 8137 get: function get$$1() { 8138 return readyState; 8139 } 8140 }); 8141 8142 /** 8143 * @memberof HTMLTrackElement 8144 * @member {TextTrack} track 8145 * The underlying TextTrack object. 8146 * @instance 8147 * 8148 */ 8149 Object.defineProperty(trackElement, 'track', { 8150 get: function get$$1() { 8151 return track; 8152 } 8153 }); 8154 8155 readyState = NONE; 8156 8157 /** 8158 * @listens TextTrack#loadeddata 8159 * @fires HTMLTrackElement#load 8160 */ 8161 track.addEventListener('loadeddata', function () { 8162 readyState = LOADED; 8163 8164 trackElement.trigger({ 8165 type: 'load', 8166 target: trackElement 8167 }); 8168 }); 8169 8170 if (IS_IE8) { 8171 var _ret; 8172 8173 return _ret = trackElement, possibleConstructorReturn(_this, _ret); 8174 } 8175 return _this; 8176 } 8177 8178 return HTMLTrackElement; 8179}(EventTarget); 8180 8181HTMLTrackElement.prototype.allowedEvents_ = { 8182 load: 'load' 8183}; 8184 8185HTMLTrackElement.NONE = NONE; 8186HTMLTrackElement.LOADING = LOADING; 8187HTMLTrackElement.LOADED = LOADED; 8188HTMLTrackElement.ERROR = ERROR; 8189 8190/* 8191 * This file contains all track properties that are used in 8192 * player.js, tech.js, html5.js and possibly other techs in the future. 8193 */ 8194 8195var NORMAL = { 8196 audio: { 8197 ListClass: AudioTrackList, 8198 TrackClass: AudioTrack, 8199 capitalName: 'Audio' 8200 }, 8201 video: { 8202 ListClass: VideoTrackList, 8203 TrackClass: VideoTrack, 8204 capitalName: 'Video' 8205 }, 8206 text: { 8207 ListClass: TextTrackList, 8208 TrackClass: TextTrack, 8209 capitalName: 'Text' 8210 } 8211}; 8212 8213Object.keys(NORMAL).forEach(function (type) { 8214 NORMAL[type].getterName = type + 'Tracks'; 8215 NORMAL[type].privateName = type + 'Tracks_'; 8216}); 8217 8218var REMOTE = { 8219 remoteText: { 8220 ListClass: TextTrackList, 8221 TrackClass: TextTrack, 8222 capitalName: 'RemoteText', 8223 getterName: 'remoteTextTracks', 8224 privateName: 'remoteTextTracks_' 8225 }, 8226 remoteTextEl: { 8227 ListClass: HtmlTrackElementList, 8228 TrackClass: HTMLTrackElement, 8229 capitalName: 'RemoteTextTrackEls', 8230 getterName: 'remoteTextTrackEls', 8231 privateName: 'remoteTextTrackEls_' 8232 } 8233}; 8234 8235var ALL = mergeOptions(NORMAL, REMOTE); 8236 8237REMOTE.names = Object.keys(REMOTE); 8238NORMAL.names = Object.keys(NORMAL); 8239ALL.names = [].concat(REMOTE.names).concat(NORMAL.names); 8240 8241/** 8242 * Copyright 2013 vtt.js Contributors 8243 * 8244 * Licensed under the Apache License, Version 2.0 (the "License");
vendor: 16,438 bytes, lines 8245-8782
8245 * you may not use this file except in compliance with the License. 8246 * You may obtain a copy of the License at 8247 * 8248 * http://www.apache.org/licenses/LICENSE-2.0 8249 * 8250 * Unless required by applicable law or agreed to in writing, software 8251 * distributed under the License is distributed on an "AS IS" BASIS, 8252 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 8253 * See the License for the specific language governing permissions and 8254 * limitations under the License. 8255 */ 8256 8257/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 8258/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ 8259var _objCreate = Object.create || (function() { 8260 function F() {} 8261 return function(o) { 8262 if (arguments.length !== 1) { 8263 throw new Error('Object.create shim only accepts one parameter.'); 8264 } 8265 F.prototype = o; 8266 return new F(); 8267 }; 8268})(); 8269 8270// Creates a new ParserError object from an errorData object. The errorData 8271// object should have default code and message properties. The default message 8272// property can be overriden by passing in a message parameter. 8273// See ParsingError.Errors below for acceptable errors. 8274function ParsingError(errorData, message) { 8275 this.name = "ParsingError"; 8276 this.code = errorData.code; 8277 this.message = message || errorData.message; 8278} 8279ParsingError.prototype = _objCreate(Error.prototype); 8280ParsingError.prototype.constructor = ParsingError; 8281 8282// ParsingError metadata for acceptable ParsingErrors. 8283ParsingError.Errors = { 8284 BadSignature: { 8285 code: 0, 8286 message: "Malformed WebVTT signature." 8287 }, 8288 BadTimeStamp: { 8289 code: 1, 8290 message: "Malformed time stamp." 8291 } 8292}; 8293 8294// Try to parse input as a time stamp. 8295function parseTimeStamp(input) { 8296 8297 function computeSeconds(h, m, s, f) { 8298 return (h | 0) * 3600 + (m | 0) * 60 + (s | 0) + (f | 0) / 1000; 8299 } 8300 8301 var m = input.match(/^(\d+):(\d{2})(:\d{2})?\.(\d{3})/); 8302 if (!m) { 8303 return null; 8304 } 8305 8306 if (m[3]) { 8307 // Timestamp takes the form of [hours]:[minutes]:[seconds].[milliseconds] 8308 return computeSeconds(m[1], m[2], m[3].replace(":", ""), m[4]); 8309 } else if (m[1] > 59) { 8310 // Timestamp takes the form of [hours]:[minutes].[milliseconds] 8311 // First position is hours as it's over 59. 8312 return computeSeconds(m[1], m[2], 0, m[4]); 8313 } else { 8314 // Timestamp takes the form of [minutes]:[seconds].[milliseconds] 8315 return computeSeconds(0, m[1], m[2], m[4]); 8316 } 8317} 8318 8319// A settings object holds key/value pairs and will ignore anything but the first 8320// assignment to a specific key. 8321function Settings() { 8322 this.values = _objCreate(null); 8323} 8324 8325Settings.prototype = { 8326 // Only accept the first assignment to any key. 8327 set: function(k, v) { 8328 if (!this.get(k) && v !== "") { 8329 this.values[k] = v; 8330 } 8331 }, 8332 // Return the value for a key, or a default value. 8333 // If 'defaultKey' is passed then 'dflt' is assumed to be an object with 8334 // a number of possible default values as properties where 'defaultKey' is 8335 // the key of the property that will be chosen; otherwise it's assumed to be 8336 // a single value. 8337 get: function(k, dflt, defaultKey) { 8338 if (defaultKey) { 8339 return this.has(k) ? this.values[k] : dflt[defaultKey]; 8340 } 8341 return this.has(k) ? this.values[k] : dflt; 8342 }, 8343 // Check whether we have a value for a key. 8344 has: function(k) { 8345 return k in this.values; 8346 }, 8347 // Accept a setting if its one of the given alternatives. 8348 alt: function(k, v, a) { 8349 for (var n = 0; n < a.length; ++n) { 8350 if (v === a[n]) { 8351 this.set(k, v); 8352 break; 8353 } 8354 } 8355 }, 8356 // Accept a setting if its a valid (signed) integer. 8357 integer: function(k, v) { 8358 if (/^-?\d+$/.test(v)) { // integer 8359 this.set(k, parseInt(v, 10)); 8360 } 8361 }, 8362 // Accept a setting if its a valid percentage. 8363 percent: function(k, v) { 8364 var m; 8365 if ((m = v.match(/^([\d]{1,3})(\.[\d]*)?%$/))) { 8366 v = parseFloat(v); 8367 if (v >= 0 && v <= 100) { 8368 this.set(k, v); 8369 return true; 8370 } 8371 } 8372 return false; 8373 } 8374}; 8375 8376// Helper function to parse input into groups separated by 'groupDelim', and 8377// interprete each group as a key/value pair separated by 'keyValueDelim'. 8378function parseOptions(input, callback, keyValueDelim, groupDelim) { 8379 var groups = groupDelim ? input.split(groupDelim) : [input]; 8380 for (var i in groups) { 8381 if (typeof groups[i] !== "string") { 8382 continue; 8383 } 8384 var kv = groups[i].split(keyValueDelim); 8385 if (kv.length !== 2) { 8386 continue; 8387 } 8388 var k = kv[0]; 8389 var v = kv[1]; 8390 callback(k, v); 8391 } 8392} 8393 8394function parseCue(input, cue, regionList) { 8395 // Remember the original input if we need to throw an error. 8396 var oInput = input; 8397 // 4.1 WebVTT timestamp 8398 function consumeTimeStamp() { 8399 var ts = parseTimeStamp(input); 8400 if (ts === null) { 8401 throw new ParsingError(ParsingError.Errors.BadTimeStamp, 8402 "Malformed timestamp: " + oInput); 8403 } 8404 // Remove time stamp from input. 8405 input = input.replace(/^[^\sa-zA-Z-]+/, ""); 8406 return ts; 8407 } 8408 8409 // 4.4.2 WebVTT cue settings 8410 function consumeCueSettings(input, cue) { 8411 var settings = new Settings(); 8412 8413 parseOptions(input, function (k, v) { 8414 switch (k) { 8415 case "region": 8416 // Find the last region we parsed with the same region id. 8417 for (var i = regionList.length - 1; i >= 0; i--) { 8418 if (regionList[i].id === v) { 8419 settings.set(k, regionList[i].region); 8420 break; 8421 } 8422 } 8423 break; 8424 case "vertical": 8425 settings.alt(k, v, ["rl", "lr"]); 8426 break; 8427 case "line": 8428 var vals = v.split(","), 8429 vals0 = vals[0]; 8430 settings.integer(k, vals0); 8431 settings.percent(k, vals0) ? settings.set("snapToLines", false) : null; 8432 settings.alt(k, vals0, ["auto"]); 8433 if (vals.length === 2) { 8434 settings.alt("lineAlign", vals[1], ["start", "middle", "end"]); 8435 } 8436 break; 8437 case "position": 8438 vals = v.split(","); 8439 settings.percent(k, vals[0]); 8440 if (vals.length === 2) { 8441 settings.alt("positionAlign", vals[1], ["start", "middle", "end"]); 8442 } 8443 break; 8444 case "size": 8445 settings.percent(k, v); 8446 break; 8447 case "align": 8448 settings.alt(k, v, ["start", "middle", "end", "left", "right"]); 8449 break; 8450 } 8451 }, /:/, /\s/); 8452 8453 // Apply default values for any missing fields. 8454 cue.region = settings.get("region", null); 8455 cue.vertical = settings.get("vertical", ""); 8456 cue.line = settings.get("line", "auto"); 8457 cue.lineAlign = settings.get("lineAlign", "start"); 8458 cue.snapToLines = settings.get("snapToLines", true); 8459 cue.size = settings.get("size", 100); 8460 cue.align = settings.get("align", "middle"); 8461 cue.position = settings.get("position", { 8462 start: 0, 8463 left: 0, 8464 middle: 50, 8465 end: 100, 8466 right: 100 8467 }, cue.align); 8468 cue.positionAlign = settings.get("positionAlign", { 8469 start: "start", 8470 left: "start", 8471 middle: "middle", 8472 end: "end", 8473 right: "end" 8474 }, cue.align); 8475 } 8476 8477 function skipWhitespace() { 8478 input = input.replace(/^\s+/, ""); 8479 } 8480 8481 // 4.1 WebVTT cue timings. 8482 skipWhitespace(); 8483 cue.startTime = consumeTimeStamp(); // (1) collect cue start time 8484 skipWhitespace(); 8485 if (input.substr(0, 3) !== "-->") { // (3) next characters must match "-->" 8486 throw new ParsingError(ParsingError.Errors.BadTimeStamp, 8487 "Malformed time stamp (time stamps must be separated by '-->'): " + 8488 oInput); 8489 } 8490 input = input.substr(3); 8491 skipWhitespace(); 8492 cue.endTime = consumeTimeStamp(); // (5) collect cue end time 8493 8494 // 4.1 WebVTT cue settings list. 8495 skipWhitespace(); 8496 consumeCueSettings(input, cue); 8497} 8498 8499var ESCAPE = { 8500 "&": "&", 8501 "<": "<", 8502 ">": ">", 8503 "‎": "\u200e", 8504 "‏": "\u200f", 8505 " ": "\u00a0" 8506}; 8507 8508var TAG_NAME = { 8509 c: "span", 8510 i: "i", 8511 b: "b", 8512 u: "u", 8513 ruby: "ruby", 8514 rt: "rt", 8515 v: "span", 8516 lang: "span" 8517}; 8518 8519var TAG_ANNOTATION = { 8520 v: "title", 8521 lang: "lang" 8522}; 8523 8524var NEEDS_PARENT = { 8525 rt: "ruby" 8526}; 8527 8528// Parse content into a document fragment. 8529function parseContent(window, input) { 8530 function nextToken() { 8531 // Check for end-of-string. 8532 if (!input) { 8533 return null; 8534 } 8535 8536 // Consume 'n' characters from the input. 8537 function consume(result) { 8538 input = input.substr(result.length); 8539 return result; 8540 } 8541 8542 var m = input.match(/^([^<]*)(<[^>]*>?)?/); 8543 // If there is some text before the next tag, return it, otherwise return 8544 // the tag. 8545 return consume(m[1] ? m[1] : m[2]); 8546 } 8547 8548 // Unescape a string 's'. 8549 function unescape1(e) { 8550 return ESCAPE[e]; 8551 } 8552 function unescape(s) { 8553 while ((m = s.match(/&(amp|lt|gt|lrm|rlm|nbsp);/))) { 8554 s = s.replace(m[0], unescape1); 8555 } 8556 return s; 8557 } 8558 8559 function shouldAdd(current, element) { 8560 return !NEEDS_PARENT[element.localName] || 8561 NEEDS_PARENT[element.localName] === current.localName; 8562 } 8563 8564 // Create an element for this tag. 8565 function createElement(type, annotation) { 8566 var tagName = TAG_NAME[type]; 8567 if (!tagName) { 8568 return null; 8569 } 8570 var element = window.document.createElement(tagName); 8571 element.localName = tagName; 8572 var name = TAG_ANNOTATION[type]; 8573 if (name && annotation) { 8574 element[name] = annotation.trim(); 8575 } 8576 return element; 8577 } 8578 8579 var rootDiv = window.document.createElement("div"), 8580 current = rootDiv, 8581 t, 8582 tagStack = []; 8583 8584 while ((t = nextToken()) !== null) { 8585 if (t[0] === '<') { 8586 if (t[1] === "/") { 8587 // If the closing tag matches, move back up to the parent node. 8588 if (tagStack.length && 8589 tagStack[tagStack.length - 1] === t.substr(2).replace(">", "")) { 8590 tagStack.pop(); 8591 current = current.parentNode; 8592 } 8593 // Otherwise just ignore the end tag. 8594 continue; 8595 } 8596 var ts = parseTimeStamp(t.substr(1, t.length - 2)); 8597 var node; 8598 if (ts) { 8599 // Timestamps are lead nodes as well. 8600 node = window.document.createProcessingInstruction("timestamp", ts); 8601 current.appendChild(node); 8602 continue; 8603 } 8604 var m = t.match(/^<([^.\s/0-9>]+)(\.[^\s\\>]+)?([^>\\]+)?(\\?)>?$/); 8605 // If we can't parse the tag, skip to the next tag. 8606 if (!m) { 8607 continue; 8608 } 8609 // Try to construct an element, and ignore the tag if we couldn't. 8610 node = createElement(m[1], m[3]); 8611 if (!node) { 8612 continue; 8613 } 8614 // Determine if the tag should be added based on the context of where it 8615 // is placed in the cuetext. 8616 if (!shouldAdd(current, node)) { 8617 continue; 8618 } 8619 // Set the class list (as a list of classes, separated by space). 8620 if (m[2]) { 8621 node.className = m[2].substr(1).replace('.', ' '); 8622 } 8623 // Append the node to the current node, and enter the scope of the new 8624 // node. 8625 tagStack.push(m[1]); 8626 current.appendChild(node); 8627 current = node; 8628 continue; 8629 } 8630 8631 // Text nodes are leaf nodes. 8632 current.appendChild(window.document.createTextNode(unescape(t))); 8633 } 8634 8635 return rootDiv; 8636} 8637 8638// This is a list of all the Unicode characters that have a strong 8639// right-to-left category. What this means is that these characters are 8640// written right-to-left for sure. It was generated by pulling all the strong 8641// right-to-left characters out of the Unicode data table. That table can 8642// found at: http://www.unicode.org/Public/UNIDATA/UnicodeData.txt 8643var strongRTLRanges = [[0x5be, 0x5be], [0x5c0, 0x5c0], [0x5c3, 0x5c3], [0x5c6, 0x5c6], 8644 [0x5d0, 0x5ea], [0x5f0, 0x5f4], [0x608, 0x608], [0x60b, 0x60b], [0x60d, 0x60d], 8645 [0x61b, 0x61b], [0x61e, 0x64a], [0x66d, 0x66f], [0x671, 0x6d5], [0x6e5, 0x6e6], 8646 [0x6ee, 0x6ef], [0x6fa, 0x70d], [0x70f, 0x710], [0x712, 0x72f], [0x74d, 0x7a5], 8647 [0x7b1, 0x7b1], [0x7c0, 0x7ea], [0x7f4, 0x7f5], [0x7fa, 0x7fa], [0x800, 0x815], 8648 [0x81a, 0x81a], [0x824, 0x824], [0x828, 0x828], [0x830, 0x83e], [0x840, 0x858], 8649 [0x85e, 0x85e], [0x8a0, 0x8a0], [0x8a2, 0x8ac], [0x200f, 0x200f], 8650 [0xfb1d, 0xfb1d], [0xfb1f, 0xfb28], [0xfb2a, 0xfb36], [0xfb38, 0xfb3c], 8651 [0xfb3e, 0xfb3e], [0xfb40, 0xfb41], [0xfb43, 0xfb44], [0xfb46, 0xfbc1], 8652 [0xfbd3, 0xfd3d], [0xfd50, 0xfd8f], [0xfd92, 0xfdc7], [0xfdf0, 0xfdfc], 8653 [0xfe70, 0xfe74], [0xfe76, 0xfefc], [0x10800, 0x10805], [0x10808, 0x10808], 8654 [0x1080a, 0x10835], [0x10837, 0x10838], [0x1083c, 0x1083c], [0x1083f, 0x10855], 8655 [0x10857, 0x1085f], [0x10900, 0x1091b], [0x10920, 0x10939], [0x1093f, 0x1093f], 8656 [0x10980, 0x109b7], [0x109be, 0x109bf], [0x10a00, 0x10a00], [0x10a10, 0x10a13], 8657 [0x10a15, 0x10a17], [0x10a19, 0x10a33], [0x10a40, 0x10a47], [0x10a50, 0x10a58], 8658 [0x10a60, 0x10a7f], [0x10b00, 0x10b35], [0x10b40, 0x10b55], [0x10b58, 0x10b72], 8659 [0x10b78, 0x10b7f], [0x10c00, 0x10c48], [0x1ee00, 0x1ee03], [0x1ee05, 0x1ee1f], 8660 [0x1ee21, 0x1ee22], [0x1ee24, 0x1ee24], [0x1ee27, 0x1ee27], [0x1ee29, 0x1ee32], 8661 [0x1ee34, 0x1ee37], [0x1ee39, 0x1ee39], [0x1ee3b, 0x1ee3b], [0x1ee42, 0x1ee42], 8662 [0x1ee47, 0x1ee47], [0x1ee49, 0x1ee49], [0x1ee4b, 0x1ee4b], [0x1ee4d, 0x1ee4f], 8663 [0x1ee51, 0x1ee52], [0x1ee54, 0x1ee54], [0x1ee57, 0x1ee57], [0x1ee59, 0x1ee59], 8664 [0x1ee5b, 0x1ee5b], [0x1ee5d, 0x1ee5d], [0x1ee5f, 0x1ee5f], [0x1ee61, 0x1ee62], 8665 [0x1ee64, 0x1ee64], [0x1ee67, 0x1ee6a], [0x1ee6c, 0x1ee72], [0x1ee74, 0x1ee77], 8666 [0x1ee79, 0x1ee7c], [0x1ee7e, 0x1ee7e], [0x1ee80, 0x1ee89], [0x1ee8b, 0x1ee9b], 8667 [0x1eea1, 0x1eea3], [0x1eea5, 0x1eea9], [0x1eeab, 0x1eebb], [0x10fffd, 0x10fffd]]; 8668 8669function isStrongRTLChar(charCode) { 8670 for (var i = 0; i < strongRTLRanges.length; i++) { 8671 var currentRange = strongRTLRanges[i]; 8672 if (charCode >= currentRange[0] && charCode <= currentRange[1]) { 8673 return true; 8674 } 8675 } 8676 8677 return false; 8678} 8679 8680function determineBidi(cueDiv) { 8681 var nodeStack = [], 8682 text = "", 8683 charCode; 8684 8685 if (!cueDiv || !cueDiv.childNodes) { 8686 return "ltr"; 8687 } 8688 8689 function pushNodes(nodeStack, node) { 8690 for (var i = node.childNodes.length - 1; i >= 0; i--) { 8691 nodeStack.push(node.childNodes[i]); 8692 } 8693 } 8694 8695 function nextTextNode(nodeStack) { 8696 if (!nodeStack || !nodeStack.length) { 8697 return null; 8698 } 8699 8700 var node = nodeStack.pop(), 8701 text = node.textContent || node.innerText; 8702 if (text) { 8703 // TODO: This should match all unicode type B characters (paragraph 8704 // separator characters). See issue #115. 8705 var m = text.match(/^.*(\n|\r)/); 8706 if (m) { 8707 nodeStack.length = 0; 8708 return m[0]; 8709 } 8710 return text; 8711 } 8712 if (node.tagName === "ruby") { 8713 return nextTextNode(nodeStack); 8714 } 8715 if (node.childNodes) { 8716 pushNodes(nodeStack, node); 8717 return nextTextNode(nodeStack); 8718 } 8719 } 8720 8721 pushNodes(nodeStack, cueDiv); 8722 while ((text = nextTextNode(nodeStack))) { 8723 for (var i = 0; i < text.length; i++) { 8724 charCode = text.charCodeAt(i); 8725 if (isStrongRTLChar(charCode)) { 8726 return "rtl"; 8727 } 8728 } 8729 } 8730 return "ltr"; 8731} 8732 8733function computeLinePos(cue) { 8734 if (typeof cue.line === "number" && 8735 (cue.snapToLines || (cue.line >= 0 && cue.line <= 100))) { 8736 return cue.line; 8737 } 8738 if (!cue.track || !cue.track.textTrackList || 8739 !cue.track.textTrackList.mediaElement) { 8740 return -1; 8741 } 8742 var track = cue.track, 8743 trackList = track.textTrackList, 8744 count = 0; 8745 for (var i = 0; i < trackList.length && trackList[i] !== track; i++) { 8746 if (trackList[i].mode === "showing") { 8747 count++; 8748 } 8749 } 8750 return ++count * -1; 8751} 8752 8753function StyleBox() { 8754} 8755 8756// Apply styles to a div. If there is no div passed then it defaults to the 8757// div on 'this'. 8758StyleBox.prototype.applyStyles = function(styles, div) { 8759 div = div || this.div; 8760 for (var prop in styles) { 8761 if (styles.hasOwnProperty(prop)) { 8762 div.style[prop] = styles[prop]; 8763 } 8764 } 8765}; 8766 8767StyleBox.prototype.formatStyle = function(val, unit) { 8768 return val === 0 ? 0 : val + unit; 8769}; 8770 8771// Constructs the computed display state of the cue (a div). Places the div 8772// into the overlay which should be a block level element (usually a div). 8773function CueStyleBox(window, cue, styleOptions) { 8774 var isIE8 = (/MSIE\s8\.0/).test(navigator.userAgent); 8775 var color = "rgba(255, 255, 255, 1)"; 8776 var backgroundColor = "rgba(0, 0, 0, 0.8)"; 8777 8778 if (isIE8) { 8779 color = "rgb(255, 255, 255)"; 8780 backgroundColor = "rgb(0, 0, 0)"; 8781 } 8782
vendor: 5,880 bytes, lines 8783-8959
8783 StyleBox.call(this); 8784 this.cue = cue; 8785 8786 // Parse our cue's text into a DOM tree rooted at 'cueDiv'. This div will 8787 // have inline positioning and will function as the cue background box. 8788 this.cueDiv = parseContent(window, cue.text); 8789 var styles = { 8790 color: color, 8791 backgroundColor: backgroundColor, 8792 position: "relative", 8793 left: 0, 8794 right: 0, 8795 top: 0, 8796 bottom: 0, 8797 display: "inline" 8798 }; 8799 8800 if (!isIE8) { 8801 styles.writingMode = cue.vertical === "" ? "horizontal-tb" 8802 : cue.vertical === "lr" ? "vertical-lr" 8803 : "vertical-rl"; 8804 styles.unicodeBidi = "plaintext"; 8805 } 8806 this.applyStyles(styles, this.cueDiv); 8807 8808 // Create an absolutely positioned div that will be used to position the cue 8809 // div. Note, all WebVTT cue-setting alignments are equivalent to the CSS 8810 // mirrors of them except "middle" which is "center" in CSS. 8811 this.div = window.document.createElement("div"); 8812 styles = { 8813 textAlign: cue.align === "middle" ? "center" : cue.align, 8814 font: styleOptions.font, 8815 whiteSpace: "pre-line", 8816 position: "absolute" 8817 }; 8818 8819 if (!isIE8) { 8820 styles.direction = determineBidi(this.cueDiv); 8821 styles.writingMode = cue.vertical === "" ? "horizontal-tb" 8822 : cue.vertical === "lr" ? "vertical-lr" 8823 : "vertical-rl". 8824 stylesunicodeBidi = "plaintext"; 8825 } 8826 8827 this.applyStyles(styles); 8828 8829 this.div.appendChild(this.cueDiv); 8830 8831 // Calculate the distance from the reference edge of the viewport to the text 8832 // position of the cue box. The reference edge will be resolved later when 8833 // the box orientation styles are applied. 8834 var textPos = 0; 8835 switch (cue.positionAlign) { 8836 case "start": 8837 textPos = cue.position; 8838 break; 8839 case "middle": 8840 textPos = cue.position - (cue.size / 2); 8841 break; 8842 case "end": 8843 textPos = cue.position - cue.size; 8844 break; 8845 } 8846 8847 // Horizontal box orientation; textPos is the distance from the left edge of the 8848 // area to the left edge of the box and cue.size is the distance extending to 8849 // the right from there. 8850 if (cue.vertical === "") { 8851 this.applyStyles({ 8852 left: this.formatStyle(textPos, "%"), 8853 width: this.formatStyle(cue.size, "%") 8854 }); 8855 // Vertical box orientation; textPos is the distance from the top edge of the 8856 // area to the top edge of the box and cue.size is the height extending 8857 // downwards from there. 8858 } else { 8859 this.applyStyles({ 8860 top: this.formatStyle(textPos, "%"), 8861 height: this.formatStyle(cue.size, "%") 8862 }); 8863 } 8864 8865 this.move = function(box) { 8866 this.applyStyles({ 8867 top: this.formatStyle(box.top, "px"), 8868 bottom: this.formatStyle(box.bottom, "px"), 8869 left: this.formatStyle(box.left, "px"), 8870 right: this.formatStyle(box.right, "px"), 8871 height: this.formatStyle(box.height, "px"), 8872 width: this.formatStyle(box.width, "px") 8873 }); 8874 }; 8875} 8876CueStyleBox.prototype = _objCreate(StyleBox.prototype); 8877CueStyleBox.prototype.constructor = CueStyleBox; 8878 8879// Represents the co-ordinates of an Element in a way that we can easily 8880// compute things with such as if it overlaps or intersects with another Element. 8881// Can initialize it with either a StyleBox or another BoxPosition. 8882function BoxPosition(obj) { 8883 var isIE8 = (/MSIE\s8\.0/).test(navigator.userAgent); 8884 8885 // Either a BoxPosition was passed in and we need to copy it, or a StyleBox 8886 // was passed in and we need to copy the results of 'getBoundingClientRect' 8887 // as the object returned is readonly. All co-ordinate values are in reference 8888 // to the viewport origin (top left). 8889 var lh, height, width, top; 8890 if (obj.div) { 8891 height = obj.div.offsetHeight; 8892 width = obj.div.offsetWidth; 8893 top = obj.div.offsetTop; 8894 8895 var rects = (rects = obj.div.childNodes) && (rects = rects[0]) && 8896 rects.getClientRects && rects.getClientRects(); 8897 obj = obj.div.getBoundingClientRect(); 8898 // In certain cases the outter div will be slightly larger then the sum of 8899 // the inner div's lines. This could be due to bold text, etc, on some platforms. 8900 // In this case we should get the average line height and use that. This will 8901 // result in the desired behaviour. 8902 lh = rects ? Math.max((rects[0] && rects[0].height) || 0, obj.height / rects.length) 8903 : 0; 8904 8905 } 8906 this.left = obj.left; 8907 this.right = obj.right; 8908 this.top = obj.top || top; 8909 this.height = obj.height || height; 8910 this.bottom = obj.bottom || (top + (obj.height || height)); 8911 this.width = obj.width || width; 8912 this.lineHeight = lh !== undefined ? lh : obj.lineHeight; 8913 8914 if (isIE8 && !this.lineHeight) { 8915 this.lineHeight = 13; 8916 } 8917} 8918 8919// Move the box along a particular axis. Optionally pass in an amount to move 8920// the box. If no amount is passed then the default is the line height of the 8921// box. 8922BoxPosition.prototype.move = function(axis, toMove) { 8923 toMove = toMove !== undefined ? toMove : this.lineHeight; 8924 switch (axis) { 8925 case "+x": 8926 this.left += toMove; 8927 this.right += toMove; 8928 break; 8929 case "-x": 8930 this.left -= toMove; 8931 this.right -= toMove; 8932 break; 8933 case "+y": 8934 this.top += toMove; 8935 this.bottom += toMove; 8936 break; 8937 case "-y": 8938 this.top -= toMove; 8939 this.bottom -= toMove; 8940 break; 8941 } 8942}; 8943 8944// Check if this box overlaps another box, b2. 8945BoxPosition.prototype.overlaps = function(b2) { 8946 return this.left < b2.right && 8947 this.right > b2.left && 8948 this.top < b2.bottom && 8949 this.bottom > b2.top; 8950}; 8951 8952// Check if this box overlaps any other boxes in boxes. 8953BoxPosition.prototype.overlapsAny = function(boxes) { 8954 for (var i = 0; i < boxes.length; i++) { 8955 if (this.overlaps(boxes[i])) { 8956 return true; 8957 } 8958 } 8959 return false;
vendor: 6,016 bytes, lines 8960-9123
8960}; 8961 8962// Check if this box is within another box. 8963BoxPosition.prototype.within = function(container) { 8964 return this.top >= container.top && 8965 this.bottom <= container.bottom && 8966 this.left >= container.left && 8967 this.right <= container.right; 8968}; 8969 8970// Check if this box is entirely within the container or it is overlapping 8971// on the edge opposite of the axis direction passed. For example, if "+x" is 8972// passed and the box is overlapping on the left edge of the container, then 8973// return true. 8974BoxPosition.prototype.overlapsOppositeAxis = function(container, axis) { 8975 switch (axis) { 8976 case "+x": 8977 return this.left < container.left; 8978 case "-x": 8979 return this.right > container.right; 8980 case "+y": 8981 return this.top < container.top; 8982 case "-y": 8983 return this.bottom > container.bottom; 8984 } 8985}; 8986 8987// Find the percentage of the area that this box is overlapping with another 8988// box. 8989BoxPosition.prototype.intersectPercentage = function(b2) { 8990 var x = Math.max(0, Math.min(this.right, b2.right) - Math.max(this.left, b2.left)), 8991 y = Math.max(0, Math.min(this.bottom, b2.bottom) - Math.max(this.top, b2.top)), 8992 intersectArea = x * y; 8993 return intersectArea / (this.height * this.width); 8994}; 8995 8996// Convert the positions from this box to CSS compatible positions using 8997// the reference container's positions. This has to be done because this 8998// box's positions are in reference to the viewport origin, whereas, CSS 8999// values are in referecne to their respective edges. 9000BoxPosition.prototype.toCSSCompatValues = function(reference) { 9001 return { 9002 top: this.top - reference.top, 9003 bottom: reference.bottom - this.bottom, 9004 left: this.left - reference.left, 9005 right: reference.right - this.right, 9006 height: this.height, 9007 width: this.width 9008 }; 9009}; 9010 9011// Get an object that represents the box's position without anything extra. 9012// Can pass a StyleBox, HTMLElement, or another BoxPositon. 9013BoxPosition.getSimpleBoxPosition = function(obj) { 9014 var height = obj.div ? obj.div.offsetHeight : obj.tagName ? obj.offsetHeight : 0; 9015 var width = obj.div ? obj.div.offsetWidth : obj.tagName ? obj.offsetWidth : 0; 9016 var top = obj.div ? obj.div.offsetTop : obj.tagName ? obj.offsetTop : 0; 9017 9018 obj = obj.div ? obj.div.getBoundingClientRect() : 9019 obj.tagName ? obj.getBoundingClientRect() : obj; 9020 var ret = { 9021 left: obj.left, 9022 right: obj.right, 9023 top: obj.top || top, 9024 height: obj.height || height, 9025 bottom: obj.bottom || (top + (obj.height || height)), 9026 width: obj.width || width 9027 }; 9028 return ret; 9029}; 9030 9031// Move a StyleBox to its specified, or next best, position. The containerBox 9032// is the box that contains the StyleBox, such as a div. boxPositions are 9033// a list of other boxes that the styleBox can't overlap with. 9034function moveBoxToLinePosition(window, styleBox, containerBox, boxPositions) { 9035 9036 // Find the best position for a cue box, b, on the video. The axis parameter 9037 // is a list of axis, the order of which, it will move the box along. For example: 9038 // Passing ["+x", "-x"] will move the box first along the x axis in the positive 9039 // direction. If it doesn't find a good position for it there it will then move 9040 // it along the x axis in the negative direction. 9041 function findBestPosition(b, axis) { 9042 var bestPosition, 9043 specifiedPosition = new BoxPosition(b), 9044 percentage = 1; // Highest possible so the first thing we get is better. 9045 9046 for (var i = 0; i < axis.length; i++) { 9047 while (b.overlapsOppositeAxis(containerBox, axis[i]) || 9048 (b.within(containerBox) && b.overlapsAny(boxPositions))) { 9049 b.move(axis[i]); 9050 } 9051 // We found a spot where we aren't overlapping anything. This is our 9052 // best position. 9053 if (b.within(containerBox)) { 9054 return b; 9055 } 9056 var p = b.intersectPercentage(containerBox); 9057 // If we're outside the container box less then we were on our last try 9058 // then remember this position as the best position. 9059 if (percentage > p) { 9060 bestPosition = new BoxPosition(b); 9061 percentage = p; 9062 } 9063 // Reset the box position to the specified position. 9064 b = new BoxPosition(specifiedPosition); 9065 } 9066 return bestPosition || specifiedPosition; 9067 } 9068 9069 var boxPosition = new BoxPosition(styleBox), 9070 cue = styleBox.cue, 9071 linePos = computeLinePos(cue), 9072 axis = []; 9073 9074 // If we have a line number to align the cue to. 9075 if (cue.snapToLines) { 9076 var size; 9077 switch (cue.vertical) { 9078 case "": 9079 axis = [ "+y", "-y" ]; 9080 size = "height"; 9081 break; 9082 case "rl": 9083 axis = [ "+x", "-x" ]; 9084 size = "width"; 9085 break; 9086 case "lr": 9087 axis = [ "-x", "+x" ]; 9088 size = "width"; 9089 break; 9090 } 9091 9092 var step = boxPosition.lineHeight, 9093 position = step * Math.round(linePos), 9094 maxPosition = containerBox[size] + step, 9095 initialAxis = axis[0]; 9096 9097 // If the specified intial position is greater then the max position then 9098 // clamp the box to the amount of steps it would take for the box to 9099 // reach the max position. 9100 if (Math.abs(position) > maxPosition) { 9101 position = position < 0 ? -1 : 1; 9102 position *= Math.ceil(maxPosition / step) * step; 9103 } 9104 9105 // If computed line position returns negative then line numbers are 9106 // relative to the bottom of the video instead of the top. Therefore, we 9107 // need to increase our initial position by the length or width of the 9108 // video, depending on the writing direction, and reverse our axis directions. 9109 if (linePos < 0) { 9110 position += cue.vertical === "" ? containerBox.height : containerBox.width; 9111 axis = axis.reverse(); 9112 } 9113 9114 // Move the box to the specified position. This may not be its best 9115 // position. 9116 boxPosition.move(initialAxis, position); 9117 9118 } else { 9119 // If we have a percentage line value for the cue. 9120 var calculatedPercentage = (boxPosition.lineHeight / containerBox.height) * 100; 9121 9122 switch (cue.lineAlign) { 9123 case "middle
vendor: 10,353 bytes, lines 9123-9484
9123": 9124 linePos -= (calculatedPercentage / 2); 9125 break; 9126 case "end": 9127 linePos -= calculatedPercentage; 9128 break; 9129 } 9130 9131 // Apply initial line position to the cue box. 9132 switch (cue.vertical) { 9133 case "": 9134 styleBox.applyStyles({ 9135 top: styleBox.formatStyle(linePos, "%") 9136 }); 9137 break; 9138 case "rl": 9139 styleBox.applyStyles({ 9140 left: styleBox.formatStyle(linePos, "%") 9141 }); 9142 break; 9143 case "lr": 9144 styleBox.applyStyles({ 9145 right: styleBox.formatStyle(linePos, "%") 9146 }); 9147 break; 9148 } 9149 9150 axis = [ "+y", "-x", "+x", "-y" ]; 9151 9152 // Get the box position again after we've applied the specified positioning 9153 // to it. 9154 boxPosition = new BoxPosition(styleBox); 9155 } 9156 9157 var bestPosition = findBestPosition(boxPosition, axis); 9158 styleBox.move(bestPosition.toCSSCompatValues(containerBox)); 9159} 9160 9161function WebVTT$1() { 9162 // Nothing 9163} 9164 9165// Helper to allow strings to be decoded instead of the default binary utf8 data. 9166WebVTT$1.StringDecoder = function() { 9167 return { 9168 decode: function(data) { 9169 if (!data) { 9170 return ""; 9171 } 9172 if (typeof data !== "string") { 9173 throw new Error("Error - expected string data."); 9174 } 9175 return decodeURIComponent(encodeURIComponent(data)); 9176 } 9177 }; 9178}; 9179 9180WebVTT$1.convertCueToDOMTree = function(window, cuetext) { 9181 if (!window || !cuetext) { 9182 return null; 9183 } 9184 return parseContent(window, cuetext); 9185}; 9186 9187var FONT_SIZE_PERCENT = 0.05; 9188var FONT_STYLE = "sans-serif"; 9189var CUE_BACKGROUND_PADDING = "1.5%"; 9190 9191// Runs the processing model over the cues and regions passed to it. 9192// @param overlay A block level element (usually a div) that the computed cues 9193// and regions will be placed into. 9194WebVTT$1.processCues = function(window, cues, overlay) { 9195 if (!window || !cues || !overlay) { 9196 return null; 9197 } 9198 9199 // Remove all previous children. 9200 while (overlay.firstChild) { 9201 overlay.removeChild(overlay.firstChild); 9202 } 9203 9204 var paddedOverlay = window.document.createElement("div"); 9205 paddedOverlay.style.position = "absolute"; 9206 paddedOverlay.style.left = "0"; 9207 paddedOverlay.style.right = "0"; 9208 paddedOverlay.style.top = "0"; 9209 paddedOverlay.style.bottom = "0"; 9210 paddedOverlay.style.margin = CUE_BACKGROUND_PADDING; 9211 overlay.appendChild(paddedOverlay); 9212 9213 // Determine if we need to compute the display states of the cues. This could 9214 // be the case if a cue's state has been changed since the last computation or 9215 // if it has not been computed yet. 9216 function shouldCompute(cues) { 9217 for (var i = 0; i < cues.length; i++) { 9218 if (cues[i].hasBeenReset || !cues[i].displayState) { 9219 return true; 9220 } 9221 } 9222 return false; 9223 } 9224 9225 // We don't need to recompute the cues' display states. Just reuse them. 9226 if (!shouldCompute(cues)) { 9227 for (var i = 0; i < cues.length; i++) { 9228 paddedOverlay.appendChild(cues[i].displayState); 9229 } 9230 return; 9231 } 9232 9233 var boxPositions = [], 9234 containerBox = BoxPosition.getSimpleBoxPosition(paddedOverlay), 9235 fontSize = Math.round(containerBox.height * FONT_SIZE_PERCENT * 100) / 100; 9236 var styleOptions = { 9237 font: fontSize + "px " + FONT_STYLE 9238 }; 9239 9240 (function() { 9241 var styleBox, cue; 9242 9243 for (var i = 0; i < cues.length; i++) { 9244 cue = cues[i]; 9245 9246 // Compute the intial position and styles of the cue div. 9247 styleBox = new CueStyleBox(window, cue, styleOptions); 9248 paddedOverlay.appendChild(styleBox.div); 9249 9250 // Move the cue div to it's correct line position. 9251 moveBoxToLinePosition(window, styleBox, containerBox, boxPositions); 9252 9253 // Remember the computed div so that we don't have to recompute it later 9254 // if we don't have too. 9255 cue.displayState = styleBox.div; 9256 9257 boxPositions.push(BoxPosition.getSimpleBoxPosition(styleBox)); 9258 } 9259 })(); 9260}; 9261 9262WebVTT$1.Parser = function(window, vttjs, decoder) { 9263 if (!decoder) { 9264 decoder = vttjs; 9265 vttjs = {}; 9266 } 9267 if (!vttjs) { 9268 vttjs = {}; 9269 } 9270 9271 this.window = window; 9272 this.vttjs = vttjs; 9273 this.state = "INITIAL"; 9274 this.buffer = ""; 9275 this.decoder = decoder || new TextDecoder("utf8"); 9276 this.regionList = []; 9277}; 9278 9279WebVTT$1.Parser.prototype = { 9280 // If the error is a ParsingError then report it to the consumer if 9281 // possible. If it's not a ParsingError then throw it like normal. 9282 reportOrThrowError: function(e) { 9283 if (e instanceof ParsingError) { 9284 this.onparsingerror && this.onparsingerror(e); 9285 } else { 9286 throw e; 9287 } 9288 }, 9289 parse: function (data) { 9290 var self = this; 9291 9292 // If there is no data then we won't decode it, but will just try to parse 9293 // whatever is in buffer already. This may occur in circumstances, for 9294 // example when flush() is called. 9295 if (data) { 9296 // Try to decode the data that we received. 9297 self.buffer += self.decoder.decode(data, {stream: true}); 9298 } 9299 9300 function collectNextLine() { 9301 var buffer = self.buffer; 9302 var pos = 0; 9303 while (pos < buffer.length && buffer[pos] !== '\r' && buffer[pos] !== '\n') { 9304 ++pos; 9305 } 9306 var line = buffer.substr(0, pos); 9307 // Advance the buffer early in case we fail below. 9308 if (buffer[pos] === '\r') { 9309 ++pos; 9310 } 9311 if (buffer[pos] === '\n') { 9312 ++pos; 9313 } 9314 self.buffer = buffer.substr(pos); 9315 return line; 9316 } 9317 9318 // 3.4 WebVTT region and WebVTT region settings syntax 9319 function parseRegion(input) { 9320 var settings = new Settings(); 9321 9322 parseOptions(input, function (k, v) { 9323 switch (k) { 9324 case "id": 9325 settings.set(k, v); 9326 break; 9327 case "width": 9328 settings.percent(k, v); 9329 break; 9330 case "lines": 9331 settings.integer(k, v); 9332 break; 9333 case "regionanchor": 9334 case "viewportanchor": 9335 var xy = v.split(','); 9336 if (xy.length !== 2) { 9337 break; 9338 } 9339 // We have to make sure both x and y parse, so use a temporary 9340 // settings object here. 9341 var anchor = new Settings(); 9342 anchor.percent("x", xy[0]); 9343 anchor.percent("y", xy[1]); 9344 if (!anchor.has("x") || !anchor.has("y")) { 9345 break; 9346 } 9347 settings.set(k + "X", anchor.get("x")); 9348 settings.set(k + "Y", anchor.get("y")); 9349 break; 9350 case "scroll": 9351 settings.alt(k, v, ["up"]); 9352 break; 9353 } 9354 }, /=/, /\s/); 9355 9356 // Create the region, using default values for any values that were not 9357 // specified. 9358 if (settings.has("id")) { 9359 var region = new (self.vttjs.VTTRegion || self.window.VTTRegion)(); 9360 region.width = settings.get("width", 100); 9361 region.lines = settings.get("lines", 3); 9362 region.regionAnchorX = settings.get("regionanchorX", 0); 9363 region.regionAnchorY = settings.get("regionanchorY", 100); 9364 region.viewportAnchorX = settings.get("viewportanchorX", 0); 9365 region.viewportAnchorY = settings.get("viewportanchorY", 100); 9366 region.scroll = settings.get("scroll", ""); 9367 // Register the region. 9368 self.onregion && self.onregion(region); 9369 // Remember the VTTRegion for later in case we parse any VTTCues that 9370 // reference it. 9371 self.regionList.push({ 9372 id: settings.get("id"), 9373 region: region 9374 }); 9375 } 9376 } 9377 9378 // draft-pantos-http-live-streaming-20 9379 // https://tools.ietf.org/html/draft-pantos-http-live-streaming-20#section-3.5 9380 // 3.5 WebVTT 9381 function parseTimestampMap(input) { 9382 var settings = new Settings(); 9383 9384 parseOptions(input, function(k, v) { 9385 switch(k) { 9386 case "MPEGT": 9387 settings.integer(k + 'S', v); 9388 break; 9389 case "LOCA": 9390 settings.set(k + 'L', parseTimeStamp(v)); 9391 break; 9392 } 9393 }, /[^\d]:/, /,/); 9394 9395 self.ontimestampmap && self.ontimestampmap({ 9396 "MPEGTS": settings.get("MPEGTS"), 9397 "LOCAL": settings.get("LOCAL") 9398 }); 9399 } 9400 9401 // 3.2 WebVTT metadata header syntax 9402 function parseHeader(input) { 9403 if (input.match(/X-TIMESTAMP-MAP/)) { 9404 // This line contains HLS X-TIMESTAMP-MAP metadata 9405 parseOptions(input, function(k, v) { 9406 switch(k) { 9407 case "X-TIMESTAMP-MAP": 9408 parseTimestampMap(v); 9409 break; 9410 } 9411 }, /=/); 9412 } else { 9413 parseOptions(input, function (k, v) { 9414 switch (k) { 9415 case "Region": 9416 // 3.3 WebVTT region metadata header syntax 9417 parseRegion(v); 9418 break; 9419 } 9420 }, /:/); 9421 } 9422 9423 } 9424 9425 // 5.1 WebVTT file parsing. 9426 try { 9427 var line; 9428 if (self.state === "INITIAL") { 9429 // We can't start parsing until we have the first line. 9430 if (!/\r\n|\n/.test(self.buffer)) { 9431 return this; 9432 } 9433 9434 line = collectNextLine(); 9435 9436 var m = line.match(/^WEBVTT([ \t].*)?$/); 9437 if (!m || !m[0]) { 9438 throw new ParsingError(ParsingError.Errors.BadSignature); 9439 } 9440 9441 self.state = "HEADER"; 9442 } 9443 9444 var alreadyCollectedLine = false; 9445 while (self.buffer) { 9446 // We can't parse a line until we have the full line. 9447 if (!/\r\n|\n/.test(self.buffer)) { 9448 return this; 9449 } 9450 9451 if (!alreadyCollectedLine) { 9452 line = collectNextLine(); 9453 } else { 9454 alreadyCollectedLine = false; 9455 } 9456 9457 switch (self.state) { 9458 case "HEADER": 9459 // 13-18 - Allow a header (metadata) under the WEBVTT line. 9460 if (/:/.test(line)) { 9461 parseHeader(line); 9462 } else if (!line) { 9463 // An empty line terminates the header and starts the body (cues). 9464 self.state = "ID"; 9465 } 9466 continue; 9467 case "NOTE": 9468 // Ignore NOTE blocks. 9469 if (!line) { 9470 self.state = "ID"; 9471 } 9472 continue; 9473 case "ID": 9474 // Check for the start of NOTE blocks. 9475 if (/^NOTE($|[ \t])/.test(line)) { 9476 self.state = "NOTE"; 9477 break; 9478 } 9479 // 19-29 - Allow any number of line terminators, then initialize new cue values. 9480 if (!line) { 9481 continue; 9482 } 9483 self.cue = new (self.vttjs.VTTCue || self.window.VTTCue)(0, 0, ""); 9484
vendor: 2,935 bytes, lines 9484-9574
9484self.state = "CUE"; 9485 // 30-39 - Check if self line contains an optional identifier or timing data. 9486 if (line.indexOf("-->") === -1) { 9487 self.cue.id = line; 9488 continue; 9489 } 9490 // Process line as start of a cue. 9491 /*falls through*/ 9492 case "CUE": 9493 // 40 - Collect cue timings and settings. 9494 try { 9495 parseCue(line, self.cue, self.regionList); 9496 } catch (e) { 9497 self.reportOrThrowError(e); 9498 // In case of an error ignore rest of the cue. 9499 self.cue = null; 9500 self.state = "BADCUE"; 9501 continue; 9502 } 9503 self.state = "CUETEXT"; 9504 continue; 9505 case "CUETEXT": 9506 var hasSubstring = line.indexOf("-->") !== -1; 9507 // 34 - If we have an empty line then report the cue. 9508 // 35 - If we have the special substring '-->' then report the cue, 9509 // but do not collect the line as we need to process the current 9510 // one as a new cue. 9511 if (!line || hasSubstring && (alreadyCollectedLine = true)) { 9512 // We are done parsing self cue. 9513 self.oncue && self.oncue(self.cue); 9514 self.cue = null; 9515 self.state = "ID"; 9516 continue; 9517 } 9518 if (self.cue.text) { 9519 self.cue.text += "\n"; 9520 } 9521 self.cue.text += line; 9522 continue; 9523 case "BADCUE": // BADCUE 9524 // 54-62 - Collect and discard the remaining cue. 9525 if (!line) { 9526 self.state = "ID"; 9527 } 9528 continue; 9529 } 9530 } 9531 } catch (e) { 9532 self.reportOrThrowError(e); 9533 9534 // If we are currently parsing a cue, report what we have. 9535 if (self.state === "CUETEXT" && self.cue && self.oncue) { 9536 self.oncue(self.cue); 9537 } 9538 self.cue = null; 9539 // Enter BADWEBVTT state if header was not parsed correctly otherwise 9540 // another exception occurred so enter BADCUE state. 9541 self.state = self.state === "INITIAL" ? "BADWEBVTT" : "BADCUE"; 9542 } 9543 return this; 9544 }, 9545 flush: function () { 9546 var self = this; 9547 try { 9548 // Finish decoding the stream. 9549 self.buffer += self.decoder.decode(); 9550 // Synthesize the end of the current cue or region. 9551 if (self.cue || self.state === "HEADER") { 9552 self.buffer += "\n\n"; 9553 self.parse(); 9554 } 9555 // If we've flushed, parsed, and we're still on the INITIAL state then 9556 // that means we don't have enough of the stream to parse the first 9557 // line. 9558 if (self.state === "INITIAL") { 9559 throw new ParsingError(ParsingError.Errors.BadSignature); 9560 } 9561 } catch(e) { 9562 self.reportOrThrowError(e); 9563 } 9564 self.onflush && self.onflush(); 9565 return this; 9566 } 9567}; 9568 9569var vtt$1 = WebVTT$1; 9570 9571/** 9572 * Copyright 2013 vtt.js Contributors 9573 * 9574 * Licensed under the Apache License, Version 2.0 (the "License");
9575 * you may not use this file except in compliance with the License. 9576 * You may obtain a copy of the License at 9577 * 9578 * http://www.apache.org/licenses/LICENSE-2.0 9579 * 9580 * Unless required by applicable law or agreed to in writing, software 9581 * distributed under the License is distributed on an "AS IS" BASIS, 9582 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9583 * See the License for the specific language governing permissions and 9584 * limitations under the License. 9585 */ 9586 9587var autoKeyword = "auto"; 9588var directionSetting = { 9589 "": true, 9590 "lr": true, 9591 "rl": true 9592}; 9593var alignSetting = { 9594 "start": true, 9595 "middle": true, 9596 "end": true, 9597 "left": true, 9598 "right": true 9599}; 9600 9601function findDirectionSetting(value) { 9602 if (typeof value !== "string") { 9603 return false; 9604 } 9605 var dir = directionSetting[value.toLowerCase()]; 9606 return dir ? value.toLowerCase() : false; 9607} 9608 9609function findAlignSetting(value) { 9610 if (typeof value !== "string") { 9611 return false; 9612 } 9613 var align = alignSetting[value.toLowerCase()]; 9614 return align ? value.toLowerCase() : false; 9615} 9616 9617function extend$1(obj) { 9618 var i = 1; 9619 for (; i < arguments.length; i++) { 9620 var cobj = arguments[i]; 9621 for (var p in cobj) { 9622 obj[p] = cobj[p]; 9623 } 9624 } 9625 9626 return obj; 9627} 9628 9629function VTTCue(startTime, endTime, text) { 9630 var cue = this; 9631 var isIE8 = (/MSIE\s8\.0/).test(navigator.userAgent); 9632 var baseObj = {}; 9633 9634 if (isIE8) { 9635 cue = document.createElement('custom'); 9636 } else { 9637 baseObj.enumerable = true; 9638 } 9639 9640 /** 9641 * Shim implementation specific properties. These properties are not in 9642 * the spec. 9643 */ 9644 9645 // Lets us know when the VTTCue's data has changed in such a way that we need 9646 // to recompute its display state. This lets us compute its display state 9647 // lazily. 9648 cue.hasBeenReset = false; 9649 9650 /** 9651 * VTTCue and TextTrackCue properties 9652 * http://dev.w3.org/html5/webvtt/#vttcue-interface 9653 */ 9654 9655 var _id = ""; 9656 var _pauseOnExit = false; 9657 var _startTime = startTime; 9658 var _endTime = endTime; 9659 var _text = text; 9660 var _region = null; 9661 var _vertical = ""; 9662 var _snapToLines = true; 9663 var _line = "auto"; 9664 var _lineAlign = "start"; 9665 var _position = 50; 9666 var _positionAlign = "middle"; 9667 var _size = 50; 9668 var _align = "middle"; 9669 9670 Object.defineProperty(cue, 9671 "id", extend$1({}, baseObj, { 9672 get: function() { 9673 return _id; 9674 }, 9675 set: function(value) { 9676 _id = "" + value; 9677 } 9678 })); 9679 9680 Object.defineProperty(cue, 9681 "pauseOnExit", extend$1({}, baseObj, { 9682 get: function() { 9683 return _pauseOnExit; 9684 }, 9685 set: function(value) { 9686 _pauseOnExit = !!value; 9687 } 9688 })); 9689 9690 Object.defineProperty(cue, 9691 "startTime", extend$1({}, baseObj, { 9692 get: function() { 9693 return _startTime; 9694 }, 9695 set: function(value) { 9696 if (typeof value !== "number") { 9697 throw new TypeError("Start time must be set to a number."); 9698 } 9699 _startTime = value; 9700 this.hasBeenReset = true; 9701 } 9702 })); 9703 9704 Object.defineProperty(cue, 9705 "endTime", extend$1({}, baseObj, { 9706 get: function() { 9707 return _endTime; 9708 }, 9709 set: function(value) { 9710 if (typeof value !== "number") { 9711 throw new TypeError("End time must be set to a number."); 9712 } 9713 _endTime = value; 9714 this.hasBeenReset = true; 9715 } 9716 })); 9717 9718 Object.defineProperty(cue, 9719 "text", extend$1({}, baseObj, { 9720 get: function() { 9721 return _text; 9722 }, 9723 set: function(value) { 9724 _text = "" + value; 9725 this.hasBeenReset = true; 9726 } 9727 })); 9728 9729 Object.defineProperty(cue, 9730 "region", extend$1({}, baseObj, { 9731 get: function() { 9732 return _region; 9733 }, 9734 set: function(value) { 9735 _region = value; 9736 this.hasBeenReset = true; 9737 } 9738 })); 9739 9740 Object.defineProperty(cue, 9741 "vertical", extend$1({}, baseObj, { 9742 get: function() { 9743 return _vertical; 9744 }, 9745 set: function(value) { 9746 var setting = findDirectionSetting(value); 9747 // Have to check for false because the setting an be an empty string. 9748 if (setting === false) { 9749 throw new SyntaxError("An invalid or illegal string was specified."); 9750 } 9751 _vertical = setting; 9752 this.hasBeenReset = true; 9753 } 9754 })); 9755 9756 Object.defineProperty(cue, 9757 "snapToLines", extend$1({}, baseObj, { 9758 get: function() { 9759 return _snapToLines; 9760 }, 9761 set: function(value) { 9762 _snapToLines = !!value; 9763 this.hasBeenReset = true; 9764 } 9765 })); 9766 9767 Object.defineProperty(cue, 9768 "line", extend$1({}, baseObj, { 9769 get: function() { 9770 return _line; 9771 }, 9772 set: function(value) {
vendor: 4,245 bytes, lines 9773-9938
9773 if (typeof value !== "number" && value !== autoKeyword) { 9774 throw new SyntaxError("An invalid number or illegal string was specified."); 9775 } 9776 _line = value; 9777 this.hasBeenReset = true; 9778 } 9779 })); 9780 9781 Object.defineProperty(cue, 9782 "lineAlign", extend$1({}, baseObj, { 9783 get: function() { 9784 return _lineAlign; 9785 }, 9786 set: function(value) { 9787 var setting = findAlignSetting(value); 9788 if (!setting) { 9789 throw new SyntaxError("An invalid or illegal string was specified."); 9790 } 9791 _lineAlign = setting; 9792 this.hasBeenReset = true; 9793 } 9794 })); 9795 9796 Object.defineProperty(cue, 9797 "position", extend$1({}, baseObj, { 9798 get: function() { 9799 return _position; 9800 }, 9801 set: function(value) { 9802 if (value < 0 || value > 100) { 9803 throw new Error("Position must be between 0 and 100."); 9804 } 9805 _position = value; 9806 this.hasBeenReset = true; 9807 } 9808 })); 9809 9810 Object.defineProperty(cue, 9811 "positionAlign", extend$1({}, baseObj, { 9812 get: function() { 9813 return _positionAlign; 9814 }, 9815 set: function(value) { 9816 var setting = findAlignSetting(value); 9817 if (!setting) { 9818 throw new SyntaxError("An invalid or illegal string was specified."); 9819 } 9820 _positionAlign = setting; 9821 this.hasBeenReset = true; 9822 } 9823 })); 9824 9825 Object.defineProperty(cue, 9826 "size", extend$1({}, baseObj, { 9827 get: function() { 9828 return _size; 9829 }, 9830 set: function(value) { 9831 if (value < 0 || value > 100) { 9832 throw new Error("Size must be between 0 and 100."); 9833 } 9834 _size = value; 9835 this.hasBeenReset = true; 9836 } 9837 })); 9838 9839 Object.defineProperty(cue, 9840 "align", extend$1({}, baseObj, { 9841 get: function() { 9842 return _align; 9843 }, 9844 set: function(value) { 9845 var setting = findAlignSetting(value); 9846 if (!setting) { 9847 throw new SyntaxError("An invalid or illegal string was specified."); 9848 } 9849 _align = setting; 9850 this.hasBeenReset = true; 9851 } 9852 })); 9853 9854 /** 9855 * Other <track> spec defined properties 9856 */ 9857 9858 // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#text-track-cue-display-state 9859 cue.displayState = undefined; 9860 9861 if (isIE8) { 9862 return cue; 9863 } 9864} 9865 9866/** 9867 * VTTCue methods 9868 */ 9869 9870VTTCue.prototype.getCueAsHTML = function() { 9871 // Assume WebVTT.convertCueToDOMTree is on the global. 9872 return WebVTT.convertCueToDOMTree(window, this.text); 9873}; 9874 9875var vttcue = VTTCue; 9876 9877/** 9878 * Copyright 2013 vtt.js Contributors 9879 * 9880 * Licensed under the Apache License, Version 2.0 (the "License"); 9881 * you may not use this file except in compliance with the License. 9882 * You may obtain a copy of the License at 9883 * 9884 * http://www.apache.org/licenses/LICENSE-2.0 9885 * 9886 * Unless required by applicable law or agreed to in writing, software 9887 * distributed under the License is distributed on an "AS IS" BASIS, 9888 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9889 * See the License for the specific language governing permissions and 9890 * limitations under the License. 9891 */ 9892 9893var scrollSetting = { 9894 "": true, 9895 "up": true 9896}; 9897 9898function findScrollSetting(value) { 9899 if (typeof value !== "string") { 9900 return false; 9901 } 9902 var scroll = scrollSetting[value.toLowerCase()]; 9903 return scroll ? value.toLowerCase() : false; 9904} 9905 9906function isValidPercentValue(value) { 9907 return typeof value === "number" && (value >= 0 && value <= 100); 9908} 9909 9910// VTTRegion shim http://dev.w3.org/html5/webvtt/#vttregion-interface 9911function VTTRegion() { 9912 var _width = 100; 9913 var _lines = 3; 9914 var _regionAnchorX = 0; 9915 var _regionAnchorY = 100; 9916 var _viewportAnchorX = 0; 9917 var _viewportAnchorY = 100; 9918 var _scroll = ""; 9919 9920 Object.defineProperties(this, { 9921 "width": { 9922 enumerable: true, 9923 get: function() { 9924 return _width; 9925 }, 9926 set: function(value) { 9927 if (!isValidPercentValue(value)) { 9928 throw new Error("Width must be between 0 and 100."); 9929 } 9930 _width = value; 9931 } 9932 }, 9933 "lines": { 9934 enumerable: true, 9935 get: function() { 9936 return _lines; 9937 }, 9938 set: function(value) {
vendor: 3,307 bytes, lines 9939-10063
9939 if (typeof value !== "number") { 9940 throw new TypeError("Lines must be set to a number."); 9941 } 9942 _lines = value; 9943 } 9944 }, 9945 "regionAnchorY": { 9946 enumerable: true, 9947 get: function() { 9948 return _regionAnchorY; 9949 }, 9950 set: function(value) { 9951 if (!isValidPercentValue(value)) { 9952 throw new Error("RegionAnchorX must be between 0 and 100."); 9953 } 9954 _regionAnchorY = value; 9955 } 9956 }, 9957 "regionAnchorX": { 9958 enumerable: true, 9959 get: function() { 9960 return _regionAnchorX; 9961 }, 9962 set: function(value) { 9963 if(!isValidPercentValue(value)) { 9964 throw new Error("RegionAnchorY must be between 0 and 100."); 9965 } 9966 _regionAnchorX = value; 9967 } 9968 }, 9969 "viewportAnchorY": { 9970 enumerable: true, 9971 get: function() { 9972 return _viewportAnchorY; 9973 }, 9974 set: function(value) { 9975 if (!isValidPercentValue(value)) { 9976 throw new Error("ViewportAnchorY must be between 0 and 100."); 9977 } 9978 _viewportAnchorY = value; 9979 } 9980 }, 9981 "viewportAnchorX": { 9982 enumerable: true, 9983 get: function() { 9984 return _viewportAnchorX; 9985 }, 9986 set: function(value) { 9987 if (!isValidPercentValue(value)) { 9988 throw new Error("ViewportAnchorX must be between 0 and 100."); 9989 } 9990 _viewportAnchorX = value; 9991 } 9992 }, 9993 "scroll": { 9994 enumerable: true, 9995 get: function() { 9996 return _scroll; 9997 }, 9998 set: function(value) { 9999 var setting = findScrollSetting(value); 10000 // Have to check for false as an empty string is a legal value. 10001 if (setting === false) { 10002 throw new SyntaxError("An invalid or illegal string was specified."); 10003 } 10004 _scroll = setting; 10005 } 10006 } 10007 }); 10008} 10009 10010var vttregion = VTTRegion; 10011 10012var browserIndex = createCommonjsModule(function (module) { 10013/** 10014 * Copyright 2013 vtt.js Contributors 10015 * 10016 * Licensed under the Apache License, Version 2.0 (the "License"); 10017 * you may not use this file except in compliance with the License. 10018 * You may obtain a copy of the License at 10019 * 10020 * http://www.apache.org/licenses/LICENSE-2.0 10021 * 10022 * Unless required by applicable law or agreed to in writing, software 10023 * distributed under the License is distributed on an "AS IS" BASIS, 10024 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10025 * See the License for the specific language governing permissions and 10026 * limitations under the License. 10027 */ 10028 10029// Default exports for Node. Export the extended versions of VTTCue and 10030// VTTRegion in Node since we likely want the capability to convert back and 10031// forth between JSON. If we don't then it's not that big of a deal since we're 10032// off browser. 10033 10034 10035 10036var vttjs = module.exports = { 10037 WebVTT: vtt$1, 10038 VTTCue: vttcue, 10039 VTTRegion: vttregion 10040}; 10041 10042window_1.vttjs = vttjs; 10043window_1.WebVTT = vttjs.WebVTT; 10044 10045var cueShim = vttjs.VTTCue; 10046var regionShim = vttjs.VTTRegion; 10047var nativeVTTCue = window_1.VTTCue; 10048var nativeVTTRegion = window_1.VTTRegion; 10049 10050vttjs.shim = function() { 10051 window_1.VTTCue = cueShim; 10052 window_1.VTTRegion = regionShim; 10053}; 10054 10055vttjs.restore = function() { 10056 window_1.VTTCue = nativeVTTCue; 10057 window_1.VTTRegion = nativeVTTRegion; 10058}; 10059 10060if (!window_1.VTTCue) { 10061 vttjs.shim(); 10062} 10063}
vendor: 13,208 bytes, lines 10063-10554
10063); 10064 10065/** 10066 * @file tech.js 10067 */ 10068 10069/** 10070 * An Object containing a structure like: `{src: 'url', type: 'mimetype'}` or string 10071 * that just contains the src url alone. 10072 * * `var SourceObject = {src: 'http://ex.com/video.mp4', type: 'video/mp4'};` 10073 * `var SourceString = 'http://example.com/some-video.mp4';` 10074 * 10075 * @typedef {Object|string} Tech~SourceObject 10076 * 10077 * @property {string} src 10078 * The url to the source 10079 * 10080 * @property {string} type 10081 * The mime type of the source 10082 */ 10083 10084/** 10085 * A function used by {@link Tech} to create a new {@link TextTrack}. 10086 * 10087 * @private 10088 * 10089 * @param {Tech} self 10090 * An instance of the Tech class. 10091 * 10092 * @param {string} kind 10093 * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata) 10094 * 10095 * @param {string} [label] 10096 * Label to identify the text track 10097 * 10098 * @param {string} [language] 10099 * Two letter language abbreviation 10100 * 10101 * @param {Object} [options={}] 10102 * An object with additional text track options 10103 * 10104 * @return {TextTrack} 10105 * The text track that was created. 10106 */ 10107function createTrackHelper(self, kind, label, language) { 10108 var options = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : {}; 10109 10110 var tracks = self.textTracks(); 10111 10112 options.kind = kind; 10113 10114 if (label) { 10115 options.label = label; 10116 } 10117 if (language) { 10118 options.language = language; 10119 } 10120 options.tech = self; 10121 10122 var track = new ALL.text.TrackClass(options); 10123 10124 tracks.addTrack(track); 10125 10126 return track; 10127} 10128 10129/** 10130 * This is the base class for media playback technology controllers, such as 10131 * {@link Flash} and {@link HTML5} 10132 * 10133 * @extends Component 10134 */ 10135 10136var Tech = function (_Component) { 10137 inherits(Tech, _Component); 10138 10139 /** 10140 * Create an instance of this Tech. 10141 * 10142 * @param {Object} [options] 10143 * The key/value store of player options. 10144 * 10145 * @param {Component~ReadyCallback} ready 10146 * Callback function to call when the `HTML5` Tech is ready. 10147 */ 10148 function Tech() { 10149 var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; 10150 var ready = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : function () {}; 10151 classCallCheck(this, Tech); 10152 10153 // we don't want the tech to report user activity automatically. 10154 // This is done manually in addControlsListeners 10155 options.reportTouchActivity = false; 10156 10157 // keep track of whether the current source has played at all to 10158 // implement a very limited played() 10159 var _this = possibleConstructorReturn(this, _Component.call(this, null, options, ready)); 10160 10161 _this.hasStarted_ = false; 10162 _this.on('playing', function () { 10163 this.hasStarted_ = true; 10164 }); 10165 _this.on('loadstart', function () { 10166 this.hasStarted_ = false; 10167 }); 10168 10169 ALL.names.forEach(function (name) { 10170 var props = ALL[name]; 10171 10172 if (options && options[props.getterName]) { 10173 _this[props.privateName] = options[props.getterName]; 10174 } 10175 }); 10176 10177 // Manually track progress in cases where the browser/flash player doesn't report it. 10178 if (!_this.featuresProgressEvents) { 10179 _this.manualProgressOn(); 10180 } 10181 10182 // Manually track timeupdates in cases where the browser/flash player doesn't report it. 10183 if (!_this.featuresTimeupdateEvents) { 10184 _this.manualTimeUpdatesOn(); 10185 } 10186 10187 ['Text', 'Audio', 'Video'].forEach(function (track) { 10188 if (options['native' + track + 'Tracks'] === false) { 10189 _this['featuresNative' + track + 'Tracks'] = false; 10190 } 10191 }); 10192 10193 if (options.nativeCaptions === false || options.nativeTextTracks === false) { 10194 _this.featuresNativeTextTracks = false; 10195 } else if (options.nativeCaptions === true || options.nativeTextTracks === true) { 10196 _this.featuresNativeTextTracks = true; 10197 } 10198 10199 if (!_this.featuresNativeTextTracks) { 10200 _this.emulateTextTracks(); 10201 } 10202 10203 _this.autoRemoteTextTracks_ = new ALL.text.ListClass(); 10204 10205 _this.initTrackListeners(); 10206 10207 // Turn on component tap events only if not using native controls 10208 if (!options.nativeControlsForTouch) { 10209 _this.emitTapEvents(); 10210 } 10211 10212 if (_this.constructor) { 10213 _this.name_ = _this.constructor.name || 'Unknown Tech'; 10214 } 10215 return _this; 10216 } 10217 10218 /** 10219 * A special function to trigger source set in a way that will allow player 10220 * to re-trigger if the player or tech are not ready yet. 10221 * 10222 * @fires Tech#sourceset 10223 * @param {string} src The source string at the time of the source changing. 10224 */ 10225 10226 10227 Tech.prototype.triggerSourceset = function triggerSourceset(src) { 10228 var _this2 = this; 10229 10230 if (!this.isReady_) { 10231 // on initial ready we have to trigger source set 10232 // 1ms after ready so that player can watch for it. 10233 this.one('ready', function () { 10234 return _this2.setTimeout(function () { 10235 return _this2.triggerSourceset(src); 10236 }, 1); 10237 }); 10238 } 10239 10240 /** 10241 * Fired when the source is set on the tech causing the media element 10242 * to reload. 10243 * 10244 * @see {@link Player#event:sourceset} 10245 * @event Tech#sourceset 10246 * @type {EventTarget~Event} 10247 */ 10248 this.trigger({ 10249 src: src, 10250 type: 'sourceset' 10251 }); 10252 }; 10253 10254 /* Fallbacks for unsupported event types 10255 ================================================================================ */ 10256 10257 /** 10258 * Polyfill the `progress` event for browsers that don't support it natively. 10259 * 10260 * @see {@link Tech#trackProgress} 10261 */ 10262 10263 10264 Tech.prototype.manualProgressOn = function manualProgressOn() { 10265 this.on('durationchange', this.onDurationChange); 10266 10267 this.manualProgress = true; 10268 10269 // Trigger progress watching when a source begins loading 10270 this.one('ready', this.trackProgress); 10271 }; 10272 10273 /** 10274 * Turn off the polyfill for `progress` events that was created in 10275 * {@link Tech#manualProgressOn} 10276 */ 10277 10278 10279 Tech.prototype.manualProgressOff = function manualProgressOff() { 10280 this.manualProgress = false; 10281 this.stopTrackingProgress(); 10282 10283 this.off('durationchange', this.onDurationChange); 10284 }; 10285 10286 /** 10287 * This is used to trigger a `progress` event when the buffered percent changes. It 10288 * sets an interval function that will be called every 500 milliseconds to check if the 10289 * buffer end percent has changed. 10290 * 10291 * > This function is called by {@link Tech#manualProgressOn} 10292 * 10293 * @param {EventTarget~Event} event 10294 * The `ready` event that caused this to run. 10295 * 10296 * @listens Tech#ready 10297 * @fires Tech#progress 10298 */ 10299 10300 10301 Tech.prototype.trackProgress = function trackProgress(event) { 10302 this.stopTrackingProgress(); 10303 this.progressInterval = this.setInterval(bind(this, function () { 10304 // Don't trigger unless buffered amount is greater than last time 10305 10306 var numBufferedPercent = this.bufferedPercent(); 10307 10308 if (this.bufferedPercent_ !== numBufferedPercent) { 10309 /** 10310 * See {@link Player#progress} 10311 * 10312 * @event Tech#progress 10313 * @type {EventTarget~Event} 10314 */ 10315 this.trigger('progress'); 10316 } 10317 10318 this.bufferedPercent_ = numBufferedPercent; 10319 10320 if (numBufferedPercent === 1) { 10321 this.stopTrackingProgress(); 10322 } 10323 }), 500); 10324 }; 10325 10326 /** 10327 * Update our internal duration on a `durationchange` event by calling 10328 * {@link Tech#duration}. 10329 * 10330 * @param {EventTarget~Event} event 10331 * The `durationchange` event that caused this to run. 10332 * 10333 * @listens Tech#durationchange 10334 */ 10335 10336 10337 Tech.prototype.onDurationChange = function onDurationChange(event) { 10338 this.duration_ = this.duration(); 10339 }; 10340 10341 /** 10342 * Get and create a `TimeRange` object for buffering. 10343 * 10344 * @return {TimeRange} 10345 * The time range object that was created. 10346 */ 10347 10348 10349 Tech.prototype.buffered = function buffered() { 10350 return createTimeRanges(0, 0); 10351 }; 10352 10353 /** 10354 * Get the percentage of the current video that is currently buffered. 10355 * 10356 * @return {number} 10357 * A number from 0 to 1 that represents the decimal percentage of the 10358 * video that is buffered. 10359 * 10360 */ 10361 10362 10363 Tech.prototype.bufferedPercent = function bufferedPercent$$1() { 10364 return bufferedPercent(this.buffered(), this.duration_); 10365 }; 10366 10367 /** 10368 * Turn off the polyfill for `progress` events that was created in 10369 * {@link Tech#manualProgressOn} 10370 * Stop manually tracking progress events by clearing the interval that was set in 10371 * {@link Tech#trackProgress}. 10372 */ 10373 10374 10375 Tech.prototype.stopTrackingProgress = function stopTrackingProgress() { 10376 this.clearInterval(this.progressInterval); 10377 }; 10378 10379 /** 10380 * Polyfill the `timeupdate` event for browsers that don't support it. 10381 * 10382 * @see {@link Tech#trackCurrentTime} 10383 */ 10384 10385 10386 Tech.prototype.manualTimeUpdatesOn = function manualTimeUpdatesOn() { 10387 this.manualTimeUpdates = true; 10388 10389 this.on('play', this.trackCurrentTime); 10390 this.on('pause', this.stopTrackingCurrentTime); 10391 }; 10392 10393 /** 10394 * Turn off the polyfill for `timeupdate` events that was created in 10395 * {@link Tech#manualTimeUpdatesOn} 10396 */ 10397 10398 10399 Tech.prototype.manualTimeUpdatesOff = function manualTimeUpdatesOff() { 10400 this.manualTimeUpdates = false; 10401 this.stopTrackingCurrentTime(); 10402 this.off('play', this.trackCurrentTime); 10403 this.off('pause', this.stopTrackingCurrentTime); 10404 }; 10405 10406 /** 10407 * Sets up an interval function to track current time and trigger `timeupdate` every 10408 * 250 milliseconds. 10409 * 10410 * @listens Tech#play 10411 * @triggers Tech#timeupdate 10412 */ 10413 10414 10415 Tech.prototype.trackCurrentTime = function trackCurrentTime() { 10416 if (this.currentTimeInterval) { 10417 this.stopTrackingCurrentTime(); 10418 } 10419 this.currentTimeInterval = this.setInterval(function () { 10420 /** 10421 * Triggered at an interval of 250ms to indicated that time is passing in the video. 10422 * 10423 * @event Tech#timeupdate 10424 * @type {EventTarget~Event} 10425 */ 10426 this.trigger({ type: 'timeupdate', target: this, manuallyTriggered: true }); 10427 10428 // 42 = 24 fps // 250 is what Webkit uses // FF uses 15 10429 }, 250); 10430 }; 10431 10432 /** 10433 * Stop the interval function created in {@link Tech#trackCurrentTime} so that the 10434 * `timeupdate` event is no longer triggered. 10435 * 10436 * @listens {Tech#pause} 10437 */ 10438 10439 10440 Tech.prototype.stopTrackingCurrentTime = function stopTrackingCurrentTime() { 10441 this.clearInterval(this.currentTimeInterval); 10442 10443 // #1002 - if the video ends right before the next timeupdate would happen, 10444 // the progress bar won't make it all the way to the end 10445 this.trigger({ type: 'timeupdate', target: this, manuallyTriggered: true }); 10446 }; 10447 10448 /** 10449 * Turn off all event polyfills, clear the `Tech`s {@link AudioTrackList}, 10450 * {@link VideoTrackList}, and {@link TextTrackList}, and dispose of this Tech. 10451 * 10452 * @fires Component#dispose 10453 */ 10454 10455 10456 Tech.prototype.dispose = function dispose() { 10457 10458 // clear out all tracks because we can't reuse them between techs 10459 this.clearTracks(NORMAL.names); 10460 10461 // Turn off any manual progress or timeupdate tracking 10462 if (this.manualProgress) { 10463 this.manualProgressOff(); 10464 } 10465 10466 if (this.manualTimeUpdates) { 10467 this.manualTimeUpdatesOff(); 10468 } 10469 10470 _Component.prototype.dispose.call(this); 10471 }; 10472 10473 /** 10474 * Clear out a single `TrackList` or an array of `TrackLists` given their names. 10475 * 10476 * > Note: Techs without source handlers should call this between sources for `video` 10477 * & `audio` tracks. You don't want to use them between tracks! 10478 * 10479 * @param {string[]|string} types 10480 * TrackList names to clear, valid names are `video`, `audio`, and 10481 * `text`. 10482 */ 10483 10484 10485 Tech.prototype.clearTracks = function clearTracks(types) { 10486 var _this3 = this; 10487 10488 types = [].concat(types); 10489 // clear out all tracks because we can't reuse them between techs 10490 types.forEach(function (type) { 10491 var list = _this3[type + 'Tracks']() || []; 10492 var i = list.length; 10493 10494 while (i--) { 10495 var track = list[i]; 10496 10497 if (type === 'text') { 10498 _this3.removeRemoteTextTrack(track); 10499 } 10500 list.removeTrack(track); 10501 } 10502 }); 10503 }; 10504 10505 /** 10506 * Remove any TextTracks added via addRemoteTextTrack that are 10507 * flagged for automatic garbage collection 10508 */ 10509 10510 10511 Tech.prototype.cleanupAutoTextTracks = function cleanupAutoTextTracks() { 10512 var list = this.autoRemoteTextTracks_ || []; 10513 var i = list.length; 10514 10515 while (i--) { 10516 var track = list[i]; 10517 10518 this.removeRemoteTextTrack(track); 10519 } 10520 }; 10521 10522 /** 10523 * Reset the tech, which will removes all sources and reset the internal readyState. 10524 * 10525 * @abstract 10526 */ 10527 10528 10529 Tech.prototype.reset = function reset() {}; 10530 10531 /** 10532 * Get or set an error on the Tech. 10533 * 10534 * @param {MediaError} [err] 10535 * Error to set on the Tech 10536 * 10537 * @return {MediaError|null} 10538 * The current error object on the tech, or null if there isn't one. 10539 */ 10540 10541 10542 Tech.prototype.error = function error(err) { 10543 if (err !== undefined) { 10544 this.error_ = new MediaError(err); 10545 this.trigger('error'); 10546 } 10547 return this.error_; 10548 }; 10549 10550 /** 10551 * Returns the `TimeRange`s that have been played through for the current source. 10552 * 10553 * > NOTE: This implementation is incomplete. It does not track the played `TimeRange`. 10554 * It only checks w
10554ether the source has played at all or not. 10555 * 10556 * @return {TimeRange} 10557 * - A single time range if this video has played 10558 * - An empty set of ranges if not. 10559 */ 10560 10561 10562 Tech.prototype.played = function played() { 10563 if (this.hasStarted_) { 10564 return createTimeRanges(0, 0); 10565 } 10566 return createTimeRanges(); 10567 }; 10568 10569 /** 10570 * Causes a manual time update to occur if {@link Tech#manualTimeUpdatesOn} was 10571 * previously called. 10572 * 10573 * @fires Tech#timeupdate 10574 */ 10575 10576 10577 Tech.prototype.setCurrentTime = function setCurrentTime() { 10578 // improve the accuracy of manual timeupdates 10579 if (this.manualTimeUpdates) { 10580 /** 10581 * A manual `timeupdate` event. 10582 * 10583 * @event Tech#timeupdate 10584 * @type {EventTarget~Event} 10585 */ 10586 this.trigger({ type: 'timeupdate', target: this, manuallyTriggered: true }); 10587 } 10588 }; 10589 10590 /** 10591 * Turn on listeners for {@link VideoTrackList}, {@link {AudioTrackList}, and 10592 * {@link TextTrackList} events. 10593 * 10594 * This adds {@link EventTarget~EventListeners} for `addtrack`, and `removetrack`. 10595 * 10596 * @fires Tech#audiotrackchange 10597 * @fires Tech#videotrackchange 10598 * @fires Tech#texttrackchange 10599 */ 10600 10601 10602 Tech.prototype.initTrackListeners = function initTrackListeners() { 10603 var _this4 = this; 10604 10605 /** 10606 * Triggered when tracks are added or removed on the Tech {@link AudioTrackList} 10607 * 10608 * @event Tech#audiotrackchange 10609 * @type {EventTarget~Event} 10610 */ 10611 10612 /** 10613 * Triggered when tracks are added or removed on the Tech {@link VideoTrackList} 10614 * 10615 * @event Tech#videotrackchange 10616 * @type {EventTarget~Event} 10617 */ 10618 10619 /** 10620 * Triggered when tracks are added or removed on the Tech {@link TextTrackList} 10621 * 10622 * @event Tech#texttrackchange 10623 * @type {EventTarget~Event} 10624 */
vendor: 213,864 bytes, lines 10625-18753
10625 NORMAL.names.forEach(function (name) { 10626 var props = NORMAL[name]; 10627 var trackListChanges = function trackListChanges() { 10628 _this4.trigger(name + 'trackchange'); 10629 }; 10630 10631 var tracks = _this4[props.getterName](); 10632 10633 tracks.addEventListener('removetrack', trackListChanges); 10634 tracks.addEventListener('addtrack', trackListChanges); 10635 10636 _this4.on('dispose', function () { 10637 tracks.removeEventListener('removetrack', trackListChanges); 10638 tracks.removeEventListener('addtrack', trackListChanges); 10639 }); 10640 }); 10641 }; 10642 10643 /** 10644 * Emulate TextTracks using vtt.js if necessary 10645 * 10646 * @fires Tech#vttjsloaded 10647 * @fires Tech#vttjserror 10648 */ 10649 10650 10651 Tech.prototype.addWebVttScript_ = function addWebVttScript_() { 10652 var _this5 = this; 10653 10654 if (window_1.WebVTT) { 10655 return; 10656 } 10657 10658 // Initially, Tech.el_ is a child of a dummy-div wait until the Component system 10659 // signals that the Tech is ready at which point Tech.el_ is part of the DOM 10660 // before inserting the WebVTT script 10661 if (document_1.body.contains(this.el())) { 10662 10663 // load via require if available and vtt.js script location was not passed in 10664 // as an option. novtt builds will turn the above require call into an empty object 10665 // which will cause this if check to always fail. 10666 if (!this.options_['vtt.js'] && isPlain(browserIndex) && Object.keys(browserIndex).length > 0) { 10667 this.trigger('vttjsloaded'); 10668 return; 10669 } 10670 10671 // load vtt.js via the script location option or the cdn of no location was 10672 // passed in 10673 var script = document_1.createElement('script'); 10674 10675 script.src = this.options_['vtt.js'] || 'https://vjs.zencdn.net/vttjs/0.12.4/vtt.min.js'; 10676 script.onload = function () { 10677 /** 10678 * Fired when vtt.js is loaded. 10679 * 10680 * @event Tech#vttjsloaded 10681 * @type {EventTarget~Event} 10682 */ 10683 _this5.trigger('vttjsloaded'); 10684 }; 10685 script.onerror = function () { 10686 /** 10687 * Fired when vtt.js was not loaded due to an error 10688 * 10689 * @event Tech#vttjsloaded 10690 * @type {EventTarget~Event} 10691 */ 10692 _this5.trigger('vttjserror'); 10693 }; 10694 this.on('dispose', function () { 10695 script.onload = null; 10696 script.onerror = null; 10697 }); 10698 // but have not loaded yet and we set it to true before the inject so that 10699 // we don't overwrite the injected window.WebVTT if it loads right away 10700 window_1.WebVTT = true; 10701 this.el().parentNode.appendChild(script); 10702 } else { 10703 this.ready(this.addWebVttScript_); 10704 } 10705 }; 10706 10707 /** 10708 * Emulate texttracks 10709 * 10710 */ 10711 10712 10713 Tech.prototype.emulateTextTracks = function emulateTextTracks() { 10714 var _this6 = this; 10715 10716 var tracks = this.textTracks(); 10717 var remoteTracks = this.remoteTextTracks(); 10718 var handleAddTrack = function handleAddTrack(e) { 10719 return tracks.addTrack(e.track); 10720 }; 10721 var handleRemoveTrack = function handleRemoveTrack(e) { 10722 return tracks.removeTrack(e.track); 10723 }; 10724 10725 remoteTracks.on('addtrack', handleAddTrack); 10726 remoteTracks.on('removetrack', handleRemoveTrack); 10727 10728 this.addWebVttScript_(); 10729 10730 var updateDisplay = function updateDisplay() { 10731 return _this6.trigger('texttrackchange'); 10732 }; 10733 10734 var textTracksChanges = function textTracksChanges() { 10735 updateDisplay(); 10736 10737 for (var i = 0; i < tracks.length; i++) { 10738 var track = tracks[i]; 10739 10740 track.removeEventListener('cuechange', updateDisplay); 10741 if (track.mode === 'showing') { 10742 track.addEventListener('cuechange', updateDisplay); 10743 } 10744 } 10745 }; 10746 10747 textTracksChanges(); 10748 tracks.addEventListener('change', textTracksChanges); 10749 tracks.addEventListener('addtrack', textTracksChanges); 10750 tracks.addEventListener('removetrack', textTracksChanges); 10751 10752 this.on('dispose', function () { 10753 remoteTracks.off('addtrack', handleAddTrack); 10754 remoteTracks.off('removetrack', handleRemoveTrack); 10755 tracks.removeEventListener('change', textTracksChanges); 10756 tracks.removeEventListener('addtrack', textTracksChanges); 10757 tracks.removeEventListener('removetrack', textTracksChanges); 10758 10759 for (var i = 0; i < tracks.length; i++) { 10760 var track = tracks[i]; 10761 10762 track.removeEventListener('cuechange', updateDisplay); 10763 } 10764 }); 10765 }; 10766 10767 /** 10768 * Create and returns a remote {@link TextTrack} object. 10769 * 10770 * @param {string} kind 10771 * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata) 10772 * 10773 * @param {string} [label] 10774 * Label to identify the text track 10775 * 10776 * @param {string} [language] 10777 * Two letter language abbreviation 10778 * 10779 * @return {TextTrack} 10780 * The TextTrack that gets created. 10781 */ 10782 10783 10784 Tech.prototype.addTextTrack = function addTextTrack(kind, label, language) { 10785 if (!kind) { 10786 throw new Error('TextTrack kind is required but was not provided'); 10787 } 10788 10789 return createTrackHelper(this, kind, label, language); 10790 }; 10791 10792 /** 10793 * Create an emulated TextTrack for use by addRemoteTextTrack 10794 * 10795 * This is intended to be overridden by classes that inherit from 10796 * Tech in order to create native or custom TextTracks. 10797 * 10798 * @param {Object} options 10799 * The object should contain the options to initialize the TextTrack with. 10800 * 10801 * @param {string} [options.kind] 10802 * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata). 10803 * 10804 * @param {string} [options.label]. 10805 * Label to identify the text track 10806 * 10807 * @param {string} [options.language] 10808 * Two letter language abbreviation. 10809 * 10810 * @return {HTMLTrackElement} 10811 * The track element that gets created. 10812 */ 10813 10814 10815 Tech.prototype.createRemoteTextTrack = function createRemoteTextTrack(options) { 10816 var track = mergeOptions(options, { 10817 tech: this 10818 }); 10819 10820 return new REMOTE.remoteTextEl.TrackClass(track); 10821 }; 10822 10823 /** 10824 * Creates a remote text track object and returns an html track element. 10825 * 10826 * > Note: This can be an emulated {@link HTMLTrackElement} or a native one. 10827 * 10828 * @param {Object} options 10829 * See {@link Tech#createRemoteTextTrack} for more detailed properties. 10830 * 10831 * @param {boolean} [manualCleanup=true] 10832 * - When false: the TextTrack will be automatically removed from the video 10833 * element whenever the source changes 10834 * - When True: The TextTrack will have to be cleaned up manually 10835 * 10836 * @return {HTMLTrackElement} 10837 * An Html Track Element. 10838 * 10839 * @deprecated The default functionality for this function will be equivalent 10840 * to "manualCleanup=false" in the future. The manualCleanup parameter will 10841 * also be removed. 10842 */ 10843 10844 10845 Tech.prototype.addRemoteTextTrack = function addRemoteTextTrack() { 10846 var _this7 = this; 10847 10848 var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; 10849 var manualCleanup = arguments[1]; 10850 10851 var htmlTrackElement = this.createRemoteTextTrack(options); 10852 10853 if (manualCleanup !== true && manualCleanup !== false) { 10854 // deprecation warning 10855 log$1.warn('Calling addRemoteTextTrack without explicitly setting the "manualCleanup" parameter to `true` is deprecated and default to `false` in future version of video.js'); 10856 manualCleanup = true; 10857 } 10858 10859 // store HTMLTrackElement and TextTrack to remote list 10860 this.remoteTextTrackEls().addTrackElement_(htmlTrackElement); 10861 this.remoteTextTracks().addTrack(htmlTrackElement.track); 10862 10863 if (manualCleanup !== true) { 10864 // create the TextTrackList if it doesn't exist 10865 this.ready(function () { 10866 return _this7.autoRemoteTextTracks_.addTrack(htmlTrackElement.track); 10867 }); 10868 } 10869 10870 return htmlTrackElement; 10871 }; 10872 10873 /** 10874 * Remove a remote text track from the remote `TextTrackList`. 10875 * 10876 * @param {TextTrack} track 10877 * `TextTrack` to remove from the `TextTrackList` 10878 */ 10879 10880 10881 Tech.prototype.removeRemoteTextTrack = function removeRemoteTextTrack(track) { 10882 var trackElement = this.remoteTextTrackEls().getTrackElementByTrack_(track); 10883 10884 // remove HTMLTrackElement and TextTrack from remote list 10885 this.remoteTextTrackEls().removeTrackElement_(trackElement); 10886 this.remoteTextTracks().removeTrack(track); 10887 this.autoRemoteTextTracks_.removeTrack(track); 10888 }; 10889 10890 /** 10891 * Gets available media playback quality metrics as specified by the W3C's Media 10892 * Playback Quality API. 10893 * 10894 * @see [Spec]{@link https://wicg.github.io/media-playback-quality} 10895 * 10896 * @return {Object} 10897 * An object with supported media playback quality metrics 10898 * 10899 * @abstract 10900 */ 10901 10902 10903 Tech.prototype.getVideoPlaybackQuality = function getVideoPlaybackQuality() { 10904 return {}; 10905 }; 10906 10907 /** 10908 * A method to set a poster from a `Tech`. 10909 * 10910 * @abstract 10911 */ 10912 10913 10914 Tech.prototype.setPoster = function setPoster() {}; 10915 10916 /** 10917 * A method to check for the presence of the 'playsinine' <video> attribute. 10918 * 10919 * @abstract 10920 */ 10921 10922 10923 Tech.prototype.playsinline = function playsinline() {}; 10924 10925 /** 10926 * A method to set or unset the 'playsinine' <video> attribute. 10927 * 10928 * @abstract 10929 */ 10930 10931 10932 Tech.prototype.setPlaysinline = function setPlaysinline() {}; 10933 10934 /* 10935 * Check if the tech can support the given mime-type. 10936 * 10937 * The base tech does not support any type, but source handlers might 10938 * overwrite this. 10939 * 10940 * @param {string} type 10941 * The mimetype to check for support 10942 * 10943 * @return {string} 10944 * 'probably', 'maybe', or empty string 10945 * 10946 * @see [Spec]{@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/canPlayType} 10947 * 10948 * @abstract 10949 */ 10950 10951 10952 Tech.prototype.canPlayType = function canPlayType() { 10953 return ''; 10954 }; 10955 10956 /** 10957 * Check if the type is supported by this tech. 10958 * 10959 * The base tech does not support any type, but source handlers might 10960 * overwrite this. 10961 * 10962 * @param {string} type 10963 * The media type to check 10964 * @return {string} Returns the native video element's response 10965 */ 10966 10967 10968 Tech.canPlayType = function canPlayType() { 10969 return ''; 10970 }; 10971 10972 /** 10973 * Check if the tech can support the given source 10974 * @param {Object} srcObj 10975 * The source object 10976 * @param {Object} options 10977 * The options passed to the tech 10978 * @return {string} 'probably', 'maybe', or '' (empty string) 10979 */ 10980 10981 10982 Tech.canPlaySource = function canPlaySource(srcObj, options) { 10983 return Tech.canPlayType(srcObj.type); 10984 }; 10985 10986 /* 10987 * Return whether the argument is a Tech or not. 10988 * Can be passed either a Class like `Html5` or a instance like `player.tech_` 10989 * 10990 * @param {Object} component 10991 * The item to check 10992 * 10993 * @return {boolean} 10994 * Whether it is a tech or not 10995 * - True if it is a tech 10996 * - False if it is not 10997 */ 10998 10999 11000 Tech.isTech = function isTech(component) { 11001 return component.prototype instanceof Tech || component instanceof Tech || component === Tech; 11002 }; 11003 11004 /** 11005 * Registers a `Tech` into a shared list for videojs. 11006 * 11007 * @param {string} name 11008 * Name of the `Tech` to register. 11009 * 11010 * @param {Object} tech 11011 * The `Tech` class to register. 11012 */ 11013 11014 11015 Tech.registerTech = function registerTech(name, tech) { 11016 if (!Tech.techs_) { 11017 Tech.techs_ = {}; 11018 } 11019 11020 if (!Tech.isTech(tech)) { 11021 throw new Error('Tech ' + name + ' must be a Tech'); 11022 } 11023 11024 if (!Tech.canPlayType) { 11025 throw new Error('Techs must have a static canPlayType method on them'); 11026 } 11027 if (!Tech.canPlaySource) { 11028 throw new Error('Techs must have a static canPlaySource method on them'); 11029 } 11030 11031 name = toTitleCase(name); 11032 11033 Tech.techs_[name] = tech; 11034 if (name !== 'Tech') { 11035 // camel case the techName for use in techOrder 11036 Tech.defaultTechOrder_.push(name); 11037 } 11038 return tech; 11039 }; 11040 11041 /** 11042 * Get a `Tech` from the shared list by name. 11043 * 11044 * @param {string} name 11045 * `camelCase` or `TitleCase` name of the Tech to get 11046 * 11047 * @return {Tech|undefined} 11048 * The `Tech` or undefined if there was no tech with the name requsted. 11049 */ 11050 11051 11052 Tech.getTech = function getTech(name) { 11053 if (!name) { 11054 return; 11055 } 11056 11057 name = toTitleCase(name); 11058 11059 if (Tech.techs_ && Tech.techs_[name]) { 11060 return Tech.techs_[name]; 11061 } 11062 11063 if (window_1 && window_1.videojs && window_1.videojs[name]) { 11064 log$1.warn('The ' + name + ' tech was added to the videojs object when it should be registered using videojs.registerTech(name, tech)'); 11065 return window_1.videojs[name]; 11066 } 11067 }; 11068 11069 return Tech; 11070}(Component); 11071 11072/** 11073 * Get the {@link VideoTrackList} 11074 * 11075 * @returns {VideoTrackList} 11076 * @method Tech.prototype.videoTracks 11077 */ 11078 11079/** 11080 * Get the {@link AudioTrackList} 11081 * 11082 * @returns {AudioTrackList} 11083 * @method Tech.prototype.audioTracks 11084 */ 11085 11086/** 11087 * Get the {@link TextTrackList} 11088 * 11089 * @returns {TextTrackList} 11090 * @method Tech.prototype.textTracks 11091 */ 11092 11093/** 11094 * Get the remote element {@link TextTrackList} 11095 * 11096 * @returns {TextTrackList} 11097 * @method Tech.prototype.remoteTextTracks 11098 */ 11099 11100/** 11101 * Get the remote element {@link HtmlTrackElementList} 11102 * 11103 * @returns {HtmlTrackElementList} 11104 * @method Tech.prototype.remoteTextTrackEls 11105 */ 11106 11107ALL.names.forEach(function (name) { 11108 var props = ALL[name]; 11109 11110 Tech.prototype[props.getterName] = function () { 11111 this[props.privateName] = this[props.privateName] || new props.ListClass(); 11112 return this[props.privateName]; 11113 }; 11114}); 11115 11116/** 11117 * List of associated text tracks 11118 * 11119 * @type {TextTrackList} 11120 * @private 11121 * @property Tech#textTracks_ 11122 */ 11123 11124/** 11125 * List of associated audio tracks. 11126 * 11127 * @type {AudioTrackList} 11128 * @private 11129 * @property Tech#audioTracks_ 11130 */ 11131 11132/** 11133 * List of associated video tracks. 11134 * 11135 * @type {VideoTrackList} 11136 * @private 11137 * @property Tech#videoTracks_ 11138 */ 11139 11140/** 11141 * Boolean indicating wether the `Tech` supports volume control. 11142 * 11143 * @type {boolean} 11144 * @default 11145 */ 11146Tech.prototype.featuresVolumeControl = true; 11147 11148/** 11149 * Boolean indicating whether the `Tech` supports muting volume. 11150 * 11151 * @type {bolean} 11152 * @default 11153 */ 11154Tech.prototype.featuresMuteControl = true; 11155 11156/** 11157 * Boolean indicating whether the `Tech` supports fullscreen resize control. 11158 * Resizing plugins using request fullscreen reloads the plugin 11159 * 11160 * @type {boolean} 11161 * @default 11162 */ 11163Tech.prototype.featuresFullscreenResize = false; 11164 11165/** 11166 * Boolean indicating wether the `Tech` supports changing the speed at which the video 11167 * plays. Examples: 11168 * - Set player to play 2x (twice) as fast 11169 * - Set player to play 0.5x (half) as fast 11170 * 11171 * @type {boolean} 11172 * @default 11173 */ 11174Tech.prototype.featuresPlaybackRate = false; 11175 11176/** 11177 * Boolean indicating wether the `Tech` supports the `progress` event. This is currently 11178 * not triggered by video-js-swf. This will be used to determine if 11179 * {@link Tech#manualProgressOn} should be called. 11180 * 11181 * @type {boolean} 11182 * @default 11183 */ 11184Tech.prototype.featuresProgressEvents = false; 11185 11186/** 11187 * Boolean indicating wether the `Tech` supports the `sourceset` event. 11188 * 11189 * A tech should set this to `true` and then use {@link Tech#triggerSourceset} 11190 * to trigger a {@link Tech#event:sourceset} at the earliest time after getting 11191 * a new source. 11192 * 11193 * @type {boolean} 11194 * @default 11195 */ 11196Tech.prototype.featuresSourceset = false; 11197 11198/** 11199 * Boolean indicating wether the `Tech` supports the `timeupdate` event. This is currently 11200 * not triggered by video-js-swf. This will be used to determine if 11201 * {@link Tech#manualTimeUpdates} should be called. 11202 * 11203 * @type {boolean} 11204 * @default 11205 */ 11206Tech.prototype.featuresTimeupdateEvents = false; 11207 11208/** 11209 * Boolean indicating wether the `Tech` supports the native `TextTrack`s. 11210 * This will help us integrate with native `TextTrack`s if the browser supports them. 11211 * 11212 * @type {boolean} 11213 * @default 11214 */ 11215Tech.prototype.featuresNativeTextTracks = false; 11216 11217/** 11218 * A functional mixin for techs that want to use the Source Handler pattern. 11219 * Source handlers are scripts for handling specific formats. 11220 * The source handler pattern is used for adaptive formats (HLS, DASH) that 11221 * manually load video data and feed it into a Source Buffer (Media Source Extensions) 11222 * Example: `Tech.withSourceHandlers.call(MyTech);` 11223 * 11224 * @param {Tech} _Tech 11225 * The tech to add source handler functions to. 11226 * 11227 * @mixes Tech~SourceHandlerAdditions 11228 */ 11229Tech.withSourceHandlers = function (_Tech) { 11230 11231 /** 11232 * Register a source handler 11233 * 11234 * @param {Function} handler 11235 * The source handler class 11236 * 11237 * @param {number} [index] 11238 * Register it at the following index 11239 */ 11240 _Tech.registerSourceHandler = function (handler, index) { 11241 var handlers = _Tech.sourceHandlers; 11242 11243 if (!handlers) { 11244 handlers = _Tech.sourceHandlers = []; 11245 } 11246 11247 if (index === undefined) { 11248 // add to the end of the list 11249 index = handlers.length; 11250 } 11251 11252 handlers.splice(index, 0, handler); 11253 }; 11254 11255 /** 11256 * Check if the tech can support the given type. Also checks the 11257 * Techs sourceHandlers. 11258 * 11259 * @param {string} type 11260 * The mimetype to check. 11261 * 11262 * @return {string} 11263 * 'probably', 'maybe', or '' (empty string) 11264 */ 11265 _Tech.canPlayType = function (type) { 11266 var handlers = _Tech.sourceHandlers || []; 11267 var can = void 0; 11268 11269 for (var i = 0; i < handlers.length; i++) { 11270 can = handlers[i].canPlayType(type); 11271 11272 if (can) { 11273 return can; 11274 } 11275 } 11276 11277 return ''; 11278 }; 11279 11280 /** 11281 * Returns the first source handler that supports the source. 11282 * 11283 * TODO: Answer question: should 'probably' be prioritized over 'maybe' 11284 * 11285 * @param {Tech~SourceObject} source 11286 * The source object 11287 * 11288 * @param {Object} options 11289 * The options passed to the tech 11290 * 11291 * @return {SourceHandler|null} 11292 * The first source handler that supports the source or null if 11293 * no SourceHandler supports the source 11294 */ 11295 _Tech.selectSourceHandler = function (source, options) { 11296 var handlers = _Tech.sourceHandlers || []; 11297 var can = void 0; 11298 11299 for (var i = 0; i < handlers.length; i++) { 11300 can = handlers[i].canHandleSource(source, options); 11301 11302 if (can) { 11303 return handlers[i]; 11304 } 11305 } 11306 11307 return null; 11308 }; 11309 11310 /** 11311 * Check if the tech can support the given source. 11312 * 11313 * @param {Tech~SourceObject} srcObj 11314 * The source object 11315 * 11316 * @param {Object} options 11317 * The options passed to the tech 11318 * 11319 * @return {string} 11320 * 'probably', 'maybe', or '' (empty string) 11321 */ 11322 _Tech.canPlaySource = function (srcObj, options) { 11323 var sh = _Tech.selectSourceHandler(srcObj, options); 11324 11325 if (sh) { 11326 return sh.canHandleSource(srcObj, options); 11327 } 11328 11329 return ''; 11330 }; 11331 11332 /** 11333 * When using a source handler, prefer its implementation of 11334 * any function normally provided by the tech. 11335 */ 11336 var deferrable = ['seekable', 'seeking', 'duration']; 11337 11338 /** 11339 * A wrapper around {@link Tech#seekable} that will call a `SourceHandler`s seekable 11340 * function if it exists, with a fallback to the Techs seekable function. 11341 * 11342 * @method _Tech.seekable 11343 */ 11344 11345 /** 11346 * A wrapper around {@link Tech#duration} that will call a `SourceHandler`s duration 11347 * function if it exists, otherwise it will fallback to the techs duration function. 11348 * 11349 * @method _Tech.duration 11350 */ 11351 11352 deferrable.forEach(function (fnName) { 11353 var originalFn = this[fnName]; 11354 11355 if (typeof originalFn !== 'function') { 11356 return; 11357 } 11358 11359 this[fnName] = function () { 11360 if (this.sourceHandler_ && this.sourceHandler_[fnName]) { 11361 return this.sourceHandler_[fnName].apply(this.sourceHandler_, arguments); 11362 } 11363 return originalFn.apply(this, arguments); 11364 }; 11365 }, _Tech.prototype); 11366 11367 /** 11368 * Create a function for setting the source using a source object 11369 * and source handlers. 11370 * Should never be called unless a source handler was found. 11371 * 11372 * @param {Tech~SourceObject} source 11373 * A source object with src and type keys 11374 */ 11375 _Tech.prototype.setSource = function (source) { 11376 var sh = _Tech.selectSourceHandler(source, this.options_); 11377 11378 if (!sh) { 11379 // Fall back to a native source hander when unsupported sources are 11380 // deliberately set 11381 if (_Tech.nativeSourceHandler) { 11382 sh = _Tech.nativeSourceHandler; 11383 } else { 11384 log$1.error('No source hander found for the current source.'); 11385 } 11386 } 11387 11388 // Dispose any existing source handler 11389 this.disposeSourceHandler(); 11390 this.off('dispose', this.disposeSourceHandler); 11391 11392 if (sh !== _Tech.nativeSourceHandler) { 11393 this.currentSource_ = source; 11394 } 11395 11396 this.sourceHandler_ = sh.handleSource(source, this, this.options_); 11397 this.on('dispose', this.disposeSourceHandler); 11398 }; 11399 11400 /** 11401 * Clean up any existing SourceHandlers and listeners when the Tech is disposed. 11402 * 11403 * @listens Tech#dispose 11404 */ 11405 _Tech.prototype.disposeSourceHandler = function () { 11406 // if we have a source and get another one 11407 // then we are loading something new 11408 // than clear all of our current tracks 11409 if (this.currentSource_) { 11410 this.clearTracks(['audio', 'video']); 11411 this.currentSource_ = null; 11412 } 11413 11414 // always clean up auto-text tracks 11415 this.cleanupAutoTextTracks(); 11416 11417 if (this.sourceHandler_) { 11418 11419 if (this.sourceHandler_.dispose) { 11420 this.sourceHandler_.dispose(); 11421 } 11422 11423 this.sourceHandler_ = null; 11424 } 11425 }; 11426}; 11427 11428// The base Tech class needs to be registered as a Component. It is the only 11429// Tech that can be registered as a Component. 11430Component.registerComponent('Tech', Tech); 11431Tech.registerTech('Tech', Tech); 11432 11433/** 11434 * A list of techs that should be added to techOrder on Players 11435 * 11436 * @private 11437 */ 11438Tech.defaultTechOrder_ = []; 11439 11440var middlewares = {}; 11441var middlewareInstances = {}; 11442 11443var TERMINATOR = {}; 11444 11445function use(type, middleware) { 11446 middlewares[type] = middlewares[type] || []; 11447 middlewares[type].push(middleware); 11448} 11449 11450 11451 11452function setSource(player, src, next) { 11453 player.setTimeout(function () { 11454 return setSourceHelper(src, middlewares[src.type], next, player); 11455 }, 1); 11456} 11457 11458function setTech(middleware, tech) { 11459 middleware.forEach(function (mw) { 11460 return mw.setTech && mw.setTech(tech); 11461 }); 11462} 11463 11464/** 11465 * Calls a getter on the tech first, through each middleware 11466 * from right to left to the player. 11467 */ 11468function get$1(middleware, tech, method) { 11469 return middleware.reduceRight(middlewareIterator(method), tech[method]()); 11470} 11471 11472/** 11473 * Takes the argument given to the player and calls the setter method on each 11474 * middlware from left to right to the tech. 11475 */ 11476function set$1(middleware, tech, method, arg) { 11477 return tech[method](middleware.reduce(middlewareIterator(method), arg)); 11478} 11479 11480/** 11481 * Takes the argument given to the player and calls the `call` version of the method 11482 * on each middleware from left to right. 11483 * Then, call the passed in method on the tech and return the result unchanged 11484 * back to the player, through middleware, this time from right to left. 11485 */ 11486function mediate(middleware, tech, method) { 11487 var arg = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null; 11488 11489 var callMethod = 'call' + toTitleCase(method); 11490 var middlewareValue = middleware.reduce(middlewareIterator(callMethod), arg); 11491 var terminated = middlewareValue === TERMINATOR; 11492 var returnValue = terminated ? null : tech[method](middlewareValue); 11493 11494 executeRight(middleware, method, returnValue, terminated); 11495 11496 return returnValue; 11497} 11498 11499var allowedGetters = { 11500 buffered: 1, 11501 currentTime: 1, 11502 duration: 1, 11503 seekable: 1, 11504 played: 1, 11505 paused: 1 11506}; 11507 11508var allowedSetters = { 11509 setCurrentTime: 1 11510}; 11511 11512var allowedMediators = { 11513 play: 1, 11514 pause: 1 11515}; 11516 11517function middlewareIterator(method) { 11518 return function (value, mw) { 11519 // if the previous middleware terminated, pass along the termination 11520 if (value === TERMINATOR) { 11521 return TERMINATOR; 11522 } 11523 11524 if (mw[method]) { 11525 return mw[method](value); 11526 } 11527 11528 return value; 11529 }; 11530} 11531 11532function executeRight(mws, method, value, terminated) { 11533 for (var i = mws.length - 1; i >= 0; i--) { 11534 var mw = mws[i]; 11535 11536 if (mw[method]) { 11537 mw[method](terminated, value); 11538 } 11539 } 11540} 11541 11542function clearCacheForPlayer(player) { 11543 middlewareInstances[player.id()] = null; 11544} 11545 11546/** 11547 * { 11548 * [playerId]: [[mwFactory, mwInstance], ...] 11549 * } 11550 */ 11551function getOrCreateFactory(player, mwFactory) { 11552 var mws = middlewareInstances[player.id()]; 11553 var mw = null; 11554 11555 if (mws === undefined || mws === null) { 11556 mw = mwFactory(player); 11557 middlewareInstances[player.id()] = [[mwFactory, mw]]; 11558 return mw; 11559 } 11560 11561 for (var i = 0; i < mws.length; i++) { 11562 var _mws$i = mws[i], 11563 mwf = _mws$i[0], 11564 mwi = _mws$i[1]; 11565 11566 11567 if (mwf !== mwFactory) { 11568 continue; 11569 } 11570 11571 mw = mwi; 11572 } 11573 11574 if (mw === null) { 11575 mw = mwFactory(player); 11576 mws.push([mwFactory, mw]); 11577 } 11578 11579 return mw; 11580} 11581 11582function setSourceHelper() { 11583 var src = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; 11584 var middleware = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : []; 11585 var next = arguments[2]; 11586 var player = arguments[3]; 11587 var acc = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : []; 11588 var lastRun = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : false; 11589 var mwFactory = middleware[0], 11590 mwrest = middleware.slice(1); 11591 11592 // if mwFactory is a string, then we're at a fork in the road 11593 11594 if (typeof mwFactory === 'string') { 11595 setSourceHelper(src, middlewares[mwFactory], next, player, acc, lastRun); 11596 11597 // if we have an mwFactory, call it with the player to get the mw, 11598 // then call the mw's setSource method 11599 } else if (mwFactory) { 11600 var mw = getOrCreateFactory(player, mwFactory); 11601 11602 // if setSource isn't present, implicitly select this middleware 11603 if (!mw.setSource) { 11604 acc.push(mw); 11605 return setSourceHelper(src, mwrest, next, player, acc, lastRun); 11606 } 11607 11608 mw.setSource(assign({}, src), function (err, _src) { 11609 11610 // something happened, try the next middleware on the current level 11611 // make sure to use the old src 11612 if (err) { 11613 return setSourceHelper(src, mwrest, next, player, acc, lastRun); 11614 } 11615 11616 // we've succeeded, now we need to go deeper 11617 acc.push(mw); 11618 11619 // if it's the same type, continue down the current chain 11620 // otherwise, we want to go down the new chain 11621 setSourceHelper(_src, src.type === _src.type ? mwrest : middlewares[_src.type], next, player, acc, lastRun); 11622 }); 11623 } else if (mwrest.length) { 11624 setSourceHelper(src, mwrest, next, player, acc, lastRun); 11625 } else if (lastRun) { 11626 next(src, acc); 11627 } else { 11628 setSourceHelper(src, middlewares['*'], next, player, acc, true); 11629 } 11630} 11631 11632/** 11633 * Mimetypes 11634 * 11635 * @see http://hul.harvard.edu/ois/////systems/wax/wax-public-help/mimetypes.htm 11636 * @typedef Mimetypes~Kind 11637 * @enum 11638 */ 11639var MimetypesKind = { 11640 opus: 'video/ogg', 11641 ogv: 'video/ogg', 11642 mp4: 'video/mp4', 11643 mov: 'video/mp4', 11644 m4v: 'video/mp4', 11645 mkv: 'video/x-matroska', 11646 mp3: 'audio/mpeg', 11647 aac: 'audio/aac', 11648 oga: 'audio/ogg', 11649 m3u8: 'application/x-mpegURL' 11650}; 11651 11652/** 11653 * Get the mimetype of a given src url if possible 11654 * 11655 * @param {string} src 11656 * The url to the src 11657 * 11658 * @return {string} 11659 * return the mimetype if it was known or empty string otherwise 11660 */ 11661var getMimetype = function getMimetype() { 11662 var src = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ''; 11663 11664 var ext = getFileExtension(src); 11665 var mimetype = MimetypesKind[ext.toLowerCase()]; 11666 11667 return mimetype || ''; 11668}; 11669 11670/** 11671 * Find the mime type of a given source string if possible. Uses the player 11672 * source cache. 11673 * 11674 * @param {Player} player 11675 * The player object 11676 * 11677 * @param {string} src 11678 * The source string 11679 * 11680 * @return {string} 11681 * The type that was found 11682 */ 11683var findMimetype = function findMimetype(player, src) { 11684 if (!src) { 11685 return ''; 11686 } 11687 11688 // 1. check for the type in the `source` cache 11689 if (player.cache_.source.src === src && player.cache_.source.type) { 11690 return player.cache_.source.type; 11691 } 11692 11693 // 2. see if we have this source in our `currentSources` cache 11694 var matchingSources = player.cache_.sources.filter(function (s) { 11695 return s.src === src; 11696 }); 11697 11698 if (matchingSources.length) { 11699 return matchingSources[0].type; 11700 } 11701 11702 // 3. look for the src url in source elements and use the type there 11703 var sources = player.$$('source'); 11704 11705 for (var i = 0; i < sources.length; i++) { 11706 var s = sources[i]; 11707 11708 if (s.type && s.src && s.src === src) { 11709 return s.type; 11710 } 11711 } 11712 11713 // 4. finally fallback to our list of mime types based on src url extension 11714 return getMimetype(src); 11715}; 11716 11717/** 11718 * @module filter-source 11719 */ 11720/** 11721 * Filter out single bad source objects or multiple source objects in an 11722 * array. Also flattens nested source object arrays into a 1 dimensional 11723 * array of source objects. 11724 * 11725 * @param {Tech~SourceObject|Tech~SourceObject[]} src 11726 * The src object to filter 11727 * 11728 * @return {Tech~SourceObject[]} 11729 * An array of sourceobjects containing only valid sources 11730 * 11731 * @private 11732 */ 11733var filterSource = function filterSource(src) { 11734 // traverse array 11735 if (Array.isArray(src)) { 11736 var newsrc = []; 11737 11738 src.forEach(function (srcobj) { 11739 srcobj = filterSource(srcobj); 11740 11741 if (Array.isArray(srcobj)) { 11742 newsrc = newsrc.concat(srcobj); 11743 } else if (isObject(srcobj)) { 11744 newsrc.push(srcobj); 11745 } 11746 }); 11747 11748 src = newsrc; 11749 } else if (typeof src === 'string' && src.trim()) { 11750 // convert string into object 11751 src = [fixSource({ src: src })]; 11752 } else if (isObject(src) && typeof src.src === 'string' && src.src && src.src.trim()) { 11753 // src is already valid 11754 src = [fixSource(src)]; 11755 } else { 11756 // invalid source, turn it into an empty array 11757 src = []; 11758 } 11759 11760 return src; 11761}; 11762 11763/** 11764 * Checks src mimetype, adding it when possible 11765 * 11766 * @param {Tech~SourceObject} src 11767 * The src object to check 11768 * @return {Tech~SourceObject} 11769 * src Object with known type 11770 */ 11771function fixSource(src) { 11772 var mimetype = getMimetype(src.src); 11773 11774 if (!src.type && mimetype) { 11775 src.type = mimetype; 11776 } 11777 11778 return src; 11779} 11780 11781/** 11782 * @file loader.js 11783 */ 11784/** 11785 * The `MediaLoader` is the `Component` that decides which playback technology to load 11786 * when a player is initialized. 11787 * 11788 * @extends Component 11789 */ 11790 11791var MediaLoader = function (_Component) { 11792 inherits(MediaLoader, _Component); 11793 11794 /** 11795 * Create an instance of this class. 11796 * 11797 * @param {Player} player 11798 * The `Player` that this class should attach to. 11799 * 11800 * @param {Object} [options] 11801 * The key/value stroe of player options. 11802 * 11803 * @param {Component~ReadyCallback} [ready] 11804 * The function that is run when this component is ready. 11805 */ 11806 function MediaLoader(player, options, ready) { 11807 classCallCheck(this, MediaLoader); 11808 11809 // MediaLoader has no element 11810 var options_ = mergeOptions({ createEl: false }, options); 11811 11812 // If there are no sources when the player is initialized, 11813 // load the first supported playback technology. 11814 11815 var _this = possibleConstructorReturn(this, _Component.call(this, player, options_, ready)); 11816 11817 if (!options.playerOptions.sources || options.playerOptions.sources.length === 0) { 11818 for (var i = 0, j = options.playerOptions.techOrder; i < j.length; i++) { 11819 var techName = toTitleCase(j[i]); 11820 var tech = Tech.getTech(techName); 11821 11822 // Support old behavior of techs being registered as components. 11823 // Remove once that deprecated behavior is removed. 11824 if (!techName) { 11825 tech = Component.getComponent(techName); 11826 } 11827 11828 // Check if the browser supports this technology 11829 if (tech && tech.isSupported()) { 11830 player.loadTech_(techName); 11831 break; 11832 } 11833 } 11834 } else { 11835 // Loop through playback technologies (HTML5, Flash) and check for support. 11836 // Then load the best source. 11837 // A few assumptions here: 11838 // All playback technologies respect preload false. 11839 player.src(options.playerOptions.sources); 11840 } 11841 return _this; 11842 } 11843 11844 return MediaLoader; 11845}(Component); 11846 11847Component.registerComponent('MediaLoader', MediaLoader); 11848 11849/** 11850 * @file button.js 11851 */ 11852/** 11853 * Clickable Component which is clickable or keyboard actionable, 11854 * but is not a native HTML button. 11855 * 11856 * @extends Component 11857 */ 11858 11859var ClickableComponent = function (_Component) { 11860 inherits(ClickableComponent, _Component); 11861 11862 /** 11863 * Creates an instance of this class. 11864 * 11865 * @param {Player} player 11866 * The `Player` that this class should be attached to. 11867 * 11868 * @param {Object} [options] 11869 * The key/value store of player options. 11870 */ 11871 function ClickableComponent(player, options) { 11872 classCallCheck(this, ClickableComponent); 11873 11874 var _this = possibleConstructorReturn(this, _Component.call(this, player, options)); 11875 11876 _this.emitTapEvents(); 11877 11878 _this.enable(); 11879 return _this; 11880 } 11881 11882 /** 11883 * Create the `Component`s DOM element. 11884 * 11885 * @param {string} [tag=div] 11886 * The element's node type. 11887 * 11888 * @param {Object} [props={}] 11889 * An object of properties that should be set on the element. 11890 * 11891 * @param {Object} [attributes={}] 11892 * An object of attributes that should be set on the element. 11893 * 11894 * @return {Element} 11895 * The element that gets created. 11896 */ 11897 11898 11899 ClickableComponent.prototype.createEl = function createEl$$1() { 11900 var tag = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'div'; 11901 var props = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 11902 var attributes = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; 11903 11904 props = assign({ 11905 innerHTML: '<span aria-hidden="true" class="vjs-icon-placeholder"></span>', 11906 className: this.buildCSSClass(), 11907 tabIndex: 0 11908 }, props); 11909 11910 if (tag === 'button') { 11911 log$1.error('Creating a ClickableComponent with an HTML element of ' + tag + ' is not supported; use a Button instead.'); 11912 } 11913 11914 // Add ARIA attributes for clickable element which is not a native HTML button 11915 attributes = assign({ 11916 role: 'button' 11917 }, attributes); 11918 11919 this.tabIndex_ = props.tabIndex; 11920 11921 var el = _Component.prototype.createEl.call(this, tag, props, attributes); 11922 11923 this.createControlTextEl(el); 11924 11925 return el; 11926 }; 11927 11928 ClickableComponent.prototype.dispose = function dispose() { 11929 // remove controlTextEl_ on dipose 11930 this.controlTextEl_ = null; 11931 11932 _Component.prototype.dispose.call(this); 11933 }; 11934 11935 /** 11936 * Create a control text element on this `Component` 11937 * 11938 * @param {Element} [el] 11939 * Parent element for the control text. 11940 * 11941 * @return {Element} 11942 * The control text element that gets created. 11943 */ 11944 11945 11946 ClickableComponent.prototype.createControlTextEl = function createControlTextEl(el) { 11947 this.controlTextEl_ = createEl('span', { 11948 className: 'vjs-control-text' 11949 }, { 11950 // let the screen reader user know that the text of the element may change 11951 'aria-live': 'polite' 11952 }); 11953 11954 if (el) { 11955 el.appendChild(this.controlTextEl_); 11956 } 11957 11958 this.controlText(this.controlText_, el); 11959 11960 return this.controlTextEl_; 11961 }; 11962 11963 /** 11964 * Get or set the localize text to use for the controls on the `Component`. 11965 * 11966 * @param {string} [text] 11967 * Control text for element. 11968 * 11969 * @param {Element} [el=this.el()] 11970 * Element to set the title on. 11971 * 11972 * @return {string} 11973 * - The control text when getting 11974 */ 11975 11976 11977 ClickableComponent.prototype.controlText = function controlText(text) { 11978 var el = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.el(); 11979 11980 if (text === undefined) { 11981 return this.controlText_ || 'Need Text'; 11982 } 11983 11984 var localizedText = this.localize(text); 11985 11986 this.controlText_ = text; 11987 textContent(this.controlTextEl_, localizedText); 11988 if (!this.nonIconControl) { 11989 // Set title attribute if only an icon is shown 11990 el.setAttribute('title', localizedText); 11991 } 11992 }; 11993 11994 /** 11995 * Builds the default DOM `className`. 11996 * 11997 * @return {string} 11998 * The DOM `className` for this object. 11999 */ 12000 12001 12002 ClickableComponent.prototype.buildCSSClass = function buildCSSClass() { 12003 return 'vjs-control vjs-button ' + _Component.prototype.buildCSSClass.call(this); 12004 }; 12005 12006 /** 12007 * Enable this `Component`s element. 12008 */ 12009 12010 12011 ClickableComponent.prototype.enable = function enable() { 12012 if (!this.enabled_) { 12013 this.enabled_ = true; 12014 this.removeClass('vjs-disabled'); 12015 this.el_.setAttribute('aria-disabled', 'false'); 12016 if (typeof this.tabIndex_ !== 'undefined') { 12017 this.el_.setAttribute('tabIndex', this.tabIndex_); 12018 } 12019 this.on(['tap', 'click'], this.handleClick); 12020 this.on('focus', this.handleFocus); 12021 this.on('blur', this.handleBlur); 12022 } 12023 }; 12024 12025 /** 12026 * Disable this `Component`s element. 12027 */ 12028 12029 12030 ClickableComponent.prototype.disable = function disable() { 12031 this.enabled_ = false; 12032 this.addClass('vjs-disabled'); 12033 this.el_.setAttribute('aria-disabled', 'true'); 12034 if (typeof this.tabIndex_ !== 'undefined') { 12035 this.el_.removeAttribute('tabIndex'); 12036 } 12037 this.off(['tap', 'click'], this.handleClick); 12038 this.off('focus', this.handleFocus); 12039 this.off('blur', this.handleBlur); 12040 }; 12041 12042 /** 12043 * This gets called when a `ClickableComponent` gets: 12044 * - Clicked (via the `click` event, listening starts in the constructor) 12045 * - Tapped (via the `tap` event, listening starts in the constructor) 12046 * - The following things happen in order: 12047 * 1. {@link ClickableComponent#handleFocus} is called via a `focus` event on the 12048 * `ClickableComponent`. 12049 * 2. {@link ClickableComponent#handleFocus} adds a listener for `keydown` on using 12050 * {@link ClickableComponent#handleKeyPress}. 12051 * 3. `ClickableComponent` has not had a `blur` event (`blur` means that focus was lost). The user presses 12052 * the space or enter key. 12053 * 4. {@link ClickableComponent#handleKeyPress} calls this function with the `keydown` 12054 * event as a parameter. 12055 * 12056 * @param {EventTarget~Event} event 12057 * The `keydown`, `tap`, or `click` event that caused this function to be 12058 * called. 12059 * 12060 * @listens tap 12061 * @listens click 12062 * @abstract 12063 */ 12064 12065 12066 ClickableComponent.prototype.handleClick = function handleClick(event) {}; 12067 12068 /** 12069 * This gets called when a `ClickableComponent` gains focus via a `focus` event. 12070 * Turns on listening for `keydown` events. When they happen it 12071 * calls `this.handleKeyPress`. 12072 * 12073 * @param {EventTarget~Event} event 12074 * The `focus` event that caused this function to be called. 12075 * 12076 * @listens focus 12077 */ 12078 12079 12080 ClickableComponent.prototype.handleFocus = function handleFocus(event) { 12081 on(document_1, 'keydown', bind(this, this.handleKeyPress)); 12082 }; 12083 12084 /** 12085 * Called when this ClickableComponent has focus and a key gets pressed down. By 12086 * default it will call `this.handleClick` when the key is space or enter. 12087 * 12088 * @param {EventTarget~Event} event 12089 * The `keydown` event that caused this function to be called. 12090 * 12091 * @listens keydown 12092 */ 12093 12094 12095 ClickableComponent.prototype.handleKeyPress = function handleKeyPress(event) { 12096 12097 // Support Space (32) or Enter (13) key operation to fire a click event 12098 if (event.which === 32 || event.which === 13) { 12099 event.preventDefault(); 12100 this.trigger('click'); 12101 } else if (_Component.prototype.handleKeyPress) { 12102 12103 // Pass keypress handling up for unsupported keys 12104 _Component.prototype.handleKeyPress.call(this, event); 12105 } 12106 }; 12107 12108 /** 12109 * Called when a `ClickableComponent` loses focus. Turns off the listener for 12110 * `keydown` events. Which Stops `this.handleKeyPress` from getting called. 12111 * 12112 * @param {EventTarget~Event} event 12113 * The `blur` event that caused this function to be called. 12114 * 12115 * @listens blur 12116 */ 12117 12118 12119 ClickableComponent.prototype.handleBlur = function handleBlur(event) { 12120 off(document_1, 'keydown', bind(this, this.handleKeyPress)); 12121 }; 12122 12123 return ClickableComponent; 12124}(Component); 12125 12126Component.registerComponent('ClickableComponent', ClickableComponent); 12127 12128/** 12129 * @file poster-image.js 12130 */ 12131/** 12132 * A `ClickableComponent` that handles showing the poster image for the player. 12133 * 12134 * @extends ClickableComponent 12135 */ 12136 12137var PosterImage = function (_ClickableComponent) { 12138 inherits(PosterImage, _ClickableComponent); 12139 12140 /** 12141 * Create an instance of this class. 12142 * 12143 * @param {Player} player 12144 * The `Player` that this class should attach to. 12145 * 12146 * @param {Object} [options] 12147 * The key/value store of player options. 12148 */ 12149 function PosterImage(player, options) { 12150 classCallCheck(this, PosterImage); 12151 12152 var _this = possibleConstructorReturn(this, _ClickableComponent.call(this, player, options)); 12153 12154 _this.update(); 12155 player.on('posterchange', bind(_this, _this.update)); 12156 return _this; 12157 } 12158 12159 /** 12160 * Clean up and dispose of the `PosterImage`. 12161 */ 12162 12163 12164 PosterImage.prototype.dispose = function dispose() { 12165 this.player().off('posterchange', this.update); 12166 _ClickableComponent.prototype.dispose.call(this); 12167 }; 12168 12169 /** 12170 * Create the `PosterImage`s DOM element. 12171 * 12172 * @return {Element} 12173 * The element that gets created. 12174 */ 12175 12176 12177 PosterImage.prototype.createEl = function createEl$$1() { 12178 var el = createEl('div', { 12179 className: 'vjs-poster', 12180 12181 // Don't want poster to be tabbable. 12182 tabIndex: -1 12183 }); 12184 12185 // To ensure the poster image resizes while maintaining its original aspect 12186 // ratio, use a div with `background-size` when available. For browsers that 12187 // do not support `background-size` (e.g. IE8), fall back on using a regular 12188 // img element. 12189 if (!BACKGROUND_SIZE_SUPPORTED) { 12190 this.fallbackImg_ = createEl('img'); 12191 el.appendChild(this.fallbackImg_); 12192 } 12193 12194 return el; 12195 }; 12196 12197 /** 12198 * An {@link EventTarget~EventListener} for {@link Player#posterchange} events. 12199 * 12200 * @listens Player#posterchange 12201 * 12202 * @param {EventTarget~Event} [event] 12203 * The `Player#posterchange` event that triggered this function. 12204 */ 12205 12206 12207 PosterImage.prototype.update = function update(event) { 12208 var url = this.player().poster(); 12209 12210 this.setSrc(url); 12211 12212 // If there's no poster source we should display:none on this component 12213 // so it's not still clickable or right-clickable 12214 if (url) { 12215 this.show(); 12216 } else { 12217 this.hide(); 12218 } 12219 }; 12220 12221 /** 12222 * Set the source of the `PosterImage` depending on the display method. 12223 * 12224 * @param {string} url 12225 * The URL to the source for the `PosterImage`. 12226 */ 12227 12228 12229 PosterImage.prototype.setSrc = function setSrc(url) { 12230 if (this.fallbackImg_) { 12231 this.fallbackImg_.src = url; 12232 } else { 12233 var backgroundImage = ''; 12234 12235 // Any falsey values should stay as an empty string, otherwise 12236 // this will throw an extra error 12237 if (url) { 12238 backgroundImage = 'url("' + url + '")'; 12239 } 12240 12241 this.el_.style.backgroundImage = backgroundImage; 12242 } 12243 }; 12244 12245 /** 12246 * An {@link EventTarget~EventListener} for clicks on the `PosterImage`. See 12247 * {@link ClickableComponent#handleClick} for instances where this will be triggered. 12248 * 12249 * @listens tap 12250 * @listens click 12251 * @listens keydown 12252 * 12253 * @param {EventTarget~Event} event 12254 + The `click`, `tap` or `keydown` event that caused this function to be called. 12255 */ 12256 12257 12258 PosterImage.prototype.handleClick = function handleClick(event) { 12259 // We don't want a click to trigger playback when controls are disabled 12260 if (!this.player_.controls()) { 12261 return; 12262 } 12263 12264 if (this.player_.paused()) { 12265 silencePromise(this.player_.play()); 12266 } else { 12267 this.player_.pause(); 12268 } 12269 }; 12270 12271 return PosterImage; 12272}(ClickableComponent); 12273 12274Component.registerComponent('PosterImage', PosterImage); 12275 12276/** 12277 * @file text-track-display.js 12278 */ 12279var darkGray = '#222'; 12280var lightGray = '#ccc'; 12281var fontMap = { 12282 monospace: 'monospace', 12283 sansSerif: 'sans-serif', 12284 serif: 'serif', 12285 monospaceSansSerif: '"Andale Mono", "Lucida Console", monospace', 12286 monospaceSerif: '"Courier New", monospace', 12287 proportionalSansSerif: 'sans-serif', 12288 proportionalSerif: 'serif', 12289 casual: '"Comic Sans MS", Impact, fantasy', 12290 script: '"Monotype Corsiva", cursive', 12291 smallcaps: '"Andale Mono", "Lucida Console", monospace, sans-serif' 12292}; 12293 12294/** 12295 * Construct an rgba color from a given hex color code. 12296 * 12297 * @param {number} color 12298 * Hex number for color, like #f0e or #f604e2. 12299 * 12300 * @param {number} opacity 12301 * Value for opacity, 0.0 - 1.0. 12302 * 12303 * @return {string} 12304 * The rgba color that was created, like 'rgba(255, 0, 0, 0.3)'. 12305 */ 12306function constructColor(color, opacity) { 12307 var hex = void 0; 12308 12309 if (color.length === 4) { 12310 // color looks like "#f0e" 12311 hex = color[1] + color[1] + color[2] + color[2] + color[3] + color[3]; 12312 } else if (color.length === 7) { 12313 // color looks like "#f604e2" 12314 hex = color.slice(1); 12315 } else { 12316 throw new Error('Invalid color code provided, ' + color + '; must be formatted as e.g. #f0e or #f604e2.'); 12317 } 12318 return 'rgba(' + parseInt(hex.slice(0, 2), 16) + ',' + parseInt(hex.slice(2, 4), 16) + ',' + parseInt(hex.slice(4, 6), 16) + ',' + opacity + ')'; 12319} 12320 12321/** 12322 * Try to update the style of a DOM element. Some style changes will throw an error, 12323 * particularly in IE8. Those should be noops. 12324 * 12325 * @param {Element} el 12326 * The DOM element to be styled. 12327 * 12328 * @param {string} style 12329 * The CSS property on the element that should be styled. 12330 * 12331 * @param {string} rule 12332 * The style rule that should be applied to the property. 12333 * 12334 * @private 12335 */ 12336function tryUpdateStyle(el, style, rule) { 12337 try { 12338 el.style[style] = rule; 12339 } catch (e) { 12340 12341 // Satisfies linter. 12342 return; 12343 } 12344} 12345 12346/** 12347 * The component for displaying text track cues. 12348 * 12349 * @extends Component 12350 */ 12351 12352var TextTrackDisplay = function (_Component) { 12353 inherits(TextTrackDisplay, _Component); 12354 12355 /** 12356 * Creates an instance of this class. 12357 * 12358 * @param {Player} player 12359 * The `Player` that this class should be attached to. 12360 * 12361 * @param {Object} [options] 12362 * The key/value store of player options. 12363 * 12364 * @param {Component~ReadyCallback} [ready] 12365 * The function to call when `TextTrackDisplay` is ready. 12366 */ 12367 function TextTrackDisplay(player, options, ready) { 12368 classCallCheck(this, TextTrackDisplay); 12369 12370 var _this = possibleConstructorReturn(this, _Component.call(this, player, options, ready)); 12371 12372 player.on('loadstart', bind(_this, _this.toggleDisplay)); 12373 player.on('texttrackchange', bind(_this, _this.updateDisplay)); 12374 player.on('loadstart', bind(_this, _this.preselectTrack)); 12375 12376 // This used to be called during player init, but was causing an error 12377 // if a track should show by default and the display hadn't loaded yet. 12378 // Should probably be moved to an external track loader when we support 12379 // tracks that don't need a display. 12380 player.ready(bind(_this, function () { 12381 if (player.tech_ && player.tech_.featuresNativeTextTracks) { 12382 this.hide(); 12383 return; 12384 } 12385 12386 player.on('fullscreenchange', bind(this, this.updateDisplay)); 12387 12388 var tracks = this.options_.playerOptions.tracks || []; 12389 12390 for (var i = 0; i < tracks.length; i++) { 12391 this.player_.addRemoteTextTrack(tracks[i], true); 12392 } 12393 12394 this.preselectTrack(); 12395 })); 12396 return _this; 12397 } 12398 12399 /** 12400 * Preselect a track following this precedence: 12401 * - matches the previously selected {@link TextTrack}'s language and kind 12402 * - matches the previously selected {@link TextTrack}'s language only 12403 * - is the first default captions track 12404 * - is the first default descriptions track 12405 * 12406 * @listens Player#loadstart 12407 */ 12408 12409 12410 TextTrackDisplay.prototype.preselectTrack = function preselectTrack() { 12411 var modes = { captions: 1, subtitles: 1 }; 12412 var trackList = this.player_.textTracks(); 12413 var userPref = this.player_.cache_.selectedLanguage; 12414 var firstDesc = void 0; 12415 var firstCaptions = void 0; 12416 var preferredTrack = void 0; 12417 12418 for (var i = 0; i < trackList.length; i++) { 12419 var track = trackList[i]; 12420 12421 if (userPref && userPref.enabled && userPref.language === track.language) { 12422 // Always choose the track that matches both language and kind 12423 if (track.kind === userPref.kind) { 12424 preferredTrack = track; 12425 // or choose the first track that matches language 12426 } else if (!preferredTrack) { 12427 preferredTrack = track; 12428 } 12429 12430 // clear everything if offTextTrackMenuItem was clicked 12431 } else if (userPref && !userPref.enabled) { 12432 preferredTrack = null; 12433 firstDesc = null; 12434 firstCaptions = null; 12435 } else if (track['default']) { 12436 if (track.kind === 'descriptions' && !firstDesc) { 12437 firstDesc = track; 12438 } else if (track.kind in modes && !firstCaptions) { 12439 firstCaptions = track; 12440 } 12441 } 12442 } 12443 12444 // The preferredTrack matches the user preference and takes 12445 // precendence over all the other tracks. 12446 // So, display the preferredTrack before the first default track 12447 // and the subtitles/captions track before the descriptions track 12448 if (preferredTrack) { 12449 preferredTrack.mode = 'showing'; 12450 } else if (firstCaptions) { 12451 firstCaptions.mode = 'showing'; 12452 } else if (firstDesc) { 12453 firstDesc.mode = 'showing'; 12454 } 12455 }; 12456 12457 /** 12458 * Turn display of {@link TextTrack}'s from the current state into the other state. 12459 * There are only two states: 12460 * - 'shown' 12461 * - 'hidden' 12462 * 12463 * @listens Player#loadstart 12464 */ 12465 12466 12467 TextTrackDisplay.prototype.toggleDisplay = function toggleDisplay() { 12468 if (this.player_.tech_ && this.player_.tech_.featuresNativeTextTracks) { 12469 this.hide(); 12470 } else { 12471 this.show(); 12472 } 12473 }; 12474 12475 /** 12476 * Create the {@link Component}'s DOM element. 12477 * 12478 * @return {Element} 12479 * The element that was created. 12480 */ 12481 12482 12483 TextTrackDisplay.prototype.createEl = function createEl() { 12484 return _Component.prototype.createEl.call(this, 'div', { 12485 className: 'vjs-text-track-display' 12486 }, { 12487 'aria-live': 'off', 12488 'aria-atomic': 'true' 12489 }); 12490 }; 12491 12492 /** 12493 * Clear all displayed {@link TextTrack}s. 12494 */ 12495 12496 12497 TextTrackDisplay.prototype.clearDisplay = function clearDisplay() { 12498 if (typeof window_1.WebVTT === 'function') { 12499 window_1.WebVTT.processCues(window_1, [], this.el_); 12500 } 12501 }; 12502 12503 /** 12504 * Update the displayed TextTrack when a either a {@link Player#texttrackchange} or 12505 * a {@link Player#fullscreenchange} is fired. 12506 * 12507 * @listens Player#texttrackchange 12508 * @listens Player#fullscreenchange 12509 */ 12510 12511 12512 TextTrackDisplay.prototype.updateDisplay = function updateDisplay() { 12513 var tracks = this.player_.textTracks(); 12514 12515 this.clearDisplay(); 12516 12517 // Track display prioritization model: if multiple tracks are 'showing', 12518 // display the first 'subtitles' or 'captions' track which is 'showing', 12519 // otherwise display the first 'descriptions' track which is 'showing' 12520 12521 var descriptionsTrack = null; 12522 var captionsSubtitlesTrack = null; 12523 var i = tracks.length; 12524 12525 while (i--) { 12526 var track = tracks[i]; 12527 12528 if (track.mode === 'showing') { 12529 if (track.kind === 'descriptions') { 12530 descriptionsTrack = track; 12531 } else { 12532 captionsSubtitlesTrack = track; 12533 } 12534 } 12535 } 12536 12537 if (captionsSubtitlesTrack) { 12538 if (this.getAttribute('aria-live') !== 'off') { 12539 this.setAttribute('aria-live', 'off'); 12540 } 12541 this.updateForTrack(captionsSubtitlesTrack); 12542 } else if (descriptionsTrack) { 12543 if (this.getAttribute('aria-live') !== 'assertive') { 12544 this.setAttribute('aria-live', 'assertive'); 12545 } 12546 this.updateForTrack(descriptionsTrack); 12547 } 12548 }; 12549 12550 /** 12551 * Add an {@link Texttrack} to to the {@link Tech}s {@link TextTrackList}. 12552 * 12553 * @param {TextTrack} track 12554 * Text track object to be added to the list. 12555 */ 12556 12557 12558 TextTrackDisplay.prototype.updateForTrack = function updateForTrack(track) { 12559 if (typeof window_1.WebVTT !== 'function' || !track.activeCues) { 12560 return; 12561 } 12562 12563 var cues = []; 12564 12565 for (var _i = 0; _i < track.activeCues.length; _i++) { 12566 cues.push(track.activeCues[_i]); 12567 } 12568 12569 window_1.WebVTT.processCues(window_1, cues, this.el_); 12570 12571 if (!this.player_.textTrackSettings) { 12572 return; 12573 } 12574 12575 var overrides = this.player_.textTrackSettings.getValues(); 12576 12577 var i = cues.length; 12578 12579 while (i--) { 12580 var cue = cues[i]; 12581 12582 if (!cue) { 12583 continue; 12584 } 12585 12586 var cueDiv = cue.displayState; 12587 12588 if (overrides.color) { 12589 cueDiv.firstChild.style.color = overrides.color; 12590 } 12591 if (overrides.textOpacity) { 12592 tryUpdateStyle(cueDiv.firstChild, 'color', constructColor(overrides.color || '#fff', overrides.textOpacity)); 12593 } 12594 if (overrides.backgroundColor) { 12595 cueDiv.firstChild.style.backgroundColor = overrides.backgroundColor; 12596 } 12597 if (overrides.backgroundOpacity) { 12598 tryUpdateStyle(cueDiv.firstChild, 'backgroundColor', constructColor(overrides.backgroundColor || '#000', overrides.backgroundOpacity)); 12599 } 12600 if (overrides.windowColor) { 12601 if (overrides.windowOpacity) { 12602 tryUpdateStyle(cueDiv, 'backgroundColor', constructColor(overrides.windowColor, overrides.windowOpacity)); 12603 } else { 12604 cueDiv.style.backgroundColor = overrides.windowColor; 12605 } 12606 } 12607 if (overrides.edgeStyle) { 12608 if (overrides.edgeStyle === 'dropshadow') { 12609 cueDiv.firstChild.style.textShadow = '2px 2px 3px ' + darkGray + ', 2px 2px 4px ' + darkGray + ', 2px 2px 5px ' + darkGray; 12610 } else if (overrides.edgeStyle === 'raised') { 12611 cueDiv.firstChild.style.textShadow = '1px 1px ' + darkGray + ', 2px 2px ' + darkGray + ', 3px 3px ' + darkGray; 12612 } else if (overrides.edgeStyle === 'depressed') { 12613 cueDiv.firstChild.style.textShadow = '1px 1px ' + lightGray + ', 0 1px ' + lightGray + ', -1px -1px ' + darkGray + ', 0 -1px ' + darkGray; 12614 } else if (overrides.edgeStyle === 'uniform') { 12615 cueDiv.firstChild.style.textShadow = '0 0 4px ' + darkGray + ', 0 0 4px ' + darkGray + ', 0 0 4px ' + darkGray + ', 0 0 4px ' + darkGray; 12616 } 12617 } 12618 if (overrides.fontPercent && overrides.fontPercent !== 1) { 12619 var fontSize = window_1.parseFloat(cueDiv.style.fontSize); 12620 12621 cueDiv.style.fontSize = fontSize * overrides.fontPercent + 'px'; 12622 cueDiv.style.height = 'auto'; 12623 cueDiv.style.top = 'auto'; 12624 cueDiv.style.bottom = '2px'; 12625 } 12626 if (overrides.fontFamily && overrides.fontFamily !== 'default') { 12627 if (overrides.fontFamily === 'small-caps') { 12628 cueDiv.firstChild.style.fontVariant = 'small-caps'; 12629 } else { 12630 cueDiv.firstChild.style.fontFamily = fontMap[overrides.fontFamily]; 12631 } 12632 } 12633 } 12634 }; 12635 12636 return TextTrackDisplay; 12637}(Component); 12638 12639Component.registerComponent('TextTrackDisplay', TextTrackDisplay); 12640 12641/** 12642 * @file loading-spinner.js 12643 */ 12644/** 12645 * A loading spinner for use during waiting/loading events. 12646 * 12647 * @extends Component 12648 */ 12649 12650var LoadingSpinner = function (_Component) { 12651 inherits(LoadingSpinner, _Component); 12652 12653 function LoadingSpinner() { 12654 classCallCheck(this, LoadingSpinner); 12655 return possibleConstructorReturn(this, _Component.apply(this, arguments)); 12656 } 12657 12658 /** 12659 * Create the `LoadingSpinner`s DOM element. 12660 * 12661 * @return {Element} 12662 * The dom element that gets created. 12663 */ 12664 LoadingSpinner.prototype.createEl = function createEl$$1() { 12665 var isAudio = this.player_.isAudio(); 12666 var playerType = this.localize(isAudio ? 'Audio Player' : 'Video Player'); 12667 var controlText = createEl('span', { 12668 className: 'vjs-control-text', 12669 innerHTML: this.localize('{1} is loading.', [playerType]) 12670 }); 12671 12672 var el = _Component.prototype.createEl.call(this, 'div', { 12673 className: 'vjs-loading-spinner', 12674 dir: 'ltr' 12675 }); 12676 12677 el.appendChild(controlText); 12678 12679 return el; 12680 }; 12681 12682 return LoadingSpinner; 12683}(Component); 12684 12685Component.registerComponent('LoadingSpinner', LoadingSpinner); 12686 12687/** 12688 * @file button.js 12689 */ 12690/** 12691 * Base class for all buttons. 12692 * 12693 * @extends ClickableComponent 12694 */ 12695 12696var Button = function (_ClickableComponent) { 12697 inherits(Button, _ClickableComponent); 12698 12699 function Button() { 12700 classCallCheck(this, Button); 12701 return possibleConstructorReturn(this, _ClickableComponent.apply(this, arguments)); 12702 } 12703 12704 /** 12705 * Create the `Button`s DOM element. 12706 * 12707 * @param {string} [tag="button"] 12708 * The element's node type. This argument is IGNORED: no matter what 12709 * is passed, it will always create a `button` element. 12710 * 12711 * @param {Object} [props={}] 12712 * An object of properties that should be set on the element. 12713 * 12714 * @param {Object} [attributes={}] 12715 * An object of attributes that should be set on the element. 12716 * 12717 * @return {Element} 12718 * The element that gets created. 12719 */ 12720 Button.prototype.createEl = function createEl(tag) { 12721 var props = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 12722 var attributes = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; 12723 12724 tag = 'button'; 12725 12726 props = assign({ 12727 innerHTML: '<span aria-hidden="true" class="vjs-icon-placeholder"></span>', 12728 className: this.buildCSSClass() 12729 }, props); 12730 12731 // Add attributes for button element 12732 attributes = assign({ 12733 12734 // Necessary since the default button type is "submit" 12735 type: 'button' 12736 }, attributes); 12737 12738 var el = Component.prototype.createEl.call(this, tag, props, attributes); 12739 12740 this.createControlTextEl(el); 12741 12742 return el; 12743 }; 12744 12745 /** 12746 * Add a child `Component` inside of this `Button`. 12747 * 12748 * @param {string|Component} child 12749 * The name or instance of a child to add. 12750 * 12751 * @param {Object} [options={}] 12752 * The key/value store of options that will get passed to children of 12753 * the child. 12754 * 12755 * @return {Component} 12756 * The `Component` that gets added as a child. When using a string the 12757 * `Component` will get created by this process. 12758 * 12759 * @deprecated since version 5 12760 */ 12761 12762 12763 Button.prototype.addChild = function addChild(child) { 12764 var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 12765 12766 var className = this.constructor.name; 12767 12768 log$1.warn('Adding an actionable (user controllable) child to a Button (' + className + ') is not supported; use a ClickableComponent instead.'); 12769 12770 // Avoid the error message generated by ClickableComponent's addChild method 12771 return Component.prototype.addChild.call(this, child, options); 12772 }; 12773 12774 /** 12775 * Enable the `Button` element so that it can be activated or clicked. Use this with 12776 * {@link Button#disable}. 12777 */ 12778 12779 12780 Button.prototype.enable = function enable() { 12781 _ClickableComponent.prototype.enable.call(this); 12782 this.el_.removeAttribute('disabled'); 12783 }; 12784 12785 /** 12786 * Disable the `Button` element so that it cannot be activated or clicked. Use this with 12787 * {@link Button#enable}. 12788 */ 12789 12790 12791 Button.prototype.disable = function disable() { 12792 _ClickableComponent.prototype.disable.call(this); 12793 this.el_.setAttribute('disabled', 'disabled'); 12794 }; 12795 12796 /** 12797 * This gets called when a `Button` has focus and `keydown` is triggered via a key 12798 * press. 12799 * 12800 * @param {EventTarget~Event} event 12801 * The event that caused this function to get called. 12802 * 12803 * @listens keydown 12804 */ 12805 12806 12807 Button.prototype.handleKeyPress = function handleKeyPress(event) { 12808 12809 // Ignore Space (32) or Enter (13) key operation, which is handled by the browser for a button. 12810 if (event.which === 32 || event.which === 13) { 12811 return; 12812 } 12813 12814 // Pass keypress handling up for unsupported keys 12815 _ClickableComponent.prototype.handleKeyPress.call(this, event); 12816 }; 12817 12818 return Button; 12819}(ClickableComponent); 12820 12821Component.registerComponent('Button', Button); 12822 12823/** 12824 * @file big-play-button.js 12825 */ 12826/** 12827 * The initial play button that shows before the video has played. The hiding of the 12828 * `BigPlayButton` get done via CSS and `Player` states. 12829 * 12830 * @extends Button 12831 */ 12832 12833var BigPlayButton = function (_Button) { 12834 inherits(BigPlayButton, _Button); 12835 12836 function BigPlayButton(player, options) { 12837 classCallCheck(this, BigPlayButton); 12838 12839 var _this = possibleConstructorReturn(this, _Button.call(this, player, options)); 12840 12841 _this.mouseused_ = false; 12842 12843 _this.on('mousedown', _this.handleMouseDown); 12844 return _this; 12845 } 12846 12847 /** 12848 * Builds the default DOM `className`. 12849 * 12850 * @return {string} 12851 * The DOM `className` for this object. Always returns 'vjs-big-play-button'. 12852 */ 12853 12854 12855 BigPlayButton.prototype.buildCSSClass = function buildCSSClass() { 12856 return 'vjs-big-play-button'; 12857 }; 12858 12859 /** 12860 * This gets called when a `BigPlayButton` "clicked". See {@link ClickableComponent} 12861 * for more detailed information on what a click can be. 12862 * 12863 * @param {EventTarget~Event} event 12864 * The `keydown`, `tap`, or `click` event that caused this function to be 12865 * called. 12866 * 12867 * @listens tap 12868 * @listens click 12869 */ 12870 12871 12872 BigPlayButton.prototype.handleClick = function handleClick(event) { 12873 var playPromise = this.player_.play(); 12874 12875 // exit early if clicked via the mouse 12876 if (this.mouseused_ && event.clientX && event.clientY) { 12877 silencePromise(playPromise); 12878 return; 12879 } 12880 12881 var cb = this.player_.getChild('controlBar'); 12882 var playToggle = cb && cb.getChild('playToggle'); 12883 12884 if (!playToggle) { 12885 this.player_.focus(); 12886 return; 12887 } 12888 12889 var playFocus = function playFocus() { 12890 return playToggle.focus(); 12891 }; 12892 12893 if (isPromise(playPromise)) { 12894 playPromise.then(playFocus, function () {}); 12895 } else { 12896 this.setTimeout(playFocus, 1); 12897 } 12898 }; 12899 12900 BigPlayButton.prototype.handleKeyPress = function handleKeyPress(event) { 12901 this.mouseused_ = false; 12902 12903 _Button.prototype.handleKeyPress.call(this, event); 12904 }; 12905 12906 BigPlayButton.prototype.handleMouseDown = function handleMouseDown(event) { 12907 this.mouseused_ = true; 12908 }; 12909 12910 return BigPlayButton; 12911}(Button); 12912 12913/** 12914 * The text that should display over the `BigPlayButton`s controls. Added to for localization. 12915 * 12916 * @type {string} 12917 * @private 12918 */ 12919 12920 12921BigPlayButton.prototype.controlText_ = 'Play Video'; 12922 12923Component.registerComponent('BigPlayButton', BigPlayButton); 12924 12925/** 12926 * @file close-button.js 12927 */ 12928/** 12929 * The `CloseButton` is a `{@link Button}` that fires a `close` event when 12930 * it gets clicked. 12931 * 12932 * @extends Button 12933 */ 12934 12935var CloseButton = function (_Button) { 12936 inherits(CloseButton, _Button); 12937 12938 /** 12939 * Creates an instance of the this class. 12940 * 12941 * @param {Player} player 12942 * The `Player` that this class should be attached to. 12943 * 12944 * @param {Object} [options] 12945 * The key/value store of player options. 12946 */ 12947 function CloseButton(player, options) { 12948 classCallCheck(this, CloseButton); 12949 12950 var _this = possibleConstructorReturn(this, _Button.call(this, player, options)); 12951 12952 _this.controlText(options && options.controlText || _this.localize('Close')); 12953 return _this; 12954 } 12955 12956 /** 12957 * Builds the default DOM `className`. 12958 * 12959 * @return {string} 12960 * The DOM `className` for this object. 12961 */ 12962 12963 12964 CloseButton.prototype.buildCSSClass = function buildCSSClass() { 12965 return 'vjs-close-button ' + _Button.prototype.buildCSSClass.call(this); 12966 }; 12967 12968 /** 12969 * This gets called when a `CloseButton` gets clicked. See 12970 * {@link ClickableComponent#handleClick} for more information on when this will be 12971 * triggered 12972 * 12973 * @param {EventTarget~Event} event 12974 * The `keydown`, `tap`, or `click` event that caused this function to be 12975 * called. 12976 * 12977 * @listens tap 12978 * @listens click 12979 * @fires CloseButton#close 12980 */ 12981 12982 12983 CloseButton.prototype.handleClick = function handleClick(event) { 12984 12985 /** 12986 * Triggered when the a `CloseButton` is clicked. 12987 * 12988 * @event CloseButton#close 12989 * @type {EventTarget~Event} 12990 * 12991 * @property {boolean} [bubbles=false] 12992 * set to false so that the close event does not 12993 * bubble up to parents if there is no listener 12994 */ 12995 this.trigger({ type: 'close', bubbles: false }); 12996 }; 12997 12998 return CloseButton; 12999}(Button); 13000 13001Component.registerComponent('CloseButton', CloseButton); 13002 13003/** 13004 * @file play-toggle.js 13005 */ 13006/** 13007 * Button to toggle between play and pause. 13008 * 13009 * @extends Button 13010 */ 13011 13012var PlayToggle = function (_Button) { 13013 inherits(PlayToggle, _Button); 13014 13015 /** 13016 * Creates an instance of this class. 13017 * 13018 * @param {Player} player 13019 * The `Player` that this class should be attached to. 13020 * 13021 * @param {Object} [options] 13022 * The key/value store of player options. 13023 */ 13024 function PlayToggle(player, options) { 13025 classCallCheck(this, PlayToggle); 13026 13027 var _this = possibleConstructorReturn(this, _Button.call(this, player, options)); 13028 13029 _this.on(player, 'play', _this.handlePlay); 13030 _this.on(player, 'pause', _this.handlePause); 13031 _this.on(player, 'ended', _this.handleEnded); 13032 return _this; 13033 } 13034 13035 /** 13036 * Builds the default DOM `className`. 13037 * 13038 * @return {string} 13039 * The DOM `className` for this object. 13040 */ 13041 13042 13043 PlayToggle.prototype.buildCSSClass = function buildCSSClass() { 13044 return 'vjs-play-control ' + _Button.prototype.buildCSSClass.call(this); 13045 }; 13046 13047 /** 13048 * This gets called when an `PlayToggle` is "clicked". See 13049 * {@link ClickableComponent} for more detailed information on what a click can be. 13050 * 13051 * @param {EventTarget~Event} [event] 13052 * The `keydown`, `tap`, or `click` event that caused this function to be 13053 * called. 13054 * 13055 * @listens tap 13056 * @listens click 13057 */ 13058 13059 13060 PlayToggle.prototype.handleClick = function handleClick(event) { 13061 if (this.player_.paused()) { 13062 this.player_.play(); 13063 } else { 13064 this.player_.pause(); 13065 } 13066 }; 13067 13068 /** 13069 * This gets called once after the video has ended and the user seeks so that 13070 * we can change the replay button back to a play button. 13071 * 13072 * @param {EventTarget~Event} [event] 13073 * The event that caused this function to run. 13074 * 13075 * @listens Player#seeked 13076 */ 13077 13078 13079 PlayToggle.prototype.handleSeeked = function handleSeeked(event) { 13080 this.removeClass('vjs-ended'); 13081 13082 if (this.player_.paused()) { 13083 this.handlePause(event); 13084 } else { 13085 this.handlePlay(event); 13086 } 13087 }; 13088 13089 /** 13090 * Add the vjs-playing class to the element so it can change appearance. 13091 * 13092 * @param {EventTarget~Event} [event] 13093 * The event that caused this function to run. 13094 * 13095 * @listens Player#play 13096 */ 13097 13098 13099 PlayToggle.prototype.handlePlay = function handlePlay(event) { 13100 this.removeClass('vjs-ended'); 13101 this.removeClass('vjs-paused'); 13102 this.addClass('vjs-playing'); 13103 // change the button text to "Pause" 13104 this.controlText('Pause'); 13105 }; 13106 13107 /** 13108 * Add the vjs-paused class to the element so it can change appearance. 13109 * 13110 * @param {EventTarget~Event} [event] 13111 * The event that caused this function to run. 13112 * 13113 * @listens Player#pause 13114 */ 13115 13116 13117 PlayToggle.prototype.handlePause = function handlePause(event) { 13118 this.removeClass('vjs-playing'); 13119 this.addClass('vjs-paused'); 13120 // change the button text to "Play" 13121 this.controlText('Play'); 13122 }; 13123 13124 /** 13125 * Add the vjs-ended class to the element so it can change appearance 13126 * 13127 * @param {EventTarget~Event} [event] 13128 * The event that caused this function to run. 13129 * 13130 * @listens Player#ended 13131 */ 13132 13133 13134 PlayToggle.prototype.handleEnded = function handleEnded(event) { 13135 this.removeClass('vjs-playing'); 13136 this.addClass('vjs-ended'); 13137 // change the button text to "Replay" 13138 this.controlText('Replay'); 13139 13140 // on the next seek remove the replay button 13141 this.one(this.player_, 'seeked', this.handleSeeked); 13142 }; 13143 13144 return PlayToggle; 13145}(Button); 13146 13147/** 13148 * The text that should display over the `PlayToggle`s controls. Added for localization. 13149 * 13150 * @type {string} 13151 * @private 13152 */ 13153 13154 13155PlayToggle.prototype.controlText_ = 'Play'; 13156 13157Component.registerComponent('PlayToggle', PlayToggle); 13158 13159/** 13160 * @file format-time.js 13161 * @module format-time 13162 */ 13163 13164/** 13165* Format seconds as a time string, H:MM:SS or M:SS. Supplying a guide (in seconds) 13166* will force a number of leading zeros to cover the length of the guide. 13167* 13168* @param {number} seconds 13169* Number of seconds to be turned into a string 13170* 13171* @param {number} guide 13172* Number (in seconds) to model the string after 13173* 13174* @return {string} 13175* Time formatted as H:MM:SS or M:SS 13176*/ 13177var defaultImplementation = function defaultImplementation(seconds, guide) { 13178 seconds = seconds < 0 ? 0 : seconds; 13179 var s = Math.floor(seconds % 60); 13180 var m = Math.floor(seconds / 60 % 60); 13181 var h = Math.floor(seconds / 3600); 13182 var gm = Math.floor(guide / 60 % 60); 13183 var gh = Math.floor(guide / 3600); 13184 13185 // handle invalid times 13186 if (isNaN(seconds) || seconds === Infinity) { 13187 // '-' is false for all relational operators (e.g. <, >=) so this setting 13188 // will add the minimum number of fields specified by the guide 13189 h = m = s = '-'; 13190 } 13191 13192 // Check if we need to show hours 13193 h = h > 0 || gh > 0 ? h + ':' : ''; 13194 13195 // If hours are showing, we may need to add a leading zero. 13196 // Always show at least one digit of minutes. 13197 m = ((h || gm >= 10) && m < 10 ? '0' + m : m) + ':'; 13198 13199 // Check if leading zero is need for seconds 13200 s = s < 10 ? '0' + s : s; 13201 13202 return h + m + s; 13203}; 13204 13205var implementation = defaultImplementation; 13206 13207/** 13208 * Replaces the default formatTime implementation with a custom implementation. 13209 * 13210 * @param {Function} customImplementation 13211 * A function which will be used in place of the default formatTime implementation. 13212 * Will receive the current time in seconds and the guide (in seconds) as arguments. 13213 */ 13214function setFormatTime(customImplementation) { 13215 implementation = customImplementation; 13216} 13217 13218/** 13219 * Resets formatTime to the default implementation. 13220 */ 13221function resetFormatTime() { 13222 implementation = defaultImplementation; 13223} 13224 13225var formatTime = function (seconds) { 13226 var guide = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : seconds; 13227 13228 return implementation(seconds, guide); 13229}; 13230 13231/** 13232 * @file time-display.js 13233 */ 13234/** 13235 * Displays the time left in the video 13236 * 13237 * @extends Component 13238 */ 13239 13240var TimeDisplay = function (_Component) { 13241 inherits(TimeDisplay, _Component); 13242 13243 /** 13244 * Creates an instance of this class. 13245 * 13246 * @param {Player} player 13247 * The `Player` that this class should be attached to. 13248 * 13249 * @param {Object} [options] 13250 * The key/value store of player options. 13251 */ 13252 function TimeDisplay(player, options) { 13253 classCallCheck(this, TimeDisplay); 13254 13255 var _this = possibleConstructorReturn(this, _Component.call(this, player, options)); 13256 13257 _this.throttledUpdateContent = throttle(bind(_this, _this.updateContent), 25); 13258 _this.on(player, 'timeupdate', _this.throttledUpdateContent); 13259 return _this; 13260 } 13261 13262 /** 13263 * Create the `Component`'s DOM element 13264 * 13265 * @return {Element} 13266 * The element that was created. 13267 */ 13268 13269 13270 TimeDisplay.prototype.createEl = function createEl$$1(plainName) { 13271 var className = this.buildCSSClass(); 13272 var el = _Component.prototype.createEl.call(this, 'div', { 13273 className: className + ' vjs-time-control vjs-control', 13274 innerHTML: '<span class="vjs-control-text">' + this.localize(this.labelText_) + '\xA0</span>' 13275 }); 13276 13277 this.contentEl_ = createEl('span', { 13278 className: className + '-display' 13279 }, { 13280 // tell screen readers not to automatically read the time as it changes 13281 'aria-live': 'off' 13282 }); 13283 13284 this.updateTextNode_(); 13285 el.appendChild(this.contentEl_); 13286 return el; 13287 }; 13288 13289 TimeDisplay.prototype.dispose = function dispose() { 13290 this.contentEl_ = null; 13291 this.textNode_ = null; 13292 13293 _Component.prototype.dispose.call(this); 13294 }; 13295 13296 /** 13297 * Updates the "remaining time" text node with new content using the 13298 * contents of the `formattedTime_` property. 13299 * 13300 * @private 13301 */ 13302 13303 13304 TimeDisplay.prototype.updateTextNode_ = function updateTextNode_() { 13305 if (!this.contentEl_) { 13306 return; 13307 } 13308 13309 while (this.contentEl_.firstChild) { 13310 this.contentEl_.removeChild(this.contentEl_.firstChild); 13311 } 13312 13313 this.textNode_ = document_1.createTextNode(this.formattedTime_ || this.formatTime_(0)); 13314 this.contentEl_.appendChild(this.textNode_); 13315 }; 13316 13317 /** 13318 * Generates a formatted time for this component to use in display. 13319 * 13320 * @param {number} time 13321 * A numeric time, in seconds. 13322 * 13323 * @return {string} 13324 * A formatted time 13325 * 13326 * @private 13327 */ 13328 13329 13330 TimeDisplay.prototype.formatTime_ = function formatTime_(time) { 13331 return formatTime(time); 13332 }; 13333 13334 /** 13335 * Updates the time display text node if it has what was passed in changed 13336 * the formatted time. 13337 * 13338 * @param {number} time 13339 * The time to update to 13340 * 13341 * @private 13342 */ 13343 13344 13345 TimeDisplay.prototype.updateFormattedTime_ = function updateFormattedTime_(time) { 13346 var formattedTime = this.formatTime_(time); 13347 13348 if (formattedTime === this.formattedTime_) { 13349 return; 13350 } 13351 13352 this.formattedTime_ = formattedTime; 13353 this.requestAnimationFrame(this.updateTextNode_); 13354 }; 13355 13356 /** 13357 * To be filled out in the child class, should update the displayed time 13358 * in accordance with the fact that the current time has changed. 13359 * 13360 * @param {EventTarget~Event} [event] 13361 * The `timeupdate` event that caused this to run. 13362 * 13363 * @listens Player#timeupdate 13364 */ 13365 13366 13367 TimeDisplay.prototype.updateContent = function updateContent(event) {}; 13368 13369 return TimeDisplay; 13370}(Component); 13371 13372/** 13373 * The text that is added to the `TimeDisplay` for screen reader users. 13374 * 13375 * @type {string} 13376 * @private 13377 */ 13378 13379 13380TimeDisplay.prototype.labelText_ = 'Time'; 13381 13382/** 13383 * The text that should display over the `TimeDisplay`s controls. Added to for localization. 13384 * 13385 * @type {string} 13386 * @private 13387 * 13388 * @deprecated in v7; controlText_ is not used in non-active display Components 13389 */ 13390TimeDisplay.prototype.controlText_ = 'Time'; 13391 13392Component.registerComponent('TimeDisplay', TimeDisplay); 13393 13394/** 13395 * @file current-time-display.js 13396 */ 13397/** 13398 * Displays the current time 13399 * 13400 * @extends Component 13401 */ 13402 13403var CurrentTimeDisplay = function (_TimeDisplay) { 13404 inherits(CurrentTimeDisplay, _TimeDisplay); 13405 13406 /** 13407 * Creates an instance of this class. 13408 * 13409 * @param {Player} player 13410 * The `Player` that this class should be attached to. 13411 * 13412 * @param {Object} [options] 13413 * The key/value store of player options. 13414 */ 13415 function CurrentTimeDisplay(player, options) { 13416 classCallCheck(this, CurrentTimeDisplay); 13417 13418 var _this = possibleConstructorReturn(this, _TimeDisplay.call(this, player, options)); 13419 13420 _this.on(player, 'ended', _this.handleEnded); 13421 return _this; 13422 } 13423 13424 /** 13425 * Builds the default DOM `className`. 13426 * 13427 * @return {string} 13428 * The DOM `className` for this object. 13429 */ 13430 13431 13432 CurrentTimeDisplay.prototype.buildCSSClass = function buildCSSClass() { 13433 return 'vjs-current-time'; 13434 }; 13435 13436 /** 13437 * Update current time display 13438 * 13439 * @param {EventTarget~Event} [event] 13440 * The `timeupdate` event that caused this function to run. 13441 * 13442 * @listens Player#timeupdate 13443 */ 13444 13445 13446 CurrentTimeDisplay.prototype.updateContent = function updateContent(event) { 13447 // Allows for smooth scrubbing, when player can't keep up. 13448 var time = this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime(); 13449 13450 this.updateFormattedTime_(time); 13451 }; 13452 13453 /** 13454 * When the player fires ended there should be no time left. Sadly 13455 * this is not always the case, lets make it seem like that is the case 13456 * for users. 13457 * 13458 * @param {EventTarget~Event} [event] 13459 * The `ended` event that caused this to run. 13460 * 13461 * @listens Player#ended 13462 */ 13463 13464 13465 CurrentTimeDisplay.prototype.handleEnded = function handleEnded(event) { 13466 if (!this.player_.duration()) { 13467 return; 13468 } 13469 this.updateFormattedTime_(this.player_.duration()); 13470 }; 13471 13472 return CurrentTimeDisplay; 13473}(TimeDisplay); 13474 13475/** 13476 * The text that is added to the `CurrentTimeDisplay` for screen reader users. 13477 * 13478 * @type {string} 13479 * @private 13480 */ 13481 13482 13483CurrentTimeDisplay.prototype.labelText_ = 'Current Time'; 13484 13485/** 13486 * The text that should display over the `CurrentTimeDisplay`s controls. Added to for localization. 13487 * 13488 * @type {string} 13489 * @private 13490 * 13491 * @deprecated in v7; controlText_ is not used in non-active display Components 13492 */ 13493CurrentTimeDisplay.prototype.controlText_ = 'Current Time'; 13494 13495Component.registerComponent('CurrentTimeDisplay', CurrentTimeDisplay); 13496 13497/** 13498 * @file duration-display.js 13499 */ 13500/** 13501 * Displays the duration 13502 * 13503 * @extends Component 13504 */ 13505 13506var DurationDisplay = function (_TimeDisplay) { 13507 inherits(DurationDisplay, _TimeDisplay); 13508 13509 /** 13510 * Creates an instance of this class. 13511 * 13512 * @param {Player} player 13513 * The `Player` that this class should be attached to. 13514 * 13515 * @param {Object} [options] 13516 * The key/value store of player options. 13517 */ 13518 function DurationDisplay(player, options) { 13519 classCallCheck(this, DurationDisplay); 13520 13521 // we do not want to/need to throttle duration changes, 13522 // as they should always display the changed duration as 13523 // it has changed 13524 var _this = possibleConstructorReturn(this, _TimeDisplay.call(this, player, options)); 13525 13526 _this.on(player, 'durationchange', _this.updateContent); 13527 13528 // Also listen for timeupdate (in the parent) and loadedmetadata because removing those 13529 // listeners could have broken dependent applications/libraries. These 13530 // can likely be removed for 7.0. 13531 _this.on(player, 'loadedmetadata', _this.throttledUpdateContent); 13532 return _this; 13533 } 13534 13535 /** 13536 * Builds the default DOM `className`. 13537 * 13538 * @return {string} 13539 * The DOM `className` for this object. 13540 */ 13541 13542 13543 DurationDisplay.prototype.buildCSSClass = function buildCSSClass() { 13544 return 'vjs-duration'; 13545 }; 13546 13547 /** 13548 * Update duration time display. 13549 * 13550 * @param {EventTarget~Event} [event] 13551 * The `durationchange`, `timeupdate`, or `loadedmetadata` event that caused 13552 * this function to be called. 13553 * 13554 * @listens Player#durationchange 13555 * @listens Player#timeupdate 13556 * @listens Player#loadedmetadata 13557 */ 13558 13559 13560 DurationDisplay.prototype.updateContent = function updateContent(event) { 13561 var duration = this.player_.duration(); 13562 13563 if (duration && this.duration_ !== duration) { 13564 this.duration_ = duration; 13565 this.updateFormattedTime_(duration); 13566 } 13567 }; 13568 13569 return DurationDisplay; 13570}(TimeDisplay); 13571 13572/** 13573 * The text that is added to the `DurationDisplay` for screen reader users. 13574 * 13575 * @type {string} 13576 * @private 13577 */ 13578 13579 13580DurationDisplay.prototype.labelText_ = 'Duration'; 13581 13582/** 13583 * The text that should display over the `DurationDisplay`s controls. Added to for localization. 13584 * 13585 * @type {string} 13586 * @private 13587 * 13588 * @deprecated in v7; controlText_ is not used in non-active display Components 13589 */ 13590DurationDisplay.prototype.controlText_ = 'Duration'; 13591 13592Component.registerComponent('DurationDisplay', DurationDisplay); 13593 13594/** 13595 * @file time-divider.js 13596 */ 13597/** 13598 * The separator between the current time and duration. 13599 * Can be hidden if it's not needed in the design. 13600 * 13601 * @extends Component 13602 */ 13603 13604var TimeDivider = function (_Component) { 13605 inherits(TimeDivider, _Component); 13606 13607 function TimeDivider() { 13608 classCallCheck(this, TimeDivider); 13609 return possibleConstructorReturn(this, _Component.apply(this, arguments)); 13610 } 13611 13612 /** 13613 * Create the component's DOM element 13614 * 13615 * @return {Element} 13616 * The element that was created. 13617 */ 13618 TimeDivider.prototype.createEl = function createEl() { 13619 return _Component.prototype.createEl.call(this, 'div', { 13620 className: 'vjs-time-control vjs-time-divider', 13621 innerHTML: '<div><span>/</span></div>' 13622 }); 13623 }; 13624 13625 return TimeDivider; 13626}(Component); 13627 13628Component.registerComponent('TimeDivider', TimeDivider); 13629 13630/** 13631 * @file remaining-time-display.js 13632 */ 13633/** 13634 * Displays the time left in the video 13635 * 13636 * @extends Component 13637 */ 13638 13639var RemainingTimeDisplay = function (_TimeDisplay) { 13640 inherits(RemainingTimeDisplay, _TimeDisplay); 13641 13642 /** 13643 * Creates an instance of this class. 13644 * 13645 * @param {Player} player 13646 * The `Player` that this class should be attached to. 13647 * 13648 * @param {Object} [options] 13649 * The key/value store of player options. 13650 */ 13651 function RemainingTimeDisplay(player, options) { 13652 classCallCheck(this, RemainingTimeDisplay); 13653 13654 var _this = possibleConstructorReturn(this, _TimeDisplay.call(this, player, options)); 13655 13656 _this.on(player, 'durationchange', _this.throttledUpdateContent); 13657 _this.on(player, 'ended', _this.handleEnded); 13658 return _this; 13659 } 13660 13661 /** 13662 * Builds the default DOM `className`. 13663 * 13664 * @return {string} 13665 * The DOM `className` for this object. 13666 */ 13667 13668 13669 RemainingTimeDisplay.prototype.buildCSSClass = function buildCSSClass() { 13670 return 'vjs-remaining-time'; 13671 }; 13672 13673 /** 13674 * The remaining time display prefixes numbers with a "minus" character. 13675 * 13676 * @param {number} time 13677 * A numeric time, in seconds. 13678 * 13679 * @return {string} 13680 * A formatted time 13681 * 13682 * @private 13683 */ 13684 13685 13686 RemainingTimeDisplay.prototype.formatTime_ = function formatTime_(time) { 13687 // TODO: The "-" should be decorative, and not announced by a screen reader 13688 return '-' + _TimeDisplay.prototype.formatTime_.call(this, time); 13689 }; 13690 13691 /** 13692 * Update remaining time display. 13693 * 13694 * @param {EventTarget~Event} [event] 13695 * The `timeupdate` or `durationchange` event that caused this to run. 13696 * 13697 * @listens Player#timeupdate 13698 * @listens Player#durationchange 13699 */ 13700 13701 13702 RemainingTimeDisplay.prototype.updateContent = function updateContent(event) { 13703 if (!this.player_.duration()) { 13704 return; 13705 } 13706 13707 // @deprecated We should only use remainingTimeDisplay 13708 // as of video.js 7 13709 if (this.player_.remainingTimeDisplay) { 13710 this.updateFormattedTime_(this.player_.remainingTimeDisplay()); 13711 } else { 13712 this.updateFormattedTime_(this.player_.remainingTime()); 13713 } 13714 }; 13715 13716 /** 13717 * When the player fires ended there should be no time left. Sadly 13718 * this is not always the case, lets make it seem like that is the case 13719 * for users. 13720 * 13721 * @param {EventTarget~Event} [event] 13722 * The `ended` event that caused this to run. 13723 * 13724 * @listens Player#ended 13725 */ 13726 13727 13728 RemainingTimeDisplay.prototype.handleEnded = function handleEnded(event) { 13729 if (!this.player_.duration()) { 13730 return; 13731 } 13732 this.updateFormattedTime_(0); 13733 }; 13734 13735 return RemainingTimeDisplay; 13736}(TimeDisplay); 13737 13738/** 13739 * The text that is added to the `RemainingTimeDisplay` for screen reader users. 13740 * 13741 * @type {string} 13742 * @private 13743 */ 13744 13745 13746RemainingTimeDisplay.prototype.labelText_ = 'Remaining Time'; 13747 13748/** 13749 * The text that should display over the `RemainingTimeDisplay`s controls. Added to for localization. 13750 * 13751 * @type {string} 13752 * @private 13753 * 13754 * @deprecated in v7; controlText_ is not used in non-active display Components 13755 */ 13756RemainingTimeDisplay.prototype.controlText_ = 'Remaining Time'; 13757 13758Component.registerComponent('RemainingTimeDisplay', RemainingTimeDisplay); 13759 13760/** 13761 * @file live-display.js 13762 */ 13763// TODO - Future make it click to snap to live 13764 13765/** 13766 * Displays the live indicator when duration is Infinity. 13767 * 13768 * @extends Component 13769 */ 13770 13771var LiveDisplay = function (_Component) { 13772 inherits(LiveDisplay, _Component); 13773 13774 /** 13775 * Creates an instance of this class. 13776 * 13777 * @param {Player} player 13778 * The `Player` that this class should be attached to. 13779 * 13780 * @param {Object} [options] 13781 * The key/value store of player options. 13782 */ 13783 function LiveDisplay(player, options) { 13784 classCallCheck(this, LiveDisplay); 13785 13786 var _this = possibleConstructorReturn(this, _Component.call(this, player, options)); 13787 13788 _this.updateShowing(); 13789 _this.on(_this.player(), 'durationchange', _this.updateShowing); 13790 return _this; 13791 } 13792 13793 /** 13794 * Create the `Component`'s DOM element 13795 * 13796 * @return {Element} 13797 * The element that was created. 13798 */ 13799 13800 13801 LiveDisplay.prototype.createEl = function createEl$$1() { 13802 var el = _Component.prototype.createEl.call(this, 'div', { 13803 className: 'vjs-live-control vjs-control' 13804 }); 13805 13806 this.contentEl_ = createEl('div', { 13807 className: 'vjs-live-display', 13808 innerHTML: '<span class="vjs-control-text">' + this.localize('Stream Type') + '\xA0</span>' + this.localize('LIVE') 13809 }, { 13810 'aria-live': 'off' 13811 }); 13812 13813 el.appendChild(this.contentEl_); 13814 return el; 13815 }; 13816 13817 LiveDisplay.prototype.dispose = function dispose() { 13818 this.contentEl_ = null; 13819 13820 _Component.prototype.dispose.call(this); 13821 }; 13822 13823 /** 13824 * Check the duration to see if the LiveDisplay should be showing or not. Then show/hide 13825 * it accordingly 13826 * 13827 * @param {EventTarget~Event} [event] 13828 * The {@link Player#durationchange} event that caused this function to run. 13829 * 13830 * @listens Player#durationchange 13831 */ 13832 13833 13834 LiveDisplay.prototype.updateShowing = function updateShowing(event) { 13835 if (this.player().duration() === Infinity) { 13836 this.show(); 13837 } else { 13838 this.hide(); 13839 } 13840 }; 13841 13842 return LiveDisplay; 13843}(Component); 13844 13845Component.registerComponent('LiveDisplay', LiveDisplay); 13846 13847/** 13848 * @file slider.js 13849 */ 13850/** 13851 * The base functionality for a slider. Can be vertical or horizontal. 13852 * For instance the volume bar or the seek bar on a video is a slider. 13853 * 13854 * @extends Component 13855 */ 13856 13857var Slider = function (_Component) { 13858 inherits(Slider, _Component); 13859 13860 /** 13861 * Create an instance of this class 13862 * 13863 * @param {Player} player 13864 * The `Player` that this class should be attached to. 13865 * 13866 * @param {Object} [options] 13867 * The key/value store of player options. 13868 */ 13869 function Slider(player, options) { 13870 classCallCheck(this, Slider); 13871 13872 // Set property names to bar to match with the child Slider class is looking for 13873 var _this = possibleConstructorReturn(this, _Component.call(this, player, options)); 13874 13875 _this.bar = _this.getChild(_this.options_.barName); 13876 13877 // Set a horizontal or vertical class on the slider depending on the slider type 13878 _this.vertical(!!_this.options_.vertical); 13879 13880 _this.enable(); 13881 return _this; 13882 } 13883 13884 /** 13885 * Are controls are currently enabled for this slider or not. 13886 * 13887 * @return {boolean} 13888 * true if controls are enabled, false otherwise 13889 */ 13890 13891 13892 Slider.prototype.enabled = function enabled() { 13893 return this.enabled_; 13894 }; 13895 13896 /** 13897 * Enable controls for this slider if they are disabled 13898 */ 13899 13900 13901 Slider.prototype.enable = function enable() { 13902 if (this.enabled()) { 13903 return; 13904 } 13905 13906 this.on('mousedown', this.handleMouseDown); 13907 this.on('touchstart', this.handleMouseDown); 13908 this.on('focus', this.handleFocus); 13909 this.on('blur', this.handleBlur); 13910 this.on('click', this.handleClick); 13911 13912 this.on(this.player_, 'controlsvisible', this.update); 13913 13914 if (this.playerEvent) { 13915 this.on(this.player_, this.playerEvent, this.update); 13916 } 13917 13918 this.removeClass('disabled'); 13919 this.setAttribute('tabindex', 0); 13920 13921 this.enabled_ = true; 13922 }; 13923 13924 /** 13925 * Disable controls for this slider if they are enabled 13926 */ 13927 13928 13929 Slider.prototype.disable = function disable() { 13930 if (!this.enabled()) { 13931 return; 13932 } 13933 var doc = this.bar.el_.ownerDocument; 13934 13935 this.off('mousedown', this.handleMouseDown); 13936 this.off('touchstart', this.handleMouseDown); 13937 this.off('focus', this.handleFocus); 13938 this.off('blur', this.handleBlur); 13939 this.off('click', this.handleClick); 13940 this.off(this.player_, 'controlsvisible', this.update); 13941 this.off(doc, 'mousemove', this.handleMouseMove); 13942 this.off(doc, 'mouseup', this.handleMouseUp); 13943 this.off(doc, 'touchmove', this.handleMouseMove); 13944 this.off(doc, 'touchend', this.handleMouseUp); 13945 this.removeAttribute('tabindex'); 13946 13947 this.addClass('disabled'); 13948 13949 if (this.playerEvent) { 13950 this.off(this.player_, this.playerEvent, this.update); 13951 } 13952 this.enabled_ = false; 13953 }; 13954 13955 /** 13956 * Create the `Button`s DOM element. 13957 * 13958 * @param {string} type 13959 * Type of element to create. 13960 * 13961 * @param {Object} [props={}] 13962 * List of properties in Object form. 13963 * 13964 * @param {Object} [attributes={}] 13965 * list of attributes in Object form. 13966 * 13967 * @return {Element} 13968 * The element that gets created. 13969 */ 13970 13971 13972 Slider.prototype.createEl = function createEl$$1(type) { 13973 var props = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 13974 var attributes = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; 13975 13976 // Add the slider element class to all sub classes 13977 props.className = props.className + ' vjs-slider'; 13978 props = assign({ 13979 tabIndex: 0 13980 }, props); 13981 13982 attributes = assign({ 13983 'role': 'slider', 13984 'aria-valuenow': 0, 13985 'aria-valuemin': 0, 13986 'aria-valuemax': 100, 13987 'tabIndex': 0 13988 }, attributes); 13989 13990 return _Component.prototype.createEl.call(this, type, props, attributes); 13991 }; 13992 13993 /** 13994 * Handle `mousedown` or `touchstart` events on the `Slider`. 13995 * 13996 * @param {EventTarget~Event} event 13997 * `mousedown` or `touchstart` event that triggered this function 13998 * 13999 * @listens mousedown 14000 * @listens touchstart 14001 * @fires Slider#slideractive 14002 */ 14003 14004 14005 Slider.prototype.handleMouseDown = function handleMouseDown(event) { 14006 var doc = this.bar.el_.ownerDocument; 14007 14008 if (event.type === 'mousedown') { 14009 event.preventDefault(); 14010 } 14011 // Do not call preventDefault() on touchstart in Chrome 14012 // to avoid console warnings. Use a 'touch-action: none' style 14013 // instead to prevent unintented scrolling. 14014 // https://developers.google.com/web/updates/2017/01/scrolling-intervention 14015 if (event.type === 'touchstart' && !IS_CHROME) { 14016 event.preventDefault(); 14017 } 14018 blockTextSelection(); 14019 14020 this.addClass('vjs-sliding'); 14021 /** 14022 * Triggered when the slider is in an active state 14023 * 14024 * @event Slider#slideractive 14025 * @type {EventTarget~Event} 14026 */ 14027 this.trigger('slideractive'); 14028 14029 this.on(doc, 'mousemove', this.handleMouseMove); 14030 this.on(doc, 'mouseup', this.handleMouseUp); 14031 this.on(doc, 'touchmove', this.handleMouseMove); 14032 this.on(doc, 'touchend', this.handleMouseUp); 14033 14034 this.handleMouseMove(event); 14035 }; 14036 14037 /** 14038 * Handle the `mousemove`, `touchmove`, and `mousedown` events on this `Slider`. 14039 * The `mousemove` and `touchmove` events will only only trigger this function during 14040 * `mousedown` and `touchstart`. This is due to {@link Slider#handleMouseDown} and 14041 * {@link Slider#handleMouseUp}. 14042 * 14043 * @param {EventTarget~Event} event 14044 * `mousedown`, `mousemove`, `touchstart`, or `touchmove` event that triggered 14045 * this function 14046 * 14047 * @listens mousemove 14048 * @listens touchmove 14049 */ 14050 14051 14052 Slider.prototype.handleMouseMove = function handleMouseMove(event) {}; 14053 14054 /** 14055 * Handle `mouseup` or `touchend` events on the `Slider`. 14056 * 14057 * @param {EventTarget~Event} event 14058 * `mouseup` or `touchend` event that triggered this function. 14059 * 14060 * @listens touchend 14061 * @listens mouseup 14062 * @fires Slider#sliderinactive 14063 */ 14064 14065 14066 Slider.prototype.handleMouseUp = function handleMouseUp() { 14067 var doc = this.bar.el_.ownerDocument; 14068 14069 unblockTextSelection(); 14070 14071 this.removeClass('vjs-sliding'); 14072 /** 14073 * Triggered when the slider is no longer in an active state. 14074 * 14075 * @event Slider#sliderinactive 14076 * @type {EventTarget~Event} 14077 */ 14078 this.trigger('sliderinactive'); 14079 14080 this.off(doc, 'mousemove', this.handleMouseMove); 14081 this.off(doc, 'mouseup', this.handleMouseUp); 14082 this.off(doc, 'touchmove', this.handleMouseMove); 14083 this.off(doc, 'touchend', this.handleMouseUp); 14084 14085 this.update(); 14086 }; 14087 14088 /** 14089 * Update the progress bar of the `Slider`. 14090 * 14091 * @returns {number} 14092 * The percentage of progress the progress bar represents as a 14093 * number from 0 to 1. 14094 */ 14095 14096 14097 Slider.prototype.update = function update() { 14098 14099 // In VolumeBar init we have a setTimeout for update that pops and update 14100 // to the end of the execution stack. The player is destroyed before then 14101 // update will cause an error 14102 if (!this.el_) { 14103 return; 14104 } 14105 14106 // If scrubbing, we could use a cached value to make the handle keep up 14107 // with the user's mouse. On HTML5 browsers scrubbing is really smooth, but 14108 // some flash players are slow, so we might want to utilize this later. 14109 // var progress = (this.player_.scrubbing()) ? this.player_.getCache().currentTime / this.player_.duration() : this.player_.currentTime() / this.player_.duration(); 14110 var progress = this.getPercent(); 14111 var bar = this.bar; 14112 14113 // If there's no bar... 14114 if (!bar) { 14115 return; 14116 } 14117 14118 // Protect against no duration and other division issues 14119 if (typeof progress !== 'number' || progress !== progress || progress < 0 || progress === Infinity) { 14120 progress = 0; 14121 } 14122 14123 // Convert to a percentage for setting 14124 var percentage = (progress * 100).toFixed(2) + '%'; 14125 var style = bar.el().style; 14126 14127 // Set the new bar width or height 14128 if (this.vertical()) { 14129 style.height = percentage; 14130 } else { 14131 style.width = percentage; 14132 } 14133 14134 return progress; 14135 }; 14136 14137 /** 14138 * Calculate distance for slider 14139 * 14140 * @param {EventTarget~Event} event 14141 * The event that caused this function to run. 14142 * 14143 * @return {number} 14144 * The current position of the Slider. 14145 * - postition.x for vertical `Slider`s 14146 * - postition.y for horizontal `Slider`s 14147 */ 14148 14149 14150 Slider.prototype.calculateDistance = function calculateDistance(event) { 14151 var position = getPointerPosition(this.el_, event); 14152 14153 if (this.vertical()) { 14154 return position.y; 14155 } 14156 return position.x; 14157 }; 14158 14159 /** 14160 * Handle a `focus` event on this `Slider`. 14161 * 14162 * @param {EventTarget~Event} event 14163 * The `focus` event that caused this function to run. 14164 * 14165 * @listens focus 14166 */ 14167 14168 14169 Slider.prototype.handleFocus = function handleFocus() { 14170 this.on(this.bar.el_.ownerDocument, 'keydown', this.handleKeyPress); 14171 }; 14172 14173 /** 14174 * Handle a `keydown` event on the `Slider`. Watches for left, rigth, up, and down 14175 * arrow keys. This function will only be called when the slider has focus. See 14176 * {@link Slider#handleFocus} and {@link Slider#handleBlur}. 14177 * 14178 * @param {EventTarget~Event} event 14179 * the `keydown` event that caused this function to run. 14180 * 14181 * @listens keydown 14182 */ 14183 14184 14185 Slider.prototype.handleKeyPress = function handleKeyPress(event) { 14186 // Left and Down Arrows 14187 if (event.which === 37 || event.which === 40) { 14188 event.preventDefault(); 14189 this.stepBack(); 14190 14191 // Up and Right Arrows 14192 } else if (event.which === 38 || event.which === 39) { 14193 event.preventDefault(); 14194 this.stepForward(); 14195 } 14196 }; 14197 14198 /** 14199 * Handle a `blur` event on this `Slider`. 14200 * 14201 * @param {EventTarget~Event} event 14202 * The `blur` event that caused this function to run. 14203 * 14204 * @listens blur 14205 */ 14206 14207 Slider.prototype.handleBlur = function handleBlur() { 14208 this.off(this.bar.el_.ownerDocument, 'keydown', this.handleKeyPress); 14209 }; 14210 14211 /** 14212 * Listener for click events on slider, used to prevent clicks 14213 * from bubbling up to parent elements like button menus. 14214 * 14215 * @param {Object} event 14216 * Event that caused this object to run 14217 */ 14218 14219 14220 Slider.prototype.handleClick = function handleClick(event) { 14221 event.stopImmediatePropagation(); 14222 event.preventDefault(); 14223 }; 14224 14225 /** 14226 * Get/set if slider is horizontal for vertical 14227 * 14228 * @param {boolean} [bool] 14229 * - true if slider is vertical, 14230 * - false is horizontal 14231 * 14232 * @return {boolean} 14233 * - true if slider is vertical, and getting 14234 * - false if the slider is horizontal, and getting 14235 */ 14236 14237 14238 Slider.prototype.vertical = function vertical(bool) { 14239 if (bool === undefined) { 14240 return this.vertical_ || false; 14241 } 14242 14243 this.vertical_ = !!bool; 14244 14245 if (this.vertical_) { 14246 this.addClass('vjs-slider-vertical'); 14247 } else { 14248 this.addClass('vjs-slider-horizontal'); 14249 } 14250 }; 14251 14252 return Slider; 14253}(Component); 14254 14255Component.registerComponent('Slider', Slider); 14256 14257/** 14258 * @file load-progress-bar.js 14259 */ 14260/** 14261 * Shows loading progress 14262 * 14263 * @extends Component 14264 */ 14265 14266var LoadProgressBar = function (_Component) { 14267 inherits(LoadProgressBar, _Component); 14268 14269 /** 14270 * Creates an instance of this class. 14271 * 14272 * @param {Player} player 14273 * The `Player` that this class should be attached to. 14274 * 14275 * @param {Object} [options] 14276 * The key/value store of player options. 14277 */ 14278 function LoadProgressBar(player, options) { 14279 classCallCheck(this, LoadProgressBar); 14280 14281 var _this = possibleConstructorReturn(this, _Component.call(this, player, options)); 14282 14283 _this.partEls_ = []; 14284 _this.on(player, 'progress', _this.update); 14285 return _this; 14286 } 14287 14288 /** 14289 * Create the `Component`'s DOM element 14290 * 14291 * @return {Element} 14292 * The element that was created. 14293 */ 14294 14295 14296 LoadProgressBar.prototype.createEl = function createEl$$1() { 14297 return _Component.prototype.createEl.call(this, 'div', { 14298 className: 'vjs-load-progress', 14299 innerHTML: '<span class="vjs-control-text"><span>' + this.localize('Loaded') + '</span>: 0%</span>' 14300 }); 14301 }; 14302 14303 LoadProgressBar.prototype.dispose = function dispose() { 14304 this.partEls_ = null; 14305 14306 _Component.prototype.dispose.call(this); 14307 }; 14308 14309 /** 14310 * Update progress bar 14311 * 14312 * @param {EventTarget~Event} [event] 14313 * The `progress` event that caused this function to run. 14314 * 14315 * @listens Player#progress 14316 */ 14317 14318 14319 LoadProgressBar.prototype.update = function update(event) { 14320 var buffered = this.player_.buffered(); 14321 var duration = this.player_.duration(); 14322 var bufferedEnd = this.player_.bufferedEnd(); 14323 var children = this.partEls_; 14324 14325 // get the percent width of a time compared to the total end 14326 var percentify = function percentify(time, end) { 14327 // no NaN 14328 var percent = time / end || 0; 14329 14330 return (percent >= 1 ? 1 : percent) * 100 + '%'; 14331 }; 14332 14333 // update the width of the progress bar 14334 this.el_.style.width = percentify(bufferedEnd, duration); 14335 14336 // add child elements to represent the individual buffered time ranges 14337 for (var i = 0; i < buffered.length; i++) { 14338 var start = buffered.start(i); 14339 var end = buffered.end(i); 14340 var part = children[i]; 14341 14342 if (!part) { 14343 part = this.el_.appendChild(createEl()); 14344 children[i] = part; 14345 } 14346 14347 // set the percent based on the width of the progress bar (bufferedEnd) 14348 part.style.left = percentify(start, bufferedEnd); 14349 part.style.width = percentify(end - start, bufferedEnd); 14350 } 14351 14352 // remove unused buffered range elements 14353 for (var _i = children.length; _i > buffered.length; _i--) { 14354 this.el_.removeChild(children[_i - 1]); 14355 } 14356 children.length = buffered.length; 14357 }; 14358 14359 return LoadProgressBar; 14360}(Component); 14361 14362Component.registerComponent('LoadProgressBar', LoadProgressBar); 14363 14364/** 14365 * @file time-tooltip.js 14366 */ 14367/** 14368 * Time tooltips display a time above the progress bar. 14369 * 14370 * @extends Component 14371 */ 14372 14373var TimeTooltip = function (_Component) { 14374 inherits(TimeTooltip, _Component); 14375 14376 function TimeTooltip() { 14377 classCallCheck(this, TimeTooltip); 14378 return possibleConstructorReturn(this, _Component.apply(this, arguments)); 14379 } 14380 14381 /** 14382 * Create the time tooltip DOM element 14383 * 14384 * @return {Element} 14385 * The element that was created. 14386 */ 14387 TimeTooltip.prototype.createEl = function createEl$$1() { 14388 return _Component.prototype.createEl.call(this, 'div', { 14389 className: 'vjs-time-tooltip' 14390 }); 14391 }; 14392 14393 /** 14394 * Updates the position of the time tooltip relative to the `SeekBar`. 14395 * 14396 * @param {Object} seekBarRect 14397 * The `ClientRect` for the {@link SeekBar} element. 14398 * 14399 * @param {number} seekBarPoint 14400 * A number from 0 to 1, representing a horizontal reference point 14401 * from the left edge of the {@link SeekBar} 14402 */ 14403 14404 14405 TimeTooltip.prototype.update = function update(seekBarRect, seekBarPoint, content) { 14406 var tooltipRect = getBoundingClientRect(this.el_); 14407 var playerRect = getBoundingClientRect(this.player_.el()); 14408 var seekBarPointPx = seekBarRect.width * seekBarPoint; 14409 14410 // do nothing if either rect isn't available 14411 // for example, if the player isn't in the DOM for testing 14412 if (!playerRect || !tooltipRect) { 14413 return; 14414 } 14415 14416 // This is the space left of the `seekBarPoint` available within the bounds 14417 // of the player. We calculate any gap between the left edge of the player 14418 // and the left edge of the `SeekBar` and add the number of pixels in the 14419 // `SeekBar` before hitting the `seekBarPoint` 14420 var spaceLeftOfPoint = seekBarRect.left - playerRect.left + seekBarPointPx; 14421 14422 // This is the space right of the `seekBarPoint` available within the bounds 14423 // of the player. We calculate the number of pixels from the `seekBarPoint` 14424 // to the right edge of the `SeekBar` and add to that any gap between the 14425 // right edge of the `SeekBar` and the player. 14426 var spaceRightOfPoint = seekBarRect.width - seekBarPointPx + (playerRect.right - seekBarRect.right); 14427 14428 // This is the number of pixels by which the tooltip will need to be pulled 14429 // further to the right to center it over the `seekBarPoint`. 14430 var pullTooltipBy = tooltipRect.width / 2; 14431 14432 // Adjust the `pullTooltipBy` distance to the left or right depending on 14433 // the results of the space calculations above. 14434 if (spaceLeftOfPoint < pullTooltipBy) { 14435 pullTooltipBy += pullTooltipBy - spaceLeftOfPoint; 14436 } else if (spaceRightOfPoint < pullTooltipBy) { 14437 pullTooltipBy = spaceRightOfPoint; 14438 } 14439 14440 // Due to the imprecision of decimal/ratio based calculations and varying 14441 // rounding behaviors, there are cases where the spacing adjustment is off 14442 // by a pixel or two. This adds insurance to these calculations. 14443 if (pullTooltipBy < 0) { 14444 pullTooltipBy = 0; 14445 } else if (pullTooltipBy > tooltipRect.width) { 14446 pullTooltipBy = tooltipRect.width; 14447 } 14448 14449 this.el_.style.right = '-' + pullTooltipBy + 'px'; 14450 textContent(this.el_, content); 14451 }; 14452 14453 return TimeTooltip; 14454}(Component); 14455 14456Component.registerComponent('TimeTooltip', TimeTooltip); 14457 14458/** 14459 * @file play-progress-bar.js 14460 */ 14461/** 14462 * Used by {@link SeekBar} to display media playback progress as part of the 14463 * {@link ProgressControl}. 14464 * 14465 * @extends Component 14466 */ 14467 14468var PlayProgressBar = function (_Component) { 14469 inherits(PlayProgressBar, _Component); 14470 14471 function PlayProgressBar() { 14472 classCallCheck(this, PlayProgressBar); 14473 return possibleConstructorReturn(this, _Component.apply(this, arguments)); 14474 } 14475 14476 /** 14477 * Create the the DOM element for this class. 14478 * 14479 * @return {Element} 14480 * The element that was created. 14481 */ 14482 PlayProgressBar.prototype.createEl = function createEl() { 14483 return _Component.prototype.createEl.call(this, 'div', { 14484 className: 'vjs-play-progress vjs-slider-bar', 14485 innerHTML: '<span class="vjs-control-text"><span>' + this.localize('Progress') + '</span>: 0%</span>' 14486 }); 14487 }; 14488 14489 /** 14490 * Enqueues updates to its own DOM as well as the DOM of its 14491 * {@link TimeTooltip} child. 14492 * 14493 * @param {Object} seekBarRect 14494 * The `ClientRect` for the {@link SeekBar} element. 14495 * 14496 * @param {number} seekBarPoint 14497 * A number from 0 to 1, representing a horizontal reference point 14498 * from the left edge of the {@link SeekBar} 14499 */ 14500 14501 14502 PlayProgressBar.prototype.update = function update(seekBarRect, seekBarPoint) { 14503 var _this2 = this; 14504 14505 // If there is an existing rAF ID, cancel it so we don't over-queue. 14506 if (this.rafId_) { 14507 this.cancelAnimationFrame(this.rafId_); 14508 } 14509 14510 this.rafId_ = this.requestAnimationFrame(function () { 14511 var time = _this2.player_.scrubbing() ? _this2.player_.getCache().currentTime : _this2.player_.currentTime(); 14512 14513 var content = formatTime(time, _this2.player_.duration()); 14514 var timeTooltip = _this2.getChild('timeTooltip'); 14515 14516 if (timeTooltip) { 14517 timeTooltip.update(seekBarRect, seekBarPoint, content); 14518 } 14519 }); 14520 }; 14521 14522 return PlayProgressBar; 14523}(Component); 14524 14525/** 14526 * Default options for {@link PlayProgressBar}. 14527 * 14528 * @type {Object} 14529 * @private 14530 */ 14531 14532 14533PlayProgressBar.prototype.options_ = { 14534 children: [] 14535}; 14536 14537// Time tooltips should not be added to a player on mobile devices or IE8 14538if ((!IE_VERSION || IE_VERSION > 8) && !IS_IOS && !IS_ANDROID) { 14539 PlayProgressBar.prototype.options_.children.push('timeTooltip'); 14540} 14541 14542Component.registerComponent('PlayProgressBar', PlayProgressBar); 14543 14544/** 14545 * @file mouse-time-display.js 14546 */ 14547/** 14548 * The {@link MouseTimeDisplay} component tracks mouse movement over the 14549 * {@link ProgressControl}. It displays an indicator and a {@link TimeTooltip} 14550 * indicating the time which is represented by a given point in the 14551 * {@link ProgressControl}. 14552 * 14553 * @extends Component 14554 */ 14555 14556var MouseTimeDisplay = function (_Component) { 14557 inherits(MouseTimeDisplay, _Component); 14558 14559 /** 14560 * Creates an instance of this class. 14561 * 14562 * @param {Player} player 14563 * The {@link Player} that this class should be attached to. 14564 * 14565 * @param {Object} [options] 14566 * The key/value store of player options. 14567 */ 14568 function MouseTimeDisplay(player, options) { 14569 classCallCheck(this, MouseTimeDisplay); 14570 14571 var _this = possibleConstructorReturn(this, _Component.call(this, player, options)); 14572 14573 _this.update = throttle(bind(_this, _this.update), 25); 14574 return _this; 14575 } 14576 14577 /** 14578 * Create the DOM element for this class. 14579 * 14580 * @return {Element} 14581 * The element that was created. 14582 */ 14583 14584 14585 MouseTimeDisplay.prototype.createEl = function createEl() { 14586 return _Component.prototype.createEl.call(this, 'div', { 14587 className: 'vjs-mouse-display' 14588 }); 14589 }; 14590 14591 /** 14592 * Enqueues updates to its own DOM as well as the DOM of its 14593 * {@link TimeTooltip} child. 14594 * 14595 * @param {Object} seekBarRect 14596 * The `ClientRect` for the {@link SeekBar} element. 14597 * 14598 * @param {number} seekBarPoint 14599 * A number from 0 to 1, representing a horizontal reference point 14600 * from the left edge of the {@link SeekBar} 14601 */ 14602 14603 14604 MouseTimeDisplay.prototype.update = function update(seekBarRect, seekBarPoint) { 14605 var _this2 = this; 14606 14607 // If there is an existing rAF ID, cancel it so we don't over-queue. 14608 if (this.rafId_) { 14609 this.cancelAnimationFrame(this.rafId_); 14610 } 14611 14612 this.rafId_ = this.requestAnimationFrame(function () { 14613 var duration = _this2.player_.duration(); 14614 var content = formatTime(seekBarPoint * duration, duration); 14615 14616 _this2.el_.style.left = seekBarRect.width * seekBarPoint + 'px'; 14617 _this2.getChild('timeTooltip').update(seekBarRect, seekBarPoint, content); 14618 }); 14619 }; 14620 14621 return MouseTimeDisplay; 14622}(Component); 14623 14624/** 14625 * Default options for `MouseTimeDisplay` 14626 * 14627 * @type {Object} 14628 * @private 14629 */ 14630 14631 14632MouseTimeDisplay.prototype.options_ = { 14633 children: ['timeTooltip'] 14634}; 14635 14636Component.registerComponent('MouseTimeDisplay', MouseTimeDisplay); 14637 14638/** 14639 * @file seek-bar.js 14640 */ 14641// The number of seconds the `step*` functions move the timeline. 14642var STEP_SECONDS = 5; 14643 14644// The interval at which the bar should update as it progresses. 14645var UPDATE_REFRESH_INTERVAL = 30; 14646 14647/** 14648 * Seek bar and container for the progress bars. Uses {@link PlayProgressBar} 14649 * as its `bar`. 14650 * 14651 * @extends Slider 14652 */ 14653 14654var SeekBar = function (_Slider) { 14655 inherits(SeekBar, _Slider); 14656 14657 /** 14658 * Creates an instance of this class. 14659 * 14660 * @param {Player} player 14661 * The `Player` that this class should be attached to. 14662 * 14663 * @param {Object} [options] 14664 * The key/value store of player options. 14665 */ 14666 function SeekBar(player, options) { 14667 classCallCheck(this, SeekBar); 14668 14669 var _this = possibleConstructorReturn(this, _Slider.call(this, player, options)); 14670 14671 _this.setEventHandlers_(); 14672 return _this; 14673 } 14674 14675 /** 14676 * Sets the event handlers 14677 * 14678 * @private 14679 */ 14680 14681 14682 SeekBar.prototype.setEventHandlers_ = function setEventHandlers_() { 14683 var _this2 = this; 14684 14685 this.update = throttle(bind(this, this.update), UPDATE_REFRESH_INTERVAL); 14686 14687 this.on(this.player_, 'timeupdate', this.update); 14688 this.on(this.player_, 'ended', this.handleEnded); 14689 14690 // when playing, let's ensure we smoothly update the play progress bar 14691 // via an interval 14692 this.updateInterval = null; 14693 14694 this.on(this.player_, ['playing'], function () { 14695 _this2.clearInterval(_this2.updateInterval); 14696 14697 _this2.updateInterval = _this2.setInterval(function () { 14698 _this2.requestAnimationFrame(function () { 14699 _this2.update(); 14700 }); 14701 }, UPDATE_REFRESH_INTERVAL); 14702 }); 14703 14704 this.on(this.player_, ['ended', 'pause', 'waiting'], function () { 14705 _this2.clearInterval(_this2.updateInterval); 14706 }); 14707 14708 this.on(this.player_, ['timeupdate', 'ended'], this.update); 14709 }; 14710 14711 /** 14712 * Create the `Component`'s DOM element 14713 * 14714 * @return {Element} 14715 * The element that was created. 14716 */ 14717 14718 14719 SeekBar.prototype.createEl = function createEl$$1() { 14720 return _Slider.prototype.createEl.call(this, 'div', { 14721 className: 'vjs-progress-holder' 14722 }, { 14723 'aria-label': this.localize('Progress Bar') 14724 }); 14725 }; 14726 14727 /** 14728 * This function updates the play progress bar and accessiblity 14729 * attributes to whatever is passed in. 14730 * 14731 * @param {number} currentTime 14732 * The currentTime value that should be used for accessiblity 14733 * 14734 * @param {number} percent 14735 * The percentage as a decimal that the bar should be filled from 0-1. 14736 * 14737 * @private 14738 */ 14739 14740 14741 SeekBar.prototype.update_ = function update_(currentTime, percent) { 14742 var duration = this.player_.duration(); 14743 14744 // machine readable value of progress bar (percentage complete) 14745 this.el_.setAttribute('aria-valuenow', (percent * 100).toFixed(2)); 14746 14747 // human readable value of progress bar (time complete) 14748 this.el_.setAttribute('aria-valuetext', this.localize('progress bar timing: currentTime={1} duration={2}', [formatTime(currentTime, duration), formatTime(duration, duration)], '{1} of {2}')); 14749 14750 // Update the `PlayProgressBar`. 14751 this.bar.update(getBoundingClientRect(this.el_), percent); 14752 }; 14753 14754 /** 14755 * Update the seek bar's UI. 14756 * 14757 * @param {EventTarget~Event} [event] 14758 * The `timeupdate` or `ended` event that caused this to run. 14759 * 14760 * @listens Player#timeupdate 14761 * 14762 * @returns {number} 14763 * The current percent at a number from 0-1 14764 */ 14765 14766 14767 SeekBar.prototype.update = function update(event) { 14768 var percent = _Slider.prototype.update.call(this); 14769 14770 this.update_(this.getCurrentTime_(), percent); 14771 return percent; 14772 }; 14773 14774 /** 14775 * Get the value of current time but allows for smooth scrubbing, 14776 * when player can't keep up. 14777 * 14778 * @return {number} 14779 * The current time value to display 14780 * 14781 * @private 14782 */ 14783 14784 14785 SeekBar.prototype.getCurrentTime_ = function getCurrentTime_() { 14786 return this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime(); 14787 }; 14788 14789 /** 14790 * We want the seek bar to be full on ended 14791 * no matter what the actual internal values are. so we force it. 14792 * 14793 * @param {EventTarget~Event} [event] 14794 * The `timeupdate` or `ended` event that caused this to run. 14795 * 14796 * @listens Player#ended 14797 */ 14798 14799 14800 SeekBar.prototype.handleEnded = function handleEnded(event) { 14801 this.update_(this.player_.duration(), 1); 14802 }; 14803 14804 /** 14805 * Get the percentage of media played so far. 14806 * 14807 * @return {number} 14808 * The percentage of media played so far (0 to 1). 14809 */ 14810 14811 14812 SeekBar.prototype.getPercent = function getPercent() { 14813 var percent = this.getCurrentTime_() / this.player_.duration(); 14814 14815 return percent >= 1 ? 1 : percent; 14816 }; 14817 14818 /** 14819 * Handle mouse down on seek bar 14820 * 14821 * @param {EventTarget~Event} event 14822 * The `mousedown` event that caused this to run. 14823 * 14824 * @listens mousedown 14825 */ 14826 14827 14828 SeekBar.prototype.handleMouseDown = function handleMouseDown(event) { 14829 if (!isSingleLeftClick(event)) { 14830 return; 14831 } 14832 14833 // Stop event propagation to prevent double fire in progress-control.js 14834 event.stopPropagation(); 14835 this.player_.scrubbing(true); 14836 14837 this.videoWasPlaying = !this.player_.paused(); 14838 this.player_.pause(); 14839 14840 _Slider.prototype.handleMouseDown.call(this, event); 14841 }; 14842 14843 /** 14844 * Handle mouse move on seek bar 14845 * 14846 * @param {EventTarget~Event} event 14847 * The `mousemove` event that caused this to run. 14848 * 14849 * @listens mousemove 14850 */ 14851 14852 14853 SeekBar.prototype.handleMouseMove = function handleMouseMove(event) { 14854 if (!isSingleLeftClick(event)) { 14855 return; 14856 } 14857 14858 var newTime = this.calculateDistance(event) * this.player_.duration(); 14859 14860 // Don't let video end while scrubbing. 14861 if (newTime === this.player_.duration()) { 14862 newTime = newTime - 0.1; 14863 } 14864 14865 // Set new time (tell player to seek to new time) 14866 this.player_.currentTime(newTime); 14867 }; 14868 14869 SeekBar.prototype.enable = function enable() { 14870 _Slider.prototype.enable.call(this); 14871 var mouseTimeDisplay = this.getChild('mouseTimeDisplay'); 14872 14873 if (!mouseTimeDisplay) { 14874 return; 14875 } 14876 14877 mouseTimeDisplay.show(); 14878 }; 14879 14880 SeekBar.prototype.disable = function disable() { 14881 _Slider.prototype.disable.call(this); 14882 var mouseTimeDisplay = this.getChild('mouseTimeDisplay'); 14883 14884 if (!mouseTimeDisplay) { 14885 return; 14886 } 14887 14888 mouseTimeDisplay.hide(); 14889 }; 14890 14891 /** 14892 * Handle mouse up on seek bar 14893 * 14894 * @param {EventTarget~Event} event 14895 * The `mouseup` event that caused this to run. 14896 * 14897 * @listens mouseup 14898 */ 14899 14900 14901 SeekBar.prototype.handleMouseUp = function handleMouseUp(event) { 14902 _Slider.prototype.handleMouseUp.call(this, event); 14903 14904 // Stop event propagation to prevent double fire in progress-control.js 14905 if (event) { 14906 event.stopPropagation(); 14907 } 14908 this.player_.scrubbing(false); 14909 14910 /** 14911 * Trigger timeupdate because we're done seeking and the time has changed. 14912 * This is particularly useful for if the player is paused to time the time displays. 14913 * 14914 * @event Tech#timeupdate 14915 * @type {EventTarget~Event} 14916 */ 14917 this.player_.trigger({ type: 'timeupdate', target: this, manuallyTriggered: true }); 14918 if (this.videoWasPlaying) { 14919 silencePromise(this.player_.play()); 14920 } 14921 }; 14922 14923 /** 14924 * Move more quickly fast forward for keyboard-only users 14925 */ 14926 14927 14928 SeekBar.prototype.stepForward = function stepForward() { 14929 this.player_.currentTime(this.player_.currentTime() + STEP_SECONDS); 14930 }; 14931 14932 /** 14933 * Move more quickly rewind for keyboard-only users 14934 */ 14935 14936 14937 SeekBar.prototype.stepBack = function stepBack() { 14938 this.player_.currentTime(this.player_.currentTime() - STEP_SECONDS); 14939 }; 14940 14941 /** 14942 * Toggles the playback state of the player 14943 * This gets called when enter or space is used on the seekbar 14944 * 14945 * @param {EventTarget~Event} event 14946 * The `keydown` event that caused this function to be called 14947 * 14948 */ 14949 14950 14951 SeekBar.prototype.handleAction = function handleAction(event) { 14952 if (this.player_.paused()) { 14953 this.player_.play(); 14954 } else { 14955 this.player_.pause(); 14956 } 14957 }; 14958 14959 /** 14960 * Called when this SeekBar has focus and a key gets pressed down. By 14961 * default it will call `this.handleAction` when the key is space or enter. 14962 * 14963 * @param {EventTarget~Event} event 14964 * The `keydown` event that caused this function to be called. 14965 * 14966 * @listens keydown 14967 */ 14968 14969 14970 SeekBar.prototype.handleKeyPress = function handleKeyPress(event) { 14971 14972 // Support Space (32) or Enter (13) key operation to fire a click event 14973 if (event.which === 32 || event.which === 13) { 14974 event.preventDefault(); 14975 this.handleAction(event); 14976 } else if (_Slider.prototype.handleKeyPress) { 14977 14978 // Pass keypress handling up for unsupported keys 14979 _Slider.prototype.handleKeyPress.call(this, event); 14980 } 14981 }; 14982 14983 return SeekBar; 14984}(Slider); 14985 14986/** 14987 * Default options for the `SeekBar` 14988 * 14989 * @type {Object} 14990 * @private 14991 */ 14992 14993 14994SeekBar.prototype.options_ = { 14995 children: ['loadProgressBar', 'playProgressBar'], 14996 barName: 'playProgressBar' 14997}; 14998 14999// MouseTimeDisplay tooltips should not be added to a player on mobile devices or IE8 15000if ((!IE_VERSION || IE_VERSION > 8) && !IS_IOS && !IS_ANDROID) { 15001 SeekBar.prototype.options_.children.splice(1, 0, 'mouseTimeDisplay'); 15002} 15003 15004/** 15005 * Call the update event for this Slider when this event happens on the player. 15006 * 15007 * @type {string} 15008 */ 15009SeekBar.prototype.playerEvent = 'timeupdate'; 15010 15011Component.registerComponent('SeekBar', SeekBar); 15012 15013/** 15014 * @file progress-control.js 15015 */ 15016/** 15017 * The Progress Control component contains the seek bar, load progress, 15018 * and play progress. 15019 * 15020 * @extends Component 15021 */ 15022 15023var ProgressControl = function (_Component) { 15024 inherits(ProgressControl, _Component); 15025 15026 /** 15027 * Creates an instance of this class. 15028 * 15029 * @param {Player} player 15030 * The `Player` that this class should be attached to. 15031 * 15032 * @param {Object} [options] 15033 * The key/value store of player options. 15034 */ 15035 function ProgressControl(player, options) { 15036 classCallCheck(this, ProgressControl); 15037 15038 var _this = possibleConstructorReturn(this, _Component.call(this, player, options)); 15039 15040 _this.handleMouseMove = throttle(bind(_this, _this.handleMouseMove), 25); 15041 _this.throttledHandleMouseSeek = throttle(bind(_this, _this.handleMouseSeek), 25); 15042 15043 _this.enable(); 15044 return _this; 15045 } 15046 15047 /** 15048 * Create the `Component`'s DOM element 15049 * 15050 * @return {Element} 15051 * The element that was created. 15052 */ 15053 15054 15055 ProgressControl.prototype.createEl = function createEl$$1() { 15056 return _Component.prototype.createEl.call(this, 'div', { 15057 className: 'vjs-progress-control vjs-control' 15058 }); 15059 }; 15060 15061 /** 15062 * When the mouse moves over the `ProgressControl`, the pointer position 15063 * gets passed down to the `MouseTimeDisplay` component. 15064 * 15065 * @param {EventTarget~Event} event 15066 * The `mousemove` event that caused this function to run. 15067 * 15068 * @listen mousemove 15069 */ 15070 15071 15072 ProgressControl.prototype.handleMouseMove = function handleMouseMove(event) { 15073 var seekBar = this.getChild('seekBar'); 15074 15075 if (seekBar) { 15076 var mouseTimeDisplay = seekBar.getChild('mouseTimeDisplay'); 15077 var seekBarEl = seekBar.el(); 15078 var seekBarRect = getBoundingClientRect(seekBarEl); 15079 var seekBarPoint = getPointerPosition(seekBarEl, event).x; 15080 15081 // The default skin has a gap on either side of the `SeekBar`. This means 15082 // that it's possible to trigger this behavior outside the boundaries of 15083 // the `SeekBar`. This ensures we stay within it at all times. 15084 if (seekBarPoint > 1) { 15085 seekBarPoint = 1; 15086 } else if (seekBarPoint < 0) { 15087 seekBarPoint = 0; 15088 } 15089 15090 if (mouseTimeDisplay) { 15091 mouseTimeDisplay.update(seekBarRect, seekBarPoint); 15092 } 15093 } 15094 }; 15095 15096 /** 15097 * A throttled version of the {@link ProgressControl#handleMouseSeek} listener. 15098 * 15099 * @method ProgressControl#throttledHandleMouseSeek 15100 * @param {EventTarget~Event} event 15101 * The `mousemove` event that caused this function to run. 15102 * 15103 * @listen mousemove 15104 * @listen touchmove 15105 */ 15106 15107 /** 15108 * Handle `mousemove` or `touchmove` events on the `ProgressControl`. 15109 * 15110 * @param {EventTarget~Event} event 15111 * `mousedown` or `touchstart` event that triggered this function 15112 * 15113 * @listens mousemove 15114 * @listens touchmove 15115 */ 15116 15117 15118 ProgressControl.prototype.handleMouseSeek = function handleMouseSeek(event) { 15119 var seekBar = this.getChild('seekBar'); 15120 15121 if (seekBar) { 15122 seekBar.handleMouseMove(event); 15123 } 15124 }; 15125 15126 /** 15127 * Are controls are currently enabled for this progress control. 15128 * 15129 * @return {boolean} 15130 * true if controls are enabled, false otherwise 15131 */ 15132 15133 15134 ProgressControl.prototype.enabled = function enabled() { 15135 return this.enabled_; 15136 }; 15137 15138 /** 15139 * Disable all controls on the progress control and its children 15140 */ 15141 15142 15143 ProgressControl.prototype.disable = function disable() { 15144 this.children().forEach(function (child) { 15145 return child.disable && child.disable(); 15146 }); 15147 15148 if (!this.enabled()) { 15149 return; 15150 } 15151 15152 this.off(['mousedown', 'touchstart'], this.handleMouseDown); 15153 this.off(this.el_, 'mousemove', this.handleMouseMove); 15154 this.handleMouseUp(); 15155 15156 this.addClass('disabled'); 15157 15158 this.enabled_ = false; 15159 }; 15160 15161 /** 15162 * Enable all controls on the progress control and its children 15163 */ 15164 15165 15166 ProgressControl.prototype.enable = function enable() { 15167 this.children().forEach(function (child) { 15168 return child.enable && child.enable(); 15169 }); 15170 15171 if (this.enabled()) { 15172 return; 15173 } 15174 15175 this.on(['mousedown', 'touchstart'], this.handleMouseDown); 15176 this.on(this.el_, 'mousemove', this.handleMouseMove); 15177 this.removeClass('disabled'); 15178 15179 this.enabled_ = true; 15180 }; 15181 15182 /** 15183 * Handle `mousedown` or `touchstart` events on the `ProgressControl`. 15184 * 15185 * @param {EventTarget~Event} event 15186 * `mousedown` or `touchstart` event that triggered this function 15187 * 15188 * @listens mousedown 15189 * @listens touchstart 15190 */ 15191 15192 15193 ProgressControl.prototype.handleMouseDown = function handleMouseDown(event) { 15194 var doc = this.el_.ownerDocument; 15195 var seekBar = this.getChild('seekBar'); 15196 15197 if (seekBar) { 15198 seekBar.handleMouseDown(event); 15199 } 15200 15201 this.on(doc, 'mousemove', this.throttledHandleMouseSeek); 15202 this.on(doc, 'touchmove', this.throttledHandleMouseSeek); 15203 this.on(doc, 'mouseup', this.handleMouseUp); 15204 this.on(doc, 'touchend', this.handleMouseUp); 15205 }; 15206 15207 /** 15208 * Handle `mouseup` or `touchend` events on the `ProgressControl`. 15209 * 15210 * @param {EventTarget~Event} event 15211 * `mouseup` or `touchend` event that triggered this function. 15212 * 15213 * @listens touchend 15214 * @listens mouseup 15215 */ 15216 15217 15218 ProgressControl.prototype.handleMouseUp = function handleMouseUp(event) { 15219 var doc = this.el_.ownerDocument; 15220 var seekBar = this.getChild('seekBar'); 15221 15222 if (seekBar) { 15223 seekBar.handleMouseUp(event); 15224 } 15225 15226 this.off(doc, 'mousemove', this.throttledHandleMouseSeek); 15227 this.off(doc, 'touchmove', this.throttledHandleMouseSeek); 15228 this.off(doc, 'mouseup', this.handleMouseUp); 15229 this.off(doc, 'touchend', this.handleMouseUp); 15230 }; 15231 15232 return ProgressControl; 15233}(Component); 15234 15235/** 15236 * Default options for `ProgressControl` 15237 * 15238 * @type {Object} 15239 * @private 15240 */ 15241 15242 15243ProgressControl.prototype.options_ = { 15244 children: ['seekBar'] 15245}; 15246 15247Component.registerComponent('ProgressControl', ProgressControl); 15248 15249/** 15250 * @file fullscreen-toggle.js 15251 */ 15252/** 15253 * Toggle fullscreen video 15254 * 15255 * @extends Button 15256 */ 15257 15258var FullscreenToggle = function (_Button) { 15259 inherits(FullscreenToggle, _Button); 15260 15261 /** 15262 * Creates an instance of this class. 15263 * 15264 * @param {Player} player 15265 * The `Player` that this class should be attached to. 15266 * 15267 * @param {Object} [options] 15268 * The key/value store of player options. 15269 */ 15270 function FullscreenToggle(player, options) { 15271 classCallCheck(this, FullscreenToggle); 15272 15273 var _this = possibleConstructorReturn(this, _Button.call(this, player, options)); 15274 15275 _this.on(player, 'fullscreenchange', _this.handleFullscreenChange); 15276 15277 if (document_1[FullscreenApi.fullscreenEnabled] === false) { 15278 _this.disable(); 15279 } 15280 return _this; 15281 } 15282 15283 /** 15284 * Builds the default DOM `className`. 15285 * 15286 * @return {string} 15287 * The DOM `className` for this object. 15288 */ 15289 15290 15291 FullscreenToggle.prototype.buildCSSClass = function buildCSSClass() { 15292 return 'vjs-fullscreen-control ' + _Button.prototype.buildCSSClass.call(this); 15293 }; 15294 15295 /** 15296 * Handles fullscreenchange on the player and change control text accordingly. 15297 * 15298 * @param {EventTarget~Event} [event] 15299 * The {@link Player#fullscreenchange} event that caused this function to be 15300 * called. 15301 * 15302 * @listens Player#fullscreenchange 15303 */ 15304 15305 15306 FullscreenToggle.prototype.handleFullscreenChange = function handleFullscreenChange(event) { 15307 if (this.player_.isFullscreen()) { 15308 this.controlText('Non-Fullscreen'); 15309 } else { 15310 this.controlText('Fullscreen'); 15311 } 15312 }; 15313 15314 /** 15315 * This gets called when an `FullscreenToggle` is "clicked". See 15316 * {@link ClickableComponent} for more detailed information on what a click can be. 15317 * 15318 * @param {EventTarget~Event} [event] 15319 * The `keydown`, `tap`, or `click` event that caused this function to be 15320 * called. 15321 * 15322 * @listens tap 15323 * @listens click 15324 */ 15325 15326 15327 FullscreenToggle.prototype.handleClick = function handleClick(event) { 15328 if (!this.player_.isFullscreen()) { 15329 this.player_.requestFullscreen(); 15330 } else { 15331 this.player_.exitFullscreen(); 15332 } 15333 }; 15334 15335 return FullscreenToggle; 15336}(Button); 15337 15338/** 15339 * The text that should display over the `FullscreenToggle`s controls. Added for localization. 15340 * 15341 * @type {string} 15342 * @private 15343 */ 15344 15345 15346FullscreenToggle.prototype.controlText_ = 'Fullscreen'; 15347 15348Component.registerComponent('FullscreenToggle', FullscreenToggle); 15349 15350/** 15351 * Check if volume control is supported and if it isn't hide the 15352 * `Component` that was passed using the `vjs-hidden` class. 15353 * 15354 * @param {Component} self 15355 * The component that should be hidden if volume is unsupported 15356 * 15357 * @param {Player} player 15358 * A reference to the player 15359 * 15360 * @private 15361 */ 15362var checkVolumeSupport = function checkVolumeSupport(self, player) { 15363 // hide volume controls when they're not supported by the current tech 15364 if (player.tech_ && !player.tech_.featuresVolumeControl) { 15365 self.addClass('vjs-hidden'); 15366 } 15367 15368 self.on(player, 'loadstart', function () { 15369 if (!player.tech_.featuresVolumeControl) { 15370 self.addClass('vjs-hidden'); 15371 } else { 15372 self.removeClass('vjs-hidden'); 15373 } 15374 }); 15375}; 15376 15377/** 15378 * @file volume-level.js 15379 */ 15380/** 15381 * Shows volume level 15382 * 15383 * @extends Component 15384 */ 15385 15386var VolumeLevel = function (_Component) { 15387 inherits(VolumeLevel, _Component); 15388 15389 function VolumeLevel() { 15390 classCallCheck(this, VolumeLevel); 15391 return possibleConstructorReturn(this, _Component.apply(this, arguments)); 15392 } 15393 15394 /** 15395 * Create the `Component`'s DOM element 15396 * 15397 * @return {Element} 15398 * The element that was created. 15399 */ 15400 VolumeLevel.prototype.createEl = function createEl() { 15401 return _Component.prototype.createEl.call(this, 'div', { 15402 className: 'vjs-volume-level', 15403 innerHTML: '<span class="vjs-control-text"></span>' 15404 }); 15405 }; 15406 15407 return VolumeLevel; 15408}(Component); 15409 15410Component.registerComponent('VolumeLevel', VolumeLevel); 15411 15412/** 15413 * @file volume-bar.js 15414 */ 15415// Required children 15416/** 15417 * The bar that contains the volume level and can be clicked on to adjust the level 15418 * 15419 * @extends Slider 15420 */ 15421 15422var VolumeBar = function (_Slider) { 15423 inherits(VolumeBar, _Slider); 15424 15425 /** 15426 * Creates an instance of this class. 15427 * 15428 * @param {Player} player 15429 * The `Player` that this class should be attached to. 15430 * 15431 * @param {Object} [options] 15432 * The key/value store of player options. 15433 */ 15434 function VolumeBar(player, options) { 15435 classCallCheck(this, VolumeBar); 15436 15437 var _this = possibleConstructorReturn(this, _Slider.call(this, player, options)); 15438 15439 _this.on('slideractive', _this.updateLastVolume_); 15440 _this.on(player, 'volumechange', _this.updateARIAAttributes); 15441 player.ready(function () { 15442 return _this.updateARIAAttributes(); 15443 }); 15444 return _this; 15445 } 15446 15447 /** 15448 * Create the `Component`'s DOM element 15449 * 15450 * @return {Element} 15451 * The element that was created. 15452 */ 15453 15454 15455 VolumeBar.prototype.createEl = function createEl$$1() { 15456 return _Slider.prototype.createEl.call(this, 'div', { 15457 className: 'vjs-volume-bar vjs-slider-bar' 15458 }, { 15459 'aria-label': this.localize('Volume Level'), 15460 'aria-live': 'polite' 15461 }); 15462 }; 15463 15464 /** 15465 * Handle mouse down on volume bar 15466 * 15467 * @param {EventTarget~Event} event 15468 * The `mousedown` event that caused this to run. 15469 * 15470 * @listens mousedown 15471 */ 15472 15473 15474 VolumeBar.prototype.handleMouseDown = function handleMouseDown(event) { 15475 if (!isSingleLeftClick(event)) { 15476 return; 15477 } 15478 15479 _Slider.prototype.handleMouseDown.call(this, event); 15480 }; 15481 15482 /** 15483 * Handle movement events on the {@link VolumeMenuButton}. 15484 * 15485 * @param {EventTarget~Event} event 15486 * The event that caused this function to run. 15487 * 15488 * @listens mousemove 15489 */ 15490 15491 15492 VolumeBar.prototype.handleMouseMove = function handleMouseMove(event) { 15493 if (!isSingleLeftClick(event)) { 15494 return; 15495 } 15496 15497 this.checkMuted(); 15498 this.player_.volume(this.calculateDistance(event)); 15499 }; 15500 15501 /** 15502 * If the player is muted unmute it. 15503 */ 15504 15505 15506 VolumeBar.prototype.checkMuted = function checkMuted() { 15507 if (this.player_.muted()) { 15508 this.player_.muted(false); 15509 } 15510 }; 15511 15512 /** 15513 * Get percent of volume level 15514 * 15515 * @return {number} 15516 * Volume level percent as a decimal number. 15517 */ 15518 15519 15520 VolumeBar.prototype.getPercent = function getPercent() { 15521 if (this.player_.muted()) { 15522 return 0; 15523 } 15524 return this.player_.volume(); 15525 }; 15526 15527 /** 15528 * Increase volume level for keyboard users 15529 */ 15530 15531 15532 VolumeBar.prototype.stepForward = function stepForward() { 15533 this.checkMuted(); 15534 this.player_.volume(this.player_.volume() + 0.1); 15535 }; 15536 15537 /** 15538 * Decrease volume level for keyboard users 15539 */ 15540 15541 15542 VolumeBar.prototype.stepBack = function stepBack() { 15543 this.checkMuted(); 15544 this.player_.volume(this.player_.volume() - 0.1); 15545 }; 15546 15547 /** 15548 * Update ARIA accessibility attributes 15549 * 15550 * @param {EventTarget~Event} [event] 15551 * The `volumechange` event that caused this function to run. 15552 * 15553 * @listens Player#volumechange 15554 */ 15555 15556 15557 VolumeBar.prototype.updateARIAAttributes = function updateARIAAttributes(event) { 15558 var ariaValue = this.player_.muted() ? 0 : this.volumeAsPercentage_(); 15559 15560 this.el_.setAttribute('aria-valuenow', ariaValue); 15561 this.el_.setAttribute('aria-valuetext', ariaValue + '%'); 15562 }; 15563 15564 /** 15565 * Returns the current value of the player volume as a percentage 15566 * 15567 * @private 15568 */ 15569 15570 15571 VolumeBar.prototype.volumeAsPercentage_ = function volumeAsPercentage_() { 15572 return Math.round(this.player_.volume() * 100); 15573 }; 15574 15575 /** 15576 * When user starts dragging the VolumeBar, store the volume and listen for 15577 * the end of the drag. When the drag ends, if the volume was set to zero, 15578 * set lastVolume to the stored volume. 15579 * 15580 * @listens slideractive 15581 * @private 15582 */ 15583 15584 15585 VolumeBar.prototype.updateLastVolume_ = function updateLastVolume_() { 15586 var _this2 = this; 15587 15588 var volumeBeforeDrag = this.player_.volume(); 15589 15590 this.one('sliderinactive', function () { 15591 if (_this2.player_.volume() === 0) { 15592 _this2.player_.lastVolume_(volumeBeforeDrag); 15593 } 15594 }); 15595 }; 15596 15597 return VolumeBar; 15598}(Slider); 15599 15600/** 15601 * Default options for the `VolumeBar` 15602 * 15603 * @type {Object} 15604 * @private 15605 */ 15606 15607 15608VolumeBar.prototype.options_ = { 15609 children: ['volumeLevel'], 15610 barName: 'volumeLevel' 15611}; 15612 15613/** 15614 * Call the update event for this Slider when this event happens on the player. 15615 * 15616 * @type {string} 15617 */ 15618VolumeBar.prototype.playerEvent = 'volumechange'; 15619 15620Component.registerComponent('VolumeBar', VolumeBar); 15621 15622/** 15623 * @file volume-control.js 15624 */ 15625// Required children 15626/** 15627 * The component for controlling the volume level 15628 * 15629 * @extends Component 15630 */ 15631 15632var VolumeControl = function (_Component) { 15633 inherits(VolumeControl, _Component); 15634 15635 /** 15636 * Creates an instance of this class. 15637 * 15638 * @param {Player} player 15639 * The `Player` that this class should be attached to. 15640 * 15641 * @param {Object} [options={}] 15642 * The key/value store of player options. 15643 */ 15644 function VolumeControl(player) { 15645 var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 15646 classCallCheck(this, VolumeControl); 15647 15648 options.vertical = options.vertical || false; 15649 15650 // Pass the vertical option down to the VolumeBar if 15651 // the VolumeBar is turned on. 15652 if (typeof options.volumeBar === 'undefined' || isPlain(options.volumeBar)) { 15653 options.volumeBar = options.volumeBar || {}; 15654 options.volumeBar.vertical = options.vertical; 15655 } 15656 15657 // hide this control if volume support is missing 15658 var _this = possibleConstructorReturn(this, _Component.call(this, player, options)); 15659 15660 checkVolumeSupport(_this, player); 15661 15662 _this.throttledHandleMouseMove = throttle(bind(_this, _this.handleMouseMove), 25); 15663 15664 _this.on('mousedown', _this.handleMouseDown); 15665 _this.on('touchstart', _this.handleMouseDown); 15666 15667 // while the slider is active (the mouse has been pressed down and 15668 // is dragging) or in focus we do not want to hide the VolumeBar 15669 _this.on(_this.volumeBar, ['focus', 'slideractive'], function () { 15670 _this.volumeBar.addClass('vjs-slider-active'); 15671 _this.addClass('vjs-slider-active'); 15672 _this.trigger('slideractive'); 15673 }); 15674 15675 _this.on(_this.volumeBar, ['blur', 'sliderinactive'], function () { 15676 _this.volumeBar.removeClass('vjs-slider-active'); 15677 _this.removeClass('vjs-slider-active'); 15678 _this.trigger('sliderinactive'); 15679 }); 15680 return _this; 15681 } 15682 15683 /** 15684 * Create the `Component`'s DOM element 15685 * 15686 * @return {Element} 15687 * The element that was created. 15688 */ 15689 15690 15691 VolumeControl.prototype.createEl = function createEl() { 15692 var orientationClass = 'vjs-volume-horizontal'; 15693 15694 if (this.options_.vertical) { 15695 orientationClass = 'vjs-volume-vertical'; 15696 } 15697 15698 return _Component.prototype.createEl.call(this, 'div', { 15699 className: 'vjs-volume-control vjs-control ' + orientationClass 15700 }); 15701 }; 15702 15703 /** 15704 * Handle `mousedown` or `touchstart` events on the `VolumeControl`. 15705 * 15706 * @param {EventTarget~Event} event 15707 * `mousedown` or `touchstart` event that triggered this function 15708 * 15709 * @listens mousedown 15710 * @listens touchstart 15711 */ 15712 15713 15714 VolumeControl.prototype.handleMouseDown = function handleMouseDown(event) { 15715 var doc = this.el_.ownerDocument; 15716 15717 this.on(doc, 'mousemove', this.throttledHandleMouseMove); 15718 this.on(doc, 'touchmove', this.throttledHandleMouseMove); 15719 this.on(doc, 'mouseup', this.handleMouseUp); 15720 this.on(doc, 'touchend', this.handleMouseUp); 15721 }; 15722 15723 /** 15724 * Handle `mouseup` or `touchend` events on the `VolumeControl`. 15725 * 15726 * @param {EventTarget~Event} event 15727 * `mouseup` or `touchend` event that triggered this function. 15728 * 15729 * @listens touchend 15730 * @listens mouseup 15731 */ 15732 15733 15734 VolumeControl.prototype.handleMouseUp = function handleMouseUp(event) { 15735 var doc = this.el_.ownerDocument; 15736 15737 this.off(doc, 'mousemove', this.throttledHandleMouseMove); 15738 this.off(doc, 'touchmove', this.throttledHandleMouseMove); 15739 this.off(doc, 'mouseup', this.handleMouseUp); 15740 this.off(doc, 'touchend', this.handleMouseUp); 15741 }; 15742 15743 /** 15744 * Handle `mousedown` or `touchstart` events on the `VolumeControl`. 15745 * 15746 * @param {EventTarget~Event} event 15747 * `mousedown` or `touchstart` event that triggered this function 15748 * 15749 * @listens mousedown 15750 * @listens touchstart 15751 */ 15752 15753 15754 VolumeControl.prototype.handleMouseMove = function handleMouseMove(event) { 15755 this.volumeBar.handleMouseMove(event); 15756 }; 15757 15758 return VolumeControl; 15759}(Component); 15760 15761/** 15762 * Default options for the `VolumeControl` 15763 * 15764 * @type {Object} 15765 * @private 15766 */ 15767 15768 15769VolumeControl.prototype.options_ = { 15770 children: ['volumeBar'] 15771}; 15772 15773Component.registerComponent('VolumeControl', VolumeControl); 15774 15775/** 15776 * Check if muting volume is supported and if it isn't hide the mute toggle 15777 * button. 15778 * 15779 * @param {Component} self 15780 * A reference to the mute toggle button 15781 * 15782 * @param {Player} player 15783 * A reference to the player 15784 * 15785 * @private 15786 */ 15787var checkMuteSupport = function checkMuteSupport(self, player) { 15788 // hide mute toggle button if it's not supported by the current tech 15789 if (player.tech_ && !player.tech_.featuresMuteControl) { 15790 self.addClass('vjs-hidden'); 15791 } 15792 15793 self.on(player, 'loadstart', function () { 15794 if (!player.tech_.featuresMuteControl) { 15795 self.addClass('vjs-hidden'); 15796 } else { 15797 self.removeClass('vjs-hidden'); 15798 } 15799 }); 15800}; 15801 15802/** 15803 * @file mute-toggle.js 15804 */ 15805/** 15806 * A button component for muting the audio. 15807 * 15808 * @extends Button 15809 */ 15810 15811var MuteToggle = function (_Button) { 15812 inherits(MuteToggle, _Button); 15813 15814 /** 15815 * Creates an instance of this class. 15816 * 15817 * @param {Player} player 15818 * The `Player` that this class should be attached to. 15819 * 15820 * @param {Object} [options] 15821 * The key/value store of player options. 15822 */ 15823 function MuteToggle(player, options) { 15824 classCallCheck(this, MuteToggle); 15825 15826 // hide this control if volume support is missing 15827 var _this = possibleConstructorReturn(this, _Button.call(this, player, options)); 15828 15829 checkMuteSupport(_this, player); 15830 15831 _this.on(player, ['loadstart', 'volumechange'], _this.update); 15832 return _this; 15833 } 15834 15835 /** 15836 * Builds the default DOM `className`. 15837 * 15838 * @return {string} 15839 * The DOM `className` for this object. 15840 */ 15841 15842 15843 MuteToggle.prototype.buildCSSClass = function buildCSSClass() { 15844 return 'vjs-mute-control ' + _Button.prototype.buildCSSClass.call(this); 15845 }; 15846 15847 /** 15848 * This gets called when an `MuteToggle` is "clicked". See 15849 * {@link ClickableComponent} for more detailed information on what a click can be. 15850 * 15851 * @param {EventTarget~Event} [event] 15852 * The `keydown`, `tap`, or `click` event that caused this function to be 15853 * called. 15854 * 15855 * @listens tap 15856 * @listens click 15857 */ 15858 15859 15860 MuteToggle.prototype.handleClick = function handleClick(event) { 15861 var vol = this.player_.volume(); 15862 var lastVolume = this.player_.lastVolume_(); 15863 15864 if (vol === 0) { 15865 var volumeToSet = lastVolume < 0.1 ? 0.1 : lastVolume; 15866 15867 this.player_.volume(volumeToSet); 15868 this.player_.muted(false); 15869 } else { 15870 this.player_.muted(this.player_.muted() ? false : true); 15871 } 15872 }; 15873 15874 /** 15875 * Update the `MuteToggle` button based on the state of `volume` and `muted` 15876 * on the player. 15877 * 15878 * @param {EventTarget~Event} [event] 15879 * The {@link Player#loadstart} event if this function was called 15880 * through an event. 15881 * 15882 * @listens Player#loadstart 15883 * @listens Player#volumechange 15884 */ 15885 15886 15887 MuteToggle.prototype.update = function update(event) { 15888 this.updateIcon_(); 15889 this.updateControlText_(); 15890 }; 15891 15892 /** 15893 * Update the appearance of the `MuteToggle` icon. 15894 * 15895 * Possible states (given `level` variable below): 15896 * - 0: crossed out 15897 * - 1: zero bars of volume 15898 * - 2: one bar of volume 15899 * - 3: two bars of volume 15900 * 15901 * @private 15902 */ 15903 15904 15905 MuteToggle.prototype.updateIcon_ = function updateIcon_() { 15906 var vol = this.player_.volume(); 15907 var level = 3; 15908 15909 // in iOS when a player is loaded with muted attribute 15910 // and volume is changed with a native mute button 15911 // we want to make sure muted state is updated 15912 if (IS_IOS) { 15913 this.player_.muted(this.player_.tech_.el_.muted); 15914 } 15915 15916 if (vol === 0 || this.player_.muted()) { 15917 level = 0; 15918 } else if (vol < 0.33) { 15919 level = 1; 15920 } else if (vol < 0.67) { 15921 level = 2; 15922 } 15923 15924 // TODO improve muted icon classes 15925 for (var i = 0; i < 4; i++) { 15926 removeClass(this.el_, 'vjs-vol-' + i); 15927 } 15928 addClass(this.el_, 'vjs-vol-' + level); 15929 }; 15930 15931 /** 15932 * If `muted` has changed on the player, update the control text 15933 * (`title` attribute on `vjs-mute-control` element and content of 15934 * `vjs-control-text` element). 15935 * 15936 * @private 15937 */ 15938 15939 15940 MuteToggle.prototype.updateControlText_ = function updateControlText_() { 15941 var soundOff = this.player_.muted() || this.player_.volume() === 0; 15942 var text = soundOff ? 'Unmute' : 'Mute'; 15943 15944 if (this.controlText() !== text) { 15945 this.controlText(text); 15946 } 15947 }; 15948 15949 return MuteToggle; 15950}(Button); 15951 15952/** 15953 * The text that should display over the `MuteToggle`s controls. Added for localization. 15954 * 15955 * @type {string} 15956 * @private 15957 */ 15958 15959 15960MuteToggle.prototype.controlText_ = 'Mute'; 15961 15962Component.registerComponent('MuteToggle', MuteToggle); 15963 15964/** 15965 * @file volume-control.js 15966 */ 15967// Required children 15968/** 15969 * A Component to contain the MuteToggle and VolumeControl so that 15970 * they can work together. 15971 * 15972 * @extends Component 15973 */ 15974 15975var VolumePanel = function (_Component) { 15976 inherits(VolumePanel, _Component); 15977 15978 /** 15979 * Creates an instance of this class. 15980 * 15981 * @param {Player} player 15982 * The `Player` that this class should be attached to. 15983 * 15984 * @param {Object} [options={}] 15985 * The key/value store of player options. 15986 */ 15987 function VolumePanel(player) { 15988 var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 15989 classCallCheck(this, VolumePanel); 15990 15991 if (typeof options.inline !== 'undefined') { 15992 options.inline = options.inline; 15993 } else { 15994 options.inline = true; 15995 } 15996 15997 // pass the inline option down to the VolumeControl as vertical if 15998 // the VolumeControl is on. 15999 if (typeof options.volumeControl === 'undefined' || isPlain(options.volumeControl)) { 16000 options.volumeControl = options.volumeControl || {}; 16001 options.volumeControl.vertical = !options.inline; 16002 } 16003 16004 var _this = possibleConstructorReturn(this, _Component.call(this, player, options)); 16005 16006 _this.on(player, ['loadstart'], _this.volumePanelState_); 16007 16008 // while the slider is active (the mouse has been pressed down and 16009 // is dragging) we do not want to hide the VolumeBar 16010 _this.on(_this.volumeControl, ['slideractive'], _this.sliderActive_); 16011 16012 _this.on(_this.volumeControl, ['sliderinactive'], _this.sliderInactive_); 16013 return _this; 16014 } 16015 16016 /** 16017 * Add vjs-slider-active class to the VolumePanel 16018 * 16019 * @listens VolumeControl#slideractive 16020 * @private 16021 */ 16022 16023 16024 VolumePanel.prototype.sliderActive_ = function sliderActive_() { 16025 this.addClass('vjs-slider-active'); 16026 }; 16027 16028 /** 16029 * Removes vjs-slider-active class to the VolumePanel 16030 * 16031 * @listens VolumeControl#sliderinactive 16032 * @private 16033 */ 16034 16035 16036 VolumePanel.prototype.sliderInactive_ = function sliderInactive_() { 16037 this.removeClass('vjs-slider-active'); 16038 }; 16039 16040 /** 16041 * Adds vjs-hidden or vjs-mute-toggle-only to the VolumePanel 16042 * depending on MuteToggle and VolumeControl state 16043 * 16044 * @listens Player#loadstart 16045 * @private 16046 */ 16047 16048 16049 VolumePanel.prototype.volumePanelState_ = function volumePanelState_() { 16050 // hide volume panel if neither volume control or mute toggle 16051 // are displayed 16052 if (this.volumeControl.hasClass('vjs-hidden') && this.muteToggle.hasClass('vjs-hidden')) { 16053 this.addClass('vjs-hidden'); 16054 } 16055 16056 // if only mute toggle is visible we don't want 16057 // volume panel expanding when hovered or active 16058 if (this.volumeControl.hasClass('vjs-hidden') && !this.muteToggle.hasClass('vjs-hidden')) { 16059 this.addClass('vjs-mute-toggle-only'); 16060 } 16061 }; 16062 16063 /** 16064 * Create the `Component`'s DOM element 16065 * 16066 * @return {Element} 16067 * The element that was created. 16068 */ 16069 16070 16071 VolumePanel.prototype.createEl = function createEl() { 16072 var orientationClass = 'vjs-volume-panel-horizontal'; 16073 16074 if (!this.options_.inline) { 16075 orientationClass = 'vjs-volume-panel-vertical'; 16076 } 16077 16078 return _Component.prototype.createEl.call(this, 'div', { 16079 className: 'vjs-volume-panel vjs-control ' + orientationClass 16080 }); 16081 }; 16082 16083 return VolumePanel; 16084}(Component); 16085 16086/** 16087 * Default options for the `VolumeControl` 16088 * 16089 * @type {Object} 16090 * @private 16091 */ 16092 16093 16094VolumePanel.prototype.options_ = { 16095 children: ['muteToggle', 'volumeControl'] 16096}; 16097 16098Component.registerComponent('VolumePanel', VolumePanel); 16099 16100/** 16101 * @file menu.js 16102 */ 16103/** 16104 * The Menu component is used to build popup menus, including subtitle and 16105 * captions selection menus. 16106 * 16107 * @extends Component 16108 */ 16109 16110var Menu = function (_Component) { 16111 inherits(Menu, _Component); 16112 16113 /** 16114 * Create an instance of this class. 16115 * 16116 * @param {Player} player 16117 * the player that this component should attach to 16118 * 16119 * @param {Object} [options] 16120 * Object of option names and values 16121 * 16122 */ 16123 function Menu(player, options) { 16124 classCallCheck(this, Menu); 16125 16126 var _this = possibleConstructorReturn(this, _Component.call(this, player, options)); 16127 16128 if (options) { 16129 _this.menuButton_ = options.menuButton; 16130 } 16131 16132 _this.focusedChild_ = -1; 16133 16134 _this.on('keydown', _this.handleKeyPress); 16135 return _this; 16136 } 16137 16138 /** 16139 * Add a {@link MenuItem} to the menu. 16140 * 16141 * @param {Object|string} component 16142 * The name or instance of the `MenuItem` to add. 16143 * 16144 */ 16145 16146 16147 Menu.prototype.addItem = function addItem(component) { 16148 this.addChild(component); 16149 component.on('click', bind(this, function (event) { 16150 // Unpress the associated MenuButton, and move focus back to it 16151 if (this.menuButton_) { 16152 this.menuButton_.unpressButton(); 16153 16154 // don't focus menu button if item is a caption settings item 16155 // because focus will move elsewhere and it logs an error on IE8 16156 if (component.name() !== 'CaptionSettingsMenuItem') { 16157 this.menuButton_.focus(); 16158 } 16159 } 16160 })); 16161 }; 16162 16163 /** 16164 * Create the `Menu`s DOM element. 16165 * 16166 * @return {Element} 16167 * the element that was created 16168 */ 16169 16170 16171 Menu.prototype.createEl = function createEl$$1() { 16172 var contentElType = this.options_.contentElType || 'ul'; 16173 16174 this.contentEl_ = createEl(contentElType, { 16175 className: 'vjs-menu-content' 16176 }); 16177 16178 this.contentEl_.setAttribute('role', 'menu'); 16179 16180 var el = _Component.prototype.createEl.call(this, 'div', { 16181 append: this.contentEl_, 16182 className: 'vjs-menu' 16183 }); 16184 16185 el.appendChild(this.contentEl_); 16186 16187 // Prevent clicks from bubbling up. Needed for Menu Buttons, 16188 // where a click on the parent is significant 16189 on(el, 'click', function (event) { 16190 event.preventDefault(); 16191 event.stopImmediatePropagation(); 16192 }); 16193 16194 return el; 16195 }; 16196 16197 Menu.prototype.dispose = function dispose() { 16198 this.contentEl_ = null; 16199 16200 _Component.prototype.dispose.call(this); 16201 }; 16202 16203 /** 16204 * Handle a `keydown` event on this menu. This listener is added in the constructor. 16205 * 16206 * @param {EventTarget~Event} event 16207 * A `keydown` event that happened on the menu. 16208 * 16209 * @listens keydown 16210 */ 16211 16212 16213 Menu.prototype.handleKeyPress = function handleKeyPress(event) { 16214 // Left and Down Arrows 16215 if (event.which === 37 || event.which === 40) { 16216 event.preventDefault(); 16217 this.stepForward(); 16218 16219 // Up and Right Arrows 16220 } else if (event.which === 38 || event.which === 39) { 16221 event.preventDefault(); 16222 this.stepBack(); 16223 } 16224 }; 16225 16226 /** 16227 * Move to next (lower) menu item for keyboard users. 16228 */ 16229 16230 16231 Menu.prototype.stepForward = function stepForward() { 16232 var stepChild = 0; 16233 16234 if (this.focusedChild_ !== undefined) { 16235 stepChild = this.focusedChild_ + 1; 16236 } 16237 this.focus(stepChild); 16238 }; 16239 16240 /** 16241 * Move to previous (higher) menu item for keyboard users. 16242 */ 16243 16244 16245 Menu.prototype.stepBack = function stepBack() { 16246 var stepChild = 0; 16247 16248 if (this.focusedChild_ !== undefined) { 16249 stepChild = this.focusedChild_ - 1; 16250 } 16251 this.focus(stepChild); 16252 }; 16253 16254 /** 16255 * Set focus on a {@link MenuItem} in the `Menu`. 16256 * 16257 * @param {Object|string} [item=0] 16258 * Index of child item set focus on. 16259 */ 16260 16261 16262 Menu.prototype.focus = function focus() { 16263 var item = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0; 16264 16265 var children = this.children().slice(); 16266 var haveTitle = children.length && children[0].className && /vjs-menu-title/.test(children[0].className); 16267 16268 if (haveTitle) { 16269 children.shift(); 16270 } 16271 16272 if (children.length > 0) { 16273 if (item < 0) { 16274 item = 0; 16275 } else if (item >= children.length) { 16276 item = children.length - 1; 16277 } 16278 16279 this.focusedChild_ = item; 16280 16281 children[item].el_.focus(); 16282 } 16283 }; 16284 16285 return Menu; 16286}(Component); 16287 16288Component.registerComponent('Menu', Menu); 16289 16290/** 16291 * @file menu-button.js 16292 */ 16293/** 16294 * A `MenuButton` class for any popup {@link Menu}. 16295 * 16296 * @extends Component 16297 */ 16298 16299var MenuButton = function (_Component) { 16300 inherits(MenuButton, _Component); 16301 16302 /** 16303 * Creates an instance of this class. 16304 * 16305 * @param {Player} player 16306 * The `Player` that this class should be attached to. 16307 * 16308 * @param {Object} [options={}] 16309 * The key/value store of player options. 16310 */ 16311 function MenuButton(player) { 16312 var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 16313 classCallCheck(this, MenuButton); 16314 16315 var _this = possibleConstructorReturn(this, _Component.call(this, player, options)); 16316 16317 _this.menuButton_ = new Button(player, options); 16318 16319 _this.menuButton_.controlText(_this.controlText_); 16320 _this.menuButton_.el_.setAttribute('aria-haspopup', 'true'); 16321 16322 // Add buildCSSClass values to the button, not the wrapper 16323 var buttonClass = Button.prototype.buildCSSClass(); 16324 16325 _this.menuButton_.el_.className = _this.buildCSSClass() + ' ' + buttonClass; 16326 _this.menuButton_.removeClass('vjs-control'); 16327 16328 _this.addChild(_this.menuButton_); 16329 16330 _this.update(); 16331 16332 _this.enabled_ = true; 16333 16334 _this.on(_this.menuButton_, 'tap', _this.handleClick); 16335 _this.on(_this.menuButton_, 'click', _this.handleClick); 16336 _this.on(_this.menuButton_, 'focus', _this.handleFocus); 16337 _this.on(_this.menuButton_, 'blur', _this.handleBlur); 16338 16339 _this.on('keydown', _this.handleSubmenuKeyPress); 16340 return _this; 16341 } 16342 16343 /** 16344 * Update the menu based on the current state of its items. 16345 */ 16346 16347 16348 MenuButton.prototype.update = function update() { 16349 var menu = this.createMenu(); 16350 16351 if (this.menu) { 16352 this.menu.dispose(); 16353 this.removeChild(this.menu); 16354 } 16355 16356 this.menu = menu; 16357 this.addChild(menu); 16358 16359 /** 16360 * Track the state of the menu button 16361 * 16362 * @type {Boolean} 16363 * @private 16364 */ 16365 this.buttonPressed_ = false; 16366 this.menuButton_.el_.setAttribute('aria-expanded', 'false'); 16367 16368 if (this.items && this.items.length <= this.hideThreshold_) { 16369 this.hide(); 16370 } else { 16371 this.show(); 16372 } 16373 }; 16374 16375 /** 16376 * Create the menu and add all items to it. 16377 * 16378 * @return {Menu} 16379 * The constructed menu 16380 */ 16381 16382 16383 MenuButton.prototype.createMenu = function createMenu() { 16384 var menu = new Menu(this.player_, { menuButton: this }); 16385 16386 /** 16387 * Hide the menu if the number of items is less than or equal to this threshold. This defaults 16388 * to 0 and whenever we add items which can be hidden to the menu we'll increment it. We list 16389 * it here because every time we run `createMenu` we need to reset the value. 16390 * 16391 * @protected 16392 * @type {Number} 16393 */ 16394 this.hideThreshold_ = 0; 16395 16396 // Add a title list item to the top 16397 if (this.options_.title) { 16398 var title = createEl('li', { 16399 className: 'vjs-menu-title', 16400 innerHTML: toTitleCase(this.options_.title), 16401 tabIndex: -1 16402 }); 16403 16404 this.hideThreshold_ += 1; 16405 16406 menu.children_.unshift(title); 16407 prependTo(title, menu.contentEl()); 16408 } 16409 16410 this.items = this.createItems(); 16411 16412 if (this.items) { 16413 // Add menu items to the menu 16414 for (var i = 0; i < this.items.length; i++) { 16415 menu.addItem(this.items[i]); 16416 } 16417 } 16418 16419 return menu; 16420 }; 16421 16422 /** 16423 * Create the list of menu items. Specific to each subclass. 16424 * 16425 * @abstract 16426 */ 16427 16428 16429 MenuButton.prototype.createItems = function createItems() {}; 16430 16431 /** 16432 * Create the `MenuButtons`s DOM element. 16433 * 16434 * @return {Element} 16435 * The element that gets created. 16436 */ 16437 16438 16439 MenuButton.prototype.createEl = function createEl$$1() { 16440 return _Component.prototype.createEl.call(this, 'div', { 16441 className: this.buildWrapperCSSClass() 16442 }, {}); 16443 }; 16444 16445 /** 16446 * Allow sub components to stack CSS class names for the wrapper element 16447 * 16448 * @return {string} 16449 * The constructed wrapper DOM `className` 16450 */ 16451 16452 16453 MenuButton.prototype.buildWrapperCSSClass = function buildWrapperCSSClass() { 16454 var menuButtonClass = 'vjs-menu-button'; 16455 16456 // If the inline option is passed, we want to use different styles altogether. 16457 if (this.options_.inline === true) { 16458 menuButtonClass += '-inline'; 16459 } else { 16460 menuButtonClass += '-popup'; 16461 } 16462 16463 // TODO: Fix the CSS so that this isn't necessary 16464 var buttonClass = Button.prototype.buildCSSClass(); 16465 16466 return 'vjs-menu-button ' + menuButtonClass + ' ' + buttonClass + ' ' + _Component.prototype.buildCSSClass.call(this); 16467 }; 16468 16469 /** 16470 * Builds the default DOM `className`. 16471 * 16472 * @return {string} 16473 * The DOM `className` for this object. 16474 */ 16475 16476 16477 MenuButton.prototype.buildCSSClass = function buildCSSClass() { 16478 var menuButtonClass = 'vjs-menu-button'; 16479 16480 // If the inline option is passed, we want to use different styles altogether. 16481 if (this.options_.inline === true) { 16482 menuButtonClass += '-inline'; 16483 } else { 16484 menuButtonClass += '-popup'; 16485 } 16486 16487 return 'vjs-menu-button ' + menuButtonClass + ' ' + _Component.prototype.buildCSSClass.call(this); 16488 }; 16489 16490 /** 16491 * Get or set the localized control text that will be used for accessibility. 16492 * 16493 * > NOTE: This will come from the internal `menuButton_` element. 16494 * 16495 * @param {string} [text] 16496 * Control text for element. 16497 * 16498 * @param {Element} [el=this.menuButton_.el()] 16499 * Element to set the title on. 16500 * 16501 * @return {string} 16502 * - The control text when getting 16503 */ 16504 16505 16506 MenuButton.prototype.controlText = function controlText(text) { 16507 var el = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.menuButton_.el(); 16508 16509 return this.menuButton_.controlText(text, el); 16510 }; 16511 16512 /** 16513 * Handle a click on a `MenuButton`. 16514 * See {@link ClickableComponent#handleClick} for instances where this is called. 16515 * 16516 * @param {EventTarget~Event} event 16517 * The `keydown`, `tap`, or `click` event that caused this function to be 16518 * called. 16519 * 16520 * @listens tap 16521 * @listens click 16522 */ 16523 16524 16525 MenuButton.prototype.handleClick = function handleClick(event) { 16526 // When you click the button it adds focus, which will show the menu. 16527 // So we'll remove focus when the mouse leaves the button. Focus is needed 16528 // for tab navigation. 16529 16530 this.one(this.menu.contentEl(), 'mouseleave', bind(this, function (e) { 16531 this.unpressButton(); 16532 this.el_.blur(); 16533 })); 16534 if (this.buttonPressed_) { 16535 this.unpressButton(); 16536 } else { 16537 this.pressButton(); 16538 } 16539 }; 16540 16541 /** 16542 * Set the focus to the actual button, not to this element 16543 */ 16544 16545 16546 MenuButton.prototype.focus = function focus() { 16547 this.menuButton_.focus(); 16548 }; 16549 16550 /** 16551 * Remove the focus from the actual button, not this element 16552 */ 16553 16554 16555 MenuButton.prototype.blur = function blur() { 16556 this.menuButton_.blur(); 16557 }; 16558 16559 /** 16560 * This gets called when a `MenuButton` gains focus via a `focus` event. 16561 * Turns on listening for `keydown` events. When they happen it 16562 * calls `this.handleKeyPress`. 16563 * 16564 * @param {EventTarget~Event} event 16565 * The `focus` event that caused this function to be called. 16566 * 16567 * @listens focus 16568 */ 16569 16570 16571 MenuButton.prototype.handleFocus = function handleFocus() { 16572 on(document_1, 'keydown', bind(this, this.handleKeyPress)); 16573 }; 16574 16575 /** 16576 * Called when a `MenuButton` loses focus. Turns off the listener for 16577 * `keydown` events. Which Stops `this.handleKeyPress` from getting called. 16578 * 16579 * @param {EventTarget~Event} event 16580 * The `blur` event that caused this function to be called. 16581 * 16582 * @listens blur 16583 */ 16584 16585 16586 MenuButton.prototype.handleBlur = function handleBlur() { 16587 off(document_1, 'keydown', bind(this, this.handleKeyPress)); 16588 }; 16589 16590 /** 16591 * Handle tab, escape, down arrow, and up arrow keys for `MenuButton`. See 16592 * {@link ClickableComponent#handleKeyPress} for instances where this is called. 16593 * 16594 * @param {EventTarget~Event} event 16595 * The `keydown` event that caused this function to be called. 16596 * 16597 * @listens keydown 16598 */ 16599 16600 16601 MenuButton.prototype.handleKeyPress = function handleKeyPress(event) { 16602 16603 // Escape (27) key or Tab (9) key unpress the 'button' 16604 if (event.which === 27 || event.which === 9) { 16605 if (this.buttonPressed_) { 16606 this.unpressButton(); 16607 } 16608 // Don't preventDefault for Tab key - we still want to lose focus 16609 if (event.which !== 9) { 16610 event.preventDefault(); 16611 // Set focus back to the menu button's button 16612 this.menuButton_.el_.focus(); 16613 } 16614 // Up (38) key or Down (40) key press the 'button' 16615 } else if (event.which === 38 || event.which === 40) { 16616 if (!this.buttonPressed_) { 16617 this.pressButton(); 16618 event.preventDefault(); 16619 } 16620 } 16621 }; 16622 16623 /** 16624 * Handle a `keydown` event on a sub-menu. The listener for this is added in 16625 * the constructor. 16626 * 16627 * @param {EventTarget~Event} event 16628 * Key press event 16629 * 16630 * @listens keydown 16631 */ 16632 16633 16634 MenuButton.prototype.handleSubmenuKeyPress = function handleSubmenuKeyPress(event) { 16635 16636 // Escape (27) key or Tab (9) key unpress the 'button' 16637 if (event.which === 27 || event.which === 9) { 16638 if (this.buttonPressed_) { 16639 this.unpressButton(); 16640 } 16641 // Don't preventDefault for Tab key - we still want to lose focus 16642 if (event.which !== 9) { 16643 event.preventDefault(); 16644 // Set focus back to the menu button's button 16645 this.menuButton_.el_.focus(); 16646 } 16647 } 16648 }; 16649 16650 /** 16651 * Put the current `MenuButton` into a pressed state. 16652 */ 16653 16654 16655 MenuButton.prototype.pressButton = function pressButton() { 16656 if (this.enabled_) { 16657 this.buttonPressed_ = true; 16658 this.menu.lockShowing(); 16659 this.menuButton_.el_.setAttribute('aria-expanded', 'true'); 16660 16661 // set the focus into the submenu, except on iOS where it is resulting in 16662 // undesired scrolling behavior when the player is in an iframe 16663 if (IS_IOS && isInFrame()) { 16664 // Return early so that the menu isn't focused 16665 return; 16666 } 16667 16668 this.menu.focus(); 16669 } 16670 }; 16671 16672 /** 16673 * Take the current `MenuButton` out of a pressed state. 16674 */ 16675 16676 16677 MenuButton.prototype.unpressButton = function unpressButton() { 16678 if (this.enabled_) { 16679 this.buttonPressed_ = false; 16680 this.menu.unlockShowing(); 16681 this.menuButton_.el_.setAttribute('aria-expanded', 'false'); 16682 } 16683 }; 16684 16685 /** 16686 * Disable the `MenuButton`. Don't allow it to be clicked. 16687 */ 16688 16689 16690 MenuButton.prototype.disable = function disable() { 16691 this.unpressButton(); 16692 16693 this.enabled_ = false; 16694 this.addClass('vjs-disabled'); 16695 16696 this.menuButton_.disable(); 16697 }; 16698 16699 /** 16700 * Enable the `MenuButton`. Allow it to be clicked. 16701 */ 16702 16703 16704 MenuButton.prototype.enable = function enable() { 16705 this.enabled_ = true; 16706 this.removeClass('vjs-disabled'); 16707 16708 this.menuButton_.enable(); 16709 }; 16710 16711 return MenuButton; 16712}(Component); 16713 16714Component.registerComponent('MenuButton', MenuButton); 16715 16716/** 16717 * @file track-button.js 16718 */ 16719/** 16720 * The base class for buttons that toggle specific track types (e.g. subtitles). 16721 * 16722 * @extends MenuButton 16723 */ 16724 16725var TrackButton = function (_MenuButton) { 16726 inherits(TrackButton, _MenuButton); 16727 16728 /** 16729 * Creates an instance of this class. 16730 * 16731 * @param {Player} player 16732 * The `Player` that this class should be attached to. 16733 * 16734 * @param {Object} [options] 16735 * The key/value store of player options. 16736 */ 16737 function TrackButton(player, options) { 16738 classCallCheck(this, TrackButton); 16739 16740 var tracks = options.tracks; 16741 16742 var _this = possibleConstructorReturn(this, _MenuButton.call(this, player, options)); 16743 16744 if (_this.items.length <= 1) { 16745 _this.hide(); 16746 } 16747 16748 if (!tracks) { 16749 return possibleConstructorReturn(_this); 16750 } 16751 16752 var updateHandler = bind(_this, _this.update); 16753 16754 tracks.addEventListener('removetrack', updateHandler); 16755 tracks.addEventListener('addtrack', updateHandler); 16756 _this.player_.on('ready', updateHandler); 16757 16758 _this.player_.on('dispose', function () { 16759 tracks.removeEventListener('removetrack', updateHandler); 16760 tracks.removeEventListener('addtrack', updateHandler); 16761 }); 16762 return _this; 16763 } 16764 16765 return TrackButton; 16766}(MenuButton); 16767 16768Component.registerComponent('TrackButton', TrackButton); 16769 16770/** 16771 * @file menu-item.js 16772 */ 16773/** 16774 * The component for a menu item. `<li>` 16775 * 16776 * @extends ClickableComponent 16777 */ 16778 16779var MenuItem = function (_ClickableComponent) { 16780 inherits(MenuItem, _ClickableComponent); 16781 16782 /** 16783 * Creates an instance of the this class. 16784 * 16785 * @param {Player} player 16786 * The `Player` that this class should be attached to. 16787 * 16788 * @param {Object} [options={}] 16789 * The key/value store of player options. 16790 * 16791 */ 16792 function MenuItem(player, options) { 16793 classCallCheck(this, MenuItem); 16794 16795 var _this = possibleConstructorReturn(this, _ClickableComponent.call(this, player, options)); 16796 16797 _this.selectable = options.selectable; 16798 _this.isSelected_ = options.selected || false; 16799 _this.multiSelectable = options.multiSelectable; 16800 16801 _this.selected(_this.isSelected_); 16802 16803 if (_this.selectable) { 16804 if (_this.multiSelectable) { 16805 _this.el_.setAttribute('role', 'menuitemcheckbox'); 16806 } else { 16807 _this.el_.setAttribute('role', 'menuitemradio'); 16808 } 16809 } else { 16810 _this.el_.setAttribute('role', 'menuitem'); 16811 } 16812 return _this; 16813 } 16814 16815 /** 16816 * Create the `MenuItem's DOM element 16817 * 16818 * @param {string} [type=li] 16819 * Element's node type, not actually used, always set to `li`. 16820 * 16821 * @param {Object} [props={}] 16822 * An object of properties that should be set on the element 16823 * 16824 * @param {Object} [attrs={}] 16825 * An object of attributes that should be set on the element 16826 * 16827 * @return {Element} 16828 * The element that gets created. 16829 */ 16830 16831 16832 MenuItem.prototype.createEl = function createEl(type, props, attrs) { 16833 // The control is textual, not just an icon 16834 this.nonIconControl = true; 16835 16836 return _ClickableComponent.prototype.createEl.call(this, 'li', assign({ 16837 className: 'vjs-menu-item', 16838 innerHTML: '<span class="vjs-menu-item-text">' + this.localize(this.options_.label) + '</span>', 16839 tabIndex: -1 16840 }, props), attrs); 16841 }; 16842 16843 /** 16844 * Any click on a `MenuItem` puts it into the selected state. 16845 * See {@link ClickableComponent#handleClick} for instances where this is called. 16846 * 16847 * @param {EventTarget~Event} event 16848 * The `keydown`, `tap`, or `click` event that caused this function to be 16849 * called. 16850 * 16851 * @listens tap 16852 * @listens click 16853 */ 16854 16855 16856 MenuItem.prototype.handleClick = function handleClick(event) { 16857 this.selected(true); 16858 }; 16859 16860 /** 16861 * Set the state for this menu item as selected or not. 16862 * 16863 * @param {boolean} selected 16864 * if the menu item is selected or not 16865 */ 16866 16867 16868 MenuItem.prototype.selected = function selected(_selected) { 16869 if (this.selectable) { 16870 if (_selected) { 16871 this.addClass('vjs-selected'); 16872 this.el_.setAttribute('aria-checked', 'true'); 16873 // aria-checked isn't fully supported by browsers/screen readers, 16874 // so indicate selected state to screen reader in the control text. 16875 this.controlText(', selected'); 16876 this.isSelected_ = true; 16877 } else { 16878 this.removeClass('vjs-selected'); 16879 this.el_.setAttribute('aria-checked', 'false'); 16880 // Indicate un-selected state to screen reader 16881 this.controlText(''); 16882 this.isSelected_ = false; 16883 } 16884 } 16885 }; 16886 16887 return MenuItem; 16888}(ClickableComponent); 16889 16890Component.registerComponent('MenuItem', MenuItem); 16891 16892/** 16893 * @file text-track-menu-item.js 16894 */ 16895/** 16896 * The specific menu item type for selecting a language within a text track kind 16897 * 16898 * @extends MenuItem 16899 */ 16900 16901var TextTrackMenuItem = function (_MenuItem) { 16902 inherits(TextTrackMenuItem, _MenuItem); 16903 16904 /** 16905 * Creates an instance of this class. 16906 * 16907 * @param {Player} player 16908 * The `Player` that this class should be attached to. 16909 * 16910 * @param {Object} [options] 16911 * The key/value store of player options. 16912 */ 16913 function TextTrackMenuItem(player, options) { 16914 classCallCheck(this, TextTrackMenuItem); 16915 16916 var track = options.track; 16917 var tracks = player.textTracks(); 16918 16919 // Modify options for parent MenuItem class's init. 16920 options.label = track.label || track.language || 'Unknown'; 16921 options.selected = track.mode === 'showing'; 16922 16923 var _this = possibleConstructorReturn(this, _MenuItem.call(this, player, options)); 16924 16925 _this.track = track; 16926 var changeHandler = function changeHandler() { 16927 for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { 16928 args[_key] = arguments[_key]; 16929 } 16930 16931 _this.handleTracksChange.apply(_this, args); 16932 }; 16933 var selectedLanguageChangeHandler = function selectedLanguageChangeHandler() { 16934 for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { 16935 args[_key2] = arguments[_key2]; 16936 } 16937 16938 _this.handleSelectedLanguageChange.apply(_this, args); 16939 }; 16940 16941 player.on(['loadstart', 'texttrackchange'], changeHandler); 16942 tracks.addEventListener('change', changeHandler); 16943 tracks.addEventListener('selectedlanguagechange', selectedLanguageChangeHandler); 16944 _this.on('dispose', function () { 16945 player.off(['loadstart', 'texttrackchange'], changeHandler); 16946 tracks.removeEventListener('change', changeHandler); 16947 tracks.removeEventListener('selectedlanguagechange', selectedLanguageChangeHandler); 16948 }); 16949 16950 // iOS7 doesn't dispatch change events to TextTrackLists when an 16951 // associated track's mode changes. Without something like 16952 // Object.observe() (also not present on iOS7), it's not 16953 // possible to detect changes to the mode attribute and polyfill 16954 // the change event. As a poor substitute, we manually dispatch 16955 // change events whenever the controls modify the mode. 16956 if (tracks.onchange === undefined) { 16957 var event = void 0; 16958 16959 _this.on(['tap', 'click'], function () { 16960 if (_typeof(window_1.Event) !== 'object') { 16961 // Android 2.3 throws an Illegal Constructor error for window.Event 16962 try { 16963 event = new window_1.Event('change'); 16964 } catch (err) { 16965 // continue regardless of error 16966 } 16967 } 16968 16969 if (!event) { 16970 event = document_1.createEvent('Event'); 16971 event.initEvent('change', true, true); 16972 } 16973 16974 tracks.dispatchEvent(event); 16975 }); 16976 } 16977 16978 // set the default state based on current tracks 16979 _this.handleTracksChange(); 16980 return _this; 16981 } 16982 16983 /** 16984 * This gets called when an `TextTrackMenuItem` is "clicked". See 16985 * {@link ClickableComponent} for more detailed information on what a click can be. 16986 * 16987 * @param {EventTarget~Event} event 16988 * The `keydown`, `tap`, or `click` event that caused this function to be 16989 * called. 16990 * 16991 * @listens tap 16992 * @listens click 16993 */ 16994 16995 16996 TextTrackMenuItem.prototype.handleClick = function handleClick(event) { 16997 var kind = this.track.kind; 16998 var kinds = this.track.kinds; 16999 var tracks = this.player_.textTracks(); 17000 17001 if (!kinds) { 17002 kinds = [kind]; 17003 } 17004 17005 _MenuItem.prototype.handleClick.call(this, event); 17006 17007 if (!tracks) { 17008 return; 17009 } 17010 17011 for (var i = 0; i < tracks.length; i++) { 17012 var track = tracks[i]; 17013 17014 if (track === this.track && kinds.indexOf(track.kind) > -1) { 17015 if (track.mode !== 'showing') { 17016 track.mode = 'showing'; 17017 } 17018 } else if (track.mode !== 'disabled') { 17019 track.mode = 'disabled'; 17020 } 17021 } 17022 }; 17023 17024 /** 17025 * Handle text track list change 17026 * 17027 * @param {EventTarget~Event} event 17028 * The `change` event that caused this function to be called. 17029 * 17030 * @listens TextTrackList#change 17031 */ 17032 17033 17034 TextTrackMenuItem.prototype.handleTracksChange = function handleTracksChange(event) { 17035 var shouldBeSelected = this.track.mode === 'showing'; 17036 17037 // Prevent redundant selected() calls because they may cause 17038 // screen readers to read the appended control text unnecessarily 17039 if (shouldBeSelected !== this.isSelected_) { 17040 this.selected(shouldBeSelected); 17041 } 17042 }; 17043 17044 TextTrackMenuItem.prototype.handleSelectedLanguageChange = function handleSelectedLanguageChange(event) { 17045 if (this.track.mode === 'showing') { 17046 var selectedLanguage = this.player_.cache_.selectedLanguage; 17047 17048 // Don't replace the kind of track across the same language 17049 if (selectedLanguage && selectedLanguage.enabled && selectedLanguage.language === this.track.language && selectedLanguage.kind !== this.track.kind) { 17050 return; 17051 } 17052 17053 this.player_.cache_.selectedLanguage = { 17054 enabled: true, 17055 language: this.track.language, 17056 kind: this.track.kind 17057 }; 17058 } 17059 }; 17060 17061 TextTrackMenuItem.prototype.dispose = function dispose() { 17062 // remove reference to track object on dispose 17063 this.track = null; 17064 17065 _MenuItem.prototype.dispose.call(this); 17066 }; 17067 17068 return TextTrackMenuItem; 17069}(MenuItem); 17070 17071Component.registerComponent('TextTrackMenuItem', TextTrackMenuItem); 17072 17073/** 17074 * @file off-text-track-menu-item.js 17075 */ 17076/** 17077 * A special menu item for turning of a specific type of text track 17078 * 17079 * @extends TextTrackMenuItem 17080 */ 17081 17082var OffTextTrackMenuItem = function (_TextTrackMenuItem) { 17083 inherits(OffTextTrackMenuItem, _TextTrackMenuItem); 17084 17085 /** 17086 * Creates an instance of this class. 17087 * 17088 * @param {Player} player 17089 * The `Player` that this class should be attached to. 17090 * 17091 * @param {Object} [options] 17092 * The key/value store of player options. 17093 */ 17094 function OffTextTrackMenuItem(player, options) { 17095 classCallCheck(this, OffTextTrackMenuItem); 17096 17097 // Create pseudo track info 17098 // Requires options['kind'] 17099 options.track = { 17100 player: player, 17101 kind: options.kind, 17102 kinds: options.kinds, 17103 'default': false, 17104 mode: 'disabled' 17105 }; 17106 17107 if (!options.kinds) { 17108 options.kinds = [options.kind]; 17109 } 17110 17111 if (options.label) { 17112 options.track.label = options.label; 17113 } else { 17114 options.track.label = options.kinds.join(' and ') + ' off'; 17115 } 17116 17117 // MenuItem is selectable 17118 options.selectable = true; 17119 // MenuItem is NOT multiSelectable (i.e. only one can be marked "selected" at a time) 17120 options.multiSelectable = false; 17121 17122 return possibleConstructorReturn(this, _TextTrackMenuItem.call(this, player, options)); 17123 } 17124 17125 /** 17126 * Handle text track change 17127 * 17128 * @param {EventTarget~Event} event 17129 * The event that caused this function to run 17130 */ 17131 17132 17133 OffTextTrackMenuItem.prototype.handleTracksChange = function handleTracksChange(event) { 17134 var tracks = this.player().textTracks(); 17135 var shouldBeSelected = true; 17136 17137 for (var i = 0, l = tracks.length; i < l; i++) { 17138 var track = tracks[i]; 17139 17140 if (this.options_.kinds.indexOf(track.kind) > -1 && track.mode === 'showing') { 17141 shouldBeSelected = false; 17142 break; 17143 } 17144 } 17145 17146 // Prevent redundant selected() calls because they may cause 17147 // screen readers to read the appended control text unnecessarily 17148 if (shouldBeSelected !== this.isSelected_) { 17149 this.selected(shouldBeSelected); 17150 } 17151 }; 17152 17153 OffTextTrackMenuItem.prototype.handleSelectedLanguageChange = function handleSelectedLanguageChange(event) { 17154 var tracks = this.player().textTracks(); 17155 var allHidden = true; 17156 17157 for (var i = 0, l = tracks.length; i < l; i++) { 17158 var track = tracks[i]; 17159 17160 if (['captions', 'descriptions', 'subtitles'].indexOf(track.kind) > -1 && track.mode === 'showing') { 17161 allHidden = false; 17162 break; 17163 } 17164 } 17165 17166 if (allHidden) { 17167 this.player_.cache_.selectedLanguage = { 17168 enabled: false 17169 }; 17170 } 17171 }; 17172 17173 return OffTextTrackMenuItem; 17174}(TextTrackMenuItem); 17175 17176Component.registerComponent('OffTextTrackMenuItem', OffTextTrackMenuItem); 17177 17178/** 17179 * @file text-track-button.js 17180 */ 17181/** 17182 * The base class for buttons that toggle specific text track types (e.g. subtitles) 17183 * 17184 * @extends MenuButton 17185 */ 17186 17187var TextTrackButton = function (_TrackButton) { 17188 inherits(TextTrackButton, _TrackButton); 17189 17190 /** 17191 * Creates an instance of this class. 17192 * 17193 * @param {Player} player 17194 * The `Player` that this class should be attached to. 17195 * 17196 * @param {Object} [options={}] 17197 * The key/value store of player options. 17198 */ 17199 function TextTrackButton(player) { 17200 var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 17201 classCallCheck(this, TextTrackButton); 17202 17203 options.tracks = player.textTracks(); 17204 17205 return possibleConstructorReturn(this, _TrackButton.call(this, player, options)); 17206 } 17207 17208 /** 17209 * Create a menu item for each text track 17210 * 17211 * @param {TextTrackMenuItem[]} [items=[]] 17212 * Existing array of items to use during creation 17213 * 17214 * @return {TextTrackMenuItem[]} 17215 * Array of menu items that were created 17216 */ 17217 17218 17219 TextTrackButton.prototype.createItems = function createItems() { 17220 var items = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; 17221 var TrackMenuItem = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : TextTrackMenuItem; 17222 17223 17224 // Label is an overide for the [track] off label 17225 // USed to localise captions/subtitles 17226 var label = void 0; 17227 17228 if (this.label_) { 17229 label = this.label_ + ' off'; 17230 } 17231 // Add an OFF menu item to turn all tracks off 17232 items.push(new OffTextTrackMenuItem(this.player_, { 17233 kinds: this.kinds_, 17234 kind: this.kind_, 17235 label: label 17236 })); 17237 17238 this.hideThreshold_ += 1; 17239 17240 var tracks = this.player_.textTracks(); 17241 17242 if (!Array.isArray(this.kinds_)) { 17243 this.kinds_ = [this.kind_]; 17244 } 17245 17246 for (var i = 0; i < tracks.length; i++) { 17247 var track = tracks[i]; 17248 17249 // only add tracks that are of an appropriate kind and have a label 17250 if (this.kinds_.indexOf(track.kind) > -1) { 17251 17252 var item = new TrackMenuItem(this.player_, { 17253 track: track, 17254 // MenuItem is selectable 17255 selectable: true, 17256 // MenuItem is NOT multiSelectable (i.e. only one can be marked "selected" at a time) 17257 multiSelectable: false 17258 }); 17259 17260 item.addClass('vjs-' + track.kind + '-menu-item'); 17261 items.push(item); 17262 } 17263 } 17264 17265 return items; 17266 }; 17267 17268 return TextTrackButton; 17269}(TrackButton); 17270 17271Component.registerComponent('TextTrackButton', TextTrackButton); 17272 17273/** 17274 * @file chapters-track-menu-item.js 17275 */ 17276/** 17277 * The chapter track menu item 17278 * 17279 * @extends MenuItem 17280 */ 17281 17282var ChaptersTrackMenuItem = function (_MenuItem) { 17283 inherits(ChaptersTrackMenuItem, _MenuItem); 17284 17285 /** 17286 * Creates an instance of this class. 17287 * 17288 * @param {Player} player 17289 * The `Player` that this class should be attached to. 17290 * 17291 * @param {Object} [options] 17292 * The key/value store of player options. 17293 */ 17294 function ChaptersTrackMenuItem(player, options) { 17295 classCallCheck(this, ChaptersTrackMenuItem); 17296 17297 var track = options.track; 17298 var cue = options.cue; 17299 var currentTime = player.currentTime(); 17300 17301 // Modify options for parent MenuItem class's init. 17302 options.selectable = true; 17303 options.multiSelectable = false; 17304 options.label = cue.text; 17305 options.selected = cue.startTime <= currentTime && currentTime < cue.endTime; 17306 17307 var _this = possibleConstructorReturn(this, _MenuItem.call(this, player, options)); 17308 17309 _this.track = track; 17310 _this.cue = cue; 17311 track.addEventListener('cuechange', bind(_this, _this.update)); 17312 return _this; 17313 } 17314 17315 /** 17316 * This gets called when an `ChaptersTrackMenuItem` is "clicked". See 17317 * {@link ClickableComponent} for more detailed information on what a click can be. 17318 * 17319 * @param {EventTarget~Event} [event] 17320 * The `keydown`, `tap`, or `click` event that caused this function to be 17321 * called. 17322 * 17323 * @listens tap 17324 * @listens click 17325 */ 17326 17327 17328 ChaptersTrackMenuItem.prototype.handleClick = function handleClick(event) { 17329 _MenuItem.prototype.handleClick.call(this); 17330 this.player_.currentTime(this.cue.startTime); 17331 this.update(this.cue.startTime); 17332 }; 17333 17334 /** 17335 * Update chapter menu item 17336 * 17337 * @param {EventTarget~Event} [event] 17338 * The `cuechange` event that caused this function to run. 17339 * 17340 * @listens TextTrack#cuechange 17341 */ 17342 17343 17344 ChaptersTrackMenuItem.prototype.update = function update(event) { 17345 var cue = this.cue; 17346 var currentTime = this.player_.currentTime(); 17347 17348 // vjs.log(currentTime, cue.startTime); 17349 this.selected(cue.startTime <= currentTime && currentTime < cue.endTime); 17350 }; 17351 17352 return ChaptersTrackMenuItem; 17353}(MenuItem); 17354 17355Component.registerComponent('ChaptersTrackMenuItem', ChaptersTrackMenuItem); 17356 17357/** 17358 * @file chapters-button.js 17359 */ 17360/** 17361 * The button component for toggling and selecting chapters 17362 * Chapters act much differently than other text tracks 17363 * Cues are navigation vs. other tracks of alternative languages 17364 * 17365 * @extends TextTrackButton 17366 */ 17367 17368var ChaptersButton = function (_TextTrackButton) { 17369 inherits(ChaptersButton, _TextTrackButton); 17370 17371 /** 17372 * Creates an instance of this class. 17373 * 17374 * @param {Player} player 17375 * The `Player` that this class should be attached to. 17376 * 17377 * @param {Object} [options] 17378 * The key/value store of player options. 17379 * 17380 * @param {Component~ReadyCallback} [ready] 17381 * The function to call when this function is ready. 17382 */ 17383 function ChaptersButton(player, options, ready) { 17384 classCallCheck(this, ChaptersButton); 17385 return possibleConstructorReturn(this, _TextTrackButton.call(this, player, options, ready)); 17386 } 17387 17388 /** 17389 * Builds the default DOM `className`. 17390 * 17391 * @return {string} 17392 * The DOM `className` for this object. 17393 */ 17394 17395 17396 ChaptersButton.prototype.buildCSSClass = function buildCSSClass() { 17397 return 'vjs-chapters-button ' + _TextTrackButton.prototype.buildCSSClass.call(this); 17398 }; 17399 17400 ChaptersButton.prototype.buildWrapperCSSClass = function buildWrapperCSSClass() { 17401 return 'vjs-chapters-button ' + _TextTrackButton.prototype.buildWrapperCSSClass.call(this); 17402 }; 17403 17404 /** 17405 * Update the menu based on the current state of its items. 17406 * 17407 * @param {EventTarget~Event} [event] 17408 * An event that triggered this function to run. 17409 * 17410 * @listens TextTrackList#addtrack 17411 * @listens TextTrackList#removetrack 17412 * @listens TextTrackList#change 17413 */ 17414 17415 17416 ChaptersButton.prototype.update = function update(event) { 17417 if (!this.track_ || event && (event.type === 'addtrack' || event.type === 'removetrack')) { 17418 this.setTrack(this.findChaptersTrack()); 17419 } 17420 _TextTrackButton.prototype.update.call(this); 17421 }; 17422 17423 /** 17424 * Set the currently selected track for the chapters button. 17425 * 17426 * @param {TextTrack} track 17427 * The new track to select. Nothing will change if this is the currently selected 17428 * track. 17429 */ 17430 17431 17432 ChaptersButton.prototype.setTrack = function setTrack(track) { 17433 if (this.track_ === track) { 17434 return; 17435 } 17436 17437 if (!this.updateHandler_) { 17438 this.updateHandler_ = this.update.bind(this); 17439 } 17440 17441 // here this.track_ refers to the old track instance 17442 if (this.track_) { 17443 var remoteTextTrackEl = this.player_.remoteTextTrackEls().getTrackElementByTrack_(this.track_); 17444 17445 if (remoteTextTrackEl) { 17446 remoteTextTrackEl.removeEventListener('load', this.updateHandler_); 17447 } 17448 17449 this.track_ = null; 17450 } 17451 17452 this.track_ = track; 17453 17454 // here this.track_ refers to the new track instance 17455 if (this.track_) { 17456 this.track_.mode = 'hidden'; 17457 17458 var _remoteTextTrackEl = this.player_.remoteTextTrackEls().getTrackElementByTrack_(this.track_); 17459 17460 if (_remoteTextTrackEl) { 17461 _remoteTextTrackEl.addEventListener('load', this.updateHandler_); 17462 } 17463 } 17464 }; 17465 17466 /** 17467 * Find the track object that is currently in use by this ChaptersButton 17468 * 17469 * @return {TextTrack|undefined} 17470 * The current track or undefined if none was found. 17471 */ 17472 17473 17474 ChaptersButton.prototype.findChaptersTrack = function findChaptersTrack() { 17475 var tracks = this.player_.textTracks() || []; 17476 17477 for (var i = tracks.length - 1; i >= 0; i--) { 17478 // We will always choose the last track as our chaptersTrack 17479 var track = tracks[i]; 17480 17481 if (track.kind === this.kind_) { 17482 return track; 17483 } 17484 } 17485 }; 17486 17487 /** 17488 * Get the caption for the ChaptersButton based on the track label. This will also 17489 * use the current tracks localized kind as a fallback if a label does not exist. 17490 * 17491 * @return {string} 17492 * The tracks current label or the localized track kind. 17493 */ 17494 17495 17496 ChaptersButton.prototype.getMenuCaption = function getMenuCaption() { 17497 if (this.track_ && this.track_.label) { 17498 return this.track_.label; 17499 } 17500 return this.localize(toTitleCase(this.kind_)); 17501 }; 17502 17503 /** 17504 * Create menu from chapter track 17505 * 17506 * @return {Menu} 17507 * New menu for the chapter buttons 17508 */ 17509 17510 17511 ChaptersButton.prototype.createMenu = function createMenu() { 17512 this.options_.title = this.getMenuCaption(); 17513 return _TextTrackButton.prototype.createMenu.call(this); 17514 }; 17515 17516 /** 17517 * Create a menu item for each text track 17518 * 17519 * @return {TextTrackMenuItem[]} 17520 * Array of menu items 17521 */ 17522 17523 17524 ChaptersButton.prototype.createItems = function createItems() { 17525 var items = []; 17526 17527 if (!this.track_) { 17528 return items; 17529 } 17530 17531 var cues = this.track_.cues; 17532 17533 if (!cues) { 17534 return items; 17535 } 17536 17537 for (var i = 0, l = cues.length; i < l; i++) { 17538 var cue = cues[i]; 17539 var mi = new ChaptersTrackMenuItem(this.player_, { track: this.track_, cue: cue }); 17540 17541 items.push(mi); 17542 } 17543 17544 return items; 17545 }; 17546 17547 return ChaptersButton; 17548}(TextTrackButton); 17549 17550/** 17551 * `kind` of TextTrack to look for to associate it with this menu. 17552 * 17553 * @type {string} 17554 * @private 17555 */ 17556 17557 17558ChaptersButton.prototype.kind_ = 'chapters'; 17559 17560/** 17561 * The text that should display over the `ChaptersButton`s controls. Added for localization. 17562 * 17563 * @type {string} 17564 * @private 17565 */ 17566ChaptersButton.prototype.controlText_ = 'Chapters'; 17567 17568Component.registerComponent('ChaptersButton', ChaptersButton); 17569 17570/** 17571 * @file descriptions-button.js 17572 */ 17573/** 17574 * The button component for toggling and selecting descriptions 17575 * 17576 * @extends TextTrackButton 17577 */ 17578 17579var DescriptionsButton = function (_TextTrackButton) { 17580 inherits(DescriptionsButton, _TextTrackButton); 17581 17582 /** 17583 * Creates an instance of this class. 17584 * 17585 * @param {Player} player 17586 * The `Player` that this class should be attached to. 17587 * 17588 * @param {Object} [options] 17589 * The key/value store of player options. 17590 * 17591 * @param {Component~ReadyCallback} [ready] 17592 * The function to call when this component is ready. 17593 */ 17594 function DescriptionsButton(player, options, ready) { 17595 classCallCheck(this, DescriptionsButton); 17596 17597 var _this = possibleConstructorReturn(this, _TextTrackButton.call(this, player, options, ready)); 17598 17599 var tracks = player.textTracks(); 17600 var changeHandler = bind(_this, _this.handleTracksChange); 17601 17602 tracks.addEventListener('change', changeHandler); 17603 _this.on('dispose', function () { 17604 tracks.removeEventListener('change', changeHandler); 17605 }); 17606 return _this; 17607 } 17608 17609 /** 17610 * Handle text track change 17611 * 17612 * @param {EventTarget~Event} event 17613 * The event that caused this function to run 17614 * 17615 * @listens TextTrackList#change 17616 */ 17617 17618 17619 DescriptionsButton.prototype.handleTracksChange = function handleTracksChange(event) { 17620 var tracks = this.player().textTracks(); 17621 var disabled = false; 17622 17623 // Check whether a track of a different kind is showing 17624 for (var i = 0, l = tracks.length; i < l; i++) { 17625 var track = tracks[i]; 17626 17627 if (track.kind !== this.kind_ && track.mode === 'showing') { 17628 disabled = true; 17629 break; 17630 } 17631 } 17632 17633 // If another track is showing, disable this menu button 17634 if (disabled) { 17635 this.disable(); 17636 } else { 17637 this.enable(); 17638 } 17639 }; 17640 17641 /** 17642 * Builds the default DOM `className`. 17643 * 17644 * @return {string} 17645 * The DOM `className` for this object. 17646 */ 17647 17648 17649 DescriptionsButton.prototype.buildCSSClass = function buildCSSClass() { 17650 return 'vjs-descriptions-button ' + _TextTrackButton.prototype.buildCSSClass.call(this); 17651 }; 17652 17653 DescriptionsButton.prototype.buildWrapperCSSClass = function buildWrapperCSSClass() { 17654 return 'vjs-descriptions-button ' + _TextTrackButton.prototype.buildWrapperCSSClass.call(this); 17655 }; 17656 17657 return DescriptionsButton; 17658}(TextTrackButton); 17659 17660/** 17661 * `kind` of TextTrack to look for to associate it with this menu. 17662 * 17663 * @type {string} 17664 * @private 17665 */ 17666 17667 17668DescriptionsButton.prototype.kind_ = 'descriptions'; 17669 17670/** 17671 * The text that should display over the `DescriptionsButton`s controls. Added for localization. 17672 * 17673 * @type {string} 17674 * @private 17675 */ 17676DescriptionsButton.prototype.controlText_ = 'Descriptions'; 17677 17678Component.registerComponent('DescriptionsButton', DescriptionsButton); 17679 17680/** 17681 * @file subtitles-button.js 17682 */ 17683/** 17684 * The button component for toggling and selecting subtitles 17685 * 17686 * @extends TextTrackButton 17687 */ 17688 17689var SubtitlesButton = function (_TextTrackButton) { 17690 inherits(SubtitlesButton, _TextTrackButton); 17691 17692 /** 17693 * Creates an instance of this class. 17694 * 17695 * @param {Player} player 17696 * The `Player` that this class should be attached to. 17697 * 17698 * @param {Object} [options] 17699 * The key/value store of player options. 17700 * 17701 * @param {Component~ReadyCallback} [ready] 17702 * The function to call when this component is ready. 17703 */ 17704 function SubtitlesButton(player, options, ready) { 17705 classCallCheck(this, SubtitlesButton); 17706 return possibleConstructorReturn(this, _TextTrackButton.call(this, player, options, ready)); 17707 } 17708 17709 /** 17710 * Builds the default DOM `className`. 17711 * 17712 * @return {string} 17713 * The DOM `className` for this object. 17714 */ 17715 17716 17717 SubtitlesButton.prototype.buildCSSClass = function buildCSSClass() { 17718 return 'vjs-subtitles-button ' + _TextTrackButton.prototype.buildCSSClass.call(this); 17719 }; 17720 17721 SubtitlesButton.prototype.buildWrapperCSSClass = function buildWrapperCSSClass() { 17722 return 'vjs-subtitles-button ' + _TextTrackButton.prototype.buildWrapperCSSClass.call(this); 17723 }; 17724 17725 return SubtitlesButton; 17726}(TextTrackButton); 17727 17728/** 17729 * `kind` of TextTrack to look for to associate it with this menu. 17730 * 17731 * @type {string} 17732 * @private 17733 */ 17734 17735 17736SubtitlesButton.prototype.kind_ = 'subtitles'; 17737 17738/** 17739 * The text that should display over the `SubtitlesButton`s controls. Added for localization. 17740 * 17741 * @type {string} 17742 * @private 17743 */ 17744SubtitlesButton.prototype.controlText_ = 'Subtitles'; 17745 17746Component.registerComponent('SubtitlesButton', SubtitlesButton); 17747 17748/** 17749 * @file caption-settings-menu-item.js 17750 */ 17751/** 17752 * The menu item for caption track settings menu 17753 * 17754 * @extends TextTrackMenuItem 17755 */ 17756 17757var CaptionSettingsMenuItem = function (_TextTrackMenuItem) { 17758 inherits(CaptionSettingsMenuItem, _TextTrackMenuItem); 17759 17760 /** 17761 * Creates an instance of this class. 17762 * 17763 * @param {Player} player 17764 * The `Player` that this class should be attached to. 17765 * 17766 * @param {Object} [options] 17767 * The key/value store of player options. 17768 */ 17769 function CaptionSettingsMenuItem(player, options) { 17770 classCallCheck(this, CaptionSettingsMenuItem); 17771 17772 options.track = { 17773 player: player, 17774 kind: options.kind, 17775 label: options.kind + ' settings', 17776 selectable: false, 17777 'default': false, 17778 mode: 'disabled' 17779 }; 17780 17781 // CaptionSettingsMenuItem has no concept of 'selected' 17782 options.selectable = false; 17783 17784 options.name = 'CaptionSettingsMenuItem'; 17785 17786 var _this = possibleConstructorReturn(this, _TextTrackMenuItem.call(this, player, options)); 17787 17788 _this.addClass('vjs-texttrack-settings'); 17789 _this.controlText(', opens ' + options.kind + ' settings dialog'); 17790 return _this; 17791 } 17792 17793 /** 17794 * This gets called when an `CaptionSettingsMenuItem` is "clicked". See 17795 * {@link ClickableComponent} for more detailed information on what a click can be. 17796 * 17797 * @param {EventTarget~Event} [event] 17798 * The `keydown`, `tap`, or `click` event that caused this function to be 17799 * called. 17800 * 17801 * @listens tap 17802 * @listens click 17803 */ 17804 17805 17806 CaptionSettingsMenuItem.prototype.handleClick = function handleClick(event) { 17807 this.player().getChild('textTrackSettings').open(); 17808 }; 17809 17810 return CaptionSettingsMenuItem; 17811}(TextTrackMenuItem); 17812 17813Component.registerComponent('CaptionSettingsMenuItem', CaptionSettingsMenuItem); 17814 17815/** 17816 * @file captions-button.js 17817 */ 17818/** 17819 * The button component for toggling and selecting captions 17820 * 17821 * @extends TextTrackButton 17822 */ 17823 17824var CaptionsButton = function (_TextTrackButton) { 17825 inherits(CaptionsButton, _TextTrackButton); 17826 17827 /** 17828 * Creates an instance of this class. 17829 * 17830 * @param {Player} player 17831 * The `Player` that this class should be attached to. 17832 * 17833 * @param {Object} [options] 17834 * The key/value store of player options. 17835 * 17836 * @param {Component~ReadyCallback} [ready] 17837 * The function to call when this component is ready. 17838 */ 17839 function CaptionsButton(player, options, ready) { 17840 classCallCheck(this, CaptionsButton); 17841 return possibleConstructorReturn(this, _TextTrackButton.call(this, player, options, ready)); 17842 } 17843 17844 /** 17845 * Builds the default DOM `className`. 17846 * 17847 * @return {string} 17848 * The DOM `className` for this object. 17849 */ 17850 17851 17852 CaptionsButton.prototype.buildCSSClass = function buildCSSClass() { 17853 return 'vjs-captions-button ' + _TextTrackButton.prototype.buildCSSClass.call(this); 17854 }; 17855 17856 CaptionsButton.prototype.buildWrapperCSSClass = function buildWrapperCSSClass() { 17857 return 'vjs-captions-button ' + _TextTrackButton.prototype.buildWrapperCSSClass.call(this); 17858 }; 17859 17860 /** 17861 * Create caption menu items 17862 * 17863 * @return {CaptionSettingsMenuItem[]} 17864 * The array of current menu items. 17865 */ 17866 17867 17868 CaptionsButton.prototype.createItems = function createItems() { 17869 var items = []; 17870 17871 if (!(this.player().tech_ && this.player().tech_.featuresNativeTextTracks) && this.player().getChild('textTrackSettings')) { 17872 items.push(new CaptionSettingsMenuItem(this.player_, { kind: this.kind_ })); 17873 17874 this.hideThreshold_ += 1; 17875 } 17876 17877 return _TextTrackButton.prototype.createItems.call(this, items); 17878 }; 17879 17880 return CaptionsButton; 17881}(TextTrackButton); 17882 17883/** 17884 * `kind` of TextTrack to look for to associate it with this menu. 17885 * 17886 * @type {string} 17887 * @private 17888 */ 17889 17890 17891CaptionsButton.prototype.kind_ = 'captions'; 17892 17893/** 17894 * The text that should display over the `CaptionsButton`s controls. Added for localization. 17895 * 17896 * @type {string} 17897 * @private 17898 */ 17899CaptionsButton.prototype.controlText_ = 'Captions'; 17900 17901Component.registerComponent('CaptionsButton', CaptionsButton); 17902 17903/** 17904 * @file subs-caps-menu-item.js 17905 */ 17906/** 17907 * SubsCapsMenuItem has an [cc] icon to distinguish captions from subtitles 17908 * in the SubsCapsMenu. 17909 * 17910 * @extends TextTrackMenuItem 17911 */ 17912 17913var SubsCapsMenuItem = function (_TextTrackMenuItem) { 17914 inherits(SubsCapsMenuItem, _TextTrackMenuItem); 17915 17916 function SubsCapsMenuItem() { 17917 classCallCheck(this, SubsCapsMenuItem); 17918 return possibleConstructorReturn(this, _TextTrackMenuItem.apply(this, arguments)); 17919 } 17920 17921 SubsCapsMenuItem.prototype.createEl = function createEl(type, props, attrs) { 17922 var innerHTML = '<span class="vjs-menu-item-text">' + this.localize(this.options_.label); 17923 17924 if (this.options_.track.kind === 'captions') { 17925 innerHTML += '\n <span aria-hidden="true" class="vjs-icon-placeholder"></span>\n <span class="vjs-control-text"> ' + this.localize('Captions') + '</span>\n '; 17926 } 17927 17928 innerHTML += '</span>'; 17929 17930 var el = _TextTrackMenuItem.prototype.createEl.call(this, type, assign({ 17931 innerHTML: innerHTML 17932 }, props), attrs); 17933 17934 return el; 17935 }; 17936 17937 return SubsCapsMenuItem; 17938}(TextTrackMenuItem); 17939 17940Component.registerComponent('SubsCapsMenuItem', SubsCapsMenuItem); 17941 17942/** 17943 * @file sub-caps-button.js 17944 */ 17945/** 17946 * The button component for toggling and selecting captions and/or subtitles 17947 * 17948 * @extends TextTrackButton 17949 */ 17950 17951var SubsCapsButton = function (_TextTrackButton) { 17952 inherits(SubsCapsButton, _TextTrackButton); 17953 17954 function SubsCapsButton(player) { 17955 var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 17956 classCallCheck(this, SubsCapsButton); 17957 17958 // Although North America uses "captions" in most cases for 17959 // "captions and subtitles" other locales use "subtitles" 17960 var _this = possibleConstructorReturn(this, _TextTrackButton.call(this, player, options)); 17961 17962 _this.label_ = 'subtitles'; 17963 if (['en', 'en-us', 'en-ca', 'fr-ca'].indexOf(_this.player_.language_) > -1) { 17964 _this.label_ = 'captions'; 17965 } 17966 _this.menuButton_.controlText(toTitleCase(_this.label_)); 17967 return _this; 17968 } 17969 17970 /** 17971 * Builds the default DOM `className`. 17972 * 17973 * @return {string} 17974 * The DOM `className` for this object. 17975 */ 17976 17977 17978 SubsCapsButton.prototype.buildCSSClass = function buildCSSClass() { 17979 return 'vjs-subs-caps-button ' + _TextTrackButton.prototype.buildCSSClass.call(this); 17980 }; 17981 17982 SubsCapsButton.prototype.buildWrapperCSSClass = function buildWrapperCSSClass() { 17983 return 'vjs-subs-caps-button ' + _TextTrackButton.prototype.buildWrapperCSSClass.call(this); 17984 }; 17985 17986 /** 17987 * Create caption/subtitles menu items 17988 * 17989 * @return {CaptionSettingsMenuItem[]} 17990 * The array of current menu items. 17991 */ 17992 17993 17994 SubsCapsButton.prototype.createItems = function createItems() { 17995 var items = []; 17996 17997 if (!(this.player().tech_ && this.player().tech_.featuresNativeTextTracks) && this.player().getChild('textTrackSettings')) { 17998 items.push(new CaptionSettingsMenuItem(this.player_, { kind: this.label_ })); 17999 18000 this.hideThreshold_ += 1; 18001 } 18002 18003 items = _TextTrackButton.prototype.createItems.call(this, items, SubsCapsMenuItem); 18004 return items; 18005 }; 18006 18007 return SubsCapsButton; 18008}(TextTrackButton); 18009 18010/** 18011 * `kind`s of TextTrack to look for to associate it with this menu. 18012 * 18013 * @type {array} 18014 * @private 18015 */ 18016 18017 18018SubsCapsButton.prototype.kinds_ = ['captions', 'subtitles']; 18019 18020/** 18021 * The text that should display over the `SubsCapsButton`s controls. 18022 * 18023 * 18024 * @type {string} 18025 * @private 18026 */ 18027SubsCapsButton.prototype.controlText_ = 'Subtitles'; 18028 18029Component.registerComponent('SubsCapsButton', SubsCapsButton); 18030 18031/** 18032 * @file audio-track-menu-item.js 18033 */ 18034/** 18035 * An {@link AudioTrack} {@link MenuItem} 18036 * 18037 * @extends MenuItem 18038 */ 18039 18040var AudioTrackMenuItem = function (_MenuItem) { 18041 inherits(AudioTrackMenuItem, _MenuItem); 18042 18043 /** 18044 * Creates an instance of this class. 18045 * 18046 * @param {Player} player 18047 * The `Player` that this class should be attached to. 18048 * 18049 * @param {Object} [options] 18050 * The key/value store of player options. 18051 */ 18052 function AudioTrackMenuItem(player, options) { 18053 classCallCheck(this, AudioTrackMenuItem); 18054 18055 var track = options.track; 18056 var tracks = player.audioTracks(); 18057 18058 // Modify options for parent MenuItem class's init. 18059 options.label = track.label || track.language || 'Unknown'; 18060 options.selected = track.enabled; 18061 18062 var _this = possibleConstructorReturn(this, _MenuItem.call(this, player, options)); 18063 18064 _this.track = track; 18065 18066 _this.addClass('vjs-' + track.kind + '-menu-item'); 18067 18068 var changeHandler = function changeHandler() { 18069 for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { 18070 args[_key] = arguments[_key]; 18071 } 18072 18073 _this.handleTracksChange.apply(_this, args); 18074 }; 18075 18076 tracks.addEventListener('change', changeHandler); 18077 _this.on('dispose', function () { 18078 tracks.removeEventListener('change', changeHandler); 18079 }); 18080 return _this; 18081 } 18082 18083 AudioTrackMenuItem.prototype.createEl = function createEl(type, props, attrs) { 18084 var innerHTML = '<span class="vjs-menu-item-text">' + this.localize(this.options_.label); 18085 18086 if (this.options_.track.kind === 'main-desc') { 18087 innerHTML += '\n <span aria-hidden="true" class="vjs-icon-placeholder"></span>\n <span class="vjs-control-text"> ' + this.localize('Descriptions') + '</span>\n '; 18088 } 18089 18090 innerHTML += '</span>'; 18091 18092 var el = _MenuItem.prototype.createEl.call(this, type, assign({ 18093 innerHTML: innerHTML 18094 }, props), attrs); 18095 18096 return el; 18097 }; 18098 18099 /** 18100 * This gets called when an `AudioTrackMenuItem is "clicked". See {@link ClickableComponent} 18101 * for more detailed information on what a click can be. 18102 * 18103 * @param {EventTarget~Event} [event] 18104 * The `keydown`, `tap`, or `click` event that caused this function to be 18105 * called. 18106 * 18107 * @listens tap 18108 * @listens click 18109 */ 18110 18111 18112 AudioTrackMenuItem.prototype.handleClick = function handleClick(event) { 18113 var tracks = this.player_.audioTracks(); 18114 18115 _MenuItem.prototype.handleClick.call(this, event); 18116 18117 for (var i = 0; i < tracks.length; i++) { 18118 var track = tracks[i]; 18119 18120 track.enabled = track === this.track; 18121 } 18122 }; 18123 18124 /** 18125 * Handle any {@link AudioTrack} change. 18126 * 18127 * @param {EventTarget~Event} [event] 18128 * The {@link AudioTrackList#change} event that caused this to run. 18129 * 18130 * @listens AudioTrackList#change 18131 */ 18132 18133 18134 AudioTrackMenuItem.prototype.handleTracksChange = function handleTracksChange(event) { 18135 this.selected(this.track.enabled); 18136 }; 18137 18138 return AudioTrackMenuItem; 18139}(MenuItem); 18140 18141Component.registerComponent('AudioTrackMenuItem', AudioTrackMenuItem); 18142 18143/** 18144 * @file audio-track-button.js 18145 */ 18146/** 18147 * The base class for buttons that toggle specific {@link AudioTrack} types. 18148 * 18149 * @extends TrackButton 18150 */ 18151 18152var AudioTrackButton = function (_TrackButton) { 18153 inherits(AudioTrackButton, _TrackButton); 18154 18155 /** 18156 * Creates an instance of this class. 18157 * 18158 * @param {Player} player 18159 * The `Player` that this class should be attached to. 18160 * 18161 * @param {Object} [options={}] 18162 * The key/value store of player options. 18163 */ 18164 function AudioTrackButton(player) { 18165 var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 18166 classCallCheck(this, AudioTrackButton); 18167 18168 options.tracks = player.audioTracks(); 18169 18170 return possibleConstructorReturn(this, _TrackButton.call(this, player, options)); 18171 } 18172 18173 /** 18174 * Builds the default DOM `className`. 18175 * 18176 * @return {string} 18177 * The DOM `className` for this object. 18178 */ 18179 18180 18181 AudioTrackButton.prototype.buildCSSClass = function buildCSSClass() { 18182 return 'vjs-audio-button ' + _TrackButton.prototype.buildCSSClass.call(this); 18183 }; 18184 18185 AudioTrackButton.prototype.buildWrapperCSSClass = function buildWrapperCSSClass() { 18186 return 'vjs-audio-button ' + _TrackButton.prototype.buildWrapperCSSClass.call(this); 18187 }; 18188 18189 /** 18190 * Create a menu item for each audio track 18191 * 18192 * @param {AudioTrackMenuItem[]} [items=[]] 18193 * An array of existing menu items to use. 18194 * 18195 * @return {AudioTrackMenuItem[]} 18196 * An array of menu items 18197 */ 18198 18199 18200 AudioTrackButton.prototype.createItems = function createItems() { 18201 var items = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; 18202 18203 // if there's only one audio track, there no point in showing it 18204 this.hideThreshold_ = 1; 18205 18206 var tracks = this.player_.audioTracks(); 18207 18208 for (var i = 0; i < tracks.length; i++) { 18209 var track = tracks[i]; 18210 18211 items.push(new AudioTrackMenuItem(this.player_, { 18212 track: track, 18213 // MenuItem is selectable 18214 selectable: true, 18215 // MenuItem is NOT multiSelectable (i.e. only one can be marked "selected" at a time) 18216 multiSelectable: false 18217 })); 18218 } 18219 18220 return items; 18221 }; 18222 18223 return AudioTrackButton; 18224}(TrackButton); 18225 18226/** 18227 * The text that should display over the `AudioTrackButton`s controls. Added for localization. 18228 * 18229 * @type {string} 18230 * @private 18231 */ 18232 18233 18234AudioTrackButton.prototype.controlText_ = 'Audio Track'; 18235Component.registerComponent('AudioTrackButton', AudioTrackButton); 18236 18237/** 18238 * @file playback-rate-menu-item.js 18239 */ 18240/** 18241 * The specific menu item type for selecting a playback rate. 18242 * 18243 * @extends MenuItem 18244 */ 18245 18246var PlaybackRateMenuItem = function (_MenuItem) { 18247 inherits(PlaybackRateMenuItem, _MenuItem); 18248 18249 /** 18250 * Creates an instance of this class. 18251 * 18252 * @param {Player} player 18253 * The `Player` that this class should be attached to. 18254 * 18255 * @param {Object} [options] 18256 * The key/value store of player options. 18257 */ 18258 function PlaybackRateMenuItem(player, options) { 18259 classCallCheck(this, PlaybackRateMenuItem); 18260 18261 var label = options.rate; 18262 var rate = parseFloat(label, 10); 18263 18264 // Modify options for parent MenuItem class's init. 18265 options.label = label; 18266 options.selected = rate === 1; 18267 options.selectable = true; 18268 options.multiSelectable = false; 18269 18270 var _this = possibleConstructorReturn(this, _MenuItem.call(this, player, options)); 18271 18272 _this.label = label; 18273 _this.rate = rate; 18274 18275 _this.on(player, 'ratechange', _this.update); 18276 return _this; 18277 } 18278 18279 /** 18280 * This gets called when an `PlaybackRateMenuItem` is "clicked". See 18281 * {@link ClickableComponent} for more detailed information on what a click can be. 18282 * 18283 * @param {EventTarget~Event} [event] 18284 * The `keydown`, `tap`, or `click` event that caused this function to be 18285 * called. 18286 * 18287 * @listens tap 18288 * @listens click 18289 */ 18290 18291 18292 PlaybackRateMenuItem.prototype.handleClick = function handleClick(event) { 18293 _MenuItem.prototype.handleClick.call(this); 18294 this.player().playbackRate(this.rate); 18295 }; 18296 18297 /** 18298 * Update the PlaybackRateMenuItem when the playbackrate changes. 18299 * 18300 * @param {EventTarget~Event} [event] 18301 * The `ratechange` event that caused this function to run. 18302 * 18303 * @listens Player#ratechange 18304 */ 18305 18306 18307 PlaybackRateMenuItem.prototype.update = function update(event) { 18308 this.selected(this.player().playbackRate() === this.rate); 18309 }; 18310 18311 return PlaybackRateMenuItem; 18312}(MenuItem); 18313 18314/** 18315 * The text that should display over the `PlaybackRateMenuItem`s controls. Added for localization. 18316 * 18317 * @type {string} 18318 * @private 18319 */ 18320 18321 18322PlaybackRateMenuItem.prototype.contentElType = 'button'; 18323 18324Component.registerComponent('PlaybackRateMenuItem', PlaybackRateMenuItem); 18325 18326/** 18327 * @file playback-rate-menu-button.js 18328 */ 18329/** 18330 * The component for controlling the playback rate. 18331 * 18332 * @extends MenuButton 18333 */ 18334 18335var PlaybackRateMenuButton = function (_MenuButton) { 18336 inherits(PlaybackRateMenuButton, _MenuButton); 18337 18338 /** 18339 * Creates an instance of this class. 18340 * 18341 * @param {Player} player 18342 * The `Player` that this class should be attached to. 18343 * 18344 * @param {Object} [options] 18345 * The key/value store of player options. 18346 */ 18347 function PlaybackRateMenuButton(player, options) { 18348 classCallCheck(this, PlaybackRateMenuButton); 18349 18350 var _this = possibleConstructorReturn(this, _MenuButton.call(this, player, options)); 18351 18352 _this.updateVisibility(); 18353 _this.updateLabel(); 18354 18355 _this.on(player, 'loadstart', _this.updateVisibility); 18356 _this.on(player, 'ratechange', _this.updateLabel); 18357 return _this; 18358 } 18359 18360 /** 18361 * Create the `Component`'s DOM element 18362 * 18363 * @return {Element} 18364 * The element that was created. 18365 */ 18366 18367 18368 PlaybackRateMenuButton.prototype.createEl = function createEl$$1() { 18369 var el = _MenuButton.prototype.createEl.call(this); 18370 18371 this.labelEl_ = createEl('div', { 18372 className: 'vjs-playback-rate-value', 18373 innerHTML: '1x' 18374 }); 18375 18376 el.appendChild(this.labelEl_); 18377 18378 return el; 18379 }; 18380 18381 PlaybackRateMenuButton.prototype.dispose = function dispose() { 18382 this.labelEl_ = null; 18383 18384 _MenuButton.prototype.dispose.call(this); 18385 }; 18386 18387 /** 18388 * Builds the default DOM `className`. 18389 * 18390 * @return {string} 18391 * The DOM `className` for this object. 18392 */ 18393 18394 18395 PlaybackRateMenuButton.prototype.buildCSSClass = function buildCSSClass() { 18396 return 'vjs-playback-rate ' + _MenuButton.prototype.buildCSSClass.call(this); 18397 }; 18398 18399 PlaybackRateMenuButton.prototype.buildWrapperCSSClass = function buildWrapperCSSClass() { 18400 return 'vjs-playback-rate ' + _MenuButton.prototype.buildWrapperCSSClass.call(this); 18401 }; 18402 18403 /** 18404 * Create the playback rate menu 18405 * 18406 * @return {Menu} 18407 * Menu object populated with {@link PlaybackRateMenuItem}s 18408 */ 18409 18410 18411 PlaybackRateMenuButton.prototype.createMenu = function createMenu() { 18412 var menu = new Menu(this.player()); 18413 var rates = this.playbackRates(); 18414 18415 if (rates) { 18416 for (var i = rates.length - 1; i >= 0; i--) { 18417 menu.addChild(new PlaybackRateMenuItem(this.player(), { rate: rates[i] + 'x' })); 18418 } 18419 } 18420 18421 return menu; 18422 }; 18423 18424 /** 18425 * Updates ARIA accessibility attributes 18426 */ 18427 18428 18429 PlaybackRateMenuButton.prototype.updateARIAAttributes = function updateARIAAttributes() { 18430 // Current playback rate 18431 this.el().setAttribute('aria-valuenow', this.player().playbackRate()); 18432 }; 18433 18434 /** 18435 * This gets called when an `PlaybackRateMenuButton` is "clicked". See 18436 * {@link ClickableComponent} for more detailed information on what a click can be. 18437 * 18438 * @param {EventTarget~Event} [event] 18439 * The `keydown`, `tap`, or `click` event that caused this function to be 18440 * called. 18441 * 18442 * @listens tap 18443 * @listens click 18444 */ 18445 18446 18447 PlaybackRateMenuButton.prototype.handleClick = function handleClick(event) { 18448 // select next rate option 18449 var currentRate = this.player().playbackRate(); 18450 var rates = this.playbackRates(); 18451 18452 // this will select first one if the last one currently selected 18453 var newRate = rates[0]; 18454 18455 for (var i = 0; i < rates.length; i++) { 18456 if (rates[i] > currentRate) { 18457 newRate = rates[i]; 18458 break; 18459 } 18460 } 18461 this.player().playbackRate(newRate); 18462 }; 18463 18464 /** 18465 * Get possible playback rates 18466 * 18467 * @return {Array} 18468 * All possible playback rates 18469 */ 18470 18471 18472 PlaybackRateMenuButton.prototype.playbackRates = function playbackRates() { 18473 return this.options_.playbackRates || this.options_.playerOptions && this.options_.playerOptions.playbackRates; 18474 }; 18475 18476 /** 18477 * Get whether playback rates is supported by the tech 18478 * and an array of playback rates exists 18479 * 18480 * @return {boolean} 18481 * Whether changing playback rate is supported 18482 */ 18483 18484 18485 PlaybackRateMenuButton.prototype.playbackRateSupported = function playbackRateSupported() { 18486 return this.player().tech_ && this.player().tech_.featuresPlaybackRate && this.playbackRates() && this.playbackRates().length > 0; 18487 }; 18488 18489 /** 18490 * Hide playback rate controls when they're no playback rate options to select 18491 * 18492 * @param {EventTarget~Event} [event] 18493 * The event that caused this function to run. 18494 * 18495 * @listens Player#loadstart 18496 */ 18497 18498 18499 PlaybackRateMenuButton.prototype.updateVisibility = function updateVisibility(event) { 18500 if (this.playbackRateSupported()) { 18501 this.removeClass('vjs-hidden'); 18502 } else { 18503 this.addClass('vjs-hidden'); 18504 } 18505 }; 18506 18507 /** 18508 * Update button label when rate changed 18509 * 18510 * @param {EventTarget~Event} [event] 18511 * The event that caused this function to run. 18512 * 18513 * @listens Player#ratechange 18514 */ 18515 18516 18517 PlaybackRateMenuButton.prototype.updateLabel = function updateLabel(event) { 18518 if (this.playbackRateSupported()) { 18519 this.labelEl_.innerHTML = this.player().playbackRate() + 'x'; 18520 } 18521 }; 18522 18523 return PlaybackRateMenuButton; 18524}(MenuButton); 18525 18526/** 18527 * The text that should display over the `FullscreenToggle`s controls. Added for localization. 18528 * 18529 * @type {string} 18530 * @private 18531 */ 18532 18533 18534PlaybackRateMenuButton.prototype.controlText_ = 'Playback Rate'; 18535 18536Component.registerComponent('PlaybackRateMenuButton', PlaybackRateMenuButton); 18537 18538/** 18539 * @file spacer.js 18540 */ 18541/** 18542 * Just an empty spacer element that can be used as an append point for plugins, etc. 18543 * Also can be used to create space between elements when necessary. 18544 * 18545 * @extends Component 18546 */ 18547 18548var Spacer = function (_Component) { 18549 inherits(Spacer, _Component); 18550 18551 function Spacer() { 18552 classCallCheck(this, Spacer); 18553 return possibleConstructorReturn(this, _Component.apply(this, arguments)); 18554 } 18555 18556 /** 18557 * Builds the default DOM `className`. 18558 * 18559 * @return {string} 18560 * The DOM `className` for this object. 18561 */ 18562 Spacer.prototype.buildCSSClass = function buildCSSClass() { 18563 return 'vjs-spacer ' + _Component.prototype.buildCSSClass.call(this); 18564 }; 18565 18566 /** 18567 * Create the `Component`'s DOM element 18568 * 18569 * @return {Element} 18570 * The element that was created. 18571 */ 18572 18573 18574 Spacer.prototype.createEl = function createEl() { 18575 return _Component.prototype.createEl.call(this, 'div', { 18576 className: this.buildCSSClass() 18577 }); 18578 }; 18579 18580 return Spacer; 18581}(Component); 18582 18583Component.registerComponent('Spacer', Spacer); 18584 18585/** 18586 * @file custom-control-spacer.js 18587 */ 18588/** 18589 * Spacer specifically meant to be used as an insertion point for new plugins, etc. 18590 * 18591 * @extends Spacer 18592 */ 18593 18594var CustomControlSpacer = function (_Spacer) { 18595 inherits(CustomControlSpacer, _Spacer); 18596 18597 function CustomControlSpacer() { 18598 classCallCheck(this, CustomControlSpacer); 18599 return possibleConstructorReturn(this, _Spacer.apply(this, arguments)); 18600 } 18601 18602 /** 18603 * Builds the default DOM `className`. 18604 * 18605 * @return {string} 18606 * The DOM `className` for this object. 18607 */ 18608 CustomControlSpacer.prototype.buildCSSClass = function buildCSSClass() { 18609 return 'vjs-custom-control-spacer ' + _Spacer.prototype.buildCSSClass.call(this); 18610 }; 18611 18612 /** 18613 * Create the `Component`'s DOM element 18614 * 18615 * @return {Element} 18616 * The element that was created. 18617 */ 18618 18619 18620 CustomControlSpacer.prototype.createEl = function createEl() { 18621 var el = _Spacer.prototype.createEl.call(this, { 18622 className: this.buildCSSClass() 18623 }); 18624 18625 // No-flex/table-cell mode requires there be some content 18626 // in the cell to fill the remaining space of the table. 18627 el.innerHTML = '\xA0'; 18628 return el; 18629 }; 18630 18631 return CustomControlSpacer; 18632}(Spacer); 18633 18634Component.registerComponent('CustomControlSpacer', CustomControlSpacer); 18635 18636/** 18637 * @file control-bar.js 18638 */ 18639// Required children 18640/** 18641 * Container of main controls. 18642 * 18643 * @extends Component 18644 */ 18645 18646var ControlBar = function (_Component) { 18647 inherits(ControlBar, _Component); 18648 18649 function ControlBar() { 18650 classCallCheck(this, ControlBar); 18651 return possibleConstructorReturn(this, _Component.apply(this, arguments)); 18652 } 18653 18654 /** 18655 * Create the `Component`'s DOM element 18656 * 18657 * @return {Element} 18658 * The element that was created. 18659 */ 18660 ControlBar.prototype.createEl = function createEl() { 18661 return _Component.prototype.createEl.call(this, 'div', { 18662 className: 'vjs-control-bar', 18663 dir: 'ltr' 18664 }); 18665 }; 18666 18667 return ControlBar; 18668}(Component); 18669 18670/** 18671 * Default options for `ControlBar` 18672 * 18673 * @type {Object} 18674 * @private 18675 */ 18676 18677 18678ControlBar.prototype.options_ = { 18679 children: ['playToggle', 'volumePanel', 'currentTimeDisplay', 'timeDivider', 'durationDisplay', 'progressControl', 'liveDisplay', 'remainingTimeDisplay', 'customControlSpacer', 'playbackRateMenuButton', 'chaptersButton', 'descriptionsButton', 'subsCapsButton', 'audioTrackButton', 'fullscreenToggle'] 18680}; 18681 18682Component.registerComponent('ControlBar', ControlBar); 18683 18684/** 18685 * @file error-display.js 18686 */ 18687/** 18688 * A display that indicates an error has occurred. This means that the video 18689 * is unplayable. 18690 * 18691 * @extends ModalDialog 18692 */ 18693 18694var ErrorDisplay = function (_ModalDialog) { 18695 inherits(ErrorDisplay, _ModalDialog); 18696 18697 /** 18698 * Creates an instance of this class. 18699 * 18700 * @param {Player} player 18701 * The `Player` that this class should be attached to. 18702 * 18703 * @param {Object} [options] 18704 * The key/value store of player options. 18705 */ 18706 function ErrorDisplay(player, options) { 18707 classCallCheck(this, ErrorDisplay); 18708 18709 var _this = possibleConstructorReturn(this, _ModalDialog.call(this, player, options)); 18710 18711 _this.on(player, 'error', _this.open); 18712 return _this; 18713 } 18714 18715 /** 18716 * Builds the default DOM `className`. 18717 * 18718 * @return {string} 18719 * The DOM `className` for this object. 18720 * 18721 * @deprecated Since version 5. 18722 */ 18723 18724 18725 ErrorDisplay.prototype.buildCSSClass = function buildCSSClass() { 18726 return 'vjs-error-display ' + _ModalDialog.prototype.buildCSSClass.call(this); 18727 }; 18728 18729 /** 18730 * Gets the localized error message based on the `Player`s error. 18731 * 18732 * @return {string} 18733 * The `Player`s error message localized or an empty string. 18734 */ 18735 18736 18737 ErrorDisplay.prototype.content = function content() { 18738 var error = this.player().error(); 18739 18740 return error ? this.localize(error.message) : ''; 18741 }; 18742 18743 return ErrorDisplay; 18744}(ModalDialog); 18745 18746/** 18747 * The default options for an `ErrorDisplay`. 18748 * 18749 * @private 18750 */ 18751 18752 18753ErrorDisplay.prototype.options_ = mergeOptions(ModalDialog.prototype.options_, {
vendor: 5,705 bytes, lines 18754-18960
18754 pauseOnOpen: false, 18755 fillAlways: true, 18756 temporary: false, 18757 uncloseable: true 18758}); 18759 18760Component.registerComponent('ErrorDisplay', ErrorDisplay); 18761 18762/** 18763 * @file text-track-settings.js 18764 */ 18765var LOCAL_STORAGE_KEY = 'vjs-text-track-settings'; 18766 18767var COLOR_BLACK = ['#000', 'Black']; 18768var COLOR_BLUE = ['#00F', 'Blue']; 18769var COLOR_CYAN = ['#0FF', 'Cyan']; 18770var COLOR_GREEN = ['#0F0', 'Green']; 18771var COLOR_MAGENTA = ['#F0F', 'Magenta']; 18772var COLOR_RED = ['#F00', 'Red']; 18773var COLOR_WHITE = ['#FFF', 'White']; 18774var COLOR_YELLOW = ['#FF0', 'Yellow']; 18775 18776var OPACITY_OPAQUE = ['1', 'Opaque']; 18777var OPACITY_SEMI = ['0.5', 'Semi-Transparent']; 18778var OPACITY_TRANS = ['0', 'Transparent']; 18779 18780// Configuration for the various <select> elements in the DOM of this component. 18781// 18782// Possible keys include: 18783// 18784// `default`: 18785// The default option index. Only needs to be provided if not zero. 18786// `parser`: 18787// A function which is used to parse the value from the selected option in 18788// a customized way. 18789// `selector`: 18790// The selector used to find the associated <select> element. 18791var selectConfigs = { 18792 backgroundColor: { 18793 selector: '.vjs-bg-color > select', 18794 id: 'captions-background-color-%s', 18795 label: 'Color', 18796 options: [COLOR_BLACK, COLOR_WHITE, COLOR_RED, COLOR_GREEN, COLOR_BLUE, COLOR_YELLOW, COLOR_MAGENTA, COLOR_CYAN] 18797 }, 18798 18799 backgroundOpacity: { 18800 selector: '.vjs-bg-opacity > select', 18801 id: 'captions-background-opacity-%s', 18802 label: 'Transparency', 18803 options: [OPACITY_OPAQUE, OPACITY_SEMI, OPACITY_TRANS] 18804 }, 18805 18806 color: { 18807 selector: '.vjs-fg-color > select', 18808 id: 'captions-foreground-color-%s', 18809 label: 'Color', 18810 options: [COLOR_WHITE, COLOR_BLACK, COLOR_RED, COLOR_GREEN, COLOR_BLUE, COLOR_YELLOW, COLOR_MAGENTA, COLOR_CYAN] 18811 }, 18812 18813 edgeStyle: { 18814 selector: '.vjs-edge-style > select', 18815 id: '%s', 18816 label: 'Text Edge Style', 18817 options: [['none', 'None'], ['raised', 'Raised'], ['depressed', 'Depressed'], ['uniform', 'Uniform'], ['dropshadow', 'Dropshadow']] 18818 }, 18819 18820 fontFamily: { 18821 selector: '.vjs-font-family > select', 18822 id: 'captions-font-family-%s', 18823 label: 'Font Family', 18824 options: [['proportionalSansSerif', 'Proportional Sans-Serif'], ['monospaceSansSerif', 'Monospace Sans-Serif'], ['proportionalSerif', 'Proportional Serif'], ['monospaceSerif', 'Monospace Serif'], ['casual', 'Casual'], ['script', 'Script'], ['small-caps', 'Small Caps']] 18825 }, 18826 18827 fontPercent: { 18828 selector: '.vjs-font-percent > select', 18829 id: 'captions-font-size-%s', 18830 label: 'Font Size', 18831 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%']], 18832 'default': 2, 18833 parser: function parser(v) { 18834 return v === '1.00' ? null : Number(v); 18835 } 18836 }, 18837 18838 textOpacity: { 18839 selector: '.vjs-text-opacity > select', 18840 id: 'captions-foreground-opacity-%s', 18841 label: 'Transparency', 18842 options: [OPACITY_OPAQUE, OPACITY_SEMI] 18843 }, 18844 18845 // Options for this object are defined below. 18846 windowColor: { 18847 selector: '.vjs-window-color > select', 18848 id: 'captions-window-color-%s', 18849 label: 'Color' 18850 }, 18851 18852 // Options for this object are defined below. 18853 windowOpacity: { 18854 selector: '.vjs-window-opacity > select', 18855 id: 'captions-window-opacity-%s', 18856 label: 'Transparency', 18857 options: [OPACITY_TRANS, OPACITY_SEMI, OPACITY_OPAQUE] 18858 } 18859}; 18860 18861selectConfigs.windowColor.options = selectConfigs.backgroundColor.options; 18862 18863/** 18864 * Get the actual value of an option. 18865 * 18866 * @param {string} value 18867 * The value to get 18868 * 18869 * @param {Function} [parser] 18870 * Optional function to adjust the value. 18871 * 18872 * @return {Mixed} 18873 * - Will be `undefined` if no value exists 18874 * - Will be `undefined` if the given value is "none". 18875 * - Will be the actual value otherwise. 18876 * 18877 * @private 18878 */ 18879function parseOptionValue(value, parser) { 18880 if (parser) { 18881 value = parser(value); 18882 } 18883 18884 if (value && value !== 'none') { 18885 return value; 18886 } 18887} 18888 18889/** 18890 * Gets the value of the selected <option> element within a <select> element. 18891 * 18892 * @param {Element} el 18893 * the element to look in 18894 * 18895 * @param {Function} [parser] 18896 * Optional function to adjust the value. 18897 * 18898 * @return {Mixed} 18899 * - Will be `undefined` if no value exists 18900 * - Will be `undefined` if the given value is "none". 18901 * - Will be the actual value otherwise. 18902 * 18903 * @private 18904 */ 18905function getSelectedOptionValue(el, parser) { 18906 var value = el.options[el.options.selectedIndex].value; 18907 18908 return parseOptionValue(value, parser); 18909} 18910 18911/** 18912 * Sets the selected <option> element within a <select> element based on a 18913 * given value. 18914 * 18915 * @param {Element} el 18916 * The element to look in. 18917 * 18918 * @param {string} value 18919 * the property to look on. 18920 * 18921 * @param {Function} [parser] 18922 * Optional function to adjust the value before comparing. 18923 * 18924 * @private 18925 */ 18926function setSelectedOption(el, value, parser) { 18927 if (!value) { 18928 return; 18929 } 18930 18931 for (var i = 0; i < el.options.length; i++) { 18932 if (parseOptionValue(el.options[i].value, parser) === value) { 18933 el.selectedIndex = i; 18934 break; 18935 } 18936 } 18937} 18938 18939/** 18940 * Manipulate Text Tracks settings. 18941 * 18942 * @extends ModalDialog 18943 */ 18944 18945var TextTrackSettings = function (_ModalDialog) { 18946 inherits(TextTrackSettings, _ModalDialog); 18947 18948 /** 18949 * Creates an instance of this class. 18950 * 18951 * @param {Player} player 18952 * The `Player` that this class should be attached to. 18953 * 18954 * @param {Object} [options] 18955 * The key/value store of player options. 18956 */ 18957 function TextTrackSettings(player, options) { 18958 classCallCheck(this, TextTrackSettings); 18959 18960 options.temporary = false;
vendor: 15,421 bytes, lines 18961-19455
18961 18962 var _this = possibleConstructorReturn(this, _ModalDialog.call(this, player, options)); 18963 18964 _this.updateDisplay = bind(_this, _this.updateDisplay); 18965 18966 // fill the modal and pretend we have opened it 18967 _this.fill(); 18968 _this.hasBeenOpened_ = _this.hasBeenFilled_ = true; 18969 18970 _this.endDialog = createEl('p', { 18971 className: 'vjs-control-text', 18972 textContent: _this.localize('End of dialog window.') 18973 }); 18974 _this.el().appendChild(_this.endDialog); 18975 18976 _this.setDefaults(); 18977 18978 // Grab `persistTextTrackSettings` from the player options if not passed in child options 18979 if (options.persistTextTrackSettings === undefined) { 18980 _this.options_.persistTextTrackSettings = _this.options_.playerOptions.persistTextTrackSettings; 18981 } 18982 18983 _this.on(_this.$('.vjs-done-button'), 'click', function () { 18984 _this.saveSettings(); 18985 _this.close(); 18986 }); 18987 18988 _this.on(_this.$('.vjs-default-button'), 'click', function () { 18989 _this.setDefaults(); 18990 _this.updateDisplay(); 18991 }); 18992 18993 each(selectConfigs, function (config) { 18994 _this.on(_this.$(config.selector), 'change', _this.updateDisplay); 18995 }); 18996 18997 if (_this.options_.persistTextTrackSettings) { 18998 _this.restoreSettings(); 18999 } 19000 return _this; 19001 } 19002 19003 TextTrackSettings.prototype.dispose = function dispose() { 19004 this.endDialog = null; 19005 19006 _ModalDialog.prototype.dispose.call(this); 19007 }; 19008 19009 /** 19010 * Create a <select> element with configured options. 19011 * 19012 * @param {string} key 19013 * Configuration key to use during creation. 19014 * 19015 * @return {string} 19016 * An HTML string. 19017 * 19018 * @private 19019 */ 19020 19021 19022 TextTrackSettings.prototype.createElSelect_ = function createElSelect_(key) { 19023 var _this2 = this; 19024 19025 var legendId = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : ''; 19026 var type = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'label'; 19027 19028 var config = selectConfigs[key]; 19029 var id = config.id.replace('%s', this.id_); 19030 var selectLabelledbyIds = [legendId, id].join(' ').trim(); 19031 19032 return ['<' + type + ' id="' + id + '" class="' + (type === 'label' ? 'vjs-label' : '') + '">', this.localize(config.label), '</' + type + '>', '<select aria-labelledby="' + selectLabelledbyIds + '">'].concat(config.options.map(function (o) { 19033 var optionId = id + '-' + o[1].replace(/\W+/g, ''); 19034 19035 return ['<option id="' + optionId + '" value="' + o[0] + '" ', 'aria-labelledby="' + selectLabelledbyIds + ' ' + optionId + '">', _this2.localize(o[1]), '</option>'].join(''); 19036 })).concat('</select>').join(''); 19037 }; 19038 19039 /** 19040 * Create foreground color element for the component 19041 * 19042 * @return {string} 19043 * An HTML string. 19044 * 19045 * @private 19046 */ 19047 19048 19049 TextTrackSettings.prototype.createElFgColor_ = function createElFgColor_() { 19050 var legendId = 'captions-text-legend-' + this.id_; 19051 19052 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(''); 19053 }; 19054 19055 /** 19056 * Create background color element for the component 19057 * 19058 * @return {string} 19059 * An HTML string. 19060 * 19061 * @private 19062 */ 19063 19064 19065 TextTrackSettings.prototype.createElBgColor_ = function createElBgColor_() { 19066 var legendId = 'captions-background-' + this.id_; 19067 19068 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(''); 19069 }; 19070 19071 /** 19072 * Create window color element for the component 19073 * 19074 * @return {string} 19075 * An HTML string. 19076 * 19077 * @private 19078 */ 19079 19080 19081 TextTrackSettings.prototype.createElWinColor_ = function createElWinColor_() { 19082 var legendId = 'captions-window-' + this.id_; 19083 19084 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(''); 19085 }; 19086 19087 /** 19088 * Create color elements for the component 19089 * 19090 * @return {Element} 19091 * The element that was created 19092 * 19093 * @private 19094 */ 19095 19096 19097 TextTrackSettings.prototype.createElColors_ = function createElColors_() { 19098 return createEl('div', { 19099 className: 'vjs-track-settings-colors', 19100 innerHTML: [this.createElFgColor_(), this.createElBgColor_(), this.createElWinColor_()].join('') 19101 }); 19102 }; 19103 19104 /** 19105 * Create font elements for the component 19106 * 19107 * @return {Element} 19108 * The element that was created. 19109 * 19110 * @private 19111 */ 19112 19113 19114 TextTrackSettings.prototype.createElFont_ = function createElFont_() { 19115 return createEl('div', { 19116 className: 'vjs-track-settings-font', 19117 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('') 19118 }); 19119 }; 19120 19121 /** 19122 * Create controls for the component 19123 * 19124 * @return {Element} 19125 * The element that was created. 19126 * 19127 * @private 19128 */ 19129 19130 19131 TextTrackSettings.prototype.createElControls_ = function createElControls_() { 19132 var defaultsDescription = this.localize('restore all settings to the default values'); 19133 19134 return createEl('div', { 19135 className: 'vjs-track-settings-controls', 19136 innerHTML: ['<button class="vjs-default-button" title="' + defaultsDescription + '">', this.localize('Reset'), '<span class="vjs-control-text"> ' + defaultsDescription + '</span>', '</button>', '<button class="vjs-done-button">' + this.localize('Done') + '</button>'].join('') 19137 }); 19138 }; 19139 19140 TextTrackSettings.prototype.content = function content() { 19141 return [this.createElColors_(), this.createElFont_(), this.createElControls_()]; 19142 }; 19143 19144 TextTrackSettings.prototype.label = function label() { 19145 return this.localize('Caption Settings Dialog'); 19146 }; 19147 19148 TextTrackSettings.prototype.description = function description() { 19149 return this.localize('Beginning of dialog window. Escape will cancel and close the window.'); 19150 }; 19151 19152 TextTrackSettings.prototype.buildCSSClass = function buildCSSClass() { 19153 return _ModalDialog.prototype.buildCSSClass.call(this) + ' vjs-text-track-settings'; 19154 }; 19155 19156 /** 19157 * Gets an object of text track settings (or null). 19158 * 19159 * @return {Object} 19160 * An object with config values parsed from the DOM or localStorage. 19161 */ 19162 19163 19164 TextTrackSettings.prototype.getValues = function getValues() { 19165 var _this3 = this; 19166 19167 return reduce(selectConfigs, function (accum, config, key) { 19168 var value = getSelectedOptionValue(_this3.$(config.selector), config.parser); 19169 19170 if (value !== undefined) { 19171 accum[key] = value; 19172 } 19173 19174 return accum; 19175 }, {}); 19176 }; 19177 19178 /** 19179 * Sets text track settings from an object of values. 19180 * 19181 * @param {Object} values 19182 * An object with config values parsed from the DOM or localStorage. 19183 */ 19184 19185 19186 TextTrackSettings.prototype.setValues = function setValues(values) { 19187 var _this4 = this; 19188 19189 each(selectConfigs, function (config, key) { 19190 setSelectedOption(_this4.$(config.selector), values[key], config.parser); 19191 }); 19192 }; 19193 19194 /** 19195 * Sets all `<select>` elements to their default values. 19196 */ 19197 19198 19199 TextTrackSettings.prototype.setDefaults = function setDefaults() { 19200 var _this5 = this; 19201 19202 each(selectConfigs, function (config) { 19203 var index = config.hasOwnProperty('default') ? config['default'] : 0; 19204 19205 _this5.$(config.selector).selectedIndex = index; 19206 }); 19207 }; 19208 19209 /** 19210 * Restore texttrack settings from localStorage 19211 */ 19212 19213 19214 TextTrackSettings.prototype.restoreSettings = function restoreSettings() { 19215 var values = void 0; 19216 19217 try { 19218 values = JSON.parse(window_1.localStorage.getItem(LOCAL_STORAGE_KEY)); 19219 } catch (err) { 19220 log$1.warn(err); 19221 } 19222 19223 if (values) { 19224 this.setValues(values); 19225 } 19226 }; 19227 19228 /** 19229 * Save text track settings to localStorage 19230 */ 19231 19232 19233 TextTrackSettings.prototype.saveSettings = function saveSettings() { 19234 if (!this.options_.persistTextTrackSettings) { 19235 return; 19236 } 19237 19238 var values = this.getValues(); 19239 19240 try { 19241 if (Object.keys(values).length) { 19242 window_1.localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(values)); 19243 } else { 19244 window_1.localStorage.removeItem(LOCAL_STORAGE_KEY); 19245 } 19246 } catch (err) { 19247 log$1.warn(err); 19248 } 19249 }; 19250 19251 /** 19252 * Update display of text track settings 19253 */ 19254 19255 19256 TextTrackSettings.prototype.updateDisplay = function updateDisplay() { 19257 var ttDisplay = this.player_.getChild('textTrackDisplay'); 19258 19259 if (ttDisplay) { 19260 ttDisplay.updateDisplay(); 19261 } 19262 }; 19263 19264 /** 19265 * conditionally blur the element and refocus the captions button 19266 * 19267 * @private 19268 */ 19269 19270 19271 TextTrackSettings.prototype.conditionalBlur_ = function conditionalBlur_() { 19272 this.previouslyActiveEl_ = null; 19273 this.off(document_1, 'keydown', this.handleKeyDown); 19274 19275 var cb = this.player_.controlBar; 19276 var subsCapsBtn = cb && cb.subsCapsButton; 19277 var ccBtn = cb && cb.captionsButton; 19278 19279 if (subsCapsBtn) { 19280 subsCapsBtn.focus(); 19281 } else if (ccBtn) { 19282 ccBtn.focus(); 19283 } 19284 }; 19285 19286 return TextTrackSettings; 19287}(ModalDialog); 19288 19289Component.registerComponent('TextTrackSettings', TextTrackSettings); 19290 19291/** 19292 * @file resize-manager.js 19293 */ 19294/** 19295 * A Resize Manager. It is in charge of triggering `playerresize` on the player in the right conditions. 19296 * 19297 * 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}. 19298 * 19299 * If the ResizeObserver is available natively, it will be used. A polyfill can be passed in as an option. 19300 * If a `playerresize` event is not needed, the ResizeManager component can be removed from the player, see the example below. 19301 * @example <caption>How to disable the resize manager</caption> 19302 * const player = videojs('#vid', { 19303 * resizeManager: false 19304 * }); 19305 * 19306 * @see {@link https://wicg.github.io/ResizeObserver/|ResizeObserver specification} 19307 * 19308 * @extends Component 19309 */ 19310 19311var ResizeManager = function (_Component) { 19312 inherits(ResizeManager, _Component); 19313 19314 /** 19315 * Create the ResizeManager. 19316 * 19317 * @param {Object} player 19318 * The `Player` that this class should be attached to. 19319 * 19320 * @param {Object} [options] 19321 * The key/value store of ResizeManager options. 19322 * 19323 * @param {Object} [options.ResizeObserver] 19324 * A polyfill for ResizeObserver can be passed in here. 19325 * If this is set to null it will ignore the native ResizeObserver and fall back to the iframe fallback. 19326 */ 19327 function ResizeManager(player, options) { 19328 classCallCheck(this, ResizeManager); 19329 19330 var RESIZE_OBSERVER_AVAILABLE = options.ResizeObserver || window_1.ResizeObserver; 19331 19332 // if `null` was passed, we want to disable the ResizeObserver 19333 if (options.ResizeObserver === null) { 19334 RESIZE_OBSERVER_AVAILABLE = false; 19335 } 19336 19337 // Only create an element when ResizeObserver isn't available 19338 var options_ = mergeOptions({ createEl: !RESIZE_OBSERVER_AVAILABLE }, options); 19339 19340 var _this = possibleConstructorReturn(this, _Component.call(this, player, options_)); 19341 19342 _this.ResizeObserver = options.ResizeObserver || window_1.ResizeObserver; 19343 _this.loadListener_ = null; 19344 _this.resizeObserver_ = null; 19345 _this.debouncedHandler_ = debounce(function () { 19346 _this.resizeHandler(); 19347 }, 100, false, player); 19348 19349 if (RESIZE_OBSERVER_AVAILABLE) { 19350 _this.resizeObserver_ = new _this.ResizeObserver(_this.debouncedHandler_); 19351 _this.resizeObserver_.observe(player.el()); 19352 } else { 19353 _this.loadListener_ = function () { 19354 if (_this.el_.contentWindow) { 19355 on(_this.el_.contentWindow, 'resize', _this.debouncedHandler_); 19356 } 19357 _this.off('load', _this.loadListener_); 19358 }; 19359 19360 _this.on('load', _this.loadListener_); 19361 } 19362 return _this; 19363 } 19364 19365 ResizeManager.prototype.createEl = function createEl() { 19366 return _Component.prototype.createEl.call(this, 'iframe', { 19367 className: 'vjs-resize-manager' 19368 }); 19369 }; 19370 19371 /** 19372 * Called when a resize is triggered on the iframe or a resize is observed via the ResizeObserver 19373 * 19374 * @fires Player#playerresize 19375 */ 19376 19377 19378 ResizeManager.prototype.resizeHandler = function resizeHandler() { 19379 /** 19380 * Called when the player size has changed 19381 * 19382 * @event Player#playerresize 19383 * @type {EventTarget~Event} 19384 */ 19385 this.player_.trigger('playerresize'); 19386 }; 19387 19388 ResizeManager.prototype.dispose = function dispose() { 19389 if (this.resizeObserver_) { 19390 if (this.player_.el()) { 19391 this.resizeObserver_.unobserve(this.player_.el()); 19392 } 19393 this.resizeObserver_.disconnect(); 19394 } 19395 19396 if (this.el_ && this.el_.contentWindow) { 19397 off(this.el_.contentWindow, 'resize', this.debouncedHandler_); 19398 } 19399 19400 if (this.loadListener_) { 19401 this.off('load', this.loadListener_); 19402 } 19403 19404 this.ResizeObserver = null; 19405 this.resizeObserver = null; 19406 this.debouncedHandler_ = null; 19407 this.loadListener_ = null; 19408 }; 19409 19410 return ResizeManager; 19411}(Component); 19412 19413Component.registerComponent('ResizeManager', ResizeManager); 19414 19415/** 19416 * This function is used to fire a sourceset when there is something 19417 * similar to `mediaEl.load()` being called. It will try to find the source via 19418 * the `src` attribute and then the `<source>` elements. It will then fire `sourceset` 19419 * with the source that was found or empty string if we cannot know. If it cannot 19420 * find a source then `sourceset` will not be fired. 19421 * 19422 * @param {Html5} tech 19423 * The tech object that sourceset was setup on 19424 * 19425 * @return {boolean} 19426 * returns false if the sourceset was not fired and true otherwise. 19427 */ 19428var sourcesetLoad = function sourcesetLoad(tech) { 19429 var el = tech.el(); 19430 19431 // if `el.src` is set, that source will be loaded. 19432 if (el.hasAttribute('src')) { 19433 tech.triggerSourceset(el.src); 19434 return true; 19435 } 19436 19437 /** 19438 * Since there isn't a src property on the media element, source elements will be used for 19439 * implementing the source selection algorithm. This happens asynchronously and 19440 * for most cases were there is more than one source we cannot tell what source will 19441 * be loaded, without re-implementing the source selection algorithm. At this time we are not 19442 * going to do that. There are three special cases that we do handle here though: 19443 * 19444 * 1. If there are no sources, do not fire `sourceset`. 19445 * 2. If there is only one `<source>` with a `src` property/attribute that is our `src` 19446 * 3. If there is more than one `<source>` but all of them have the same `src` url. 19447 * That will be our src. 19448 */ 19449 var sources = tech.$$('source'); 19450 var srcUrls = []; 19451 var src = ''; 19452 19453 // if there are no sources, do not fire sourceset 19454 if (!sources.length) { 19455 return false;
vendor: 4,312 bytes, lines 19456-19606
19456 } 19457 19458 // only count valid/non-duplicate source elements 19459 for (var i = 0; i < sources.length; i++) { 19460 var url = sources[i].src; 19461 19462 if (url && srcUrls.indexOf(url) === -1) { 19463 srcUrls.push(url); 19464 } 19465 } 19466 19467 // there were no valid sources 19468 if (!srcUrls.length) { 19469 return false; 19470 } 19471 19472 // there is only one valid source element url 19473 // use that 19474 if (srcUrls.length === 1) { 19475 src = srcUrls[0]; 19476 } 19477 19478 tech.triggerSourceset(src); 19479 return true; 19480}; 19481 19482/** 19483 * our implementation of an `innerHTML` descriptor for browsers 19484 * that do not have one. 19485 */ 19486var innerHTMLDescriptorPolyfill = {}; 19487 19488if (!IS_IE8) { 19489 innerHTMLDescriptorPolyfill = Object.defineProperty({}, 'innerHTML', { 19490 get: function get() { 19491 return this.cloneNode(true).innerHTML; 19492 }, 19493 set: function set(v) { 19494 // make a dummy node to use innerHTML on 19495 var dummy = document_1.createElement(this.nodeName.toLowerCase()); 19496 19497 // set innerHTML to the value provided 19498 dummy.innerHTML = v; 19499 19500 // make a document fragment to hold the nodes from dummy 19501 var docFrag = document_1.createDocumentFragment(); 19502 19503 // copy all of the nodes created by the innerHTML on dummy 19504 // to the document fragment 19505 while (dummy.childNodes.length) { 19506 docFrag.appendChild(dummy.childNodes[0]); 19507 } 19508 19509 // remove content 19510 this.innerText = ''; 19511 19512 // now we add all of that html in one by appending the 19513 // document fragment. This is how innerHTML does it. 19514 window_1.Element.prototype.appendChild.call(this, docFrag); 19515 19516 // then return the result that innerHTML's setter would 19517 return this.innerHTML; 19518 } 19519 }); 19520} 19521/** 19522 * Get a property descriptor given a list of priorities and the 19523 * property to get. 19524 */ 19525var getDescriptor = function getDescriptor(priority, prop) { 19526 var descriptor = {}; 19527 19528 for (var i = 0; i < priority.length; i++) { 19529 descriptor = Object.getOwnPropertyDescriptor(priority[i], prop); 19530 19531 if (descriptor && descriptor.set && descriptor.get) { 19532 break; 19533 } 19534 } 19535 19536 descriptor.enumerable = true; 19537 descriptor.configurable = true; 19538 19539 return descriptor; 19540}; 19541 19542var getInnerHTMLDescriptor = function getInnerHTMLDescriptor(tech) { 19543 return getDescriptor([tech.el(), window_1.HTMLMediaElement.prototype, window_1.Element.prototype, innerHTMLDescriptorPolyfill], 'innerHTML'); 19544}; 19545 19546/** 19547 * Patches browser internal functions so that we can tell syncronously 19548 * if a `<source>` was appended to the media element. For some reason this 19549 * causes a `sourceset` if the the media element is ready and has no source. 19550 * This happens when: 19551 * - The page has just loaded and the media element does not have a source. 19552 * - The media element was emptied of all sources, then `load()` was called. 19553 * 19554 * It does this by patching the following functions/properties when they are supported: 19555 * 19556 * - `append()` - can be used to add a `<source>` element to the media element 19557 * - `appendChild()` - can be used to add a `<source>` element to the media element 19558 * - `insertAdjacentHTML()` - can be used to add a `<source>` element to the media element 19559 * - `innerHTML` - can be used to add a `<source>` element to the media element 19560 * 19561 * @param {Html5} tech 19562 * The tech object that sourceset is being setup on. 19563 */ 19564var firstSourceWatch = function firstSourceWatch(tech) { 19565 var el = tech.el(); 19566 19567 // make sure firstSourceWatch isn't setup twice. 19568 if (el.resetSourceWatch_) { 19569 return; 19570 } 19571 19572 var old = {}; 19573 var innerDescriptor = getInnerHTMLDescriptor(tech); 19574 var appendWrapper = function appendWrapper(appendFn) { 19575 return function () { 19576 for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { 19577 args[_key] = arguments[_key]; 19578 } 19579 19580 var retval = appendFn.apply(el, args); 19581 19582 sourcesetLoad(tech); 19583 19584 return retval; 19585 }; 19586 }; 19587 19588 ['append', 'appendChild', 'insertAdjacentHTML'].forEach(function (k) { 19589 if (!el[k]) { 19590 return; 19591 } 19592 19593 // store the old function 19594 old[k] = el[k]; 19595 19596 // call the old function with a sourceset if a source 19597 // was loaded 19598 el[k] = appendWrapper(old[k]); 19599 }); 19600 19601 Object.defineProperty(el, 'innerHTML', mergeOptions(innerDescriptor, { 19602 set: appendWrapper(innerDescriptor.set) 19603 })); 19604 19605 el.resetSourceWatch_ = function () { 19606 el.resetSourceWatch_ = null;
vendor: 33,747 bytes, lines 19607-20726
19607 Object.keys(old).forEach(function (k) { 19608 el[k] = old[k]; 19609 }); 19610 19611 Object.defineProperty(el, 'innerHTML', innerDescriptor); 19612 }; 19613 19614 // on the first sourceset, we need to revert our changes 19615 tech.one('sourceset', el.resetSourceWatch_); 19616}; 19617 19618/** 19619 * our implementation of a `src` descriptor for browsers 19620 * that do not have one. 19621 */ 19622 19623var srcDescriptorPolyfill = {}; 19624 19625if (!IS_IE8) { 19626 srcDescriptorPolyfill = Object.defineProperty({}, 'src', { 19627 get: function get() { 19628 if (this.hasAttribute('src')) { 19629 return getAbsoluteURL(window_1.Element.prototype.getAttribute.call(this, 'src')); 19630 } 19631 19632 return ''; 19633 }, 19634 set: function set(v) { 19635 window_1.Element.prototype.setAttribute.call(this, 'src', v); 19636 19637 return v; 19638 } 19639 }); 19640} 19641 19642var getSrcDescriptor = function getSrcDescriptor(tech) { 19643 return getDescriptor([tech.el(), window_1.HTMLMediaElement.prototype, srcDescriptorPolyfill], 'src'); 19644}; 19645 19646/** 19647 * setup `sourceset` handling on the `Html5` tech. This function 19648 * patches the following element properties/functions: 19649 * 19650 * - `src` - to determine when `src` is set 19651 * - `setAttribute()` - to determine when `src` is set 19652 * - `load()` - this re-triggers the source selection algorithm, and can 19653 * cause a sourceset. 19654 * 19655 * If there is no source when we are adding `sourceset` support or during a `load()` 19656 * we also patch the functions listed in `firstSourceWatch`. 19657 * 19658 * @param {Html5} tech 19659 * The tech to patch 19660 */ 19661var setupSourceset = function setupSourceset(tech) { 19662 if (!tech.featuresSourceset) { 19663 return; 19664 } 19665 19666 var el = tech.el(); 19667 19668 // make sure sourceset isn't setup twice. 19669 if (el.resetSourceset_) { 19670 return; 19671 } 19672 19673 var srcDescriptor = getSrcDescriptor(tech); 19674 var oldSetAttribute = el.setAttribute; 19675 var oldLoad = el.load; 19676 19677 Object.defineProperty(el, 'src', mergeOptions(srcDescriptor, { 19678 set: function set(v) { 19679 var retval = srcDescriptor.set.call(el, v); 19680 19681 // we use the getter here to get the actual value set on src 19682 tech.triggerSourceset(el.src); 19683 19684 return retval; 19685 } 19686 })); 19687 19688 el.setAttribute = function (n, v) { 19689 var retval = oldSetAttribute.call(el, n, v); 19690 19691 if (/src/i.test(n)) { 19692 tech.triggerSourceset(el.src); 19693 } 19694 19695 return retval; 19696 }; 19697 19698 el.load = function () { 19699 var retval = oldLoad.call(el); 19700 19701 // if load was called, but there was no source to fire 19702 // sourceset on. We have to watch for a source append 19703 // as that can trigger a `sourceset` when the media element 19704 // has no source 19705 if (!sourcesetLoad(tech)) { 19706 tech.triggerSourceset(''); 19707 firstSourceWatch(tech); 19708 } 19709 19710 return retval; 19711 }; 19712 19713 if (el.currentSrc) { 19714 tech.triggerSourceset(el.currentSrc); 19715 } else if (!sourcesetLoad(tech)) { 19716 firstSourceWatch(tech); 19717 } 19718 19719 el.resetSourceset_ = function () { 19720 el.resetSourceset_ = null; 19721 el.load = oldLoad; 19722 el.setAttribute = oldSetAttribute; 19723 Object.defineProperty(el, 'src', srcDescriptor); 19724 if (el.resetSourceWatch_) { 19725 el.resetSourceWatch_(); 19726 } 19727 }; 19728}; 19729 19730var _templateObject$2 = taggedTemplateLiteralLoose(['Text Tracks are being loaded from another origin but the crossorigin attribute isn\'t used.\n This may prevent text tracks from loading.'], ['Text Tracks are being loaded from another origin but the crossorigin attribute isn\'t used.\n This may prevent text tracks from loading.']); 19731 19732/** 19733 * @file html5.js 19734 */ 19735/** 19736 * HTML5 Media Controller - Wrapper for HTML5 Media API 19737 * 19738 * @mixes Tech~SouceHandlerAdditions 19739 * @extends Tech 19740 */ 19741 19742var Html5 = function (_Tech) { 19743 inherits(Html5, _Tech); 19744 19745 /** 19746 * Create an instance of this Tech. 19747 * 19748 * @param {Object} [options] 19749 * The key/value store of player options. 19750 * 19751 * @param {Component~ReadyCallback} ready 19752 * Callback function to call when the `HTML5` Tech is ready. 19753 */ 19754 function Html5(options, ready) { 19755 classCallCheck(this, Html5); 19756 19757 var _this = possibleConstructorReturn(this, _Tech.call(this, options, ready)); 19758 19759 var source = options.source; 19760 var crossoriginTracks = false; 19761 19762 // Set the source if one is provided 19763 // 1) Check if the source is new (if not, we want to keep the original so playback isn't interrupted) 19764 // 2) Check to see if the network state of the tag was failed at init, and if so, reset the source 19765 // anyway so the error gets fired. 19766 if (source && (_this.el_.currentSrc !== source.src || options.tag && options.tag.initNetworkState_ === 3)) { 19767 _this.setSource(source); 19768 } else { 19769 _this.handleLateInit_(_this.el_); 19770 } 19771 19772 // setup sourceset after late sourceset/init 19773 if (options.enableSourceset) { 19774 _this.setupSourcesetHandling_(); 19775 } 19776 19777 if (_this.el_.hasChildNodes()) { 19778 19779 var nodes = _this.el_.childNodes; 19780 var nodesLength = nodes.length; 19781 var removeNodes = []; 19782 19783 while (nodesLength--) { 19784 var node = nodes[nodesLength]; 19785 var nodeName = node.nodeName.toLowerCase(); 19786 19787 if (nodeName === 'track') { 19788 if (!_this.featuresNativeTextTracks) { 19789 // Empty video tag tracks so the built-in player doesn't use them also. 19790 // This may not be fast enough to stop HTML5 browsers from reading the tags 19791 // so we'll need to turn off any default tracks if we're manually doing 19792 // captions and subtitles. videoElement.textTracks 19793 removeNodes.push(node); 19794 } else { 19795 // store HTMLTrackElement and TextTrack to remote list 19796 _this.remoteTextTrackEls().addTrackElement_(node); 19797 _this.remoteTextTracks().addTrack(node.track); 19798 _this.textTracks().addTrack(node.track); 19799 if (!crossoriginTracks && !_this.el_.hasAttribute('crossorigin') && isCrossOrigin(node.src)) { 19800 crossoriginTracks = true; 19801 } 19802 } 19803 } 19804 } 19805 19806 for (var i = 0; i < removeNodes.length; i++) { 19807 _this.el_.removeChild(removeNodes[i]); 19808 } 19809 } 19810 19811 _this.proxyNativeTracks_(); 19812 if (_this.featuresNativeTextTracks && crossoriginTracks) { 19813 log$1.warn(tsml(_templateObject$2)); 19814 } 19815 19816 // prevent iOS Safari from disabling metadata text tracks during native playback 19817 _this.restoreMetadataTracksInIOSNativePlayer_(); 19818 19819 // Determine if native controls should be used 19820 // Our goal should be to get the custom controls on mobile solid everywhere 19821 // so we can remove this all together. Right now this will block custom 19822 // controls on touch enabled laptops like the Chrome Pixel 19823 if ((TOUCH_ENABLED || IS_IPHONE || IS_NATIVE_ANDROID) && options.nativeControlsForTouch === true) { 19824 _this.setControls(true); 19825 } 19826 19827 // on iOS, we want to proxy `webkitbeginfullscreen` and `webkitendfullscreen` 19828 // into a `fullscreenchange` event 19829 _this.proxyWebkitFullscreen_(); 19830 19831 _this.triggerReady(); 19832 return _this; 19833 } 19834 19835 /** 19836 * Dispose of `HTML5` media element and remove all tracks. 19837 */ 19838 19839 19840 Html5.prototype.dispose = function dispose() { 19841 if (this.el_ && this.el_.resetSourceset_) { 19842 this.el_.resetSourceset_(); 19843 } 19844 Html5.disposeMediaElement(this.el_); 19845 this.options_ = null; 19846 19847 // tech will handle clearing of the emulated track list 19848 _Tech.prototype.dispose.call(this); 19849 }; 19850 19851 /** 19852 * Modify the media element so that we can detect when 19853 * the source is changed. Fires `sourceset` just after the source has changed 19854 */ 19855 19856 19857 Html5.prototype.setupSourcesetHandling_ = function setupSourcesetHandling_() { 19858 setupSourceset(this); 19859 }; 19860 19861 /** 19862 * When a captions track is enabled in the iOS Safari native player, all other 19863 * tracks are disabled (including metadata tracks), which nulls all of their 19864 * associated cue points. This will restore metadata tracks to their pre-fullscreen 19865 * state in those cases so that cue points are not needlessly lost. 19866 * 19867 * @private 19868 */ 19869 19870 19871 Html5.prototype.restoreMetadataTracksInIOSNativePlayer_ = function restoreMetadataTracksInIOSNativePlayer_() { 19872 var textTracks = this.textTracks(); 19873 var metadataTracksPreFullscreenState = void 0; 19874 19875 // captures a snapshot of every metadata track's current state 19876 var takeMetadataTrackSnapshot = function takeMetadataTrackSnapshot() { 19877 metadataTracksPreFullscreenState = []; 19878 19879 for (var i = 0; i < textTracks.length; i++) { 19880 var track = textTracks[i]; 19881 19882 if (track.kind === 'metadata') { 19883 metadataTracksPreFullscreenState.push({ 19884 track: track, 19885 storedMode: track.mode 19886 }); 19887 } 19888 } 19889 }; 19890 19891 // snapshot each metadata track's initial state, and update the snapshot 19892 // each time there is a track 'change' event 19893 takeMetadataTrackSnapshot(); 19894 textTracks.addEventListener('change', takeMetadataTrackSnapshot); 19895 19896 this.on('dispose', function () { 19897 return textTracks.removeEventListener('change', takeMetadataTrackSnapshot); 19898 }); 19899 19900 var restoreTrackMode = function restoreTrackMode() { 19901 for (var i = 0; i < metadataTracksPreFullscreenState.length; i++) { 19902 var storedTrack = metadataTracksPreFullscreenState[i]; 19903 19904 if (storedTrack.track.mode === 'disabled' && storedTrack.track.mode !== storedTrack.storedMode) { 19905 storedTrack.track.mode = storedTrack.storedMode; 19906 } 19907 } 19908 // we only want this handler to be executed on the first 'change' event 19909 textTracks.removeEventListener('change', restoreTrackMode); 19910 }; 19911 19912 // when we enter fullscreen playback, stop updating the snapshot and 19913 // restore all track modes to their pre-fullscreen state 19914 this.on('webkitbeginfullscreen', function () { 19915 textTracks.removeEventListener('change', takeMetadataTrackSnapshot); 19916 19917 // remove the listener before adding it just in case it wasn't previously removed 19918 textTracks.removeEventListener('change', restoreTrackMode); 19919 textTracks.addEventListener('change', restoreTrackMode); 19920 }); 19921 19922 // start updating the snapshot again after leaving fullscreen 19923 this.on('webkitendfullscreen', function () { 19924 // remove the listener before adding it just in case it wasn't previously removed 19925 textTracks.removeEventListener('change', takeMetadataTrackSnapshot); 19926 textTracks.addEventListener('change', takeMetadataTrackSnapshot); 19927 19928 // remove the restoreTrackMode handler in case it wasn't triggered during fullscreen playback 19929 textTracks.removeEventListener('change', restoreTrackMode); 19930 }); 19931 }; 19932 19933 /** 19934 * Proxy all native track list events to our track lists if the browser we are playing 19935 * in supports that type of track list. 19936 * 19937 * @private 19938 */ 19939 19940 19941 Html5.prototype.proxyNativeTracks_ = function proxyNativeTracks_() { 19942 var _this2 = this; 19943 19944 NORMAL.names.forEach(function (name) { 19945 var props = NORMAL[name]; 19946 var elTracks = _this2.el()[props.getterName]; 19947 var techTracks = _this2[props.getterName](); 19948 19949 if (!_this2['featuresNative' + props.capitalName + 'Tracks'] || !elTracks || !elTracks.addEventListener) { 19950 return; 19951 } 19952 var listeners = { 19953 change: function change(e) { 19954 techTracks.trigger({ 19955 type: 'change', 19956 target: techTracks, 19957 currentTarget: techTracks, 19958 srcElement: techTracks 19959 }); 19960 }, 19961 addtrack: function addtrack(e) { 19962 techTracks.addTrack(e.track); 19963 }, 19964 removetrack: function removetrack(e) { 19965 techTracks.removeTrack(e.track); 19966 } 19967 }; 19968 var removeOldTracks = function removeOldTracks() { 19969 var removeTracks = []; 19970 19971 for (var i = 0; i < techTracks.length; i++) { 19972 var found = false; 19973 19974 for (var j = 0; j < elTracks.length; j++) { 19975 if (elTracks[j] === techTracks[i]) { 19976 found = true; 19977 break; 19978 } 19979 } 19980 19981 if (!found) { 19982 removeTracks.push(techTracks[i]); 19983 } 19984 } 19985 19986 while (removeTracks.length) { 19987 techTracks.removeTrack(removeTracks.shift()); 19988 } 19989 }; 19990 19991 Object.keys(listeners).forEach(function (eventName) { 19992 var listener = listeners[eventName]; 19993 19994 elTracks.addEventListener(eventName, listener); 19995 _this2.on('dispose', function (e) { 19996 return elTracks.removeEventListener(eventName, listener); 19997 }); 19998 }); 19999 20000 // Remove (native) tracks that are not used anymore 20001 _this2.on('loadstart', removeOldTracks); 20002 _this2.on('dispose', function (e) { 20003 return _this2.off('loadstart', removeOldTracks); 20004 }); 20005 }); 20006 }; 20007 20008 /** 20009 * Create the `Html5` Tech's DOM element. 20010 * 20011 * @return {Element} 20012 * The element that gets created. 20013 */ 20014 20015 20016 Html5.prototype.createEl = function createEl$$1() { 20017 var el = this.options_.tag; 20018 20019 // Check if this browser supports moving the element into the box. 20020 // On the iPhone video will break if you move the element, 20021 // So we have to create a brand new element. 20022 // If we ingested the player div, we do not need to move the media element. 20023 if (!el || !(this.options_.playerElIngest || this.movingMediaElementInDOM)) { 20024 20025 // If the original tag is still there, clone and remove it. 20026 if (el) { 20027 var clone = el.cloneNode(true); 20028 20029 if (el.parentNode) { 20030 el.parentNode.insertBefore(clone, el); 20031 } 20032 Html5.disposeMediaElement(el); 20033 el = clone; 20034 } else { 20035 el = document_1.createElement('video'); 20036 20037 // determine if native controls should be used 20038 var tagAttributes = this.options_.tag && getAttributes(this.options_.tag); 20039 var attributes = mergeOptions({}, tagAttributes); 20040 20041 if (!TOUCH_ENABLED || this.options_.nativeControlsForTouch !== true) { 20042 delete attributes.controls; 20043 } 20044 20045 setAttributes(el, assign(attributes, { 20046 id: this.options_.techId, 20047 'class': 'vjs-tech' 20048 })); 20049 } 20050 20051 el.playerId = this.options_.playerId; 20052 } 20053 20054 if (typeof this.options_.preload !== 'undefined') { 20055 setAttribute(el, 'preload', this.options_.preload); 20056 } 20057 20058 // Update specific tag settings, in case they were overridden 20059 // `autoplay` has to be *last* so that `muted` and `playsinline` are present 20060 // when iOS/Safari or other browsers attempt to autoplay. 20061 var settingsAttrs = ['loop', 'muted', 'playsinline', 'autoplay']; 20062 20063 for (var i = 0; i < settingsAttrs.length; i++) { 20064 var attr = settingsAttrs[i]; 20065 var value = this.options_[attr]; 20066 20067 if (typeof value !== 'undefined') { 20068 if (value) { 20069 setAttribute(el, attr, attr); 20070 } else { 20071 removeAttribute(el, attr); 20072 } 20073 el[attr] = value; 20074 } 20075 } 20076 20077 return el; 20078 }; 20079 20080 /** 20081 * This will be triggered if the loadstart event has already fired, before videojs was 20082 * ready. Two known examples of when this can happen are: 20083 * 1. If we're loading the playback object after it has started loading 20084 * 2. The media is already playing the (often with autoplay on) then 20085 * 20086 * This function will fire another loadstart so that videojs can catchup. 20087 * 20088 * @fires Tech#loadstart 20089 * 20090 * @return {undefined} 20091 * returns nothing. 20092 */ 20093 20094 20095 Html5.prototype.handleLateInit_ = function handleLateInit_(el) { 20096 if (el.networkState === 0 || el.networkState === 3) { 20097 // The video element hasn't started loading the source yet 20098 // or didn't find a source 20099 return; 20100 } 20101 20102 if (el.readyState === 0) { 20103 // NetworkState is set synchronously BUT loadstart is fired at the 20104 // end of the current stack, usually before setInterval(fn, 0). 20105 // So at this point we know loadstart may have already fired or is 20106 // about to fire, and either way the player hasn't seen it yet. 20107 // We don't want to fire loadstart prematurely here and cause a 20108 // double loadstart so we'll wait and see if it happens between now 20109 // and the next loop, and fire it if not. 20110 // HOWEVER, we also want to make sure it fires before loadedmetadata 20111 // which could also happen between now and the next loop, so we'll 20112 // watch for that also. 20113 var loadstartFired = false; 20114 var setLoadstartFired = function setLoadstartFired() { 20115 loadstartFired = true; 20116 }; 20117 20118 this.on('loadstart', setLoadstartFired); 20119 20120 var triggerLoadstart = function triggerLoadstart() { 20121 // We did miss the original loadstart. Make sure the player 20122 // sees loadstart before loadedmetadata 20123 if (!loadstartFired) { 20124 this.trigger('loadstart'); 20125 } 20126 }; 20127 20128 this.on('loadedmetadata', triggerLoadstart); 20129 20130 this.ready(function () { 20131 this.off('loadstart', setLoadstartFired); 20132 this.off('loadedmetadata', triggerLoadstart); 20133 20134 if (!loadstartFired) { 20135 // We did miss the original native loadstart. Fire it now. 20136 this.trigger('loadstart'); 20137 } 20138 }); 20139 20140 return; 20141 } 20142 20143 // From here on we know that loadstart already fired and we missed it. 20144 // The other readyState events aren't as much of a problem if we double 20145 // them, so not going to go to as much trouble as loadstart to prevent 20146 // that unless we find reason to. 20147 var eventsToTrigger = ['loadstart']; 20148 20149 // loadedmetadata: newly equal to HAVE_METADATA (1) or greater 20150 eventsToTrigger.push('loadedmetadata'); 20151 20152 // loadeddata: newly increased to HAVE_CURRENT_DATA (2) or greater 20153 if (el.readyState >= 2) { 20154 eventsToTrigger.push('loadeddata'); 20155 } 20156 20157 // canplay: newly increased to HAVE_FUTURE_DATA (3) or greater 20158 if (el.readyState >= 3) { 20159 eventsToTrigger.push('canplay'); 20160 } 20161 20162 // canplaythrough: newly equal to HAVE_ENOUGH_DATA (4) 20163 if (el.readyState >= 4) { 20164 eventsToTrigger.push('canplaythrough'); 20165 } 20166 20167 // We still need to give the player time to add event listeners 20168 this.ready(function () { 20169 eventsToTrigger.forEach(function (type) { 20170 this.trigger(type); 20171 }, this); 20172 }); 20173 }; 20174 20175 /** 20176 * Set current time for the `HTML5` tech. 20177 * 20178 * @param {number} seconds 20179 * Set the current time of the media to this. 20180 */ 20181 20182 20183 Html5.prototype.setCurrentTime = function setCurrentTime(seconds) { 20184 try { 20185 this.el_.currentTime = seconds; 20186 } catch (e) { 20187 log$1(e, 'Video is not ready. (Video.js)'); 20188 // this.warning(VideoJS.warnings.videoNotReady); 20189 } 20190 }; 20191 20192 /** 20193 * Get the current duration of the HTML5 media element. 20194 * 20195 * @return {number} 20196 * The duration of the media or 0 if there is no duration. 20197 */ 20198 20199 20200 Html5.prototype.duration = function duration() { 20201 var _this3 = this; 20202 20203 // Android Chrome will report duration as Infinity for VOD HLS until after 20204 // playback has started, which triggers the live display erroneously. 20205 // Return NaN if playback has not started and trigger a durationupdate once 20206 // the duration can be reliably known. 20207 if (this.el_.duration === Infinity && IS_ANDROID && IS_CHROME && this.el_.currentTime === 0) { 20208 // Wait for the first `timeupdate` with currentTime > 0 - there may be 20209 // several with 0 20210 var checkProgress = function checkProgress() { 20211 if (_this3.el_.currentTime > 0) { 20212 // Trigger durationchange for genuinely live video 20213 if (_this3.el_.duration === Infinity) { 20214 _this3.trigger('durationchange'); 20215 } 20216 _this3.off('timeupdate', checkProgress); 20217 } 20218 }; 20219 20220 this.on('timeupdate', checkProgress); 20221 return NaN; 20222 } 20223 return this.el_.duration || NaN; 20224 }; 20225 20226 /** 20227 * Get the current width of the HTML5 media element. 20228 * 20229 * @return {number} 20230 * The width of the HTML5 media element. 20231 */ 20232 20233 20234 Html5.prototype.width = function width() { 20235 return this.el_.offsetWidth; 20236 }; 20237 20238 /** 20239 * Get the current height of the HTML5 media element. 20240 * 20241 * @return {number} 20242 * The heigth of the HTML5 media element. 20243 */ 20244 20245 20246 Html5.prototype.height = function height() { 20247 return this.el_.offsetHeight; 20248 }; 20249 20250 /** 20251 * Proxy iOS `webkitbeginfullscreen` and `webkitendfullscreen` into 20252 * `fullscreenchange` event. 20253 * 20254 * @private 20255 * @fires fullscreenchange 20256 * @listens webkitendfullscreen 20257 * @listens webkitbeginfullscreen 20258 * @listens webkitbeginfullscreen 20259 */ 20260 20261 20262 Html5.prototype.proxyWebkitFullscreen_ = function proxyWebkitFullscreen_() { 20263 var _this4 = this; 20264 20265 if (!('webkitDisplayingFullscreen' in this.el_)) { 20266 return; 20267 } 20268 20269 var endFn = function endFn() { 20270 this.trigger('fullscreenchange', { isFullscreen: false }); 20271 }; 20272 20273 var beginFn = function beginFn() { 20274 if ('webkitPresentationMode' in this.el_ && this.el_.webkitPresentationMode !== 'picture-in-picture') { 20275 this.one('webkitendfullscreen', endFn); 20276 20277 this.trigger('fullscreenchange', { isFullscreen: true }); 20278 } 20279 }; 20280 20281 this.on('webkitbeginfullscreen', beginFn); 20282 this.on('dispose', function () { 20283 _this4.off('webkitbeginfullscreen', beginFn); 20284 _this4.off('webkitendfullscreen', endFn); 20285 }); 20286 }; 20287 20288 /** 20289 * Check if fullscreen is supported on the current playback device. 20290 * 20291 * @return {boolean} 20292 * - True if fullscreen is supported. 20293 * - False if fullscreen is not supported. 20294 */ 20295 20296 20297 Html5.prototype.supportsFullScreen = function supportsFullScreen() { 20298 if (typeof this.el_.webkitEnterFullScreen === 'function') { 20299 var userAgent = window_1.navigator && window_1.navigator.userAgent || ''; 20300 20301 // Seems to be broken in Chromium/Chrome && Safari in Leopard 20302 if (/Android/.test(userAgent) || !/Chrome|Mac OS X 10.5/.test(userAgent)) { 20303 return true; 20304 } 20305 } 20306 return false; 20307 }; 20308 20309 /** 20310 * Request that the `HTML5` Tech enter fullscreen. 20311 */ 20312 20313 20314 Html5.prototype.enterFullScreen = function enterFullScreen() { 20315 var video = this.el_; 20316 20317 if (video.paused && video.networkState <= video.HAVE_METADATA) { 20318 // attempt to prime the video element for programmatic access 20319 // this isn't necessary on the desktop but shouldn't hurt 20320 this.el_.play(); 20321 20322 // playing and pausing synchronously during the transition to fullscreen 20323 // can get iOS ~6.1 devices into a play/pause loop 20324 this.setTimeout(function () { 20325 video.pause(); 20326 video.webkitEnterFullScreen(); 20327 }, 0); 20328 } else { 20329 video.webkitEnterFullScreen(); 20330 } 20331 }; 20332 20333 /** 20334 * Request that the `HTML5` Tech exit fullscreen. 20335 */ 20336 20337 20338 Html5.prototype.exitFullScreen = function exitFullScreen() { 20339 this.el_.webkitExitFullScreen(); 20340 }; 20341 20342 /** 20343 * A getter/setter for the `Html5` Tech's source object. 20344 * > Note: Please use {@link Html5#setSource} 20345 * 20346 * @param {Tech~SourceObject} [src] 20347 * The source object you want to set on the `HTML5` techs element. 20348 * 20349 * @return {Tech~SourceObject|undefined} 20350 * - The current source object when a source is not passed in. 20351 * - undefined when setting 20352 * 20353 * @deprecated Since version 5. 20354 */ 20355 20356 20357 Html5.prototype.src = function src(_src) { 20358 if (_src === undefined) { 20359 return this.el_.src; 20360 } 20361 20362 // Setting src through `src` instead of `setSrc` will be deprecated 20363 this.setSrc(_src); 20364 }; 20365 20366 /** 20367 * Reset the tech by removing all sources and then calling 20368 * {@link Html5.resetMediaElement}. 20369 */ 20370 20371 20372 Html5.prototype.reset = function reset() { 20373 Html5.resetMediaElement(this.el_); 20374 }; 20375 20376 /** 20377 * Get the current source on the `HTML5` Tech. Falls back to returning the source from 20378 * the HTML5 media element. 20379 * 20380 * @return {Tech~SourceObject} 20381 * The current source object from the HTML5 tech. With a fallback to the 20382 * elements source. 20383 */ 20384 20385 20386 Html5.prototype.currentSrc = function currentSrc() { 20387 if (this.currentSource_) { 20388 return this.currentSource_.src; 20389 } 20390 return this.el_.currentSrc; 20391 }; 20392 20393 /** 20394 * Set controls attribute for the HTML5 media Element. 20395 * 20396 * @param {string} val 20397 * Value to set the controls attribute to 20398 */ 20399 20400 20401 Html5.prototype.setControls = function setControls(val) { 20402 this.el_.controls = !!val; 20403 }; 20404 20405 /** 20406 * Create and returns a remote {@link TextTrack} object. 20407 * 20408 * @param {string} kind 20409 * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata) 20410 * 20411 * @param {string} [label] 20412 * Label to identify the text track 20413 * 20414 * @param {string} [language] 20415 * Two letter language abbreviation 20416 * 20417 * @return {TextTrack} 20418 * The TextTrack that gets created. 20419 */ 20420 20421 20422 Html5.prototype.addTextTrack = function addTextTrack(kind, label, language) { 20423 if (!this.featuresNativeTextTracks) { 20424 return _Tech.prototype.addTextTrack.call(this, kind, label, language); 20425 } 20426 20427 return this.el_.addTextTrack(kind, label, language); 20428 }; 20429 20430 /** 20431 * Creates either native TextTrack or an emulated TextTrack depending 20432 * on the value of `featuresNativeTextTracks` 20433 * 20434 * @param {Object} options 20435 * The object should contain the options to intialize the TextTrack with. 20436 * 20437 * @param {string} [options.kind] 20438 * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata). 20439 * 20440 * @param {string} [options.label]. 20441 * Label to identify the text track 20442 * 20443 * @param {string} [options.language] 20444 * Two letter language abbreviation. 20445 * 20446 * @param {boolean} [options.default] 20447 * Default this track to on. 20448 * 20449 * @param {string} [options.id] 20450 * The internal id to assign this track. 20451 * 20452 * @param {string} [options.src] 20453 * A source url for the track. 20454 * 20455 * @return {HTMLTrackElement} 20456 * The track element that gets created. 20457 */ 20458 20459 20460 Html5.prototype.createRemoteTextTrack = function createRemoteTextTrack(options) { 20461 if (!this.featuresNativeTextTracks) { 20462 return _Tech.prototype.createRemoteTextTrack.call(this, options); 20463 } 20464 var htmlTrackElement = document_1.createElement('track'); 20465 20466 if (options.kind) { 20467 htmlTrackElement.kind = options.kind; 20468 } 20469 if (options.label) { 20470 htmlTrackElement.label = options.label; 20471 } 20472 if (options.language || options.srclang) { 20473 htmlTrackElement.srclang = options.language || options.srclang; 20474 } 20475 if (options['default']) { 20476 htmlTrackElement['default'] = options['default']; 20477 } 20478 if (options.id) { 20479 htmlTrackElement.id = options.id; 20480 } 20481 if (options.src) { 20482 htmlTrackElement.src = options.src; 20483 } 20484 20485 return htmlTrackElement; 20486 }; 20487 20488 /** 20489 * Creates a remote text track object and returns an html track element. 20490 * 20491 * @param {Object} options The object should contain values for 20492 * kind, language, label, and src (location of the WebVTT file) 20493 * @param {Boolean} [manualCleanup=true] if set to false, the TextTrack will be 20494 * automatically removed from the video element whenever the source changes 20495 * @return {HTMLTrackElement} An Html Track Element. 20496 * This can be an emulated {@link HTMLTrackElement} or a native one. 20497 * @deprecated The default value of the "manualCleanup" parameter will default 20498 * to "false" in upcoming versions of Video.js 20499 */ 20500 20501 20502 Html5.prototype.addRemoteTextTrack = function addRemoteTextTrack(options, manualCleanup) { 20503 var htmlTrackElement = _Tech.prototype.addRemoteTextTrack.call(this, options, manualCleanup); 20504 20505 if (this.featuresNativeTextTracks) { 20506 this.el().appendChild(htmlTrackElement); 20507 } 20508 20509 return htmlTrackElement; 20510 }; 20511 20512 /** 20513 * Remove remote `TextTrack` from `TextTrackList` object 20514 * 20515 * @param {TextTrack} track 20516 * `TextTrack` object to remove 20517 */ 20518 20519 20520 Html5.prototype.removeRemoteTextTrack = function removeRemoteTextTrack(track) { 20521 _Tech.prototype.removeRemoteTextTrack.call(this, track); 20522 20523 if (this.featuresNativeTextTracks) { 20524 var tracks = this.$$('track'); 20525 20526 var i = tracks.length; 20527 20528 while (i--) { 20529 if (track === tracks[i] || track === tracks[i].track) { 20530 this.el().removeChild(tracks[i]); 20531 } 20532 } 20533 } 20534 }; 20535 20536 /** 20537 * Gets available media playback quality metrics as specified by the W3C's Media 20538 * Playback Quality API. 20539 * 20540 * @see [Spec]{@link https://wicg.github.io/media-playback-quality} 20541 * 20542 * @return {Object} 20543 * An object with supported media playback quality metrics 20544 */ 20545 20546 20547 Html5.prototype.getVideoPlaybackQuality = function getVideoPlaybackQuality() { 20548 if (typeof this.el().getVideoPlaybackQuality === 'function') { 20549 return this.el().getVideoPlaybackQuality(); 20550 } 20551 20552 var videoPlaybackQuality = {}; 20553 20554 if (typeof this.el().webkitDroppedFrameCount !== 'undefined' && typeof this.el().webkitDecodedFrameCount !== 'undefined') { 20555 videoPlaybackQuality.droppedVideoFrames = this.el().webkitDroppedFrameCount; 20556 videoPlaybackQuality.totalVideoFrames = this.el().webkitDecodedFrameCount; 20557 } 20558 20559 if (window_1.performance && typeof window_1.performance.now === 'function') { 20560 videoPlaybackQuality.creationTime = window_1.performance.now(); 20561 } else if (window_1.performance && window_1.performance.timing && typeof window_1.performance.timing.navigationStart === 'number') { 20562 videoPlaybackQuality.creationTime = window_1.Date.now() - window_1.performance.timing.navigationStart; 20563 } 20564 20565 return videoPlaybackQuality; 20566 }; 20567 20568 return Html5; 20569}(Tech); 20570 20571/* HTML5 Support Testing ---------------------------------------------------- */ 20572 20573if (isReal()) { 20574 20575 /** 20576 * Element for testing browser HTML5 media capabilities 20577 * 20578 * @type {Element} 20579 * @constant 20580 * @private 20581 */ 20582 Html5.TEST_VID = document_1.createElement('video'); 20583 var track = document_1.createElement('track'); 20584 20585 track.kind = 'captions'; 20586 track.srclang = 'en'; 20587 track.label = 'English'; 20588 Html5.TEST_VID.appendChild(track); 20589} 20590 20591/** 20592 * Check if HTML5 media is supported by this browser/device. 20593 * 20594 * @return {boolean} 20595 * - True if HTML5 media is supported. 20596 * - False if HTML5 media is not supported. 20597 */ 20598Html5.isSupported = function () { 20599 // IE9 with no Media Player is a LIAR! (#984) 20600 try { 20601 Html5.TEST_VID.volume = 0.5; 20602 } catch (e) { 20603 return false; 20604 } 20605 20606 return !!(Html5.TEST_VID && Html5.TEST_VID.canPlayType); 20607}; 20608 20609/** 20610 * Check if the tech can support the given type 20611 * 20612 * @param {string} type 20613 * The mimetype to check 20614 * @return {string} 'probably', 'maybe', or '' (empty string) 20615 */ 20616Html5.canPlayType = function (type) { 20617 return Html5.TEST_VID.canPlayType(type); 20618}; 20619 20620/** 20621 * Check if the tech can support the given source 20622 * @param {Object} srcObj 20623 * The source object 20624 * @param {Object} options 20625 * The options passed to the tech 20626 * @return {string} 'probably', 'maybe', or '' (empty string) 20627 */ 20628Html5.canPlaySource = function (srcObj, options) { 20629 return Html5.canPlayType(srcObj.type); 20630}; 20631 20632/** 20633 * Check if the volume can be changed in this browser/device. 20634 * Volume cannot be changed in a lot of mobile devices. 20635 * Specifically, it can't be changed from 1 on iOS. 20636 * 20637 * @return {boolean} 20638 * - True if volume can be controlled 20639 * - False otherwise 20640 */ 20641Html5.canControlVolume = function () { 20642 // IE will error if Windows Media Player not installed #3315 20643 try { 20644 var volume = Html5.TEST_VID.volume; 20645 20646 Html5.TEST_VID.volume = volume / 2 + 0.1; 20647 return volume !== Html5.TEST_VID.volume; 20648 } catch (e) { 20649 return false; 20650 } 20651}; 20652 20653/** 20654 * Check if the volume can be muted in this browser/device. 20655 * Some devices, e.g. iOS, don't allow changing volume 20656 * but permits muting/unmuting. 20657 * 20658 * @return {bolean} 20659 * - True if volume can be muted 20660 * - False otherwise 20661 */ 20662Html5.canMuteVolume = function () { 20663 try { 20664 var muted = Html5.TEST_VID.muted; 20665 20666 // in some versions of iOS muted property doesn't always 20667 // work, so we want to set both property and attribute 20668 Html5.TEST_VID.muted = !muted; 20669 if (Html5.TEST_VID.muted) { 20670 setAttribute(Html5.TEST_VID, 'muted', 'muted'); 20671 } else { 20672 removeAttribute(Html5.TEST_VID, 'muted', 'muted'); 20673 } 20674 return muted !== Html5.TEST_VID.muted; 20675 } catch (e) { 20676 return false; 20677 } 20678}; 20679 20680/** 20681 * Check if the playback rate can be changed in this browser/device. 20682 * 20683 * @return {boolean} 20684 * - True if playback rate can be controlled 20685 * - False otherwise 20686 */ 20687Html5.canControlPlaybackRate = function () { 20688 // Playback rate API is implemented in Android Chrome, but doesn't do anything 20689 // https://github.com/videojs/video.js/issues/3180 20690 if (IS_ANDROID && IS_CHROME && CHROME_VERSION < 58) { 20691 return false; 20692 } 20693 // IE will error if Windows Media Player not installed #3315 20694 try { 20695 var playbackRate = Html5.TEST_VID.playbackRate; 20696 20697 Html5.TEST_VID.playbackRate = playbackRate / 2 + 0.1; 20698 return playbackRate !== Html5.TEST_VID.playbackRate; 20699 } catch (e) { 20700 return false; 20701 } 20702}; 20703 20704/** 20705 * Check if we can override a video/audio elements attributes, with 20706 * Object.defineProperty. 20707 * 20708 * @return {boolean} 20709 * - True if builtin attributes can be overriden 20710 * - False otherwise 20711 */ 20712Html5.canOverrideAttributes = function () { 20713 if (IS_IE8) { 20714 return false; 20715 } 20716 // if we cannot overwrite the src/innerHTML property, there is no support 20717 // iOS 7 safari for instance cannot do this. 20718 try { 20719 var noop = function noop() {}; 20720 20721 Object.defineProperty(document_1.createElement('video'), 'src', { get: noop, set: noop }); 20722 Object.defineProperty(document_1.createElement('audio'), 'src', { get: noop, set: noop }); 20723 Object.defineProperty(document_1.createElement('video'), 'innerHTML', { get: noop, set: noop }); 20724 Object.defineProperty(document_1.createElement('audio'), 'innerHTML', { get: noop, set: noop }); 20725 } catch (e) { 20726 return false;
20727 } 20728 20729 return true; 20730}; 20731 20732/** 20733 * Check to see if native `TextTrack`s are supported by this browser/device. 20734 * 20735 * @return {boolean} 20736 * - True if native `TextTrack`s are supported. 20737 * - False otherwise 20738 */ 20739Html5.supportsNativeTextTracks = function () { 20740 return IS_ANY_SAFARI || IS_IOS && IS_CHROME; 20741}; 20742 20743/** 20744 * Check to see if native `VideoTrack`s are supported by this browser/device 20745 * 20746 * @return {boolean} 20747 * - True if native `VideoTrack`s are supported. 20748 * - False otherwise 20749 */ 20750Html5.supportsNativeVideoTracks = function () { 20751 return !!(Html5.TEST_VID && Html5.TEST_VID.videoTracks); 20752}; 20753 20754/** 20755 * Check to see if native `AudioTrack`s are supported by this browser/device 20756 * 20757 * @return {boolean} 20758 * - True if native `AudioTrack`s are supported. 20759 * - False otherwise 20760 */ 20761Html5.supportsNativeAudioTracks = function () { 20762 return !!(Html5.TEST_VID && Html5.TEST_VID.audioTracks); 20763}; 20764 20765/** 20766 * An array of events available on the Html5 tech. 20767 * 20768 * @private 20769 * @type {Array} 20770 */ 20771Html5.Events = ['loadstart', 'suspend', 'abort', 'error', 'emptied', 'stalled', 'loadedmetadata', 'loadeddata', 'canplay', 'canplaythrough', 'playing', 'waiting', 'seeking', 'seeked', 'ended', 'durationchange', 'timeupdate', 'progress', 'play', 'pause', 'ratechange', 'resize', 'volumechange']; 20772 20773/** 20774 * Boolean indicating whether the `Tech` supports volume control. 20775 * 20776 * @type {boolean} 20777 * @default {@link Html5.canControlVolume} 20778 */ 20779Html5.prototype.featuresVolumeControl = Html5.canControlVolume(); 20780 20781/** 20782 * Boolean indicating whether the `Tech` supports muting volume. 20783 * 20784 * @type {bolean} 20785 * @default {@link Html5.canMuteVolume} 20786 */ 20787Html5.prototype.featuresMuteControl = Html5.canMuteVolume(); 20788 20789/** 20790 * Boolean indicating whether the `Tech` supports changing the speed at which the media 20791 * plays. Examples: 20792 * - Set player to play 2x (twice) as fast 20793 * - Set player to play 0.5x (half) as fast 20794 * 20795 * @type {boolean} 20796 * @default {@link Html5.canControlPlaybackRate} 20797 */ 20798Html5.prototype.featuresPlaybackRate = Html5.canControlPlaybackRate(); 20799 20800/** 20801 * Boolean indicating wether the `Tech` supports the `sourceset` event. 20802 * 20803 * @type {boolean} 20804 * @default 20805 */ 20806Html5.prototype.featuresSourceset = Html5.canOverrideAttributes(); 20807 20808/** 20809 * Boolean indicating whether the `HTML5` tech currently supports the media element 20810 * moving in the DOM. iOS breaks if you move the media element, so this is set this to 20811 * false there. Everywhere else this should be true. 20812 * 20813 * @type {boolean} 20814 * @default 20815 */ 20816Html5.prototype.movingMediaElementInDOM = !IS_IOS; 20817 20818// TODO: Previous comment: No longer appears to be used. Can probably be removed. 20819// Is this true? 20820/** 20821 * Boolean indicating whether the `HTML5` tech currently supports automatic media resize 20822 * when going into fullscreen. 20823 * 20824 * @type {boolean} 20825 * @default 20826 */ 20827Html5.prototype.featuresFullscreenResize = true; 20828 20829/** 20830 * Boolean indicating whether the `HTML5` tech currently supports the progress event. 20831 * If this is false, manual `progress` events will be triggred instead. 20832 * 20833 * @type {boolean} 20834 * @default 20835 */ 20836Html5.prototype.featuresProgressEvents = true; 20837 20838/** 20839 * Boolean indicating whether the `HTML5` tech currently supports the timeupdate event. 20840 * If this is false, manual `timeupdate` events will be triggred instead. 20841 * 20842 * @default 20843 */ 20844Html5.prototype.featuresTimeupdateEvents = true; 20845 20846/** 20847 * Boolean indicating whether the `HTML5` tech currently supports native `TextTrack`s. 20848 * 20849 * @type {boolean} 20850 * @default {@link Html5.supportsNativeTextTracks} 20851 */ 20852Html5.prototype.featuresNativeTextTracks = Html5.supportsNativeTextTracks(); 20853 20854/** 20855 * Boolean indicating whether the `HTML5` tech currently supports native `VideoTrack`s. 20856 * 20857 * @type {boolean} 20858 * @default {@link Html5.supportsNativeVideoTracks} 20859 */ 20860Html5.prototype.featuresNativeVideoTracks = Html5.supportsNativeVideoTracks(); 20861 20862/** 20863 * Boolean indicating whether the `HTML5` tech currently supports native `AudioTrack`s. 20864 * 20865 * @type {boolean} 20866 * @default {@link Html5.supportsNativeAudioTracks} 20867 */ 20868Html5.prototype.featuresNativeAudioTracks = Html5.supportsNativeAudioTracks(); 20869 20870// HTML5 Feature detection and Device Fixes --------------------------------- //
vendor: 14,210 bytes, lines 20871-21295
20871var canPlayType = Html5.TEST_VID && Html5.TEST_VID.constructor.prototype.canPlayType; 20872var mpegurlRE = /^application\/(?:x-|vnd\.apple\.)mpegurl/i; 20873var mp4RE = /^video\/mp4/i; 20874 20875Html5.patchCanPlayType = function () { 20876 20877 // Android 4.0 and above can play HLS to some extent but it reports being unable to do so 20878 // Firefox and Chrome report correctly 20879 if (ANDROID_VERSION >= 4.0 && !IS_FIREFOX && !IS_CHROME) { 20880 Html5.TEST_VID.constructor.prototype.canPlayType = function (type) { 20881 if (type && mpegurlRE.test(type)) { 20882 return 'maybe'; 20883 } 20884 return canPlayType.call(this, type); 20885 }; 20886 20887 // Override Android 2.2 and less canPlayType method which is broken 20888 } else if (IS_OLD_ANDROID) { 20889 Html5.TEST_VID.constructor.prototype.canPlayType = function (type) { 20890 if (type && mp4RE.test(type)) { 20891 return 'maybe'; 20892 } 20893 return canPlayType.call(this, type); 20894 }; 20895 } 20896}; 20897 20898Html5.unpatchCanPlayType = function () { 20899 var r = Html5.TEST_VID.constructor.prototype.canPlayType; 20900 20901 Html5.TEST_VID.constructor.prototype.canPlayType = canPlayType; 20902 return r; 20903}; 20904 20905// by default, patch the media element 20906Html5.patchCanPlayType(); 20907 20908Html5.disposeMediaElement = function (el) { 20909 if (!el) { 20910 return; 20911 } 20912 20913 if (el.parentNode) { 20914 el.parentNode.removeChild(el); 20915 } 20916 20917 // remove any child track or source nodes to prevent their loading 20918 while (el.hasChildNodes()) { 20919 el.removeChild(el.firstChild); 20920 } 20921 20922 // remove any src reference. not setting `src=''` because that causes a warning 20923 // in firefox 20924 el.removeAttribute('src'); 20925 20926 // force the media element to update its loading state by calling load() 20927 // however IE on Windows 7N has a bug that throws an error so need a try/catch (#793) 20928 if (typeof el.load === 'function') { 20929 // wrapping in an iife so it's not deoptimized (#1060#discussion_r10324473) 20930 (function () { 20931 try { 20932 el.load(); 20933 } catch (e) { 20934 // not supported 20935 } 20936 })(); 20937 } 20938}; 20939 20940Html5.resetMediaElement = function (el) { 20941 if (!el) { 20942 return; 20943 } 20944 20945 var sources = el.querySelectorAll('source'); 20946 var i = sources.length; 20947 20948 while (i--) { 20949 el.removeChild(sources[i]); 20950 } 20951 20952 // remove any src reference. 20953 // not setting `src=''` because that throws an error 20954 el.removeAttribute('src'); 20955 20956 if (typeof el.load === 'function') { 20957 // wrapping in an iife so it's not deoptimized (#1060#discussion_r10324473) 20958 (function () { 20959 try { 20960 el.load(); 20961 } catch (e) { 20962 // satisfy linter 20963 } 20964 })(); 20965 } 20966}; 20967 20968/* Native HTML5 element property wrapping ----------------------------------- */ 20969// Wrap native boolean attributes with getters that check both property and attribute 20970// The list is as followed: 20971// muted, defaultMuted, autoplay, controls, loop, playsinline 20972[ 20973/** 20974 * Get the value of `muted` from the media element. `muted` indicates 20975 * that the volume for the media should be set to silent. This does not actually change 20976 * the `volume` attribute. 20977 * 20978 * @method Html5#muted 20979 * @return {boolean} 20980 * - True if the value of `volume` should be ignored and the audio set to silent. 20981 * - False if the value of `volume` should be used. 20982 * 20983 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-muted} 20984 */ 20985'muted', 20986 20987/** 20988 * Get the value of `defaultMuted` from the media element. `defaultMuted` indicates 20989 * whether the media should start muted or not. Only changes the default state of the 20990 * media. `muted` and `defaultMuted` can have different values. {@link Html5#muted} indicates the 20991 * current state. 20992 * 20993 * @method Html5#defaultMuted 20994 * @return {boolean} 20995 * - The value of `defaultMuted` from the media element. 20996 * - True indicates that the media should start muted. 20997 * - False indicates that the media should not start muted 20998 * 20999 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultmuted} 21000 */ 21001'defaultMuted', 21002 21003/** 21004 * Get the value of `autoplay` from the media element. `autoplay` indicates 21005 * that the media should start to play as soon as the page is ready. 21006 * 21007 * @method Html5#autoplay 21008 * @return {boolean} 21009 * - The value of `autoplay` from the media element. 21010 * - True indicates that the media should start as soon as the page loads. 21011 * - False indicates that the media should not start as soon as the page loads. 21012 * 21013 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-autoplay} 21014 */ 21015'autoplay', 21016 21017/** 21018 * Get the value of `controls` from the media element. `controls` indicates 21019 * whether the native media controls should be shown or hidden. 21020 * 21021 * @method Html5#controls 21022 * @return {boolean} 21023 * - The value of `controls` from the media element. 21024 * - True indicates that native controls should be showing. 21025 * - False indicates that native controls should be hidden. 21026 * 21027 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-controls} 21028 */ 21029'controls', 21030 21031/** 21032 * Get the value of `loop` from the media element. `loop` indicates 21033 * that the media should return to the start of the media and continue playing once 21034 * it reaches the end. 21035 * 21036 * @method Html5#loop 21037 * @return {boolean} 21038 * - The value of `loop` from the media element. 21039 * - True indicates that playback should seek back to start once 21040 * the end of a media is reached. 21041 * - False indicates that playback should not loop back to the start when the 21042 * end of the media is reached. 21043 * 21044 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-loop} 21045 */ 21046'loop', 21047 21048/** 21049 * Get the value of `playsinline` from the media element. `playsinline` indicates 21050 * to the browser that non-fullscreen playback is preferred when fullscreen 21051 * playback is the native default, such as in iOS Safari. 21052 * 21053 * @method Html5#playsinline 21054 * @return {boolean} 21055 * - The value of `playsinline` from the media element. 21056 * - True indicates that the media should play inline. 21057 * - False indicates that the media should not play inline. 21058 * 21059 * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline} 21060 */ 21061'playsinline'].forEach(function (prop) { 21062 Html5.prototype[prop] = function () { 21063 return this.el_[prop] || this.el_.hasAttribute(prop); 21064 }; 21065}); 21066 21067// Wrap native boolean attributes with setters that set both property and attribute 21068// The list is as followed: 21069// setMuted, setDefaultMuted, setAutoplay, setLoop, setPlaysinline 21070// setControls is special-cased above 21071[ 21072/** 21073 * Set the value of `muted` on the media element. `muted` indicates that the current 21074 * audio level should be silent. 21075 * 21076 * @method Html5#setMuted 21077 * @param {boolean} muted 21078 * - True if the audio should be set to silent 21079 * - False otherwise 21080 * 21081 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-muted} 21082 */ 21083'muted', 21084 21085/** 21086 * Set the value of `defaultMuted` on the media element. `defaultMuted` indicates that the current 21087 * audio level should be silent, but will only effect the muted level on intial playback.. 21088 * 21089 * @method Html5.prototype.setDefaultMuted 21090 * @param {boolean} defaultMuted 21091 * - True if the audio should be set to silent 21092 * - False otherwise 21093 * 21094 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultmuted} 21095 */ 21096'defaultMuted', 21097 21098/** 21099 * Set the value of `autoplay` on the media element. `autoplay` indicates 21100 * that the media should start to play as soon as the page is ready. 21101 * 21102 * @method Html5#setAutoplay 21103 * @param {boolean} autoplay 21104 * - True indicates that the media should start as soon as the page loads. 21105 * - False indicates that the media should not start as soon as the page loads. 21106 * 21107 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-autoplay} 21108 */ 21109'autoplay', 21110 21111/** 21112 * Set the value of `loop` on the media element. `loop` indicates 21113 * that the media should return to the start of the media and continue playing once 21114 * it reaches the end. 21115 * 21116 * @method Html5#setLoop 21117 * @param {boolean} loop 21118 * - True indicates that playback should seek back to start once 21119 * the end of a media is reached. 21120 * - False indicates that playback should not loop back to the start when the 21121 * end of the media is reached. 21122 * 21123 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-loop} 21124 */ 21125'loop', 21126 21127/** 21128 * Set the value of `playsinline` from the media element. `playsinline` indicates 21129 * to the browser that non-fullscreen playback is preferred when fullscreen 21130 * playback is the native default, such as in iOS Safari. 21131 * 21132 * @method Html5#setPlaysinline 21133 * @param {boolean} playsinline 21134 * - True indicates that the media should play inline. 21135 * - False indicates that the media should not play inline. 21136 * 21137 * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline} 21138 */ 21139'playsinline'].forEach(function (prop) { 21140 Html5.prototype['set' + toTitleCase(prop)] = function (v) { 21141 this.el_[prop] = v; 21142 21143 if (v) { 21144 this.el_.setAttribute(prop, prop); 21145 } else { 21146 this.el_.removeAttribute(prop); 21147 } 21148 }; 21149}); 21150 21151// Wrap native properties with a getter 21152// The list is as followed 21153// paused, currentTime, buffered, volume, poster, preload, error, seeking 21154// seekable, ended, playbackRate, defaultPlaybackRate, played, networkState 21155// readyState, videoWidth, videoHeight 21156[ 21157/** 21158 * Get the value of `paused` from the media element. `paused` indicates whether the media element 21159 * is currently paused or not. 21160 * 21161 * @method Html5#paused 21162 * @return {boolean} 21163 * The value of `paused` from the media element. 21164 * 21165 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-paused} 21166 */ 21167'paused', 21168 21169/** 21170 * Get the value of `currentTime` from the media element. `currentTime` indicates 21171 * the current second that the media is at in playback. 21172 * 21173 * @method Html5#currentTime 21174 * @return {number} 21175 * The value of `currentTime` from the media element. 21176 * 21177 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-currenttime} 21178 */ 21179'currentTime', 21180 21181/** 21182 * Get the value of `buffered` from the media element. `buffered` is a `TimeRange` 21183 * object that represents the parts of the media that are already downloaded and 21184 * available for playback. 21185 * 21186 * @method Html5#buffered 21187 * @return {TimeRange} 21188 * The value of `buffered` from the media element. 21189 * 21190 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-buffered} 21191 */ 21192'buffered', 21193 21194/** 21195 * Get the value of `volume` from the media element. `volume` indicates 21196 * the current playback volume of audio for a media. `volume` will be a value from 0 21197 * (silent) to 1 (loudest and default). 21198 * 21199 * @method Html5#volume 21200 * @return {number} 21201 * The value of `volume` from the media element. Value will be between 0-1. 21202 * 21203 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-a-volume} 21204 */ 21205'volume', 21206 21207/** 21208 * Get the value of `poster` from the media element. `poster` indicates 21209 * that the url of an image file that can/will be shown when no media data is available. 21210 * 21211 * @method Html5#poster 21212 * @return {string} 21213 * The value of `poster` from the media element. Value will be a url to an 21214 * image. 21215 * 21216 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-video-poster} 21217 */ 21218'poster', 21219 21220/** 21221 * Get the value of `preload` from the media element. `preload` indicates 21222 * what should download before the media is interacted with. It can have the following 21223 * values: 21224 * - none: nothing should be downloaded 21225 * - metadata: poster and the first few frames of the media may be downloaded to get 21226 * media dimensions and other metadata 21227 * - auto: allow the media and metadata for the media to be downloaded before 21228 * interaction 21229 * 21230 * @method Html5#preload 21231 * @return {string} 21232 * The value of `preload` from the media element. Will be 'none', 'metadata', 21233 * or 'auto'. 21234 * 21235 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-preload} 21236 */ 21237'preload', 21238 21239/** 21240 * Get the value of the `error` from the media element. `error` indicates any 21241 * MediaError that may have occured during playback. If error returns null there is no 21242 * current error. 21243 * 21244 * @method Html5#error 21245 * @return {MediaError|null} 21246 * The value of `error` from the media element. Will be `MediaError` if there 21247 * is a current error and null otherwise. 21248 * 21249 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-error} 21250 */ 21251'error', 21252 21253/** 21254 * Get the value of `seeking` from the media element. `seeking` indicates whether the 21255 * media is currently seeking to a new position or not. 21256 * 21257 * @method Html5#seeking 21258 * @return {boolean} 21259 * - The value of `seeking` from the media element. 21260 * - True indicates that the media is currently seeking to a new position. 21261 * - Flase indicates that the media is not seeking to a new position at this time. 21262 * 21263 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-seeking} 21264 */ 21265'seeking', 21266 21267/** 21268 * Get the value of `seekable` from the media element. `seekable` returns a 21269 * `TimeRange` object indicating ranges of time that can currently be `seeked` to. 21270 * 21271 * @method Html5#seekable 21272 * @return {TimeRange} 21273 * The value of `seekable` from the media element. A `TimeRange` object 21274 * indicating the current ranges of time that can be seeked to. 21275 * 21276 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-seekable} 21277 */ 21278'seekable', 21279 21280/** 21281 * Get the value of `ended` from the media element. `ended` indicates whether 21282 * the media has reached the end or not. 21283 * 21284 * @method Html5#ended 21285 * @return {boolean} 21286 * - The value of `ended` from the media element. 21287 * - True indicates that the media has ended. 21288 * - False indicates that the media has not ended. 21289 * 21290 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-ended} 21291 */ 21292'ended', 21293 21294/** 21295 * Get the value of `playbackRate` from the media element. `playbackRate` indicates
21296 * the rate at which the media is currently playing back. Examples: 21297 * - if playbackRate is set to 2, media will play twice as fast. 21298 * - if playbackRate is set to 0.5, media will play half as fast. 21299 * 21300 * @method Html5#playbackRate 21301 * @return {number} 21302 * The value of `playbackRate` from the media element. A number indicating 21303 * the current playback speed of the media, where 1 is normal speed. 21304 * 21305 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate} 21306 */ 21307'playbackRate', 21308 21309/** 21310 * Get the value of `defaultPlaybackRate` from the media element. `defaultPlaybackRate` indicates 21311 * the rate at which the media is currently playing back. This value will not indicate the current 21312 * `playbackRate` after playback has started, use {@link Html5#playbackRate} for that. 21313 * 21314 * Examples: 21315 * - if defaultPlaybackRate is set to 2, media will play twice as fast. 21316 * - if defaultPlaybackRate is set to 0.5, media will play half as fast. 21317 * 21318 * @method Html5.prototype.defaultPlaybackRate 21319 * @return {number} 21320 * The value of `defaultPlaybackRate` from the media element. A number indicating 21321 * the current playback speed of the media, where 1 is normal speed. 21322 * 21323 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate} 21324 */ 21325'defaultPlaybackRate', 21326 21327/** 21328 * Get the value of `played` from the media element. `played` returns a `TimeRange` 21329 * object representing points in the media timeline that have been played. 21330 * 21331 * @method Html5#played 21332 * @return {TimeRange} 21333 * The value of `played` from the media element. A `TimeRange` object indicating 21334 * the ranges of time that have been played. 21335 * 21336 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-played} 21337 */ 21338'played', 21339 21340/** 21341 * Get the value of `networkState` from the media element. `networkState` indicates 21342 * the current network state. It returns an enumeration from the following list: 21343 * - 0: NETWORK_EMPTY 21344 * - 1: NEWORK_IDLE 21345 * - 2: NETWORK_LOADING 21346 * - 3: NETWORK_NO_SOURCE 21347 * 21348 * @method Html5#networkState 21349 * @return {number} 21350 * The value of `networkState` from the media element. This will be a number 21351 * from the list in the description. 21352 * 21353 * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-networkstate} 21354 */ 21355'networkState', 21356 21357/** 21358 * Get the value of `readyState` from the media element. `readyState` indicates 21359 * the current state of the media element. It returns an enumeration from the 21360 * following list: 21361 * - 0: HAVE_NOTHING 21362 * - 1: HAVE_METADATA 21363 * - 2: HAVE_CURRENT_DATA 21364 * - 3: HAVE_FUTURE_DATA 21365 * - 4: HAVE_ENOUGH_DATA 21366 * 21367 * @method Html5#readyState 21368 * @return {number} 21369 * The value of `readyState` from the media element. This will be a number 21370 * from the list in the description. 21371 * 21372 * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#ready-states} 21373 */ 21374'readyState', 21375 21376/** 21377 * Get the value of `videoWidth` from the video element. `videoWidth` indicates 21378 * the current width of the video in css pixels. 21379 * 21380 * @method Html5#videoWidth 21381 * @return {number} 21382 * The value of `videoWidth` from the video element. This will be a number 21383 * in css pixels. 21384 * 21385 * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-video-videowidth} 21386 */ 21387'videoWidth', 21388 21389/** 21390 * Get the value of `videoHeight` from the video element. `videoHeigth` indicates 21391 * the current height of the video in css pixels. 21392 * 21393 * @method Html5#videoHeight 21394 * @return {number} 21395 * The value of `videoHeight` from the video element. This will be a number 21396 * in css pixels. 21397 * 21398 * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-video-videowidth} 21399 */ 21400'videoHeight'].forEach(function (prop) { 21401 Html5.prototype[prop] = function () { 21402 return this.el_[prop]; 21403 }; 21404}); 21405 21406// Wrap native properties with a setter in this format: 21407// set + toTitleCase(name) 21408// The list is as follows: 21409// setVolume, setSrc, setPoster, setPreload, setPlaybackRate, setDefaultPlaybackRate 21410[ 21411/** 21412 * Set the value of `volume` on the media element. `volume` indicates the current 21413 * audio level as a percentage in decimal form. This means that 1 is 100%, 0.5 is 50%, and 21414 * so on. 21415 * 21416 * @method Html5#setVolume 21417 * @param {number} percentAsDecimal 21418 * The volume percent as a decimal. Valid range is from 0-1. 21419 * 21420 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-a-volume} 21421 */ 21422'volume', 21423 21424/** 21425 * Set the value of `src` on the media element. `src` indicates the current 21426 * {@link Tech~SourceObject} for the media. 21427 * 21428 * @method Html5#setSrc 21429 * @param {Tech~SourceObject} src 21430 * The source object to set as the current source. 21431 * 21432 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-src} 21433 */ 21434'src', 21435 21436/** 21437 * Set the value of `poster` on the media element. `poster` is the url to 21438 * an image file that can/will be shown when no media data is available. 21439 * 21440 * @method Html5#setPoster 21441 * @param {string} poster 21442 * The url to an image that should be used as the `poster` for the media 21443 * element. 21444 * 21445 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-poster} 21446 */ 21447'poster', 21448 21449/** 21450 * Set the value of `preload` on the media element. `preload` indicates 21451 * what should download before the media is interacted with. It can have the following 21452 * values: 21453 * - none: nothing should be downloaded 21454 * - metadata: poster and the first few frames of the media may be downloaded to get 21455 * media dimensions and other metadata 21456 * - auto: allow the media and metadata for the media to be downloaded before 21457 * interaction 21458 * 21459 * @method Html5#setPreload 21460 * @param {string} preload 21461 * The value of `preload` to set on the media element. Must be 'none', 'metadata', 21462 * or 'auto'. 21463 * 21464 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-preload} 21465 */ 21466'preload', 21467 21468/** 21469 * Set the value of `playbackRate` on the media element. `playbackRate` indicates 21470 * the rate at which the media should play back. Examples: 21471 * - if playbackRate is set to 2, media will play twice as fast. 21472 * - if playbackRate is set to 0.5, media will play half as fast. 21473 * 21474 * @method Html5#setPlaybackRate 21475 * @return {number} 21476 * The value of `playbackRate` from the media element. A number indicating 21477 * the current playback speed of the media, where 1 is normal speed. 21478 * 21479 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate} 21480 */ 21481'playbackRate', 21482 21483/** 21484 * Set the value of `defaultPlaybackRate` on the media element. `defaultPlaybackRate` indicates 21485 * the rate at which the media should play back upon initial startup. Changing this value 21486 * after a video has started will do nothing. Instead you should used {@link Html5#setPlaybackRate}. 21487 * 21488 * Example Values: 21489 * - if playbackRate is set to 2, media will play twice as fast. 21490 * - if playbackRate is set to 0.5, media will play half as fast. 21491 * 21492 * @method Html5.prototype.setDefaultPlaybackRate 21493 * @return {number} 21494 * The value of `defaultPlaybackRate` from the media element. A number indicating 21495 * the current playback speed of the media, where 1 is normal speed. 21496 * 21497 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultplaybackrate} 21498 */ 21499'defaultPlaybackRate'].forEach(function (prop) { 21500 Html5.prototype['set' + toTitleCase(prop)] = function (v) { 21501 this.el_[prop] = v; 21502 }; 21503}); 21504 21505// wrap native functions with a function 21506// The list is as follows: 21507// pause, load play 21508[ 21509/** 21510 * A wrapper around the media elements `pause` function. This will call the `HTML5` 21511 * media elements `pause` function. 21512 * 21513 * @method Html5#pause 21514 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-pause} 21515 */ 21516'pause', 21517 21518/** 21519 * A wrapper around the media elements `load` function. This will call the `HTML5`s 21520 * media element `load` function. 21521 * 21522 * @method Html5#load 21523 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-load} 21524 */ 21525'load', 21526 21527/** 21528 * A wrapper around the media elements `play` function. This will call the `HTML5`s 21529 * media element `play` function. 21530 * 21531 * @method Html5#play 21532 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-play} 21533 */ 21534'play'].forEach(function (prop) { 21535 Html5.prototype[prop] = function () { 21536 return this.el_[prop](); 21537 }; 21538}); 21539 21540Tech.withSourceHandlers(Html5); 21541 21542/** 21543 * Native source handler for Html5, simply passes the source to the media element. 21544 * 21545 * @proprety {Tech~SourceObject} source 21546 * The source object 21547 * 21548 * @proprety {Html5} tech 21549 * The instance of the HTML5 tech. 21550 */ 21551Html5.nativeSourceHandler = {}; 21552 21553/** 21554 * Check if the media element can play the given mime type. 21555 * 21556 * @param {string} type 21557 * The mimetype to check 21558 * 21559 * @return {string} 21560 * 'probably', 'maybe', or '' (empty string) 21561 */ 21562Html5.nativeSourceHandler.canPlayType = function (type) { 21563 // IE9 on Windows 7 without MediaPlayer throws an error here 21564 // https://github.com/videojs/video.js/issues/519 21565 try {
21566 return Html5.TEST_VID.canPlayType(type); 21567 } catch (e) { 21568 return ''; 21569 } 21570}; 21571 21572/** 21573 * Check if the media element can handle a source natively. 21574 * 21575 * @param {Tech~SourceObject} source 21576 * The source object 21577 * 21578 * @param {Object} [options] 21579 * Options to be passed to the tech. 21580 * 21581 * @return {string} 21582 * 'probably', 'maybe', or '' (empty string). 21583 */ 21584Html5.nativeSourceHandler.canHandleSource = function (source, options) { 21585 21586 // If a type was provided we should rely on that 21587 if (source.type) { 21588 return Html5.nativeSourceHandler.canPlayType(source.type); 21589 21590 // If no type, fall back to checking 'video/[EXTENSION]' 21591 } else if (source.src) { 21592 var ext = getFileExtension(source.src); 21593 21594 return Html5.nativeSourceHandler.canPlayType('video/' + ext); 21595 } 21596 21597 return ''; 21598}; 21599 21600/** 21601 * Pass the source to the native media element. 21602 * 21603 * @param {Tech~SourceObject} source 21604 * The source object 21605 * 21606 * @param {Html5} tech 21607 * The instance of the Html5 tech 21608 * 21609 * @param {Object} [options] 21610 * The options to pass to the source 21611 */ 21612Html5.nativeSourceHandler.handleSource = function (source, tech, options) { 21613 tech.setSrc(source.src); 21614}; 21615 21616/** 21617 * A noop for the native dispose function, as cleanup is not needed. 21618 */ 21619Html5.nativeSourceHandler.dispose = function () {}; 21620 21621// Register the native source handler 21622Html5.registerSourceHandler(Html5.nativeSourceHandler); 21623 21624Tech.registerTech('Html5', Html5); 21625 21626var _templateObject$1 = taggedTemplateLiteralLoose(['\n Using the tech directly can be dangerous. I hope you know
21626what you\'re doing.\n See https://github.com/videojs/video.js/issues/2617 for more info.\n '], ['\n Using the tech directly can be dangerous. I hope you know what you\'re doing.\n See https://github.com/videojs/video.js/issues/2617 for more info.\n ']); 21627 21628/** 21629 * @file player.js 21630 */ 21631// Subclasses Component 21632// The following imports are used only to ensure that the corresponding modules 21633// are always included in the video.js package. Importing the modules will 21634// execute them and they will register themselves with video.js. 21635// Import Html5 tech, at least for disposing the original video tag.
vendor: 14,011 bytes, lines 21635-22158
21635 21636// The following tech events are simply re-triggered 21637// on the player when they happen 21638var TECH_EVENTS_RETRIGGER = [ 21639/** 21640 * Fired while the user agent is downloading media data. 21641 * 21642 * @event Player#progress 21643 * @type {EventTarget~Event} 21644 */ 21645/** 21646 * Retrigger the `progress` event that was triggered by the {@link Tech}. 21647 * 21648 * @private 21649 * @method Player#handleTechProgress_ 21650 * @fires Player#progress 21651 * @listens Tech#progress 21652 */ 21653'progress', 21654 21655/** 21656 * Fires when the loading of an audio/video is aborted. 21657 * 21658 * @event Player#abort 21659 * @type {EventTarget~Event} 21660 */ 21661/** 21662 * Retrigger the `abort` event that was triggered by the {@link Tech}. 21663 * 21664 * @private 21665 * @method Player#handleTechAbort_ 21666 * @fires Player#abort 21667 * @listens Tech#abort 21668 */ 21669'abort', 21670 21671/** 21672 * Fires when the browser is intentionally not getting media data. 21673 * 21674 * @event Player#suspend 21675 * @type {EventTarget~Event} 21676 */ 21677/** 21678 * Retrigger the `suspend` event that was triggered by the {@link Tech}. 21679 * 21680 * @private 21681 * @method Player#handleTechSuspend_ 21682 * @fires Player#suspend 21683 * @listens Tech#suspend 21684 */ 21685'suspend', 21686 21687/** 21688 * Fires when the current playlist is empty. 21689 * 21690 * @event Player#emptied 21691 * @type {EventTarget~Event} 21692 */ 21693/** 21694 * Retrigger the `emptied` event that was triggered by the {@link Tech}. 21695 * 21696 * @private 21697 * @method Player#handleTechEmptied_ 21698 * @fires Player#emptied 21699 * @listens Tech#emptied 21700 */ 21701'emptied', 21702/** 21703 * Fires when the browser is trying to get media data, but data is not available. 21704 * 21705 * @event Player#stalled 21706 * @type {EventTarget~Event} 21707 */ 21708/** 21709 * Retrigger the `stalled` event that was triggered by the {@link Tech}. 21710 * 21711 * @private 21712 * @method Player#handleTechStalled_ 21713 * @fires Player#stalled 21714 * @listens Tech#stalled 21715 */ 21716'stalled', 21717 21718/** 21719 * Fires when the browser has loaded meta data for the audio/video. 21720 * 21721 * @event Player#loadedmetadata 21722 * @type {EventTarget~Event} 21723 */ 21724/** 21725 * Retrigger the `stalled` event that was triggered by the {@link Tech}. 21726 * 21727 * @private 21728 * @method Player#handleTechLoadedmetadata_ 21729 * @fires Player#loadedmetadata 21730 * @listens Tech#loadedmetadata 21731 */ 21732'loadedmetadata', 21733 21734/** 21735 * Fires when the browser has loaded the current frame of the audio/video. 21736 * 21737 * @event Player#loadeddata 21738 * @type {event} 21739 */ 21740/** 21741 * Retrigger the `loadeddata` event that was triggered by the {@link Tech}. 21742 * 21743 * @private 21744 * @method Player#handleTechLoaddeddata_ 21745 * @fires Player#loadeddata 21746 * @listens Tech#loadeddata 21747 */ 21748'loadeddata', 21749 21750/** 21751 * Fires when the current playback position has changed. 21752 * 21753 * @event Player#timeupdate 21754 * @type {event} 21755 */ 21756/** 21757 * Retrigger the `timeupdate` event that was triggered by the {@link Tech}. 21758 * 21759 * @private 21760 * @method Player#handleTechTimeUpdate_ 21761 * @fires Player#timeupdate 21762 * @listens Tech#timeupdate 21763 */ 21764'timeupdate', 21765 21766/** 21767 * Fires when the video's intrinsic dimensions change 21768 * 21769 * @event Player#resize 21770 * @type {event} 21771 */ 21772/** 21773 * Retrigger the `resize` event that was triggered by the {@link Tech}. 21774 * 21775 * @private 21776 * @method Player#handleTechResize_ 21777 * @fires Player#resize 21778 * @listens Tech#resize 21779 */ 21780'resize', 21781 21782/** 21783 * Fires when the volume has been changed 21784 * 21785 * @event Player#volumechange 21786 * @type {event} 21787 */ 21788/** 21789 * Retrigger the `volumechange` event that was triggered by the {@link Tech}. 21790 * 21791 * @private 21792 * @method Player#handleTechVolumechange_ 21793 * @fires Player#volumechange 21794 * @listens Tech#volumechange 21795 */ 21796'volumechange', 21797 21798/** 21799 * Fires when the text track has been changed 21800 * 21801 * @event Player#texttrackchange 21802 * @type {event} 21803 */ 21804/** 21805 * Retrigger the `texttrackchange` event that was triggered by the {@link Tech}. 21806 * 21807 * @private 21808 * @method Player#handleTechTexttrackchange_ 21809 * @fires Player#texttrackchange 21810 * @listens Tech#texttrackchange 21811 */ 21812'texttrackchange']; 21813 21814// events to queue when playback rate is zero 21815// this is a hash for the sole purpose of mapping non-camel-cased event names 21816// to camel-cased function names 21817var TECH_EVENTS_QUEUE = { 21818 canplay: 'CanPlay', 21819 canplaythrough: 'CanPlayThrough', 21820 playing: 'Playing', 21821 seeked: 'Seeked' 21822}; 21823 21824/** 21825 * An instance of the `Player` class is created when any of the Video.js setup methods 21826 * are used to initialize a video. 21827 * 21828 * After an instance has been created it can be accessed globally in two ways: 21829 * 1. By calling `videojs('example_video_1');` 21830 * 2. By using it directly via `videojs.players.example_video_1;` 21831 * 21832 * @extends Component 21833 */ 21834 21835var Player = function (_Component) { 21836 inherits(Player, _Component); 21837 21838 /** 21839 * Create an instance of this class. 21840 * 21841 * @param {Element} tag 21842 * The original video DOM element used for configuring options. 21843 * 21844 * @param {Object} [options] 21845 * Object of option names and values. 21846 * 21847 * @param {Component~ReadyCallback} [ready] 21848 * Ready callback function. 21849 */ 21850 function Player(tag, options, ready) { 21851 classCallCheck(this, Player); 21852 21853 // Make sure tag ID exists 21854 tag.id = tag.id || options.id || 'vjs_video_' + newGUID(); 21855 21856 // Set Options 21857 // The options argument overrides options set in the video tag 21858 // which overrides globally set options. 21859 // This latter part coincides with the load order 21860 // (tag must exist before Player) 21861 options = assign(Player.getTagSettings(tag), options); 21862 21863 // Delay the initialization of children because we need to set up 21864 // player properties first, and can't use `this` before `super()` 21865 options.initChildren = false; 21866 21867 // Same with creating the element 21868 options.createEl = false; 21869 21870 // don't auto mixin the evented mixin 21871 options.evented = false; 21872 21873 // we don't want the player to report touch activity on itself 21874 // see enableTouchActivity in Component 21875 options.reportTouchActivity = false; 21876 21877 // If language is not set, get the closest lang attribute 21878 if (!options.language) { 21879 if (typeof tag.closest === 'function') { 21880 var closest = tag.closest('[lang]'); 21881 21882 if (closest && closest.getAttribute) { 21883 options.language = closest.getAttribute('lang'); 21884 } 21885 } else { 21886 var element = tag; 21887 21888 while (element && element.nodeType === 1) { 21889 if (getAttributes(element).hasOwnProperty('lang')) { 21890 options.language = element.getAttribute('lang'); 21891 break; 21892 } 21893 element = element.parentNode; 21894 } 21895 } 21896 } 21897 21898 // Run base component initializing with new options 21899 21900 // Tracks when a tech changes the poster 21901 var _this = possibleConstructorReturn(this, _Component.call(this, null, options, ready)); 21902 21903 _this.isPosterFromTech_ = false; 21904 21905 // Holds callback info that gets queued when playback rate is zero 21906 // and a seek is happening 21907 _this.queuedCallbacks_ = []; 21908 21909 // Turn off API access because we're loading a new tech that might load asynchronously 21910 _this.isReady_ = false; 21911 21912 // Init state hasStarted_ 21913 _this.hasStarted_ = false; 21914 21915 // Init state userActive_ 21916 _this.userActive_ = false; 21917 21918 // if the global option object was accidentally blown away by 21919 // someone, bail early with an informative error 21920 if (!_this.options_ || !_this.options_.techOrder || !_this.options_.techOrder.length) { 21921 throw new Error('No techOrder specified. Did you overwrite ' + 'videojs.options instead of just changing the ' + 'properties you want to override?'); 21922 } 21923 21924 // Store the original tag used to set options 21925 _this.tag = tag; 21926 21927 // Store the tag attributes used to restore html5 element 21928 _this.tagAttributes = tag && getAttributes(tag); 21929 21930 // Update current language 21931 _this.language(_this.options_.language); 21932 21933 // Update Supported Languages 21934 if (options.languages) { 21935 // Normalise player option languages to lowercase 21936 var languagesToLower = {}; 21937 21938 Object.getOwnPropertyNames(options.languages).forEach(function (name$$1) { 21939 languagesToLower[name$$1.toLowerCase()] = options.languages[name$$1]; 21940 }); 21941 _this.languages_ = languagesToLower; 21942 } else { 21943 _this.languages_ = Player.prototype.options_.languages; 21944 } 21945 21946 // Cache for video property values. 21947 _this.cache_ = {}; 21948 21949 // Set poster 21950 _this.poster_ = options.poster || ''; 21951 21952 // Set controls 21953 _this.controls_ = !!options.controls; 21954 21955 // Set default values for lastVolume 21956 _this.cache_.lastVolume = 1; 21957 21958 // Original tag settings stored in options 21959 // now remove immediately so native controls don't flash. 21960 // May be turned back on by HTML5 tech if nativeControlsForTouch is true 21961 tag.controls = false; 21962 tag.removeAttribute('controls'); 21963 21964 // the attribute overrides the option 21965 if (tag.hasAttribute('autoplay')) { 21966 _this.options_.autoplay = true; 21967 } else { 21968 // otherwise use the setter to validate and 21969 // set the correct value. 21970 _this.autoplay(_this.options_.autoplay); 21971 } 21972 21973 /* 21974 * Store the internal state of scrubbing 21975 * 21976 * @private 21977 * @return {Boolean} True if the user is scrubbing 21978 */ 21979 _this.scrubbing_ = false; 21980 21981 _this.el_ = _this.createEl(); 21982 21983 // Set default value for lastPlaybackRate 21984 _this.cache_.lastPlaybackRate = _this.defaultPlaybackRate(); 21985 21986 // Make this an evented object and use `el_` as its event bus. 21987 evented(_this, { eventBusKey: 'el_' }); 21988 21989 // We also want to pass the original player options to each component and plugin 21990 // as well so they don't need to reach back into the player for options later. 21991 // We also need to do another copy of this.options_ so we don't end up with 21992 // an infinite loop. 21993 var playerOptionsCopy = mergeOptions(_this.options_); 21994 21995 // Load plugins 21996 if (options.plugins) { 21997 var plugins = options.plugins; 21998 21999 Object.keys(plugins).forEach(function (name$$1) { 22000 if (typeof this[name$$1] === 'function') { 22001 this[name$$1](plugins[name$$1]); 22002 } else { 22003 throw new Error('plugin "' + name$$1 + '" does not exist'); 22004 } 22005 }, _this); 22006 } 22007 22008 _this.options_.playerOptions = playerOptionsCopy; 22009 22010 _this.middleware_ = []; 22011 22012 _this.initChildren(); 22013 22014 // Set isAudio based on whether or not an audio tag was used 22015 _this.isAudio(tag.nodeName.toLowerCase() === 'audio'); 22016 22017 // Update controls className. Can't do this when the controls are initially 22018 // set because the element doesn't exist yet. 22019 if (_this.controls()) { 22020 _this.addClass('vjs-controls-enabled'); 22021 } else { 22022 _this.addClass('vjs-controls-disabled'); 22023 } 22024 22025 // Set ARIA label and region role depending on player type 22026 _this.el_.setAttribute('role', 'region'); 22027 if (_this.isAudio()) { 22028 _this.el_.setAttribute('aria-label', _this.localize('Audio Player')); 22029 } else { 22030 _this.el_.setAttribute('aria-label', _this.localize('Video Player')); 22031 } 22032 22033 if (_this.isAudio()) { 22034 _this.addClass('vjs-audio'); 22035 } 22036 22037 if (_this.flexNotSupported_()) { 22038 _this.addClass('vjs-no-flex'); 22039 } 22040 22041 // TODO: Make this smarter. Toggle user state between touching/mousing 22042 // using events, since devices can have both touch and mouse events. 22043 // if (browser.TOUCH_ENABLED) { 22044 // this.addClass('vjs-touch-enabled'); 22045 // } 22046 22047 // iOS Safari has broken hover handling 22048 if (!IS_IOS) { 22049 _this.addClass('vjs-workinghover'); 22050 } 22051 22052 // Make player easily findable by ID 22053 Player.players[_this.id_] = _this; 22054 22055 // Add a major version class to aid css in plugins 22056 var majorVersion = version.split('.')[0]; 22057 22058 _this.addClass('vjs-v' + majorVersion); 22059 22060 // When the player is first initialized, trigger activity so components 22061 // like the control bar show themselves if needed 22062 _this.userActive(true); 22063 _this.reportUserActivity(); 22064 22065 _this.one('play', _this.listenForUserActivity_); 22066 _this.on('fullscreenchange', _this.handleFullscreenChange_); 22067 _this.on('stageclick', _this.handleStageClick_); 22068 22069 _this.changingSrc_ = false; 22070 _this.playWaitingForReady_ = false; 22071 _this.playOnLoadstart_ = null; 22072 return _this; 22073 } 22074 22075 /** 22076 * Destroys the video player and does any necessary cleanup. 22077 * 22078 * This is especially helpful if you are dynamically adding and removing videos 22079 * to/from the DOM. 22080 * 22081 * @fires Player#dispose 22082 */ 22083 22084 22085 Player.prototype.dispose = function dispose() { 22086 /** 22087 * Called when the player is being disposed of. 22088 * 22089 * @event Player#dispose 22090 * @type {EventTarget~Event} 22091 */ 22092 this.trigger('dispose'); 22093 // prevent dispose from being called twice 22094 this.off('dispose'); 22095 22096 if (this.styleEl_ && this.styleEl_.parentNode) { 22097 this.styleEl_.parentNode.removeChild(this.styleEl_); 22098 this.styleEl_ = null; 22099 } 22100 22101 // Kill reference to this player 22102 Player.players[this.id_] = null; 22103 22104 if (this.tag && this.tag.player) { 22105 this.tag.player = null; 22106 } 22107 22108 if (this.el_ && this.el_.player) { 22109 this.el_.player = null; 22110 } 22111 22112 if (this.tech_) { 22113 this.tech_.dispose(); 22114 this.isPosterFromTech_ = false; 22115 this.poster_ = ''; 22116 } 22117 22118 if (this.playerElIngest_) { 22119 this.playerElIngest_ = null; 22120 } 22121 22122 if (this.tag) { 22123 this.tag = null; 22124 } 22125 22126 clearCacheForPlayer(this); 22127 22128 // the actual .el_ is removed here 22129 _Component.prototype.dispose.call(this); 22130 }; 22131 22132 /** 22133 * Create the `Player`'s DOM element. 22134 * 22135 * @return {Element} 22136 * The DOM element that gets created. 22137 */ 22138 22139 22140 Player.prototype.createEl = function createEl$$1() { 22141 var tag = this.tag; 22142 var el = void 0; 22143 var playerElIngest = this.playerElIngest_ = tag.parentNode && tag.parentNode.hasAttribute && tag.parentNode.hasAttribute('data-vjs-player'); 22144 var divEmbed = this.tag.tagName.toLowerCase() === 'video-js'; 22145 22146 if (playerElIngest) { 22147 el = this.el_ = tag.parentNode; 22148 } else if (!divEmbed) { 22149 el = this.el_ = _Component.prototype.createEl.call(this, 'div'); 22150 } 22151 22152 // Copy over all the attributes from the tag, including ID and class 22153 // ID will now reference player box, not the video tag 22154 var attrs = getAttributes(tag); 22155 22156 if (divEmbed) { 22157 el = this.el_ = tag; 22158 tag = this.tag =
22158document_1.createElement('video'); 22159 while (el.children.length) { 22160 tag.appendChild(el.firstChild); 22161 } 22162 22163 if (!hasClass(el, 'video-js')) { 22164 addClass(el, 'video-js'); 22165 } 22166 22167 el.appendChild(tag); 22168 22169 playerElIngest = this.playerElIngest_ = el; 22170 22171 // copy over properties from the video-js element 22172 // ie8 doesn't support Object.keys nor hasOwnProperty 22173 // on dom elements so we have to specify properties individually 22174 ['autoplay', 'controls', 'crossOrigin', 'defaultMuted', 'defaultPlaybackRate', 'loop', 'muted', 'playbackRate', 'src', 'volume'].forEach(function (prop) { 22175 if (typeof el[prop] !== 'undefined') { 22176 tag[prop] = el[prop]; 22177 } 22178 }); 22179 } 22180 22181 // set tabindex to -1 to remove the video element from the focus order 22182 tag.setAttribute('tabindex', '-1'); 22183 // Workaround for #4583 (JAWS+IE doesn't announce BPB or play button) 22184 // See https://github.com/FreedomScientific/VFO-standards-support/issues/78 22185 // Note that we can't detect if JAWS is being used, but this ARIA attribute 22186 // doesn't change behavior of IE11 if JAWS is not being used 22187 if (IE_VERSION) { 22188 tag.setAttribute('role', 'application'); 22189 } 22190 22191 // Remove width/height attrs from tag so CSS can make it 100% width/height 22192 tag.removeAttribute('width'); 22193 tag.removeAttribute('height'); 22194 22195 Object.getOwnPropertyNames(attrs).forEach(function (attr) { 22196 // workaround so we don't totally break IE7 22197 // http://stackoverflow.com/questions/3653444/css-styles-not-applied-on-dynamic-elements-in-internet-explorer-7 22198 if (attr === 'class') { 22199 el.className += ' ' + attrs[attr]; 22200 22201 if (divEmbed) { 22202 tag.className += ' ' + attrs[attr]; 22203 } 22204 } else { 22205 el.setAttribute(attr, attrs[attr]); 22206 22207 if (divEmbed) { 22208 tag.setAttribute(attr, attrs[attr]); 22209 } 22210 } 22211 }); 22212 22213 // Update tag id/class for use as HTML5 playback tech 22214 // Might think we should do this after embedding in container so .vjs-tech class 22215 // doesn't flash 100% width/height, but class only applies with .video-js parent 22216 tag.playerId = tag.id; 22217 tag.id += '_html5_api'; 22218 tag.className = 'vjs-tech'; 22219 22220 // Make player findable on elements 22221 tag.player = el.player = this; 22222 // Default state of video is paused 22223 this.addClass('vjs-paused'); 22224 22225 // Add a style element in the player that we'll use to set the width/height 22226 // of the player in a way that's still overrideable by CSS, just like the 22227 // video element 22228 if (window_1.VIDEOJS_NO_DYNAMIC_STYLE !== true) { 22229 this.styleEl_ = createStyleElement('vjs-styles-dimensions'); 22230 var defaultsStyleEl = $('.vjs-styles-defaults'); 22231 var head = $('head'); 22232 22233 head.insertBefore(this.styleEl_, defaultsStyleEl ? defaultsStyleEl.nextSibling : head.firstChild); 22234 } 22235 22236 // Pass in the width/height/aspectRatio options which will update the style el 22237 this.width(this.options_.width); 22238 this.height(this.options_.height); 22239 this.fluid(this.options_.fluid); 22240 this.aspectRatio(this.options_.aspectRatio); 22241 22242 // Hide any links within the video/audio tag, because IE doesn't hide them completely. 22243 var links = tag.getElementsByTagName('a'); 22244 22245 for (var i = 0; i < links.length; i++) { 22246 var linkEl = links.item(i); 22247 22248 addClass(linkEl, 'vjs-hidden'); 22249 linkEl.setAttribute('hidden', 'hidden'); 22250 } 22251 22252 // insertElFirst seems to cause the networkState to flicker from 3 to 2, so 22253 // keep track of the original for later so we can know if the source originally failed 22254 tag.initNetworkState_ = tag.networkState; 22255 22256 // Wrap video tag in div (el/box) container 22257 if (tag.parentNode && !playerElIngest) { 22258 tag.parentNode.insertBefore(el, tag); 22259 } 22260 22261 // insert the tag as the first child of the player element 22262 // then manually add it to the children array so that this.addChild 22263 // will work properly for other components 22264 // 22265 // Breaks iPhone, fixed in HTML5 setup. 22266 prependTo(tag, el); 22267 this.children_.unshift(tag); 22268 22269 // Set lang attr on player to ensure CSS :lang() in consistent with player 22270 // if it's been set to something different to the doc 22271 this.el_.setAttribute('lang', this.language_); 22272 22273 this.el_ = el; 22274 22275 return el; 22276 }; 22277 22278 /** 22279 * A getter/setter for the `Player`'s width. Returns the player'
vendor: 3,783 bytes, lines 22279-22430
22279s configured value. 22280 * To get the current width use `currentWidth()`. 22281 * 22282 * @param {number} [value] 22283 * The value to set the `Player`'s width to. 22284 * 22285 * @return {number} 22286 * The current width of the `Player` when getting. 22287 */ 22288 22289 22290 Player.prototype.width = function width(value) { 22291 return this.dimension('width', value); 22292 }; 22293 22294 /** 22295 * A getter/setter for the `Player`'s height. Returns the player's configured value. 22296 * To get the current height use `currentheight()`. 22297 * 22298 * @param {number} [value] 22299 * The value to set the `Player`'s heigth to. 22300 * 22301 * @return {number} 22302 * The current height of the `Player` when getting. 22303 */ 22304 22305 22306 Player.prototype.height = function height(value) { 22307 return this.dimension('height', value); 22308 }; 22309 22310 /** 22311 * A getter/setter for the `Player`'s width & height. 22312 * 22313 * @param {string} dimension 22314 * This string can be: 22315 * - 'width' 22316 * - 'height' 22317 * 22318 * @param {number} [value] 22319 * Value for dimension specified in the first argument. 22320 * 22321 * @return {number} 22322 * The dimension arguments value when getting (width/height). 22323 */ 22324 22325 22326 Player.prototype.dimension = function dimension(_dimension, value) { 22327 var privDimension = _dimension + '_'; 22328 22329 if (value === undefined) { 22330 return this[privDimension] || 0; 22331 } 22332 22333 if (value === '') { 22334 // If an empty string is given, reset the dimension to be automatic 22335 this[privDimension] = undefined; 22336 this.updateStyleEl_(); 22337 return; 22338 } 22339 22340 var parsedVal = parseFloat(value); 22341 22342 if (isNaN(parsedVal)) { 22343 log$1.error('Improper value "' + value + '" supplied for for ' + _dimension); 22344 return; 22345 } 22346 22347 this[privDimension] = parsedVal; 22348 this.updateStyleEl_(); 22349 }; 22350 22351 /** 22352 * A getter/setter/toggler for the vjs-fluid `className` on the `Player`. 22353 * 22354 * @param {boolean} [bool] 22355 * - A value of true adds the class. 22356 * - A value of false removes the class. 22357 * - No value will toggle the fluid class. 22358 * 22359 * @return {boolean|undefined} 22360 * - The value of fluid when getting. 22361 * - `undefined` when setting. 22362 */ 22363 22364 22365 Player.prototype.fluid = function fluid(bool) { 22366 if (bool === undefined) { 22367 return !!this.fluid_; 22368 } 22369 22370 this.fluid_ = !!bool; 22371 22372 if (bool) { 22373 this.addClass('vjs-fluid'); 22374 } else { 22375 this.removeClass('vjs-fluid'); 22376 } 22377 22378 this.updateStyleEl_(); 22379 }; 22380 22381 /** 22382 * Get/Set the aspect ratio 22383 * 22384 * @param {string} [ratio] 22385 * Aspect ratio for player 22386 * 22387 * @return {string|undefined} 22388 * returns the current aspect ratio when getting 22389 */ 22390 22391 /** 22392 * A getter/setter for the `Player`'s aspect ratio. 22393 * 22394 * @param {string} [ratio] 22395 * The value to set the `Player's aspect ratio to. 22396 * 22397 * @return {string|undefined} 22398 * - The current aspect ratio of the `Player` when getting. 22399 * - undefined when setting 22400 */ 22401 22402 22403 Player.prototype.aspectRatio = function aspectRatio(ratio) { 22404 if (ratio === undefined) { 22405 return this.aspectRatio_; 22406 } 22407 22408 // Check for width:height format 22409 if (!/^\d+\:\d+$/.test(ratio)) { 22410 throw new Error('Improper value supplied for aspect ratio. The format should be width:height, for example 16:9.'); 22411 } 22412 this.aspectRatio_ = ratio; 22413 22414 // We're assuming if you set an aspect ratio you want fluid mode, 22415 // because in fixed mode you could calculate width and height yourself. 22416 this.fluid(true); 22417 22418 this.updateStyleEl_(); 22419 }; 22420 22421 /** 22422 * Update styles of the `Player` element (height, width and aspect ratio). 22423 * 22424 * @private 22425 * @listens Tech#loadedmetadata 22426 */ 22427 22428 22429 Player.prototype.updateStyleEl_ = function updateStyleEl_() { 22430 if (window_1
vendor: 9,090 bytes, lines 22430-22692
22430.VIDEOJS_NO_DYNAMIC_STYLE === true) { 22431 var _width = typeof this.width_ === 'number' ? this.width_ : this.options_.width; 22432 var _height = typeof this.height_ === 'number' ? this.height_ : this.options_.height; 22433 var techEl = this.tech_ && this.tech_.el(); 22434 22435 if (techEl) { 22436 if (_width >= 0) { 22437 techEl.width = _width; 22438 } 22439 if (_height >= 0) { 22440 techEl.height = _height; 22441 } 22442 } 22443 22444 return; 22445 } 22446 22447 var width = void 0; 22448 var height = void 0; 22449 var aspectRatio = void 0; 22450 var idClass = void 0; 22451 22452 // The aspect ratio is either used directly or to calculate width and height. 22453 if (this.aspectRatio_ !== undefined && this.aspectRatio_ !== 'auto') { 22454 // Use any aspectRatio that's been specifically set 22455 aspectRatio = this.aspectRatio_; 22456 } else if (this.videoWidth() > 0) { 22457 // Otherwise try to get the aspect ratio from the video metadata 22458 aspectRatio = this.videoWidth() + ':' + this.videoHeight(); 22459 } else { 22460 // Or use a default. The video element's is 2:1, but 16:9 is more common. 22461 aspectRatio = '16:9'; 22462 } 22463 22464 // Get the ratio as a decimal we can use to calculate dimensions 22465 var ratioParts = aspectRatio.split(':'); 22466 var ratioMultiplier = ratioParts[1] / ratioParts[0]; 22467 22468 if (this.width_ !== undefined) { 22469 // Use any width that's been specifically set 22470 width = this.width_; 22471 } else if (this.height_ !== undefined) { 22472 // Or calulate the width from the aspect ratio if a height has been set 22473 width = this.height_ / ratioMultiplier; 22474 } else { 22475 // Or use the video's metadata, or use the video el's default of 300 22476 width = this.videoWidth() || 300; 22477 } 22478 22479 if (this.height_ !== undefined) { 22480 // Use any height that's been specifically set 22481 height = this.height_; 22482 } else { 22483 // Otherwise calculate the height from the ratio and the width 22484 height = width * ratioMultiplier; 22485 } 22486 22487 // Ensure the CSS class is valid by starting with an alpha character 22488 if (/^[^a-zA-Z]/.test(this.id())) { 22489 idClass = 'dimensions-' + this.id(); 22490 } else { 22491 idClass = this.id() + '-dimensions'; 22492 } 22493 22494 // Ensure the right class is still on the player for the style element 22495 this.addClass(idClass); 22496 22497 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 '); 22498 }; 22499 22500 /** 22501 * Load/Create an instance of playback {@link Tech} including element 22502 * and API methods. Then append the `Tech` element in `Player` as a child. 22503 * 22504 * @param {string} techName 22505 * name of the playback technology 22506 * 22507 * @param {string} source 22508 * video source 22509 * 22510 * @private 22511 */ 22512 22513 22514 Player.prototype.loadTech_ = function loadTech_(techName, source) { 22515 var _this2 = this; 22516 22517 // Pause and remove current playback technology 22518 if (this.tech_) { 22519 this.unloadTech_(); 22520 } 22521 22522 var titleTechName = toTitleCase(techName); 22523 var camelTechName = techName.charAt(0).toLowerCase() + techName.slice(1); 22524 22525 // get rid of the HTML5 video tag as soon as we are using another tech 22526 if (titleTechName !== 'Html5' && this.tag) { 22527 Tech.getTech('Html5').disposeMediaElement(this.tag); 22528 this.tag.player = null; 22529 this.tag = null; 22530 } 22531 22532 this.techName_ = titleTechName; 22533 22534 // Turn off API access because we're loading a new tech that might load asynchronously 22535 this.isReady_ = false; 22536 22537 // if autoplay is a string we pass false to the tech 22538 // because the player is going to handle autoplay on `loadstart` 22539 var autoplay = typeof this.autoplay() === 'string' ? false : this.autoplay(); 22540 22541 // Grab tech-specific options from player options and add source and parent element to use. 22542 var techOptions = { 22543 source: source, 22544 autoplay: autoplay, 22545 'nativeControlsForTouch': this.options_.nativeControlsForTouch, 22546 'playerId': this.id(), 22547 'techId': this.id() + '_' + titleTechName + '_api', 22548 'playsinline': this.options_.playsinline, 22549 'preload': this.options_.preload, 22550 'loop': this.options_.loop, 22551 'muted': this.options_.muted, 22552 'poster': this.poster(), 22553 'language': this.language(), 22554 'playerElIngest': this.playerElIngest_ || false, 22555 'vtt.js': this.options_['vtt.js'], 22556 'canOverridePoster': !!this.options_.techCanOverridePoster, 22557 'enableSourceset': this.options_.enableSourceset 22558 }; 22559 22560 ALL.names.forEach(function (name$$1) { 22561 var props = ALL[name$$1]; 22562 22563 techOptions[props.getterName] = _this2[props.privateName]; 22564 }); 22565 22566 assign(techOptions, this.options_[titleTechName]); 22567 assign(techOptions, this.options_[camelTechName]); 22568 assign(techOptions, this.options_[techName.toLowerCase()]); 22569 22570 if (this.tag) { 22571 techOptions.tag = this.tag; 22572 } 22573 22574 if (source && source.src === this.cache_.src && this.cache_.currentTime > 0) { 22575 techOptions.startTime = this.cache_.currentTime; 22576 } 22577 22578 // Initialize tech instance 22579 var TechClass = Tech.getTech(techName); 22580 22581 if (!TechClass) { 22582 throw new Error('No Tech named \'' + titleTechName + '\' exists! \'' + titleTechName + '\' should be registered using videojs.registerTech()\''); 22583 } 22584 22585 this.tech_ = new TechClass(techOptions); 22586 22587 // player.triggerReady is always async, so don't need this to be async 22588 this.tech_.ready(bind(this, this.handleTechReady_), true); 22589 22590 textTrackConverter.jsonToTextTracks(this.textTracksJson_ || [], this.tech_); 22591 22592 // Listen to all HTML5-defined events and trigger them on the player 22593 TECH_EVENTS_RETRIGGER.forEach(function (event) { 22594 _this2.on(_this2.tech_, event, _this2['handleTech' + toTitleCase(event) + '_']); 22595 }); 22596 22597 Object.keys(TECH_EVENTS_QUEUE).forEach(function (event) { 22598 _this2.on(_this2.tech_, event, function (eventObj) { 22599 if (_this2.tech_.playbackRate() === 0 && _this2.tech_.seeking()) { 22600 _this2.queuedCallbacks_.push({ 22601 callback: _this2['handleTech' + TECH_EVENTS_QUEUE[event] + '_'].bind(_this2), 22602 event: eventObj 22603 }); 22604 return; 22605 } 22606 _this2['handleTech' + TECH_EVENTS_QUEUE[event] + '_'](eventObj); 22607 }); 22608 }); 22609 22610 this.on(this.tech_, 'loadstart', this.handleTechLoadStart_); 22611 this.on(this.tech_, 'sourceset', this.handleTechSourceset_); 22612 this.on(this.tech_, 'waiting', this.handleTechWaiting_); 22613 this.on(this.tech_, 'ended', this.handleTechEnded_); 22614 this.on(this.tech_, 'seeking', this.handleTechSeeking_); 22615 this.on(this.tech_, 'play', this.handleTechPlay_); 22616 this.on(this.tech_, 'firstplay', this.handleTechFirstPlay_); 22617 this.on(this.tech_, 'pause', this.handleTechPause_); 22618 this.on(this.tech_, 'durationchange', this.handleTechDurationChange_); 22619 this.on(this.tech_, 'fullscreenchange', this.handleTechFullscreenChange_); 22620 this.on(this.tech_, 'error', this.handleTechError_); 22621 this.on(this.tech_, 'loadedmetadata', this.updateStyleEl_); 22622 this.on(this.tech_, 'posterchange', this.handleTechPosterChange_); 22623 this.on(this.tech_, 'textdata', this.handleTechTextData_); 22624 this.on(this.tech_, 'ratechange', this.handleTechRateChange_); 22625 22626 this.usingNativeControls(this.techGet_('controls')); 22627 22628 if (this.controls() && !this.usingNativeControls()) { 22629 this.addTechControlsListeners_(); 22630 } 22631 22632 // Add the tech element in the DOM if it was not already there 22633 // Make sure to not insert the original video element if using Html5 22634 if (this.tech_.el().parentNode !== this.el() && (titleTechName !== 'Html5' || !this.tag)) { 22635 prependTo(this.tech_.el(), this.el()); 22636 } 22637 22638 // Get rid of the original video tag reference after the first tech is loaded 22639 if (this.tag) { 22640 this.tag.player = null; 22641 this.tag = null; 22642 } 22643 }; 22644 22645 /** 22646 * Unload and dispose of the current playback {@link Tech}. 22647 * 22648 * @private 22649 */ 22650 22651 22652 Player.prototype.unloadTech_ = function unloadTech_() { 22653 var _this3 = this; 22654 22655 // Save the current text tracks so that we can reuse the same text tracks with the next tech 22656 ALL.names.forEach(function (name$$1) { 22657 var props = ALL[name$$1]; 22658 22659 _this3[props.privateName] = _this3[props.getterName](); 22660 }); 22661 this.textTracksJson_ = textTrackConverter.textTracksToJson(this.tech_); 22662 22663 this.isReady_ = false; 22664 22665 this.tech_.dispose(); 22666 22667 this.tech_ = false; 22668 22669 if (this.isPosterFromTech_) { 22670 this.poster_ = ''; 22671 this.trigger('posterchange'); 22672 } 22673 22674 this.isPosterFromTech_ = false; 22675 }; 22676 22677 /** 22678 * Return a reference to the current {@link Tech}. 22679 * It will print a warning by default about the danger of using the tech directly 22680 * but any argument that is passed in will silence the warning. 22681 * 22682 * @param {*} [safety] 22683 * Anything passed in to silence the warning 22684 * 22685 * @return {Tech} 22686 * The Tech 22687 */ 22688 22689 22690 Player.prototype.tech = function tech(safety) { 22691 if (safety === undefined) { 22692 log$1.warn(
22692tsml(_templateObject$1)); 22693 } 22694 22695 return this.tech_; 22696 }; 22697 22698 /** 22699 * Set up click and touch listeners for the playback element 22700 * 22701 * - On desktops: a click on the video itself will toggle playback 22702 * - On mobile devices: a click on the video toggles controls 22703 * which is done by toggling the user state between active and 22704 * inactive 22705 * - A tap can signal that a user has become active or has become inactive 22706 * e.g. a quick tap on an iPhone movie should reveal the controls. Another 22707 * quick tap should hide them again (signaling the user is in an inactive 22708 * viewing state) 22709 * - In addition to this, we still want the user to be considered inactive after 22710 * a few seconds of inactivity. 22711 * 22712 * > Note: the only part of iOS interaction we can't mimic with this setup 22713 * is a touch and hold on the video element counting as activity in order to 22714 * keep the controls showing, but that shouldn't be an issue. A touch and hold 22715 * on any controls will still keep the user active 22716 * 22717 * @private 22718 */ 22719 22720 22721 Player.prototype.addTechControlsListeners_ = function addTechControlsListeners_() { 22722 // Make sure to remove all the previous listeners in case we are called multiple times. 22723 this.removeTechControlsListeners_(); 22724 22725 // Some browsers (Chrome & IE) don't trigger a click on a flash swf, but do 22726 // trigger mousedown/up. 22727 // http://stackoverflow.com/questions/1444562/javascript-onclick-event-over-flash-object 22728 // Any touch events are set to block the mousedown event from happening 22729 this.on(this.tech_, 'mousedown', this.handleTechClick_); 22730 22731 // If the controls were hidden we don't want that to change without a tap event 22732 // so we'll check if the controls were already showing before reporting user 22733 // activity 22734 this.on(this.tech_, 'touchstart', this.handleTechTouchStart_); 22735 this.on(this.tech_, 'touchmove', this.handleTechTouchMove_); 22736 this.on(this.tech_, 'touchend', this.handleTechTouchEnd_); 22737 22738 // The tap listener needs to come after the touchend listener because the tap 22739 // listener cancels out any reportedUserActivity when setting userActive(false) 22740 this.on(this.tech_, 'tap', this.handleTechTap_); 22741 }; 22742 22743 /** 22744 * Remove the listeners used for click and tap controls. This is needed for 22745 * toggling to controls disabled, where a tap/touch should do nothing. 22746 * 22747 * @private 22748 */ 22749 22750 22751 Player.prototype.removeTechControlsListeners_ = function removeTechControlsListeners_() { 22752 // We don't want to just use `this.off()` because there might be other needed 22753 // listeners added by techs that extend this. 22754 this.off(this.tech_, 'tap', this.handleTechTap_); 22755 this.off(this.tech_, 'touchstart', this.handleTechTouchStart_); 22756 this.off(this.tech_, 'touchmove', this.handleTechTouchMove_); 22757 this.off(this.tech_, 'touchend', this.handleTechTouchEnd_); 22758 this.off(this.tech_, 'mousedown', this.handleTechClick_); 22759 }; 22760 22761 /** 22762 * Player waits for the tech to be ready 22763 * 22764 * @private 22765 */ 22766 22767 22768 Player.prototype.handleTechReady_ = function handleTechReady_() { 22769 this.triggerReady(); 22770 22771 // Keep the same volume as before 22772 if (this.cache_.volume) { 22773 this.techCall_('setVolume', this.cache_.volume); 22774 } 22775 22776 // Look if the tech found a higher resolution poster while loading 22777 this.handleTechPosterChange_(); 22778 22779 // Update the duration if available 22780 this.handleTechDurationChange_(); 22781 22782 // Chrome and Safari both have issues with autoplay. 22783 // In Safari (5.1.1), when we move the video element into the container div, autoplay doesn't work. 22784 // In Chrome (15), if you have autoplay + a poster + no controls, the video gets hidden (but audio plays) 22785 // This fixes both issues. Need to wait for API, so it updates displays correctly 22786 if ((this.src() || this.currentSrc()) && this.tag && this.options_.autoplay && this.paused()) {
vendor: 5,078 bytes, lines 22787-22967
22787 try { 22788 // Chrome Fix. Fixed in Chrome v16. 22789 delete this.tag.poster; 22790 } catch (e) { 22791 log$1('deleting tag.poster throws in some browsers', e); 22792 } 22793 } 22794 }; 22795 22796 /** 22797 * Retrigger the `loadstart` event that was triggered by the {@link Tech}. This 22798 * function will also trigger {@link Player#firstplay} if it is the first loadstart 22799 * for a video. 22800 * 22801 * @fires Player#loadstart 22802 * @fires Player#firstplay 22803 * @listens Tech#loadstart 22804 * @private 22805 */ 22806 22807 22808 Player.prototype.handleTechLoadStart_ = function handleTechLoadStart_() { 22809 // TODO: Update to use `emptied` event instead. See #1277. 22810 22811 this.removeClass('vjs-ended'); 22812 this.removeClass('vjs-seeking'); 22813 22814 // reset the error state 22815 this.error(null); 22816 22817 // If it's already playing we want to trigger a firstplay event now. 22818 // The firstplay event relies on both the play and loadstart events 22819 // which can happen in any order for a new source 22820 if (!this.paused()) { 22821 /** 22822 * Fired when the user agent begins looking for media data 22823 * 22824 * @event Player#loadstart 22825 * @type {EventTarget~Event} 22826 */ 22827 this.trigger('loadstart'); 22828 this.trigger('firstplay'); 22829 } else { 22830 // reset the hasStarted state 22831 this.hasStarted(false); 22832 this.trigger('loadstart'); 22833 } 22834 22835 // autoplay happens after loadstart for the browser, 22836 // so we mimic that behavior 22837 this.manualAutoplay_(this.autoplay()); 22838 }; 22839 22840 /** 22841 * Handle autoplay string values, rather than the typical boolean 22842 * values that should be handled by the tech. Note that this is not 22843 * part of any specification. Valid values and what they do can be 22844 * found on the autoplay getter at Player#autoplay() 22845 */ 22846 22847 22848 Player.prototype.manualAutoplay_ = function manualAutoplay_(type) { 22849 var _this4 = this; 22850 22851 if (!this.tech_ || typeof type !== 'string') { 22852 return; 22853 } 22854 22855 var muted = function muted() { 22856 var previouslyMuted = _this4.muted(); 22857 22858 _this4.muted(true); 22859 22860 var playPromise = _this4.play(); 22861 22862 if (!playPromise || !playPromise.then || !playPromise['catch']) { 22863 return; 22864 } 22865 22866 return playPromise['catch'](function (e) { 22867 // restore old value of muted on failure 22868 _this4.muted(previouslyMuted); 22869 }); 22870 }; 22871 22872 var promise = void 0; 22873 22874 if (type === 'any') { 22875 promise = this.play(); 22876 22877 if (promise && promise.then && promise['catch']) { 22878 promise['catch'](function () { 22879 return muted(); 22880 }); 22881 } 22882 } else if (type === 'muted') { 22883 promise = muted(); 22884 } else { 22885 promise = this.play(); 22886 } 22887 22888 if (!promise || !promise.then || !promise['catch']) { 22889 return; 22890 } 22891 22892 return promise.then(function () { 22893 _this4.trigger({ type: 'autoplay-success', autoplay: type }); 22894 })['catch'](function (e) { 22895 _this4.trigger({ type: 'autoplay-failure', autoplay: type }); 22896 }); 22897 }; 22898 22899 /** 22900 * Update the internal source caches so that we return the correct source from 22901 * `src()`, `currentSource()`, and `currentSources()`. 22902 * 22903 * > Note: `currentSources` will not be updated if the source that is passed in exists 22904 * in the current `currentSources` cache. 22905 * 22906 * 22907 * @param {Tech~SourceObject} srcObj 22908 * A string or object source to update our caches to. 22909 */ 22910 22911 22912 Player.prototype.updateSourceCaches_ = function updateSourceCaches_() { 22913 var srcObj = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ''; 22914 22915 22916 var src = srcObj; 22917 var type = ''; 22918 22919 if (typeof src !== 'string') { 22920 src = srcObj.src; 22921 type = srcObj.type; 22922 } 22923 // make sure all the caches are set to default values 22924 // to prevent null checking 22925 this.cache_.source = this.cache_.source || {}; 22926 this.cache_.sources = this.cache_.sources || []; 22927 22928 // try to get the type of the src that was passed in 22929 if (src && !type) { 22930 type = findMimetype(this, src); 22931 } 22932 22933 // update `currentSource` cache always 22934 this.cache_.source = mergeOptions({}, srcObj, { src: src, type: type }); 22935 22936 var matchingSources = this.cache_.sources.filter(function (s) { 22937 return s.src && s.src === src; 22938 }); 22939 var sourceElSources = []; 22940 var sourceEls = this.$$('source'); 22941 var matchingSourceEls = []; 22942 22943 for (var i = 0; i < sourceEls.length; i++) { 22944 var sourceObj = getAttributes(sourceEls[i]); 22945 22946 sourceElSources.push(sourceObj); 22947 22948 if (sourceObj.src && sourceObj.src === src) { 22949 matchingSourceEls.push(sourceObj.src); 22950 } 22951 } 22952 22953 // if we have matching source els but not matching sources 22954 // the current source cache is not up to date 22955 if (matchingSourceEls.length && !matchingSources.length) { 22956 this.cache_.sources = sourceElSources; 22957 // if we don't have matching source or source els set the 22958 // sources cache to the `currentSource` cache 22959 } else if (!matchingSources.length) { 22960 this.cache_.sources = [this.cache_.source]; 22961 } 22962 22963 // update the tech `src` cache 22964 this.cache_.src = src; 22965 }; 22966 22967 /**
22968 * *EXPERIMENTAL* Fired when the source is set or changed on the {@link Tech} 22969 * causing the media element to reload. 22970 * 22971 * It will fire for the initial source and each subsequent source. 22972 * This event is a custom event from Video.js and is triggered by the {@link Tech}. 22973 * 22974 * The event object for this event contains a `src` property that will contain the source 22975 * that was available when the event was triggered. This is generally only necessary if Video.js 22976 * is switching techs while the source was being changed. 22977 * 22978 * It is also fired when `load` is called on the player (or media element) 22979 * because the {@link https://html.spec.whatwg.org/multipage/media.html#dom-media-load|specification for `load`} 22980 * says that the resource selection algorithm needs to be aborted and restarted. 22981 * In this case, it is very likely that the `src` property will be set to the 22982 * empty string `""` to indicate we do not know what the source will be but 22983 * that it is changing. 22984 * 22985 * *This event is currently still experimental and may change in minor releases.* 22986 * __To use this, pass `enableSourceset` option to the player.__ 22987 * 22988 * @event Player#sourceset 22989 * @type {EventTarget~Event} 22990 * @prop {string} src 22991 * The source url available when the `sourceset` was triggered. 22992 * It will be an empty string if we cannot know what the source is 22993 * but know that the source will change. 22994 */ 22995 /** 22996 * Retrigger the `sourceset` event that was triggered by the {@link Tech}. 22997 * 22998 * @fires Player#sourceset 22999 * @listens Tech#sourceset 23000 * @private 23001 */ 23002 23003 23004 Player.prototype.handleTechSourceset_ = function handleTechSourceset_(event) { 23005 var _this5 = this; 23006 23007 // only update the source cache when the source 23008 // was not updated using the player api 23009 if (!this.changingSrc_) { 23010 // update the source to the intial source right away 23011 // in some cases this will be empty string 23012 this.updateSourceCaches_(event.src); 23013 23014 // if the `sourceset` `src` was an empty string 23015 // wait for a `loadstart` to update the cache to `currentSrc`. 23016 // If a sourceset happens before a `loadstart`, we reset the state 23017 // as this function will be called again. 23018 if (!event.src) { 23019 var updateCache = function updateCache(e) { 23020 if (e.type !== 'sourceset') { 23021 _this5.updateSourceCaches_(_this5.techGet_('currentSrc')); 23022 } 23023 23024 _this5.tech_.off(['sourceset', 'loadstart'], updateCache); 23025 }; 23026 23027 this.tech_.one(['sourceset', 'loadstart'], updateCache); 23028 } 23029 } 23030 23031 this.trigger({ 23032 src: event.src, 23033 type: 'sourceset' 23034 }); 23035 }; 23036 23037 /** 23038 * Add/remove the vjs-has-started class 23039 * 23040 * @fires Player#firstplay 23041 * 23042 * @param {boolean} request 23043 * - true: adds the class 23044 * - false: remove the class 23045 * 23046 * @return {boolean} 23047 * the boolean value of hasStarted_ 23048 */ 23049 23050 23051 Player.prototype.hasStarted = function hasStarted(request) { 23052 if (request === undefined) { 23053 // act as getter, if we have no request to change 23054 return this.hasStarted_; 23055 } 23056 23057 if (request === this.hasStarted_) { 23058 return; 23059 } 23060 23061 this.hasStarted_ = request; 23062 23063 if (this.hasStarted_) { 23064 this.addClass('vjs-has-started'); 23065 this.trigger('firstplay'); 23066 } else { 23067 this.removeClass('vjs-has-started'); 23068 } 23069 }; 23070 23071 /** 23072 * Fired whenever the media begins or resumes playback 23073 * 23074 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-play} 23075 * @fires Player#play 23076 * @listens Tech#play 23077 * @private 23078 */ 23079 23080 23081 Player.prototype.handleTechPlay_ = function handleTechPlay_() { 23082 this.removeClass('vjs-ended'); 23083 this.removeClass('vjs-paused'); 23084 this.addClass('vjs-playing'); 23085 23086 // hide the poster when the user hits play 23087 this.hasStarted(true); 23088 /** 23089 * Triggered whenever an {@link Tech#play} event happens. Indicates that 23090 * playback has started or resumed. 23091 * 23092 * @event Player#play 23093 * @type {EventTarget~Event} 23094 */ 23095 this.trigger('play'); 23096 }; 23097 23098 /** 23099 * Retrigger the `ratechange` event that was triggered by the {@link Tech}. 23100 * 23101 * If there were any events queued while the playback rate was zero, fire 23102 * those events now. 23103 * 23104 * @private 23105 * @method Player#handleTechRateChange_ 23106 * @fires Player#ratechange 23107 * @listens Tech#ratechange 23108 */ 23109 23110 23111 Player.prototype.handleTechRateChange_ = function handleTechRateChange_() { 23112 if (this.tech_.playbackRate() > 0 && this.cache_.lastPlaybackRate === 0) { 23113 this.queuedCallbacks_.forEach(function (queued) { 23114 return queued.callback(queued.event); 23115 }); 23116 this.queuedCallbacks_ = []; 23117 } 23118 this.cache_.lastPlaybackRate = this.tech_.playbackRate(); 23119 /** 23120 * Fires when the playing speed of the audio/video is changed 23121 * 23122 * @event Player#ratechange 23123 * @type {event} 23124 */ 23125 this.trigger('ratechange'); 23126 }; 23127 23128 /** 23129 * Retrigger the `waiting` event that was triggered by the {@link Tech}. 23130 * 23131 * @fires Player#waiting 23132 * @listens Tech#waiting 23133 * @private 23134 */ 23135 23136 23137 Player.prototype.handleTechWaiting_ = function handleTechWaiting_() { 23138 var _this6 = this; 23139 23140 this.addClass('vjs-waiting'); 23141 /** 23142 * A readyState change on the DOM element has caused playback to stop. 23143 * 23144 * @event Player#waiting 23145 * @type {EventTarget~Event} 23146 */ 23147 this.trigger('waiting'); 23148 this.one('timeupdate', function () { 23149 return _this6.removeClass('vjs-waiting'); 23150 }); 23151 }; 23152 23153 /** 23154 * Retrigger the `canplay` event that was triggered by the {@link Tech}. 23155 * > Note: This is not consistent between browsers. See #1351 23156 * 23157 * @fires Player#canplay 23158 * @listens Tech#canplay 23159 * @private 23160 */ 23161 23162 23163 Player.prototype.handleTechCanPlay_ = function handleTechCanPlay_() { 23164 this.removeClass('vjs-waiting'); 23165 /** 23166 * The media has a readyState of HAVE_FUTURE_DATA or greater. 23167 * 23168 * @event Player#canplay 23169 * @type {EventTarget~Event} 23170 */ 23171 this.trigger('canplay'); 23172 }; 23173 23174 /** 23175 * Retrigger the `canplaythrough` event that was triggered by the {@link Tech}. 23176 * 23177 * @fires Player#canplaythrough 23178 * @listens Tech#canplaythrough 23179 * @private 23180 */ 23181 23182 23183 Player.prototype.handleTechCanPlayThrough_ = function handleTechCanPlayThrough_() { 23184 this.removeClass('vjs-waiting'); 23185 /** 23186 * The media has a readyState of HAVE_ENOUGH_DATA or greater. This means that the 23187 * entire media file can be played without buffering. 23188 * 23189 * @event Player#canplaythrough 23190 * @type {EventTarget~Event} 23191 */ 23192 this.trigger('canplaythrough'); 23193 }; 23194 23195 /** 23196 * Retrigger the `playing` event that was triggered by the {@link Tech}. 23197 * 23198 * @fires Player#playing 23199 * @listens Tech#playing 23200 * @private 23201 */ 23202 23203 23204 Player.prototype.handleTechPlaying_ = function handleTechPlaying_() { 23205 this.removeClass('vjs-waiting'); 23206 /** 23207 * The media is no longer blocked from playback, and has started playing. 23208 * 23209 * @event Player#playing 23210 * @type {EventTarget~Event} 23211 */ 23212 this.trigger('playing'); 23213 }; 23214 23215 /** 23216 * Retrigger the `seeking` event that was triggered by the {@link Tech}. 23217 * 23218 * @fires Player#seeking 23219 * @listens Tech#seeking 23220 * @private 23221 */ 23222 23223 23224 Player.prototype.handleTechSeeking_ = function handleTechSeeking_() { 23225 this.addClass('vjs-seeking'); 23226 /** 23227 * Fired whenever the player is jumping to a new time 23228 * 23229 * @event Player#seeking 23230 * @type {EventTarget~Event} 23231 */ 23232 this.trigger('seeking'); 23233 }; 23234 23235 /** 23236 * Retrigger the `seeked` event that was triggered by the {@link Tech}. 23237 * 23238 * @fires Player#seeked 23239 * @listens Tech#seeked 23240 * @private 23241 */ 23242 23243 23244 Player.prototype.handleTechSeeked_ = function handleTechSeeked_() { 23245 this.removeClass('vjs-seeking'); 23246 /** 23247 * Fired when the player has finished jumping to a new time 23248 * 23249 * @event Player#seeked 23250 * @type {EventTarget~Event} 23251 */ 23252 this.trigger('seeked'); 23253 }; 23254 23255 /** 23256 * Retrigger the `firstplay` event that was triggered by the {@link Tech}. 23257 * 23258 * @fires Player#firstplay 23259 * @listens Tech#firstplay 23260 * @deprecated As of 6.0 firstplay event is deprecated. 23261 * As of 6.0 passing the `starttime` option to the player and the firstplay event are deprecated. 23262 * @private 23263 */ 23264 23265 23266 Player.prototype.handleTechFirstPlay_ = function handleTechFirstPlay_() { 23267 // If the first starttime attribute is specified 23268 // then we will start at the given offset in seconds 23269 if (this.options_.starttime) { 23270 log$1.warn('Passing the `starttime` option to the player will be deprecated
vendor: 17,788 bytes, lines 23270-23970
23270in 6.0'); 23271 this.currentTime(this.options_.starttime); 23272 } 23273 23274 this.addClass('vjs-has-started'); 23275 /** 23276 * Fired the first time a video is played. Not part of the HLS spec, and this is 23277 * probably not the best implementation yet, so use sparingly. If you don't have a 23278 * reason to prevent playback, use `myPlayer.one('play');` instead. 23279 * 23280 * @event Player#firstplay 23281 * @deprecated As of 6.0 firstplay event is deprecated. 23282 * @type {EventTarget~Event} 23283 */ 23284 this.trigger('firstplay'); 23285 }; 23286 23287 /** 23288 * Retrigger the `pause` event that was triggered by the {@link Tech}. 23289 * 23290 * @fires Player#pause 23291 * @listens Tech#pause 23292 * @private 23293 */ 23294 23295 23296 Player.prototype.handleTechPause_ = function handleTechPause_() { 23297 this.removeClass('vjs-playing'); 23298 this.addClass('vjs-paused'); 23299 /** 23300 * Fired whenever the media has been paused 23301 * 23302 * @event Player#pause 23303 * @type {EventTarget~Event} 23304 */ 23305 this.trigger('pause'); 23306 }; 23307 23308 /** 23309 * Retrigger the `ended` event that was triggered by the {@link Tech}. 23310 * 23311 * @fires Player#ended 23312 * @listens Tech#ended 23313 * @private 23314 */ 23315 23316 23317 Player.prototype.handleTechEnded_ = function handleTechEnded_() { 23318 this.addClass('vjs-ended'); 23319 if (this.options_.loop) { 23320 this.currentTime(0); 23321 this.play(); 23322 } else if (!this.paused()) { 23323 this.pause(); 23324 } 23325 23326 /** 23327 * Fired when the end of the media resource is reached (currentTime == duration) 23328 * 23329 * @event Player#ended 23330 * @type {EventTarget~Event} 23331 */ 23332 this.trigger('ended'); 23333 }; 23334 23335 /** 23336 * Fired when the duration of the media resource is first known or changed 23337 * 23338 * @listens Tech#durationchange 23339 * @private 23340 */ 23341 23342 23343 Player.prototype.handleTechDurationChange_ = function handleTechDurationChange_() { 23344 this.duration(this.techGet_('duration')); 23345 }; 23346 23347 /** 23348 * Handle a click on the media element to play/pause 23349 * 23350 * @param {EventTarget~Event} event 23351 * the event that caused this function to trigger 23352 * 23353 * @listens Tech#mousedown 23354 * @private 23355 */ 23356 23357 23358 Player.prototype.handleTechClick_ = function handleTechClick_(event) { 23359 if (!isSingleLeftClick(event)) { 23360 return; 23361 } 23362 23363 // When controls are disabled a click should not toggle playback because 23364 // the click is considered a control 23365 if (!this.controls_) { 23366 return; 23367 } 23368 23369 if (this.paused()) { 23370 silencePromise(this.play()); 23371 } else { 23372 this.pause(); 23373 } 23374 }; 23375 23376 /** 23377 * Handle a tap on the media element. It will toggle the user 23378 * activity state, which hides and shows the controls. 23379 * 23380 * @listens Tech#tap 23381 * @private 23382 */ 23383 23384 23385 Player.prototype.handleTechTap_ = function handleTechTap_() { 23386 this.userActive(!this.userActive()); 23387 }; 23388 23389 /** 23390 * Handle touch to start 23391 * 23392 * @listens Tech#touchstart 23393 * @private 23394 */ 23395 23396 23397 Player.prototype.handleTechTouchStart_ = function handleTechTouchStart_() { 23398 this.userWasActive = this.userActive(); 23399 }; 23400 23401 /** 23402 * Handle touch to move 23403 * 23404 * @listens Tech#touchmove 23405 * @private 23406 */ 23407 23408 23409 Player.prototype.handleTechTouchMove_ = function handleTechTouchMove_() { 23410 if (this.userWasActive) { 23411 this.reportUserActivity(); 23412 } 23413 }; 23414 23415 /** 23416 * Handle touch to end 23417 * 23418 * @param {EventTarget~Event} event 23419 * the touchend event that triggered 23420 * this function 23421 * 23422 * @listens Tech#touchend 23423 * @private 23424 */ 23425 23426 23427 Player.prototype.handleTechTouchEnd_ = function handleTechTouchEnd_(event) { 23428 // Stop the mouse events from also happening 23429 event.preventDefault(); 23430 }; 23431 23432 /** 23433 * Fired when the player switches in or out of fullscreen mode 23434 * 23435 * @private 23436 * @listens Player#fullscreenchange 23437 */ 23438 23439 23440 Player.prototype.handleFullscreenChange_ = function handleFullscreenChange_() { 23441 if (this.isFullscreen()) { 23442 this.addClass('vjs-fullscreen'); 23443 } else { 23444 this.removeClass('vjs-fullscreen'); 23445 } 23446 }; 23447 23448 /** 23449 * native click events on the SWF aren't triggered on IE11, Win8.1RT 23450 * use stageclick events triggered from inside the SWF instead 23451 * 23452 * @private 23453 * @listens stageclick 23454 */ 23455 23456 23457 Player.prototype.handleStageClick_ = function handleStageClick_() { 23458 this.reportUserActivity(); 23459 }; 23460 23461 /** 23462 * Handle Tech Fullscreen Change 23463 * 23464 * @param {EventTarget~Event} event 23465 * the fullscreenchange event that triggered this function 23466 * 23467 * @param {Object} data 23468 * the data that was sent with the event 23469 * 23470 * @private 23471 * @listens Tech#fullscreenchange 23472 * @fires Player#fullscreenchange 23473 */ 23474 23475 23476 Player.prototype.handleTechFullscreenChange_ = function handleTechFullscreenChange_(event, data) { 23477 if (data) { 23478 this.isFullscreen(data.isFullscreen); 23479 } 23480 /** 23481 * Fired when going in and out of fullscreen. 23482 * 23483 * @event Player#fullscreenchange 23484 * @type {EventTarget~Event} 23485 */ 23486 this.trigger('fullscreenchange'); 23487 }; 23488 23489 /** 23490 * Fires when an error occurred during the loading of an audio/video. 23491 * 23492 * @private 23493 * @listens Tech#error 23494 */ 23495 23496 23497 Player.prototype.handleTechError_ = function handleTechError_() { 23498 var error = this.tech_.error(); 23499 23500 this.error(error); 23501 }; 23502 23503 /** 23504 * Retrigger the `textdata` event that was triggered by the {@link Tech}. 23505 * 23506 * @fires Player#textdata 23507 * @listens Tech#textdata 23508 * @private 23509 */ 23510 23511 23512 Player.prototype.handleTechTextData_ = function handleTechTextData_() { 23513 var data = null; 23514 23515 if (arguments.length > 1) { 23516 data = arguments[1]; 23517 } 23518 23519 /** 23520 * Fires when we get a textdata event from tech 23521 * 23522 * @event Player#textdata 23523 * @type {EventTarget~Event} 23524 */ 23525 this.trigger('textdata', data); 23526 }; 23527 23528 /** 23529 * Get object for cached values. 23530 * 23531 * @return {Object} 23532 * get the current object cache 23533 */ 23534 23535 23536 Player.prototype.getCache = function getCache() { 23537 return this.cache_; 23538 }; 23539 23540 /** 23541 * Pass values to the playback tech 23542 * 23543 * @param {string} [method] 23544 * the method to call 23545 * 23546 * @param {Object} arg 23547 * the argument to pass 23548 * 23549 * @private 23550 */ 23551 23552 23553 Player.prototype.techCall_ = function techCall_(method, arg) { 23554 // If it's not ready yet, call method when it is 23555 23556 this.ready(function () { 23557 if (method in allowedSetters) { 23558 return set$1(this.middleware_, this.tech_, method, arg); 23559 } else if (method in allowedMediators) { 23560 return mediate(this.middleware_, this.tech_, method, arg); 23561 } 23562 23563 try { 23564 if (this.tech_) { 23565 this.tech_[method](arg); 23566 } 23567 } catch (e) { 23568 log$1(e); 23569 throw e; 23570 } 23571 }, true); 23572 }; 23573 23574 /** 23575 * Get calls can't wait for the tech, and sometimes don't need to. 23576 * 23577 * @param {string} method 23578 * Tech method 23579 * 23580 * @return {Function|undefined} 23581 * the method or undefined 23582 * 23583 * @private 23584 */ 23585 23586 23587 Player.prototype.techGet_ = function techGet_(method) { 23588 if (!this.tech_ || !this.tech_.isReady_) { 23589 return; 23590 } 23591 23592 if (method in allowedGetters) { 23593 return get$1(this.middleware_, this.tech_, method); 23594 } else if (method in allowedMediators) { 23595 return mediate(this.middleware_, this.tech_, method); 23596 } 23597 23598 // Flash likes to die and reload when you hide or reposition it. 23599 // In these cases the object methods go away and we get errors. 23600 // When that happens we'll catch the errors and inform tech that it's not ready any more. 23601 try { 23602 return this.tech_[method](); 23603 } catch (e) { 23604 23605 // When building additional tech libs, an expected method may not be defined yet 23606 if (this.tech_[method] === undefined) { 23607 log$1('Video.js: ' + method + ' method not defined for ' + this.techName_ + ' playback technology.', e); 23608 throw e; 23609 } 23610 23611 // When a method isn't available on the object it throws a TypeError 23612 if (e.name === 'TypeError') { 23613 log$1('Video.js: ' + method + ' unavailable on ' + this.techName_ + ' playback technology element.', e); 23614 this.tech_.isReady_ = false; 23615 throw e; 23616 } 23617 23618 // If error unknown, just log and throw 23619 log$1(e); 23620 throw e; 23621 } 23622 }; 23623 23624 /** 23625 * Attempt to begin playback at the first opportunity. 23626 * 23627 * @return {Promise|undefined} 23628 * Returns a `Promise` only if the browser returns one and the player 23629 * is ready to begin playback. For some browsers and all non-ready 23630 * situations, this will return `undefined`. 23631 */ 23632 23633 23634 Player.prototype.play = function play() { 23635 var _this7 = this; 23636 23637 // If this is called while we have a play queued up on a loadstart, remove 23638 // that listener to avoid getting in a potentially bad state. 23639 if (this.playOnLoadstart_) { 23640 this.off('loadstart', this.playOnLoadstart_); 23641 } 23642 23643 // If the player/tech is not ready, queue up another call to `play()` for 23644 // when it is. This will loop back into this method for another attempt at 23645 // playback when the tech is ready. 23646 if (!this.isReady_) { 23647 23648 // Bail out if we're already waiting for `ready`! 23649 if (this.playWaitingForReady_) { 23650 return; 23651 } 23652 23653 this.playWaitingForReady_ = true; 23654 this.ready(function () { 23655 _this7.playWaitingForReady_ = false; 23656 silencePromise(_this7.play()); 23657 }); 23658 23659 // If the player/tech is ready and we have a source, we can attempt playback. 23660 } else if (!this.changingSrc_ && (this.src() || this.currentSrc())) { 23661 return this.techGet_('play'); 23662 23663 // If the tech is ready, but we do not have a source, we'll need to wait 23664 // for both the `ready` and a `loadstart` when the source is finally 23665 // resolved by middleware and set on the player. 23666 // 23667 // This can happen if `play()` is called while changing sources or before 23668 // one has been set on the player. 23669 } else { 23670 23671 this.playOnLoadstart_ = function () { 23672 _this7.playOnLoadstart_ = null; 23673 silencePromise(_this7.play()); 23674 }; 23675 23676 this.one('loadstart', this.playOnLoadstart_); 23677 } 23678 }; 23679 23680 /** 23681 * Pause the video playback 23682 * 23683 * @return {Player} 23684 * A reference to the player object this function was called on 23685 */ 23686 23687 23688 Player.prototype.pause = function pause() { 23689 this.techCall_('pause'); 23690 }; 23691 23692 /** 23693 * Check if the player is paused or has yet to play 23694 * 23695 * @return {boolean} 23696 * - false: if the media is currently playing 23697 * - true: if media is not currently playing 23698 */ 23699 23700 23701 Player.prototype.paused = function paused() { 23702 // The initial state of paused should be true (in Safari it's actually false) 23703 return this.techGet_('paused') === false ? false : true; 23704 }; 23705 23706 /** 23707 * Get a TimeRange object representing the current ranges of time that the user 23708 * has played. 23709 * 23710 * @return {TimeRange} 23711 * A time range object that represents all the increments of time that have 23712 * been played. 23713 */ 23714 23715 23716 Player.prototype.played = function played() { 23717 return this.techGet_('played') || createTimeRanges(0, 0); 23718 }; 23719 23720 /** 23721 * Returns whether or not the user is "scrubbing". Scrubbing is 23722 * when the user has clicked the progress bar handle and is 23723 * dragging it along the progress bar. 23724 * 23725 * @param {boolean} [isScrubbing] 23726 * wether the user is or is not scrubbing 23727 * 23728 * @return {boolean} 23729 * The value of scrubbing when getting 23730 */ 23731 23732 23733 Player.prototype.scrubbing = function scrubbing(isScrubbing) { 23734 if (typeof isScrubbing === 'undefined') { 23735 return this.scrubbing_; 23736 } 23737 this.scrubbing_ = !!isScrubbing; 23738 23739 if (isScrubbing) { 23740 this.addClass('vjs-scrubbing'); 23741 } else { 23742 this.removeClass('vjs-scrubbing'); 23743 } 23744 }; 23745 23746 /** 23747 * Get or set the current time (in seconds) 23748 * 23749 * @param {number|string} [seconds] 23750 * The time to seek to in seconds 23751 * 23752 * @return {number} 23753 * - the current time in seconds when getting 23754 */ 23755 23756 23757 Player.prototype.currentTime = function currentTime(seconds) { 23758 if (typeof seconds !== 'undefined') { 23759 if (seconds < 0) { 23760 seconds = 0; 23761 } 23762 this.techCall_('setCurrentTime', seconds); 23763 return; 23764 } 23765 23766 // cache last currentTime and return. default to 0 seconds 23767 // 23768 // Caching the currentTime is meant to prevent a massive amount of reads on the tech's 23769 // currentTime when scrubbing, but may not provide much performance benefit afterall. 23770 // Should be tested. Also something has to read the actual current time or the cache will 23771 // never get updated. 23772 this.cache_.currentTime = this.techGet_('currentTime') || 0; 23773 return this.cache_.currentTime; 23774 }; 23775 23776 /** 23777 * Normally gets the length in time of the video in seconds; 23778 * in all but the rarest use cases an argument will NOT be passed to the method 23779 * 23780 * > **NOTE**: The video must have started loading before the duration can be 23781 * known, and in the case of Flash, may not be known until the video starts 23782 * playing. 23783 * 23784 * @fires Player#durationchange 23785 * 23786 * @param {number} [seconds] 23787 * The duration of the video to set in seconds 23788 * 23789 * @return {number} 23790 * - The duration of the video in seconds when getting 23791 */ 23792 23793 23794 Player.prototype.duration = function duration(seconds) { 23795 if (seconds === undefined) { 23796 // return NaN if the duration is not known 23797 return this.cache_.duration !== undefined ? this.cache_.duration : NaN; 23798 } 23799 23800 seconds = parseFloat(seconds); 23801 23802 // Standardize on Inifity for signaling video is live 23803 if (seconds < 0) { 23804 seconds = Infinity; 23805 } 23806 23807 if (seconds !== this.cache_.duration) { 23808 // Cache the last set value for optimized scrubbing (esp. Flash) 23809 this.cache_.duration = seconds; 23810 23811 if (seconds === Infinity) { 23812 this.addClass('vjs-live'); 23813 } else { 23814 this.removeClass('vjs-live'); 23815 } 23816 /** 23817 * @event Player#durationchange 23818 * @type {EventTarget~Event} 23819 */ 23820 this.trigger('durationchange'); 23821 } 23822 }; 23823 23824 /** 23825 * Calculates how much time is left in the video. Not part 23826 * of the native video API. 23827 * 23828 * @return {number} 23829 * The time remaining in seconds 23830 */ 23831 23832 23833 Player.prototype.remainingTime = function remainingTime() { 23834 return this.duration() - this.currentTime(); 23835 }; 23836 23837 /** 23838 * A remaining time function that is intented to be used when 23839 * the time is to be displayed directly to the user. 23840 * 23841 * @return {number} 23842 * The rounded time remaining in seconds 23843 */ 23844 23845 23846 Player.prototype.remainingTimeDisplay = function remainingTimeDisplay() { 23847 return Math.floor(this.duration()) - Math.floor(this.currentTime()); 23848 }; 23849 23850 // 23851 // Kind of like an array of portions of the video that have been downloaded. 23852 23853 /** 23854 * Get a TimeRange object with an array of the times of the video 23855 * that have been downloaded. If you just want the percent of the 23856 * video that's been downloaded, use bufferedPercent. 23857 * 23858 * @see [Buffered Spec]{@link http://dev.w3.org/html5/spec/video.html#dom-media-buffered} 23859 * 23860 * @return {TimeRange} 23861 * A mock TimeRange object (following HTML spec) 23862 */ 23863 23864 23865 Player.prototype.buffered = function buffered() { 23866 var buffered = this.techGet_('buffered'); 23867 23868 if (!buffered || !buffered.length) { 23869 buffered = createTimeRanges(0, 0); 23870 } 23871 23872 return buffered; 23873 }; 23874 23875 /** 23876 * Get the percent (as a decimal) of the video that's been downloaded. 23877 * This method is not a part of the native HTML video API. 23878 * 23879 * @return {number} 23880 * A decimal between 0 and 1 representing the percent 23881 * that is bufferred 0 being 0% and 1 being 100% 23882 */ 23883 23884 23885 Player.prototype.bufferedPercent = function bufferedPercent$$1() { 23886 return bufferedPercent(this.buffered(), this.duration()); 23887 }; 23888 23889 /** 23890 * Get the ending time of the last buffered time range 23891 * This is used in the progress bar to encapsulate all time ranges. 23892 * 23893 * @return {number} 23894 * The end of the last buffered time range 23895 */ 23896 23897 23898 Player.prototype.bufferedEnd = function bufferedEnd() { 23899 var buffered = this.buffered(); 23900 var duration = this.duration(); 23901 var end = buffered.end(buffered.length - 1); 23902 23903 if (end > duration) { 23904 end = duration; 23905 } 23906 23907 return end; 23908 }; 23909 23910 /** 23911 * Get or set the current volume of the media 23912 * 23913 * @param {number} [percentAsDecimal] 23914 * The new volume as a decimal percent: 23915 * - 0 is muted/0%/off 23916 * - 1.0 is 100%/full 23917 * - 0.5 is half volume or 50% 23918 * 23919 * @return {number} 23920 * The current volume as a percent when getting 23921 */ 23922 23923 23924 Player.prototype.volume = function volume(percentAsDecimal) { 23925 var vol = void 0; 23926 23927 if (percentAsDecimal !== undefined) { 23928 // Force value to between 0 and 1 23929 vol = Math.max(0, Math.min(1, parseFloat(percentAsDecimal))); 23930 this.cache_.volume = vol; 23931 this.techCall_('setVolume', vol); 23932 23933 if (vol > 0) { 23934 this.lastVolume_(vol); 23935 } 23936 23937 return; 23938 } 23939 23940 // Default to 1 when returning current volume. 23941 vol = parseFloat(this.techGet_('volume')); 23942 return isNaN(vol) ? 1 : vol; 23943 }; 23944 23945 /** 23946 * Get the current muted state, or turn mute on or off 23947 * 23948 * @param {boolean} [muted] 23949 * - true to mute 23950 * - false to unmute 23951 * 23952 * @return {boolean} 23953 * - true if mute is on and getting 23954 * - false if mute is off and getting 23955 */ 23956 23957 23958 Player.prototype.muted = function muted(_muted) { 23959 if (_muted !== undefined) { 23960 this.techCall_('setMuted', _muted); 23961 return; 23962 } 23963 return this.techGet_('muted') || false; 23964 }; 23965 23966 /** 23967 * Get the current defaultMuted state, or turn defaultMuted on or off. defaultMuted 23968 * indicates the state of muted on intial playback. 23969 * 23970 * ```js
23971 * var myPlayer = videojs('some-player-id'); 23972 * 23973 * myPlayer.src("http://www.example.com/path/to/video.mp4"); 23974 * 23975 * // get, should be false 23976 * console.log(myPlayer.defaultMuted()); 23977 * // set to true 23978 * myPlayer.defaultMuted(true); 23979 * // get should be true 23980 * console.log(myPlayer.defaultMuted()); 23981 * ``` 23982 * 23983 * @param {boolean} [defaultMuted] 23984 * - true to mute 23985 * - false to unmute 23986 * 23987 * @return {boolean|Player} 23988 * - true if defaultMuted is on and getting 23989 * - false if defaultMuted is off and getting 23990 * - A reference to the current player when setting 23991 */ 23992 23993 23994 Player.prototype.defaultMuted = function defaultMuted(_defaultMuted) { 23995 if (_defaultMuted !== undefined) { 23996 return this.techCall_('setDefaultMuted', _defaultMuted); 23997 } 23998 return this.techGet_('defaultMuted') || false; 23999 }; 24000 24001 /** 24002 * Get the last volume, or set it 24003 * 24004 * @param {number} [percentAsDecimal] 24005 * The new last volume as a decimal percent: 24006 * - 0 is muted/0%/off 24007 * - 1.0 is 100%/full 24008 * - 0.5 is half volume or 50% 24009 * 24010 * @return {number} 24011 * the current value of lastVolume as a percent when getting 24012 * 24013 * @private 24014 */ 24015 24016 24017 Player.prototype.lastVolume_ = function lastVolume_(percentAsDecimal) { 24018 if (percentAsDecimal !== undefined && percentAsDecimal !== 0) { 24019 this.cache_.lastVolume = percentAsDecimal; 24020 return; 24021 } 24022 return this.cache_.lastVolume; 24023 }; 24024 24025 /** 24026 * Check if current tech can support native fullscreen 24027 * (e.g. with built in controls like iOS, so not our flash swf) 24028 * 24029 * @return {boolean} 24030 * if native fullscreen is supported 24031 */ 24032 24033 24034 Player.prototype.supportsFullScreen = function supportsFullScreen() { 24035 return this.techGet_('supportsFullScreen') || false; 24036 }; 24037 24038 /** 24039 * Check if the player is in fullscreen mode or tell the player that it 24040 * is or is not in fullscreen mode. 24041 * 24042 * > NOTE: As of the latest HTML5 spec, isFullscreen is no longer an official 24043 * property and instead document.fullscreenElement is used. But isFullscreen is 24044 * still a valuable property for internal player workings. 24045 * 24046 * @param {boolean} [isFS] 24047 * Set the players current fullscreen state 24048 * 24049 * @return {boolean} 24050 * - true if fullscreen is on and getting 24051 * - false if fullscreen is off and getting 24052 */ 24053 24054 24055 Player.prototype.isFullscreen = function isFullscreen(isFS) { 24056 if (isFS !== undefined) { 24057 this.isFullscreen_ = !!isFS; 24058 return; 24059 } 24060 return !!this.isFullscreen_; 24061 }; 24062 24063 /** 24064 * Increase the size of the video to full screen 24065 * In some browsers, full screen is not supported natively, so it enters 24066 * "full window mode", where the video fills the browser window. 24067 * In browsers and devices that support native full screen, sometimes the 24068 * browser's default controls will be shown, and not the Video.js custom skin. 24069 * This includes most mobile devices (iOS, Android) and older versions of 24070 * Safari. 24071 * 24072 * @fires Player#fullscreenchange 24073 */ 24074 24075 24076 Player.prototype.requestFullscreen = function requestFullscreen() { 24077 var fsApi = FullscreenApi; 24078 24079 this.isFullscreen(true); 24080 24081 if (fsApi.requestFullscreen) { 24082 // the browser supports going fullscreen at the element level so we can 24083 // take the controls fullscreen as well as the video 24084 24085 // Trigger fullscreenchange event after change 24086 // We have to specifically add this each time, and remove 24087 // when canceling fullscreen. Otherwise if there's multiple 24088 // players on a page, they would all be reacting to the same fullscreen 24089 // events 24090 on(document_1, fsApi.fullscreenchange, bind(this, function documentFullscreenChange(e) { 24091 this.isFullscreen(document_1[fsApi.fullscreenElement]); 24092 24093 // If cancelling fullscreen, remove event listener. 24094 if (this.isFullscreen() === false) { 24095 off(document_1, fsApi.fullscreenchange, documentFullscreenChange); 24096 } 24097 /** 24098 * @event Player#fullscreenchange 24099 * @type {EventTarget~Event} 24100 */ 24101 this.trigger('fullscreenchange'); 24102 })); 24103 24104 this.el_[fsApi.requestFullscreen](); 24105 } else if (this.tech_.supportsFullScreen()) { 24106 // we can't take the video.js controls fullscreen but we can go fullscreen 24107 // with native controls 24108 this.techCall_('enterFullScreen'); 24109 } else { 24110 // fullscreen isn't supported so we'll just stretch the video element to 24111 // fill the viewport 24112 this.enterFullWindow(); 24113 /** 24114 * @event Player#fullscreenchange 24115 * @type {EventTarget~Event} 24116 */ 24117 this.trigger('fullscreenchange'); 24118 } 24119 }; 24120 24121 /** 24122 * Return the video to its normal size after having been in full screen mode 24123 * 24124 * @fires Player#fullscreenchange 24125 */ 24126 24127 24128 Player.prototype.exitFullscreen = function exitFullscreen() { 24129 var fsApi = FullscreenApi; 24130 24131 this.isFullscreen(false); 24132 24133 // Check for browser element fullscreen support 24134 if (fsApi.requestFullscreen) { 24135 document_1[fsApi.exitFullscreen](); 24136 } else if (this.tech_.supportsFullScreen()) { 24137 this.techCall_('exitFullScreen'); 24138 } else { 24139 this.exitFullWindow(); 24140 /** 24141 * @event Player#fullscreenchange 24142 * @type {EventTarget~Event} 24143 */ 24144 this.trigger('fullscreenchange'); 24145 } 24146 }; 24147 24148 /** 24149 * When fullscreen isn't supported we can stretch the 24150 * video container to as wide as the browser will let us. 24151 * 24152 * @fires Player#enterFullWindow 24153 */ 24154 24155 24156 Player.prototype.enterFullWindow = function enterFullWindow() { 24157 this.isFullWindow = true; 24158 24159 // Storing original doc overflow value to return to when fullscreen is off 24160 this.docOrigOverflow = document_1.documentElement.style.overflow; 24161 24162 // Add listener for esc key to exit fullscreen 24163 on(document_1, 'keydown', bind(this, this.fullWindowOnEscKey)); 24164 24165 // Hide any scroll bars 24166 document_1.documentElement.style.overflow = 'hidden'; 24167 24168 // Apply fullscreen styles 24169 addClass(document_1.body, 'vjs-full-window'); 24170 24171 /** 24172 * @event Player#enterFullWindow 24173 * @type {EventTarget~Event} 24174 */ 24175 this.trigger('enterFullWindow'); 24176 }; 24177 24178 /** 24179 * Check for call to either exit full window or 24180 * full screen on ESC key 24181 * 24182 * @param {string} event 24183 * Event to check for key press 24184 */ 24185 24186 24187 Player.prototype.fullWindowOnEscKey = function fullWindowOnEscKey(event) { 24188 if (event.keyCode === 27) { 24189 if (this.isFullscreen() === true) { 24190 this.exitFullscreen(); 24191 } else { 24192 this.exitFullWindow(); 24193 } 24194 } 24195 }; 24196 24197 /** 24198 * Exit full window 24199 * 24200 * @fires Player#exitFullWindow 24201 */ 24202 24203 24204 Player.prototype.exitFullWindow = function exitFullWindow() { 24205 this.isFullWindow = false;
vendor: 10,398 bytes, lines 24206-24569
24206 off(document_1, 'keydown', this.fullWindowOnEscKey); 24207 24208 // Unhide scroll bars. 24209 document_1.documentElement.style.overflow = this.docOrigOverflow; 24210 24211 // Remove fullscreen styles 24212 removeClass(document_1.body, 'vjs-full-window'); 24213 24214 // Resize the box, controller, and poster to original sizes 24215 // this.positionAll(); 24216 /** 24217 * @event Player#exitFullWindow 24218 * @type {EventTarget~Event} 24219 */ 24220 this.trigger('exitFullWindow'); 24221 }; 24222 24223 /** 24224 * Check whether the player can play a given mimetype 24225 * 24226 * @see https://www.w3.org/TR/2011/WD-html5-20110113/video.html#dom-navigator-canplaytype 24227 * 24228 * @param {string} type 24229 * The mimetype to check 24230 * 24231 * @return {string} 24232 * 'probably', 'maybe', or '' (empty string) 24233 */ 24234 24235 24236 Player.prototype.canPlayType = function canPlayType(type) { 24237 var can = void 0; 24238 24239 // Loop through each playback technology in the options order 24240 for (var i = 0, j = this.options_.techOrder; i < j.length; i++) { 24241 var techName = j[i]; 24242 var tech = Tech.getTech(techName); 24243 24244 // Support old behavior of techs being registered as components. 24245 // Remove once that deprecated behavior is removed. 24246 if (!tech) { 24247 tech = Component.getComponent(techName); 24248 } 24249 24250 // Check if the current tech is defined before continuing 24251 if (!tech) { 24252 log$1.error('The "' + techName + '" tech is undefined. Skipped browser support check for that tech.'); 24253 continue; 24254 } 24255 24256 // Check if the browser supports this technology 24257 if (tech.isSupported()) { 24258 can = tech.canPlayType(type); 24259 24260 if (can) { 24261 return can; 24262 } 24263 } 24264 } 24265 24266 return ''; 24267 }; 24268 24269 /** 24270 * Select source based on tech-order or source-order 24271 * Uses source-order selection if `options.sourceOrder` is truthy. Otherwise, 24272 * defaults to tech-order selection 24273 * 24274 * @param {Array} sources 24275 * The sources for a media asset 24276 * 24277 * @return {Object|boolean} 24278 * Object of source and tech order or false 24279 */ 24280 24281 24282 Player.prototype.selectSource = function selectSource(sources) { 24283 var _this8 = this; 24284 24285 // Get only the techs specified in `techOrder` that exist and are supported by the 24286 // current platform 24287 var techs = this.options_.techOrder.map(function (techName) { 24288 return [techName, Tech.getTech(techName)]; 24289 }).filter(function (_ref) { 24290 var techName = _ref[0], 24291 tech = _ref[1]; 24292 24293 // Check if the current tech is defined before continuing 24294 if (tech) { 24295 // Check if the browser supports this technology 24296 return tech.isSupported(); 24297 } 24298 24299 log$1.error('The "' + techName + '" tech is undefined. Skipped browser support check for that tech.'); 24300 return false; 24301 }); 24302 24303 // Iterate over each `innerArray` element once per `outerArray` element and execute 24304 // `tester` with both. If `tester` returns a non-falsy value, exit early and return 24305 // that value. 24306 var findFirstPassingTechSourcePair = function findFirstPassingTechSourcePair(outerArray, innerArray, tester) { 24307 var found = void 0; 24308 24309 outerArray.some(function (outerChoice) { 24310 return innerArray.some(function (innerChoice) { 24311 found = tester(outerChoice, innerChoice); 24312 24313 if (found) { 24314 return true; 24315 } 24316 }); 24317 }); 24318 24319 return found; 24320 }; 24321 24322 var foundSourceAndTech = void 0; 24323 var flip = function flip(fn) { 24324 return function (a, b) { 24325 return fn(b, a); 24326 }; 24327 }; 24328 var finder = function finder(_ref2, source) { 24329 var techName = _ref2[0], 24330 tech = _ref2[1]; 24331 24332 if (tech.canPlaySource(source, _this8.options_[techName.toLowerCase()])) { 24333 return { source: source, tech: techName }; 24334 } 24335 }; 24336 24337 // Depending on the truthiness of `options.sourceOrder`, we swap the order of techs and sources 24338 // to select from them based on their priority. 24339 if (this.options_.sourceOrder) { 24340 // Source-first ordering 24341 foundSourceAndTech = findFirstPassingTechSourcePair(sources, techs, flip(finder)); 24342 } else { 24343 // Tech-first ordering 24344 foundSourceAndTech = findFirstPassingTechSourcePair(techs, sources, finder); 24345 } 24346 24347 return foundSourceAndTech || false; 24348 }; 24349 24350 /** 24351 * Get or set the video source. 24352 * 24353 * @param {Tech~SourceObject|Tech~SourceObject[]|string} [source] 24354 * A SourceObject, an array of SourceObjects, or a string referencing 24355 * a URL to a media source. It is _highly recommended_ that an object 24356 * or array of objects is used here, so that source selection 24357 * algorithms can take the `type` into account. 24358 * 24359 * If not provided, this method acts as a getter. 24360 * 24361 * @return {string|undefined} 24362 * If the `source` argument is missing, returns the current source 24363 * URL. Otherwise, returns nothing/undefined. 24364 */ 24365 24366 24367 Player.prototype.src = function src(source) { 24368 var _this9 = this; 24369 24370 // getter usage 24371 if (typeof source === 'undefined') { 24372 return this.cache_.src || ''; 24373 } 24374 // filter out invalid sources and turn our source into 24375 // an array of source objects 24376 var sources = filterSource(source); 24377 24378 // if a source was passed in then it is invalid because 24379 // it was filtered to a zero length Array. So we have to 24380 // show an error 24381 if (!sources.length) { 24382 this.setTimeout(function () { 24383 this.error({ code: 4, message: this.localize(this.options_.notSupportedMessage) }); 24384 }, 0); 24385 return; 24386 } 24387 24388 // intial sources 24389 this.changingSrc_ = true; 24390 24391 this.cache_.sources = sources; 24392 this.updateSourceCaches_(sources[0]); 24393 24394 // middlewareSource is the source after it has been changed by middleware 24395 setSource(this, sources[0], function (middlewareSource, mws) { 24396 _this9.middleware_ = mws; 24397 24398 // since sourceSet is async we have to update the cache again after we select a source since 24399 // the source that is selected could be out of order from the cache update above this callback. 24400 _this9.cache_.sources = sources; 24401 _this9.updateSourceCaches_(middlewareSource); 24402 24403 var err = _this9.src_(middlewareSource); 24404 24405 if (err) { 24406 if (sources.length > 1) { 24407 return _this9.src(sources.slice(1)); 24408 } 24409 24410 _this9.changingSrc_ = false; 24411 24412 // We need to wrap this in a timeout to give folks a chance to add error event handlers 24413 _this9.setTimeout(function () { 24414 this.error({ code: 4, message: this.localize(this.options_.notSupportedMessage) }); 24415 }, 0); 24416 24417 // we could not find an appropriate tech, but let's still notify the delegate that this is it 24418 // this needs a better comment about why this is needed 24419 _this9.triggerReady(); 24420 24421 return; 24422 } 24423 24424 setTech(mws, _this9.tech_); 24425 }); 24426 }; 24427 24428 /** 24429 * Set the source object on the tech, returns a boolean that indicates wether 24430 * there is a tech that can play the source or not 24431 * 24432 * @param {Tech~SourceObject} source 24433 * The source object to set on the Tech 24434 * 24435 * @return {Boolean} 24436 * - True if there is no Tech to playback this source 24437 * - False otherwise 24438 * 24439 * @private 24440 */ 24441 24442 24443 Player.prototype.src_ = function src_(source) { 24444 var _this10 = this; 24445 24446 var sourceTech = this.selectSource([source]); 24447 24448 if (!sourceTech) { 24449 return true; 24450 } 24451 24452 if (!titleCaseEquals(sourceTech.tech, this.techName_)) { 24453 this.changingSrc_ = true; 24454 // load this technology with the chosen source 24455 this.loadTech_(sourceTech.tech, sourceTech.source); 24456 this.tech_.ready(function () { 24457 _this10.changingSrc_ = false; 24458 }); 24459 return false; 24460 } 24461 24462 // wait until the tech is ready to set the source 24463 // and set it synchronously if possible (#2326) 24464 this.ready(function () { 24465 24466 // The setSource tech method was added with source handlers 24467 // so older techs won't support it 24468 // We need to check the direct prototype for the case where subclasses 24469 // of the tech do not support source handlers 24470 if (this.tech_.constructor.prototype.hasOwnProperty('setSource')) { 24471 this.techCall_('setSource', source); 24472 } else { 24473 this.techCall_('src', source.src); 24474 } 24475 24476 this.changingSrc_ = false; 24477 }, true); 24478 24479 return false; 24480 }; 24481 24482 /** 24483 * Begin loading the src data. 24484 */ 24485 24486 24487 Player.prototype.load = function load() { 24488 this.techCall_('load'); 24489 }; 24490 24491 /** 24492 * Reset the player. Loads the first tech in the techOrder, 24493 * and calls `reset` on the tech`. 24494 */ 24495 24496 24497 Player.prototype.reset = function reset() { 24498 if (this.tech_) { 24499 this.tech_.clearTracks('text'); 24500 } 24501 this.loadTech_(this.options_.techOrder[0], null); 24502 this.techCall_('reset'); 24503 }; 24504 24505 /** 24506 * Returns all of the current source objects. 24507 * 24508 * @return {Tech~SourceObject[]} 24509 * The current source objects 24510 */ 24511 24512 24513 Player.prototype.currentSources = function currentSources() { 24514 var source = this.currentSource(); 24515 var sources = []; 24516 24517 // assume `{}` or `{ src }` 24518 if (Object.keys(source).length !== 0) { 24519 sources.push(source); 24520 } 24521 24522 return this.cache_.sources || sources; 24523 }; 24524 24525 /** 24526 * Returns the current source object. 24527 * 24528 * @return {Tech~SourceObject} 24529 * The current source object 24530 */ 24531 24532 24533 Player.prototype.currentSource = function currentSource() { 24534 return this.cache_.source || {}; 24535 }; 24536 24537 /** 24538 * Returns the fully qualified URL of the current source value e.g. http://mysite.com/video.mp4 24539 * Can be used in conjuction with `currentType` to assist in rebuilding the current source object. 24540 * 24541 * @return {string} 24542 * The current source 24543 */ 24544 24545 24546 Player.prototype.currentSrc = function currentSrc() { 24547 return this.currentSource() && this.currentSource().src || ''; 24548 }; 24549 24550 /** 24551 * Get the current source type e.g. video/mp4 24552 * This can allow you rebuild the current source object so that you could load the same 24553 * source and tech later 24554 * 24555 * @return {string} 24556 * The source MIME type 24557 */ 24558 24559 24560 Player.prototype.currentType = function currentType() { 24561 return this.currentSource() && this.currentSource().type || ''; 24562 }; 24563 24564 /** 24565 * Get or set the preload attribute 24566 * 24567 * @param {boolean} [value] 24568 * - true means that we should preload 24569 * - false mae
vendor: 9,205 bytes, lines 24569-24908
24569ns that we should not preload 24570 * 24571 * @return {string} 24572 * The preload attribute value when getting 24573 */ 24574 24575 24576 Player.prototype.preload = function preload(value) { 24577 if (value !== undefined) { 24578 this.techCall_('setPreload', value); 24579 this.options_.preload = value; 24580 return; 24581 } 24582 return this.techGet_('preload'); 24583 }; 24584 24585 /** 24586 * Get or set the autoplay option. When this is a boolean it will 24587 * modify the attribute on the tech. When this is a string the attribute on 24588 * the tech will be removed and `Player` will handle autoplay on loadstarts. 24589 * 24590 * @param {boolean|string} [value] 24591 * - true: autoplay using the browser behavior 24592 * - false: do not autoplay 24593 * - 'play': call play() on every loadstart 24594 * - 'muted': call muted() then play() on every loadstart 24595 * - 'any': call play() on every loadstart. if that fails call muted() then play(). 24596 * - *: values other than those listed here will be set `autoplay` to true 24597 * 24598 * @return {boolean|string} 24599 * The current value of autoplay when getting 24600 */ 24601 24602 24603 Player.prototype.autoplay = function autoplay(value) { 24604 // getter usage 24605 if (value === undefined) { 24606 return this.options_.autoplay || false; 24607 } 24608 24609 var techAutoplay = void 0; 24610 24611 // if the value is a valid string set it to that 24612 if (typeof value === 'string' && /(any|play|muted)/.test(value)) { 24613 this.options_.autoplay = value; 24614 this.manualAutoplay_(value); 24615 techAutoplay = false; 24616 24617 // any falsy value sets autoplay to false in the browser, 24618 // lets do the same 24619 } else if (!value) { 24620 this.options_.autoplay = false; 24621 24622 // any other value (ie truthy) sets autoplay to true 24623 } else { 24624 this.options_.autoplay = true; 24625 } 24626 24627 techAutoplay = techAutoplay || this.options_.autoplay; 24628 24629 // if we don't have a tech then we do not queue up 24630 // a setAutoplay call on tech ready. We do this because the 24631 // autoplay option will be passed in the constructor and we 24632 // do not need to set it twice 24633 if (this.tech_) { 24634 this.techCall_('setAutoplay', techAutoplay); 24635 } 24636 }; 24637 24638 /** 24639 * Set or unset the playsinline attribute. 24640 * Playsinline tells the browser that non-fullscreen playback is preferred. 24641 * 24642 * @param {boolean} [value] 24643 * - true means that we should try to play inline by default 24644 * - false means that we should use the browser's default playback mode, 24645 * which in most cases is inline. iOS Safari is a notable exception 24646 * and plays fullscreen by default. 24647 * 24648 * @return {string|Player} 24649 * - the current value of playsinline 24650 * - the player when setting 24651 * 24652 * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline} 24653 */ 24654 24655 24656 Player.prototype.playsinline = function playsinline(value) { 24657 if (value !== undefined) { 24658 this.techCall_('setPlaysinline', value); 24659 this.options_.playsinline = value; 24660 return this; 24661 } 24662 return this.techGet_('playsinline'); 24663 }; 24664 24665 /** 24666 * Get or set the loop attribute on the video element. 24667 * 24668 * @param {boolean} [value] 24669 * - true means that we should loop the video 24670 * - false means that we should not loop the video 24671 * 24672 * @return {string} 24673 * The current value of loop when getting 24674 */ 24675 24676 24677 Player.prototype.loop = function loop(value) { 24678 if (value !== undefined) { 24679 this.techCall_('setLoop', value); 24680 this.options_.loop = value; 24681 return; 24682 } 24683 return this.techGet_('loop'); 24684 }; 24685 24686 /** 24687 * Get or set the poster image source url 24688 * 24689 * @fires Player#posterchange 24690 * 24691 * @param {string} [src] 24692 * Poster image source URL 24693 * 24694 * @return {string} 24695 * The current value of poster when getting 24696 */ 24697 24698 24699 Player.prototype.poster = function poster(src) { 24700 if (src === undefined) { 24701 return this.poster_; 24702 } 24703 24704 // The correct way to remove a poster is to set as an empty string 24705 // other falsey values will throw errors 24706 if (!src) { 24707 src = ''; 24708 } 24709 24710 if (src === this.poster_) { 24711 return; 24712 } 24713 24714 // update the internal poster variable 24715 this.poster_ = src; 24716 24717 // update the tech's poster 24718 this.techCall_('setPoster', src); 24719 24720 this.isPosterFromTech_ = false; 24721 24722 // alert components that the poster has been set 24723 /** 24724 * This event fires when the poster image is changed on the player. 24725 * 24726 * @event Player#posterchange 24727 * @type {EventTarget~Event} 24728 */ 24729 this.trigger('posterchange'); 24730 }; 24731 24732 /** 24733 * Some techs (e.g. YouTube) can provide a poster source in an 24734 * asynchronous way. We want the poster component to use this 24735 * poster source so that it covers up the tech's controls. 24736 * (YouTube's play button). However we only want to use this 24737 * source if the player user hasn't set a poster through 24738 * the normal APIs. 24739 * 24740 * @fires Player#posterchange 24741 * @listens Tech#posterchange 24742 * @private 24743 */ 24744 24745 24746 Player.prototype.handleTechPosterChange_ = function handleTechPosterChange_() { 24747 if ((!this.poster_ || this.options_.techCanOverridePoster) && this.tech_ && this.tech_.poster) { 24748 var newPoster = this.tech_.poster() || ''; 24749 24750 if (newPoster !== this.poster_) { 24751 this.poster_ = newPoster; 24752 this.isPosterFromTech_ = true; 24753 24754 // Let components know the poster has changed 24755 this.trigger('posterchange'); 24756 } 24757 } 24758 }; 24759 24760 /** 24761 * Get or set whether or not the controls are showing. 24762 * 24763 * @fires Player#controlsenabled 24764 * 24765 * @param {boolean} [bool] 24766 * - true to turn controls on 24767 * - false to turn controls off 24768 * 24769 * @return {boolean} 24770 * The current value of controls when getting 24771 */ 24772 24773 24774 Player.prototype.controls = function controls(bool) { 24775 if (bool === undefined) { 24776 return !!this.controls_; 24777 } 24778 24779 bool = !!bool; 24780 24781 // Don't trigger a change event unless it actually changed 24782 if (this.controls_ === bool) { 24783 return; 24784 } 24785 24786 this.controls_ = bool; 24787 24788 if (this.usingNativeControls()) { 24789 this.techCall_('setControls', bool); 24790 } 24791 24792 if (this.controls_) { 24793 this.removeClass('vjs-controls-disabled'); 24794 this.addClass('vjs-controls-enabled'); 24795 /** 24796 * @event Player#controlsenabled 24797 * @type {EventTarget~Event} 24798 */ 24799 this.trigger('controlsenabled'); 24800 if (!this.usingNativeControls()) { 24801 this.addTechControlsListeners_(); 24802 } 24803 } else { 24804 this.removeClass('vjs-controls-enabled'); 24805 this.addClass('vjs-controls-disabled'); 24806 /** 24807 * @event Player#controlsdisabled 24808 * @type {EventTarget~Event} 24809 */ 24810 this.trigger('controlsdisabled'); 24811 if (!this.usingNativeControls()) { 24812 this.removeTechControlsListeners_(); 24813 } 24814 } 24815 }; 24816 24817 /** 24818 * Toggle native controls on/off. Native controls are the controls built into 24819 * devices (e.g. default iPhone controls), Flash, or other techs 24820 * (e.g. Vimeo Controls) 24821 * **This should only be set by the current tech, because only the tech knows 24822 * if it can support native controls** 24823 * 24824 * @fires Player#usingnativecontrols 24825 * @fires Player#usingcustomcontrols 24826 * 24827 * @param {boolean} [bool] 24828 * - true to turn native controls on 24829 * - false to turn native controls off 24830 * 24831 * @return {boolean} 24832 * The current value of native controls when getting 24833 */ 24834 24835 24836 Player.prototype.usingNativeControls = function usingNativeControls(bool) { 24837 if (bool === undefined) { 24838 return !!this.usingNativeControls_; 24839 } 24840 24841 bool = !!bool; 24842 24843 // Don't trigger a change event unless it actually changed 24844 if (this.usingNativeControls_ === bool) { 24845 return; 24846 } 24847 24848 this.usingNativeControls_ = bool; 24849 24850 if (this.usingNativeControls_) { 24851 this.addClass('vjs-using-native-controls'); 24852 24853 /** 24854 * player is using the native device controls 24855 * 24856 * @event Player#usingnativecontrols 24857 * @type {EventTarget~Event} 24858 */ 24859 this.trigger('usingnativecontrols'); 24860 } else { 24861 this.removeClass('vjs-using-native-controls'); 24862 24863 /** 24864 * player is using the custom HTML controls 24865 * 24866 * @event Player#usingcustomcontrols 24867 * @type {EventTarget~Event} 24868 */ 24869 this.trigger('usingcustomcontrols'); 24870 } 24871 }; 24872 24873 /** 24874 * Set or get the current MediaError 24875 * 24876 * @fires Player#error 24877 * 24878 * @param {MediaError|string|number} [err] 24879 * A MediaError or a string/number to be turned 24880 * into a MediaError 24881 * 24882 * @return {MediaError|null} 24883 * The current MediaError when getting (or null) 24884 */ 24885 24886 24887 Player.prototype.error = function error(err) { 24888 if (err === undefined) { 24889 return this.error_ || null; 24890 } 24891 24892 // restoring to default 24893 if (err === null) { 24894 this.error_ = err; 24895 this.removeClass('vjs-error'); 24896 if (this.errorDisplay) { 24897 this.errorDisplay.close(); 24898 } 24899 return; 24900 } 24901 24902 this.error_ = new MediaError(err); 24903 24904 // add the vjs-error classname to the player 24905 this.addClass('vjs-error'); 24906 24907 // log the name of the error type and any message 24908 //
24908ie8 just logs "[object object]" if you just log th
vendor: 6,559 bytes, lines 24908-25121
24908e error object 24909 log$1.error('(CODE:' + this.error_.code + ' ' + MediaError.errorTypes[this.error_.code] + ')', this.error_.message, this.error_); 24910 24911 /** 24912 * @event Player#error 24913 * @type {EventTarget~Event} 24914 */ 24915 this.trigger('error'); 24916 24917 return; 24918 }; 24919 24920 /** 24921 * Report user activity 24922 * 24923 * @param {Object} event 24924 * Event object 24925 */ 24926 24927 24928 Player.prototype.reportUserActivity = function reportUserActivity(event) { 24929 this.userActivity_ = true; 24930 }; 24931 24932 /** 24933 * Get/set if user is active 24934 * 24935 * @fires Player#useractive 24936 * @fires Player#userinactive 24937 * 24938 * @param {boolean} [bool] 24939 * - true if the user is active 24940 * - false if the user is inactive 24941 * 24942 * @return {boolean} 24943 * The current value of userActive when getting 24944 */ 24945 24946 24947 Player.prototype.userActive = function userActive(bool) { 24948 if (bool === undefined) { 24949 return this.userActive_; 24950 } 24951 24952 bool = !!bool; 24953 24954 if (bool === this.userActive_) { 24955 return; 24956 } 24957 24958 this.userActive_ = bool; 24959 24960 if (this.userActive_) { 24961 this.userActivity_ = true; 24962 this.removeClass('vjs-user-inactive'); 24963 this.addClass('vjs-user-active'); 24964 /** 24965 * @event Player#useractive 24966 * @type {EventTarget~Event} 24967 */ 24968 this.trigger('useractive'); 24969 return; 24970 } 24971 24972 // Chrome/Safari/IE have bugs where when you change the cursor it can 24973 // trigger a mousemove event. This causes an issue when you're hiding 24974 // the cursor when the user is inactive, and a mousemove signals user 24975 // activity. Making it impossible to go into inactive mode. Specifically 24976 // this happens in fullscreen when we really need to hide the cursor. 24977 // 24978 // When this gets resolved in ALL browsers it can be removed 24979 // https://code.google.com/p/chromium/issues/detail?id=103041 24980 if (this.tech_) { 24981 this.tech_.one('mousemove', function (e) { 24982 e.stopPropagation(); 24983 e.preventDefault(); 24984 }); 24985 } 24986 24987 this.userActivity_ = false; 24988 this.removeClass('vjs-user-active'); 24989 this.addClass('vjs-user-inactive'); 24990 /** 24991 * @event Player#userinactive 24992 * @type {EventTarget~Event} 24993 */ 24994 this.trigger('userinactive'); 24995 }; 24996 24997 /** 24998 * Listen for user activity based on timeout value 24999 * 25000 * @private 25001 */ 25002 25003 25004 Player.prototype.listenForUserActivity_ = function listenForUserActivity_() { 25005 var mouseInProgress = void 0; 25006 var lastMoveX = void 0; 25007 var lastMoveY = void 0; 25008 var handleActivity = bind(this, this.reportUserActivity); 25009 25010 var handleMouseMove = function handleMouseMove(e) { 25011 // #1068 - Prevent mousemove spamming 25012 // Chrome Bug: https://code.google.com/p/chromium/issues/detail?id=366970 25013 if (e.screenX !== lastMoveX || e.screenY !== lastMoveY) { 25014 lastMoveX = e.screenX; 25015 lastMoveY = e.screenY; 25016 handleActivity(); 25017 } 25018 }; 25019 25020 var handleMouseDown = function handleMouseDown() { 25021 handleActivity(); 25022 // For as long as the they are touching the device or have their mouse down, 25023 // we consider them active even if they're not moving their finger or mouse. 25024 // So we want to continue to update that they are active 25025 this.clearInterval(mouseInProgress); 25026 // Setting userActivity=true now and setting the interval to the same time 25027 // as the activityCheck interval (250) should ensure we never miss the 25028 // next activityCheck 25029 mouseInProgress = this.setInterval(handleActivity, 250); 25030 }; 25031 25032 var handleMouseUp = function handleMouseUp(event) { 25033 handleActivity(); 25034 // Stop the interval that maintains activity if the mouse/touch is down 25035 this.clearInterval(mouseInProgress); 25036 }; 25037 25038 // Any mouse movement will be considered user activity 25039 this.on('mousedown', handleMouseDown); 25040 this.on('mousemove', handleMouseMove); 25041 this.on('mouseup', handleMouseUp); 25042 25043 // Listen for keyboard navigation 25044 // Shouldn't need to use inProgress interval because of key repeat 25045 this.on('keydown', handleActivity); 25046 this.on('keyup', handleActivity); 25047 25048 // Run an interval every 250 milliseconds instead of stuffing everything into 25049 // the mousemove/touchmove function itself, to prevent performance degradation. 25050 // `this.reportUserActivity` simply sets this.userActivity_ to true, which 25051 // then gets picked up by this loop 25052 // http://ejohn.org/blog/learning-from-twitter/ 25053 var inactivityTimeout = void 0; 25054 25055 this.setInterval(function () { 25056 // Check to see if mouse/touch activity has happened 25057 if (!this.userActivity_) { 25058 return; 25059 } 25060 25061 // Reset the activity tracker 25062 this.userActivity_ = false; 25063 25064 // If the user state was inactive, set the state to active 25065 this.userActive(true); 25066 25067 // Clear any existing inactivity timeout to start the timer over 25068 this.clearTimeout(inactivityTimeout); 25069 25070 var timeout = this.options_.inactivityTimeout; 25071 25072 if (timeout <= 0) { 25073 return; 25074 } 25075 25076 // In <timeout> milliseconds, if no more activity has occurred the 25077 // user will be considered inactive 25078 inactivityTimeout = this.setTimeout(function () { 25079 // Protect against the case where the inactivityTimeout can trigger just 25080 // before the next user activity is picked up by the activity check loop 25081 // causing a flicker 25082 if (!this.userActivity_) { 25083 this.userActive(false); 25084 } 25085 }, timeout); 25086 }, 250); 25087 }; 25088 25089 /** 25090 * Gets or sets the current playback rate. A playback rate of 25091 * 1.0 represents normal speed and 0.5 would indicate half-speed 25092 * playback, for instance. 25093 * 25094 * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-playbackrate 25095 * 25096 * @param {number} [rate] 25097 * New playback rate to set. 25098 * 25099 * @return {number} 25100 * The current playback rate when getting or 1.0 25101 */ 25102 25103 25104 Player.prototype.playbackRate = function playbackRate(rate) { 25105 if (rate !== undefined) { 25106 // NOTE: this.cache_.lastPlaybackRate is set from the tech handler 25107 // that is registered above 25108 this.techCall_('setPlaybackRate', rate); 25109 return; 25110 } 25111 25112 if (this.tech_ && this.tech_.featuresPlaybackRate) { 25113 return this.cache_.lastPlaybackRate || this.techGet_('playbackRate'); 25114 } 25115 return 1.0; 25116 }; 25117 25118 /** 25119 * Gets or sets the current default playback rate. A default playback rate of 25120 * 1.0 represents normal speed and 0.5 would indicate half-speed playback, for instance. 25121 * defaultPlaybackRate will only represent what the in
25121tial playbackRate of a video was, not 25122 * not the current playbackRate. 25123 * 25124 * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-defaultplaybackrate 25125 * 25126 * @param {number} [rate] 25127 * New default playback rate to set. 25128 * 25129 * @return {number|Player} 25130 * - The default playback rate when getting or 1.0 25131 * - the player when setting 25132 */ 25133 25134 25135 Player.prototype.defaultPlaybackRate = function defaultPlaybackRate(rate) { 25136 if (rate !== undefined) { 25137 return this.techCall_('setDefaultPlaybackRate', rate); 25138 } 25139 25140 if (this.tech_ && this.tech_.featuresPlaybackRate) { 25141 return this.techGet_('defaultPlaybackRate'); 25142 } 25143 return 1.0; 25144 }; 25145 25146 /** 25147 * Gets or sets the audio flag 25148 * 25149 * @param {boolean} bool 25150 * - true signals that this is an audio player 25151 * - false signals that this is not an audio player 25152 * 25153 * @return {boolean} 25154 * The current value of isAudio when getting 25155 */ 25156 25157 25158 Player.prototype.isAudio = function isAudio(bool) { 25159 if (bool !== undefined) { 25160 this.isAudio_ = !!bool; 25161 return; 25162 } 25163 25164 return !!this.isAudio_; 25165 }; 25166 25167 /** 25168 * A helper method for adding a {@link TextTrack} to our 25169 * {@link TextTrackList}. 25170 * 25171 * In addition to the W3C settings we allow adding additional info through options. 25172 * 25173 * @see http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-addtexttrack 25174 * 25175 * @param {string} [kind] 25176 * the kind of TextTrack you are adding 25177 * 25178 * @param {string} [label] 25179 * the label to give the TextTrack label 25180 * 25181 * @param {string} [language] 25182 * the language to set on the TextTrack 25183 * 25184 * @return {TextTrack|undefined} 25185 * the TextTrack that was added or undefined 25186 * if there is no tech 25187 */ 25188 25189 25190 Player.prototype.addTextTrack = function addTextTrack(kind, label, language) { 25191 if (this.tech_) { 25192 return this.tech_.addTextTrack(kind, label, language); 25193 } 25194 }; 25195 25196 /** 25197 * Create a remote {@link TextTrack} and an {@link HTMLTrackElement}. It will 25198 * automatically removed from the video element whenever the source changes, unless 25199 * manualCleanup is set to false. 25200 * 25201 * @param {Object} options 25202 * Options to pass to {@link HTMLTrackElement} during creation. See 25203 * {@link HTMLTrackElement} for object properties that you should use. 25204 * 25205 * @param {boolean} [manualCleanup=true] if set to false, the TextTrack will be 25206 * 25207 * @return {HtmlTrackElement} 25208 * the HTMLTrackElement that was created and added 25209 * to the HtmlTrackElementList and the remote 25210 * TextTrackList 25211 * 25212 * @deprecated The default value of the "manualCleanup" parameter will default
vendor: 4,868 bytes, lines 25213-25392
25213 * to "false" in upcoming versions of Video.js 25214 */ 25215 25216 25217 Player.prototype.addRemoteTextTrack = function addRemoteTextTrack(options, manualCleanup) { 25218 if (this.tech_) { 25219 return this.tech_.addRemoteTextTrack(options, manualCleanup); 25220 } 25221 }; 25222 25223 /** 25224 * Remove a remote {@link TextTrack} from the respective 25225 * {@link TextTrackList} and {@link HtmlTrackElementList}. 25226 * 25227 * @param {Object} track 25228 * Remote {@link TextTrack} to remove 25229 * 25230 * @return {undefined} 25231 * does not return anything 25232 */ 25233 25234 25235 Player.prototype.removeRemoteTextTrack = function removeRemoteTextTrack() { 25236 var _ref3 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, 25237 _ref3$track = _ref3.track, 25238 track = _ref3$track === undefined ? arguments[0] : _ref3$track; 25239 25240 // destructure the input into an object with a track argument, defaulting to arguments[0] 25241 // default the whole argument to an empty object if nothing was passed in 25242 25243 if (this.tech_) { 25244 return this.tech_.removeRemoteTextTrack(track); 25245 } 25246 }; 25247 25248 /** 25249 * Gets available media playback quality metrics as specified by the W3C's Media 25250 * Playback Quality API. 25251 * 25252 * @see [Spec]{@link https://wicg.github.io/media-playback-quality} 25253 * 25254 * @return {Object|undefined} 25255 * An object with supported media playback quality metrics or undefined if there 25256 * is no tech or the tech does not support it. 25257 */ 25258 25259 25260 Player.prototype.getVideoPlaybackQuality = function getVideoPlaybackQuality() { 25261 return this.techGet_('getVideoPlaybackQuality'); 25262 }; 25263 25264 /** 25265 * Get video width 25266 * 25267 * @return {number} 25268 * current video width 25269 */ 25270 25271 25272 Player.prototype.videoWidth = function videoWidth() { 25273 return this.tech_ && this.tech_.videoWidth && this.tech_.videoWidth() || 0; 25274 }; 25275 25276 /** 25277 * Get video height 25278 * 25279 * @return {number} 25280 * current video height 25281 */ 25282 25283 25284 Player.prototype.videoHeight = function videoHeight() { 25285 return this.tech_ && this.tech_.videoHeight && this.tech_.videoHeight() || 0; 25286 }; 25287 25288 /** 25289 * The player's language code 25290 * NOTE: The language should be set in the player options if you want the 25291 * the controls to be built with a specific language. Changing the lanugage 25292 * later will not update controls text. 25293 * 25294 * @param {string} [code] 25295 * the language code to set the player to 25296 * 25297 * @return {string} 25298 * The current language code when getting 25299 */ 25300 25301 25302 Player.prototype.language = function language(code) { 25303 if (code === undefined) { 25304 return this.language_; 25305 } 25306 25307 this.language_ = String(code).toLowerCase(); 25308 }; 25309 25310 /** 25311 * Get the player's language dictionary 25312 * Merge every time, because a newly added plugin might call videojs.addLanguage() at any time 25313 * Languages specified directly in the player options have precedence 25314 * 25315 * @return {Array} 25316 * An array of of supported languages 25317 */ 25318 25319 25320 Player.prototype.languages = function languages() { 25321 return mergeOptions(Player.prototype.options_.languages, this.languages_); 25322 }; 25323 25324 /** 25325 * returns a JavaScript object reperesenting the current track 25326 * information. **DOES not return it as JSON** 25327 * 25328 * @return {Object} 25329 * Object representing the current of track info 25330 */ 25331 25332 25333 Player.prototype.toJSON = function toJSON() { 25334 var options = mergeOptions(this.options_); 25335 var tracks = options.tracks; 25336 25337 options.tracks = []; 25338 25339 for (var i = 0; i < tracks.length; i++) { 25340 var track = tracks[i]; 25341 25342 // deep merge tracks and null out player so no circular references 25343 track = mergeOptions(track); 25344 track.player = undefined; 25345 options.tracks[i] = track; 25346 } 25347 25348 return options; 25349 }; 25350 25351 /** 25352 * Creates a simple modal dialog (an instance of the {@link ModalDialog} 25353 * component) that immediately overlays the player with arbitrary 25354 * content and removes itself when closed. 25355 * 25356 * @param {string|Function|Element|Array|null} content 25357 * Same as {@link ModalDialog#content}'s param of the same name. 25358 * The most straight-forward usage is to provide a string or DOM 25359 * element. 25360 * 25361 * @param {Object} [options] 25362 * Extra options which will be passed on to the {@link ModalDialog}. 25363 * 25364 * @return {ModalDialog} 25365 * the {@link ModalDialog} that was created 25366 */ 25367 25368 25369 Player.prototype.createModal = function createModal(content, options) { 25370 var _this11 = this; 25371 25372 options = options || {}; 25373 options.content = content || ''; 25374 25375 var modal = new ModalDialog(this, options); 25376 25377 this.addChild(modal); 25378 modal.on('dispose', function () { 25379 _this11.removeChild(modal); 25380 }); 25381 25382 modal.open(); 25383 return modal; 25384 }; 25385 25386 /** 25387 * Gets tag settings 25388 * 25389 * @param {Element} tag 25390 * The player tag 25391 * 25392 * @return {Object}
vendor: 4,271 bytes, lines 25393-25563
25393 * An object containing all of the settings 25394 * for a player tag 25395 */ 25396 25397 25398 Player.getTagSettings = function getTagSettings(tag) { 25399 var baseOptions = { 25400 sources: [], 25401 tracks: [] 25402 }; 25403 25404 var tagOptions = getAttributes(tag); 25405 var dataSetup = tagOptions['data-setup']; 25406 25407 if (hasClass(tag, 'vjs-fluid')) { 25408 tagOptions.fluid = true; 25409 } 25410 25411 // Check if data-setup attr exists. 25412 if (dataSetup !== null) { 25413 // Parse options JSON 25414 // If empty string, make it a parsable json object. 25415 var _safeParseTuple = tuple(dataSetup || '{}'), 25416 err = _safeParseTuple[0], 25417 data = _safeParseTuple[1]; 25418 25419 if (err) { 25420 log$1.error(err); 25421 } 25422 assign(tagOptions, data); 25423 } 25424 25425 assign(baseOptions, tagOptions); 25426 25427 // Get tag children settings 25428 if (tag.hasChildNodes()) { 25429 var children = tag.childNodes; 25430 25431 for (var i = 0, j = children.length; i < j; i++) { 25432 var child = children[i]; 25433 // Change case needed: http://ejohn.org/blog/nodename-case-sensitivity/ 25434 var childName = child.nodeName.toLowerCase(); 25435 25436 if (childName === 'source') { 25437 baseOptions.sources.push(getAttributes(child)); 25438 } else if (childName === 'track') { 25439 baseOptions.tracks.push(getAttributes(child)); 25440 } 25441 } 25442 } 25443 25444 return baseOptions; 25445 }; 25446 25447 /** 25448 * Determine wether or not flexbox is supported 25449 * 25450 * @return {boolean} 25451 * - true if flexbox is supported 25452 * - false if flexbox is not supported 25453 */ 25454 25455 25456 Player.prototype.flexNotSupported_ = function flexNotSupported_() { 25457 var elem = document_1.createElement('i'); 25458 25459 // Note: We don't actually use flexBasis (or flexOrder), but it's one of the more 25460 // common flex features that we can rely on when checking for flex support. 25461 return !('flexBasis' in elem.style || 'webkitFlexBasis' in elem.style || 'mozFlexBasis' in elem.style || 'msFlexBasis' in elem.style || 25462 // IE10-specific (2012 flex spec) 25463 'msFlexOrder' in elem.style); 25464 }; 25465 25466 return Player; 25467}(Component); 25468 25469/** 25470 * Get the {@link VideoTrackList} 25471 * @link https://html.spec.whatwg.org/multipage/embedded-content.html#videotracklist 25472 * 25473 * @return {VideoTrackList} 25474 * the current video track list 25475 * 25476 * @method Player.prototype.videoTracks 25477 */ 25478 25479/** 25480 * Get the {@link AudioTrackList} 25481 * @link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotracklist 25482 * 25483 * @return {AudioTrackList} 25484 * the current audio track list 25485 * 25486 * @method Player.prototype.audioTracks 25487 */ 25488 25489/** 25490 * Get the {@link TextTrackList} 25491 * 25492 * @link http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-texttracks 25493 * 25494 * @return {TextTrackList} 25495 * the current text track list 25496 * 25497 * @method Player.prototype.textTracks 25498 */ 25499 25500/** 25501 * Get the remote {@link TextTrackList} 25502 * 25503 * @return {TextTrackList} 25504 * The current remote text track list 25505 * 25506 * @method Player.prototype.remoteTextTracks 25507 */ 25508 25509/** 25510 * Get the remote {@link HtmlTrackElementList} tracks. 25511 * 25512 * @return {HtmlTrackElementList} 25513 * The current remote text track element list 25514 * 25515 * @method Player.prototype.remoteTextTrackEls 25516 */ 25517 25518ALL.names.forEach(function (name$$1) { 25519 var props = ALL[name$$1]; 25520 25521 Player.prototype[props.getterName] = function () { 25522 if (this.tech_) { 25523 return this.tech_[props.getterName](); 25524 } 25525 25526 // if we have not yet loadTech_, we create {video,audio,text}Tracks_ 25527 // these will be passed to the tech during loading 25528 this[props.privateName] = this[props.privateName] || new props.ListClass(); 25529 return this[props.privateName]; 25530 }; 25531}); 25532 25533/** 25534 * Global player list 25535 * 25536 * @type {Object} 25537 */ 25538Player.players = {}; 25539 25540var navigator$1 = window_1.navigator; 25541 25542/* 25543 * Player instance options, surfaced using options 25544 * options = Player.prototype.options_ 25545 * Make changes in options, not here. 25546 * 25547 * @type {Object} 25548 * @private 25549 */ 25550Player.prototype.options_ = { 25551 // Default order of fallback technology 25552 techOrder: Tech.defaultTechOrder_, 25553 25554 html5: {}, 25555 flash: {}, 25556 25557 // default inactivity timeout 25558 inactivityTimeout: 2000, 25559 25560 // default playback rates 25561 playbackRates: [], 25562 // Add playback rate selection by adding rates 25563 // 'playbackRates': [0.5, 1, 1.5, 2],
25564 25565 // Included control sets 25566 children: ['mediaLoader', 'posterImage', 'textTrackDisplay', 'loadingSpinner', 'bigPlayButton', 'controlBar', 'errorDisplay', 'textTrackSettings'], 25567 25568 language: navigator$1 && (navigator$1.languages && navigator$1.languages[0] || navigator$1.userLanguage || navigator$1.language) || 'en', 25569 25570 // locales and their language translations 25571 languages: {}, 25572 25573 // Default message to show when a video cannot be played. 25574 notSupportedMessage: 'No compatible source was found for this media.' 25575}; 25576 25577if (!IS_IE8) { 25578 Player.prototype.options_.children.push('resizeManager'); 25579} 25580 25581[ 25582/** 25583 * Returns whether or not the player is in the "ended" state. 25584 * 25585 * @return {Boolean} True if the player is in the ended state, false if not. 25586 * @method Player#ended 25587 */ 25588'ended', 25589/** 25590 * Returns whether or not the player is in the "seeking" state. 25591 * 25592 * @return {Boolean} True if the player is in the seeking state, false if not. 25593 * @method Player#seeking 25594 */ 25595'seeking', 25596/** 25597 * Returns the TimeRanges of the media that are currently available 25598 * for seeking to. 25599 * 25600 * @return {TimeRanges} the seekable intervals of the media timeline 25601 * @method Player#seekable 25602 */ 25603'seekable', 25604/** 25605 * Returns the current state of network activity for the element, from 25606 * the codes in the list below. 25607 * - NETWORK_EMPTY (numeric value 0) 25608 * The element has not yet been initialised. All attributes are in 25609 * their initial states. 25610 * - NETWORK_IDLE (numeric value 1) 25611 * The element's resource selection algorithm is active and has 25612 * selected a resource, but it is not actually using the network at 25613 * this time. 25614 * - NETWORK_LOADING (numeric value 2) 25615 * The user agent is actively trying to download data. 25616 * - NETWORK_NO_SOURCE (numeric value 3) 25617 * The element's resource selection algorithm is active, but it has 25618 * not yet found a resource to use. 25619 * 25620 * @see https://html.spec.whatwg.org/multipage/embedded-content.html#network-states 25621 * @return {number} the current network activity state 25622 * @method Player#networkState 25623 */ 25624'networkState', 25625/** 25626 * Returns a value that expresses the current state of the element 25627 * with respect to rendering the current playback position, from the 25628 * codes in the list below. 25629 * - HAVE_NOTHING (numeric value 0) 25630 * No information regarding the media resource is available. 25631 * - HAVE_METADATA (numeric value 1) 25632 * Enough of the resource has been obtained that the duration of the 25633 * resource is available. 25634 * - HAVE_CURRENT_DATA (numeric value 2) 25635 * Data for the immediate current playback position is available. 25636 * - HAVE_FUTURE_DATA (numeric value 3) 25637 * Data for the immediate current playback position is available, as 25638 * well as enough data for the user agent to advance the current 25639 * playback position in the direction of playback. 25640 * - HAVE_ENOUGH_DATA (numeric value 4) 25641 * The user agent estimates that enough data is available for 25642 * playback to proceed uninterrupted. 25643 * 25644 * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-readystate 25645 * @return {number} the current playback rendering state 25646 * @method Player#readyState 25647 */ 25648'readyState'].forEach(function (fn) { 25649 Player.prototype[fn] = function () { 25650 return this.techGet_(fn); 25651 }; 25652}); 25653 25654TECH_EVENTS_RETRIGGER.forEach(function (event) { 25655 Player.prototype['handleTech' + toTitleCase(event) + '_'] = function () { 25656 return this.trigger(event); 25657 }; 25658}); 25659 25660/** 25661 * Fired when the player has initial duration and dimension information 25662 * 25663 * @event Player#loadedmetadata 25664 * @type {EventTarget~Event} 25665 */ 25666 25667/** 25668 * Fired when the player has downloaded data at the current playback position 25669 * 25670 * @event Player#loadeddata 25671 * @type {EventTarget~Event} 25672 */ 25673 25674/** 25675 * Fired when the current playback position has changed * 25676 * During playback this is fired every 15-250 milliseconds, depending on the 25677 * playback technology in use. 25678 * 25679 * @event Player#timeupdate 25680 * @type {EventTarget~Event} 25681 */ 25682 25683/** 25684 * Fired when the volume changes 25685 * 25686 * @event Player#volumechange 25687 * @type {EventTarget~Event} 25688 */ 25689 25690/** 25691 * Reports whether or not a player has a plugin available. 25692 * 25693 * This does not report whether or not the plugin has ever been initialized 25694 * on this player. For that, [usingPlugin]{@link Player#usingPlugin}. 25695 * 25696 * @method Player#hasPlugin 25697 * @param {string} name 25698 * The name of a plugin. 25699 * 25700 * @return {boolean} 25701 * Whether or not this player has the requested plugin available. 25702 */ 25703 25704/** 25705 * Reports whether or not a player is using a plugin by name. 25706 * 25707 * For basic plugins, this only reports whether the plugin has _ever_ been 25708 * initialized on this player. 25709 * 25710 * @method Player#usingPlugin 25711 * @param {string} name 25712 * The name of a plugin. 25713 * 25714 * @return {boolean} 25715 * Whether or not this player is using the requested plugin. 25716 */ 25717 25718Component.registerComponent('Player', Player); 25719 25720/** 25721 * @file plugin.js 25722 */
vendor: 13,630 bytes, lines 25722-26230
25722 25723/** 25724 * The base plugin name. 25725 * 25726 * @private 25727 * @constant 25728 * @type {string} 25729 */ 25730var BASE_PLUGIN_NAME = 'plugin'; 25731 25732/** 25733 * The key on which a player's active plugins cache is stored. 25734 * 25735 * @private 25736 * @constant 25737 * @type {string} 25738 */ 25739var PLUGIN_CACHE_KEY = 'activePlugins_'; 25740 25741/** 25742 * Stores registered plugins in a private space. 25743 * 25744 * @private 25745 * @type {Object} 25746 */ 25747var pluginStorage = {}; 25748 25749/** 25750 * Reports whether or not a plugin has been registered. 25751 * 25752 * @private 25753 * @param {string} name 25754 * The name of a plugin. 25755 * 25756 * @returns {boolean} 25757 * Whether or not the plugin has been registered. 25758 */ 25759var pluginExists = function pluginExists(name) { 25760 return pluginStorage.hasOwnProperty(name); 25761}; 25762 25763/** 25764 * Get a single registered plugin by name. 25765 * 25766 * @private 25767 * @param {string} name 25768 * The name of a plugin. 25769 * 25770 * @returns {Function|undefined} 25771 * The plugin (or undefined). 25772 */ 25773var getPlugin = function getPlugin(name) { 25774 return pluginExists(name) ? pluginStorage[name] : undefined; 25775}; 25776 25777/** 25778 * Marks a plugin as "active" on a player. 25779 * 25780 * Also, ensures that the player has an object for tracking active plugins. 25781 * 25782 * @private 25783 * @param {Player} player 25784 * A Video.js player instance. 25785 * 25786 * @param {string} name 25787 * The name of a plugin. 25788 */ 25789var markPluginAsActive = function markPluginAsActive(player, name) { 25790 player[PLUGIN_CACHE_KEY] = player[PLUGIN_CACHE_KEY] || {}; 25791 player[PLUGIN_CACHE_KEY][name] = true; 25792}; 25793 25794/** 25795 * Triggers a pair of plugin setup events. 25796 * 25797 * @private 25798 * @param {Player} player 25799 * A Video.js player instance. 25800 * 25801 * @param {Plugin~PluginEventHash} hash 25802 * A plugin event hash. 25803 * 25804 * @param {Boolean} [before] 25805 * If true, prefixes the event name with "before". In other words, 25806 * use this to trigger "beforepluginsetup" instead of "pluginsetup". 25807 */ 25808var triggerSetupEvent = function triggerSetupEvent(player, hash, before) { 25809 var eventName = (before ? 'before' : '') + 'pluginsetup'; 25810 25811 player.trigger(eventName, hash); 25812 player.trigger(eventName + ':' + hash.name, hash); 25813}; 25814 25815/** 25816 * Takes a basic plugin function and returns a wrapper function which marks 25817 * on the player that the plugin has been activated. 25818 * 25819 * @private 25820 * @param {string} name 25821 * The name of the plugin. 25822 * 25823 * @param {Function} plugin 25824 * The basic plugin. 25825 * 25826 * @returns {Function} 25827 * A wrapper function for the given plugin. 25828 */ 25829var createBasicPlugin = function createBasicPlugin(name, plugin) { 25830 var basicPluginWrapper = function basicPluginWrapper() { 25831 25832 // We trigger the "beforepluginsetup" and "pluginsetup" events on the player 25833 // regardless, but we want the hash to be consistent with the hash provided 25834 // for advanced plugins. 25835 // 25836 // The only potentially counter-intuitive thing here is the `instance` in 25837 // the "pluginsetup" event is the value returned by the `plugin` function. 25838 triggerSetupEvent(this, { name: name, plugin: plugin, instance: null }, true); 25839 25840 var instance = plugin.apply(this, arguments); 25841 25842 markPluginAsActive(this, name); 25843 triggerSetupEvent(this, { name: name, plugin: plugin, instance: instance }); 25844 25845 return instance; 25846 }; 25847 25848 Object.keys(plugin).forEach(function (prop) { 25849 basicPluginWrapper[prop] = plugin[prop]; 25850 }); 25851 25852 return basicPluginWrapper; 25853}; 25854 25855/** 25856 * Takes a plugin sub-class and returns a factory function for generating 25857 * instances of it. 25858 * 25859 * This factory function will replace itself with an instance of the requested 25860 * sub-class of Plugin. 25861 * 25862 * @private 25863 * @param {string} name 25864 * The name of the plugin. 25865 * 25866 * @param {Plugin} PluginSubClass 25867 * The advanced plugin. 25868 * 25869 * @returns {Function} 25870 */ 25871var createPluginFactory = function createPluginFactory(name, PluginSubClass) { 25872 25873 // Add a `name` property to the plugin prototype so that each plugin can 25874 // refer to itself by name. 25875 PluginSubClass.prototype.name = name; 25876 25877 return function () { 25878 triggerSetupEvent(this, { name: name, plugin: PluginSubClass, instance: null }, true); 25879 25880 for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { 25881 args[_key] = arguments[_key]; 25882 } 25883 25884 var instance = new (Function.prototype.bind.apply(PluginSubClass, [null].concat([this].concat(args))))(); 25885 25886 // The plugin is replaced by a function that returns the current instance. 25887 this[name] = function () { 25888 return instance; 25889 }; 25890 25891 triggerSetupEvent(this, instance.getEventHash()); 25892 25893 return instance; 25894 }; 25895}; 25896 25897/** 25898 * Parent class for all advanced plugins. 25899 * 25900 * @mixes module:evented~EventedMixin 25901 * @mixes module:stateful~StatefulMixin 25902 * @fires Player#beforepluginsetup 25903 * @fires Player#beforepluginsetup:$name 25904 * @fires Player#pluginsetup 25905 * @fires Player#pluginsetup:$name 25906 * @listens Player#dispose 25907 * @throws {Error} 25908 * If attempting to instantiate the base {@link Plugin} class 25909 * directly instead of via a sub-class. 25910 */ 25911 25912var Plugin = function () { 25913 25914 /** 25915 * Creates an instance of this class. 25916 * 25917 * Sub-classes should call `super` to ensure plugins are properly initialized. 25918 * 25919 * @param {Player} player 25920 * A Video.js player instance. 25921 */ 25922 function Plugin(player) { 25923 classCallCheck(this, Plugin); 25924 25925 if (this.constructor === Plugin) { 25926 throw new Error('Plugin must be sub-classed; not directly instantiated.'); 25927 } 25928 25929 this.player = player; 25930 25931 // Make this object evented, but remove the added `trigger` method so we 25932 // use the prototype version instead. 25933 evented(this); 25934 delete this.trigger; 25935 25936 stateful(this, this.constructor.defaultState); 25937 markPluginAsActive(player, this.name); 25938 25939 // Auto-bind the dispose method so we can use it as a listener and unbind 25940 // it later easily. 25941 this.dispose = bind(this, this.dispose); 25942 25943 // If the player is disposed, dispose the plugin. 25944 player.on('dispose', this.dispose); 25945 } 25946 25947 /** 25948 * Get the version of the plugin that was set on <pluginName>.VERSION 25949 */ 25950 25951 25952 Plugin.prototype.version = function version() { 25953 return this.constructor.VERSION; 25954 }; 25955 25956 /** 25957 * Each event triggered by plugins includes a hash of additional data with 25958 * conventional properties. 25959 * 25960 * This returns that object or mutates an existing hash. 25961 * 25962 * @param {Object} [hash={}] 25963 * An object to be used as event an event hash. 25964 * 25965 * @returns {Plugin~PluginEventHash} 25966 * An event hash object with provided properties mixed-in. 25967 */ 25968 25969 25970 Plugin.prototype.getEventHash = function getEventHash() { 25971 var hash = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; 25972 25973 hash.name = this.name; 25974 hash.plugin = this.constructor; 25975 hash.instance = this; 25976 return hash; 25977 }; 25978 25979 /** 25980 * Triggers an event on the plugin object and overrides 25981 * {@link module:evented~EventedMixin.trigger|EventedMixin.trigger}. 25982 * 25983 * @param {string|Object} event 25984 * An event type or an object with a type property. 25985 * 25986 * @param {Object} [hash={}] 25987 * Additional data hash to merge with a 25988 * {@link Plugin~PluginEventHash|PluginEventHash}. 25989 * 25990 * @returns {boolean} 25991 * Whether or not default was prevented. 25992 */ 25993 25994 25995 Plugin.prototype.trigger = function trigger$$1(event) { 25996 var hash = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 25997 25998 return trigger(this.eventBusEl_, event, this.getEventHash(hash)); 25999 }; 26000 26001 /** 26002 * Handles "statechanged" events on the plugin. No-op by default, override by 26003 * subclassing. 26004 * 26005 * @abstract 26006 * @param {Event} e 26007 * An event object provided by a "statechanged" event. 26008 * 26009 * @param {Object} e.changes 26010 * An object describing changes that occurred with the "statechanged" 26011 * event. 26012 */ 26013 26014 26015 Plugin.prototype.handleStateChanged = function handleStateChanged(e) {}; 26016 26017 /** 26018 * Disposes a plugin. 26019 * 26020 * Subclasses can override this if they want, but for the sake of safety, 26021 * it's probably best to subscribe the "dispose" event. 26022 * 26023 * @fires Plugin#dispose 26024 */ 26025 26026 26027 Plugin.prototype.dispose = function dispose() { 26028 var name = this.name, 26029 player = this.player; 26030 26031 /** 26032 * Signals that a advanced plugin is about to be disposed. 26033 * 26034 * @event Plugin#dispose 26035 * @type {EventTarget~Event} 26036 */ 26037 26038 this.trigger('dispose'); 26039 this.off(); 26040 player.off('dispose', this.dispose); 26041 26042 // Eliminate any possible sources of leaking memory by clearing up 26043 // references between the player and the plugin instance and nulling out 26044 // the plugin's state and replacing methods with a function that throws. 26045 player[PLUGIN_CACHE_KEY][name] = false; 26046 this.player = this.state = null; 26047 26048 // Finally, replace the plugin name on the player with a new factory 26049 // function, so that the plugin is ready to be set up again. 26050 player[name] = createPluginFactory(name, pluginStorage[name]); 26051 }; 26052 26053 /** 26054 * Determines if a plugin is a basic plugin (i.e. not a sub-class of `Plugin`). 26055 * 26056 * @param {string|Function} plugin 26057 * If a string, matches the name of a plugin. If a function, will be 26058 * tested directly. 26059 * 26060 * @returns {boolean} 26061 * Whether or not a plugin is a basic plugin. 26062 */ 26063 26064 26065 Plugin.isBasic = function isBasic(plugin) { 26066 var p = typeof plugin === 'string' ? getPlugin(plugin) : plugin; 26067 26068 return typeof p === 'function' && !Plugin.prototype.isPrototypeOf(p.prototype); 26069 }; 26070 26071 /** 26072 * Register a Video.js plugin. 26073 * 26074 * @param {string} name 26075 * The name of the plugin to be registered. Must be a string and 26076 * must not match an existing plugin or a method on the `Player` 26077 * prototype. 26078 * 26079 * @param {Function} plugin 26080 * A sub-class of `Plugin` or a function for basic plugins. 26081 * 26082 * @returns {Function} 26083 * For advanced plugins, a factory function for that plugin. For 26084 * basic plugins, a wrapper function that initializes the plugin. 26085 */ 26086 26087 26088 Plugin.registerPlugin = function registerPlugin(name, plugin) { 26089 if (typeof name !== 'string') { 26090 throw new Error('Illegal plugin name, "' + name + '", must be a string, was ' + (typeof name === 'undefined' ? 'undefined' : _typeof(name)) + '.'); 26091 } 26092 26093 if (pluginExists(name)) { 26094 log$1.warn('A plugin named "' + name + '" already exists. You may want to avoid re-registering plugins!'); 26095 } else if (Player.prototype.hasOwnProperty(name)) { 26096 throw new Error('Illegal plugin name, "' + name + '", cannot share a name with an existing player method!'); 26097 } 26098 26099 if (typeof plugin !== 'function') { 26100 throw new Error('Illegal plugin for "' + name + '", must be a function, was ' + (typeof plugin === 'undefined' ? 'undefined' : _typeof(plugin)) + '.'); 26101 } 26102 26103 pluginStorage[name] = plugin; 26104 26105 // Add a player prototype method for all sub-classed plugins (but not for 26106 // the base Plugin class). 26107 if (name !== BASE_PLUGIN_NAME) { 26108 if (Plugin.isBasic(plugin)) { 26109 Player.prototype[name] = createBasicPlugin(name, plugin); 26110 } else { 26111 Player.prototype[name] = createPluginFactory(name, plugin); 26112 } 26113 } 26114 26115 return plugin; 26116 }; 26117 26118 /** 26119 * De-register a Video.js plugin. 26120 * 26121 * @param {string} name 26122 * The name of the plugin to be deregistered. 26123 */ 26124 26125 26126 Plugin.deregisterPlugin = function deregisterPlugin(name) { 26127 if (name === BASE_PLUGIN_NAME) { 26128 throw new Error('Cannot de-register base plugin.'); 26129 } 26130 if (pluginExists(name)) { 26131 delete pluginStorage[name]; 26132 delete Player.prototype[name]; 26133 } 26134 }; 26135 26136 /** 26137 * Gets an object containing multiple Video.js plugins. 26138 * 26139 * @param {Array} [names] 26140 * If provided, should be an array of plugin names. Defaults to _all_ 26141 * plugin names. 26142 * 26143 * @returns {Object|undefined} 26144 * An object containing plugin(s) associated with their name(s) or 26145 * `undefined` if no matching plugins exist). 26146 */ 26147 26148 26149 Plugin.getPlugins = function getPlugins() { 26150 var names = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : Object.keys(pluginStorage); 26151 26152 var result = void 0; 26153 26154 names.forEach(function (name) { 26155 var plugin = getPlugin(name); 26156 26157 if (plugin) { 26158 result = result || {}; 26159 result[name] = plugin; 26160 } 26161 }); 26162 26163 return result; 26164 }; 26165 26166 /** 26167 * Gets a plugin's version, if available 26168 * 26169 * @param {string} name 26170 * The name of a plugin. 26171 * 26172 * @returns {string} 26173 * The plugin's version or an empty string. 26174 */ 26175 26176 26177 Plugin.getPluginVersion = function getPluginVersion(name) { 26178 var plugin = getPlugin(name); 26179 26180 return plugin && plugin.VERSION || ''; 26181 }; 26182 26183 return Plugin; 26184}(); 26185 26186/** 26187 * Gets a plugin by name if it exists. 26188 * 26189 * @static 26190 * @method getPlugin 26191 * @memberOf Plugin 26192 * @param {string} name 26193 * The name of a plugin. 26194 * 26195 * @returns {Function|undefined} 26196 * The plugin (or `undefined`). 26197 */ 26198 26199 26200Plugin.getPlugin = getPlugin; 26201 26202/** 26203 * The name of the base plugin class as it is registered. 26204 * 26205 * @type {string} 26206 */ 26207Plugin.BASE_PLUGIN_NAME = BASE_PLUGIN_NAME; 26208 26209Plugin.registerPlugin(BASE_PLUGIN_NAME, Plugin); 26210 26211/** 26212 * Documented in player.js 26213 * 26214 * @ignore 26215 */ 26216Player.prototype.usingPlugin = function (name) { 26217 return !!this[PLUGIN_CACHE_KEY] && this[PLUGIN_CACHE_KEY][name] === true; 26218}; 26219 26220/** 26221 * Documented in player.js 26222 * 26223 * @ignore 26224 */ 26225Player.prototype.hasPlugin = function (name) { 26226 return !!pluginExists(name); 26227}; 26228 26229/** 26230 *
vendor: 12,140 bytes, lines 26230-26703
26230Signals that a plugin is about to be set up on a player. 26231 * 26232 * @event Player#beforepluginsetup 26233 * @type {Plugin~PluginEventHash} 26234 */ 26235 26236/** 26237 * Signals that a plugin is about to be set up on a player - by name. The name 26238 * is the name of the plugin. 26239 * 26240 * @event Player#beforepluginsetup:$name 26241 * @type {Plugin~PluginEventHash} 26242 */ 26243 26244/** 26245 * Signals that a plugin has just been set up on a player. 26246 * 26247 * @event Player#pluginsetup 26248 * @type {Plugin~PluginEventHash} 26249 */ 26250 26251/** 26252 * Signals that a plugin has just been set up on a player - by name. The name 26253 * is the name of the plugin. 26254 * 26255 * @event Player#pluginsetup:$name 26256 * @type {Plugin~PluginEventHash} 26257 */ 26258 26259/** 26260 * @typedef {Object} Plugin~PluginEventHash 26261 * 26262 * @property {string} instance 26263 * For basic plugins, the return value of the plugin function. For 26264 * advanced plugins, the plugin instance on which the event is fired. 26265 * 26266 * @property {string} name 26267 * The name of the plugin. 26268 * 26269 * @property {string} plugin 26270 * For basic plugins, the plugin function. For advanced plugins, the 26271 * plugin class/constructor. 26272 */ 26273 26274/** 26275 * @file extend.js 26276 * @module extend 26277 */ 26278 26279/** 26280 * A combination of node inherits and babel's inherits (after transpile). 26281 * Both work the same but node adds `super_` to the subClass 26282 * and Bable adds the superClass as __proto__. Both seem useful. 26283 * 26284 * @param {Object} subClass 26285 * The class to inherit to 26286 * 26287 * @param {Object} superClass 26288 * The class to inherit from 26289 * 26290 * @private 26291 */ 26292var _inherits = function _inherits(subClass, superClass) { 26293 if (typeof superClass !== 'function' && superClass !== null) { 26294 throw new TypeError('Super expression must either be null or a function, not ' + (typeof superClass === 'undefined' ? 'undefined' : _typeof(superClass))); 26295 } 26296 26297 subClass.prototype = Object.create(superClass && superClass.prototype, { 26298 constructor: { 26299 value: subClass, 26300 enumerable: false, 26301 writable: true, 26302 configurable: true 26303 } 26304 }); 26305 26306 if (superClass) { 26307 // node 26308 subClass.super_ = superClass; 26309 } 26310}; 26311 26312/** 26313 * Function for subclassing using the same inheritance that 26314 * videojs uses internally 26315 * 26316 * @static 26317 * @const 26318 * 26319 * @param {Object} superClass 26320 * The class to inherit from 26321 * 26322 * @param {Object} [subClassMethods={}] 26323 * The class to inherit to 26324 * 26325 * @return {Object} 26326 * The new object with subClassMethods that inherited superClass. 26327 */ 26328var extendFn = function extendFn(superClass) { 26329 var subClassMethods = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 26330 26331 var subClass = function subClass() { 26332 superClass.apply(this, arguments); 26333 }; 26334 26335 var methods = {}; 26336 26337 if ((typeof subClassMethods === 'undefined' ? 'undefined' : _typeof(subClassMethods)) === 'object') { 26338 if (subClassMethods.constructor !== Object.prototype.constructor) { 26339 subClass = subClassMethods.constructor; 26340 } 26341 methods = subClassMethods; 26342 } else if (typeof subClassMethods === 'function') { 26343 subClass = subClassMethods; 26344 } 26345 26346 _inherits(subClass, superClass); 26347 26348 // Extend subObj's prototype with functions and other properties from props 26349 for (var name in methods) { 26350 if (methods.hasOwnProperty(name)) { 26351 subClass.prototype[name] = methods[name]; 26352 } 26353 } 26354 26355 return subClass; 26356}; 26357 26358/** 26359 * @file video.js 26360 * @module videojs 26361 */ 26362// Include the built-in techs 26363// HTML5 Element Shim for IE8 26364if (typeof HTMLVideoElement === 'undefined' && isReal()) { 26365 document_1.createElement('video'); 26366 document_1.createElement('audio'); 26367 document_1.createElement('track'); 26368 document_1.createElement('video-js'); 26369} 26370 26371/** 26372 * Normalize an `id` value by trimming off a leading `#` 26373 * 26374 * @param {string} id 26375 * A string, maybe with a leading `#`. 26376 * 26377 * @returns {string} 26378 * The string, without any leading `#`. 26379 */ 26380var normalizeId = function normalizeId(id) { 26381 return id.indexOf('#') === 0 ? id.slice(1) : id; 26382}; 26383 26384/** 26385 * Doubles as the main function for users to create a player instance and also 26386 * the main library object. 26387 * The `videojs` function can be used to initialize or retrieve a player. 26388 * 26389 * @param {string|Element} id 26390 * Video element or video element ID 26391 * 26392 * @param {Object} [options] 26393 * Optional options object for config/settings 26394 * 26395 * @param {Component~ReadyCallback} [ready] 26396 * Optional ready callback 26397 * 26398 * @return {Player} 26399 * A player instance 26400 */ 26401function videojs(id, options, ready) { 26402 var player = videojs.getPlayer(id); 26403 26404 if (player) { 26405 if (options) { 26406 log$1.warn('Player "' + id + '" is already initialised. Options will not be applied.'); 26407 } 26408 if (ready) { 26409 player.ready(ready); 26410 } 26411 return player; 26412 } 26413 26414 var el = typeof id === 'string' ? $('#' + normalizeId(id)) : id; 26415 26416 if (!isEl(el)) { 26417 throw new TypeError('The element or ID supplied is not valid. (videojs)'); 26418 } 26419 26420 if (!document_1.body.contains(el)) { 26421 log$1.warn('The element supplied is not included in the DOM'); 26422 } 26423 26424 options = options || {}; 26425 26426 videojs.hooks('beforesetup').forEach(function (hookFunction) { 26427 var opts = hookFunction(el, mergeOptions(options)); 26428 26429 if (!isObject(opts) || Array.isArray(opts)) { 26430 log$1.error('please return an object in beforesetup hooks'); 26431 return; 26432 } 26433 26434 options = mergeOptions(options, opts); 26435 }); 26436 26437 // We get the current "Player" component here in case an integration has 26438 // replaced it with a custom player. 26439 var PlayerComponent = Component.getComponent('Player'); 26440 26441 player = new PlayerComponent(el, options, ready); 26442 26443 videojs.hooks('setup').forEach(function (hookFunction) { 26444 return hookFunction(player); 26445 }); 26446 26447 return player; 26448} 26449 26450/** 26451 * An Object that contains lifecycle hooks as keys which point to an array 26452 * of functions that are run when a lifecycle is triggered 26453 */ 26454videojs.hooks_ = {}; 26455 26456/** 26457 * Get a list of hooks for a specific lifecycle 26458 * @function videojs.hooks 26459 * 26460 * @param {string} type 26461 * the lifecyle to get hooks from 26462 * 26463 * @param {Function|Function[]} [fn] 26464 * Optionally add a hook (or hooks) to the lifecycle that your are getting. 26465 * 26466 * @return {Array} 26467 * an array of hooks, or an empty array if there are none. 26468 */ 26469videojs.hooks = function (type, fn) { 26470 videojs.hooks_[type] = videojs.hooks_[type] || []; 26471 if (fn) { 26472 videojs.hooks_[type] = videojs.hooks_[type].concat(fn); 26473 } 26474 return videojs.hooks_[type]; 26475}; 26476 26477/** 26478 * Add a function hook to a specific videojs lifecycle. 26479 * 26480 * @param {string} type 26481 * the lifecycle to hook the function to. 26482 * 26483 * @param {Function|Function[]} 26484 * The function or array of functions to attach. 26485 */ 26486videojs.hook = function (type, fn) { 26487 videojs.hooks(type, fn); 26488}; 26489 26490/** 26491 * Add a function hook that will only run once to a specific videojs lifecycle. 26492 * 26493 * @param {string} type 26494 * the lifecycle to hook the function to. 26495 * 26496 * @param {Function|Function[]} 26497 * The function or array of functions to attach. 26498 */ 26499videojs.hookOnce = function (type, fn) { 26500 videojs.hooks(type, [].concat(fn).map(function (original) { 26501 var wrapper = function wrapper() { 26502 videojs.removeHook(type, wrapper); 26503 return original.apply(undefined, arguments); 26504 }; 26505 26506 return wrapper; 26507 })); 26508}; 26509 26510/** 26511 * Remove a hook from a specific videojs lifecycle. 26512 * 26513 * @param {string} type 26514 * the lifecycle that the function hooked to 26515 * 26516 * @param {Function} fn 26517 * The hooked function to remove 26518 * 26519 * @return {boolean} 26520 * The function that was removed or undef 26521 */ 26522videojs.removeHook = function (type, fn) { 26523 var index = videojs.hooks(type).indexOf(fn); 26524 26525 if (index <= -1) { 26526 return false; 26527 } 26528 26529 videojs.hooks_[type] = videojs.hooks_[type].slice(); 26530 videojs.hooks_[type].splice(index, 1); 26531 26532 return true; 26533}; 26534 26535// Add default styles 26536if (window_1.VIDEOJS_NO_DYNAMIC_STYLE !== true && isReal()) { 26537 var style = $('.vjs-styles-defaults'); 26538 26539 if (!style) { 26540 style = createStyleElement('vjs-styles-defaults'); 26541 var head = $('head'); 26542 26543 if (head) { 26544 head.insertBefore(style, head.firstChild); 26545 } 26546 setTextContent(style, '\n .video-js {\n width: 300px;\n height: 150px;\n }\n\n .vjs-fluid {\n padding-top: 56.25%\n }\n '); 26547 } 26548} 26549 26550// Run Auto-load players 26551// You have to wait at least once in case this script is loaded after your 26552// video in the DOM (weird behavior only with minified version) 26553autoSetupTimeout(1, videojs); 26554 26555/** 26556 * Current software version. Follows semver. 26557 * 26558 * @type {string} 26559 */ 26560videojs.VERSION = version; 26561 26562/** 26563 * The global options object. These are the settings that take effect 26564 * if no overrides are specified when the player is created. 26565 * 26566 * @type {Object} 26567 */ 26568videojs.options = Player.prototype.options_; 26569 26570/** 26571 * Get an object with the currently created players, keyed by player ID 26572 * 26573 * @return {Object} 26574 * The created players 26575 */ 26576videojs.getPlayers = function () { 26577 return Player.players; 26578}; 26579 26580/** 26581 * Get a single player based on an ID or DOM element. 26582 * 26583 * This is useful if you want to check if an element or ID has an associated 26584 * Video.js player, but not create one if it doesn't. 26585 * 26586 * @param {string|Element} id 26587 * An HTML element - `<video>`, `<audio>`, or `<video-js>` - 26588 * or a string matching the `id` of such an element. 26589 * 26590 * @returns {Player|undefined} 26591 * A player instance or `undefined` if there is no player instance 26592 * matching the argument. 26593 */ 26594videojs.getPlayer = function (id) { 26595 var players = Player.players; 26596 var tag = void 0; 26597 26598 if (typeof id === 'string') { 26599 var nId = normalizeId(id); 26600 var player = players[nId]; 26601 26602 if (player) { 26603 return player; 26604 } 26605 26606 tag = $('#' + nId); 26607 } else { 26608 tag = id; 26609 } 26610 26611 if (isEl(tag)) { 26612 var _tag = tag, 26613 _player = _tag.player, 26614 playerId = _tag.playerId; 26615 26616 // Element may have a `player` property referring to an already created 26617 // player instance. If so, return that. 26618 26619 if (_player || players[playerId]) { 26620 return _player || players[playerId]; 26621 } 26622 } 26623}; 26624 26625/** 26626 * Returns an array of all current players. 26627 * 26628 * @return {Array} 26629 * An array of all players. The array will be in the order that 26630 * `Object.keys` provides, which could potentially vary between 26631 * JavaScript engines. 26632 * 26633 */ 26634videojs.getAllPlayers = function () { 26635 return ( 26636 26637 // Disposed players leave a key with a `null` value, so we need to make sure 26638 // we filter those out. 26639 Object.keys(Player.players).map(function (k) { 26640 return Player.players[k]; 26641 }).filter(Boolean) 26642 ); 26643}; 26644 26645/** 26646 * Expose players object. 26647 * 26648 * @memberOf videojs 26649 * @property {Object} players 26650 */ 26651videojs.players = Player.players; 26652 26653/** 26654 * Get a component class object by name 26655 * 26656 * @borrows Component.getComponent as videojs.getComponent 26657 */ 26658videojs.getComponent = Component.getComponent; 26659 26660/** 26661 * Register a component so it can referred to by name. Used when adding to other 26662 * components, either through addChild `component.addChild('myComponent')` or through 26663 * default children options `{ children: ['myComponent'] }`. 26664 * 26665 * > NOTE: You could also just initialize the component before adding. 26666 * `component.addChild(new MyComponent());` 26667 * 26668 * @param {string} name 26669 * The class name of the component 26670 * 26671 * @param {Component} comp 26672 * The component class 26673 * 26674 * @return {Component} 26675 * The newly registered component 26676 */ 26677videojs.registerComponent = function (name$$1, comp) { 26678 if (Tech.isTech(comp)) { 26679 log$1.warn('The ' + name$$1 + ' tech was registered as a component. It should instead be registered using videojs.registerTech(name, tech)'); 26680 } 26681 26682 Component.registerComponent.call(Component, name$$1, comp); 26683}; 26684 26685/** 26686 * Get a Tech class object by name 26687 * 26688 * @borrows Tech.getTech as videojs.getTech 26689 */ 26690videojs.getTech = Tech.getTech; 26691 26692/** 26693 * Register a Tech so it can referred to by name. 26694 * This is used in the tech order for the player. 26695 * 26696 * @borrows Tech.registerTech as videojs.registerTech 26697 */ 26698videojs.registerTech = Tech.registerTech; 26699 26700/** 26701 * Register a middleware to a source type. 26702 * 26703 * @param {String}
26703 type A string representing a MIME type. 26704 * @param {function(player):object} middleware A middleware factory that takes a player. 26705 */ 26706videojs.use = use; 26707 26708/** 26709 * An object that can be returned by a middleware to signify 26710 * that the middleware is being terminated. 26711 * 26712 * @type {object} 26713 * @memberOf {videojs} 26714 * @property {object} middleware.TERMINATOR 26715 */ 26716// Object.defineProperty is not available in IE8 26717if (!IS_IE8 && Object.defineProperty) { 26718 Object.defineProperty(videojs, 'middleware', { 26719 value: {}, 26720 writeable: false, 26721 enumerable: true 26722 }); 26723 26724 Object.defineProperty(videojs.middleware, 'TERMINATOR', { 26725 value: TERMINATOR, 26726 writeable: false, 26727 enumerable: true 26728 }); 26729} else { 26730 videojs.middleware = { TERMINATOR: TERMINATOR }; 26731} 26732 26733/** 26734 * A suite of browser and device tests from {@link browser}. 26735 * 26736 * @type {Object} 26737 * @private 26738 */ 26739videojs.browser = browser; 26740 26741/** 26742 * Whether or not the browser supports touch events. Included for backward 26743 * compatibility with 4.x, but deprecated. Use `videojs.browser.TOUCH_ENABLED` 26744 * instead going forward. 26745 * 26746 * @deprecated since version 5.0 26747 * @type {boolean} 26748 */ 26749videojs.TOUCH_ENABLED = TOUCH_ENABLED; 26750 26751/** 26752 * Subclass an existing class 26753 * Mimics ES6 subclassing with the `extend` keyword 26754 * 26755 * @borrows extend:extendFn as videojs.extend 26756 */ 26757videojs.extend = extendFn; 26758 26759/** 26760 * Merge two options objects recursively 26761 * Performs a deep merge like lodash.merge but **only merges plain objects** 26762 * (not arrays, elements, anything else) 26763 * Other values will be copied directly from the second object. 26764 * 26765 * @borrows merge-options:mergeOptions as videojs.mergeOptions 26766 */ 26767videojs.mergeOptions = mergeOptions; 26768 26769/** 26770 * Change the context (this) of a function 26771 * 26772 * > NOTE: as of v5.0 we require an ES5 shim, so you should use the native 26773 * `function() {}.bind(newContext);` instead of this. 26774 * 26775 * @borrows fn:bind as videojs.bind 26776 */ 26777videojs.bind = bind; 26778 26779/** 26780 * Register a Video.js plugin. 26781 * 26782 * @borrows plugin:registerPlugin as videojs.registerPlugin 26783 * @method registerPlugin 26784 * 26785 * @param {string} name 26786 * The name of the plugin to be registered. Must be a string and 26787 * must not match an existing plugin or a method on the `Player` 26788 * prototype. 26789 * 26790 * @param {Function} plugin 26791 * A sub-class of `Plugin` or a function for basic plugins. 26792 * 26793 * @return {Function} 26794 * For advanced plugins, a factory function for that plugin. For 26795 * basic plugins, a wrapper function that initializes the plugin. 26796 */ 26797videojs.registerPlugin = Plugin.registerPlugin; 26798 26799/** 26800 * Deregister a Video.js plugin. 26801 * 26802 * @borrows plugin:deregisterPlugin as videojs.deregisterPlugin 26803 * @method deregisterPlugin 26804 * 26805 * @param {string} name 26806 * The name of the plugin to be deregistered. Must be a string and 26807 * must match an existing plugin or a method on the `Player` 26808 * prototype. 26809 * 26810 */ 26811videojs.deregisterPlugin = Plugin.deregisterPlugin; 26812 26813/** 26814 * Deprecated method to register a plugin with Video.js 26815 * 26816 * @deprecated 26817 * videojs.plugin() is deprecated; use videojs.registerPlugin() instead 26818 * 26819 * @param {string} name 26820 * The plugin name 26821 * 26822 * @param {Plugin|Function} plugin 26823 * The plugin sub-class or function 26824 */ 26825videojs.plugin = function (name$$1, plugin) { 26826 log$1.warn('videojs.plugin() is deprecated; use videojs.registerPlugin() instead'); 26827 return Plugin.registerPlugin(name$$1, plugin); 26828}; 26829 26830/**
26831 * Gets an object containing multiple Video.js plugins. 26832 * 26833 * @param {Array} [names] 26834 * If provided, should be an array of plugin names. Defaults to _all_ 26835 * plugin names. 26836 * 26837 * @return {Object|undefined} 26838 * An object containing plugin(s) associated with their name(s) or 26839 * `undefined` if no matching plugins exist). 26840 */ 26841videojs.getPlugins = Plugin.getPlugins; 26842 26843/** 26844 * Gets a plugin by name if it exists. 26845 * 26846 * @param {string} name 26847 * The name of a plugin. 26848 * 26849 * @return {Function|undefined} 26850 * The plugin (or `undefined`). 26851 */ 26852videojs.getPlugin = Plugin.getPlugin; 26853 26854/** 26855 * Gets a plugin's version, if available 26856 * 26857 * @param {string} name 26858 * The name of a plugin. 26859 * 26860 * @return {string} 26861 * The plugin's version or an empty string. 26862 */ 26863videojs.getPluginVersion = Plugin.getPluginVersion; 26864 26865/** 26866 * Adding languages so that they're available to all players. 26867 * Example: `videojs.addLanguage('es', { 'Hello': 'Hola' });` 26868 * 26869 * @param {string} code 26870 * The language code or dictionary property 26871 * 26872 * @param {Object} data 26873 * The data values to be translated 26874 * 26875 * @return {Object} 26876 * The resulting language dictionary object 26877 */ 26878videojs.addLanguage = function (code, data) { 26879 var _mergeOptions; 26880 26881 code = ('' + code).toLowerCase(); 26882 26883 videojs.options.languages = mergeOptions(videojs.options.languages, (_mergeOptions = {}, _mergeOptions[code] = data, _mergeOptions)); 26884 26885 return videojs.options.languages[code]; 26886}; 26887 26888/** 26889 * Log messages 26890 * 26891 * @borrows log:log as videojs.log 26892 */ 26893videojs.log = log$1; 26894 26895/** 26896 * Creates an emulated TimeRange object. 26897 * 26898 * @borrows time-ranges:createTimeRanges as videojs.createTimeRange 26899 */ 26900/** 26901 * @borrows time-ranges:createTimeRanges as videojs.createTimeRanges 26902 */ 26903videojs.createTimeRange = videojs.createTimeRanges = createTimeRanges; 26904 26905/** 26906 * Format seconds as a time string, H:MM:SS or M:SS 26907 * Supplying a guide (in seconds) will force a number of leading zeros 26908 * to cover the length of the guide 26909 * 26910 * @borrows format-time:formatTime as videojs.formatTime 26911 */ 26912videojs.formatTime = formatTime; 26913 26914/** 26915 * Replaces format-time with a custom implementation, to be used in place of the default. 26916 * 26917 * @borrows format-time:setFormatTime as videojs.setFormatTime 26918 * 26919 * @method setFormatTime 26920 * 26921 * @param {Function} customFn 26922 * A custom format-time function which will be called with the current time and guide (in seconds) as arguments. 26923 * Passed fn should return a string. 26924 */ 26925videojs.setFormatTime = setFormatTime; 26926 26927/** 26928 * Resets format-time to the default implementation. 26929 * 26930 * @borrows format-time:resetFormatTime as videojs.resetFormatTime 26931 * 26932 * @method resetFormatTime 26933 */ 26934videojs.resetFormatTime = resetFormatTime; 26935 26936/** 26937 * Resolve and parse the elements of a URL 26938 * 26939 * @borrows url:parseUrl as videojs.parseUrl 26940 * 26941 */ 26942videojs.parseUrl = parseUrl; 26943 26944/** 26945 * Returns whether the url passed is a cross domain request or not. 26946 * 26947 * @borrows url:isCrossOrigin as videojs.isCrossOrigin 26948 */ 26949videojs.isCrossOrigin = isCrossOrigin; 26950 26951/** 26952 * Event target class. 26953 * 26954 * @borrows EventTarget as videojs.EventTarget 26955 */ 26956videojs.EventTarget = EventTarget; 26957 26958/** 26959 * Add an event listener to element 26960 * It stores the handler function in a separate cache object 26961 * and adds a generic handler to the element's event, 26962 * along with a unique id (guid) to the element. 26963 * 26964 * @borrows events:on as videojs.on 26965 */ 26966videojs.on = on; 26967 26968/** 26969 * Trigger a listener only once for an event 26970 * 26971 * @borrows events:one as videojs.one 26972 */ 26973videojs.one = one; 26974 26975/** 26976 * Removes event listeners from an element 26977 * 26978 * @borrows events:off as videojs.off 26979 */ 26980videojs.off = off; 26981 26982/** 26983 * Trigger an event for an element 26984 * 26985 * @borrows events:trigger as videojs.trigger 26986 */ 26987videojs.trigger = trigger; 26988 26989/** 26990 * A cross-browser XMLHttpRequest wrapper. Here's a simple example: 26991 * 26992 * @param {Object} options 26993 * settings for the request. 26994 * 26995 * @return {XMLHttpRequest|XDomainRequest} 26996 * The request object. 26997 * 26998 * @see https://github.com/Raynos/xhr 26999 */ 27000videojs.xhr = xhr; 27001 27002/** 27003 * TextTrack class 27004 * 27005 * @borrows TextTrack as videojs.TextTrack 27006 */ 27007videojs.TextTrack = TextTrack; 27008 27009/** 27010 * export the AudioTrack class so that source handlers can create 27011 * AudioTracks and then add them to the players AudioTrackList 27012 * 27013 * @borrows AudioTrack as videojs.AudioTrack 27014 */ 27015videojs.AudioTrack = AudioTrack; 27016 27017/** 27018 * export the VideoTrack class so that source handlers can create 27019 * VideoTracks and then add them to the players VideoTrackList 27020 * 27021 * @borrows VideoTrack as videojs.VideoTrack 27022 */ 27023videojs.VideoTrack = VideoTrack; 27024 27025/** 27026 * Determines, via duck typing, whether or not a value is a DOM element. 27027 * 27028 * @borrows dom:isEl as videojs.isEl 27029 * @deprecated Use videojs.dom.isEl() instead 27030 */ 27031
27032/** 27033 * Determines, via duck typing, whether or not a value is a text node. 27034 * 27035 * @borrows dom:isTextNode as videojs.isTextNode 27036 * @deprecated Use videojs.dom.isTextNode() instead 27037 */ 27038 27039/** 27040 * Creates an element and applies properties. 27041 * 27042 * @borrows dom:createEl as videojs.createEl 27043 * @deprecated Use videojs.dom.createEl() instead 27044 */ 27045 27046/** 27047 * Check if an element has a CSS class 27048 * 27049 * @borrows dom:hasElClass as videojs.hasClass 27050 * @deprecated Use videojs.dom.hasClass() instead 27051 */ 27052 27053/** 27054 * Add a CSS class name to an element 27055 * 27056 * @borrows dom:addElClass as videojs.addClass 27057 * @deprecated Use videojs.dom.addClass() instead 27058 */ 27059 27060/** 27061 * Remove a CSS class name from an element 27062 * 27063 * @borrows dom:removeElClass as videojs.removeClass 27064 * @deprecated Use videojs.dom.removeClass() instead 27065 */ 27066 27067/** 27068 * Adds or removes a CSS class name on an element depending on an optional 27069 * condition or the presence/absence of the class name. 27070 * 27071 * @borrows dom:toggleElClass as videojs.toggleClass 27072 * @deprecated Use videojs.dom.toggleClass() instead 27073 */ 27074 27075/** 27076 * Apply attributes to an HTML element. 27077 * 27078 * @borrows dom:setElAttributes as videojs.setAttribute 27079 * @deprecated Use videojs.dom.setAttributes() instead 27080 */ 27081 27082/** 27083 * Get an element's attribute values, as defined on the HTML tag 27084 * Attributes are not the same as properties. They're defined on the tag 27085 * or with setAttribute (which shouldn't be used with HTML) 27086 * This will return true or false for boolean attributes. 27087 * 27088 * @borrows dom:getElAttributes as videojs.getAttributes 27089 * @deprecated Use videojs.dom.getAttributes() instead 27090 */ 27091 27092/** 27093 * Empties the contents of an element. 27094 * 27095 * @borrows dom:emptyEl as videojs.emptyEl 27096 * @deprecated Use videojs.dom.emptyEl() instead 27097 */ 27098 27099/** 27100 * Normalizes and appends content to an element. 27101 * 27102 * The content for an element can be passed in multiple types and 27103 * combinations, whose behavior is as follows: 27104 * 27105 * - String 27106 * Normalized into a text node. 27107 * 27108 * - Element, TextNode 27109 * Passed through. 27110 * 27111 * - Array 27112 * A one-dimensional array of strings, elements, nodes, or functions (which 27113 * return single strings, elements, or nodes). 27114 * 27115 * - Function 27116 * If the sole argument, is expected to produce a string, element, 27117 * node, or array. 27118 * 27119 * @borrows dom:appendContents as videojs.appendContet 27120 * @deprecated Use videojs.dom.appendContent() instead 27121 */ 27122 27123/** 27124 * Normalizes and inserts content into an element; this is identical to 27125 * `appendContent()`, except it empties the element first. 27126 * 27127 * The content for an element can be passed in multiple types and 27128 * combinations, whose behavior is as follows: 27129 * 27130 * - String 27131 * Normalized into a text node. 27132 * 27133 * - Element, TextNode 27134 * Passed through. 27135 * 27136 * - Array 27137 * A one-dimensional array of strings, elements, nodes, or functions (which 27138 * return single strings, elements, or nodes). 27139 * 27140 * - Function 27141 * If the sole argument, is expected to produce a string, element, 27142 * node, or array. 27143 * 27144 * @borrows dom:insertContent as videojs.insertContent 27145 * @deprecated Use videojs.dom.insertContent() instead 27146 */ 27147['isEl', 'isTextNode', 'createEl', 'hasClass', 'addClass', 'removeClass', 'toggleClass', 'setAttributes', 'getAttributes', 'emptyEl', 'appendContent', 'insertContent'].forEach(function (k) { 27148 videojs[k] = function () { 27149 log$1.warn('videojs.' + k + '() is deprecated; use videojs.dom.' + k + '() instead'); 27150 return Dom[k].apply(null, arguments); 27151 }; 27152}); 27153 27154/** 27155 * A safe getComputedStyle with an IE8 fallback. 27156 * 27157 * This is because in Firefox, if the player is loaded in an iframe with `display:none`, 27158 * then `getComputedStyle` returns `null`, so, we do a null-check to make sure 27159 * that the player doesn't break in these cases. 27160 * See https://bugzilla.mozilla.org/show_bug.cgi?id=548397 for more details. 27161 * 27162 * @borrows computed-style:computedStyle as videojs.computedStyle 27163 */ 27164videojs.computedStyle = computedStyle; 27165 27166/** 27167 * Export the Dom utilities for use in external plugins 27168 * and Tech's 27169 */ 27170videojs.dom = Dom; 27171 27172/** 27173 * Export the Url utilities for use in external plugins 27174 * and Tech's 27175 */ 27176videojs.url = Url; 27177 27178return videojs; 27179 27180}))); 27181 27182!function(){!function(a){var b=a&&a.videojs;b&&(b.CDN_VERSION="6.12.0")}(window),function(a,b,c,d,e,f,g){b&&b.HELP_IMPROVE_VIDEOJS!==!1&&(c&&c.doNotTrack&&"1"===c.doNotTrack||c&&c.msDoNotTrack&&"1"===c.msDoNotTrack||b.doNotTrack&&"1"===b.doNotTrack||e.random()>.01||(f=b.location,g=b.videojs||{},a.src="//www.google-analytics.com/__utm.gif?utmwv=5.4.2&utmac=UA-16505296-3&utmn=1&utmhn="+d(f.hostname)+"&utmsr="+b.screen.availWi
vendor: 24,293 bytes, lines 27182-27980
27182dth+"x"+b.screen.availHeight+"&utmul="+(c.language||c.userLanguage||"").toLowerCase()+"&utmr="+d(f.href)+"&utmp="+d(f.hostname+f.pathname)+"&utmcc=__utma%3D1."+e.floor(1e10*e.random())+".1.1.1.1%3B&utme=8(vjsv*cdnv)9("+g.VERSION+"*"+g.CDN_VERSION+")"))}(new Image,window,navigator,encodeURIComponent,Math)}(); 27183/* The MIT License (MIT) 27184 27185Copyright (c) 2014-2015 Benoit Tremblay <[email protected]> 27186 27187Permission is hereby granted, free of charge, to any person obtaining a copy 27188of this software and associated documentation files (the "Software"), to deal 27189in the Software without restriction, including without limitation the rights 27190to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 27191copies of the Software, and to permit persons to whom the Software is 27192furnished to do so, subject to the following conditions: 27193 27194The above copyright notice and this permission notice shall be included in 27195all copies or substantial portions of the Software. 27196 27197THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 27198IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 27199FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 27200AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27201LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 27202OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27203THE SOFTWARE. */ 27204/*global define, YT*/ 27205(function (root, factory) { 27206 if(typeof exports==='object' && typeof module!=='undefined') { 27207 var videojs = require('video.js'); 27208 module.exports = factory(videojs.default || videojs); 27209 } else if(typeof define === 'function' && define.amd) { 27210 define(['videojs'], function(videojs){ 27211 return (root.Youtube = factory(videojs)); 27212 }); 27213 } else { 27214 root.Youtube = factory(root.videojs); 27215 } 27216 }(this, function(videojs) { 27217 'use strict'; 27218 27219 var _isOnMobile = videojs.browser.IS_IOS || videojs.browser.IS_NATIVE_ANDROID; 27220 var Tech = videojs.getTech('Tech'); 27221 27222 class Youtube extends Tech { 27223 27224 constructor(options, ready) { 27225 super(options, ready); 27226 27227 this.setPoster(options.poster); 27228 this.setSrc(this.options_.source, true); 27229 27230 // Set the vjs-youtube class to the player 27231 // Parent is not set yet so we have to wait a tick 27232 this.setTimeout(function() { 27233 if (this.el_) { 27234 this.el_.parentNode.className += ' vjs-youtube'; 27235 27236 if (_isOnMobile) { 27237 this.el_.parentNode.className += ' vjs-youtube-mobile'; 27238 } 27239 27240 if (Youtube.isApiReady) { 27241 this.initYTPlayer(); 27242 } else { 27243 Youtube.apiReadyQueue.push(this); 27244 } 27245 } 27246 }.bind(this)); 27247 } 27248 27249 dispose() { 27250 if (this.ytPlayer) { 27251 //Dispose of the YouTube Player 27252 if (this.ytPlayer.stopVideo) { 27253 this.ytPlayer.stopVideo(); 27254 } 27255 if (this.ytPlayer.destroy) { 27256 this.ytPlayer.destroy(); 27257 } 27258 } else { 27259 //YouTube API hasn't finished loading or the player is already disposed 27260 var index = Youtube.apiReadyQueue.indexOf(this); 27261 if (index !== -1) { 27262 Youtube.apiReadyQueue.splice(index, 1); 27263 } 27264 } 27265 this.ytPlayer = null; 27266 27267 this.el_.parentNode.className = this.el_.parentNode.className 27268 .replace(' vjs-youtube', '') 27269 .replace(' vjs-youtube-mobile', ''); 27270 this.el_.parentNode.removeChild(this.el_); 27271 27272 //Needs to be called after the YouTube player is destroyed, otherwise there will be a null reference exception 27273 Tech.prototype.dispose.call(this); 27274 } 27275 27276 createEl() { 27277 var div = document.createElement('div'); 27278 div.setAttribute('id', this.options_.techId); 27279 div.setAttribute('style', 'width:100%;height:100%;top:0;left:0;position:absolute'); 27280 div.setAttribute('class', 'vjs-tech'); 27281 27282 var divWrapper = document.createElement('div'); 27283 divWrapper.appendChild(div); 27284 27285 if (!_isOnMobile && !this.options_.ytControls) { 27286 var divBlocker = document.createElement('div'); 27287 divBlocker.setAttribute('class', 'vjs-iframe-blocker'); 27288 divBlocker.setAttribute('style', 'position:absolute;top:0;left:0;width:100%;height:100%'); 27289 27290 // In case the blocker is still there and we want to pause 27291 divBlocker.onclick = function() { 27292 this.pause(); 27293 }.bind(this); 27294 27295 divWrapper.appendChild(divBlocker); 27296 } 27297 27298 return divWrapper; 27299 } 27300 27301 initYTPlayer() { 27302 var playerVars = { 27303 controls: 0, 27304 modestbranding: 1, 27305 rel: 0, 27306 showinfo: 0, 27307 loop: this.options_.loop ? 1 : 0 27308 }; 27309 27310 // Let the user set any YouTube parameter 27311 // https://developers.google.com/youtube/player_parameters?playerVersion=HTML5#Parameters 27312 // To use YouTube controls, you must use ytControls instead 27313 // To use the loop or autoplay, use the video.js settings 27314 27315 if (typeof this.options_.autohide !== 'undefined') { 27316 playerVars.autohide = this.options_.autohide; 27317 } 27318 27319 if (typeof this.options_['cc_load_policy'] !== 'undefined') { 27320 playerVars['cc_load_policy'] = this.options_['cc_load_policy']; 27321 } 27322 27323 if (typeof this.options_.ytControls !== 'undefined') { 27324 playerVars.controls = this.options_.ytControls; 27325 } 27326 27327 if (typeof this.options_.disablekb !== 'undefined') { 27328 playerVars.disablekb = this.options_.disablekb; 27329 } 27330 27331 if (typeof this.options_.color !== 'undefined') { 27332 playerVars.color = this.options_.color; 27333 } 27334 27335 if (!playerVars.controls) { 27336 // Let video.js handle the fullscreen unless it is the YouTube native controls 27337 playerVars.fs = 0; 27338 } else if (typeof this.options_.fs !== 'undefined') { 27339 playerVars.fs = this.options_.fs; 27340 } 27341 27342 if (this.options_.source.src.indexOf('end=') !== -1) { 27343 var srcEndTime = this.options_.source.src.match(/end=([0-9]*)/); 27344 this.options_.end = parseInt(srcEndTime[1]); 27345 } 27346 27347 if (typeof this.options_.end !== 'undefined') { 27348 playerVars.end = this.options_.end; 27349 } 27350 27351 if (typeof this.options_.hl !== 'undefined') { 27352 playerVars.hl = this.options_.hl; 27353 } else if (typeof this.options_.language !== 'undefined') { 27354 // Set the YouTube player on the same language than video.js 27355 playerVars.hl = this.options_.language.substr(0, 2); 27356 } 27357 27358 if (typeof this.options_['iv_load_policy'] !== 'undefined') { 27359 playerVars['iv_load_policy'] = this.options_['iv_load_policy']; 27360 } 27361 27362 if (typeof this.options_.list !== 'undefined') { 27363 playerVars.list = this.options_.list; 27364 } else if (this.url && typeof this.url.listId !== 'undefined') { 27365 playerVars.list = this.url.listId; 27366 } 27367 27368 if (typeof this.options_.listType !== 'undefined') { 27369 playerVars.listType = this.options_.listType; 27370 } 27371 27372 if (typeof this.options_.modestbranding !== 'undefined') { 27373 playerVars.modestbranding = this.options_.modestbranding; 27374 } 27375 27376 if (typeof this.options_.playlist !== 'undefined') { 27377 playerVars.playlist = this.options_.playlist; 27378 } 27379 27380 if (typeof this.options_.playsinline !== 'undefined') { 27381 playerVars.playsinline = this.options_.playsinline; 27382 } 27383 27384 if (typeof this.options_.rel !== 'undefined') { 27385 playerVars.rel = this.options_.rel; 27386 } 27387 27388 if (typeof this.options_.showinfo !== 'undefined') { 27389 playerVars.showinfo = this.options_.showinfo; 27390 } 27391 27392 if (this.options_.source.src.indexOf('start=') !== -1) { 27393 var srcStartTime = this.options_.source.src.match(/start=([0-9]*)/); 27394 this.options_.start = parseInt(srcStartTime[1]); 27395 } 27396 27397 if (typeof this.options_.start !== 'undefined') { 27398 playerVars.start = this.options_.start; 27399 } 27400 27401 if (typeof this.options_.theme !== 'undefined') { 27402 playerVars.theme = this.options_.theme; 27403 } 27404 27405 // Allow undocumented options to be passed along via customVars 27406 if (typeof this.options_.customVars !== 'undefined') { 27407 var customVars = this.options_.customVars; 27408 Object.keys(customVars).forEach(function(key) { 27409 playerVars[key] = customVars[key]; 27410 }); 27411 } 27412 27413 this.activeVideoId = this.url ? this.url.videoId : null; 27414 this.activeList = playerVars.list; 27415 27416 var playerConfig = { 27417 videoId: this.activeVideoId, 27418 playerVars: playerVars, 27419 events: { 27420 onReady: this.onPlayerReady.bind(this), 27421 onPlaybackQualityChange: this.onPlayerPlaybackQualityChange.bind(this), 27422 onPlaybackRateChange: this.onPlayerPlaybackRateChange.bind(this), 27423 onStateChange: this.onPlayerStateChange.bind(this), 27424 onVolumeChange: this.onPlayerVolumeChange.bind(this), 27425 onError: this.onPlayerError.bind(this) 27426 } 27427 }; 27428 27429 if (typeof this.options_.enablePrivacyEnhancedMode !== 'undefined' && this.options_.enablePrivacyEnhancedMode) { 27430 playerConfig.host = 'https://www.youtube-nocookie.com'; 27431 } 27432 27433 this.ytPlayer = new YT.Player(this.options_.techId, playerConfig); 27434 } 27435 27436 onPlayerReady() { 27437 if (this.options_.muted) { 27438 this.ytPlayer.mute(); 27439 } 27440 27441 var playbackRates = this.ytPlayer.getAvailablePlaybackRates(); 27442 if (playbackRates.length > 1) { 27443 this.featuresPlaybackRate = true; 27444 } 27445 27446 this.playerReady_ = true; 27447 this.triggerReady(); 27448 27449 if (this.playOnReady) { 27450 this.play(); 27451 } else if (this.cueOnReady) { 27452 this.cueVideoById_(this.url.videoId); 27453 this.activeVideoId = this.url.videoId; 27454 } 27455 } 27456 27457 onPlayerPlaybackQualityChange() { 27458 27459 } 27460 27461 onPlayerPlaybackRateChange() { 27462 this.trigger('ratechange'); 27463 } 27464 27465 onPlayerStateChange(e) { 27466 var state = e.data; 27467 27468 if (state === this.lastState || this.errorNumber) { 27469 return; 27470 } 27471 27472 this.lastState = state; 27473 27474 switch (state) { 27475 case -1: 27476 this.trigger('loadstart'); 27477 this.trigger('loadedmetadata'); 27478 this.trigger('durationchange'); 27479 this.trigger('ratechange'); 27480 break; 27481 27482 case YT.PlayerState.ENDED: 27483 this.trigger('ended'); 27484 break; 27485 27486 case YT.PlayerState.PLAYING: 27487 this.trigger('timeupdate'); 27488 this.trigger('durationchange'); 27489 this.trigger('playing'); 27490 this.trigger('play'); 27491 27492 if (this.isSeeking) { 27493 this.onSeeked(); 27494 } 27495 break; 27496 27497 case YT.PlayerState.PAUSED: 27498 this.trigger('canplay'); 27499 if (this.isSeeking) { 27500 this.onSeeked(); 27501 } else { 27502 this.trigger('pause'); 27503 } 27504 break; 27505 27506 case YT.PlayerState.BUFFERING: 27507 this.player_.trigger('timeupdate'); 27508 this.player_.trigger('waiting'); 27509 break; 27510 } 27511 } 27512 27513 onPlayerVolumeChange() { 27514 this.trigger('volumechange'); 27515 } 27516 27517 onPlayerError(e) { 27518 this.errorNumber = e.data; 27519 this.trigger('pause'); 27520 this.trigger('error'); 27521 } 27522 27523 error() { 27524 var code = 1000 + this.errorNumber; // as smaller codes are reserved 27525 switch (this.errorNumber) { 27526 case 5: 27527 return { code: code, message: 'Error while trying to play the video' }; 27528 27529 case 2: 27530 case 100: 27531 return { code: code, message: 'Unable to find the video' }; 27532 27533 case 101: 27534 case 150: 27535 return { 27536 code: code, 27537 message: 'Playback on other Websites has been disabled by the video owner.' 27538 }; 27539 } 27540 27541 return { code: code, message: 'YouTube unknown error (' + this.errorNumber + ')' }; 27542 } 27543 27544 loadVideoById_(id) { 27545 var options = { 27546 videoId: id 27547 }; 27548 if (this.options_.start) { 27549 options.startSeconds = this.options_.start; 27550 } 27551 if (this.options_.end) { 27552 options.endSeconds = this.options_.end; 27553 } 27554 this.ytPlayer.loadVideoById(options); 27555 } 27556 27557 cueVideoById_(id) { 27558 var options = { 27559 videoId: id 27560 }; 27561 if (this.options_.start) { 27562 options.startSeconds = this.options_.start; 27563 } 27564 if (this.options_.end) { 27565 options.endSeconds = this.options_.end; 27566 } 27567 this.ytPlayer.cueVideoById(options); 27568 } 27569 27570 src(src) { 27571 if (src) { 27572 this.setSrc({ src: src }); 27573 } 27574 27575 return this.source; 27576 } 27577 27578 poster() { 27579 // You can't start programmaticlly a video with a mobile 27580 // through the iframe so we hide the poster and the play button (with CSS) 27581 if (_isOnMobile) { 27582 return null; 27583 } 27584 27585 return this.poster_; 27586 } 27587 27588 setPoster(poster) { 27589 this.poster_ = poster; 27590 } 27591 27592 setSrc(source) { 27593 if (!source || !source.src) { 27594 return; 27595 } 27596 27597 delete this.errorNumber; 27598 this.source = source; 27599 this.url = Youtube.parseUrl(source.src); 27600 27601 if (!this.options_.poster) { 27602 if (this.url.videoId) { 27603 // Set the low resolution first 27604 this.poster_ = 'https://img.youtube.com/vi/' + this.url.videoId + '/0.jpg'; 27605 this.trigger('posterchange'); 27606 27607 // Check if their is a high res 27608 this.checkHighResPoster(); 27609 } 27610 } 27611 27612 if (this.options_.autoplay && !_isOnMobile) { 27613 if (this.isReady_) { 27614 this.play(); 27615 } else { 27616 this.playOnReady = true; 27617 } 27618 } else if (this.activeVideoId !== this.url.videoId) { 27619 if (this.isReady_) { 27620 this.cueVideoById_(this.url.videoId); 27621 this.activeVideoId = this.url.videoId; 27622 } else { 27623 this.cueOnReady = true; 27624 } 27625 } 27626 } 27627 27628 autoplay() { 27629 return this.options_.autoplay; 27630 } 27631 27632 setAutoplay(val) { 27633 this.options_.autoplay = val; 27634 } 27635 27636 loop() { 27637 return this.options_.loop; 27638 } 27639 27640 setLoop(val) { 27641 this.options_.loop = val; 27642 } 27643 27644 play() { 27645 if (!this.url || !this.url.videoId) { 27646 return; 27647 } 27648 27649 this.wasPausedBeforeSeek = false; 27650 27651 if (this.isReady_) { 27652 if (this.url.listId) { 27653 if (this.activeList === this.url.listId) { 27654 this.ytPlayer.playVideo(); 27655 } else { 27656 this.ytPlayer.loadPlaylist(this.url.listId); 27657 this.activeList = this.url.listId; 27658 } 27659 } 27660 27661 if (this.activeVideoId === this.url.videoId) { 27662 this.ytPlayer.playVideo(); 27663 } else { 27664 this.loadVideoById_(this.url.videoId); 27665 this.activeVideoId = this.url.videoId; 27666 } 27667 } else { 27668 this.trigger('waiting'); 27669 this.playOnReady = true; 27670 } 27671 } 27672 27673 pause() { 27674 if (this.ytPlayer) { 27675 this.ytPlayer.pauseVideo(); 27676 } 27677 } 27678 27679 paused() { 27680 return (this.ytPlayer) ? 27681 (this.lastState !== YT.PlayerState.PLAYING && this.lastState !== YT.PlayerState.BUFFERING) 27682 : true; 27683 } 27684 27685 currentTime() { 27686 return this.ytPlayer ? this.ytPlayer.getCurrentTime() : 0; 27687 } 27688 27689 setCurrentTime(seconds) { 27690 if (this.lastState === YT.PlayerState.PAUSED) { 27691 this.timeBeforeSeek = this.currentTime(); 27692 } 27693 27694 if (!this.isSeeking) { 27695 this.wasPausedBeforeSeek = this.paused(); 27696 } 27697 27698 this.ytPlayer.seekTo(seconds, true); 27699 this.trigger('timeupdate'); 27700 this.trigger('seeking'); 27701 this.isSeeking = true; 27702 27703 // A seek event during pause does not return an event to trigger a seeked event, 27704 // so run an interval timer to look for the currentTime to change 27705 if (this.lastState === YT.PlayerState.PAUSED && this.timeBeforeSeek !== seconds) { 27706 clearInterval(this.checkSeekedInPauseInterval); 27707 this.checkSeekedInPauseInterval = setInterval(function() { 27708 if (this.lastState !== YT.PlayerState.PAUSED || !this.isSeeking) { 27709 // If something changed while we were waiting for the currentTime to change, 27710 // clear the interval timer 27711 clearInterval(this.checkSeekedInPauseInterval); 27712 } else if (this.currentTime() !== this.timeBeforeSeek) { 27713 this.trigger('timeupdate'); 27714 this.onSeeked(); 27715 } 27716 }.bind(this), 250); 27717 } 27718 } 27719 27720 seeking() { 27721 return this.isSeeking; 27722 } 27723 27724 seekable() { 27725 if(!this.ytPlayer) { 27726 return videojs.createTimeRange(); 27727 } 27728 27729 return videojs.createTimeRange(0, this.ytPlayer.getDuration()); 27730 } 27731 27732 onSeeked() { 27733 clearInterval(this.checkSeekedInPauseInterval); 27734 this.isSeeking = false; 27735 27736 if (this.wasPausedBeforeSeek) { 27737 this.pause(); 27738 } 27739 27740 this.trigger('seeked'); 27741 } 27742 27743 playbackRate() { 27744 return this.ytPlayer ? this.ytPlayer.getPlaybackRate() : 1; 27745 } 27746 27747 setPlaybackRate(suggestedRate) { 27748 if (!this.ytPlayer) { 27749 return; 27750 } 27751 27752 this.ytPlayer.setPlaybackRate(suggestedRate); 27753 } 27754 27755 duration() { 27756 return this.ytPlayer ? this.ytPlayer.getDuration() : 0; 27757 } 27758 27759 currentSrc() { 27760 return this.source && this.source.src; 27761 } 27762 27763 ended() { 27764 return this.ytPlayer ? (this.lastState === YT.PlayerState.ENDED) : false; 27765 } 27766 27767 volume() { 27768 return this.ytPlayer ? this.ytPlayer.getVolume() / 100.0 : 1; 27769 } 27770 27771 setVolume(percentAsDecimal) { 27772 if (!this.ytPlayer) { 27773 return; 27774 } 27775 27776 this.ytPlayer.setVolume(percentAsDecimal * 100.0); 27777 } 27778 27779 muted() { 27780 return this.ytPlayer ? this.ytPlayer.isMuted() : false; 27781 } 27782 27783 setMuted(mute) { 27784 if (!this.ytPlayer) { 27785 return; 27786 } 27787 else{ 27788 this.muted(true); 27789 } 27790 27791 if (mute) { 27792 this.ytPlayer.mute(); 27793 } else { 27794 this.ytPlayer.unMute(); 27795 } 27796 this.setTimeout( function(){ 27797 this.trigger('volumechange'); 27798 }, 50); 27799 } 27800 27801 buffered() { 27802 if(!this.ytPlayer || !this.ytPlayer.getVideoLoadedFraction) { 27803 return videojs.createTimeRange(); 27804 } 27805 27806 var bufferedEnd = this.ytPlayer.getVideoLoadedFraction() * this.ytPlayer.getDuration(); 27807 27808 return videojs.createTimeRange(0, bufferedEnd); 27809 } 27810 27811 // TODO: Can we really do something with this on YouTUbe? 27812 preload() {} 27813 load() {} 27814 reset() {} 27815 networkState() { 27816 if (!this.ytPlayer) { 27817 return 0; //NETWORK_EMPTY 27818 } 27819 switch (this.ytPlayer.getPlayerState()) { 27820 case -1: //unstarted 27821 return 0; //NETWORK_EMPTY 27822 case 3: //buffering 27823 return 2; //NETWORK_LOADING 27824 default: 27825 return 1; //NETWORK_IDLE 27826 } 27827 } 27828 readyState() { 27829 if (!this.ytPlayer) { 27830 return 0; //HAVE_NOTHING 27831 } 27832 switch (this.ytPlayer.getPlayerState()) { 27833 case -1: //unstarted 27834 return 0; //HAVE_NOTHING 27835 case 5: //video cued 27836 return 1; //HAVE_METADATA 27837 case 3: //buffering 27838 return 2; //HAVE_CURRENT_DATA 27839 default: 27840 return 4; //HAVE_ENOUGH_DATA 27841 } 27842 } 27843 27844 supportsFullScreen() { 27845 return document.fullscreenEnabled || 27846 document.webkitFullscreenEnabled || 27847 document.mozFullScreenEnabled || 27848 document.msFullscreenEnabled; 27849 } 27850 27851 // Tries to get the highest resolution thumbnail available for the video 27852 checkHighResPoster(){ 27853 var uri = 'https://img.youtube.com/vi/' + this.url.videoId + '/maxresdefault.jpg'; 27854 27855 try { 27856 var image = new Image(); 27857 image.onload = function(){ 27858 // Onload may still be called if YouTube returns the 120x90 error thumbnail 27859 if('naturalHeight' in image){ 27860 if (image.naturalHeight <= 90 || image.naturalWidth <= 120) { 27861 return; 27862 } 27863 } else if(image.height <= 90 || image.width <= 120) { 27864 return; 27865 } 27866 27867 this.poster_ = uri; 27868 this.trigger('posterchange'); 27869 }.bind(this); 27870 image.onerror = function(){}; 27871 image.src = uri; 27872 } 27873 catch(e){} 27874 } 27875 } 27876 27877 Youtube.isSupported = function() { 27878 return true; 27879 }; 27880 27881 Youtube.canPlaySource = function(e) { 27882 return Youtube.canPlayType(e.type); 27883 }; 27884 27885 Youtube.canPlayType = function(e) { 27886 return (e === 'video/youtube'); 27887 }; 27888 27889 Youtube.parseUrl = function(url) { 27890 var result = { 27891 videoId: null 27892 }; 27893 27894 var regex = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/; 27895 var match = url.match(regex); 27896 27897 if (match && match[2].length === 11) { 27898 result.videoId = match[2]; 27899 } 27900 27901 var regPlaylist = /[?&]list=([^#\&\?]+)/; 27902 match = url.match(regPlaylist); 27903 27904 if(match && match[1]) { 27905 result.listId = match[1]; 27906 } 27907 27908 return result; 27909 }; 27910 27911 function apiLoaded() { 27912 YT.ready(function() { 27913 Youtube.isApiReady = true; 27914 27915 for (var i = 0; i < Youtube.apiReadyQueue.length; ++i) { 27916 Youtube.apiReadyQueue[i].initYTPlayer(); 27917 } 27918 }); 27919 } 27920 27921 function loadScript(src, callback) { 27922 var loaded = false; 27923 var tag = document.createElement('script'); 27924 var firstScriptTag = document.getElementsByTagName('script')[0]; 27925 if (!firstScriptTag) { 27926 // when loaded in jest without jsdom setup it doesn't get any element. 27927 // In jest it doesn't really make sense to do anything, because no one is watching youtube in jest 27928 return; 27929 } 27930 firstScriptTag.parentNode.insertBefore(tag, firstScriptTag); 27931 tag.onload = function () { 27932 if (!loaded) { 27933 loaded = true; 27934 callback(); 27935 } 27936 }; 27937 tag.onreadystatechange = function () { 27938 if (!loaded && (this.readyState === 'complete' || this.readyState === 'loaded')) { 27939 loaded = true; 27940 callback(); 27941 } 27942 }; 27943 tag.src = src; 27944 } 27945 27946 function injectCss() { 27947 var css = // iframe blocker to catch mouse events 27948 '.vjs-youtube .vjs-iframe-blocker { display: none; }' + 27949 '.vjs-youtube.vjs-user-inactive .vjs-iframe-blocker { display: block; }' + 27950 '.vjs-youtube .vjs-poster { background-size: cover; }' + 27951 '.vjs-youtube-mobile .vjs-big-play-button { display: none; }'; 27952 27953 var head = document.head || document.getElementsByTagName('head')[0]; 27954 27955 var style = document.createElement('style'); 27956 style.type = 'text/css'; 27957 27958 if (style.styleSheet){ 27959 style.styleSheet.cssText = css; 27960 } else { 27961 style.appendChild(document.createTextNode(css)); 27962 } 27963 27964 head.appendChild(style); 27965 } 27966 27967 Youtube.apiReadyQueue = []; 27968 27969 if (typeof document !== 'undefined'){ 27970 loadScript('https://www.youtube.com/iframe_api', apiLoaded); 27971 injectCss(); 27972 } 27973 27974 // Older versions of VJS5 doesn't have the registerTech function 27975 if (typeof videojs.registerTech !== 'undefined') { 27976 videojs.registerTech('Youtube', Youtube); 27977 } else { 27978 videojs.registerComponent('Youtube', Youtube); 27979 } 27980 }));
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.