vendor: 15,376 bytes, lines 1-610
1/** 2 * @license 3 * Video.js 6.2.8 <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.2.8"; 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); 133var CHROME_VERSION = function () { 134 var match = USER_AGENT.match(/Chrome\/(\d+)/); 135 136 if (match && match[1]) { 137 return parseFloat(match[1]); 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; 156 157var TOUCH_ENABLED = isReal() && ('ontouchstart' in window_1 || 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 = 'all'; 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 // 455 // Was setting these once outside of this function, but containing them 456 // in the function makes it easier to test cases where console doesn't exist 457 // when the module is executed. 458 var fn = window_1.console && window_1.console[type]; 459 460 // Bail out if there's no console or if this type is not allowed by the 461 // current logging level. 462 if (!fn || !lvl || !lvlRegExp.test(type)) { 463 return; 464 } 465 466 // IEs previous to 11 log objects uselessly as "[object Object]"; so, JSONify 467 // objects and arrays for those less-capable browsers. 468 if (stringify) { 469 args = args.map(function (a) { 470 if (isObject(a) || Array.isArray(a)) { 471 try { 472 return JSON.stringify(a); 473 } catch (x) { 474 return String(a); 475 } 476 } 477 478 // Cast to string before joining, so we get null and undefined explicitly 479 // included in output (as we would in a modern console). 480 return String(a); 481 }).join(' '); 482 } 483 484 // Old IE versions do not allow .apply() for console methods (they are 485 // reported as objects rather than functions). 486 if (!fn.apply) { 487 fn(args); 488 } else { 489 fn[Array.isArray(args) ? 'apply' : 'call'](window_1.console, args); 490 } 491}; 492 493/** 494 * Logs plain debug messages. Similar to `console.log`. 495 * 496 * @class 497 * @param {Mixed[]} args 498 * One or more messages or objects that should be logged. 499 */ 500log = function log() { 501 for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { 502 args[_key] = arguments[_key]; 503 } 504 505 logByType('log', args); 506}; 507 508/** 509 * Enumeration of available logging levels, where the keys are the level names 510 * and the values are `|`-separated strings containing logging methods allowed 511 * in that logging level. These strings are used to create a regular expression 512 * matching the function name being called. 513 * 514 * Levels provided by video.js are: 515 * 516 * - `off`: Matches no calls. Any value that can be cast to `false` will have 517 * this effect. The most restrictive. 518 * - `all` (default): Matches only Video.js-provided functions (`log`, 519 * `log.warn`, and `log.error`). 520 * - `warn`: Matches `log.warn` and `log.error` calls. 521 * - `error`: Matches only `log.error` calls. 522 * 523 * @type {Object} 524 */ 525log.levels = { 526 all: 'log|warn|error', 527 error: 'error', 528 off: '', 529 warn: 'warn|error', 530 DEFAULT: level 531}; 532 533/** 534 * Get or set the current logging level. If a string matching a key from 535 * {@link log.levels} is provided, acts as a setter. Regardless of argument, 536 * returns the current logging level. 537 * 538 * @param {string} [lvl] 539 * Pass to set a new logging level. 540 * 541 * @return {string} 542 * The current logging level. 543 */ 544log.level = function (lvl) { 545 if (typeof lvl === 'string') { 546 if (!log.levels.hasOwnProperty(lvl)) { 547 throw new Error('"' + lvl + '" in not a valid log level'); 548 } 549 level = lvl; 550 } 551 return level; 552}; 553 554/** 555 * Returns an array containing everything that has been logged to the history. 556 * 557 * This array is a shallow clone of the internal history record. However, its 558 * contents are _not_ cloned; so, mutating objects inside this array will 559 * mutate them in history. 560 * 561 * @return {Array} 562 */ 563log.history = function () { 564 return history ? [].concat(history) : []; 565}; 566 567/** 568 * Clears the internal history tracking, but does not prevent further history 569 * tracking. 570 */ 571log.history.clear = function () { 572 if (history) { 573 history.length = 0; 574 } 575}; 576 577/** 578 * Disable history tracking if it is currently enabled. 579 */ 580log.history.disable = function () { 581 if (history !== null) { 582 history.length = 0; 583 history = null; 584 } 585}; 586 587/** 588 * Enable history tracking if it is currently disabled. 589 */ 590log.history.enable = function () { 591 if (history === null) { 592 history = []; 593 } 594}; 595 596/** 597 * Logs error messages. Similar to `console.error`. 598 * 599 * @param {Mixed[]} args 600 * One or more messages or objects that should be logged as an error 601 */ 602log.error = function () { 603 for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { 604 args[_key2] = arguments[_key2]; 605 } 606 607 return logByType('error', args); 608}; 609 610/**
611 * Logs warning messages. Similar to `console.warn`. 612 * 613 * @param {Mixed[]} args 614 * One or more messages or objects that should be logged as a warning. 615 */ 616log.warn = function () { 617 for (var _len3 = arguments.length, args = Array(_len3), _key3 = 0; _key3 < _len3; _key3++) { 618 args[_key3] = arguments[_key3]; 619 } 620 621 return logByType('warn', args); 622}; 623 624var log$1 = log; 625 626function clean (s) { 627 return s.replace(/\n\r?\s*/g, '') 628} 629 630 631var tsml = function tsml (sa) { 632 var s = '' 633 , i = 0; 634 635 for (; i < arguments.length; i++) 636 s += clean(sa[i]) + (arguments[i + 1] || ''); 637 638 return s 639}; 640 641/** 642 * @file computed-style.js 643 * @module computed-style 644 */ 645/** 646 * A safe getComputedStyle with an IE8 fallback. 647 * 648 * This is needed because in Firefox, if the player is loaded in an iframe with 649 * `display:none`, then `getComputedStyle` returns `null`, so, we do a null-check to 650 * make sure that the player doesn't break in these cases. 651 * 652 * @param {Element} el 653 * The element you want the computed style of 654 * 655 * @param {string} prop 656 * The property name you want 657 * 658 * @see https://bugzilla.mozilla.org/show_bug.cgi?id=548397 659 * 660 * @static 661 * @const 662 */ 663function computedStyle(el, prop) { 664 if (!el || !prop) { 665 return ''; 666 } 667 668 if (typeof window_1.getComputedStyle === 'function') { 669 var cs = window_1.getComputedStyle(el); 670 671 return cs ? cs[prop] : ''; 672 } 673 674 return el.currentStyle[prop] || ''; 675} 676 677var _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 ', '.']); 678 679/** 680 * @file dom.js 681 * @module dom 682 */ 683/** 684 * Detect if a value is a string with any non-whitespace characters. 685 * 686 * @param {string} str 687 * The string to check 688 * 689 * @return {boolean} 690 * - True if the string is non-blank 691 * - False otherwise 692 * 693 */ 694function isNonBlankString(str) { 695 return typeof str === 'string' && /\S/.test(str); 696} 697 698/** 699 * Throws an error if the passed string has whitespace. This is used by 700 * class methods to be relatively consistent with the classList API. 701 * 702 * @param {string} str 703 * The string to check for whitespace. 704 * 705 * @throws {Error} 706 * Throws an error if there is whitespace in the string. 707 * 708 */ 709function throwIfWhitespace(str) { 710 if (/\s/.test(str)) { 711 throw new Error('class has illegal whitespace characters'); 712 } 713} 714 715/** 716 * Produce a regular expression for matching a className within an elements className. 717 * 718 * @param {string} className 719 * The className to generate the RegExp for. 720 * 721 * @return {RegExp} 722 * The RegExp that will check for a specific `className` in an elements 723 * className. 724 */ 725function classRegExp(className) { 726 return new RegExp('(^|\\s)' + className + '($|\\s)'); 727} 728 729/** 730 * Whether the current DOM interface appears to be real. 731 * 732 * @return {Boolean} 733 */ 734function isReal() { 735 return ( 736 737 // Both document and window will never be undefined thanks to `global`. 738 document_1 === window_1.document && 739 740 // In IE < 9, DOM methods return "object" as their type, so all we can 741 // confidently check is that it exists. 742 typeof document_1.createElement !== 'undefined' 743 ); 744} 745 746/** 747 * Determines, via duck typing, whether or not a value is a DOM element. 748 * 749 * @param {Mixed} value 750 * The thing to check 751 * 752 * @return {boolean} 753 * - True if it is a DOM element 754 * - False otherwise 755 */ 756function isEl(value) { 757 return isObject(value) && value.nodeType === 1; 758} 759 760/** 761 * Creates functions to query the DOM using a given method. 762 * 763 * @param {string} method 764 * The method to create the query with. 765 * 766 * @return {Function} 767 * The query method 768 */ 769function createQuerier(method) { 770 return function (selector, context) { 771 if (!isNonBlankString(selector)) { 772 return document_1[method](null); 773 } 774 if (isNonBlankString(context)) { 775 context = document_1.querySelector(context); 776 } 777 778 var ctx = isEl(context) ? context : document_1; 779 780 return ctx[method] && ctx[method](selector); 781 }; 782} 783 784/** 785 * Creates an element and applies properties. 786 * 787 * @param {string} [tagName='div'] 788 * Name of tag to be created. 789 * 790 * @param {Object} [properties={}] 791 * Element properties to be applied. 792 * 793 * @param {Object} [attributes={}] 794 * Element attributes to be applied. 795 * 796 * @param {String|Element|TextNode|Array|Function} [content] 797 * Contents for the element (see: {@link dom:normalizeContent}) 798 * 799 * @return {Element} 800 * The element that was created. 801 */ 802function createEl() { 803 var tagName = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'div'; 804 var properties = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 805 var attributes = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; 806 var content = arguments[3]; 807 808 var el = document_1.createElement(tagName); 809
vendor: 4,609 bytes, lines 810-986
810 Object.getOwnPropertyNames(properties).forEach(function (propName) { 811 var val = properties[propName]; 812 813 // See #2176 814 // We originally were accepting both properties and attributes in the 815 // same object, but that doesn't work so well. 816 if (propName.indexOf('aria-') !== -1 || propName === 'role' || propName === 'type') { 817 log$1.warn(tsml(_templateObject, propName, val)); 818 el.setAttribute(propName, val); 819 820 // Handle textContent since it's not supported everywhere and we have a 821 // method for it. 822 } else if (propName === 'textContent') { 823 textContent(el, val); 824 } else { 825 el[propName] = val; 826 } 827 }); 828 829 Object.getOwnPropertyNames(attributes).forEach(function (attrName) { 830 el.setAttribute(attrName, attributes[attrName]); 831 }); 832 833 if (content) { 834 appendContent(el, content); 835 } 836 837 return el; 838} 839 840/** 841 * Injects text into an element, replacing any existing contents entirely. 842 * 843 * @param {Element} el 844 * The element to add text content into 845 * 846 * @param {string} text 847 * The text content to add. 848 * 849 * @return {Element} 850 * The element with added text content. 851 */ 852function textContent(el, text) { 853 if (typeof el.textContent === 'undefined') { 854 el.innerText = text; 855 } else { 856 el.textContent = text; 857 } 858 return el; 859} 860 861/** 862 * Insert an element as the first child node of another 863 * 864 * @param {Element} child 865 * Element to insert 866 * 867 * @param {Element} parent 868 * Element to insert child into 869 */ 870function prependTo(child, parent) { 871 if (parent.firstChild) { 872 parent.insertBefore(child, parent.firstChild); 873 } else { 874 parent.appendChild(child); 875 } 876} 877 878/** 879 * Check if an element has a CSS class 880 * 881 * @param {Element} element 882 * Element to check 883 * 884 * @param {string} classToCheck 885 * Class name to check for 886 * 887 * @return {boolean} 888 * - True if the element had the class 889 * - False otherwise. 890 * 891 * @throws {Error} 892 * Throws an error if `classToCheck` has white space. 893 */ 894function hasClass(element, classToCheck) { 895 throwIfWhitespace(classToCheck); 896 if (element.classList) { 897 return element.classList.contains(classToCheck); 898 } 899 return classRegExp(classToCheck).test(element.className); 900} 901 902/** 903 * Add a CSS class name to an element 904 * 905 * @param {Element} element 906 * Element to add class name to. 907 * 908 * @param {string} classToAdd 909 * Class name to add. 910 * 911 * @return {Element} 912 * The dom element with the added class name. 913 */ 914function addClass(element, classToAdd) { 915 if (element.classList) { 916 element.classList.add(classToAdd); 917 918 // Don't need to `throwIfWhitespace` here because `hasElClass` will do it 919 // in the case of classList not being supported. 920 } else if (!hasClass(element, classToAdd)) { 921 element.className = (element.className + ' ' + classToAdd).trim(); 922 } 923 924 return element; 925} 926 927/** 928 * Remove a CSS class name from an element 929 * 930 * @param {Element} element 931 * Element to remove a class name from. 932 * 933 * @param {string} classToRemove 934 * Class name to remove 935 * 936 * @return {Element} 937 * The dom element with class name removed. 938 */ 939function removeClass(element, classToRemove) { 940 if (element.classList) { 941 element.classList.remove(classToRemove); 942 } else { 943 throwIfWhitespace(classToRemove); 944 element.className = element.className.split(/\s+/).filter(function (c) { 945 return c !== classToRemove; 946 }).join(' '); 947 } 948 949 return element; 950} 951 952/** 953 * The callback definition for toggleElClass. 954 * 955 * @callback Dom~PredicateCallback 956 * @param {Element} element 957 * The DOM element of the Component. 958 * 959 * @param {string} classToToggle 960 * The `className` that wants to be toggled 961 * 962 * @return {boolean|undefined} 963 * - If true the `classToToggle` will get added to `element`. 964 * - If false the `classToToggle` will get removed from `element`. 965 * - If undefined this callback will be ignored 966 */ 967 968/** 969 * Adds or removes a CSS class name on an element depending on an optional 970 * condition or the presence/absence of the class name. 971 * 972 * @param {Element} element 973 * The element to toggle a class name on. 974 * 975 * @param {string} classToToggle 976 * The class that should be toggled 977 * 978 * @param {boolean|PredicateCallback} [predicate] 979 * See the return value for {@link Dom~PredicateCallback} 980 * 981 * @return {Element} 982 * The element with a class that has been toggled. 983 */ 984function toggleClass(element, classToToggle, predicate) { 985 986 // This CANNOT use `classList` internally because IE does not support the
vendor: 5,424 bytes, lines 987-1195
987 // second parameter to the `classList.toggle()` method! Which is fine because 988 // `classList` will be used by the add/remove functions. 989 var has = hasClass(element, classToToggle); 990 991 if (typeof predicate === 'function') { 992 predicate = predicate(element, classToToggle); 993 } 994 995 if (typeof predicate !== 'boolean') { 996 predicate = !has; 997 } 998 999 // If the necessary class operation matches the current state of the 1000 // element, no action is required. 1001 if (predicate === has) { 1002 return; 1003 } 1004 1005 if (predicate) { 1006 addClass(element, classToToggle); 1007 } else { 1008 removeClass(element, classToToggle); 1009 } 1010 1011 return element; 1012} 1013 1014/** 1015 * Apply attributes to an HTML element. 1016 * 1017 * @param {Element} el 1018 * Element to add attributes to. 1019 * 1020 * @param {Object} [attributes] 1021 * Attributes to be applied. 1022 */ 1023function setAttributes(el, attributes) { 1024 Object.getOwnPropertyNames(attributes).forEach(function (attrName) { 1025 var attrValue = attributes[attrName]; 1026 1027 if (attrValue === null || typeof attrValue === 'undefined' || attrValue === false) { 1028 el.removeAttribute(attrName); 1029 } else { 1030 el.setAttribute(attrName, attrValue === true ? '' : attrValue); 1031 } 1032 }); 1033} 1034 1035/** 1036 * Get an element's attribute values, as defined on the HTML tag 1037 * Attributes are not the same as properties. They're defined on the tag 1038 * or with setAttribute (which shouldn't be used with HTML) 1039 * This will return true or false for boolean attributes. 1040 * 1041 * @param {Element} tag 1042 * Element from which to get tag attributes. 1043 * 1044 * @return {Object} 1045 * All attributes of the element. 1046 */ 1047function getAttributes(tag) { 1048 var obj = {}; 1049 1050 // known boolean attributes 1051 // we can check for matching boolean properties, but older browsers 1052 // won't know about HTML5 boolean attributes that we still read from 1053 var knownBooleans = ',' + 'autoplay,controls,playsinline,loop,muted,default,defaultMuted' + ','; 1054 1055 if (tag && tag.attributes && tag.attributes.length > 0) { 1056 var attrs = tag.attributes; 1057 1058 for (var i = attrs.length - 1; i >= 0; i--) { 1059 var attrName = attrs[i].name; 1060 var attrVal = attrs[i].value; 1061 1062 // check for known booleans 1063 // the matching element property will return a value for typeof 1064 if (typeof tag[attrName] === 'boolean' || knownBooleans.indexOf(',' + attrName + ',') !== -1) { 1065 // the value of an included boolean attribute is typically an empty 1066 // string ('') which would equal false if we just check for a false value. 1067 // we also don't want support bad code like autoplay='false' 1068 attrVal = attrVal !== null ? true : false; 1069 } 1070 1071 obj[attrName] = attrVal; 1072 } 1073 } 1074 1075 return obj; 1076} 1077 1078/** 1079 * Get the value of an element's attribute 1080 * 1081 * @param {Element} el 1082 * A DOM element 1083 * 1084 * @param {string} attribute 1085 * Attribute to get the value of 1086 * 1087 * @return {string} 1088 * value of the attribute 1089 */ 1090function getAttribute(el, attribute) { 1091 return el.getAttribute(attribute); 1092} 1093 1094/** 1095 * Set the value of an element's attribute 1096 * 1097 * @param {Element} el 1098 * A DOM element 1099 * 1100 * @param {string} attribute 1101 * Attribute to set 1102 * 1103 * @param {string} value 1104 * Value to set the attribute to 1105 */ 1106function setAttribute(el, attribute, value) { 1107 el.setAttribute(attribute, value); 1108} 1109 1110/** 1111 * Remove an element's attribute 1112 * 1113 * @param {Element} el 1114 * A DOM element 1115 * 1116 * @param {string} attribute 1117 * Attribute to remove 1118 */ 1119function removeAttribute(el, attribute) { 1120 el.removeAttribute(attribute); 1121} 1122 1123/** 1124 * Attempt to block the ability to select text while dragging controls 1125 */ 1126function blockTextSelection() { 1127 document_1.body.focus(); 1128 document_1.onselectstart = function () { 1129 return false; 1130 }; 1131} 1132 1133/** 1134 * Turn off text selection blocking 1135 */ 1136function unblockTextSelection() { 1137 document_1.onselectstart = function () { 1138 return true; 1139 }; 1140} 1141 1142/** 1143 * Identical to the native `getBoundingClientRect` function, but ensures that 1144 * the method is supported at all (it is in all browsers we claim to support) 1145 * and that the element is in the DOM before continuing. 1146 * 1147 * This wrapper function also shims properties which are not provided by some 1148 * older browsers (namely, IE8). 1149 * 1150 * Additionally, some browsers do not support adding properties to a 1151 * `ClientRect`/`DOMRect` object; so, we shallow-copy it with the standard 1152 * properties (except `x` and `y` which are not widely supported). This helps 1153 * avoid implementations where keys are non-enumerable. 1154 * 1155 * @param {Element} el 1156 * Element whose `ClientRect` we want to calculate. 1157 * 1158 * @return {Object|undefined} 1159 * Always returns a plain 1160 */ 1161function getBoundingClientRect(el) { 1162 if (el && el.getBoundingClientRect && el.parentNode) { 1163 var rect = el.getBoundingClientRect(); 1164 var result = {}; 1165 1166 ['bottom', 'height', 'left', 'right', 'top', 'width'].forEach(function (k) { 1167 if (rect[k] !== undefined) { 1168 result[k] = rect[k]; 1169 } 1170 }); 1171 1172 if (!result.height) { 1173 result.height = parseFloat(computedStyle(el, 'height')); 1174 } 1175 1176 if (!result.width) { 1177 result.width = parseFloat(computedStyle(el, 'width')); 1178 } 1179 1180 return result; 1181 } 1182} 1183 1184/** 1185 * The postion of a DOM element on the page. 1186 * 1187 * @typedef {Object} module:dom~Position 1188 * 1189 * @property {number} left 1190 * Pixels to the left 1191 * 1192 * @property {number} top 1193 * Pixels on top 1194 */ 1195
vendor: 18,409 bytes, lines 1196-1893
1196/** 1197 * Offset Left. 1198 * getBoundingClientRect technique from 1199 * John Resig 1200 * 1201 * @see http://ejohn.org/blog/getboundingclientrect-is-awesome/ 1202 * 1203 * @param {Element} el 1204 * Element from which to get offset 1205 * 1206 * @return {module:dom~Position} 1207 * The position of the element that was passed in. 1208 */ 1209function findPosition(el) { 1210 var box = void 0; 1211 1212 if (el.getBoundingClientRect && el.parentNode) { 1213 box = el.getBoundingClientRect(); 1214 } 1215 1216 if (!box) { 1217 return { 1218 left: 0, 1219 top: 0 1220 }; 1221 } 1222 1223 var docEl = document_1.documentElement; 1224 var body = document_1.body; 1225 1226 var clientLeft = docEl.clientLeft || body.clientLeft || 0; 1227 var scrollLeft = window_1.pageXOffset || body.scrollLeft; 1228 var left = box.left + scrollLeft - clientLeft; 1229 1230 var clientTop = docEl.clientTop || body.clientTop || 0; 1231 var scrollTop = window_1.pageYOffset || body.scrollTop; 1232 var top = box.top + scrollTop - clientTop; 1233 1234 // Android sometimes returns slightly off decimal values, so need to round 1235 return { 1236 left: Math.round(left), 1237 top: Math.round(top) 1238 }; 1239} 1240 1241/** 1242 * x and y coordinates for a dom element or mouse pointer 1243 * 1244 * @typedef {Object} Dom~Coordinates 1245 * 1246 * @property {number} x 1247 * x coordinate in pixels 1248 * 1249 * @property {number} y 1250 * y coordinate in pixels 1251 */ 1252 1253/** 1254 * Get pointer position in element 1255 * Returns an object with x and y coordinates. 1256 * The base on the coordinates are the bottom left of the element. 1257 * 1258 * @param {Element} el 1259 * Element on which to get the pointer position on 1260 * 1261 * @param {EventTarget~Event} event 1262 * Event object 1263 * 1264 * @return {Dom~Coordinates} 1265 * A Coordinates object corresponding to the mouse position. 1266 * 1267 */ 1268function getPointerPosition(el, event) { 1269 var position = {}; 1270 var box = findPosition(el); 1271 var boxW = el.offsetWidth; 1272 var boxH = el.offsetHeight; 1273 1274 var boxY = box.top; 1275 var boxX = box.left; 1276 var pageY = event.pageY; 1277 var pageX = event.pageX; 1278 1279 if (event.changedTouches) { 1280 pageX = event.changedTouches[0].pageX; 1281 pageY = event.changedTouches[0].pageY; 1282 } 1283 1284 position.y = Math.max(0, Math.min(1, (boxY - pageY + boxH) / boxH)); 1285 position.x = Math.max(0, Math.min(1, (pageX - boxX) / boxW)); 1286 1287 return position; 1288} 1289 1290/** 1291 * Determines, via duck typing, whether or not a value is a text node. 1292 * 1293 * @param {Mixed} value 1294 * Check if this value is a text node. 1295 * 1296 * @return {boolean} 1297 * - True if it is a text node 1298 * - False otherwise 1299 */ 1300function isTextNode(value) { 1301 return isObject(value) && value.nodeType === 3; 1302} 1303 1304/** 1305 * Empties the contents of an element. 1306 * 1307 * @param {Element} el 1308 * The element to empty children from 1309 * 1310 * @return {Element} 1311 * The element with no children 1312 */ 1313function emptyEl(el) { 1314 while (el.firstChild) { 1315 el.removeChild(el.firstChild); 1316 } 1317 return el; 1318} 1319 1320/** 1321 * Normalizes content for eventual insertion into the DOM. 1322 * 1323 * This allows a wide range of content definition methods, but protects 1324 * from falling into the trap of simply writing to `innerHTML`, which is 1325 * an XSS concern. 1326 * 1327 * The content for an element can be passed in multiple types and 1328 * combinations, whose behavior is as follows: 1329 * 1330 * @param {String|Element|TextNode|Array|Function} content 1331 * - String: Normalized into a text node. 1332 * - Element/TextNode: Passed through. 1333 * - Array: A one-dimensional array of strings, elements, nodes, or functions 1334 * (which return single strings, elements, or nodes). 1335 * - Function: If the sole argument, is expected to produce a string, element, 1336 * node, or array as defined above. 1337 * 1338 * @return {Array} 1339 * All of the content that was passed in normalized. 1340 */ 1341function normalizeContent(content) { 1342 1343 // First, invoke content if it is a function. If it produces an array, 1344 // that needs to happen before normalization. 1345 if (typeof content === 'function') { 1346 content = content(); 1347 } 1348 1349 // Next up, normalize to an array, so one or many items can be normalized, 1350 // filtered, and returned. 1351 return (Array.isArray(content) ? content : [content]).map(function (value) { 1352 1353 // First, invoke value if it is a function to produce a new value, 1354 // which will be subsequently normalized to a Node of some kind. 1355 if (typeof value === 'function') { 1356 value = value(); 1357 } 1358 1359 if (isEl(value) || isTextNode(value)) { 1360 return value; 1361 } 1362 1363 if (typeof value === 'string' && /\S/.test(value)) { 1364 return document_1.createTextNode(value); 1365 } 1366 }).filter(function (value) { 1367 return value; 1368 }); 1369} 1370 1371/** 1372 * Normalizes and appends content to an element. 1373 * 1374 * @param {Element} el 1375 * Element to append normalized content to. 1376 * 1377 * 1378 * @param {String|Element|TextNode|Array|Function} content 1379 * See the `content` argument of {@link dom:normalizeContent} 1380 * 1381 * @return {Element} 1382 * The element with appended normalized content. 1383 */ 1384function appendContent(el, content) { 1385 normalizeContent(content).forEach(function (node) { 1386 return el.appendChild(node); 1387 }); 1388 return el; 1389} 1390 1391/** 1392 * Normalizes and inserts content into an element; this is identical to 1393 * `appendContent()`, except it empties the element first. 1394 * 1395 * @param {Element} el 1396 * Element to insert normalized content into. 1397 * 1398 * @param {String|Element|TextNode|Array|Function} content 1399 * See the `content` argument of {@link dom:normalizeContent} 1400 * 1401 * @return {Element} 1402 * The element with inserted normalized content. 1403 * 1404 */ 1405function insertContent(el, content) { 1406 return appendContent(emptyEl(el), content); 1407} 1408 1409/** 1410 * Finds a single DOM element matching `selector` within the optional 1411 * `context` of another DOM element (defaulting to `document`). 1412 * 1413 * @param {string} selector 1414 * A valid CSS selector, which will be passed to `querySelector`. 1415 * 1416 * @param {Element|String} [context=document] 1417 * A DOM element within which to query. Can also be a selector 1418 * string in which case the first matching element will be used 1419 * as context. If missing (or no element matches selector), falls 1420 * back to `document`. 1421 * 1422 * @return {Element|null} 1423 * The element that was found or null. 1424 */ 1425var $ = createQuerier('querySelector'); 1426 1427/** 1428 * Finds a all DOM elements matching `selector` within the optional 1429 * `context` of another DOM element (defaulting to `document`). 1430 * 1431 * @param {string} selector 1432 * A valid CSS selector, which will be passed to `querySelectorAll`. 1433 * 1434 * @param {Element|String} [context=document] 1435 * A DOM element within which to query. Can also be a selector 1436 * string in which case the first matching element will be used 1437 * as context. If missing (or no element matches selector), falls 1438 * back to `document`. 1439 * 1440 * @return {NodeList} 1441 * A element list of elements that were found. Will be empty if none were found. 1442 * 1443 */ 1444var $$ = createQuerier('querySelectorAll'); 1445 1446 1447 1448var Dom = (Object.freeze || Object)({ 1449 isReal: isReal, 1450 isEl: isEl, 1451 createEl: createEl, 1452 textContent: textContent, 1453 prependTo: prependTo, 1454 hasClass: hasClass, 1455 addClass: addClass, 1456 removeClass: removeClass, 1457 toggleClass: toggleClass, 1458 setAttributes: setAttributes, 1459 getAttributes: getAttributes, 1460 getAttribute: getAttribute, 1461 setAttribute: setAttribute, 1462 removeAttribute: removeAttribute, 1463 blockTextSelection: blockTextSelection, 1464 unblockTextSelection: unblockTextSelection, 1465 getBoundingClientRect: getBoundingClientRect, 1466 findPosition: findPosition, 1467 getPointerPosition: getPointerPosition, 1468 isTextNode: isTextNode, 1469 emptyEl: emptyEl, 1470 normalizeContent: normalizeContent, 1471 appendContent: appendContent, 1472 insertContent: insertContent, 1473 $: $, 1474 $$: $$ 1475}); 1476 1477/** 1478 * @file guid.js 1479 * @module guid 1480 */ 1481 1482/** 1483 * Unique ID for an element or function 1484 * @type {Number} 1485 */ 1486var _guid = 1; 1487 1488/** 1489 * Get a unique auto-incrementing ID by number that has not been returned before. 1490 * 1491 * @return {number} 1492 * A new unique ID. 1493 */ 1494function newGUID() { 1495 return _guid++; 1496} 1497 1498/** 1499 * @file dom-data.js 1500 * @module dom-data 1501 */ 1502/** 1503 * Element Data Store. 1504 * 1505 * Allows for binding data to an element without putting it directly on the 1506 * element. Ex. Event listeners are stored here. 1507 * (also from jsninja.com, slightly modified and updated for closure compiler) 1508 * 1509 * @type {Object} 1510 * @private 1511 */ 1512var elData = {}; 1513 1514/* 1515 * Unique attribute name to store an element's guid in 1516 * 1517 * @type {String} 1518 * @constant 1519 * @private 1520 */ 1521var elIdAttr = 'vdata' + new Date().getTime(); 1522 1523/** 1524 * Returns the cache object where data for an element is stored 1525 * 1526 * @param {Element} el 1527 * Element to store data for. 1528 * 1529 * @return {Object} 1530 * The cache object for that el that was passed in. 1531 */ 1532function getData(el) { 1533 var id = el[elIdAttr]; 1534 1535 if (!id) { 1536 id = el[elIdAttr] = newGUID(); 1537 } 1538 1539 if (!elData[id]) { 1540 elData[id] = {}; 1541 } 1542 1543 return elData[id]; 1544} 1545 1546/** 1547 * Returns whether or not an element has cached data 1548 * 1549 * @param {Element} el 1550 * Check if this element has cached data. 1551 * 1552 * @return {boolean} 1553 * - True if the DOM element has cached data. 1554 * - False otherwise. 1555 */ 1556function hasData(el) { 1557 var id = el[elIdAttr]; 1558 1559 if (!id) { 1560 return false; 1561 } 1562 1563 return !!Object.getOwnPropertyNames(elData[id]).length; 1564} 1565 1566/** 1567 * Delete data for the element from the cache and the guid attr from getElementById 1568 * 1569 * @param {Element} el 1570 * Remove cached data for this element. 1571 */ 1572function removeData(el) { 1573 var id = el[elIdAttr]; 1574 1575 if (!id) { 1576 return; 1577 } 1578 1579 // Remove all stored data 1580 delete elData[id]; 1581 1582 // Remove the elIdAttr property from the DOM node 1583 try { 1584 delete el[elIdAttr]; 1585 } catch (e) { 1586 if (el.removeAttribute) { 1587 el.removeAttribute(elIdAttr); 1588 } else { 1589 // IE doesn't appear to support removeAttribute on the document element 1590 el[elIdAttr] = null; 1591 } 1592 } 1593} 1594 1595/** 1596 * @file events.js. An Event System (John Resig - Secrets of a JS Ninja http://jsninja.com/) 1597 * (Original book version wasn't completely usable, so fixed some things and made Closure Compiler compatible) 1598 * This should work very similarly to jQuery's events, however it's based off the book version which isn't as 1599 * robust as jquery's, so there's probably some differences. 1600 * 1601 * @module events 1602 */ 1603 1604/** 1605 * Clean up the listener cache and dispatchers 1606 * 1607 * @param {Element|Object} elem 1608 * Element to clean up 1609 * 1610 * @param {string} type 1611 * Type of event to clean up 1612 */ 1613function _cleanUpEvents(elem, type) { 1614 var data = getData(elem); 1615 1616 // Remove the events of a particular type if there are none left 1617 if (data.handlers[type].length === 0) { 1618 delete data.handlers[type]; 1619 // data.handlers[type] = null; 1620 // Setting to null was causing an error with data.handlers 1621 1622 // Remove the meta-handler from the element 1623 if (elem.removeEventListener) { 1624 elem.removeEventListener(type, data.dispatcher, false); 1625 } else if (elem.detachEvent) { 1626 elem.detachEvent('on' + type, data.dispatcher); 1627 } 1628 } 1629 1630 // Remove the events object if there are no types left 1631 if (Object.getOwnPropertyNames(data.handlers).length <= 0) { 1632 delete data.handlers; 1633 delete data.dispatcher; 1634 delete data.disabled; 1635 } 1636 1637 // Finally remove the element data if there is no data left 1638 if (Object.getOwnPropertyNames(data).length === 0) { 1639 removeData(elem); 1640 } 1641} 1642 1643/** 1644 * Loops through an array of event types and calls the requested method for each type. 1645 * 1646 * @param {Function} fn 1647 * The event method we want to use. 1648 * 1649 * @param {Element|Object} elem 1650 * Element or object to bind listeners to 1651 * 1652 * @param {string} type 1653 * Type of event to bind to. 1654 * 1655 * @param {EventTarget~EventListener} callback 1656 * Event listener. 1657 */ 1658function _handleMultipleEvents(fn, elem, types, callback) { 1659 types.forEach(function (type) { 1660 // Call the event method for each one of the types 1661 fn(elem, type, callback); 1662 }); 1663} 1664 1665/** 1666 * Fix a native event to have standard property values 1667 * 1668 * @param {Object} event 1669 * Event object to fix. 1670 * 1671 * @return {Object} 1672 * Fixed event object. 1673 */ 1674function fixEvent(event) { 1675 1676 function returnTrue() { 1677 return true; 1678 } 1679 1680 function returnFalse() { 1681 return false; 1682 } 1683 1684 // Test if fixing up is needed 1685 // Used to check if !event.stopPropagation instead of isPropagationStopped 1686 // But native events return true for stopPropagation, but don't have 1687 // other expected methods like isPropagationStopped. Seems to be a problem 1688 // with the Javascript Ninja code. So we're just overriding all events now. 1689 if (!event || !event.isPropagationStopped) { 1690 var old = event || window_1.event; 1691 1692 event = {}; 1693 // Clone the old object so that we can modify the values event = {}; 1694 // IE8 Doesn't like when you mess with native event properties 1695 // Firefox returns false for event.hasOwnProperty('type') and other props 1696 // which makes copying more difficult. 1697 // TODO: Probably best to create a whitelist of event props 1698 for (var key in old) { 1699 // Safari 6.0.3 warns you if you try to copy deprecated layerX/Y 1700 // Chrome warns you if you try to copy deprecated keyboardEvent.keyLocation 1701 // and webkitMovementX/Y 1702 if (key !== 'layerX' && key !== 'layerY' && key !== 'keyLocation' && key !== 'webkitMovementX' && key !== 'webkitMovementY') { 1703 // Chrome 32+ warns if you try to copy deprecated returnValue, but 1704 // we still want to if preventDefault isn't supported (IE8). 1705 if (!(key === 'returnValue' && old.preventDefault)) { 1706 event[key] = old[key]; 1707 } 1708 } 1709 } 1710 1711 // The event occurred on this element 1712 if (!event.target) { 1713 event.target = event.srcElement || document_1; 1714 } 1715 1716 // Handle which other element the event is related to 1717 if (!event.relatedTarget) { 1718 event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement; 1719 } 1720 1721 // Stop the default browser action 1722 event.preventDefault = function () { 1723 if (old.preventDefault) { 1724 old.preventDefault(); 1725 } 1726 event.returnValue = false; 1727 old.returnValue = false; 1728 event.defaultPrevented = true; 1729 }; 1730 1731 event.defaultPrevented = false; 1732 1733 // Stop the event from bubbling 1734 event.stopPropagation = function () { 1735 if (old.stopPropagation) { 1736 old.stopPropagation(); 1737 } 1738 event.cancelBubble = true; 1739 old.cancelBubble = true; 1740 event.isPropagationStopped = returnTrue; 1741 }; 1742 1743 event.isPropagationStopped = returnFalse; 1744 1745 // Stop the event from bubbling and executing other handlers 1746 event.stopImmediatePropagation = function () { 1747 if (old.stopImmediatePropagation) { 1748 old.stopImmediatePropagation(); 1749 } 1750 event.isImmediatePropagationStopped = returnTrue; 1751 event.stopPropagation(); 1752 }; 1753 1754 event.isImmediatePropagationStopped = returnFalse; 1755 1756 // Handle mouse position 1757 if (event.clientX !== null && event.clientX !== undefined) { 1758 var doc = document_1.documentElement; 1759 var body = document_1.body; 1760 1761 event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0); 1762 event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0); 1763 } 1764 1765 // Handle key presses 1766 event.which = event.charCode || event.keyCode; 1767 1768 // Fix button for mouse clicks: 1769 // 0 == left; 1 == middle; 2 == right 1770 if (event.button !== null && event.button !== undefined) { 1771 1772 // The following is disabled because it does not pass videojs-standard 1773 // and... yikes. 1774 /* eslint-disable */ 1775 event.button = event.button & 1 ? 0 : event.button & 4 ? 1 : event.button & 2 ? 2 : 0; 1776 /* eslint-enable */ 1777 } 1778 } 1779 1780 // Returns fixed-up instance 1781 return event; 1782} 1783 1784/** 1785 * Whether passive event listeners are supported 1786 */ 1787var _supportsPassive = false; 1788 1789(function () { 1790 try { 1791 var opts = Object.defineProperty({}, 'passive', { 1792 get: function get() { 1793 _supportsPassive = true; 1794 } 1795 }); 1796 1797 window_1.addEventListener('test', null, opts); 1798 } catch (e) { 1799 // disregard 1800 } 1801})(); 1802 1803/** 1804 * Touch events Chrome expects to be passive 1805 */ 1806var passiveEvents = ['touchstart', 'touchmove']; 1807 1808/** 1809 * Add an event listener to element 1810 * It stores the handler function in a separate cache object 1811 * and adds a generic handler to the element's event, 1812 * along with a unique id (guid) to the element. 1813 * 1814 * @param {Element|Object} elem 1815 * Element or object to bind listeners to 1816 * 1817 * @param {string|string[]} type 1818 * Type of event to bind to. 1819 * 1820 * @param {EventTarget~EventListener} fn 1821 * Event listener. 1822 */ 1823function on(elem, type, fn) { 1824 if (Array.isArray(type)) { 1825 return _handleMultipleEvents(on, elem, type, fn); 1826 } 1827 1828 var data = getData(elem); 1829 1830 // We need a place to store all our handler data 1831 if (!data.handlers) { 1832 data.handlers = {}; 1833 } 1834 1835 if (!data.handlers[type]) { 1836 data.handlers[type] = []; 1837 } 1838 1839 if (!fn.guid) { 1840 fn.guid = newGUID(); 1841 } 1842 1843 data.handlers[type].push(fn); 1844 1845 if (!data.dispatcher) { 1846 data.disabled = false; 1847 1848 data.dispatcher = function (event, hash) { 1849 1850 if (data.disabled) { 1851 return; 1852 } 1853 1854 event = fixEvent(event); 1855 1856 var handlers = data.handlers[event.type]; 1857 1858 if (handlers) { 1859 // Copy handlers so if handlers are added/removed during the process it doesn't throw everything off. 1860 var handlersCopy = handlers.slice(0); 1861 1862 for (var m = 0, n = handlersCopy.length; m < n; m++) { 1863 if (event.isImmediatePropagationStopped()) { 1864 break; 1865 } else { 1866 try { 1867 handlersCopy[m].call(elem, event, hash); 1868 } catch (e) { 1869 log$1.error(e); 1870 } 1871 } 1872 } 1873 } 1874 }; 1875 } 1876 1877 if (data.handlers[type].length === 1) { 1878 if (elem.addEventListener) { 1879 var options = false; 1880 1881 if (_supportsPassive && passiveEvents.indexOf(type) > -1) { 1882 options = { passive: true }; 1883 } 1884 elem.addEventListener(type, data.dispatcher, options); 1885 } else if (elem.attachEvent) { 1886 elem.attachEvent('on' + type, data.dispatcher); 1887 } 1888 } 1889} 1890 1891/** 1892 * Removes event listeners from an element 1893 *
1894 * @param {Element|Object} elem 1895 * Object to remove listeners from. 1896 * 1897 * @param {string|string[]} [type] 1898 * Type of listener to remove. Don't include to remove all events from element. 1899 * 1900 * @param {EventTarget~EventListener} [fn] 1901 * Specific listener to remove. Don't include to remove listeners for an event 1902 * type. 1903 */ 1904function off(elem, type, fn) { 1905 // Don't want to add a cache object through getElData if not needed 1906 if (!hasData(elem)) { 1907 return; 1908 } 1909 1910 var data = getData(elem); 1911 1912 // If no events exist, nothing to unbind 1913 if (!data.handlers) { 1914 return; 1915 } 1916 1917 if (Array.isArray(type)) { 1918 return _handleMultipleEvents(off, elem, type, fn); 1919 } 1920 1921 // Utility function 1922 var removeType = function removeType(t) { 1923 data.handlers[t] = []; 1924 _cleanUpEvents(elem, t); 1925 }; 1926 1927 // Are we removing all bound events? 1928 if (!type) { 1929 for (var t in data.handlers) { 1930 removeType(t); 1931 } 1932 return; 1933 } 1934 1935 var handlers = data.handlers[type]; 1936 1937 // If no handlers exist, nothing to unbind 1938 if (!handlers) { 1939 return; 1940 } 1941 1942 // If no listener was provided, remove all listeners for type 1943 if (!fn) { 1944 removeType(type); 1945 return; 1946 } 1947 1948 // We're only removing a single handler 1949 if (fn.guid) { 1950 for (var n = 0; n < handlers.length; n++) { 1951 if (handlers[n].guid === fn.guid) { 1952 handlers.splice(n--, 1); 1953 } 1954 } 1955 } 1956 1957 _cleanUpEvents(elem, type); 1958} 1959 1960/** 1961 * Trigger an event for an element 1962 * 1963 * @param {Element|Object} elem 1964 * Element to trigger an event on 1965 * 1966 * @param {EventTarget~Event|string} event 1967 * A string (the type) or an event object with a type attribute 1968 * 1969 * @param {Object} [hash] 1970 * data hash to pass along with the event 1971 * 1972 * @return {boolean|undefined} 1973 * - Returns the opposite of `defaultPrevented` if default was prevented 1974 * - Otherwise returns undefined 1975 */ 1976function trigger(elem, event, hash) { 1977 // Fetches element data and a reference to the parent (for bubbling). 1978 // Don't want to add a data object to cache for every parent, 1979 // so checking hasElData first. 1980 var elemData = hasData(elem) ? getData(elem) : {}; 1981 var parent = elem.parentNode || elem.ownerDocument; 1982 // type = event.type || event, 1983 // handler; 1984 1985 // If an event name was passed as a string, creates an event out of it 1986 if (typeof event === 'string') { 1987 event = { type: event, target: elem }; 1988 } 1989 // Normalizes the event properties. 1990 event = fixEvent(event); 1991 1992 // If the passed element has a dispatcher, executes the established handlers. 1993 if (elemData.dispatcher) { 1994 elemData.dispatcher.call(elem, event, hash); 1995 } 1996 1997 // Unless explicitly stopped or the event does not bubble (e.g. media events) 1998 // recursively calls this function to bubble the event up the DOM. 1999 if (parent && !event.isPropagationStopped() && event.bubbles === true) { 2000 trigger.call(null, parent, event, hash); 2001 2002 // If at the top of the DOM, triggers the default action unless disabled. 2003 } else if (!parent && !event.defaultPrevented) { 2004 var targetData = getData(event.target); 2005 2006 // Checks if the target has a default action for this event. 2007 if (event.target[event.type]) { 2008 // Temporarily disables event dispatching on the target as we have already executed the handler. 2009 targetData.disabled = true; 2010 // Executes the default action. 2011 if (typeof event.target[event.type] === 'function') { 2012 event.target[event.type](); 2013 } 2014 // Re-enables event dispatching. 2015 targetData.disabled = false; 2016 } 2017 } 2018 2019 // Inform the triggerer if the default was prevented by returning false 2020 return !event.defaultPrevented; 2021} 2022 2023/** 2024 * Trigger a listener only once for an event 2025 * 2026 * @param {Element|Object} elem 2027 * Element or object to bind to. 2028 * 2029 * @param {string|string[]} type 2030 * Name/type of event 2031 * 2032 * @param {Event~EventListener} fn 2033 * Event Listener function 2034 */ 2035function one(elem, type, fn) { 2036 if (Array.isArray(type)) { 2037 return _handleMultipleEvents(one, elem, type, fn); 2038 } 2039 var func = function func() { 2040 off(elem, type, func); 2041 fn.apply(this, arguments); 2042 }; 2043 2044 // copy the guid to the new function so it can removed using the original function's ID 2045 func.guid = fn.guid = fn.guid || newGUID(); 2046 on(elem, type, func); 2047} 2048 2049var Events = (Object.freeze || Object)({
2050 fixEvent: fixEvent, 2051 on: on, 2052 off: off, 2053 trigger: trigger, 2054 one: one 2055}); 2056 2057/** 2058 * @file setup.js - Functions for setting up a player without 2059 * user interaction based on the data-setup `attribute` of the video tag. 2060 * 2061 * @module setup 2062 */ 2063var _windowLoaded = false; 2064var videojs$2 = void 0; 2065 2066/** 2067 * Set up any tags that have a data-setup `attribute` when the player is started. 2068 */ 2069var autoSetup = function autoSetup() { 2070 2071 // Protect against breakage in non-browser environments. 2072 if (!isReal()) { 2073 return; 2074 } 2075 2076 // One day, when we stop supporting IE8, go back to this, but in the meantime...*hack hack hack* 2077 // var vids = Array.prototype.slice.call(document.getElementsByTagName('video')); 2078 // var audios = Array.prototype.slice.call(document.getElementsByTagName('audio')); 2079 // var mediaEls = vids.concat(audios); 2080 2081 // Because IE8 doesn't support calling slice on a node list, we need to loop 2082 // through each list of elements to build up a new, combined list of elements. 2083 var vids = document_1.getElementsByTagName('video'); 2084 var audios = document_1.getElementsByTagName('audio'); 2085 var mediaEls = []; 2086 2087 if (vids && vids.length > 0) { 2088 for (var i = 0, e = vids.length; i < e; i++) { 2089 mediaEls.push(vids[i]); 2090 } 2091 } 2092 2093 if (audios && audios.length > 0) { 2094 for (var _i = 0, _e = audios.length; _i < _e; _i++) { 2095 mediaEls.push(audios[_i]); 2096 } 2097 } 2098 2099 // Check if any media elements exist 2100 if (mediaEls && mediaEls.length > 0) { 2101 2102 for (var _i2 = 0, _e2 = mediaEls.length; _i2 < _e2; _i2++) { 2103 var mediaEl = mediaEls[_i2]; 2104 2105 // Check if element exists, has getAttribute func. 2106 // IE seems to consider typeof el.getAttribute == 'object' instead of 2107 // 'function' like expected, at least when loading the player immediately. 2108 if (mediaEl && mediaEl.getAttribute) { 2109 2110 // Make sure this player hasn't already been set up. 2111 if (mediaEl.player === undefined) { 2112 var options = mediaEl.getAttribute('data-setup'); 2113 2114 // Check if data-setup attr exists. 2115 // We only auto-setup if they've added the data-setup attr. 2116 if (options !== null) { 2117 // Create new video.js instance. 2118 videojs$2(mediaEl); 2119 } 2120 } 2121 2122 // If getAttribute isn't defined, we need to wait for the DOM. 2123 } else { 2124 autoSetupTimeout(1); 2125 break; 2126 } 2127 } 2128 2129 // No videos were found, so keep looping unless page is finished loading. 2130 } else if (!_windowLoaded) { 2131 autoSetupTimeout(1); 2132 } 2133}; 2134 2135/** 2136 * Wait until the page is loaded before running autoSetup. This will be called in 2137 * autoSetup if `hasLoaded` returns false. 2138 * 2139 * @param {number} wait 2140 * How long to wait in ms 2141 * 2142 * @param {module:videojs} [vjs] 2143 * The videojs library function 2144 */ 2145function autoSetupTimeout(wait, vjs) { 2146 if (vjs) { 2147 videojs$2 = vjs; 2148 } 2149 2150 window_1.setTimeout(autoSetup, wait); 2151} 2152 2153if (isReal() && document_1.readyState === 'complete') { 2154 _windowLoaded = true; 2155} else { 2156 /** 2157 * Listen for the load event on window, and set _windowLoaded to true. 2158 * 2159 * @listens load 2160 */ 2161 one(window_1, 'load', function () { 2162 _windowLoaded = true; 2163 }); 2164} 2165 2166/** 2167 * @file stylesheet.js 2168 * @module stylesheet 2169 */ 2170/** 2171 * Create a DOM syle element given a className for it. 2172 * 2173 * @param {string} className 2174 * The className to add to the created style element. 2175 * 2176 * @return {Element} 2177 * The element that was created. 2178 */ 2179var createStyleElement = function createStyleElement(className) { 2180 var style = document_1.createElement('style'); 2181 2182 style.className = className; 2183 2184 return style; 2185}; 2186 2187/** 2188 * Add text to a DOM element. 2189 * 2190 * @param {Element} el 2191 * The Element to add text content to. 2192 * 2193 * @param {string} content 2194 * The text to add to the element. 2195 */ 2196var setTextContent = function setTextContent(el, content) { 2197 if (el.styleSheet) { 2198 el.styleSheet.cssText = content; 2199 } else { 2200 el.textContent = content; 2201 } 2202}; 2203 2204/** 2205 * @file fn.js 2206 * @module fn 2207 */ 2208/** 2209 * Bind (a.k.a proxy or Context). A simple method for changing the context of a function 2210 * It also stores a unique id on the function so it can be easily removed from events. 2211 * 2212 * @param {Mixed} context 2213 * The object to bind as scope. 2214 * 2215 * @param {Function} fn
2216 * The function to be bound to a scope. 2217 * 2218 * @param {number} [uid] 2219 * An optional unique ID for the function to be set 2220 * 2221 * @return {Function} 2222 * The new function that will be bound into the context given 2223 */ 2224var bind = function bind(context, fn, uid) { 2225 // Make sure the function has a unique ID 2226 if (!fn.guid) { 2227 fn.guid = newGUID(); 2228 } 2229 2230 // Create the new function that changes the context 2231 var bound = function bound() { 2232 return fn.apply(context, arguments); 2233 }; 2234 2235 // Allow for the ability to individualize this function 2236 // Needed in the case where multiple objects might share the same prototype 2237 // IF both items add an event listener with the same function, then you try to remove just one 2238 // it will remove both because they both have the same guid. 2239 // when using this, you need to use the bind method when you remove the listener as well. 2240 // currently used in text tracks 2241 bound.guid = uid ? uid + '_' + fn.guid : fn.guid; 2242 2243 return bound; 2244}; 2245 2246/** 2247 * Wraps the given function, `fn`, with a new function that only invokes `fn` 2248 * at most once per every `wait` milliseconds. 2249 * 2250 * @param {Function} fn 2251 * The function to be throttled. 2252 * 2253 * @param {Number} wait 2254 * The number of milliseconds by which to throttle. 2255 * 2256 * @return {Function} 2257 */ 2258var throttle = function throttle(fn, wait) { 2259 var last = Date.now(); 2260 2261 var throttled = function throttled() { 2262 var now = Date.now(); 2263 2264 if (now - last >= wait) { 2265 fn.apply(undefined, arguments); 2266 last = now; 2267 } 2268 }; 2269 2270 return throttled; 2271}; 2272 2273/** 2274 * @file src/js/event-target.js 2275 */ 2276/** 2277 * `EventTarget` is a class that can have the same API as the DOM `EventTarget`. It 2278 * adds shorthand functions that wrap around lengthy functions. For example: 2279 * the `on` function is a wrapper around `addEventListener`. 2280 * 2281 * @see [EventTarget Spec]{@link https://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-EventTarget} 2282 * @class EventTarget 2283 */ 2284var EventTarget = function EventTarget() {}; 2285 2286/** 2287 * A Custom DOM event. 2288 * 2289 * @typedef {Object} EventTarget~Event 2290 * @see [Properties]{@link https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent} 2291 */ 2292 2293/** 2294 * All event listeners should follow the following format. 2295 * 2296 * @callback EventTarget~EventListener 2297 * @this {EventTarget} 2298 * 2299 * @param {EventTarget~Event} event 2300 * the event that triggered this function 2301 * 2302 * @param {Object} [hash] 2303 * hash of data sent during the event 2304 */ 2305 2306/** 2307 * An object containing event names as keys and booleans as values. 2308 * 2309 * > NOTE: If an event name is set to a true value here {@link EventTarget#trigger} 2310 * will have extra functionality. See that function for more information. 2311 * 2312 * @property EventTarget.prototype.allowedEvents_ 2313 * @private 2314 */ 2315EventTarget.prototype.allowedEvents_ = {}; 2316 2317/** 2318 * Adds an `event listener` to an instance of an `EventTarget`. An `event listener` is a 2319 * function that will get called when an event with a certain name gets triggered. 2320 * 2321 * @param {string|string[]} type 2322 * An event name or an array of event names. 2323 * 2324 * @param {EventTarget~EventListener} fn 2325 * The function to call with `EventTarget`s 2326 */ 2327EventTarget.prototype.on = function (type, fn) { 2328 // Remove the addEventListener alias before calling Events.on 2329 // so we don't get into an infinite type loop 2330 var ael = this.addEventListener; 2331 2332 this.addEventListener = function () {}; 2333 on(this, type, fn); 2334 this.addEventListener = ael; 2335}; 2336 2337/** 2338 * An alias of {@link EventTarget#on}. Allows `EventTarget` to mimic 2339 * the standard DOM API. 2340 * 2341 * @function 2342 * @see {@link EventTarget#on} 2343 */ 2344EventTarget.prototype.addEventListener = EventTarget.prototype.on; 2345 2346/** 2347 * Removes an `event listener` for a specific event from an instance of `EventTarget`. 2348 * This makes it so that the `event listener` will no longer get called when the 2349 * named event happens. 2350 * 2351 * @param {string|string[]} type 2352 * An event name or an array of event names. 2353 * 2354 * @param {EventTarget~EventListener} fn 2355 * The function to remove. 2356 */
vendor: 6,590 bytes, lines 2357-2594
2357EventTarget.prototype.off = function (type, fn) { 2358 off(this, type, fn); 2359}; 2360 2361/** 2362 * An alias of {@link EventTarget#off}. Allows `EventTarget` to mimic 2363 * the standard DOM API. 2364 * 2365 * @function 2366 * @see {@link EventTarget#off} 2367 */ 2368EventTarget.prototype.removeEventListener = EventTarget.prototype.off; 2369 2370/** 2371 * This function will add an `event listener` that gets triggered only once. After the 2372 * first trigger it will get removed. This is like adding an `event listener` 2373 * with {@link EventTarget#on} that calls {@link EventTarget#off} on itself. 2374 * 2375 * @param {string|string[]} type 2376 * An event name or an array of event names. 2377 * 2378 * @param {EventTarget~EventListener} fn 2379 * The function to be called once for each event name. 2380 */ 2381EventTarget.prototype.one = function (type, fn) { 2382 // Remove the addEventListener alialing Events.on 2383 // so we don't get into an infinite type loop 2384 var ael = this.addEventListener; 2385 2386 this.addEventListener = function () {}; 2387 one(this, type, fn); 2388 this.addEventListener = ael; 2389}; 2390 2391/** 2392 * This function causes an event to happen. This will then cause any `event listeners` 2393 * that are waiting for that event, to get called. If there are no `event listeners` 2394 * for an event then nothing will happen. 2395 * 2396 * If the name of the `Event` that is being triggered is in `EventTarget.allowedEvents_`. 2397 * Trigger will also call the `on` + `uppercaseEventName` function. 2398 * 2399 * Example: 2400 * 'click' is in `EventTarget.allowedEvents_`, so, trigger will attempt to call 2401 * `onClick` if it exists. 2402 * 2403 * @param {string|EventTarget~Event|Object} event 2404 * The name of the event, an `Event`, or an object with a key of type set to 2405 * an event name. 2406 */ 2407EventTarget.prototype.trigger = function (event) { 2408 var type = event.type || event; 2409 2410 if (typeof event === 'string') { 2411 event = { type: type }; 2412 } 2413 event = fixEvent(event); 2414 2415 if (this.allowedEvents_[type] && this['on' + type]) { 2416 this['on' + type](event); 2417 } 2418 2419 trigger(this, event); 2420}; 2421 2422/** 2423 * An alias of {@link EventTarget#trigger}. Allows `EventTarget` to mimic 2424 * the standard DOM API. 2425 * 2426 * @function 2427 * @see {@link EventTarget#trigger} 2428 */ 2429EventTarget.prototype.dispatchEvent = EventTarget.prototype.trigger; 2430 2431/** 2432 * @file mixins/evented.js 2433 * @module evented 2434 */ 2435/** 2436 * Returns whether or not an object has had the evented mixin applied. 2437 * 2438 * @param {Object} object 2439 * An object to test. 2440 * 2441 * @return {boolean} 2442 * Whether or not the object appears to be evented. 2443 */ 2444var isEvented = function isEvented(object) { 2445 return object instanceof EventTarget || !!object.eventBusEl_ && ['on', 'one', 'off', 'trigger'].every(function (k) { 2446 return typeof object[k] === 'function'; 2447 }); 2448}; 2449 2450/** 2451 * Whether a value is a valid event type - non-empty string or array. 2452 * 2453 * @private 2454 * @param {string|Array} type 2455 * The type value to test. 2456 * 2457 * @return {boolean} 2458 * Whether or not the type is a valid event type. 2459 */ 2460var isValidEventType = function isValidEventType(type) { 2461 return ( 2462 // The regex here verifies that the `type` contains at least one non- 2463 // whitespace character. 2464 typeof type === 'string' && /\S/.test(type) || Array.isArray(type) && !!type.length 2465 ); 2466}; 2467 2468/** 2469 * Validates a value to determine if it is a valid event target. Throws if not. 2470 * 2471 * @private 2472 * @throws {Error} 2473 * If the target does not appear to be a valid event target. 2474 * 2475 * @param {Object} target 2476 * The object to test. 2477 */ 2478var validateTarget = function validateTarget(target) { 2479 if (!target.nodeName && !isEvented(target)) { 2480 throw new Error('Invalid target; must be a DOM node or evented object.'); 2481 } 2482}; 2483 2484/** 2485 * Validates a value to determine if it is a valid event target. Throws if not. 2486 * 2487 * @private 2488 * @throws {Error} 2489 * If the type does not appear to be a valid event type. 2490 * 2491 * @param {string|Array} type 2492 * The type to test. 2493 */ 2494var validateEventType = function validateEventType(type) { 2495 if (!isValidEventType(type)) { 2496 throw new Error('Invalid event type; must be a non-empty string or array.'); 2497 } 2498}; 2499 2500/** 2501 * Validates a value to determine if it is a valid listener. Throws if not. 2502 * 2503 * @private 2504 * @throws {Error} 2505 * If the listener is not a function. 2506 * 2507 * @param {Function} listener 2508 * The listener to test. 2509 */ 2510var validateListener = function validateListener(listener) { 2511 if (typeof listener !== 'function') { 2512 throw new Error('Invalid listener; must be a function.'); 2513 } 2514}; 2515 2516/** 2517 * Takes an array of arguments given to `on()` or `one()`, validates them, and 2518 * normalizes them into an object. 2519 * 2520 * @private 2521 * @param {Object} self 2522 * The evented object on which `on()` or `one()` was called. This 2523 * object will be bound as the `this` value for the listener. 2524 * 2525 * @param {Array} args 2526 * An array of arguments passed to `on()` or `one()`. 2527 * 2528 * @return {Object} 2529 * An object containing useful values for `on()` or `one()` calls. 2530 */ 2531var normalizeListenArgs = function normalizeListenArgs(self, args) { 2532 2533 // If the number of arguments is less than 3, the target is always the 2534 // evented object itself. 2535 var isTargetingSelf = args.length < 3 || args[0] === self || args[0] === self.eventBusEl_; 2536 var target = void 0; 2537 var type = void 0; 2538 var listener = void 0; 2539 2540 if (isTargetingSelf) { 2541 target = self.eventBusEl_; 2542 2543 // Deal with cases where we got 3 arguments, but we are still listening to 2544 // the evented object itself. 2545 if (args.length >= 3) { 2546 args.shift(); 2547 } 2548 2549 type = args[0]; 2550 listener = args[1]; 2551 } else { 2552 target = args[0]; 2553 type = args[1]; 2554 listener = args[2]; 2555 } 2556 2557 validateTarget(target); 2558 validateEventType(type); 2559 validateListener(listener); 2560 2561 listener = bind(self, listener); 2562 2563 return { isTargetingSelf: isTargetingSelf, target: target, type: type, listener: listener }; 2564}; 2565 2566/** 2567 * Adds the listener to the event type(s) on the target, normalizing for 2568 * the type of target. 2569 * 2570 * @private 2571 * @param {Element|Object} target 2572 * A DOM node or evented object. 2573 * 2574 * @param {string} method 2575 * The event binding method to use ("on" or "one"). 2576 * 2577 * @param {string|Array} type 2578 * One or more event type(s). 2579 * 2580 * @param {Function} listener 2581 * A listener function. 2582 */ 2583var listen = function listen(target, method, type, listener) { 2584 validateTarget(target); 2585 2586 if (target.nodeName) { 2587 Events[method](target, type, listener); 2588 } else { 2589 target[method](type, listener); 2590 } 2591}; 2592 2593/** 2594 * Contains methods that provide event capabilit
vendor: 8,378 bytes, lines 2594-2835
2594es to an object which is passed 2595 * to {@link module:evented|evented}. 2596 * 2597 * @mixin EventedMixin 2598 */ 2599var EventedMixin = { 2600 2601 /** 2602 * Add a listener to an event (or events) on this object or another evented 2603 * object. 2604 * 2605 * @param {string|Array|Element|Object} targetOrType 2606 * If this is a string or array, it represents the event type(s) 2607 * that will trigger the listener. 2608 * 2609 * Another evented object can be passed here instead, which will 2610 * cause the listener to listen for events on _that_ object. 2611 * 2612 * In either case, the listener's `this` value will be bound to 2613 * this object. 2614 * 2615 * @param {string|Array|Function} typeOrListener 2616 * If the first argument was a string or array, this should be the 2617 * listener function. Otherwise, this is a string or array of event 2618 * type(s). 2619 * 2620 * @param {Function} [listener] 2621 * If the first argument was another evented object, this will be 2622 * the listener function. 2623 */ 2624 on: function on$$1() { 2625 var _this = this; 2626 2627 for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { 2628 args[_key] = arguments[_key]; 2629 } 2630 2631 var _normalizeListenArgs = normalizeListenArgs(this, args), 2632 isTargetingSelf = _normalizeListenArgs.isTargetingSelf, 2633 target = _normalizeListenArgs.target, 2634 type = _normalizeListenArgs.type, 2635 listener = _normalizeListenArgs.listener; 2636 2637 listen(target, 'on', type, listener); 2638 2639 // If this object is listening to another evented object. 2640 if (!isTargetingSelf) { 2641 2642 // If this object is disposed, remove the listener. 2643 var removeListenerOnDispose = function removeListenerOnDispose() { 2644 return _this.off(target, type, listener); 2645 }; 2646 2647 // Use the same function ID as the listener so we can remove it later it 2648 // using the ID of the original listener. 2649 removeListenerOnDispose.guid = listener.guid; 2650 2651 // Add a listener to the target's dispose event as well. This ensures 2652 // that if the target is disposed BEFORE this object, we remove the 2653 // removal listener that was just added. Otherwise, we create a memory leak. 2654 var removeRemoverOnTargetDispose = function removeRemoverOnTargetDispose() { 2655 return _this.off('dispose', removeListenerOnDispose); 2656 }; 2657 2658 // Use the same function ID as the listener so we can remove it later 2659 // it using the ID of the original listener. 2660 removeRemoverOnTargetDispose.guid = listener.guid; 2661 2662 listen(this, 'on', 'dispose', removeListenerOnDispose); 2663 listen(target, 'on', 'dispose', removeRemoverOnTargetDispose); 2664 } 2665 }, 2666 2667 2668 /** 2669 * Add a listener to an event (or events) on this object or another evented 2670 * object. The listener will only be called once and then removed. 2671 * 2672 * @param {string|Array|Element|Object} targetOrType 2673 * If this is a string or array, it represents the event type(s) 2674 * that will trigger the listener. 2675 * 2676 * Another evented object can be passed here instead, which will 2677 * cause the listener to listen for events on _that_ object. 2678 * 2679 * In either case, the listener's `this` value will be bound to 2680 * this object. 2681 * 2682 * @param {string|Array|Function} typeOrListener 2683 * If the first argument was a string or array, this should be the 2684 * listener function. Otherwise, this is a string or array of event 2685 * type(s). 2686 * 2687 * @param {Function} [listener] 2688 * If the first argument was another evented object, this will be 2689 * the listener function. 2690 */ 2691 one: function one$$1() { 2692 var _this2 = this; 2693 2694 for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { 2695 args[_key2] = arguments[_key2]; 2696 } 2697 2698 var _normalizeListenArgs2 = normalizeListenArgs(this, args), 2699 isTargetingSelf = _normalizeListenArgs2.isTargetingSelf, 2700 target = _normalizeListenArgs2.target, 2701 type = _normalizeListenArgs2.type, 2702 listener = _normalizeListenArgs2.listener; 2703 2704 // Targeting this evented object. 2705 2706 2707 if (isTargetingSelf) { 2708 listen(target, 'one', type, listener); 2709 2710 // Targeting another evented object. 2711 } else { 2712 var wrapper = function wrapper() { 2713 for (var _len3 = arguments.length, largs = Array(_len3), _key3 = 0; _key3 < _len3; _key3++) { 2714 largs[_key3] = arguments[_key3]; 2715 } 2716 2717 _this2.off(target, type, wrapper); 2718 listener.apply(null, largs); 2719 }; 2720 2721 // Use the same function ID as the listener so we can remove it later 2722 // it using the ID of the original listener. 2723 wrapper.guid = listener.guid; 2724 listen(target, 'one', type, wrapper); 2725 } 2726 }, 2727 2728 2729 /** 2730 * Removes listener(s) from event(s) on an evented object. 2731 * 2732 * @param {string|Array|Element|Object} [targetOrType] 2733 * If this is a string or array, it represents the event type(s). 2734 * 2735 * Another evented object can be passed here instead, in which case 2736 * ALL 3 arguments are _required_. 2737 * 2738 * @param {string|Array|Function} [typeOrListener] 2739 * If the first argument was a string or array, this may be the 2740 * listener function. Otherwise, this is a string or array of event 2741 * type(s). 2742 * 2743 * @param {Function} [listener] 2744 * If the first argument was another evented object, this will be 2745 * the listener function; otherwise, _all_ listeners bound to the 2746 * event type(s) will be removed. 2747 */ 2748 off: function off$$1(targetOrType, typeOrListener, listener) { 2749 2750 // Targeting this evented object. 2751 if (!targetOrType || isValidEventType(targetOrType)) { 2752 off(this.eventBusEl_, targetOrType, typeOrListener); 2753 2754 // Targeting another evented object. 2755 } else { 2756 var target = targetOrType; 2757 var type = typeOrListener; 2758 2759 // Fail fast and in a meaningful way! 2760 validateTarget(target); 2761 validateEventType(type); 2762 validateListener(listener); 2763 2764 // Ensure there's at least a guid, even if the function hasn't been used 2765 listener = bind(this, listener); 2766 2767 // Remove the dispose listener on this evented object, which was given 2768 // the same guid as the event listener in on(). 2769 this.off('dispose', listener); 2770 2771 if (target.nodeName) { 2772 off(target, type, listener); 2773 off(target, 'dispose', listener); 2774 } else if (isEvented(target)) { 2775 target.off(type, listener); 2776 target.off('dispose', listener); 2777 } 2778 } 2779 }, 2780 2781 2782 /** 2783 * Fire an event on this evented object, causing its listeners to be called. 2784 * 2785 * @param {string|Object} event 2786 * An event type or an object with a type property. 2787 * 2788 * @param {Object} [hash] 2789 * An additional object to pass along to listeners. 2790 * 2791 * @returns {boolean} 2792 * Whether or not the default behavior was prevented. 2793 */ 2794 trigger: function trigger$$1(event, hash) { 2795 return trigger(this.eventBusEl_, event, hash); 2796 } 2797}; 2798 2799/** 2800 * Applies {@link module:evented~EventedMixin|EventedMixin} to a target object. 2801 * 2802 * @param {Object} target 2803 * The object to which to add event methods. 2804 * 2805 * @param {Object} [options={}] 2806 * Options for customizing the mixin behavior. 2807 * 2808 * @param {String} [options.eventBusKey] 2809 * By default, adds a `eventBusEl_` DOM element to the target object, 2810 * which is used as an event bus. If the target object already has a 2811 * DOM element that should be used, pass its key here. 2812 * 2813 * @return {Object} 2814 * The target object. 2815 */ 2816function evented(target) { 2817 var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 2818 var eventBusKey = options.eventBusKey; 2819 2820 // Set or create the eventBusEl_. 2821 2822 if (eventBusKey) { 2823 if (!target[eventBusKey].nodeName) { 2824 throw new Error('The eventBusKey "' + eventBusKey + '" does not refer to an element.'); 2825 } 2826 target.eventBusEl_ = target[eventBusKey]; 2827 } else { 2828 target.eventBusEl_ = createEl('span', { className: 'vjs-event-bus' }); 2829 } 2830 2831 assign(target, EventedMixin); 2832 2833 // When any evented object is disposed, it removes all its listeners. 2834 target.on('dispose', function () { 2835
vendor: 9,256 bytes, lines 2835-3186
2835return target.off(); 2836 }); 2837 2838 return target; 2839} 2840 2841/** 2842 * @file mixins/stateful.js 2843 * @module stateful 2844 */ 2845/** 2846 * Contains methods that provide statefulness to an object which is passed 2847 * to {@link module:stateful}. 2848 * 2849 * @mixin StatefulMixin 2850 */ 2851var StatefulMixin = { 2852 2853 /** 2854 * A hash containing arbitrary keys and values representing the state of 2855 * the object. 2856 * 2857 * @type {Object} 2858 */ 2859 state: {}, 2860 2861 /** 2862 * Set the state of an object by mutating its 2863 * {@link module:stateful~StatefulMixin.state|state} object in place. 2864 * 2865 * @fires module:stateful~StatefulMixin#statechanged 2866 * @param {Object|Function} stateUpdates 2867 * A new set of properties to shallow-merge into the plugin state. 2868 * Can be a plain object or a function returning a plain object. 2869 * 2870 * @returns {Object|undefined} 2871 * An object containing changes that occurred. If no changes 2872 * occurred, returns `undefined`. 2873 */ 2874 setState: function setState(stateUpdates) { 2875 var _this = this; 2876 2877 // Support providing the `stateUpdates` state as a function. 2878 if (typeof stateUpdates === 'function') { 2879 stateUpdates = stateUpdates(); 2880 } 2881 2882 var changes = void 0; 2883 2884 each(stateUpdates, function (value, key) { 2885 2886 // Record the change if the value is different from what's in the 2887 // current state. 2888 if (_this.state[key] !== value) { 2889 changes = changes || {}; 2890 changes[key] = { 2891 from: _this.state[key], 2892 to: value 2893 }; 2894 } 2895 2896 _this.state[key] = value; 2897 }); 2898 2899 // Only trigger "statechange" if there were changes AND we have a trigger 2900 // function. This allows us to not require that the target object be an 2901 // evented object. 2902 if (changes && isEvented(this)) { 2903 2904 /** 2905 * An event triggered on an object that is both 2906 * {@link module:stateful|stateful} and {@link module:evented|evented} 2907 * indicating that its state has changed. 2908 * 2909 * @event module:stateful~StatefulMixin#statechanged 2910 * @type {Object} 2911 * @property {Object} changes 2912 * A hash containing the properties that were changed and 2913 * the values they were changed `from` and `to`. 2914 */ 2915 this.trigger({ 2916 changes: changes, 2917 type: 'statechanged' 2918 }); 2919 } 2920 2921 return changes; 2922 } 2923}; 2924 2925/** 2926 * Applies {@link module:stateful~StatefulMixin|StatefulMixin} to a target 2927 * object. 2928 * 2929 * If the target object is {@link module:evented|evented} and has a 2930 * `handleStateChanged` method, that method will be automatically bound to the 2931 * `statechanged` event on itself. 2932 * 2933 * @param {Object} target 2934 * The object to be made stateful. 2935 * 2936 * @param {Object} [defaultState] 2937 * A default set of properties to populate the newly-stateful object's 2938 * `state` property. 2939 * 2940 * @returns {Object} 2941 * Returns the `target`. 2942 */ 2943function stateful(target, defaultState) { 2944 assign(target, StatefulMixin); 2945 2946 // This happens after the mixing-in because we need to replace the `state` 2947 // added in that step. 2948 target.state = assign({}, target.state, defaultState); 2949 2950 // Auto-bind the `handleStateChanged` method of the target object if it exists. 2951 if (typeof target.handleStateChanged === 'function' && isEvented(target)) { 2952 target.on('statechanged', target.handleStateChanged); 2953 } 2954 2955 return target; 2956} 2957 2958/** 2959 * @file to-title-case.js 2960 * @module to-title-case 2961 */ 2962 2963/** 2964 * Uppercase the first letter of a string. 2965 * 2966 * @param {string} string 2967 * String to be uppercased 2968 * 2969 * @return {string} 2970 * The string with an uppercased first letter 2971 */ 2972function toTitleCase(string) { 2973 if (typeof string !== 'string') { 2974 return string; 2975 } 2976 2977 return string.charAt(0).toUpperCase() + string.slice(1); 2978} 2979 2980/** 2981 * Compares the TitleCase versions of the two strings for equality. 2982 * 2983 * @param {string} str1 2984 * The first string to compare 2985 * 2986 * @param {string} str2 2987 * The second string to compare 2988 * 2989 * @return {boolean} 2990 * Whether the TitleCase versions of the strings are equal 2991 */ 2992function titleCaseEquals(str1, str2) { 2993 return toTitleCase(str1) === toTitleCase(str2); 2994} 2995 2996/** 2997 * @file merge-options.js 2998 * @module merge-options 2999 */ 3000/** 3001 * Deep-merge one or more options objects, recursively merging **only** plain 3002 * object properties. 3003 * 3004 * @param {Object[]} sources 3005 * One or more objects to merge into a new object. 3006 * 3007 * @returns {Object} 3008 * A new object that is the merged result of all sources. 3009 */ 3010function mergeOptions() { 3011 var result = {}; 3012 3013 for (var _len = arguments.length, sources = Array(_len), _key = 0; _key < _len; _key++) { 3014 sources[_key] = arguments[_key]; 3015 } 3016 3017 sources.forEach(function (source) { 3018 if (!source) { 3019 return; 3020 } 3021 3022 each(source, function (value, key) { 3023 if (!isPlain(value)) { 3024 result[key] = value; 3025 return; 3026 } 3027 3028 if (!isPlain(result[key])) { 3029 result[key] = {}; 3030 } 3031 3032 result[key] = mergeOptions(result[key], value); 3033 }); 3034 }); 3035 3036 return result; 3037} 3038 3039/** 3040 * Player Component - Base class for all UI objects 3041 * 3042 * @file component.js 3043 */ 3044/** 3045 * Base class for all UI Components. 3046 * Components are UI objects which represent both a javascript object and an element 3047 * in the DOM. They can be children of other components, and can have 3048 * children themselves. 3049 * 3050 * Components can also use methods from {@link EventTarget} 3051 */ 3052 3053var Component = function () { 3054 3055 /** 3056 * A callback that is called when a component is ready. Does not have any 3057 * paramters and any callback value will be ignored. 3058 * 3059 * @callback Component~ReadyCallback 3060 * @this Component 3061 */ 3062 3063 /** 3064 * Creates an instance of this class. 3065 * 3066 * @param {Player} player 3067 * The `Player` that this class should be attached to. 3068 * 3069 * @param {Object} [options] 3070 * The key/value store of player options. 3071 * 3072 * @param {Object[]} [options.children] 3073 * An array of children objects to intialize this component with. Children objects have 3074 * a name property that will be used if more than one component of the same type needs to be 3075 * added. 3076 * 3077 * @param {Component~ReadyCallback} [ready] 3078 * Function that gets called when the `Component` is ready. 3079 */ 3080 function Component(player, options, ready) { 3081 classCallCheck(this, Component); 3082 3083 3084 // The component might be the player itself and we can't pass `this` to super 3085 if (!player && this.play) { 3086 this.player_ = player = this; // eslint-disable-line 3087 } else { 3088 this.player_ = player; 3089 } 3090 3091 // Make a copy of prototype.options_ to protect against overriding defaults 3092 this.options_ = mergeOptions({}, this.options_); 3093 3094 // Updated options with supplied options 3095 options = this.options_ = mergeOptions(this.options_, options); 3096 3097 // Get ID from options or options element if one is supplied 3098 this.id_ = options.id || options.el && options.el.id; 3099 3100 // If there was no ID from the options, generate one 3101 if (!this.id_) { 3102 // Don't require the player ID function in the case of mock players 3103 var id = player && player.id && player.id() || 'no_player'; 3104 3105 this.id_ = id + '_component_' + newGUID(); 3106 } 3107 3108 this.name_ = options.name || null; 3109 3110 // Create element if one wasn't provided in options 3111 if (options.el) { 3112 this.el_ = options.el; 3113 } else if (options.createEl !== false) { 3114 this.el_ = this.createEl(); 3115 } 3116 3117 // Make this an evented object and use `el_`, if available, as its event bus 3118 evented(this, { eventBusKey: this.el_ ? 'el_' : null }); 3119 stateful(this, this.constructor.defaultState); 3120 3121 this.children_ = []; 3122 this.childIndex_ = {}; 3123 this.childNameIndex_ = {}; 3124 3125 // Add any child components in options 3126 if (options.initChildren !== false) { 3127 this.initChildren(); 3128 } 3129 3130 this.ready(ready); 3131 // Don't want to trigger ready here or it will before init is actually 3132 // finished for all children that run this constructor 3133 3134 if (options.reportTouchActivity !== false) { 3135 this.enableTouchActivity(); 3136 } 3137 } 3138 3139 /** 3140 * Dispose of the `Component` and all child components. 3141 * 3142 * @fires Component#dispose 3143 */ 3144 3145 3146 Component.prototype.dispose = function dispose() { 3147 3148 /** 3149 * Triggered when a `Component` is disposed. 3150 * 3151 * @event Component#dispose 3152 * @type {EventTarget~Event} 3153 * 3154 * @property {boolean} [bubbles=false] 3155 * set to false so that the close event does not 3156 * bubble up 3157 */ 3158 this.trigger({ type: 'dispose', bubbles: false }); 3159 3160 // Dispose all children. 3161 if (this.children_) { 3162 for (var i = this.children_.length - 1; i >= 0; i--) { 3163 if (this.children_[i].dispose) { 3164 this.children_[i].dispose(); 3165 } 3166 } 3167 } 3168 3169 // Delete child references 3170 this.children_ = null; 3171 this.childIndex_ = null; 3172 this.childNameIndex_ = null; 3173 3174 if (this.el_) { 3175 // Remove element from DOM 3176 if (this.el_.parentNode) { 3177 this.el_.parentNode.removeChild(this.el_); 3178 } 3179 3180 removeData(this.el_); 3181 this.el_ = null; 3182 } 3183 }; 3184 3185 /** 3186 * Return the {@link Player}
3186 that the `Component` has attached to. 3187 * 3188 * @return {Player} 3189 * The player that this `Component` has attached to. 3190 */ 3191 3192 3193 Component.prototype.player = function player() { 3194 return this.player_; 3195 }; 3196 3197 /** 3198 * Deep merge of options objects with new options. 3199 * > Note: When both `obj` and `options` contain properties whose values are objects. 3200 * The two properties get merged using {@link module:mergeOptions} 3201 * 3202 * @param {Object} obj 3203 * The object that contains new options. 3204 * 3205 * @return {Object} 3206 * A new object of `this.options_` and `obj` merged together. 3207 * 3208 * @deprecated since version 5 3209 */ 3210 3211 3212 Component.prototype.options = function options(obj) { 3213 log$1.warn('this.options() has been deprecated and will be moved to the constructor in 6.0'); 3214 3215 if (!obj) { 3216 return this.options_; 3217 } 3218 3219 this.options_ = mergeOptions(this.options_, obj); 3220 return this.options_; 3221 }; 3222 3223 /** 3224 * Get the `Component`s DOM element 3225 * 3226 * @return {Element} 3227 * The DOM element for this `Component`. 3228 */ 3229 3230 3231 Component.prototype.el = function el() { 3232 return this.el_; 3233 }; 3234 3235 /** 3236 * Create the `Component`s DOM element. 3237 * 3238 * @param {string} [tagName] 3239 * Element's DOM node type. e.g. 'div' 3240 * 3241 * @param {Object} [properties] 3242 * An object of properties that should be set. 3243 * 3244 * @param {Object} [attributes] 3245 * An object of attributes that should be set. 3246 * 3247 * @return {Element} 3248 * The element that gets created. 3249 */ 3250 3251 3252 Component.prototype.createEl = function createEl$$1(tagName, properties, attributes) { 3253 return createEl(tagName, properties, attributes); 3254 }; 3255 3256 /** 3257 * Localize a string given the string in english. 3258 * 3259 * If tokens are provided, it'll try and run a simple token replacement on the provided string. 3260 * The tokens it lo
vendor: 11,969 bytes, lines 3260-3669
3260ooks for look like `{1}` with the index being 1-indexed into the tokens array. 3261 * 3262 * If a `defaultValue` is provided, it'll use that over `string`, 3263 * if a value isn't found in provided language files. 3264 * This is useful if you want to have a descriptive key for token replacement 3265 * but have a succinct localized string and not require `en.json` to be included. 3266 * 3267 * Currently, it is used for the progress bar timing. 3268 * ```js 3269 * { 3270 * "progress bar timing: currentTime={1} duration={2}": "{1} of {2}" 3271 * } 3272 * ``` 3273 * It is then used like so: 3274 * ```js 3275 * this.localize('progress bar timing: currentTime={1} duration{2}', 3276 * [this.player_.currentTime(), this.player_.duration()], 3277 * '{1} of {2}'); 3278 * ``` 3279 * 3280 * Which outputs something like: `01:23 of 24:56`. 3281 * 3282 * 3283 * @param {string} string 3284 * The string to localize and the key to lookup in the language files. 3285 * @param {string[]} [tokens] 3286 * If the current item has token replacements, provide the tokens here. 3287 * @param {string} [defaultValue] 3288 * Defaults to `string`. Can be a default value to use for token replacement 3289 * if the lookup key is needed to be separate. 3290 * 3291 * @return {string} 3292 * The localized string or if no localization exists the english string. 3293 */ 3294 3295 3296 Component.prototype.localize = function localize(string, tokens) { 3297 var defaultValue = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : string; 3298 3299 var code = this.player_.language && this.player_.language(); 3300 var languages = this.player_.languages && this.player_.languages(); 3301 var language = languages && languages[code]; 3302 var primaryCode = code && code.split('-')[0]; 3303 var primaryLang = languages && languages[primaryCode]; 3304 3305 var localizedString = defaultValue; 3306 3307 if (language && language[string]) { 3308 localizedString = language[string]; 3309 } else if (primaryLang && primaryLang[string]) { 3310 localizedString = primaryLang[string]; 3311 } 3312 3313 if (tokens) { 3314 localizedString = localizedString.replace(/\{(\d+)\}/g, function (match, index) { 3315 var value = tokens[index - 1]; 3316 var ret = value; 3317 3318 if (typeof value === 'undefined') { 3319 ret = match; 3320 } 3321 3322 return ret; 3323 }); 3324 } 3325 3326 return localizedString; 3327 }; 3328 3329 /** 3330 * Return the `Component`s DOM element. This is where children get inserted. 3331 * This will usually be the the same as the element returned in {@link Component#el}. 3332 * 3333 * @return {Element} 3334 * The content element for this `Component`. 3335 */ 3336 3337 3338 Component.prototype.contentEl = function contentEl() { 3339 return this.contentEl_ || this.el_; 3340 }; 3341 3342 /** 3343 * Get this `Component`s ID 3344 * 3345 * @return {string} 3346 * The id of this `Component` 3347 */ 3348 3349 3350 Component.prototype.id = function id() { 3351 return this.id_; 3352 }; 3353 3354 /** 3355 * Get the `Component`s name. The name gets used to reference the `Component` 3356 * and is set during registration. 3357 * 3358 * @return {string} 3359 * The name of this `Component`. 3360 */ 3361 3362 3363 Component.prototype.name = function name() { 3364 return this.name_; 3365 }; 3366 3367 /** 3368 * Get an array of all child components 3369 * 3370 * @return {Array} 3371 * The children 3372 */ 3373 3374 3375 Component.prototype.children = function children() { 3376 return this.children_; 3377 }; 3378 3379 /** 3380 * Returns the child `Component` with the given `id`. 3381 * 3382 * @param {string} id 3383 * The id of the child `Component` to get. 3384 * 3385 * @return {Component|undefined} 3386 * The child `Component` with the given `id` or undefined. 3387 */ 3388 3389 3390 Component.prototype.getChildById = function getChildById(id) { 3391 return this.childIndex_[id]; 3392 }; 3393 3394 /** 3395 * Returns the child `Component` with the given `name`. 3396 * 3397 * @param {string} name 3398 * The name of the child `Component` to get. 3399 * 3400 * @return {Component|undefined} 3401 * The child `Component` with the given `name` or undefined. 3402 */ 3403 3404 3405 Component.prototype.getChild = function getChild(name) { 3406 if (!name) { 3407 return; 3408 } 3409 3410 name = toTitleCase(name); 3411 3412 return this.childNameIndex_[name]; 3413 }; 3414 3415 /** 3416 * Add a child `Component` inside the current `Component`. 3417 * 3418 * 3419 * @param {string|Component} child 3420 * The name or instance of a child to add. 3421 * 3422 * @param {Object} [options={}] 3423 * The key/value store of options that will get passed to children of 3424 * the child. 3425 * 3426 * @param {number} [index=this.children_.length] 3427 * The index to attempt to add a child into. 3428 * 3429 * @return {Component} 3430 * The `Component` that gets added as a child. When using a string the 3431 * `Component` will get created by this process. 3432 */ 3433 3434 3435 Component.prototype.addChild = function addChild(child) { 3436 var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 3437 var index = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : this.children_.length; 3438 3439 var component = void 0; 3440 var componentName = void 0; 3441 3442 // If child is a string, create component with options 3443 if (typeof child === 'string') { 3444 componentName = toTitleCase(child); 3445 3446 var componentClassName = options.componentClass || componentName; 3447 3448 // Set name through options 3449 options.name = componentName; 3450 3451 // Create a new object & element for this controls set 3452 // If there's no .player_, this is a player 3453 var ComponentClass = Component.getComponent(componentClassName); 3454 3455 if (!ComponentClass) { 3456 throw new Error('Component ' + componentClassName + ' does not exist'); 3457 } 3458 3459 // data stored directly on the videojs object may be 3460 // misidentified as a component to retain 3461 // backwards-compatibility with 4.x. check to make sure the 3462 // component class can be instantiated. 3463 if (typeof ComponentClass !== 'function') { 3464 return null; 3465 } 3466 3467 component = new ComponentClass(this.player_ || this, options); 3468 3469 // child is a component instance 3470 } else { 3471 component = child; 3472 } 3473 3474 this.children_.splice(index, 0, component); 3475 3476 if (typeof component.id === 'function') { 3477 this.childIndex_[component.id()] = component; 3478 } 3479 3480 // If a name wasn't used to create the component, check if we can use the 3481 // name function of the component 3482 componentName = componentName || component.name && toTitleCase(component.name()); 3483 3484 if (componentName) { 3485 this.childNameIndex_[componentName] = component; 3486 } 3487 3488 // Add the UI object's element to the container div (box) 3489 // Having an element is not required 3490 if (typeof component.el === 'function' && component.el()) { 3491 var childNodes = this.contentEl().children; 3492 var refNode = childNodes[index] || null; 3493 3494 this.contentEl().insertBefore(component.el(), refNode); 3495 } 3496 3497 // Return so it can stored on parent object if desired. 3498 return component; 3499 }; 3500 3501 /** 3502 * Remove a child `Component` from this `Component`s list of children. Also removes 3503 * the child `Component`s element from this `Component`s element. 3504 * 3505 * @param {Component} component 3506 * The child `Component` to remove. 3507 */ 3508 3509 3510 Component.prototype.removeChild = function removeChild(component) { 3511 if (typeof component === 'string') { 3512 component = this.getChild(component); 3513 } 3514 3515 if (!component || !this.children_) { 3516 return; 3517 } 3518 3519 var childFound = false; 3520 3521 for (var i = this.children_.length - 1; i >= 0; i--) { 3522 if (this.children_[i] === component) { 3523 childFound = true; 3524 this.children_.splice(i, 1); 3525 break; 3526 } 3527 } 3528 3529 if (!childFound) { 3530 return; 3531 } 3532 3533 this.childIndex_[component.id()] = null; 3534 this.childNameIndex_[component.name()] = null; 3535 3536 var compEl = component.el(); 3537 3538 if (compEl && compEl.parentNode === this.contentEl()) { 3539 this.contentEl().removeChild(component.el()); 3540 } 3541 }; 3542 3543 /** 3544 * Add and initialize default child `Component`s based upon options. 3545 */ 3546 3547 3548 Component.prototype.initChildren = function initChildren() { 3549 var _this = this; 3550 3551 var children = this.options_.children; 3552 3553 if (children) { 3554 // `this` is `parent` 3555 var parentOptions = this.options_; 3556 3557 var handleAdd = function handleAdd(child) { 3558 var name = child.name; 3559 var opts = child.opts; 3560 3561 // Allow options for children to be set at the parent options 3562 // e.g. videojs(id, { controlBar: false }); 3563 // instead of videojs(id, { children: { controlBar: false }); 3564 if (parentOptions[name] !== undefined) { 3565 opts = parentOptions[name]; 3566 } 3567 3568 // Allow for disabling default components 3569 // e.g. options['children']['posterImage'] = false 3570 if (opts === false) { 3571 return; 3572 } 3573 3574 // Allow options to be passed as a simple boolean if no configuration 3575 // is necessary. 3576 if (opts === true) { 3577 opts = {}; 3578 } 3579 3580 // We also want to pass the original player options 3581 // to each component as well so they don't need to 3582 // reach back into the player for options later. 3583 opts.playerOptions = _this.options_.playerOptions; 3584 3585 // Create and add the child component. 3586 // Add a direct reference to the child by name on the parent instance. 3587 // If two of the same component are used, different names should be supplied 3588 // for each 3589 var newChild = _this.addChild(name, opts); 3590 3591 if (newChild) { 3592 _this[name] = newChild; 3593 } 3594 }; 3595 3596 // Allow for an array of children details to passed in the options 3597 var workingChildren = void 0; 3598 var Tech = Component.getComponent('Tech'); 3599 3600 if (Array.isArray(children)) { 3601 workingChildren = children; 3602 } else { 3603 workingChildren = Object.keys(children); 3604 } 3605 3606 workingChildren 3607 // children that are in this.options_ but also in workingChildren would 3608 // give us extra children we do not want. So, we want to filter them out. 3609 .concat(Object.keys(this.options_).filter(function (child) { 3610 return !workingChildren.some(function (wchild) { 3611 if (typeof wchild === 'string') { 3612 return child === wchild; 3613 } 3614 return child === wchild.name; 3615 }); 3616 })).map(function (child) { 3617 var name = void 0; 3618 var opts = void 0; 3619 3620 if (typeof child === 'string') { 3621 name = child; 3622 opts = children[name] || _this.options_[name] || {}; 3623 } else { 3624 name = child.name; 3625 opts = child; 3626 } 3627 3628 return { name: name, opts: opts }; 3629 }).filter(function (child) { 3630 // we have to make sure that child.name isn't in the techOrder since 3631 // techs are registerd as Components but can't aren't compatible 3632 // See https://github.com/videojs/video.js/issues/2772 3633 var c = Component.getComponent(child.opts.componentClass || toTitleCase(child.name)); 3634 3635 return c && !Tech.isTech(c); 3636 }).forEach(handleAdd); 3637 } 3638 }; 3639 3640 /** 3641 * Builds the default DOM class name. Should be overriden by sub-components. 3642 * 3643 * @return {string} 3644 * The DOM class name for this object. 3645 * 3646 * @abstract 3647 */ 3648 3649 3650 Component.prototype.buildCSSClass = function buildCSSClass() { 3651 // Child classes can include a function that does: 3652 // return 'CLASS NAME' + this._super(); 3653 return ''; 3654 }; 3655 3656 /** 3657 * Bind a listener to the component's ready state. 3658 * Different from event listeners in that if the ready event has already happened 3659 * it will trigger the function immediately. 3660 * 3661 * @return {Component} 3662 * Returns itself; method can be chained. 3663 */ 3664 3665 3666 Component.prototype.ready = function ready(fn) { 3667 var sync = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; 3668 3669 if (
3669fn) { 3670 if (this.isReady_) { 3671 if (sync) { 3672 fn.call(this); 3673 } else { 3674 // Call the function asynchronously by default for consistency 3675 this.setTimeout(fn, 1); 3676 } 3677 } else { 3678 this.readyQueue_ = this.readyQueue_ || []; 3679 this.readyQueue_.push(fn); 3680 } 3681 } 3682 }; 3683 3684 /** 3685 * Trigger all the ready listeners for this `Component`. 3686 * 3687 * @fires Component#ready 3688 */ 3689 3690 3691 Component.prototype.triggerReady = function triggerReady() { 3692 this.isReady_ = true; 3693 3694 // Ensure ready is trigger
vendor: 11,813 bytes, lines 3694-4077
3694d asynchronously 3695 this.setTimeout(function () { 3696 var readyQueue = this.readyQueue_; 3697 3698 // Reset Ready Queue 3699 this.readyQueue_ = []; 3700 3701 if (readyQueue && readyQueue.length > 0) { 3702 readyQueue.forEach(function (fn) { 3703 fn.call(this); 3704 }, this); 3705 } 3706 3707 // Allow for using event listeners also 3708 /** 3709 * Triggered when a `Component` is ready. 3710 * 3711 * @event Component#ready 3712 * @type {EventTarget~Event} 3713 */ 3714 this.trigger('ready'); 3715 }, 1); 3716 }; 3717 3718 /** 3719 * Find a single DOM element matching a `selector`. This can be within the `Component`s 3720 * `contentEl()` or another custom context. 3721 * 3722 * @param {string} selector 3723 * A valid CSS selector, which will be passed to `querySelector`. 3724 * 3725 * @param {Element|string} [context=this.contentEl()] 3726 * A DOM element within which to query. Can also be a selector string in 3727 * which case the first matching element will get used as context. If 3728 * missing `this.contentEl()` gets used. If `this.contentEl()` returns 3729 * nothing it falls back to `document`. 3730 * 3731 * @return {Element|null} 3732 * the dom element that was found, or null 3733 * 3734 * @see [Information on CSS Selectors](https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Getting_Started/Selectors) 3735 */ 3736 3737 3738 Component.prototype.$ = function $$$1(selector, context) { 3739 return $(selector, context || this.contentEl()); 3740 }; 3741 3742 /** 3743 * Finds all DOM element matching a `selector`. This can be within the `Component`s 3744 * `contentEl()` or another custom context. 3745 * 3746 * @param {string} selector 3747 * A valid CSS selector, which will be passed to `querySelectorAll`. 3748 * 3749 * @param {Element|string} [context=this.contentEl()] 3750 * A DOM element within which to query. Can also be a selector string in 3751 * which case the first matching element will get used as context. If 3752 * missing `this.contentEl()` gets used. If `this.contentEl()` returns 3753 * nothing it falls back to `document`. 3754 * 3755 * @return {NodeList} 3756 * a list of dom elements that were found 3757 * 3758 * @see [Information on CSS Selectors](https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Getting_Started/Selectors) 3759 */ 3760 3761 3762 Component.prototype.$$ = function $$$$1(selector, context) { 3763 return $$(selector, context || this.contentEl()); 3764 }; 3765 3766 /** 3767 * Check if a component's element has a CSS class name. 3768 * 3769 * @param {string} classToCheck 3770 * CSS class name to check. 3771 * 3772 * @return {boolean} 3773 * - True if the `Component` has the class. 3774 * - False if the `Component` does not have the class` 3775 */ 3776 3777 3778 Component.prototype.hasClass = function hasClass$$1(classToCheck) { 3779 return hasClass(this.el_, classToCheck); 3780 }; 3781 3782 /** 3783 * Add a CSS class name to the `Component`s element. 3784 * 3785 * @param {string} classToAdd 3786 * CSS class name to add 3787 */ 3788 3789 3790 Component.prototype.addClass = function addClass$$1(classToAdd) { 3791 addClass(this.el_, classToAdd); 3792 }; 3793 3794 /** 3795 * Remove a CSS class name from the `Component`s element. 3796 * 3797 * @param {string} classToRemove 3798 * CSS class name to remove 3799 */ 3800 3801 3802 Component.prototype.removeClass = function removeClass$$1(classToRemove) { 3803 removeClass(this.el_, classToRemove); 3804 }; 3805 3806 /** 3807 * Add or remove a CSS class name from the component's element. 3808 * - `classToToggle` gets added when {@link Component#hasClass} would return false. 3809 * - `classToToggle` gets removed when {@link Component#hasClass} would return true. 3810 * 3811 * @param {string} classToToggle 3812 * The class to add or remove based on (@link Component#hasClass} 3813 * 3814 * @param {boolean|Dom~predicate} [predicate] 3815 * An {@link Dom~predicate} function or a boolean 3816 */ 3817 3818 3819 Component.prototype.toggleClass = function toggleClass$$1(classToToggle, predicate) { 3820 toggleClass(this.el_, classToToggle, predicate); 3821 }; 3822 3823 /** 3824 * Show the `Component`s element if it is hidden by removing the 3825 * 'vjs-hidden' class name from it. 3826 */ 3827 3828 3829 Component.prototype.show = function show() { 3830 this.removeClass('vjs-hidden'); 3831 }; 3832 3833 /** 3834 * Hide the `Component`s element if it is currently showing by adding the 3835 * 'vjs-hidden` class name to it. 3836 */ 3837 3838 3839 Component.prototype.hide = function hide() { 3840 this.addClass('vjs-hidden'); 3841 }; 3842 3843 /** 3844 * Lock a `Component`s element in its visible state by adding the 'vjs-lock-showing' 3845 * class name to it. Used during fadeIn/fadeOut. 3846 * 3847 * @private 3848 */ 3849 3850 3851 Component.prototype.lockShowing = function lockShowing() { 3852 this.addClass('vjs-lock-showing'); 3853 }; 3854 3855 /** 3856 * Unlock a `Component`s element from its visible state by removing the 'vjs-lock-showing' 3857 * class name from it. Used during fadeIn/fadeOut. 3858 * 3859 * @private 3860 */ 3861 3862 3863 Component.prototype.unlockShowing = function unlockShowing() { 3864 this.removeClass('vjs-lock-showing'); 3865 }; 3866 3867 /** 3868 * Get the value of an attribute on the `Component`s element. 3869 * 3870 * @param {string} attribute 3871 * Name of the attribute to get the value from. 3872 * 3873 * @return {string|null} 3874 * - The value of the attribute that was asked for. 3875 * - Can be an empty string on some browsers if the attribute does not exist 3876 * or has no value 3877 * - Most browsers will return null if the attibute does not exist or has 3878 * no value. 3879 * 3880 * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/getAttribute} 3881 */ 3882 3883 3884 Component.prototype.getAttribute = function getAttribute$$1(attribute) { 3885 return getAttribute(this.el_, attribute); 3886 }; 3887 3888 /** 3889 * Set the value of an attribute on the `Component`'s element 3890 * 3891 * @param {string} attribute 3892 * Name of the attribute to set. 3893 * 3894 * @param {string} value 3895 * Value to set the attribute to. 3896 * 3897 * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/setAttribute} 3898 */ 3899 3900 3901 Component.prototype.setAttribute = function setAttribute$$1(attribute, value) { 3902 setAttribute(this.el_, attribute, value); 3903 }; 3904 3905 /** 3906 * Remove an attribute from the `Component`s element. 3907 * 3908 * @param {string} attribute 3909 * Name of the attribute to remove. 3910 * 3911 * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/removeAttribute} 3912 */ 3913 3914 3915 Component.prototype.removeAttribute = function removeAttribute$$1(attribute) { 3916 removeAttribute(this.el_, attribute); 3917 }; 3918 3919 /** 3920 * Get or set the width of the component based upon the CSS styles. 3921 * See {@link Component#dimension} for more detailed information. 3922 * 3923 * @param {number|string} [num] 3924 * The width that you want to set postfixed with '%', 'px' or nothing. 3925 * 3926 * @param {boolean} [skipListeners] 3927 * Skip the componentresize event trigger 3928 * 3929 * @return {number|string} 3930 * The width when getting, zero if there is no width. Can be a string 3931 * postpixed with '%' or 'px'. 3932 */ 3933 3934 3935 Component.prototype.width = function width(num, skipListeners) { 3936 return this.dimension('width', num, skipListeners); 3937 }; 3938 3939 /** 3940 * Get or set the height of the component based upon the CSS styles. 3941 * See {@link Component#dimension} for more detailed information. 3942 * 3943 * @param {number|string} [num] 3944 * The height that you want to set postfixed with '%', 'px' or nothing. 3945 * 3946 * @param {boolean} [skipListeners] 3947 * Skip the componentresize event trigger 3948 * 3949 * @return {number|string} 3950 * The width when getting, zero if there is no width. Can be a string 3951 * postpixed with '%' or 'px'. 3952 */ 3953 3954 3955 Component.prototype.height = function height(num, skipListeners) { 3956 return this.dimension('height', num, skipListeners); 3957 }; 3958 3959 /** 3960 * Set both the width and height of the `Component` element at the same time. 3961 * 3962 * @param {number|string} width 3963 * Width to set the `Component`s element to. 3964 * 3965 * @param {number|string} height 3966 * Height to set the `Component`s element to. 3967 */ 3968 3969 3970 Component.prototype.dimensions = function dimensions(width, height) { 3971 // Skip componentresize listeners on width for optimization 3972 this.width(width, true); 3973 this.height(height); 3974 }; 3975 3976 /** 3977 * Get or set width or height of the `Component` element. This is the shared code 3978 * for the {@link Component#width} and {@link Component#height}. 3979 * 3980 * Things to know: 3981 * - If the width or height in an number this will return the number postfixed with 'px'. 3982 * - If the width/height is a percent this will return the percent postfixed with '%' 3983 * - Hidden elements have a width of 0 with `window.getComputedStyle`. This function 3984 * defaults to the `Component`s `style.width` and falls back to `window.getComputedStyle`. 3985 * See [this]{@link http://www.foliotek.com/devblog/getting-the-width-of-a-hidden-element-with-jquery-using-width/} 3986 * for more information 3987 * - If you want the computed style of the component, use {@link Component#currentWidth} 3988 * and {@link {Component#currentHeight} 3989 * 3990 * @fires Component#componentresize 3991 * 3992 * @param {string} widthOrHeight 3993 8 'width' or 'height' 3994 * 3995 * @param {number|string} [num] 3996 8 New dimension 3997 * 3998 * @param {boolean} [skipListeners] 3999 * Skip componentresize event trigger 4000 * 4001 * @return {number} 4002 * The dimension when getting or 0 if unset 4003 */ 4004 4005 4006 Component.prototype.dimension = function dimension(widthOrHeight, num, skipListeners) { 4007 if (num !== undefined) { 4008 // Set to zero if null or literally NaN (NaN !== NaN) 4009 if (num === null || num !== num) { 4010 num = 0; 4011 } 4012 4013 // Check if using css width/height (% or px) and adjust 4014 if (('' + num).indexOf('%') !== -1 || ('' + num).indexOf('px') !== -1) { 4015 this.el_.style[widthOrHeight] = num; 4016 } else if (num === 'auto') { 4017 this.el_.style[widthOrHeight] = ''; 4018 } else { 4019 this.el_.style[widthOrHeight] = num + 'px'; 4020 } 4021 4022 // skipListeners allows us to avoid triggering the resize event when setting both width and height 4023 if (!skipListeners) { 4024 /** 4025 * Triggered when a component is resized. 4026 * 4027 * @event Component#componentresize 4028 * @type {EventTarget~Event} 4029 */ 4030 this.trigger('componentresize'); 4031 } 4032 4033 return; 4034 } 4035 4036 // Not setting a value, so getting it 4037 // Make sure element exists 4038 if (!this.el_) { 4039 return 0; 4040 } 4041 4042 // Get dimension value from style 4043 var val = this.el_.style[widthOrHeight]; 4044 var pxIndex = val.indexOf('px'); 4045 4046 if (pxIndex !== -1) { 4047 // Return the pixel value with no 'px' 4048 return parseInt(val.slice(0, pxIndex), 10); 4049 } 4050 4051 // No px so using % or no style was set, so falling back to offsetWidth/height 4052 // If component has display:none, offset will return 0 4053 // TODO: handle display:none and no dimension style using px 4054 return parseInt(this.el_['offset' + toTitleCase(widthOrHeight)], 10); 4055 }; 4056 4057 /** 4058 * Get the width or the height of the `Component` elements computed style. Uses 4059 * `window.getComputedStyle`. 4060 * 4061 * @param {string} widthOrHeight 4062 * A string containing 'width' or 'height'. Whichever one you want to get. 4063 * 4064 * @return {number} 4065 * The dimension that gets asked for or 0 if nothing was set 4066 * for that dimension. 4067 */ 4068 4069 4070 Component.prototype.currentDimension = function currentDimension(widthOrHeight) { 4071 var computedWidthOrHeight = 0; 4072 4073 if (widthOrHeight !== 'width' && widthOrHeight !== 'height') { 4074 throw new Error('currentDimension only accepts width or height value'); 4075 } 4076 4077 if (typeof
vendor: 11,564 bytes, lines 4077-4416
4077window_1.getComputedStyle === 'function') { 4078 var computedStyle = window_1.getComputedStyle(this.el_); 4079 4080 computedWidthOrHeight = computedStyle.getPropertyValue(widthOrHeight) || computedStyle[widthOrHeight]; 4081 } 4082 4083 // remove 'px' from variable and parse as integer 4084 computedWidthOrHeight = parseFloat(computedWidthOrHeight); 4085 4086 // if the computed value is still 0, it's possible that the browser is lying 4087 // and we want to check the offset values. 4088 // This code also runs on IE8 and wherever getComputedStyle doesn't exist. 4089 if (computedWidthOrHeight === 0) { 4090 var rule = 'offset' + toTitleCase(widthOrHeight); 4091 4092 computedWidthOrHeight = this.el_[rule]; 4093 } 4094 4095 return computedWidthOrHeight; 4096 }; 4097 4098 /** 4099 * An object that contains width and height values of the `Component`s 4100 * computed style. Uses `window.getComputedStyle`. 4101 * 4102 * @typedef {Object} Component~DimensionObject 4103 * 4104 * @property {number} width 4105 * The width of the `Component`s computed style. 4106 * 4107 * @property {number} height 4108 * The height of the `Component`s computed style. 4109 */ 4110 4111 /** 4112 * Get an object that contains width and height values of the `Component`s 4113 * computed style. 4114 * 4115 * @return {Component~DimensionObject} 4116 * The dimensions of the components element 4117 */ 4118 4119 4120 Component.prototype.currentDimensions = function currentDimensions() { 4121 return { 4122 width: this.currentDimension('width'), 4123 height: this.currentDimension('height') 4124 }; 4125 }; 4126 4127 /** 4128 * Get the width of the `Component`s computed style. Uses `window.getComputedStyle`. 4129 * 4130 * @return {number} width 4131 * The width of the `Component`s computed style. 4132 */ 4133 4134 4135 Component.prototype.currentWidth = function currentWidth() { 4136 return this.currentDimension('width'); 4137 }; 4138 4139 /** 4140 * Get the height of the `Component`s computed style. Uses `window.getComputedStyle`. 4141 * 4142 * @return {number} height 4143 * The height of the `Component`s computed style. 4144 */ 4145 4146 4147 Component.prototype.currentHeight = function currentHeight() { 4148 return this.currentDimension('height'); 4149 }; 4150 4151 /** 4152 * Set the focus to this component 4153 */ 4154 4155 4156 Component.prototype.focus = function focus() { 4157 this.el_.focus(); 4158 }; 4159 4160 /** 4161 * Remove the focus from this component 4162 */ 4163 4164 4165 Component.prototype.blur = function blur() { 4166 this.el_.blur(); 4167 }; 4168 4169 /** 4170 * Emit a 'tap' events when touch event support gets detected. This gets used to 4171 * support toggling the controls through a tap on the video. They get enabled 4172 * because every sub-component would have extra overhead otherwise. 4173 * 4174 * @private 4175 * @fires Component#tap 4176 * @listens Component#touchstart 4177 * @listens Component#touchmove 4178 * @listens Component#touchleave 4179 * @listens Component#touchcancel 4180 * @listens Component#touchend 4181 */ 4182 4183 4184 Component.prototype.emitTapEvents = function emitTapEvents() { 4185 // Track the start time so we can determine how long the touch lasted 4186 var touchStart = 0; 4187 var firstTouch = null; 4188 4189 // Maximum movement allowed during a touch event to still be considered a tap 4190 // Other popular libs use anywhere from 2 (hammer.js) to 15, 4191 // so 10 seems like a nice, round number. 4192 var tapMovementThreshold = 10; 4193 4194 // The maximum length a touch can be while still being considered a tap 4195 var touchTimeThreshold = 200; 4196 4197 var couldBeTap = void 0; 4198 4199 this.on('touchstart', function (event) { 4200 // If more than one finger, don't consider treating this as a click 4201 if (event.touches.length === 1) { 4202 // Copy pageX/pageY from the object 4203 firstTouch = { 4204 pageX: event.touches[0].pageX, 4205 pageY: event.touches[0].pageY 4206 }; 4207 // Record start time so we can detect a tap vs. "touch and hold" 4208 touchStart = new Date().getTime(); 4209 // Reset couldBeTap tracking 4210 couldBeTap = true; 4211 } 4212 }); 4213 4214 this.on('touchmove', function (event) { 4215 // If more than one finger, don't consider treating this as a click 4216 if (event.touches.length > 1) { 4217 couldBeTap = false; 4218 } else if (firstTouch) { 4219 // Some devices will throw touchmoves for all but the slightest of taps. 4220 // So, if we moved only a small distance, this could still be a tap 4221 var xdiff = event.touches[0].pageX - firstTouch.pageX; 4222 var ydiff = event.touches[0].pageY - firstTouch.pageY; 4223 var touchDistance = Math.sqrt(xdiff * xdiff + ydiff * ydiff); 4224 4225 if (touchDistance > tapMovementThreshold) { 4226 couldBeTap = false; 4227 } 4228 } 4229 }); 4230 4231 var noTap = function noTap() { 4232 couldBeTap = false; 4233 }; 4234 4235 // TODO: Listen to the original target. http://youtu.be/DujfpXOKUp8?t=13m8s 4236 this.on('touchleave', noTap); 4237 this.on('touchcancel', noTap); 4238 4239 // When the touch ends, measure how long it took and trigger the appropriate 4240 // event 4241 this.on('touchend', function (event) { 4242 firstTouch = null; 4243 // Proceed only if the touchmove/leave/cancel event didn't happen 4244 if (couldBeTap === true) { 4245 // Measure how long the touch lasted 4246 var touchTime = new Date().getTime() - touchStart; 4247 4248 // Make sure the touch was less than the threshold to be considered a tap 4249 if (touchTime < touchTimeThreshold) { 4250 // Don't let browser turn this into a click 4251 event.preventDefault(); 4252 /** 4253 * Triggered when a `Component` is tapped. 4254 * 4255 * @event Component#tap 4256 * @type {EventTarget~Event} 4257 */ 4258 this.trigger('tap'); 4259 // It may be good to copy the touchend event object and change the 4260 // type to tap, if the other event properties aren't exact after 4261 // Events.fixEvent runs (e.g. event.target) 4262 } 4263 } 4264 }); 4265 }; 4266 4267 /** 4268 * This function reports user activity whenever touch events happen. This can get 4269 * turned off by any sub-components that wants touch events to act another way. 4270 * 4271 * Report user touch activity when touch events occur. User activity gets used to 4272 * determine when controls should show/hide. It is simple when it comes to mouse 4273 * events, because any mouse event should show the controls. So we capture mouse 4274 * events that bubble up to the player and report activity when that happens. 4275 * With touch events it isn't as easy as `touchstart` and `touchend` toggle player 4276 * controls. So touch events can't help us at the player level either. 4277 * 4278 * User activity gets checked asynchronously. So what could happen is a tap event 4279 * on the video turns the controls off. Then the `touchend` event bubbles up to 4280 * the player. Which, if it reported user activity, would turn the controls right 4281 * back on. We also don't want to completely block touch events from bubbling up. 4282 * Furthermore a `touchmove` event and anything other than a tap, should not turn 4283 * controls back on. 4284 * 4285 * @listens Component#touchstart 4286 * @listens Component#touchmove 4287 * @listens Component#touchend 4288 * @listens Component#touchcancel 4289 */ 4290 4291 4292 Component.prototype.enableTouchActivity = function enableTouchActivity() { 4293 // Don't continue if the root player doesn't support reporting user activity 4294 if (!this.player() || !this.player().reportUserActivity) { 4295 return; 4296 } 4297 4298 // listener for reporting that the user is active 4299 var report = bind(this.player(), this.player().reportUserActivity); 4300 4301 var touchHolding = void 0; 4302 4303 this.on('touchstart', function () { 4304 report(); 4305 // For as long as the they are touching the device or have their mouse down, 4306 // we consider them active even if they're not moving their finger or mouse. 4307 // So we want to continue to update that they are active 4308 this.clearInterval(touchHolding); 4309 // report at the same interval as activityCheck 4310 touchHolding = this.setInterval(report, 250); 4311 }); 4312 4313 var touchEnd = function touchEnd(event) { 4314 report(); 4315 // stop the interval that maintains activity if the touch is holding 4316 this.clearInterval(touchHolding); 4317 }; 4318 4319 this.on('touchmove', report); 4320 this.on('touchend', touchEnd); 4321 this.on('touchcancel', touchEnd); 4322 }; 4323 4324 /** 4325 * A callback that has no parameters and is bound into `Component`s context. 4326 * 4327 * @callback Component~GenericCallback 4328 * @this Component 4329 */ 4330 4331 /** 4332 * Creates a function that runs after an `x` millisecond timeout. This function is a 4333 * wrapper around `window.setTimeout`. There are a few reasons to use this one 4334 * instead though: 4335 * 1. It gets cleared via {@link Component#clearTimeout} when 4336 * {@link Component#dispose} gets called. 4337 * 2. The function callback will gets turned into a {@link Component~GenericCallback} 4338 * 4339 * > Note: You can use `window.clearTimeout` on the id returned by this function. This 4340 * will cause its dispose listener not to get cleaned up! Please use 4341 * {@link Component#clearTimeout} or {@link Component#dispose}. 4342 * 4343 * @param {Component~GenericCallback} fn 4344 * The function that will be run after `timeout`. 4345 * 4346 * @param {number} timeout 4347 * Timeout in milliseconds to delay before executing the specified function. 4348 * 4349 * @return {number} 4350 * Returns a timeout ID that gets used to identify the timeout. It can also 4351 * get used in {@link Component#clearTimeout} to clear the timeout that 4352 * was set. 4353 * 4354 * @listens Component#dispose 4355 * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setTimeout} 4356 */ 4357 4358 4359 Component.prototype.setTimeout = function setTimeout(fn, timeout) { 4360 fn = bind(this, fn); 4361 4362 var timeoutId = window_1.setTimeout(fn, timeout); 4363 var disposeFn = function disposeFn() { 4364 this.clearTimeout(timeoutId); 4365 }; 4366 4367 disposeFn.guid = 'vjs-timeout-' + timeoutId; 4368 4369 this.on('dispose', disposeFn); 4370 4371 return timeoutId; 4372 }; 4373 4374 /** 4375 * Clears a timeout that gets created via `window.setTimeout` or 4376 * {@link Component#setTimeout}. If you set a timeout via {@link Component#setTimeout} 4377 * use this function instead of `window.clearTimout`. If you don't your dispose 4378 * listener will not get cleaned up until {@link Component#dispose}! 4379 * 4380 * @param {number} timeoutId 4381 * The id of the timeout to clear. The return value of 4382 * {@link Component#setTimeout} or `window.setTimeout`. 4383 * 4384 * @return {number} 4385 * Returns the timeout id that was cleared. 4386 * 4387 * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/clearTimeout} 4388 */ 4389 4390 4391 Component.prototype.clearTimeout = function clearTimeout(timeoutId) { 4392 window_1.clearTimeout(timeoutId); 4393 4394 var disposeFn = function disposeFn() {}; 4395 4396 disposeFn.guid = 'vjs-timeout-' + timeoutId; 4397 4398 this.off('dispose', disposeFn); 4399 4400 return timeoutId; 4401 }; 4402 4403 /** 4404 * Creates a function that gets run every `x` milliseconds. This function is a wrapper 4405 * around `window.setInterval`. There are a few reasons to use this one instead though. 4406 * 1. It gets cleared via {@link Component#clearInterval} when 4407 * {@link Component#dispose} gets called. 4408 * 2. The function callback will be a {@link Component~GenericCallback} 4409 * 4410 * @param {Component~GenericCallback} fn 4411 * The function to run every `x` seconds. 4412 * 4413 * @param {number} interval 4414 * Execute the specified function every `x` milliseconds. 4415 * 4416 * @return {number}
4417 * Returns an id that can be used to identify the interval. It can also be be used in 4418 * {@link Component#clearInterval} to clear the interval. 4419 * 4420 * @listens Component#dispose 4421 * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setInterval} 4422 */ 4423 4424 4425 Component.prototype.setInterval = function setInterval(fn, interval) { 4426 fn = bind(this, fn); 4427 4428 var intervalId = window_1.setInterval(fn, interval); 4429 4430 var disposeFn = function disposeFn() { 4431 this.clearInterval(intervalId); 4432 }; 4433 4434 disposeFn.guid = 'vjs-interval-' + intervalId; 4435 4436 this.on('dispose', disposeFn); 4437 4438 return intervalId; 4439 }; 4440 4441 /** 4442 * Clears an interval that gets created via `window.setInterval` or 4443 * {@link Component#setInterval}. If you set an inteval via {@link Component#setInterval} 4444 * use this function instead of `window.clearInterval`. If you don't your dispose 4445 * listener will not get cleaned up until {@link Component#dispose}! 4446 * 4447 * @param {number} intervalId 4448 * The id of the interval to clear. The return value of 4449 * {@link Component#setInterval} or `window.setInterval`. 4450 * 4451 * @return {number} 4452 * Returns the interval id that was cleared. 4453 * 4454 * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/clearInterval} 4455 */ 4456 4457 4458 Component.prototype.clearInterval = function clearInterval(intervalId) { 4459 window_1.clearInterval(intervalId); 4460 4461 var disposeFn = function disposeFn() {}; 4462 4463 disposeFn.guid = 'vjs-interval-' + intervalId; 4464 4465 this.off('dispose', disposeFn); 4466 4467 return intervalId; 4468 }; 4469 4470 /** 4471 * Queues up a callback to be passed to requestAnimationFrame (rAF), but 4472 * with a few extra bonuses: 4473 * 4474 * - Supports browsers that do not support rAF by falling back to 4475 * {@link Component#setTimeout}. 4476 * 4477 * - The callback is turned into a {@link Component~GenericCallback} (i.e. 4478 * bound to the component). 4479 * 4480 * - Automatic cancellation of the rAF callback is handled if the component 4481 * is disposed before it is called. 4482 * 4483 * @param {Component~GenericCallback} fn 4484 * A function that will be bound to this component and executed just 4485 * before the browser's next repaint. 4486 * 4487 * @return {number} 4488 * Returns an rAF ID that gets used to identify the timeout. It can 4489 * also be used in {@link Component#cancelAnimationFrame} to cancel 4490 * the animation frame callback. 4491 * 4492 * @listens Component#dispose 4493 * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame} 4494 */ 4495 4496 4497 Component.prototype.requestAnimationFrame = function requestAnimationFrame(fn) { 4498 var _this2 = this; 4499 4500 if (this.supportsRaf_) { 4501 fn = bind(this, fn); 4502 4503 var id = window_1.requestAnimationFrame(fn); 4504 var disposeFn = function disposeFn() { 4505 return _this2.cancelAnimationFrame(id); 4506 }; 4507 4508 disposeFn.guid = 'vjs-raf-' + id; 4509 this.on('dispose', disposeFn); 4510 4511 return id; 4512 } 4513 4514 // Fall back to using a timer. 4515 return this.setTimeout(fn, 1000 / 60); 4516 }; 4517 4518 /** 4519 * Cancels a queued callback passed to {@link Component#requestAnimationFrame} 4520 * (rAF). 4521 * 4522 * If you queue an rAF callback via {@link Component#requestAnimationFrame}, 4523 * use this function instead of `window.cancelAnimationFrame`. If you don't, 4524 * your dispose listener will not get cleaned up until {@link Component#dispose}! 4525 * 4526 * @param {number} id 4527 * The rAF ID to clear. The return value of {@link Component#requestAnimationFrame}. 4528 * 4529 * @return {number} 4530 * Returns the rAF ID that was cleared. 4531 * 4532 * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/window/cancelAnimationFrame} 4533 */ 4534 4535 4536 Component.prototype.cancelAnimationFrame = function cancelAnimationFrame(id) { 4537 if (this.supportsRaf_) { 4538 window_1.cancelAnimationFrame(id); 4539 4540 var disposeFn = function disposeFn() {}; 4541 4542 disposeFn.guid = 'vjs-raf-' + id; 4543 4544 this.off('dispose', disposeFn); 4545 4546 return id; 4547 } 4548 4549 // Fall back to using a timer. 4550 return this.clearTimeout(id); 4551 }; 4552 4553 /** 4554 * Register a `Component` with `videojs` given the name and the component. 4555 * 4556 * > NOTE: {@link Tech}s should not be registered as a `Component`. {@link Tech}s 4557 * should be registered using {@link Tech.registerTech} or
4558 * {@link videojs:videojs.registerTech}. 4559 * 4560 * > NOTE: This function can also be seen on videojs as 4561 * {@link videojs:videojs.registerComponent}. 4562 * 4563 * @param {string} name 4564 * The name of the `Component` to register. 4565 * 4566 * @param {Component} ComponentToRegister 4567 * The `Component` class to register. 4568 * 4569 * @return {Component} 4570 * The `Component` that was registered. 4571 */ 4572 4573 4574 Component.registerComponent = function registerComponent(name, ComponentToRegister) { 4575 if (typeof name !== 'string' || !name) { 4576 throw new Error('Illegal component name, "' + name + '"; must be a non-empty string.'); 4577 } 4578 4579 var Tech = Component.getComponent('Tech'); 4580 4581 // We need to make sure this check is only done if Tech has been registered. 4582 var isTech = Tech && Tech.isTech(ComponentToRegister); 4583 var isComp = Component === ComponentToRegister || Component.prototype.isPrototypeOf(ComponentToRegister.prototype); 4584 4585 if (isTech || !isComp) { 4586 var reason = void 0; 4587 4588 if (isTech) { 4589 reason = 'techs must be registered using Tech.registerTech()'; 4590 } else { 4591 reason = 'must be a Component subclass'; 4592 } 4593 4594 throw new Error('Illegal component, "' + name + '"; ' + reason + '.'); 4595 } 4596 4597 name = toTitleCase(name); 4598 4599 if (!Component.components_) { 4600 Component.components_ = {}; 4601 } 4602 4603 var Player = Component.getComponent('Player'); 4604 4605 if (name === 'Player' && Player && Player.players) { 4606 var players = Player.players; 4607 var playerNames = Object.keys(players); 4608 4609 // If we have players that were disposed, then their name will still be 4610 // in Players.players. So, we must loop through and verify that the value 4611 // for each item is not null. This allows registration of the Player component 4612 // after all players have been disposed or before any were created. 4613 if (players && playerNames.length > 0 && playerNames.map(function (pname) { 4614 return players[pname]; 4615 }).every(Boolean)) { 4616 throw new Error('Can not register Player component after player has been created.'); 4617 } 4618 } 4619 4620 Component.components_[name] = ComponentToRegister; 4621 4622 return ComponentToRegister; 4623 }; 4624 4625 /** 4626 * Get a `Component` based on the name it was registered with. 4627 * 4628 * @param {string} name 4629 * The Name of the component to get. 4630 * 4631 * @return {Component} 4632 * The `Component` that got registered under the given name. 4633 * 4634 * @deprecated In `videojs` 6 this will not return `Component`s that were not 4635 * registered using {@link Component.registerComponent}. Currently we 4636 * check the global `videojs` object for a `Component` name and 4637 * return that if it exists. 4638 */ 4639 4640 4641 Component.getComponent = function getComponent(name) { 4642 if (!name) { 4643 return; 4644 } 4645 4646 name = toTitleCase(name); 4647 4648 if (Component.components_ && Component.components_[name]) { 4649 return Component.components_[name]; 4650 } 4651 }; 4652 4653 return Component; 4654}(); 4655 4656/** 4657 * Whether or not this component supports `requestAnimationFrame`. 4658 * 4659 * This is exposed primarily for testing purposes. 4660 * 4661 * @private 4662 * @type {Boolean} 4663 */ 4664 4665 4666Component.prototype.supportsRaf_ = typeof window_1.requestAnimationFrame === 'function' && typeof window_1.cancelAnimationFrame === 'function'; 4667 4668Component.registerComponent('Component', Component); 4669 4670/** 4671 * @file time-ranges.js 4672 * @module time-ranges 4673 */ 4674 4675/** 4676 * Returns the time for the specified index at the start or end 4677 * of a TimeRange object. 4678 * 4679 * @function time-ranges:indexFunction 4680 * 4681 * @param {number} [index=0] 4682 * The range number to return the time for. 4683 * 4684 * @return {number} 4685 * The time that offset at the specified index. 4686 * 4687 * @depricated index must be set to a value, in the future this will throw an error. 4688 */ 4689 4690/** 4691 * An object that contains ranges of time for various reasons. 4692 * 4693 * @typedef {Object} TimeRange 4694 * 4695 * @property {number} length 4696 * The number of time ranges represented by this Object 4697 * 4698 * @property {time-ranges:indexFunction} start 4699 * Returns the time offset at which a specified time range begins. 4700 * 4701 * @property {time-ranges:indexFunction} end 4702 * Returns the time offset at which a specified time range begins. 4703 * 4704 * @see https://developer.mozilla.org/en-US/docs/Web/API/TimeRanges 4705 */ 4706
vendor: 6,212 bytes, lines 4707-4917
4707/** 4708 * Check if any of the time ranges are over the maximum index. 4709 * 4710 * @param {string} fnName 4711 * The function name to use for logging 4712 * 4713 * @param {number} index 4714 * The index to check 4715 * 4716 * @param {number} maxIndex 4717 * The maximum possible index 4718 * 4719 * @throws {Error} if the timeRanges provided are over the maxIndex 4720 */ 4721function rangeCheck(fnName, index, maxIndex) { 4722 if (typeof index !== 'number' || index < 0 || index > maxIndex) { 4723 throw new Error('Failed to execute \'' + fnName + '\' on \'TimeRanges\': The index provided (' + index + ') is non-numeric or out of bounds (0-' + maxIndex + ').'); 4724 } 4725} 4726 4727/** 4728 * Check if any of the time ranges are over the maximum index. 4729 * 4730 * @param {string} fnName 4731 * The function name to use for logging 4732 * 4733 * @param {string} valueIndex 4734 * The proprety that should be used to get the time. should be 'start' or 'end' 4735 * 4736 * @param {Array} ranges 4737 * An array of time ranges 4738 * 4739 * @param {Array} [rangeIndex=0] 4740 * The index to start the search at 4741 * 4742 * @return {number} 4743 * The time that offset at the specified index. 4744 * 4745 * 4746 * @depricated rangeIndex must be set to a value, in the future this will throw an error. 4747 * @throws {Error} if rangeIndex is more than the length of ranges 4748 */ 4749function getRange(fnName, valueIndex, ranges, rangeIndex) { 4750 rangeCheck(fnName, rangeIndex, ranges.length - 1); 4751 return ranges[rangeIndex][valueIndex]; 4752} 4753 4754/** 4755 * Create a time range object givent ranges of time. 4756 * 4757 * @param {Array} [ranges] 4758 * An array of time ranges. 4759 */ 4760function createTimeRangesObj(ranges) { 4761 if (ranges === undefined || ranges.length === 0) { 4762 return { 4763 length: 0, 4764 start: function start() { 4765 throw new Error('This TimeRanges object is empty'); 4766 }, 4767 end: function end() { 4768 throw new Error('This TimeRanges object is empty'); 4769 } 4770 }; 4771 } 4772 return { 4773 length: ranges.length, 4774 start: getRange.bind(null, 'start', 0, ranges), 4775 end: getRange.bind(null, 'end', 1, ranges) 4776 }; 4777} 4778 4779/** 4780 * Should create a fake `TimeRange` object which mimics an HTML5 time range instance. 4781 * 4782 * @param {number|Array} start 4783 * The start of a single range or an array of ranges 4784 * 4785 * @param {number} end 4786 * The end of a single range. 4787 * 4788 * @private 4789 */ 4790function createTimeRanges(start, end) { 4791 if (Array.isArray(start)) { 4792 return createTimeRangesObj(start); 4793 } else if (start === undefined || end === undefined) { 4794 return createTimeRangesObj(); 4795 } 4796 return createTimeRangesObj([[start, end]]); 4797} 4798 4799/** 4800 * @file buffer.js 4801 * @module buffer 4802 */ 4803/** 4804 * Compute the percentage of the media that has been buffered. 4805 * 4806 * @param {TimeRange} buffered 4807 * The current `TimeRange` object representing buffered time ranges 4808 * 4809 * @param {number} duration 4810 * Total duration of the media 4811 * 4812 * @return {number} 4813 * Percent buffered of the total duration in decimal form. 4814 */ 4815function bufferedPercent(buffered, duration) { 4816 var bufferedDuration = 0; 4817 var start = void 0; 4818 var end = void 0; 4819 4820 if (!duration) { 4821 return 0; 4822 } 4823 4824 if (!buffered || !buffered.length) { 4825 buffered = createTimeRanges(0, 0); 4826 } 4827 4828 for (var i = 0; i < buffered.length; i++) { 4829 start = buffered.start(i); 4830 end = buffered.end(i); 4831 4832 // buffered end can be bigger than duration by a very small fraction 4833 if (end > duration) { 4834 end = duration; 4835 } 4836 4837 bufferedDuration += end - start; 4838 } 4839 4840 return bufferedDuration / duration; 4841} 4842 4843/** 4844 * @file fullscreen-api.js 4845 * @module fullscreen-api 4846 * @private 4847 */ 4848/** 4849 * Store the browser-specific methods for the fullscreen API. 4850 * 4851 * @type {Object} 4852 * @see [Specification]{@link https://fullscreen.spec.whatwg.org} 4853 * @see [Map Approach From Screenfull.js]{@link https://github.com/sindresorhus/screenfull.js} 4854 */ 4855var FullscreenApi = {}; 4856 4857// browser API methods 4858var apiMap = [['requestFullscreen', 'exitFullscreen', 'fullscreenElement', 'fullscreenEnabled', 'fullscreenchange', 'fullscreenerror'], 4859// WebKit 4860['webkitRequestFullscreen', 'webkitExitFullscreen', 'webkitFullscreenElement', 'webkitFullscreenEnabled', 'webkitfullscreenchange', 'webkitfullscreenerror'], 4861// Old WebKit (Safari 5.1) 4862['webkitRequestFullScreen', 'webkitCancelFullScreen', 'webkitCurrentFullScreenElement', 'webkitCancelFullScreen', 'webkitfullscreenchange', 'webkitfullscreenerror'], 4863// Mozilla 4864['mozRequestFullScreen', 'mozCancelFullScreen', 'mozFullScreenElement', 'mozFullScreenEnabled', 'mozfullscreenchange', 'mozfullscreenerror'], 4865// Microsoft 4866['msRequestFullscreen', 'msExitFullscreen', 'msFullscreenElement', 'msFullscreenEnabled', 'MSFullscreenChange', 'MSFullscreenError']]; 4867 4868var specApi = apiMap[0]; 4869var browserApi = void 0; 4870 4871// determine the supported set of functions 4872for (var i = 0; i < apiMap.length; i++) { 4873 // check for exitFullscreen function 4874 if (apiMap[i][1] in document_1) { 4875 browserApi = apiMap[i]; 4876 break; 4877 } 4878} 4879 4880// map the browser API names to the spec API names 4881if (browserApi) { 4882 for (var _i = 0; _i < browserApi.length; _i++) { 4883 FullscreenApi[specApi[_i]] = browserApi[_i]; 4884 } 4885} 4886 4887/** 4888 * @file media-error.js 4889 */ 4890/** 4891 * A Custom `MediaError` class which mimics the standard HTML5 `MediaError` class. 4892 * 4893 * @param {number|string|Object|MediaError} value 4894 * This can be of multiple types: 4895 * - number: should be a standard error code 4896 * - string: an error message (the code will be 0) 4897 * - Object: arbitrary properties 4898 * - `MediaError` (native): used to populate a video.js `MediaError` object 4899 * - `MediaError` (video.js): will return itself if it's already a 4900 * video.js `MediaError` object. 4901 * 4902 * @see [MediaError Spec]{@link https://dev.w3.org/html5/spec-author-view/video.html#mediaerror} 4903 * @see [Encrypted MediaError Spec]{@link https://www.w3.org/TR/2013/WD-encrypted-media-20130510/#error-codes} 4904 * 4905 * @class MediaError 4906 */ 4907function MediaError(value) { 4908 4909 // Allow redundant calls to this constructor to avoid having `instanceof` 4910 // checks peppered around the code. 4911 if (value instanceof MediaError) { 4912 return value; 4913 } 4914 4915 if (typeof value === 'number') { 4916 this.code = value; 4917 } else if (typeof value === 'string') {
vendor: 5,604 bytes, lines 4918-5102
4918 // default code is zero, so this is a custom error 4919 this.message = value; 4920 } else if (isObject(value)) { 4921 4922 // We assign the `code` property manually because native `MediaError` objects 4923 // do not expose it as an own/enumerable property of the object. 4924 if (typeof value.code === 'number') { 4925 this.code = value.code; 4926 } 4927 4928 assign(this, value); 4929 } 4930 4931 if (!this.message) { 4932 this.message = MediaError.defaultMessages[this.code] || ''; 4933 } 4934} 4935 4936/** 4937 * The error code that refers two one of the defined `MediaError` types 4938 * 4939 * @type {Number} 4940 */ 4941MediaError.prototype.code = 0; 4942 4943/** 4944 * An optional message that to show with the error. Message is not part of the HTML5 4945 * video spec but allows for more informative custom errors. 4946 * 4947 * @type {String} 4948 */ 4949MediaError.prototype.message = ''; 4950 4951/** 4952 * An optional status code that can be set by plugins to allow even more detail about 4953 * the error. For example a plugin might provide a specific HTTP status code and an 4954 * error message for that code. Then when the plugin gets that error this class will 4955 * know how to display an error message for it. This allows a custom message to show 4956 * up on the `Player` error overlay. 4957 * 4958 * @type {Array} 4959 */ 4960MediaError.prototype.status = null; 4961 4962/** 4963 * Errors indexed by the W3C standard. The order **CANNOT CHANGE**! See the 4964 * specification listed under {@link MediaError} for more information. 4965 * 4966 * @enum {array} 4967 * @readonly 4968 * @property {string} 0 - MEDIA_ERR_CUSTOM 4969 * @property {string} 1 - MEDIA_ERR_CUSTOM 4970 * @property {string} 2 - MEDIA_ERR_ABORTED 4971 * @property {string} 3 - MEDIA_ERR_NETWORK 4972 * @property {string} 4 - MEDIA_ERR_SRC_NOT_SUPPORTED 4973 * @property {string} 5 - MEDIA_ERR_ENCRYPTED 4974 */ 4975MediaError.errorTypes = ['MEDIA_ERR_CUSTOM', 'MEDIA_ERR_ABORTED', 'MEDIA_ERR_NETWORK', 'MEDIA_ERR_DECODE', 'MEDIA_ERR_SRC_NOT_SUPPORTED', 'MEDIA_ERR_ENCRYPTED']; 4976 4977/** 4978 * The default `MediaError` messages based on the {@link MediaError.errorTypes}. 4979 * 4980 * @type {Array} 4981 * @constant 4982 */ 4983MediaError.defaultMessages = { 4984 1: 'You aborted the media playback', 4985 2: 'A network error caused the media download to fail part-way.', 4986 3: 'The media playback was aborted due to a corruption problem or because the media used features your browser did not support.', 4987 4: 'The media could not be loaded, either because the server or network failed or because the format is not supported.', 4988 5: 'The media is encrypted and we do not have the keys to decrypt it.' 4989}; 4990 4991// Add types as properties on MediaError 4992// e.g. MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED = 4; 4993for (var errNum = 0; errNum < MediaError.errorTypes.length; errNum++) { 4994 MediaError[MediaError.errorTypes[errNum]] = errNum; 4995 // values should be accessible on both the class and instance 4996 MediaError.prototype[MediaError.errorTypes[errNum]] = errNum; 4997} 4998 4999var tuple = SafeParseTuple; 5000 5001function SafeParseTuple(obj, reviver) { 5002 var json; 5003 var error = null; 5004 5005 try { 5006 json = JSON.parse(obj, reviver); 5007 } catch (err) { 5008 error = err; 5009 } 5010 5011 return [error, json] 5012} 5013 5014/** 5015 * @file text-track-list-converter.js Utilities for capturing text track state and 5016 * re-creating tracks based on a capture. 5017 * 5018 * @module text-track-list-converter 5019 */ 5020 5021/** 5022 * Examine a single {@link TextTrack} and return a JSON-compatible javascript object that 5023 * represents the {@link TextTrack}'s state. 5024 * 5025 * @param {TextTrack} track 5026 * The text track to query. 5027 * 5028 * @return {Object} 5029 * A serializable javascript representation of the TextTrack. 5030 * @private 5031 */ 5032var trackToJson_ = function trackToJson_(track) { 5033 var ret = ['kind', 'label', 'language', 'id', 'inBandMetadataTrackDispatchType', 'mode', 'src'].reduce(function (acc, prop, i) { 5034 5035 if (track[prop]) { 5036 acc[prop] = track[prop]; 5037 } 5038 5039 return acc; 5040 }, { 5041 cues: track.cues && Array.prototype.map.call(track.cues, function (cue) { 5042 return { 5043 startTime: cue.startTime, 5044 endTime: cue.endTime, 5045 text: cue.text, 5046 id: cue.id 5047 }; 5048 }) 5049 }); 5050 5051 return ret; 5052}; 5053 5054/** 5055 * Examine a {@link Tech} and return a JSON-compatible javascript array that represents the 5056 * state of all {@link TextTrack}s currently configured. The return array is compatible with 5057 * {@link text-track-list-converter:jsonToTextTracks}. 5058 * 5059 * @param {Tech} tech 5060 * The tech object to query 5061 * 5062 * @return {Array} 5063 * A serializable javascript representation of the {@link Tech}s 5064 * {@link TextTrackList}. 5065 */ 5066var textTracksToJson = function textTracksToJson(tech) { 5067 5068 var trackEls = tech.$$('track'); 5069 5070 var trackObjs = Array.prototype.map.call(trackEls, function (t) { 5071 return t.track; 5072 }); 5073 var tracks = Array.prototype.map.call(trackEls, function (trackEl) { 5074 var json = trackToJson_(trackEl.track); 5075 5076 if (trackEl.src) { 5077 json.src = trackEl.src; 5078 } 5079 return json; 5080 }); 5081 5082 return tracks.concat(Array.prototype.filter.call(tech.textTracks(), function (track) { 5083 return trackObjs.indexOf(track) === -1; 5084 }).map(trackToJson_)); 5085}; 5086 5087/** 5088 * Create a set of remote {@link TextTrack}s on a {@link Tech} based on an array of javascript 5089 * object {@link TextTrack} representations. 5090 * 5091 * @param {Array} json 5092 * An array of `TextTrack` representation objects, like those that would be 5093 * produced by `textTracksToJson`. 5094 * 5095 * @param {Tech} tech 5096 * The `Tech` to create the `TextTrack`s on. 5097 */ 5098var jsonToTextTracks = function jsonToTextTracks(json, tech) { 5099 json.forEach(function (track) { 5100 var addedTrack = tech.addRemoteTextTrack(track).track; 5101 5102 if (!track.src && track.cues) {
vendor: 14,117 bytes, lines 5103-5634
5103 track.cues.forEach(function (cue) { 5104 return addedTrack.addCue(cue); 5105 }); 5106 } 5107 }); 5108 5109 return tech.textTracks(); 5110}; 5111 5112var textTrackConverter = { textTracksToJson: textTracksToJson, jsonToTextTracks: jsonToTextTracks, trackToJson_: trackToJson_ }; 5113 5114/** 5115 * @file modal-dialog.js 5116 */ 5117var MODAL_CLASS_NAME = 'vjs-modal-dialog'; 5118var ESC = 27; 5119 5120/** 5121 * The `ModalDialog` displays over the video and its controls, which blocks 5122 * interaction with the player until it is closed. 5123 * 5124 * Modal dialogs include a "Close" button and will close when that button 5125 * is activated - or when ESC is pressed anywhere. 5126 * 5127 * @extends Component 5128 */ 5129 5130var ModalDialog = function (_Component) { 5131 inherits(ModalDialog, _Component); 5132 5133 /** 5134 * Create an instance of this class. 5135 * 5136 * @param {Player} player 5137 * The `Player` that this class should be attached to. 5138 * 5139 * @param {Object} [options] 5140 * The key/value store of player options. 5141 * 5142 * @param {Mixed} [options.content=undefined] 5143 * Provide customized content for this modal. 5144 * 5145 * @param {string} [options.description] 5146 * A text description for the modal, primarily for accessibility. 5147 * 5148 * @param {boolean} [options.fillAlways=false] 5149 * Normally, modals are automatically filled only the first time 5150 * they open. This tells the modal to refresh its content 5151 * every time it opens. 5152 * 5153 * @param {string} [options.label] 5154 * A text label for the modal, primarily for accessibility. 5155 * 5156 * @param {boolean} [options.temporary=true] 5157 * If `true`, the modal can only be opened once; it will be 5158 * disposed as soon as it's closed. 5159 * 5160 * @param {boolean} [options.uncloseable=false] 5161 * If `true`, the user will not be able to close the modal 5162 * through the UI in the normal ways. Programmatic closing is 5163 * still possible. 5164 */ 5165 function ModalDialog(player, options) { 5166 classCallCheck(this, ModalDialog); 5167 5168 var _this = possibleConstructorReturn(this, _Component.call(this, player, options)); 5169 5170 _this.opened_ = _this.hasBeenOpened_ = _this.hasBeenFilled_ = false; 5171 5172 _this.closeable(!_this.options_.uncloseable); 5173 _this.content(_this.options_.content); 5174 5175 // Make sure the contentEl is defined AFTER any children are initialized 5176 // because we only want the contents of the modal in the contentEl 5177 // (not the UI elements like the close button). 5178 _this.contentEl_ = createEl('div', { 5179 className: MODAL_CLASS_NAME + '-content' 5180 }, { 5181 role: 'document' 5182 }); 5183 5184 _this.descEl_ = createEl('p', { 5185 className: MODAL_CLASS_NAME + '-description vjs-control-text', 5186 id: _this.el().getAttribute('aria-describedby') 5187 }); 5188 5189 textContent(_this.descEl_, _this.description()); 5190 _this.el_.appendChild(_this.descEl_); 5191 _this.el_.appendChild(_this.contentEl_); 5192 return _this; 5193 } 5194 5195 /** 5196 * Create the `ModalDialog`'s DOM element 5197 * 5198 * @return {Element} 5199 * The DOM element that gets created. 5200 */ 5201 5202 5203 ModalDialog.prototype.createEl = function createEl$$1() { 5204 return _Component.prototype.createEl.call(this, 'div', { 5205 className: this.buildCSSClass(), 5206 tabIndex: -1 5207 }, { 5208 'aria-describedby': this.id() + '_description', 5209 'aria-hidden': 'true', 5210 'aria-label': this.label(), 5211 'role': 'dialog' 5212 }); 5213 }; 5214 5215 /** 5216 * Builds the default DOM `className`. 5217 * 5218 * @return {string} 5219 * The DOM `className` for this object. 5220 */ 5221 5222 5223 ModalDialog.prototype.buildCSSClass = function buildCSSClass() { 5224 return MODAL_CLASS_NAME + ' vjs-hidden ' + _Component.prototype.buildCSSClass.call(this); 5225 }; 5226 5227 /** 5228 * Handles `keydown` events on the document, looking for ESC, which closes 5229 * the modal. 5230 * 5231 * @param {EventTarget~Event} e 5232 * The keypress that triggered this event. 5233 * 5234 * @listens keydown 5235 */ 5236 5237 5238 ModalDialog.prototype.handleKeyPress = function handleKeyPress(e) { 5239 if (e.which === ESC && this.closeable()) { 5240 this.close(); 5241 } 5242 }; 5243 5244 /** 5245 * Returns the label string for this modal. Primarily used for accessibility. 5246 * 5247 * @return {string} 5248 * the localized or raw label of this modal. 5249 */ 5250 5251 5252 ModalDialog.prototype.label = function label() { 5253 return this.localize(this.options_.label || 'Modal Window'); 5254 }; 5255 5256 /** 5257 * Returns the description string for this modal. Primarily used for 5258 * accessibility. 5259 * 5260 * @return {string} 5261 * The localized or raw description of this modal. 5262 */ 5263 5264 5265 ModalDialog.prototype.description = function description() { 5266 var desc = this.options_.description || this.localize('This is a modal window.'); 5267 5268 // Append a universal closeability message if the modal is closeable. 5269 if (this.closeable()) { 5270 desc += ' ' + this.localize('This modal can be closed by pressing the Escape key or activating the close button.'); 5271 } 5272 5273 return desc; 5274 }; 5275 5276 /** 5277 * Opens the modal. 5278 * 5279 * @fires ModalDialog#beforemodalopen 5280 * @fires ModalDialog#modalopen 5281 */ 5282 5283 5284 ModalDialog.prototype.open = function open() { 5285 if (!this.opened_) { 5286 var player = this.player(); 5287 5288 /** 5289 * Fired just before a `ModalDialog` is opened. 5290 * 5291 * @event ModalDialog#beforemodalopen 5292 * @type {EventTarget~Event} 5293 */ 5294 this.trigger('beforemodalopen'); 5295 this.opened_ = true; 5296 5297 // Fill content if the modal has never opened before and 5298 // never been filled. 5299 if (this.options_.fillAlways || !this.hasBeenOpened_ && !this.hasBeenFilled_) { 5300 this.fill(); 5301 } 5302 5303 // If the player was playing, pause it and take note of its previously 5304 // playing state. 5305 this.wasPlaying_ = !player.paused(); 5306 5307 if (this.options_.pauseOnOpen && this.wasPlaying_) { 5308 player.pause(); 5309 } 5310 5311 if (this.closeable()) { 5312 this.on(this.el_.ownerDocument, 'keydown', bind(this, this.handleKeyPress)); 5313 } 5314 5315 player.controls(false); 5316 this.show(); 5317 this.conditionalFocus_(); 5318 this.el().setAttribute('aria-hidden', 'false'); 5319 5320 /** 5321 * Fired just after a `ModalDialog` is opened. 5322 * 5323 * @event ModalDialog#modalopen 5324 * @type {EventTarget~Event} 5325 */ 5326 this.trigger('modalopen'); 5327 this.hasBeenOpened_ = true; 5328 } 5329 }; 5330 5331 /** 5332 * If the `ModalDialog` is currently open or closed. 5333 * 5334 * @param {boolean} [value] 5335 * If given, it will open (`true`) or close (`false`) the modal. 5336 * 5337 * @return {boolean} 5338 * the current open state of the modaldialog 5339 */ 5340 5341 5342 ModalDialog.prototype.opened = function opened(value) { 5343 if (typeof value === 'boolean') { 5344 this[value ? 'open' : 'close'](); 5345 } 5346 return this.opened_; 5347 }; 5348 5349 /** 5350 * Closes the modal, does nothing if the `ModalDialog` is 5351 * not open. 5352 * 5353 * @fires ModalDialog#beforemodalclose 5354 * @fires ModalDialog#modalclose 5355 */ 5356 5357 5358 ModalDialog.prototype.close = function close() { 5359 if (!this.opened_) { 5360 return; 5361 } 5362 var player = this.player(); 5363 5364 /** 5365 * Fired just before a `ModalDialog` is closed. 5366 * 5367 * @event ModalDialog#beforemodalclose 5368 * @type {EventTarget~Event} 5369 */ 5370 this.trigger('beforemodalclose'); 5371 this.opened_ = false; 5372 5373 if (this.wasPlaying_ && this.options_.pauseOnOpen) { 5374 player.play(); 5375 } 5376 5377 if (this.closeable()) { 5378 this.off(this.el_.ownerDocument, 'keydown', bind(this, this.handleKeyPress)); 5379 } 5380 5381 player.controls(true); 5382 this.hide(); 5383 this.el().setAttribute('aria-hidden', 'true'); 5384 5385 /** 5386 * Fired just after a `ModalDialog` is closed. 5387 * 5388 * @event ModalDialog#modalclose 5389 * @type {EventTarget~Event} 5390 */ 5391 this.trigger('modalclose'); 5392 this.conditionalBlur_(); 5393 5394 if (this.options_.temporary) { 5395 this.dispose(); 5396 } 5397 }; 5398 5399 /** 5400 * Check to see if the `ModalDialog` is closeable via the UI. 5401 * 5402 * @param {boolean} [value] 5403 * If given as a boolean, it will set the `closeable` option. 5404 * 5405 * @return {boolean} 5406 * Returns the final value of the closable option. 5407 */ 5408 5409 5410 ModalDialog.prototype.closeable = function closeable(value) { 5411 if (typeof value === 'boolean') { 5412 var closeable = this.closeable_ = !!value; 5413 var close = this.getChild('closeButton'); 5414 5415 // If this is being made closeable and has no close button, add one. 5416 if (closeable && !close) { 5417 5418 // The close button should be a child of the modal - not its 5419 // content element, so temporarily change the content element. 5420 var temp = this.contentEl_; 5421 5422 this.contentEl_ = this.el_; 5423 close = this.addChild('closeButton', { controlText: 'Close Modal Dialog' }); 5424 this.contentEl_ = temp; 5425 this.on(close, 'close', this.close); 5426 } 5427 5428 // If this is being made uncloseable and has a close button, remove it. 5429 if (!closeable && close) { 5430 this.off(close, 'close', this.close); 5431 this.removeChild(close); 5432 close.dispose(); 5433 } 5434 } 5435 return this.closeable_; 5436 }; 5437 5438 /** 5439 * Fill the modal's content element with the modal's "content" option. 5440 * The content element will be emptied before this change takes place. 5441 */ 5442 5443 5444 ModalDialog.prototype.fill = function fill() { 5445 this.fillWith(this.content()); 5446 }; 5447 5448 /** 5449 * Fill the modal's content element with arbitrary content. 5450 * The content element will be emptied before this change takes place. 5451 * 5452 * @fires ModalDialog#beforemodalfill 5453 * @fires ModalDialog#modalfill 5454 * 5455 * @param {Mixed} [content] 5456 * The same rules apply to this as apply to the `content` option. 5457 */ 5458 5459 5460 ModalDialog.prototype.fillWith = function fillWith(content) { 5461 var contentEl = this.contentEl(); 5462 var parentEl = contentEl.parentNode; 5463 var nextSiblingEl = contentEl.nextSibling; 5464 5465 /** 5466 * Fired just before a `ModalDialog` is filled with content. 5467 * 5468 * @event ModalDialog#beforemodalfill 5469 * @type {EventTarget~Event} 5470 */ 5471 this.trigger('beforemodalfill'); 5472 this.hasBeenFilled_ = true; 5473 5474 // Detach the content element from the DOM before performing 5475 // manipulation to avoid modifying the live DOM multiple times. 5476 parentEl.removeChild(contentEl); 5477 this.empty(); 5478 insertContent(contentEl, content); 5479 /** 5480 * Fired just after a `ModalDialog` is filled with content. 5481 * 5482 * @event ModalDialog#modalfill 5483 * @type {EventTarget~Event} 5484 */ 5485 this.trigger('modalfill'); 5486 5487 // Re-inject the re-filled content element. 5488 if (nextSiblingEl) { 5489 parentEl.insertBefore(contentEl, nextSiblingEl); 5490 } else { 5491 parentEl.appendChild(contentEl); 5492 } 5493 5494 // make sure that the close button is last in the dialog DOM 5495 var closeButton = this.getChild('closeButton'); 5496 5497 if (closeButton) { 5498 parentEl.appendChild(closeButton.el_); 5499 } 5500 }; 5501 5502 /** 5503 * Empties the content element. This happens anytime the modal is filled. 5504 * 5505 * @fires ModalDialog#beforemodalempty 5506 * @fires ModalDialog#modalempty 5507 */ 5508 5509 5510 ModalDialog.prototype.empty = function empty() { 5511 /** 5512 * Fired just before a `ModalDialog` is emptied. 5513 * 5514 * @event ModalDialog#beforemodalempty 5515 * @type {EventTarget~Event} 5516 */ 5517 this.trigger('beforemodalempty'); 5518 emptyEl(this.contentEl()); 5519 5520 /** 5521 * Fired just after a `ModalDialog` is emptied. 5522 * 5523 * @event ModalDialog#modalempty 5524 * @type {EventTarget~Event} 5525 */ 5526 this.trigger('modalempty'); 5527 }; 5528 5529 /** 5530 * Gets or sets the modal content, which gets normalized before being 5531 * rendered into the DOM. 5532 * 5533 * This does not update the DOM or fill the modal, but it is called during 5534 * that process. 5535 * 5536 * @param {Mixed} [value] 5537 * If defined, sets the internal content value to be used on the 5538 * next call(s) to `fill`. This value is normalized before being 5539 * inserted. To "clear" the internal content value, pass `null`. 5540 * 5541 * @return {Mixed} 5542 * The current content of the modal dialog 5543 */ 5544 5545 5546 ModalDialog.prototype.content = function content(value) { 5547 if (typeof value !== 'undefined') { 5548 this.content_ = value; 5549 } 5550 return this.content_; 5551 }; 5552 5553 /** 5554 * conditionally focus the modal dialog if focus was previously on the player. 5555 * 5556 * @private 5557 */ 5558 5559 5560 ModalDialog.prototype.conditionalFocus_ = function conditionalFocus_() { 5561 var activeEl = document_1.activeElement; 5562 var playerEl = this.player_.el_; 5563 5564 this.previouslyActiveEl_ = null; 5565 5566 if (playerEl.contains(activeEl) || playerEl === activeEl) { 5567 this.previouslyActiveEl_ = activeEl; 5568 5569 this.focus(); 5570 5571 this.on(document_1, 'keydown', this.handleKeyDown); 5572 } 5573 }; 5574 5575 /** 5576 * conditionally blur the element and refocus the last focused element 5577 * 5578 * @private 5579 */ 5580 5581 5582 ModalDialog.prototype.conditionalBlur_ = function conditionalBlur_() { 5583 if (this.previouslyActiveEl_) { 5584 this.previouslyActiveEl_.focus(); 5585 this.previouslyActiveEl_ = null; 5586 } 5587 5588 this.off(document_1, 'keydown', this.handleKeyDown); 5589 }; 5590 5591 /** 5592 * Keydown handler. Attached when modal is focused. 5593 * 5594 * @listens keydown 5595 */ 5596 5597 5598 ModalDialog.prototype.handleKeyDown = function handleKeyDown(event) { 5599 // exit early if it isn't a tab key 5600 if (event.which !== 9) { 5601 return; 5602 } 5603 5604 var focusableEls = this.focusableEls_(); 5605 var activeEl = this.el_.querySelector(':focus'); 5606 var focusIndex = void 0; 5607 5608 for (var i = 0; i < focusableEls.length; i++) { 5609 if (activeEl === focusableEls[i]) { 5610 focusIndex = i; 5611 break; 5612 } 5613 } 5614 5615 if (document_1.activeElement === this.el_) { 5616 focusIndex = 0; 5617 } 5618 5619 if (event.shiftKey && focusIndex === 0) { 5620 focusableEls[focusableEls.length - 1].focus(); 5621 event.preventDefault(); 5622 } else if (!event.shiftKey && focusIndex === focusableEls.length - 1) { 5623 focusableEls[0].focus(); 5624 event.preventDefault(); 5625 } 5626 }; 5627 5628 /** 5629 * get all focusable elements 5630 * 5631 * @private 5632 */ 5633 5634
vendor: 6,242 bytes, lines 5635-5888
5635 ModalDialog.prototype.focusableEls_ = function focusableEls_() { 5636 var allChildren = this.el_.querySelectorAll('*'); 5637 5638 return Array.prototype.filter.call(allChildren, function (child) { 5639 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'); 5640 }); 5641 }; 5642 5643 return ModalDialog; 5644}(Component); 5645 5646/** 5647 * Default options for `ModalDialog` default options. 5648 * 5649 * @type {Object} 5650 * @private 5651 */ 5652 5653 5654ModalDialog.prototype.options_ = { 5655 pauseOnOpen: true, 5656 temporary: true 5657}; 5658 5659Component.registerComponent('ModalDialog', ModalDialog); 5660 5661/** 5662 * @file track-list.js 5663 */ 5664/** 5665 * Common functionaliy between {@link TextTrackList}, {@link AudioTrackList}, and 5666 * {@link VideoTrackList} 5667 * 5668 * @extends EventTarget 5669 */ 5670 5671var TrackList = function (_EventTarget) { 5672 inherits(TrackList, _EventTarget); 5673 5674 /** 5675 * Create an instance of this class 5676 * 5677 * @param {Track[]} tracks 5678 * A list of tracks to initialize the list with. 5679 * 5680 * @param {Object} [list] 5681 * The child object with inheritance done manually for ie8. 5682 * 5683 * @abstract 5684 */ 5685 function TrackList() { 5686 var tracks = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; 5687 5688 var _ret; 5689 5690 var list = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null; 5691 classCallCheck(this, TrackList); 5692 5693 var _this = possibleConstructorReturn(this, _EventTarget.call(this)); 5694 5695 if (!list) { 5696 list = _this; // eslint-disable-line 5697 if (IS_IE8) { 5698 list = document_1.createElement('custom'); 5699 for (var prop in TrackList.prototype) { 5700 if (prop !== 'constructor') { 5701 list[prop] = TrackList.prototype[prop]; 5702 } 5703 } 5704 } 5705 } 5706 5707 list.tracks_ = []; 5708 5709 /** 5710 * @memberof TrackList 5711 * @member {number} length 5712 * The current number of `Track`s in the this Trackist. 5713 * @instance 5714 */ 5715 Object.defineProperty(list, 'length', { 5716 get: function get$$1() { 5717 return this.tracks_.length; 5718 } 5719 }); 5720 5721 for (var i = 0; i < tracks.length; i++) { 5722 list.addTrack(tracks[i]); 5723 } 5724 5725 // must return the object, as for ie8 it will not be this 5726 // but a reference to a document object 5727 return _ret = list, possibleConstructorReturn(_this, _ret); 5728 } 5729 5730 /** 5731 * Add a {@link Track} to the `TrackList` 5732 * 5733 * @param {Track} track 5734 * The audio, video, or text track to add to the list. 5735 * 5736 * @fires TrackList#addtrack 5737 */ 5738 5739 5740 TrackList.prototype.addTrack = function addTrack(track) { 5741 var index = this.tracks_.length; 5742 5743 if (!('' + index in this)) { 5744 Object.defineProperty(this, index, { 5745 get: function get$$1() { 5746 return this.tracks_[index]; 5747 } 5748 }); 5749 } 5750 5751 // Do not add duplicate tracks 5752 if (this.tracks_.indexOf(track) === -1) { 5753 this.tracks_.push(track); 5754 /** 5755 * Triggered when a track is added to a track list. 5756 * 5757 * @event TrackList#addtrack 5758 * @type {EventTarget~Event} 5759 * @property {Track} track 5760 * A reference to track that was added. 5761 */ 5762 this.trigger({ 5763 track: track, 5764 type: 'addtrack' 5765 }); 5766 } 5767 }; 5768 5769 /** 5770 * Remove a {@link Track} from the `TrackList` 5771 * 5772 * @param {Track} rtrack 5773 * The audio, video, or text track to remove from the list. 5774 * 5775 * @fires TrackList#removetrack 5776 */ 5777 5778 5779 TrackList.prototype.removeTrack = function removeTrack(rtrack) { 5780 var track = void 0; 5781 5782 for (var i = 0, l = this.length; i < l; i++) { 5783 if (this[i] === rtrack) { 5784 track = this[i]; 5785 if (track.off) { 5786 track.off(); 5787 } 5788 5789 this.tracks_.splice(i, 1); 5790 5791 break; 5792 } 5793 } 5794 5795 if (!track) { 5796 return; 5797 } 5798 5799 /** 5800 * Triggered when a track is removed from track list. 5801 * 5802 * @event TrackList#removetrack 5803 * @type {EventTarget~Event} 5804 * @property {Track} track 5805 * A reference to track that was removed. 5806 */ 5807 this.trigger({ 5808 track: track, 5809 type: 'removetrack' 5810 }); 5811 }; 5812 5813 /** 5814 * Get a Track from the TrackList by a tracks id 5815 * 5816 * @param {String} id - the id of the track to get 5817 * @method getTrackById 5818 * @return {Track} 5819 * @private 5820 */ 5821 5822 5823 TrackList.prototype.getTrackById = function getTrackById(id) { 5824 var result = null; 5825 5826 for (var i = 0, l = this.length; i < l; i++) { 5827 var track = this[i]; 5828 5829 if (track.id === id) { 5830 result = track; 5831 break; 5832 } 5833 } 5834 5835 return result; 5836 }; 5837 5838 return TrackList; 5839}(EventTarget); 5840 5841/** 5842 * Triggered when a different track is selected/enabled. 5843 * 5844 * @event TrackList#change 5845 * @type {EventTarget~Event} 5846 */ 5847 5848/** 5849 * Events that can be called with on + eventName. See {@link EventHandler}. 5850 * 5851 * @property {Object} TrackList#allowedEvents_ 5852 * @private 5853 */ 5854 5855 5856TrackList.prototype.allowedEvents_ = { 5857 change: 'change', 5858 addtrack: 'addtrack', 5859 removetrack: 'removetrack' 5860}; 5861 5862// emulate attribute EventHandler support to allow for feature detection 5863for (var event in TrackList.prototype.allowedEvents_) { 5864 TrackList.prototype['on' + event] = null; 5865} 5866 5867/** 5868 * @file audio-track-list.js 5869 */ 5870/** 5871 * Anywhere we call this function we diverge from the spec 5872 * as we only support one enabled audiotrack at a time 5873 * 5874 * @param {AudioTrackList} list 5875 * list to work on 5876 * 5877 * @param {AudioTrack} track 5878 * The track to skip 5879 * 5880 * @private 5881 */ 5882var disableOthers = function disableOthers(list, track) { 5883 for (var i = 0; i < list.length; i++) { 5884 if (!Object.keys(list[i]).length || track.id === list[i].id) { 5885 continue; 5886 } 5887 // another audio track is enabled, disable it 5888 list[i].enabled = false;
5889 } 5890}; 5891 5892/** 5893 * The current list of {@link AudioTrack} for a media file. 5894 * 5895 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotracklist} 5896 * @extends TrackList 5897 */ 5898 5899var AudioTrackList = function (_TrackList) { 5900 inherits(AudioTrackList, _TrackList); 5901 5902 /** 5903 * Create an instance of this class. 5904 * 5905 * @param {AudioTrack[]} [tracks=[]] 5906 * A list of `AudioTrack` to instantiate the list with. 5907 */ 5908 function AudioTrackList() { 5909 var _this, _ret; 5910 5911 var tracks = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; 5912 classCallCheck(this, AudioTrackList); 5913 5914 var list = void 0; 5915 5916 // make sure only 1 track is enabled 5917 // sorted from last index to first index 5918 for (var i = tracks.length - 1; i >= 0; i--) { 5919 if (tracks[i].enabled) { 5920 disableOthers(tracks, tracks[i]); 5921 break; 5922 } 5923 } 5924 5925 // IE8 forces us to implement inheritance ourselves 5926 // as it does not support Object.defineProperty properly 5927 if (IS_IE8) { 5928 list = document_1.createElement('custom'); 5929 for (var prop in TrackList.prototype) { 5930 if (prop !== 'constructor') { 5931 list[prop] = TrackList.prototype[prop]; 5932 } 5933 } 5934 for (var _prop in AudioTrackList.prototype) { 5935 if (_prop !== 'constructor') { 5936 list[_prop] = AudioTrackList.prototype[_prop]; 5937 } 5938 } 5939 } 5940
5941 list = (_this = possibleConstructorReturn(this, _TrackList.call(this, tracks, list)), _this); 5942 list.changing_ = false; 5943 5944 return _ret = list, possibleConstructorReturn(_this, _ret); 5945 } 5946 5947 /** 5948 * Add an {@link AudioTrack} to the `AudioTrackList`. 5949 * 5950 * @param {AudioTrack} track 5951 * The AudioTrack to add to the list 5952 * 5953 * @fires TrackList#addtrack 5954 */ 5955 5956 5957 AudioTrackList.prototype.addTrack = function addTrack(track) { 5958 var _this2 = this; 5959 5960 if (track.enabled) { 5961 disableOthers(this, track); 5962 } 5963 5964 _TrackList.prototype.addTrack.call(this, track); 5965 // native tracks don't have this 5966 if (!track.addEventListener) { 5967 return; 5968 } 5969 5970 /** 5971 * @listens AudioTrack#enabledchange 5972 * @fires TrackList#change 5973 */ 5974 track.addEventListener('enabledchange', function () { 5975 // when we are disabling other tracks (since we don't support 5976 // more than one track at a time) we will set changing_ 5977 // to true so that we don't trigger additional change events 5978 if (_this2.changing_) { 5979 return; 5980 } 5981 _this2.changing_ = true; 5982 disableOthers(_this2, track); 5983 _this2.changing_ = false; 5984 _this2.trigger('change'); 5985 }); 5986 }; 5987 5988 return AudioTrackList; 5989}(TrackList); 5990 5991/** 5992 * @file video-track-list.js 5993 */ 5994/** 5995 * Un-select all other {@link VideoTrack}s that are selected. 5996 * 5997 * @param {VideoTrackList} list 5998 * list to work on 5999 * 6000 * @param {VideoTrack} track 6001 * The track to skip 6002 * 6003 * @private 6004 */ 6005var disableOthers$1 = function disableOthers(list, track) { 6006 for (var i = 0; i < list.length; i++) { 6007 if (!Object.keys(list[i]).length || track.id === list[i].id) { 6008 continue; 6009 } 6010 // another video track is enabled, disable it 6011 list[i].selected = false; 6012 } 6013}; 6014 6015/** 6016 * The current list of {@link VideoTrack} for a video. 6017 * 6018 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#videotracklist} 6019 * @extends TrackList 6020 */ 6021 6022var VideoTrackList = function (_TrackList) { 6023 inherits(VideoTrackList, _TrackList); 6024 6025 /** 6026 * Create an instance of this class. 6027 * 6028 * @param {VideoTrack[]} [tracks=[]] 6029 * A list of `VideoTrack` to instantiate the list with. 6030 */ 6031 function VideoTrackList() { 6032 var _this, _ret; 6033 6034 var tracks = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; 6035 classCallCheck(this, VideoTrackList); 6036 6037 var list = void 0; 6038 6039 // make sure only 1 track is enabled 6040 // sorted from last index to first index 6041 for (var i = tracks.length - 1; i >= 0; i--) { 6042 if (tracks[i].selected) { 6043 disableOthers$1(tracks, tracks[i]); 6044 break; 6045 } 6046 } 6047 6048 // IE8 forces us to implement inheritance ourselves 6049 // as it does not support Object.defineProperty properly 6050 if (IS_IE8) { 6051 list = document_1.createElement('custom'); 6052 for (var prop in TrackList.prototype) { 6053 if (prop !== 'constructor') { 6054 list[prop] = TrackList.prototype[prop]; 6055 } 6056 } 6057 for (var _prop in VideoTrackList.prototype) { 6058 if (_prop !== 'constructor') { 6059 list[_prop] = VideoTrackList.prototype[_prop]; 6060 } 6061 } 6062 } 6063
6064 list = (_this = possibleConstructorReturn(this, _TrackList.call(this, tracks, list)), _this); 6065 list.changing_ = false; 6066 6067 /** 6068 * @member {number} VideoTrackList#selectedIndex 6069 * The current index of the selected {@link VideoTrack`}. 6070 */ 6071 Object.defineProperty(list, 'selectedIndex', { 6072 get: function get$$1() { 6073 for (var _i = 0; _i < this.length; _i++) { 6074 if (this[_i].selected) { 6075 return _i; 6076 } 6077 } 6078 return -1; 6079 }, 6080 set: function set$$1() {} 6081 }); 6082 6083 return _ret = list, possibleConstructorReturn(_this, _ret); 6084 } 6085 6086 /** 6087 * Add a {@link VideoTrack} to the `VideoTrackList`. 6088 * 6089 * @param {VideoTrack} track 6090 * The VideoTrack to add to the list 6091 * 6092 * @fires TrackList#addtrack 6093 */ 6094 6095 6096 VideoTrackList.prototype.addTrack = function addTrack(track) { 6097 var _this2 = this; 6098 6099 if (track.selected) { 6100 disableOthers$1(this, track); 6101 } 6102 6103 _TrackList.prototype.addTrack.call(this, track); 6104 // native tracks don't have this 6105 if (!track.addEventListener) { 6106 return; 6107 } 6108 6109 /** 6110 * @listens VideoTrack#selectedchange 6111 * @fires TrackList#change 6112 */ 6113 track.addEventListener('selectedchange', function () { 6114 if (_this2.changing_) { 6115 return; 6116 } 6117 _this2.changing_ = true; 6118 disableOthers$1(_this2, track); 6119 _this2.changing_ = false; 6120 _this2.trigger('change'); 6121 }); 6122 }; 6123 6124 return VideoTrackList; 6125}(TrackList); 6126 6127/** 6128 * @file text-track-list.js 6129 */ 6130/** 6131 * The current list of {@link TextTrack} for a media file. 6132 * 6133 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttracklist} 6134 * @extends TrackList 6135 */ 6136 6137var TextTrackList = function (_TrackList) { 6138 inherits(TextTrackList, _TrackList); 6139 6140 /** 6141 * Create an instance of this class. 6142 * 6143 * @param {TextTrack[]} [tracks=[]] 6144 * A list of `TextTrack` to instantiate the list with. 6145 */ 6146 function TextTrackList() { 6147 var _this, _ret; 6148 6149 var tracks = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; 6150 classCallCheck(this, TextTrackList); 6151 6152 var list = void 0; 6153 6154 // IE8 forces us to implement inheritance ourselves 6155 // as it does not support Object.defineProperty properly 6156 if (IS_IE8) { 6157 list = document_1.createElement('custom'); 6158 for (var prop in TrackList.prototype) { 6159 if (prop !== 'constructor') { 6160 list[prop] = TrackList.prototype[prop]; 6161 } 6162 } 6163 for (var _prop in TextTrackList.prototype) { 6164 if (_prop !== 'constructor') { 6165 list[_prop] = TextTrackList.prototype[_prop]; 6166 } 6167 } 6168 } 6169
vendor: 7,767 bytes, lines 6170-6504
6170 list = (_this = possibleConstructorReturn(this, _TrackList.call(this, tracks, list)), _this); 6171 return _ret = list, possibleConstructorReturn(_this, _ret); 6172 } 6173 6174 /** 6175 * Add a {@link TextTrack} to the `TextTrackList` 6176 * 6177 * @param {TextTrack} track 6178 * The text track to add to the list. 6179 * 6180 * @fires TrackList#addtrack 6181 */ 6182 6183 6184 TextTrackList.prototype.addTrack = function addTrack(track) { 6185 _TrackList.prototype.addTrack.call(this, track); 6186 6187 /** 6188 * @listens TextTrack#modechange 6189 * @fires TrackList#change 6190 */ 6191 track.addEventListener('modechange', bind(this, function () { 6192 this.trigger('change'); 6193 })); 6194 6195 var nonLanguageTextTrackKind = ['metadata', 'chapters']; 6196 6197 if (nonLanguageTextTrackKind.indexOf(track.kind) === -1) { 6198 track.addEventListener('modechange', bind(this, function () { 6199 this.trigger('selectedlanguagechange'); 6200 })); 6201 } 6202 }; 6203 6204 return TextTrackList; 6205}(TrackList); 6206 6207/** 6208 * @file html-track-element-list.js 6209 */ 6210 6211/** 6212 * The current list of {@link HtmlTrackElement}s. 6213 */ 6214 6215var HtmlTrackElementList = function () { 6216 6217 /** 6218 * Create an instance of this class. 6219 * 6220 * @param {HtmlTrackElement[]} [tracks=[]] 6221 * A list of `HtmlTrackElement` to instantiate the list with. 6222 */ 6223 function HtmlTrackElementList() { 6224 var trackElements = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; 6225 classCallCheck(this, HtmlTrackElementList); 6226 6227 var list = this; // eslint-disable-line 6228 6229 if (IS_IE8) { 6230 list = document_1.createElement('custom'); 6231 6232 for (var prop in HtmlTrackElementList.prototype) { 6233 if (prop !== 'constructor') { 6234 list[prop] = HtmlTrackElementList.prototype[prop]; 6235 } 6236 } 6237 } 6238 6239 list.trackElements_ = []; 6240 6241 /** 6242 * @memberof HtmlTrackElementList 6243 * @member {number} length 6244 * The current number of `Track`s in the this Trackist. 6245 * @instance 6246 */ 6247 Object.defineProperty(list, 'length', { 6248 get: function get$$1() { 6249 return this.trackElements_.length; 6250 } 6251 }); 6252 6253 for (var i = 0, length = trackElements.length; i < length; i++) { 6254 list.addTrackElement_(trackElements[i]); 6255 } 6256 6257 if (IS_IE8) { 6258 return list; 6259 } 6260 } 6261 6262 /** 6263 * Add an {@link HtmlTrackElement} to the `HtmlTrackElementList` 6264 * 6265 * @param {HtmlTrackElement} trackElement 6266 * The track element to add to the list. 6267 * 6268 * @private 6269 */ 6270 6271 6272 HtmlTrackElementList.prototype.addTrackElement_ = function addTrackElement_(trackElement) { 6273 var index = this.trackElements_.length; 6274 6275 if (!('' + index in this)) { 6276 Object.defineProperty(this, index, { 6277 get: function get$$1() { 6278 return this.trackElements_[index]; 6279 } 6280 }); 6281 } 6282 6283 // Do not add duplicate elements 6284 if (this.trackElements_.indexOf(trackElement) === -1) { 6285 this.trackElements_.push(trackElement); 6286 } 6287 }; 6288 6289 /** 6290 * Get an {@link HtmlTrackElement} from the `HtmlTrackElementList` given an 6291 * {@link TextTrack}. 6292 * 6293 * @param {TextTrack} track 6294 * The track associated with a track element. 6295 * 6296 * @return {HtmlTrackElement|undefined} 6297 * The track element that was found or undefined. 6298 * 6299 * @private 6300 */ 6301 6302 6303 HtmlTrackElementList.prototype.getTrackElementByTrack_ = function getTrackElementByTrack_(track) { 6304 var trackElement_ = void 0; 6305 6306 for (var i = 0, length = this.trackElements_.length; i < length; i++) { 6307 if (track === this.trackElements_[i].track) { 6308 trackElement_ = this.trackElements_[i]; 6309 6310 break; 6311 } 6312 } 6313 6314 return trackElement_; 6315 }; 6316 6317 /** 6318 * Remove a {@link HtmlTrackElement} from the `HtmlTrackElementList` 6319 * 6320 * @param {HtmlTrackElement} trackElement 6321 * The track element to remove from the list. 6322 * 6323 * @private 6324 */ 6325 6326 6327 HtmlTrackElementList.prototype.removeTrackElement_ = function removeTrackElement_(trackElement) { 6328 for (var i = 0, length = this.trackElements_.length; i < length; i++) { 6329 if (trackElement === this.trackElements_[i]) { 6330 this.trackElements_.splice(i, 1); 6331 6332 break; 6333 } 6334 } 6335 }; 6336 6337 return HtmlTrackElementList; 6338}(); 6339 6340/** 6341 * @file text-track-cue-list.js 6342 */ 6343/** 6344 * @typedef {Object} TextTrackCueList~TextTrackCue 6345 * 6346 * @property {string} id 6347 * The unique id for this text track cue 6348 * 6349 * @property {number} startTime 6350 * The start time for this text track cue 6351 * 6352 * @property {number} endTime 6353 * The end time for this text track cue 6354 * 6355 * @property {boolean} pauseOnExit 6356 * Pause when the end time is reached if true. 6357 * 6358 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackcue} 6359 */ 6360 6361/** 6362 * A List of TextTrackCues. 6363 * 6364 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackcuelist} 6365 */ 6366 6367var TextTrackCueList = function () { 6368 6369 /** 6370 * Create an instance of this class.. 6371 * 6372 * @param {Array} cues 6373 * A list of cues to be initialized with 6374 */ 6375 function TextTrackCueList(cues) { 6376 classCallCheck(this, TextTrackCueList); 6377 6378 var list = this; // eslint-disable-line 6379 6380 if (IS_IE8) { 6381 list = document_1.createElement('custom'); 6382 6383 for (var prop in TextTrackCueList.prototype) { 6384 if (prop !== 'constructor') { 6385 list[prop] = TextTrackCueList.prototype[prop]; 6386 } 6387 } 6388 } 6389 6390 TextTrackCueList.prototype.setCues_.call(list, cues); 6391 6392 /** 6393 * @memberof TextTrackCueList 6394 * @member {number} length 6395 * The current number of `TextTrackCue`s in the TextTrackCueList. 6396 * @instance 6397 */ 6398 Object.defineProperty(list, 'length', { 6399 get: function get$$1() { 6400 return this.length_; 6401 } 6402 }); 6403 6404 if (IS_IE8) { 6405 return list; 6406 } 6407 } 6408 6409 /** 6410 * A setter for cues in this list. Creates getters 6411 * an an index for the cues. 6412 * 6413 * @param {Array} cues 6414 * An array of cues to set 6415 * 6416 * @private 6417 */ 6418 6419 6420 TextTrackCueList.prototype.setCues_ = function setCues_(cues) { 6421 var oldLength = this.length || 0; 6422 var i = 0; 6423 var l = cues.length; 6424 6425 this.cues_ = cues; 6426 this.length_ = cues.length; 6427 6428 var defineProp = function defineProp(index) { 6429 if (!('' + index in this)) { 6430 Object.defineProperty(this, '' + index, { 6431 get: function get$$1() { 6432 return this.cues_[index]; 6433 } 6434 }); 6435 } 6436 }; 6437 6438 if (oldLength < l) { 6439 i = oldLength; 6440 6441 for (; i < l; i++) { 6442 defineProp.call(this, i); 6443 } 6444 } 6445 }; 6446 6447 /** 6448 * Get a `TextTrackCue` that is currently in the `TextTrackCueList` by id. 6449 * 6450 * @param {string} id 6451 * The id of the cue that should be searched for. 6452 * 6453 * @return {TextTrackCueList~TextTrackCue|null} 6454 * A single cue or null if none was found. 6455 */ 6456 6457 6458 TextTrackCueList.prototype.getCueById = function getCueById(id) { 6459 var result = null; 6460 6461 for (var i = 0, l = this.length; i < l; i++) { 6462 var cue = this[i]; 6463 6464 if (cue.id === id) { 6465 result = cue; 6466 break; 6467 } 6468 } 6469 6470 return result; 6471 }; 6472 6473 return TextTrackCueList; 6474}(); 6475 6476/** 6477 * @file track-kinds.js 6478 */ 6479 6480/** 6481 * All possible `VideoTrackKind`s 6482 * 6483 * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-videotrack-kind 6484 * @typedef VideoTrack~Kind 6485 * @enum 6486 */ 6487var VideoTrackKind = { 6488 alternative: 'alternative', 6489 captions: 'captions', 6490 main: 'main', 6491 sign: 'sign', 6492 subtitles: 'subtitles', 6493 commentary: 'commentary' 6494}; 6495 6496/** 6497 * All possible `AudioTrackKind`s 6498 * 6499 * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-audiotrack-kind 6500 * @typedef AudioTrack~Kind 6501 * @enum 6502 */ 6503var AudioTrackKind = { 6504 'alternative': 'alternative',
6505 'descriptions': 'descriptions', 6506 'main': 'main', 6507 'main-desc': 'main-desc', 6508 'translation': 'translation', 6509 'commentary': 'commentary' 6510}; 6511 6512/** 6513 * All possible `TextTrackKind`s 6514 * 6515 * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-texttrack-kind 6516 * @typedef TextTrack~Kind 6517 * @enum 6518 */ 6519var TextTrackKind = { 6520 subtitles: 'subtitles', 6521 captions: 'captions', 6522 descriptions: 'descriptions', 6523 chapters: 'chapters', 6524 metadata: 'metadata' 6525}; 6526 6527/** 6528 * All possible `TextTrackMode`s 6529 * 6530 * @see https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackmode 6531 * @typedef TextTrack~Mode 6532 * @enum 6533 */ 6534var TextTrackMode = { 6535 disabled: 'disabled', 6536 hidden: 'hidden', 6537 showing: 'showing' 6538}; 6539 6540/** 6541 * @file track.js 6542 */ 6543/** 6544 * A Track class that contains all of the common functionality for {@link AudioTrack}, 6545 * {@link VideoTrack}, and {@link TextTrack}. 6546 * 6547 * > Note: This class should not be used directly 6548 * 6549 * @see {@link https://html.spec.whatwg.org/multipage/embedded-content.html} 6550 * @extends EventTarget 6551 * @abstract 6552 */ 6553 6554var Track = function (_EventTarget) { 6555 inherits(Track, _EventTarget); 6556 6557 /** 6558 * Create an instance of this class. 6559 * 6560 * @param {Object} [options={}] 6561 * Object of option names and values 6562 * 6563 * @param {string} [options.kind=''] 6564 * A valid kind for the track type you are creating. 6565 * 6566 * @param {string} [options.id='vjs_track_' + Guid.newGUID()] 6567 * A unique id for this AudioTrack. 6568 * 6569 * @param {string} [options.label=''] 6570 * The menu label for this track. 6571 * 6572 * @param {string} [options.language=''] 6573 * A valid two character language code. 6574 * 6575 * @abstract 6576 */ 6577 function Track() { 6578 var _ret; 6579 6580 var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; 6581 classCallCheck(this, Track); 6582 6583 var _this = possibleConstructorReturn(this, _EventTarget.call(this)); 6584 6585 var track = _this; // eslint-disable-line 6586 6587 if (IS_IE8) { 6588 track = document_1.createElement('custom'); 6589 for (var prop in Track.prototype) { 6590 if (prop !== 'constructor') { 6591 track[prop] = Track.prototype[prop]; 6592 } 6593 } 6594 } 6595 6596 var trackProps = { 6597 id: options.id || 'vjs_track_' + newGUID(), 6598 kind: options.kind || '', 6599 label: options.label || '', 6600 language: options.language || '' 6601 }; 6602 6603 /** 6604 * @memberof Track 6605 * @member {string} id 6606 * The id of this track. Cannot be changed after creation. 6607 * @instance 6608 * 6609 * @readonly 6610 */ 6611 6612 /** 6613 * @memberof Track 6614 * @member {string} kind 6615 * The kind of track that this is. Cannot be changed after creation. 6616 * @instance 6617 * 6618 * @readonly 6619 */ 6620 6621 /** 6622 * @memberof Track 6623 * @member {string} label 6624 * The label of this track. Cannot be changed after creation. 6625 * @instance 6626 * 6627 * @readonly 6628 */ 6629 6630 /** 6631 * @memberof Track 6632 * @member {string} language 6633 * The two letter language code for this track. Cannot be changed after 6634 * creation. 6635 * @instance 6636 * 6637 * @readonly 6638 */ 6639 6640 var _loop = function _loop(key) { 6641 Object.defineProperty(track, key, { 6642 get: function get$$1() { 6643 return trackProps[key]; 6644 }, 6645 set: function set$$1() {} 6646 }); 6647 }; 6648 6649 for (var key in trackProps) { 6650 _loop(key); 6651 } 6652 6653 return _ret = track, possibleConstructorReturn(_this, _ret); 6654 } 6655 6656 return Track; 6657}(EventTarget); 6658 6659/** 6660 * @file url.js 6661 * @module url 6662 */ 6663/** 6664 * @typedef {Object} url:URLObject 6665 * 6666 * @property {string} protocol 6667 * The protocol of the url that was parsed. 6668 * 6669 * @property {string} hostname 6670 * The hostname of the url that was parsed. 6671 * 6672 * @property {string} port 6673 * The port of the url that was parsed. 6674 * 6675 * @property {string} pathname 6676 * The pathname of the url that was parsed. 6677 * 6678 * @property {string} search 6679 * The search query of the url that was parsed. 6680 * 6681 * @property {string} hash 6682 * The hash of the url that was parsed. 6683 * 6684 * @property {string} host 6685 * The host of the url that was parsed. 6686 */ 6687 6688/** 6689 * Resolve and parse the elements of a URL. 6690 * 6691 * @param {String} url 6692 * The url to parse 6693 * 6694 * @return {url:URLObject} 6695 * An object of url details 6696 */ 6697var parseUrl = function parseUrl(url) { 6698 var props = ['protocol', 'hostname', 'port', 'pathname', 'search', 'hash', 'host']; 6699 6700 // add the url to an anchor and let the browser parse the URL 6701 var a = document_1.createElement('a'); 6702 6703 a.href = url; 6704 6705 // IE8 (and 9?) Fix
6706 // ie8 doesn't parse the URL correctly until the anchor is actually 6707 // added to the body, and an innerHTML is needed to trigger the parsing 6708 var addToBody = a.host === '' && a.protocol !== 'file:'; 6709 var div = void 0; 6710 6711 if (addToBody) { 6712 div = document_1.createElement('div'); 6713 div.innerHTML = '<a href="' + url + '"></a>'; 6714 a = div.firstChild; 6715 // prevent the div from affecting layout 6716 div.setAttribute('style', 'display:none; position:absolute;'); 6717 document_1.body.appendChild(div); 6718 } 6719 6720 // Copy the specific URL properties to a new object 6721 // This is also needed for IE8 because the anchor loses its 6722 // properties when it's removed from the dom 6723 var details = {}; 6724 6725 for (var i = 0; i < props.length; i++) { 6726 details[props[i]] = a[props[i]]; 6727 } 6728 6729 // IE9 adds the port to the host property unlike everyone else. If 6730 // a port identifier is added for standard ports, strip it. 6731 if (details.protocol === 'http:') { 6732 details.host = details.host.replace(/:80$/, ''); 6733 } 6734 6735 if (details.protocol === 'https:') { 6736 details.host = details.host.replace(/:443$/, ''); 6737 } 6738 6739 if (addToBody) { 6740 document_1.body.removeChild(div); 6741 } 6742 6743 return details; 6744}; 6745 6746/** 6747 * Get absolute version of relative URL. Used to tell flash correct URL. 6748 * 6749 * 6750 * @param {string} url 6751 * URL to make absolute 6752 * 6753 * @return {string} 6754 * Absolute URL 6755 * 6756 * @see http://stackoverflow.com/questions/470832/getting-an-absolute-url-from-a-relative-one-ie6-issue 6757 */ 6758var getAbsoluteURL = function getAbsoluteURL(url) { 6759 // Check if absolute URL 6760 if (!url.match(/^https?:\/\//)) { 6761 // Convert to absolute URL. Flash hosted off-site needs an absolute URL. 6762 var div = document_1.createElement('div'); 6763 6764 div.innerHTML = '<a href="' + url + '">x</a>'; 6765 url = div.firstChild.href; 6766 } 6767 6768 return url; 6769}; 6770 6771/** 6772 * Returns the extension of the passed file name. It will return an empty string 6773 * if passed an invalid path. 6774 * 6775 * @param {string} path 6776 * The fileName path like '/path/to/file.mp4' 6777 * 6778 * @returns {string} 6779 * The extension in lower case or an empty string if no 6780 * extension could be found. 6781 */ 6782var getFileExtension = function getFileExtension(path) { 6783 if (typeof path === 'string') { 6784 var splitPathRe = /^(\/?)([\s\S]*?)((?:\.{1,2}|[^\/]+?)(\.([^\.\/\?]+)))(?:[\/]*|[\?].*)$/i; 6785 var pathParts = splitPathRe.exec(path); 6786 6787 if (pathParts) { 6788 return pathParts.pop().toLowerCase(); 6789 } 6790 } 6791 6792 return ''; 6793}; 6794 6795/** 6796 * Returns whether the url passed is a cross domain request or not. 6797 * 6798 * @param {string} url 6799 * The url to check. 6800 * 6801 * @return {boolean} 6802 * Whether it is a cross domain request or not. 6803 */ 6804var isCrossOrigin = function isCrossOrigin(url) { 6805 var winLoc = window_1.location; 6806 var urlInfo = parseUrl(url); 6807 6808 // IE8 protocol relative urls will return ':' for protocol 6809 var srcProtocol = urlInfo.protocol === ':' ? winLoc.protocol : urlInfo.protocol; 6810 6811 // Check if url is for another domain/origin 6812 // IE8 doesn't know location.origin, so we won't rely on it here 6813 var crossOrigin = srcProtocol + urlInfo.host !== winLoc.protocol + winLoc.host; 6814 6815 return crossOrigin; 6816}; 6817 6818var Url = (Object.freeze || Object)({ 6819 parseUrl: parseUrl, 6820 getAbsoluteURL: getAbsoluteURL, 6821 getFileExtension: getFileExtension, 6822 isCrossOrigin: isCrossOrigin 6823}); 6824 6825var isFunction_1 = isFunction; 6826 6827var toString$1 = Object.prototype.toString; 6828 6829function isFunction (fn) { 6830 var string = toString$1.call(fn); 6831 return string === '[object Function]' || 6832 (typeof fn === 'function' && string !== '[object RegExp]') || 6833 (typeof window !== 'undefined' && 6834 // IE8 and below 6835 (fn === window.setTimeout || 6836 fn === window.alert || 6837 fn === window.confirm || 6838 fn === window.prompt)) 6839} 6840 6841var trim_1 = createCommonjsModule(function (module, exports) { 6842exports = module.exports = trim; 6843 6844function trim(str){ 6845 return str.replace(/^\s*|\s*$/g, ''); 6846} 6847 6848exports.left = function(str){ 6849 return str.replace(/^\s*/, ''); 6850}; 6851 6852exports.right = function(str){ 6853 return str.replace(/\s*$/, ''); 6854}; 6855}); 6856 6857var forEach_1 = forEach; 6858 6859var toString$2 = Object.prototype.toString; 6860var hasOwnProperty = Object.prototype.hasOwnProperty; 6861 6862function forEach(list, iterator, context) { 6863 if (!isFunction_1(iterator)) { 6864 throw new TypeError('iterator must be a function') 6865 } 6866 6867 if (arguments.length < 3) { 6868 context = this; 6869 } 6870 6871 if (toString$2.call(list) === '[object Array]')
6872 forEachArray$1(list, iterator, context); 6873 else if (typeof list === 'string') 6874 forEachString(list, iterator, context); 6875 else 6876 forEachObject(list, iterator, context); 6877} 6878 6879function forEachArray$1(array, iterator, context) { 6880 for (var i = 0, len = array.length; i < len; i++) { 6881 if (hasOwnProperty.call(array, i)) { 6882 iterator.call(context, array[i], i, array); 6883 } 6884 } 6885} 6886 6887function forEachString(string, iterator, context) { 6888 for (var i = 0, len = string.length; i < len; i++) { 6889 // no such thing as a sparse string. 6890 iterator.call(context, string.charAt(i), i, string); 6891 } 6892} 6893 6894function forEachObject(object, iterator, context) { 6895 for (var k in object) { 6896 if (hasOwnProperty.call(object, k)) { 6897 iterator.call(context, object[k], k, object); 6898 } 6899 } 6900} 6901 6902var isArray = function(arg) { 6903 return Object.prototype.toString.call(arg) === '[object Array]'; 6904 }; 6905 6906var parseHeaders = function (headers) { 6907 if (!headers) 6908 return {} 6909 6910 var result = {}; 6911 6912 forEach_1( 6913 trim_1(headers).split('\n') 6914 , function (row) { 6915 var index = row.indexOf(':') 6916 , key = trim_1(row.slice(0, index)).toLowerCase() 6917 , value = trim_1(row.slice(index + 1)); 6918 6919 if (typeof(result[key]) === 'undefined') { 6920 result[key] = value; 6921 } else if (isArray(result[key])) { 6922 result[key].push(value); 6923 } else { 6924 result[key] = [ result[key], value ]; 6925 } 6926 } 6927 ); 6928 6929 return result 6930}; 6931 6932var immutable = extend; 6933 6934var hasOwnProperty$1 = Object.prototype.hasOwnProperty; 6935 6936function extend() { 6937 var target = {}; 6938 6939 for (var i = 0; i < arguments.length; i++) { 6940 var source = arguments[i]; 6941 6942 for (var key in source) { 6943 if (hasOwnProperty$1.call(source, key)) { 6944 target[key] = source[key]; 6945 } 6946 } 6947 } 6948 6949 return target 6950} 6951 6952var xhr = createXHR; 6953createXHR.XMLHttpRequest = window_1.XMLHttpRequest || noop; 6954createXHR.XDomainRequest = "withCredentials" in (new createXHR.XMLHttpRequest()) ? createXHR.XMLHttpRequest : window_1.XDomainRequest; 6955 6956forEachArray(["get", "put", "post", "patch", "head", "delete"], function(method) { 6957 createXHR[method === "delete" ? "del" : method] = function(uri, options, callback) { 6958 options = initParams(uri, options, callback); 6959 options.method = method.toUpperCase(); 6960 return _createXHR(options) 6961 }; 6962}); 6963 6964function forEachArray(array, iterator) { 6965 for (var i = 0; i < array.length; i++) { 6966 iterator(array[i]); 6967 } 6968} 6969 6970function isEmpty(obj){ 6971 for(var i in obj){ 6972 if(obj.hasOwnProperty(i)) return false 6973 } 6974 return true 6975} 6976 6977function initParams(uri, options, callback) { 6978 var params = uri; 6979 6980 if (isFunction_1(options)) { 6981 callback = options; 6982 if (typeof uri === "string") { 6983 params = {uri:uri}; 6984 } 6985 } else { 6986 params = immutable(options, {uri: uri}); 6987 } 6988 6989 params.callback = callback; 6990 return params 6991} 6992 6993function createXHR(uri, options, callback) { 6994 options = initParams(uri, options, callback); 6995 return _createXHR(options) 6996} 6997 6998function _createXHR(options) { 6999 if(typeof options.callback === "undefined"){ 7000 throw new Error("callback argument missing") 7001 } 7002 7003 var called = false; 7004 var callback = function cbOnce(err, response, body){ 7005 if(!called){ 7006 called = true; 7007 options.callback(err, response, body); 7008 } 7009 }; 7010 7011 function readystatechange() { 7012 if (xhr.readyState === 4) { 7013 setTimeout(loadFunc, 0); 7014 } 7015 } 7016 7017 function getBody() { 7018 // Chrome with requestType=blob throws errors arround when even testing access to responseText 7019 var body = undefined; 7020 7021 if (xhr.response) { 7022 body = xhr.response; 7023 } else { 7024 body = xhr.responseText || getXml(xhr); 7025 } 7026 7027 if (isJson) { 7028 try { 7029 body = JSON.parse(body); 7030 } catch (e) {} 7031 } 7032 7033 return body 7034 } 7035 7036 function errorFunc(evt) { 7037 clearTimeout(timeoutTimer); 7038 if(!(evt instanceof Error)){ 7039 evt = new Error("" + (evt || "Unknown XMLHttpRequest Error") ); 7040 } 7041 evt.statusCode = 0; 7042 return callback(evt, failureResponse) 7043 } 7044 7045 // will load the data & process the response in a special response object 7046 function loadFunc() { 7047 if (aborted) return 7048 var status; 7049 clearTimeout(timeoutTimer); 7050 if(options.useXDR && xhr.status===undefined) { 7051 //IE8 CORS GET successful response doesn't have a status field, but body is fine 7052 status = 200; 7053 } else { 7054 status = (xhr.status === 1223 ? 204 : xhr.status); 7055 } 7056 var response = failureResponse; 7057 var err = null; 7058 7059 if (status !== 0){ 7060 response = { 7061 body: getBody(), 7062 statusCode: status, 7063 method: method, 7064 headers: {},
vendor: 4,867 bytes, lines 7065-7225
7065 url: uri, 7066 rawRequest: xhr 7067 }; 7068 if(xhr.getAllResponseHeaders){ //remember xhr can in fact be XDR for CORS in IE 7069 response.headers = parseHeaders(xhr.getAllResponseHeaders()); 7070 } 7071 } else { 7072 err = new Error("Internal XMLHttpRequest Error"); 7073 } 7074 return callback(err, response, response.body) 7075 } 7076 7077 var xhr = options.xhr || null; 7078 7079 if (!xhr) { 7080 if (options.cors || options.useXDR) { 7081 xhr = new createXHR.XDomainRequest(); 7082 }else{ 7083 xhr = new createXHR.XMLHttpRequest(); 7084 } 7085 } 7086 7087 var key; 7088 var aborted; 7089 var uri = xhr.url = options.uri || options.url; 7090 var method = xhr.method = options.method || "GET"; 7091 var body = options.body || options.data; 7092 var headers = xhr.headers = options.headers || {}; 7093 var sync = !!options.sync; 7094 var isJson = false; 7095 var timeoutTimer; 7096 var failureResponse = { 7097 body: undefined, 7098 headers: {}, 7099 statusCode: 0, 7100 method: method, 7101 url: uri, 7102 rawRequest: xhr 7103 }; 7104 7105 if ("json" in options && options.json !== false) { 7106 isJson = true; 7107 headers["accept"] || headers["Accept"] || (headers["Accept"] = "application/json"); //Don't override existing accept header declared by user 7108 if (method !== "GET" && method !== "HEAD") { 7109 headers["content-type"] || headers["Content-Type"] || (headers["Content-Type"] = "application/json"); //Don't override existing accept header declared by user 7110 body = JSON.stringify(options.json === true ? body : options.json); 7111 } 7112 } 7113 7114 xhr.onreadystatechange = readystatechange; 7115 xhr.onload = loadFunc; 7116 xhr.onerror = errorFunc; 7117 // IE9 must have onprogress be set to a unique function. 7118 xhr.onprogress = function () { 7119 // IE must die 7120 }; 7121 xhr.onabort = function(){ 7122 aborted = true; 7123 }; 7124 xhr.ontimeout = errorFunc; 7125 xhr.open(method, uri, !sync, options.username, options.password); 7126 //has to be after open 7127 if(!sync) { 7128 xhr.withCredentials = !!options.withCredentials; 7129 } 7130 // Cannot set timeout with sync request 7131 // not setting timeout on the xhr object, because of old webkits etc. not handling that correctly 7132 // both npm's request and jquery 1.x use this kind of timeout, so this is being consistent 7133 if (!sync && options.timeout > 0 ) { 7134 timeoutTimer = setTimeout(function(){ 7135 if (aborted) return 7136 aborted = true;//IE9 may still call readystatechange 7137 xhr.abort("timeout"); 7138 var e = new Error("XMLHttpRequest timeout"); 7139 e.code = "ETIMEDOUT"; 7140 errorFunc(e); 7141 }, options.timeout ); 7142 } 7143 7144 if (xhr.setRequestHeader) { 7145 for(key in headers){ 7146 if(headers.hasOwnProperty(key)){ 7147 xhr.setRequestHeader(key, headers[key]); 7148 } 7149 } 7150 } else if (options.headers && !isEmpty(options.headers)) { 7151 throw new Error("Headers cannot be set on an XDomainRequest object") 7152 } 7153 7154 if ("responseType" in options) { 7155 xhr.responseType = options.responseType; 7156 } 7157 7158 if ("beforeSend" in options && 7159 typeof options.beforeSend === "function" 7160 ) { 7161 options.beforeSend(xhr); 7162 } 7163 7164 // Microsoft Edge browser sends "undefined" when send is called with undefined value. 7165 // XMLHttpRequest spec says to pass null as body to indicate no body 7166 // See https://github.com/naugtur/xhr/issues/100. 7167 xhr.send(body || null); 7168 7169 return xhr 7170 7171 7172} 7173 7174function getXml(xhr) { 7175 if (xhr.responseType === "document") { 7176 return xhr.responseXML 7177 } 7178 var firefoxBugTakenEffect = xhr.responseXML && xhr.responseXML.documentElement.nodeName === "parsererror"; 7179 if (xhr.responseType === "" && !firefoxBugTakenEffect) { 7180 return xhr.responseXML 7181 } 7182 7183 return null 7184} 7185 7186function noop() {} 7187 7188/** 7189 * @file text-track.js 7190 */ 7191/** 7192 * Takes a webvtt file contents and parses it into cues 7193 * 7194 * @param {string} srcContent 7195 * webVTT file contents 7196 * 7197 * @param {TextTrack} track 7198 * TextTrack to add cues to. Cues come from the srcContent. 7199 * 7200 * @private 7201 */ 7202var parseCues = function parseCues(srcContent, track) { 7203 var parser = new window_1.WebVTT.Parser(window_1, window_1.vttjs, window_1.WebVTT.StringDecoder()); 7204 var errors = []; 7205 7206 parser.oncue = function (cue) { 7207 track.addCue(cue); 7208 }; 7209 7210 parser.onparsingerror = function (error) { 7211 errors.push(error); 7212 }; 7213 7214 parser.onflush = function () { 7215 track.trigger({ 7216 type: 'loadeddata', 7217 target: track 7218 }); 7219 }; 7220 7221 parser.parse(srcContent); 7222 if (errors.length > 0) { 7223 if (window_1.console && window_1.console.groupCollapsed) { 7224 window_1.console.groupCollapsed('Text Track parsing errors for ' + track.src); 7225 }
7226 errors.forEach(function (error) { 7227 return log$1.error(error); 7228 }); 7229 if (window_1.console && window_1.console.groupEnd) { 7230 window_1.console.groupEnd(); 7231 } 7232 } 7233 7234 parser.flush(); 7235}; 7236 7237/** 7238 * Load a `TextTrack` from a specifed url. 7239 * 7240 * @param {string} src 7241 * Url to load track from. 7242 * 7243 * @param {TextTrack} track 7244 * Track to add cues to. Comes from the content at the end of `url`. 7245 * 7246 * @private 7247 */ 7248var loadTrack = function loadTrack(src, track) { 7249 var opts = { 7250 uri: src 7251 }; 7252 var crossOrigin = isCrossOrigin(src); 7253 7254 if (crossOrigin) { 7255 opts.cors = crossOrigin; 7256 } 7257 7258 xhr(opts, bind(this, function (err, response, responseBody) { 7259 if (err) { 7260 return log$1.error(err, response); 7261 } 7262 7263 track.loaded_ = true; 7264 7265 // Make sure that vttjs has loaded, otherwise, wait till it finished loading 7266 // NOTE: this is only used for the alt/video.novtt.js build 7267 if (typeof window_1.WebVTT !== 'function') { 7268 if (track.tech_) { 7269 var loadHandler = function loadHandler() { 7270 return parseCues(responseBody, track); 7271 }; 7272 7273 track.tech_.on('vttjsloaded', loadHandler); 7274 track.tech_.on('vttjserror', function () { 7275 log$1.error('vttjs failed to load, stopping trying to process ' + track.src); 7276 track.tech_.off('vttjsloaded', loadHandler); 7277 }); 7278 } 7279 } else { 7280 parseCues(responseBody, track); 7281 } 7282 })); 7283}; 7284 7285/** 7286 * A representation of a single `TextTrack`. 7287 * 7288 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrack} 7289 * @extends Track 7290 */ 7291 7292var TextTrack = function (_Track) { 7293 inherits(TextTrack, _Track); 7294 7295 /** 7296 * Create an instance of this class. 7297 * 7298 * @param {Object} options={} 7299 * Object of option names and values 7300 * 7301 * @param {Tech} options.tech 7302 * A reference to the tech that owns this TextTrack. 7303 * 7304 * @param {TextTrack~Kind} [options.kind='subtitles'] 7305 * A valid text track kind. 7306 * 7307 * @param {TextTrack~Mode} [options.mode='disabled'] 7308 * A valid text track mode. 7309 * 7310 * @param {string} [options.id='vjs_track_' + Guid.newGUID()] 7311 * A unique id for this TextTrack. 7312 * 7313 * @param {string} [options.label=''] 7314 * The menu label for this track. 7315 * 7316 * @param {string} [options.language=''] 7317 * A valid two character language code. 7318 * 7319 * @param {string} [options.srclang=''] 7320 * A valid two character language code. An alternative, but deprioritized 7321 * vesion of `options.language` 7322 * 7323 * @param {string} [options.src] 7324 * A url to TextTrack cues. 7325 * 7326 * @param {boolean} [options.default] 7327 * If this track should default to on or off. 7328 */ 7329 function TextTrack() { 7330 var _this, _ret; 7331 7332 var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; 7333 classCallCheck(this, TextTrack); 7334 7335 if (!options.tech) { 7336 throw new Error('A tech was not provided.'); 7337 } 7338 7339 var settings = mergeOptions(options, { 7340 kind: TextTrackKind[options.kind] || 'subtitles', 7341 language: options.language || options.srclang || '' 7342 }); 7343 var mode = TextTrackMode[settings.mode] || 'disabled'; 7344 var default_ = settings['default']; 7345 7346 if (settings.kind === 'metadata' || settings.kind === 'chapters') { 7347 mode = 'hidden'; 7348 } 7349 // on IE8 this will be a document element 7350 // for every other browser this will be a normal object 7351 var tt = (_this = possibleConstructorReturn(this, _Track.call(this, settings)), _this); 7352 7353 tt.tech_ = settings.tech; 7354 7355 if (IS_IE8) { 7356 for (var prop in TextTrack.prototype) { 7357 if (prop !== 'constructor') { 7358 tt[prop] = TextTrack.prototype[prop]; 7359 } 7360 } 7361 } 7362 7363 tt.cues_ = []; 7364 tt.activeCues_ = []; 7365 7366 var cues = new TextTrackCueList(tt.cues_); 7367 var activeCues = new TextTrackCueList(tt.activeCues_); 7368 var changed = false; 7369 var timeupdateHandler = bind(tt, function () { 7370 7371 // Accessing this.activeCues for the side-effects of updating itself 7372 // due to it's nature as a getter function. Do not remove or cues will 7373 // stop updating! 7374 /* eslint-disable no-unused-expressions */ 7375 this.activeCues; 7376 /* eslint-enable no-unused-expressions */ 7377 if (changed) { 7378 this.trigger('cuechange'); 7379 changed = false;
vendor: 6,553 bytes, lines 7380-7635
7380 } 7381 }); 7382 7383 if (mode !== 'disabled') { 7384 tt.tech_.ready(function () { 7385 tt.tech_.on('timeupdate', timeupdateHandler); 7386 }, true); 7387 } 7388 7389 /** 7390 * @memberof TextTrack 7391 * @member {boolean} default 7392 * If this track was set to be on or off by default. Cannot be changed after 7393 * creation. 7394 * @instance 7395 * 7396 * @readonly 7397 */ 7398 Object.defineProperty(tt, 'default', { 7399 get: function get$$1() { 7400 return default_; 7401 }, 7402 set: function set$$1() {} 7403 }); 7404 7405 /** 7406 * @memberof TextTrack 7407 * @member {string} mode 7408 * Set the mode of this TextTrack to a valid {@link TextTrack~Mode}. Will 7409 * not be set if setting to an invalid mode. 7410 * @instance 7411 * 7412 * @fires TextTrack#modechange 7413 */ 7414 Object.defineProperty(tt, 'mode', { 7415 get: function get$$1() { 7416 return mode; 7417 }, 7418 set: function set$$1(newMode) { 7419 var _this2 = this; 7420 7421 if (!TextTrackMode[newMode]) { 7422 return; 7423 } 7424 mode = newMode; 7425 if (mode === 'showing') { 7426 7427 this.tech_.ready(function () { 7428 _this2.tech_.on('timeupdate', timeupdateHandler); 7429 }, true); 7430 } 7431 /** 7432 * An event that fires when mode changes on this track. This allows 7433 * the TextTrackList that holds this track to act accordingly. 7434 * 7435 * > Note: This is not part of the spec! 7436 * 7437 * @event TextTrack#modechange 7438 * @type {EventTarget~Event} 7439 */ 7440 this.trigger('modechange'); 7441 } 7442 }); 7443 7444 /** 7445 * @memberof TextTrack 7446 * @member {TextTrackCueList} cues 7447 * The text track cue list for this TextTrack. 7448 * @instance 7449 */ 7450 Object.defineProperty(tt, 'cues', { 7451 get: function get$$1() { 7452 if (!this.loaded_) { 7453 return null; 7454 } 7455 7456 return cues; 7457 }, 7458 set: function set$$1() {} 7459 }); 7460 7461 /** 7462 * @memberof TextTrack 7463 * @member {TextTrackCueList} activeCues 7464 * The list text track cues that are currently active for this TextTrack. 7465 * @instance 7466 */ 7467 Object.defineProperty(tt, 'activeCues', { 7468 get: function get$$1() { 7469 if (!this.loaded_) { 7470 return null; 7471 } 7472 7473 // nothing to do 7474 if (this.cues.length === 0) { 7475 return activeCues; 7476 } 7477 7478 var ct = this.tech_.currentTime(); 7479 var active = []; 7480 7481 for (var i = 0, l = this.cues.length; i < l; i++) { 7482 var cue = this.cues[i]; 7483 7484 if (cue.startTime <= ct && cue.endTime >= ct) { 7485 active.push(cue); 7486 } else if (cue.startTime === cue.endTime && cue.startTime <= ct && cue.startTime + 0.5 >= ct) { 7487 active.push(cue); 7488 } 7489 } 7490 7491 changed = false; 7492 7493 if (active.length !== this.activeCues_.length) { 7494 changed = true; 7495 } else { 7496 for (var _i = 0; _i < active.length; _i++) { 7497 if (this.activeCues_.indexOf(active[_i]) === -1) { 7498 changed = true; 7499 } 7500 } 7501 } 7502 7503 this.activeCues_ = active; 7504 activeCues.setCues_(this.activeCues_); 7505 7506 return activeCues; 7507 }, 7508 set: function set$$1() {} 7509 }); 7510 7511 if (settings.src) { 7512 tt.src = settings.src; 7513 loadTrack(settings.src, tt); 7514 } else { 7515 tt.loaded_ = true; 7516 } 7517 7518 return _ret = tt, possibleConstructorReturn(_this, _ret); 7519 } 7520 7521 /** 7522 * Add a cue to the internal list of cues. 7523 * 7524 * @param {TextTrack~Cue} cue 7525 * The cue to add to our internal list 7526 */ 7527 7528 7529 TextTrack.prototype.addCue = function addCue(originalCue) { 7530 var cue = originalCue; 7531 7532 if (window_1.vttjs && !(originalCue instanceof window_1.vttjs.VTTCue)) { 7533 cue = new window_1.vttjs.VTTCue(originalCue.startTime, originalCue.endTime, originalCue.text); 7534 7535 for (var prop in originalCue) { 7536 if (!(prop in cue)) { 7537 cue[prop] = originalCue[prop]; 7538 } 7539 } 7540 7541 // make sure that `id` is copied over 7542 cue.id = originalCue.id; 7543 cue.originalCue_ = originalCue; 7544 } 7545 7546 var tracks = this.tech_.textTracks(); 7547 7548 for (var i = 0; i < tracks.length; i++) { 7549 if (tracks[i] !== this) { 7550 tracks[i].removeCue(cue); 7551 } 7552 } 7553 7554 this.cues_.push(cue); 7555 this.cues.setCues_(this.cues_); 7556 }; 7557 7558 /** 7559 * Remove a cue from our internal list 7560 * 7561 * @param {TextTrack~Cue} removeCue 7562 * The cue to remove from our internal list 7563 */ 7564 7565 7566 TextTrack.prototype.removeCue = function removeCue(_removeCue) { 7567 var i = this.cues_.length; 7568 7569 while (i--) { 7570 var cue = this.cues_[i]; 7571 7572 if (cue === _removeCue || cue.originalCue_ && cue.originalCue_ === _removeCue) { 7573 this.cues_.splice(i, 1); 7574 this.cues.setCues_(this.cues_); 7575 break; 7576 } 7577 } 7578 }; 7579 7580 return TextTrack; 7581}(Track); 7582 7583/** 7584 * cuechange - One or more cues in the track have become active or stopped being active. 7585 */ 7586 7587 7588TextTrack.prototype.allowedEvents_ = { 7589 cuechange: 'cuechange' 7590}; 7591 7592/** 7593 * A representation of a single `AudioTrack`. If it is part of an {@link AudioTrackList} 7594 * only one `AudioTrack` in the list will be enabled at a time. 7595 * 7596 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotrack} 7597 * @extends Track 7598 */ 7599 7600var AudioTrack = function (_Track) { 7601 inherits(AudioTrack, _Track); 7602 7603 /** 7604 * Create an instance of this class. 7605 * 7606 * @param {Object} [options={}] 7607 * Object of option names and values 7608 * 7609 * @param {AudioTrack~Kind} [options.kind=''] 7610 * A valid audio track kind 7611 * 7612 * @param {string} [options.id='vjs_track_' + Guid.newGUID()] 7613 * A unique id for this AudioTrack. 7614 * 7615 * @param {string} [options.label=''] 7616 * The menu label for this track. 7617 * 7618 * @param {string} [options.language=''] 7619 * A valid two character language code. 7620 * 7621 * @param {boolean} [options.enabled] 7622 * If this track is the one that is currently playing. If this track is part of 7623 * an {@link AudioTrackList}, only one {@link AudioTrack} will be enabled. 7624 */ 7625 function AudioTrack() { 7626 var _this, _ret; 7627 7628 var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; 7629 classCallCheck(this, AudioTrack); 7630 7631 var settings = mergeOptions(options, { 7632 kind: AudioTrackKind[options.kind] || '' 7633 }); 7634 // on IE8 this will be a document element 7635 // for every other browser this will be a normal object
7636 var track = (_this = possibleConstructorReturn(this, _Track.call(this, settings)), _this); 7637 var enabled = false; 7638 7639 if (IS_IE8) { 7640 for (var prop in AudioTrack.prototype) { 7641 if (prop !== 'constructor') { 7642 track[prop] = AudioTrack.prototype[prop]; 7643 } 7644 } 7645 } 7646 /** 7647 * @memberof AudioTrack 7648 * @member {boolean} enabled 7649 * If this `AudioTrack` is enabled or not. When setting this will 7650 * fire {@link AudioTrack#enabledchange} if the state of enabled is changed. 7651 * @instance 7652 * 7653 * @fires VideoTrack#selectedchange 7654 */ 7655 Object.defineProperty(track, 'enabled', { 7656 get: function get$$1() { 7657 return enabled; 7658 }, 7659 set: function set$$1(newEnabled) { 7660 // an invalid or unchanged value 7661 if (typeof newEnabled !== 'boolean' || newEnabled === enabled) { 7662 return; 7663 } 7664 enabled = newEnabled; 7665 7666 /** 7667 * An event that fires when enabled changes on this track. This allows 7668 * the AudioTrackList that holds this track to act accordingly. 7669 * 7670 * > Note: This is not part of the spec! Native tracks will do 7671 * this internally without an event. 7672 * 7673 * @event AudioTrack#enabledchange 7674 * @type {EventTarget~Event} 7675 */ 7676 this.trigger('enabledchange'); 7677 } 7678 }); 7679 7680 // if the user sets this track to selected then 7681 // set selected to that true value otherwise 7682 // we keep it false 7683 if (settings.enabled) { 7684 track.enabled = settings.enabled; 7685 } 7686 track.loaded_ = true; 7687 7688 return _ret = track, possibleConstructorReturn(_this, _ret); 7689 } 7690 7691 return AudioTrack; 7692}(Track); 7693 7694/** 7695 * A representation of a single `VideoTrack`. 7696 * 7697 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#videotrack} 7698 * @extends Track 7699 */ 7700 7701var VideoTrack = function (_Track) { 7702 inherits(VideoTrack, _Track); 7703 7704 /** 7705 * Create an instance of this class. 7706 * 7707 * @param {Object} [options={}] 7708 * Object of option names and values 7709 * 7710 * @param {string} [options.kind=''] 7711 * A valid {@link VideoTrack~Kind} 7712 * 7713 * @param {string} [options.id='vjs_track_' + Guid.newGUID()] 7714 * A unique id for this AudioTrack. 7715 * 7716 * @param {string} [options.label=''] 7717 * The menu label for this track. 7718 * 7719 * @param {string} [options.language=''] 7720 * A valid two character language code. 7721 * 7722 * @param {boolean} [options.selected] 7723 * If this track is the one that is currently playing. 7724 */ 7725 function VideoTrack() { 7726 var _this, _ret; 7727 7728 var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; 7729 classCallCheck(this, VideoTrack); 7730 7731 var settings = mergeOptions(options, { 7732 kind: VideoTrackKind[options.kind] || '' 7733 }); 7734 7735 // on IE8 this will be a document element 7736 // for every other browser this will be a normal object 7737 var track = (_this = possibleConstructorReturn(this, _Track.call(this, settings)), _this); 7738 var selected = false; 7739 7740 if (IS_IE8) { 7741 for (var prop in VideoTrack.prototype) { 7742 if (prop !== 'constructor') { 7743 track[prop] = VideoTrack.prototype[prop]; 7744 } 7745 } 7746 } 7747 7748 /** 7749 * @memberof VideoTrack 7750 * @member {boolean} selected 7751 * If this `VideoTrack` is selected or not. When setting this will 7752 * fire {@link VideoTrack#selectedchange} if the state of selected changed. 7753 * @instance 7754 * 7755 * @fires VideoTrack#selectedchange 7756 */ 7757 Object.defineProperty(track, 'selected', { 7758 get: function get$$1() { 7759 return selected; 7760 }, 7761 set: function set$$1(newSelected) { 7762 // an invalid or unchanged value 7763 if (typeof newSelected !== 'boolean' || newSelected === selected) { 7764 return; 7765 } 7766 selected = newSelected; 7767 7768 /** 7769 * An event that fires when selected changes on this track. This allows 7770 * the VideoTrackList that holds this track to act accordingly. 7771 * 7772 * > Note: This is not part of the spec! Native tracks will do 7773 * this internally without an event. 7774 * 7775 * @event VideoTrack#selectedchange 7776 * @type {EventTarget~Event} 7777 */ 7778 this.trigger('selectedchange'); 7779 } 7780 }); 7781 7782 // if the user sets this track to selected then 7783 // set selected to that true value otherwise 7784 // we keep it false 7785 if (settings.selected) { 7786 track.selected = settings.selected; 7787 } 7788 7789 return _ret = track, possibleConstructorReturn(_this, _ret); 7790 } 7791 7792 return VideoTrack; 7793}(Track); 7794 7795/** 7796 * @file html-track-element.js 7797 */ 7798 7799/** 7800 * @memberof HTMLTrackElement 7801 * @typedef {HTMLTrackElement~ReadyState} 7802 * @enum {number} 7803 */ 7804var NONE = 0; 7805var LOADING = 1; 7806var LOADED = 2; 7807var ERROR = 3; 7808 7809/** 7810 * A single track represented in the DOM. 7811 * 7812 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#htmltrackelement}
7813 * @extends EventTarget 7814 */ 7815 7816var HTMLTrackElement = function (_EventTarget) { 7817 inherits(HTMLTrackElement, _EventTarget); 7818 7819 /** 7820 * Create an instance of this class. 7821 * 7822 * @param {Object} options={} 7823 * Object of option names and values 7824 * 7825 * @param {Tech} options.tech 7826 * A reference to the tech that owns this HTMLTrackElement. 7827 * 7828 * @param {TextTrack~Kind} [options.kind='subtitles'] 7829 * A valid text track kind. 7830 * 7831 * @param {TextTrack~Mode} [options.mode='disabled'] 7832 * A valid text track mode. 7833 * 7834 * @param {string} [options.id='vjs_track_' + Guid.newGUID()] 7835 * A unique id for this TextTrack. 7836 * 7837 * @param {string} [options.label=''] 7838 * The menu label for this track. 7839 * 7840 * @param {string} [options.language=''] 7841 * A valid two character language code. 7842 * 7843 * @param {string} [options.srclang=''] 7844 * A valid two character language code. An alternative, but deprioritized 7845 * vesion of `options.language` 7846 * 7847 * @param {string} [options.src] 7848 * A url to TextTrack cues. 7849 * 7850 * @param {boolean} [options.default] 7851 * If this track should default to on or off. 7852 */ 7853 function HTMLTrackElement() { 7854 var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; 7855 classCallCheck(this, HTMLTrackElement); 7856 7857 var _this = possibleConstructorReturn(this, _EventTarget.call(this)); 7858 7859 var readyState = void 0; 7860 var trackElement = _this; // eslint-disable-line 7861 7862 if (IS_IE8) { 7863 trackElement = document_1.createElement('custom'); 7864 7865 for (var prop in HTMLTrackElement.prototype) { 7866 if (prop !== 'constructor') { 7867 trackElement[prop] = HTMLTrackElement.prototype[prop]; 7868 } 7869 } 7870 } 7871 7872 var track = new TextTrack(options); 7873 7874 trackElement.kind = track.kind; 7875 trackElement.src = track.src; 7876 trackElement.srclang = track.language; 7877 trackElement.label = track.label; 7878 trackElement['default'] = track['default']; 7879 7880 /** 7881 * @memberof HTMLTrackElement 7882 * @member {HTMLTrackElement~ReadyState} readyState 7883 * The current ready state of the track element. 7884 * @instance 7885 */ 7886 Object.defineProperty(trackElement, 'readyState', { 7887 get: function get$$1() { 7888 return readyState; 7889 } 7890 }); 7891 7892 /** 7893 * @memberof HTMLTrackElement 7894 * @member {TextTrack} track 7895 * The underlying TextTrack object. 7896 * @instance 7897 * 7898 */ 7899 Object.defineProperty(trackElement, 'track', { 7900 get: function get$$1() { 7901 return track; 7902 } 7903 }); 7904 7905 readyState = NONE; 7906 7907 /** 7908 * @listens TextTrack#loadeddata 7909 * @fires HTMLTrackElement#load 7910 */ 7911 track.addEventListener('loadeddata', function () { 7912 readyState = LOADED; 7913 7914 trackElement.trigger({ 7915 type: 'load', 7916 target: trackElement 7917 }); 7918 }); 7919 7920 if (IS_IE8) { 7921 var _ret; 7922 7923 return _ret = trackElement, possibleConstructorReturn(_this, _ret); 7924 } 7925 return _this; 7926 } 7927 7928 return HTMLTrackElement; 7929}(EventTarget); 7930 7931HTMLTrackElement.prototype.allowedEvents_ = { 7932 load: 'load' 7933}; 7934 7935HTMLTrackElement.NONE = NONE; 7936HTMLTrackElement.LOADING = LOADING; 7937HTMLTrackElement.LOADED = LOADED; 7938HTMLTrackElement.ERROR = ERROR; 7939 7940/* 7941 * This file contains all track properties that are used in 7942 * player.js, tech.js, html5.js and possibly other techs in the future. 7943 */ 7944 7945var NORMAL = { 7946 audio: { 7947 ListClass: AudioTrackList, 7948 TrackClass: AudioTrack, 7949 capitalName: 'Audio' 7950 }, 7951 video: { 7952 ListClass: VideoTrackList, 7953 TrackClass: VideoTrack, 7954 capitalName: 'Video' 7955 }, 7956 text: { 7957 ListClass: TextTrackList, 7958 TrackClass: TextTrack, 7959 capitalName: 'Text' 7960 } 7961}; 7962 7963Object.keys(NORMAL).forEach(function (type) { 7964 NORMAL[type].getterName = type + 'Tracks'; 7965 NORMAL[type].privateName = type + 'Tracks_'; 7966}); 7967 7968var REMOTE = { 7969 remoteText: { 7970 ListClass: TextTrackList, 7971 TrackClass: TextTrack, 7972 capitalName: 'RemoteText', 7973 getterName: 'remoteTextTracks', 7974 privateName: 'remoteTextTracks_' 7975 }, 7976 remoteTextEl: { 7977 ListClass: HtmlTrackElementList, 7978 TrackClass: HTMLTrackElement, 7979 capitalName: 'RemoteTextTrackEls', 7980 getterName: 'remoteTextTrackEls', 7981 privateName: 'remoteTextTrackEls_' 7982 } 7983}; 7984 7985var ALL = mergeOptions(NORMAL, REMOTE); 7986 7987REMOTE.names = Object.keys(REMOTE); 7988NORMAL.names = Object.keys(NORMAL); 7989ALL.names = [].concat(REMOTE.names).concat(NORMAL.names); 7990 7991/** 7992 * Copyright 2013 vtt.js Contributors 7993 * 7994 * Licensed under the Apache License, Version 2.0 (the "License");
vendor: 16,438 bytes, lines 7995-8532
7995 * you may not use this file except in compliance with the License. 7996 * You may obtain a copy of the License at 7997 * 7998 * http://www.apache.org/licenses/LICENSE-2.0 7999 * 8000 * Unless required by applicable law or agreed to in writing, software 8001 * distributed under the License is distributed on an "AS IS" BASIS, 8002 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 8003 * See the License for the specific language governing permissions and 8004 * limitations under the License. 8005 */ 8006 8007/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 8008/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ 8009var _objCreate = Object.create || (function() { 8010 function F() {} 8011 return function(o) { 8012 if (arguments.length !== 1) { 8013 throw new Error('Object.create shim only accepts one parameter.'); 8014 } 8015 F.prototype = o; 8016 return new F(); 8017 }; 8018})(); 8019 8020// Creates a new ParserError object from an errorData object. The errorData 8021// object should have default code and message properties. The default message 8022// property can be overriden by passing in a message parameter. 8023// See ParsingError.Errors below for acceptable errors. 8024function ParsingError(errorData, message) { 8025 this.name = "ParsingError"; 8026 this.code = errorData.code; 8027 this.message = message || errorData.message; 8028} 8029ParsingError.prototype = _objCreate(Error.prototype); 8030ParsingError.prototype.constructor = ParsingError; 8031 8032// ParsingError metadata for acceptable ParsingErrors. 8033ParsingError.Errors = { 8034 BadSignature: { 8035 code: 0, 8036 message: "Malformed WebVTT signature." 8037 }, 8038 BadTimeStamp: { 8039 code: 1, 8040 message: "Malformed time stamp." 8041 } 8042}; 8043 8044// Try to parse input as a time stamp. 8045function parseTimeStamp(input) { 8046 8047 function computeSeconds(h, m, s, f) { 8048 return (h | 0) * 3600 + (m | 0) * 60 + (s | 0) + (f | 0) / 1000; 8049 } 8050 8051 var m = input.match(/^(\d+):(\d{2})(:\d{2})?\.(\d{3})/); 8052 if (!m) { 8053 return null; 8054 } 8055 8056 if (m[3]) { 8057 // Timestamp takes the form of [hours]:[minutes]:[seconds].[milliseconds] 8058 return computeSeconds(m[1], m[2], m[3].replace(":", ""), m[4]); 8059 } else if (m[1] > 59) { 8060 // Timestamp takes the form of [hours]:[minutes].[milliseconds] 8061 // First position is hours as it's over 59. 8062 return computeSeconds(m[1], m[2], 0, m[4]); 8063 } else { 8064 // Timestamp takes the form of [minutes]:[seconds].[milliseconds] 8065 return computeSeconds(0, m[1], m[2], m[4]); 8066 } 8067} 8068 8069// A settings object holds key/value pairs and will ignore anything but the first 8070// assignment to a specific key. 8071function Settings() { 8072 this.values = _objCreate(null); 8073} 8074 8075Settings.prototype = { 8076 // Only accept the first assignment to any key. 8077 set: function(k, v) { 8078 if (!this.get(k) && v !== "") { 8079 this.values[k] = v; 8080 } 8081 }, 8082 // Return the value for a key, or a default value. 8083 // If 'defaultKey' is passed then 'dflt' is assumed to be an object with 8084 // a number of possible default values as properties where 'defaultKey' is 8085 // the key of the property that will be chosen; otherwise it's assumed to be 8086 // a single value. 8087 get: function(k, dflt, defaultKey) { 8088 if (defaultKey) { 8089 return this.has(k) ? this.values[k] : dflt[defaultKey]; 8090 } 8091 return this.has(k) ? this.values[k] : dflt; 8092 }, 8093 // Check whether we have a value for a key. 8094 has: function(k) { 8095 return k in this.values; 8096 }, 8097 // Accept a setting if its one of the given alternatives. 8098 alt: function(k, v, a) { 8099 for (var n = 0; n < a.length; ++n) { 8100 if (v === a[n]) { 8101 this.set(k, v); 8102 break; 8103 } 8104 } 8105 }, 8106 // Accept a setting if its a valid (signed) integer. 8107 integer: function(k, v) { 8108 if (/^-?\d+$/.test(v)) { // integer 8109 this.set(k, parseInt(v, 10)); 8110 } 8111 }, 8112 // Accept a setting if its a valid percentage. 8113 percent: function(k, v) { 8114 var m; 8115 if ((m = v.match(/^([\d]{1,3})(\.[\d]*)?%$/))) { 8116 v = parseFloat(v); 8117 if (v >= 0 && v <= 100) { 8118 this.set(k, v); 8119 return true; 8120 } 8121 } 8122 return false; 8123 } 8124}; 8125 8126// Helper function to parse input into groups separated by 'groupDelim', and 8127// interprete each group as a key/value pair separated by 'keyValueDelim'. 8128function parseOptions(input, callback, keyValueDelim, groupDelim) { 8129 var groups = groupDelim ? input.split(groupDelim) : [input]; 8130 for (var i in groups) { 8131 if (typeof groups[i] !== "string") { 8132 continue; 8133 } 8134 var kv = groups[i].split(keyValueDelim); 8135 if (kv.length !== 2) { 8136 continue; 8137 } 8138 var k = kv[0]; 8139 var v = kv[1]; 8140 callback(k, v); 8141 } 8142} 8143 8144function parseCue(input, cue, regionList) { 8145 // Remember the original input if we need to throw an error. 8146 var oInput = input; 8147 // 4.1 WebVTT timestamp 8148 function consumeTimeStamp() { 8149 var ts = parseTimeStamp(input); 8150 if (ts === null) { 8151 throw new ParsingError(ParsingError.Errors.BadTimeStamp, 8152 "Malformed timestamp: " + oInput); 8153 } 8154 // Remove time stamp from input. 8155 input = input.replace(/^[^\sa-zA-Z-]+/, ""); 8156 return ts; 8157 } 8158 8159 // 4.4.2 WebVTT cue settings 8160 function consumeCueSettings(input, cue) { 8161 var settings = new Settings(); 8162 8163 parseOptions(input, function (k, v) { 8164 switch (k) { 8165 case "region": 8166 // Find the last region we parsed with the same region id. 8167 for (var i = regionList.length - 1; i >= 0; i--) { 8168 if (regionList[i].id === v) { 8169 settings.set(k, regionList[i].region); 8170 break; 8171 } 8172 } 8173 break; 8174 case "vertical": 8175 settings.alt(k, v, ["rl", "lr"]); 8176 break; 8177 case "line": 8178 var vals = v.split(","), 8179 vals0 = vals[0]; 8180 settings.integer(k, vals0); 8181 settings.percent(k, vals0) ? settings.set("snapToLines", false) : null; 8182 settings.alt(k, vals0, ["auto"]); 8183 if (vals.length === 2) { 8184 settings.alt("lineAlign", vals[1], ["start", "middle", "end"]); 8185 } 8186 break; 8187 case "position": 8188 vals = v.split(","); 8189 settings.percent(k, vals[0]); 8190 if (vals.length === 2) { 8191 settings.alt("positionAlign", vals[1], ["start", "middle", "end"]); 8192 } 8193 break; 8194 case "size": 8195 settings.percent(k, v); 8196 break; 8197 case "align": 8198 settings.alt(k, v, ["start", "middle", "end", "left", "right"]); 8199 break; 8200 } 8201 }, /:/, /\s/); 8202 8203 // Apply default values for any missing fields. 8204 cue.region = settings.get("region", null); 8205 cue.vertical = settings.get("vertical", ""); 8206 cue.line = settings.get("line", "auto"); 8207 cue.lineAlign = settings.get("lineAlign", "start"); 8208 cue.snapToLines = settings.get("snapToLines", true); 8209 cue.size = settings.get("size", 100); 8210 cue.align = settings.get("align", "middle"); 8211 cue.position = settings.get("position", { 8212 start: 0, 8213 left: 0, 8214 middle: 50, 8215 end: 100, 8216 right: 100 8217 }, cue.align); 8218 cue.positionAlign = settings.get("positionAlign", { 8219 start: "start", 8220 left: "start", 8221 middle: "middle", 8222 end: "end", 8223 right: "end" 8224 }, cue.align); 8225 } 8226 8227 function skipWhitespace() { 8228 input = input.replace(/^\s+/, ""); 8229 } 8230 8231 // 4.1 WebVTT cue timings. 8232 skipWhitespace(); 8233 cue.startTime = consumeTimeStamp(); // (1) collect cue start time 8234 skipWhitespace(); 8235 if (input.substr(0, 3) !== "-->") { // (3) next characters must match "-->" 8236 throw new ParsingError(ParsingError.Errors.BadTimeStamp, 8237 "Malformed time stamp (time stamps must be separated by '-->'): " + 8238 oInput); 8239 } 8240 input = input.substr(3); 8241 skipWhitespace(); 8242 cue.endTime = consumeTimeStamp(); // (5) collect cue end time 8243 8244 // 4.1 WebVTT cue settings list. 8245 skipWhitespace(); 8246 consumeCueSettings(input, cue); 8247} 8248 8249var ESCAPE = { 8250 "&": "&", 8251 "<": "<", 8252 ">": ">", 8253 "‎": "\u200e", 8254 "‏": "\u200f", 8255 " ": "\u00a0" 8256}; 8257 8258var TAG_NAME = { 8259 c: "span", 8260 i: "i", 8261 b: "b", 8262 u: "u", 8263 ruby: "ruby", 8264 rt: "rt", 8265 v: "span", 8266 lang: "span" 8267}; 8268 8269var TAG_ANNOTATION = { 8270 v: "title", 8271 lang: "lang" 8272}; 8273 8274var NEEDS_PARENT = { 8275 rt: "ruby" 8276}; 8277 8278// Parse content into a document fragment. 8279function parseContent(window, input) { 8280 function nextToken() { 8281 // Check for end-of-string. 8282 if (!input) { 8283 return null; 8284 } 8285 8286 // Consume 'n' characters from the input. 8287 function consume(result) { 8288 input = input.substr(result.length); 8289 return result; 8290 } 8291 8292 var m = input.match(/^([^<]*)(<[^>]+>?)?/); 8293 // If there is some text before the next tag, return it, otherwise return 8294 // the tag. 8295 return consume(m[1] ? m[1] : m[2]); 8296 } 8297 8298 // Unescape a string 's'. 8299 function unescape1(e) { 8300 return ESCAPE[e]; 8301 } 8302 function unescape(s) { 8303 while ((m = s.match(/&(amp|lt|gt|lrm|rlm|nbsp);/))) { 8304 s = s.replace(m[0], unescape1); 8305 } 8306 return s; 8307 } 8308 8309 function shouldAdd(current, element) { 8310 return !NEEDS_PARENT[element.localName] || 8311 NEEDS_PARENT[element.localName] === current.localName; 8312 } 8313 8314 // Create an element for this tag. 8315 function createElement(type, annotation) { 8316 var tagName = TAG_NAME[type]; 8317 if (!tagName) { 8318 return null; 8319 } 8320 var element = window.document.createElement(tagName); 8321 element.localName = tagName; 8322 var name = TAG_ANNOTATION[type]; 8323 if (name && annotation) { 8324 element[name] = annotation.trim(); 8325 } 8326 return element; 8327 } 8328 8329 var rootDiv = window.document.createElement("div"), 8330 current = rootDiv, 8331 t, 8332 tagStack = []; 8333 8334 while ((t = nextToken()) !== null) { 8335 if (t[0] === '<') { 8336 if (t[1] === "/") { 8337 // If the closing tag matches, move back up to the parent node. 8338 if (tagStack.length && 8339 tagStack[tagStack.length - 1] === t.substr(2).replace(">", "")) { 8340 tagStack.pop(); 8341 current = current.parentNode; 8342 } 8343 // Otherwise just ignore the end tag. 8344 continue; 8345 } 8346 var ts = parseTimeStamp(t.substr(1, t.length - 2)); 8347 var node; 8348 if (ts) { 8349 // Timestamps are lead nodes as well. 8350 node = window.document.createProcessingInstruction("timestamp", ts); 8351 current.appendChild(node); 8352 continue; 8353 } 8354 var m = t.match(/^<([^.\s/0-9>]+)(\.[^\s\\>]+)?([^>\\]+)?(\\?)>?$/); 8355 // If we can't parse the tag, skip to the next tag. 8356 if (!m) { 8357 continue; 8358 } 8359 // Try to construct an element, and ignore the tag if we couldn't. 8360 node = createElement(m[1], m[3]); 8361 if (!node) { 8362 continue; 8363 } 8364 // Determine if the tag should be added based on the context of where it 8365 // is placed in the cuetext. 8366 if (!shouldAdd(current, node)) { 8367 continue; 8368 } 8369 // Set the class list (as a list of classes, separated by space). 8370 if (m[2]) { 8371 node.className = m[2].substr(1).replace('.', ' '); 8372 } 8373 // Append the node to the current node, and enter the scope of the new 8374 // node. 8375 tagStack.push(m[1]); 8376 current.appendChild(node); 8377 current = node; 8378 continue; 8379 } 8380 8381 // Text nodes are leaf nodes. 8382 current.appendChild(window.document.createTextNode(unescape(t))); 8383 } 8384 8385 return rootDiv; 8386} 8387 8388// This is a list of all the Unicode characters that have a strong 8389// right-to-left category. What this means is that these characters are 8390// written right-to-left for sure. It was generated by pulling all the strong 8391// right-to-left characters out of the Unicode data table. That table can 8392// found at: http://www.unicode.org/Public/UNIDATA/UnicodeData.txt 8393var strongRTLRanges = [[0x5be, 0x5be], [0x5c0, 0x5c0], [0x5c3, 0x5c3], [0x5c6, 0x5c6], 8394 [0x5d0, 0x5ea], [0x5f0, 0x5f4], [0x608, 0x608], [0x60b, 0x60b], [0x60d, 0x60d], 8395 [0x61b, 0x61b], [0x61e, 0x64a], [0x66d, 0x66f], [0x671, 0x6d5], [0x6e5, 0x6e6], 8396 [0x6ee, 0x6ef], [0x6fa, 0x70d], [0x70f, 0x710], [0x712, 0x72f], [0x74d, 0x7a5], 8397 [0x7b1, 0x7b1], [0x7c0, 0x7ea], [0x7f4, 0x7f5], [0x7fa, 0x7fa], [0x800, 0x815], 8398 [0x81a, 0x81a], [0x824, 0x824], [0x828, 0x828], [0x830, 0x83e], [0x840, 0x858], 8399 [0x85e, 0x85e], [0x8a0, 0x8a0], [0x8a2, 0x8ac], [0x200f, 0x200f], 8400 [0xfb1d, 0xfb1d], [0xfb1f, 0xfb28], [0xfb2a, 0xfb36], [0xfb38, 0xfb3c], 8401 [0xfb3e, 0xfb3e], [0xfb40, 0xfb41], [0xfb43, 0xfb44], [0xfb46, 0xfbc1], 8402 [0xfbd3, 0xfd3d], [0xfd50, 0xfd8f], [0xfd92, 0xfdc7], [0xfdf0, 0xfdfc], 8403 [0xfe70, 0xfe74], [0xfe76, 0xfefc], [0x10800, 0x10805], [0x10808, 0x10808], 8404 [0x1080a, 0x10835], [0x10837, 0x10838], [0x1083c, 0x1083c], [0x1083f, 0x10855], 8405 [0x10857, 0x1085f], [0x10900, 0x1091b], [0x10920, 0x10939], [0x1093f, 0x1093f], 8406 [0x10980, 0x109b7], [0x109be, 0x109bf], [0x10a00, 0x10a00], [0x10a10, 0x10a13], 8407 [0x10a15, 0x10a17], [0x10a19, 0x10a33], [0x10a40, 0x10a47], [0x10a50, 0x10a58], 8408 [0x10a60, 0x10a7f], [0x10b00, 0x10b35], [0x10b40, 0x10b55], [0x10b58, 0x10b72], 8409 [0x10b78, 0x10b7f], [0x10c00, 0x10c48], [0x1ee00, 0x1ee03], [0x1ee05, 0x1ee1f], 8410 [0x1ee21, 0x1ee22], [0x1ee24, 0x1ee24], [0x1ee27, 0x1ee27], [0x1ee29, 0x1ee32], 8411 [0x1ee34, 0x1ee37], [0x1ee39, 0x1ee39], [0x1ee3b, 0x1ee3b], [0x1ee42, 0x1ee42], 8412 [0x1ee47, 0x1ee47], [0x1ee49, 0x1ee49], [0x1ee4b, 0x1ee4b], [0x1ee4d, 0x1ee4f], 8413 [0x1ee51, 0x1ee52], [0x1ee54, 0x1ee54], [0x1ee57, 0x1ee57], [0x1ee59, 0x1ee59], 8414 [0x1ee5b, 0x1ee5b], [0x1ee5d, 0x1ee5d], [0x1ee5f, 0x1ee5f], [0x1ee61, 0x1ee62], 8415 [0x1ee64, 0x1ee64], [0x1ee67, 0x1ee6a], [0x1ee6c, 0x1ee72], [0x1ee74, 0x1ee77], 8416 [0x1ee79, 0x1ee7c], [0x1ee7e, 0x1ee7e], [0x1ee80, 0x1ee89], [0x1ee8b, 0x1ee9b], 8417 [0x1eea1, 0x1eea3], [0x1eea5, 0x1eea9], [0x1eeab, 0x1eebb], [0x10fffd, 0x10fffd]]; 8418 8419function isStrongRTLChar(charCode) { 8420 for (var i = 0; i < strongRTLRanges.length; i++) { 8421 var currentRange = strongRTLRanges[i]; 8422 if (charCode >= currentRange[0] && charCode <= currentRange[1]) { 8423 return true; 8424 } 8425 } 8426 8427 return false; 8428} 8429 8430function determineBidi(cueDiv) { 8431 var nodeStack = [], 8432 text = "", 8433 charCode; 8434 8435 if (!cueDiv || !cueDiv.childNodes) { 8436 return "ltr"; 8437 } 8438 8439 function pushNodes(nodeStack, node) { 8440 for (var i = node.childNodes.length - 1; i >= 0; i--) { 8441 nodeStack.push(node.childNodes[i]); 8442 } 8443 } 8444 8445 function nextTextNode(nodeStack) { 8446 if (!nodeStack || !nodeStack.length) { 8447 return null; 8448 } 8449 8450 var node = nodeStack.pop(), 8451 text = node.textContent || node.innerText; 8452 if (text) { 8453 // TODO: This should match all unicode type B characters (paragraph 8454 // separator characters). See issue #115. 8455 var m = text.match(/^.*(\n|\r)/); 8456 if (m) { 8457 nodeStack.length = 0; 8458 return m[0]; 8459 } 8460 return text; 8461 } 8462 if (node.tagName === "ruby") { 8463 return nextTextNode(nodeStack); 8464 } 8465 if (node.childNodes) { 8466 pushNodes(nodeStack, node); 8467 return nextTextNode(nodeStack); 8468 } 8469 } 8470 8471 pushNodes(nodeStack, cueDiv); 8472 while ((text = nextTextNode(nodeStack))) { 8473 for (var i = 0; i < text.length; i++) { 8474 charCode = text.charCodeAt(i); 8475 if (isStrongRTLChar(charCode)) { 8476 return "rtl"; 8477 } 8478 } 8479 } 8480 return "ltr"; 8481} 8482 8483function computeLinePos(cue) { 8484 if (typeof cue.line === "number" && 8485 (cue.snapToLines || (cue.line >= 0 && cue.line <= 100))) { 8486 return cue.line; 8487 } 8488 if (!cue.track || !cue.track.textTrackList || 8489 !cue.track.textTrackList.mediaElement) { 8490 return -1; 8491 } 8492 var track = cue.track, 8493 trackList = track.textTrackList, 8494 count = 0; 8495 for (var i = 0; i < trackList.length && trackList[i] !== track; i++) { 8496 if (trackList[i].mode === "showing") { 8497 count++; 8498 } 8499 } 8500 return ++count * -1; 8501} 8502 8503function StyleBox() { 8504} 8505 8506// Apply styles to a div. If there is no div passed then it defaults to the 8507// div on 'this'. 8508StyleBox.prototype.applyStyles = function(styles, div) { 8509 div = div || this.div; 8510 for (var prop in styles) { 8511 if (styles.hasOwnProperty(prop)) { 8512 div.style[prop] = styles[prop]; 8513 } 8514 } 8515}; 8516 8517StyleBox.prototype.formatStyle = function(val, unit) { 8518 return val === 0 ? 0 : val + unit; 8519}; 8520 8521// Constructs the computed display state of the cue (a div). Places the div 8522// into the overlay which should be a block level element (usually a div). 8523function CueStyleBox(window, cue, styleOptions) { 8524 var isIE8 = (/MSIE\s8\.0/).test(navigator.userAgent); 8525 var color = "rgba(255, 255, 255, 1)"; 8526 var backgroundColor = "rgba(0, 0, 0, 0.8)"; 8527 8528 if (isIE8) { 8529 color = "rgb(255, 255, 255)"; 8530 backgroundColor = "rgb(0, 0, 0)"; 8531 } 8532
vendor: 5,880 bytes, lines 8533-8709
8533 StyleBox.call(this); 8534 this.cue = cue; 8535 8536 // Parse our cue's text into a DOM tree rooted at 'cueDiv'. This div will 8537 // have inline positioning and will function as the cue background box. 8538 this.cueDiv = parseContent(window, cue.text); 8539 var styles = { 8540 color: color, 8541 backgroundColor: backgroundColor, 8542 position: "relative", 8543 left: 0, 8544 right: 0, 8545 top: 0, 8546 bottom: 0, 8547 display: "inline" 8548 }; 8549 8550 if (!isIE8) { 8551 styles.writingMode = cue.vertical === "" ? "horizontal-tb" 8552 : cue.vertical === "lr" ? "vertical-lr" 8553 : "vertical-rl"; 8554 styles.unicodeBidi = "plaintext"; 8555 } 8556 this.applyStyles(styles, this.cueDiv); 8557 8558 // Create an absolutely positioned div that will be used to position the cue 8559 // div. Note, all WebVTT cue-setting alignments are equivalent to the CSS 8560 // mirrors of them except "middle" which is "center" in CSS. 8561 this.div = window.document.createElement("div"); 8562 styles = { 8563 textAlign: cue.align === "middle" ? "center" : cue.align, 8564 font: styleOptions.font, 8565 whiteSpace: "pre-line", 8566 position: "absolute" 8567 }; 8568 8569 if (!isIE8) { 8570 styles.direction = determineBidi(this.cueDiv); 8571 styles.writingMode = cue.vertical === "" ? "horizontal-tb" 8572 : cue.vertical === "lr" ? "vertical-lr" 8573 : "vertical-rl". 8574 stylesunicodeBidi = "plaintext"; 8575 } 8576 8577 this.applyStyles(styles); 8578 8579 this.div.appendChild(this.cueDiv); 8580 8581 // Calculate the distance from the reference edge of the viewport to the text 8582 // position of the cue box. The reference edge will be resolved later when 8583 // the box orientation styles are applied. 8584 var textPos = 0; 8585 switch (cue.positionAlign) { 8586 case "start": 8587 textPos = cue.position; 8588 break; 8589 case "middle": 8590 textPos = cue.position - (cue.size / 2); 8591 break; 8592 case "end": 8593 textPos = cue.position - cue.size; 8594 break; 8595 } 8596 8597 // Horizontal box orientation; textPos is the distance from the left edge of the 8598 // area to the left edge of the box and cue.size is the distance extending to 8599 // the right from there. 8600 if (cue.vertical === "") { 8601 this.applyStyles({ 8602 left: this.formatStyle(textPos, "%"), 8603 width: this.formatStyle(cue.size, "%") 8604 }); 8605 // Vertical box orientation; textPos is the distance from the top edge of the 8606 // area to the top edge of the box and cue.size is the height extending 8607 // downwards from there. 8608 } else { 8609 this.applyStyles({ 8610 top: this.formatStyle(textPos, "%"), 8611 height: this.formatStyle(cue.size, "%") 8612 }); 8613 } 8614 8615 this.move = function(box) { 8616 this.applyStyles({ 8617 top: this.formatStyle(box.top, "px"), 8618 bottom: this.formatStyle(box.bottom, "px"), 8619 left: this.formatStyle(box.left, "px"), 8620 right: this.formatStyle(box.right, "px"), 8621 height: this.formatStyle(box.height, "px"), 8622 width: this.formatStyle(box.width, "px") 8623 }); 8624 }; 8625} 8626CueStyleBox.prototype = _objCreate(StyleBox.prototype); 8627CueStyleBox.prototype.constructor = CueStyleBox; 8628 8629// Represents the co-ordinates of an Element in a way that we can easily 8630// compute things with such as if it overlaps or intersects with another Element. 8631// Can initialize it with either a StyleBox or another BoxPosition. 8632function BoxPosition(obj) { 8633 var isIE8 = (/MSIE\s8\.0/).test(navigator.userAgent); 8634 8635 // Either a BoxPosition was passed in and we need to copy it, or a StyleBox 8636 // was passed in and we need to copy the results of 'getBoundingClientRect' 8637 // as the object returned is readonly. All co-ordinate values are in reference 8638 // to the viewport origin (top left). 8639 var lh, height, width, top; 8640 if (obj.div) { 8641 height = obj.div.offsetHeight; 8642 width = obj.div.offsetWidth; 8643 top = obj.div.offsetTop; 8644 8645 var rects = (rects = obj.div.childNodes) && (rects = rects[0]) && 8646 rects.getClientRects && rects.getClientRects(); 8647 obj = obj.div.getBoundingClientRect(); 8648 // In certain cases the outter div will be slightly larger then the sum of 8649 // the inner div's lines. This could be due to bold text, etc, on some platforms. 8650 // In this case we should get the average line height and use that. This will 8651 // result in the desired behaviour. 8652 lh = rects ? Math.max((rects[0] && rects[0].height) || 0, obj.height / rects.length) 8653 : 0; 8654 8655 } 8656 this.left = obj.left; 8657 this.right = obj.right; 8658 this.top = obj.top || top; 8659 this.height = obj.height || height; 8660 this.bottom = obj.bottom || (top + (obj.height || height)); 8661 this.width = obj.width || width; 8662 this.lineHeight = lh !== undefined ? lh : obj.lineHeight; 8663 8664 if (isIE8 && !this.lineHeight) { 8665 this.lineHeight = 13; 8666 } 8667} 8668 8669// Move the box along a particular axis. Optionally pass in an amount to move 8670// the box. If no amount is passed then the default is the line height of the 8671// box. 8672BoxPosition.prototype.move = function(axis, toMove) { 8673 toMove = toMove !== undefined ? toMove : this.lineHeight; 8674 switch (axis) { 8675 case "+x": 8676 this.left += toMove; 8677 this.right += toMove; 8678 break; 8679 case "-x": 8680 this.left -= toMove; 8681 this.right -= toMove; 8682 break; 8683 case "+y": 8684 this.top += toMove; 8685 this.bottom += toMove; 8686 break; 8687 case "-y": 8688 this.top -= toMove; 8689 this.bottom -= toMove; 8690 break; 8691 } 8692}; 8693 8694// Check if this box overlaps another box, b2. 8695BoxPosition.prototype.overlaps = function(b2) { 8696 return this.left < b2.right && 8697 this.right > b2.left && 8698 this.top < b2.bottom && 8699 this.bottom > b2.top; 8700}; 8701 8702// Check if this box overlaps any other boxes in boxes. 8703BoxPosition.prototype.overlapsAny = function(boxes) { 8704 for (var i = 0; i < boxes.length; i++) { 8705 if (this.overlaps(boxes[i])) { 8706 return true; 8707 } 8708 } 8709 return false;
vendor: 6,016 bytes, lines 8710-8873
8710}; 8711 8712// Check if this box is within another box. 8713BoxPosition.prototype.within = function(container) { 8714 return this.top >= container.top && 8715 this.bottom <= container.bottom && 8716 this.left >= container.left && 8717 this.right <= container.right; 8718}; 8719 8720// Check if this box is entirely within the container or it is overlapping 8721// on the edge opposite of the axis direction passed. For example, if "+x" is 8722// passed and the box is overlapping on the left edge of the container, then 8723// return true. 8724BoxPosition.prototype.overlapsOppositeAxis = function(container, axis) { 8725 switch (axis) { 8726 case "+x": 8727 return this.left < container.left; 8728 case "-x": 8729 return this.right > container.right; 8730 case "+y": 8731 return this.top < container.top; 8732 case "-y": 8733 return this.bottom > container.bottom; 8734 } 8735}; 8736 8737// Find the percentage of the area that this box is overlapping with another 8738// box. 8739BoxPosition.prototype.intersectPercentage = function(b2) { 8740 var x = Math.max(0, Math.min(this.right, b2.right) - Math.max(this.left, b2.left)), 8741 y = Math.max(0, Math.min(this.bottom, b2.bottom) - Math.max(this.top, b2.top)), 8742 intersectArea = x * y; 8743 return intersectArea / (this.height * this.width); 8744}; 8745 8746// Convert the positions from this box to CSS compatible positions using 8747// the reference container's positions. This has to be done because this 8748// box's positions are in reference to the viewport origin, whereas, CSS 8749// values are in referecne to their respective edges. 8750BoxPosition.prototype.toCSSCompatValues = function(reference) { 8751 return { 8752 top: this.top - reference.top, 8753 bottom: reference.bottom - this.bottom, 8754 left: this.left - reference.left, 8755 right: reference.right - this.right, 8756 height: this.height, 8757 width: this.width 8758 }; 8759}; 8760 8761// Get an object that represents the box's position without anything extra. 8762// Can pass a StyleBox, HTMLElement, or another BoxPositon. 8763BoxPosition.getSimpleBoxPosition = function(obj) { 8764 var height = obj.div ? obj.div.offsetHeight : obj.tagName ? obj.offsetHeight : 0; 8765 var width = obj.div ? obj.div.offsetWidth : obj.tagName ? obj.offsetWidth : 0; 8766 var top = obj.div ? obj.div.offsetTop : obj.tagName ? obj.offsetTop : 0; 8767 8768 obj = obj.div ? obj.div.getBoundingClientRect() : 8769 obj.tagName ? obj.getBoundingClientRect() : obj; 8770 var ret = { 8771 left: obj.left, 8772 right: obj.right, 8773 top: obj.top || top, 8774 height: obj.height || height, 8775 bottom: obj.bottom || (top + (obj.height || height)), 8776 width: obj.width || width 8777 }; 8778 return ret; 8779}; 8780 8781// Move a StyleBox to its specified, or next best, position. The containerBox 8782// is the box that contains the StyleBox, such as a div. boxPositions are 8783// a list of other boxes that the styleBox can't overlap with. 8784function moveBoxToLinePosition(window, styleBox, containerBox, boxPositions) { 8785 8786 // Find the best position for a cue box, b, on the video. The axis parameter 8787 // is a list of axis, the order of which, it will move the box along. For example: 8788 // Passing ["+x", "-x"] will move the box first along the x axis in the positive 8789 // direction. If it doesn't find a good position for it there it will then move 8790 // it along the x axis in the negative direction. 8791 function findBestPosition(b, axis) { 8792 var bestPosition, 8793 specifiedPosition = new BoxPosition(b), 8794 percentage = 1; // Highest possible so the first thing we get is better. 8795 8796 for (var i = 0; i < axis.length; i++) { 8797 while (b.overlapsOppositeAxis(containerBox, axis[i]) || 8798 (b.within(containerBox) && b.overlapsAny(boxPositions))) { 8799 b.move(axis[i]); 8800 } 8801 // We found a spot where we aren't overlapping anything. This is our 8802 // best position. 8803 if (b.within(containerBox)) { 8804 return b; 8805 } 8806 var p = b.intersectPercentage(containerBox); 8807 // If we're outside the container box less then we were on our last try 8808 // then remember this position as the best position. 8809 if (percentage > p) { 8810 bestPosition = new BoxPosition(b); 8811 percentage = p; 8812 } 8813 // Reset the box position to the specified position. 8814 b = new BoxPosition(specifiedPosition); 8815 } 8816 return bestPosition || specifiedPosition; 8817 } 8818 8819 var boxPosition = new BoxPosition(styleBox), 8820 cue = styleBox.cue, 8821 linePos = computeLinePos(cue), 8822 axis = []; 8823 8824 // If we have a line number to align the cue to. 8825 if (cue.snapToLines) { 8826 var size; 8827 switch (cue.vertical) { 8828 case "": 8829 axis = [ "+y", "-y" ]; 8830 size = "height"; 8831 break; 8832 case "rl": 8833 axis = [ "+x", "-x" ]; 8834 size = "width"; 8835 break; 8836 case "lr": 8837 axis = [ "-x", "+x" ]; 8838 size = "width"; 8839 break; 8840 } 8841 8842 var step = boxPosition.lineHeight, 8843 position = step * Math.round(linePos), 8844 maxPosition = containerBox[size] + step, 8845 initialAxis = axis[0]; 8846 8847 // If the specified intial position is greater then the max position then 8848 // clamp the box to the amount of steps it would take for the box to 8849 // reach the max position. 8850 if (Math.abs(position) > maxPosition) { 8851 position = position < 0 ? -1 : 1; 8852 position *= Math.ceil(maxPosition / step) * step; 8853 } 8854 8855 // If computed line position returns negative then line numbers are 8856 // relative to the bottom of the video instead of the top. Therefore, we 8857 // need to increase our initial position by the length or width of the 8858 // video, depending on the writing direction, and reverse our axis directions. 8859 if (linePos < 0) { 8860 position += cue.vertical === "" ? containerBox.height : containerBox.width; 8861 axis = axis.reverse(); 8862 } 8863 8864 // Move the box to the specified position. This may not be its best 8865 // position. 8866 boxPosition.move(initialAxis, position); 8867 8868 } else { 8869 // If we have a percentage line value for the cue. 8870 var calculatedPercentage = (boxPosition.lineHeight / containerBox.height) * 100; 8871 8872 switch (cue.lineAlign) { 8873 case "middle
vendor: 10,353 bytes, lines 8873-9234
8873": 8874 linePos -= (calculatedPercentage / 2); 8875 break; 8876 case "end": 8877 linePos -= calculatedPercentage; 8878 break; 8879 } 8880 8881 // Apply initial line position to the cue box. 8882 switch (cue.vertical) { 8883 case "": 8884 styleBox.applyStyles({ 8885 top: styleBox.formatStyle(linePos, "%") 8886 }); 8887 break; 8888 case "rl": 8889 styleBox.applyStyles({ 8890 left: styleBox.formatStyle(linePos, "%") 8891 }); 8892 break; 8893 case "lr": 8894 styleBox.applyStyles({ 8895 right: styleBox.formatStyle(linePos, "%") 8896 }); 8897 break; 8898 } 8899 8900 axis = [ "+y", "-x", "+x", "-y" ]; 8901 8902 // Get the box position again after we've applied the specified positioning 8903 // to it. 8904 boxPosition = new BoxPosition(styleBox); 8905 } 8906 8907 var bestPosition = findBestPosition(boxPosition, axis); 8908 styleBox.move(bestPosition.toCSSCompatValues(containerBox)); 8909} 8910 8911function WebVTT$1() { 8912 // Nothing 8913} 8914 8915// Helper to allow strings to be decoded instead of the default binary utf8 data. 8916WebVTT$1.StringDecoder = function() { 8917 return { 8918 decode: function(data) { 8919 if (!data) { 8920 return ""; 8921 } 8922 if (typeof data !== "string") { 8923 throw new Error("Error - expected string data."); 8924 } 8925 return decodeURIComponent(encodeURIComponent(data)); 8926 } 8927 }; 8928}; 8929 8930WebVTT$1.convertCueToDOMTree = function(window, cuetext) { 8931 if (!window || !cuetext) { 8932 return null; 8933 } 8934 return parseContent(window, cuetext); 8935}; 8936 8937var FONT_SIZE_PERCENT = 0.05; 8938var FONT_STYLE = "sans-serif"; 8939var CUE_BACKGROUND_PADDING = "1.5%"; 8940 8941// Runs the processing model over the cues and regions passed to it. 8942// @param overlay A block level element (usually a div) that the computed cues 8943// and regions will be placed into. 8944WebVTT$1.processCues = function(window, cues, overlay) { 8945 if (!window || !cues || !overlay) { 8946 return null; 8947 } 8948 8949 // Remove all previous children. 8950 while (overlay.firstChild) { 8951 overlay.removeChild(overlay.firstChild); 8952 } 8953 8954 var paddedOverlay = window.document.createElement("div"); 8955 paddedOverlay.style.position = "absolute"; 8956 paddedOverlay.style.left = "0"; 8957 paddedOverlay.style.right = "0"; 8958 paddedOverlay.style.top = "0"; 8959 paddedOverlay.style.bottom = "0"; 8960 paddedOverlay.style.margin = CUE_BACKGROUND_PADDING; 8961 overlay.appendChild(paddedOverlay); 8962 8963 // Determine if we need to compute the display states of the cues. This could 8964 // be the case if a cue's state has been changed since the last computation or 8965 // if it has not been computed yet. 8966 function shouldCompute(cues) { 8967 for (var i = 0; i < cues.length; i++) { 8968 if (cues[i].hasBeenReset || !cues[i].displayState) { 8969 return true; 8970 } 8971 } 8972 return false; 8973 } 8974 8975 // We don't need to recompute the cues' display states. Just reuse them. 8976 if (!shouldCompute(cues)) { 8977 for (var i = 0; i < cues.length; i++) { 8978 paddedOverlay.appendChild(cues[i].displayState); 8979 } 8980 return; 8981 } 8982 8983 var boxPositions = [], 8984 containerBox = BoxPosition.getSimpleBoxPosition(paddedOverlay), 8985 fontSize = Math.round(containerBox.height * FONT_SIZE_PERCENT * 100) / 100; 8986 var styleOptions = { 8987 font: fontSize + "px " + FONT_STYLE 8988 }; 8989 8990 (function() { 8991 var styleBox, cue; 8992 8993 for (var i = 0; i < cues.length; i++) { 8994 cue = cues[i]; 8995 8996 // Compute the intial position and styles of the cue div. 8997 styleBox = new CueStyleBox(window, cue, styleOptions); 8998 paddedOverlay.appendChild(styleBox.div); 8999 9000 // Move the cue div to it's correct line position. 9001 moveBoxToLinePosition(window, styleBox, containerBox, boxPositions); 9002 9003 // Remember the computed div so that we don't have to recompute it later 9004 // if we don't have too. 9005 cue.displayState = styleBox.div; 9006 9007 boxPositions.push(BoxPosition.getSimpleBoxPosition(styleBox)); 9008 } 9009 })(); 9010}; 9011 9012WebVTT$1.Parser = function(window, vttjs, decoder) { 9013 if (!decoder) { 9014 decoder = vttjs; 9015 vttjs = {}; 9016 } 9017 if (!vttjs) { 9018 vttjs = {}; 9019 } 9020 9021 this.window = window; 9022 this.vttjs = vttjs; 9023 this.state = "INITIAL"; 9024 this.buffer = ""; 9025 this.decoder = decoder || new TextDecoder("utf8"); 9026 this.regionList = []; 9027}; 9028 9029WebVTT$1.Parser.prototype = { 9030 // If the error is a ParsingError then report it to the consumer if 9031 // possible. If it's not a ParsingError then throw it like normal. 9032 reportOrThrowError: function(e) { 9033 if (e instanceof ParsingError) { 9034 this.onparsingerror && this.onparsingerror(e); 9035 } else { 9036 throw e; 9037 } 9038 }, 9039 parse: function (data) { 9040 var self = this; 9041 9042 // If there is no data then we won't decode it, but will just try to parse 9043 // whatever is in buffer already. This may occur in circumstances, for 9044 // example when flush() is called. 9045 if (data) { 9046 // Try to decode the data that we received. 9047 self.buffer += self.decoder.decode(data, {stream: true}); 9048 } 9049 9050 function collectNextLine() { 9051 var buffer = self.buffer; 9052 var pos = 0; 9053 while (pos < buffer.length && buffer[pos] !== '\r' && buffer[pos] !== '\n') { 9054 ++pos; 9055 } 9056 var line = buffer.substr(0, pos); 9057 // Advance the buffer early in case we fail below. 9058 if (buffer[pos] === '\r') { 9059 ++pos; 9060 } 9061 if (buffer[pos] === '\n') { 9062 ++pos; 9063 } 9064 self.buffer = buffer.substr(pos); 9065 return line; 9066 } 9067 9068 // 3.4 WebVTT region and WebVTT region settings syntax 9069 function parseRegion(input) { 9070 var settings = new Settings(); 9071 9072 parseOptions(input, function (k, v) { 9073 switch (k) { 9074 case "id": 9075 settings.set(k, v); 9076 break; 9077 case "width": 9078 settings.percent(k, v); 9079 break; 9080 case "lines": 9081 settings.integer(k, v); 9082 break; 9083 case "regionanchor": 9084 case "viewportanchor": 9085 var xy = v.split(','); 9086 if (xy.length !== 2) { 9087 break; 9088 } 9089 // We have to make sure both x and y parse, so use a temporary 9090 // settings object here. 9091 var anchor = new Settings(); 9092 anchor.percent("x", xy[0]); 9093 anchor.percent("y", xy[1]); 9094 if (!anchor.has("x") || !anchor.has("y")) { 9095 break; 9096 } 9097 settings.set(k + "X", anchor.get("x")); 9098 settings.set(k + "Y", anchor.get("y")); 9099 break; 9100 case "scroll": 9101 settings.alt(k, v, ["up"]); 9102 break; 9103 } 9104 }, /=/, /\s/); 9105 9106 // Create the region, using default values for any values that were not 9107 // specified. 9108 if (settings.has("id")) { 9109 var region = new (self.vttjs.VTTRegion || self.window.VTTRegion)(); 9110 region.width = settings.get("width", 100); 9111 region.lines = settings.get("lines", 3); 9112 region.regionAnchorX = settings.get("regionanchorX", 0); 9113 region.regionAnchorY = settings.get("regionanchorY", 100); 9114 region.viewportAnchorX = settings.get("viewportanchorX", 0); 9115 region.viewportAnchorY = settings.get("viewportanchorY", 100); 9116 region.scroll = settings.get("scroll", ""); 9117 // Register the region. 9118 self.onregion && self.onregion(region); 9119 // Remember the VTTRegion for later in case we parse any VTTCues that 9120 // reference it. 9121 self.regionList.push({ 9122 id: settings.get("id"), 9123 region: region 9124 }); 9125 } 9126 } 9127 9128 // draft-pantos-http-live-streaming-20 9129 // https://tools.ietf.org/html/draft-pantos-http-live-streaming-20#section-3.5 9130 // 3.5 WebVTT 9131 function parseTimestampMap(input) { 9132 var settings = new Settings(); 9133 9134 parseOptions(input, function(k, v) { 9135 switch(k) { 9136 case "MPEGT": 9137 settings.integer(k + 'S', v); 9138 break; 9139 case "LOCA": 9140 settings.set(k + 'L', parseTimeStamp(v)); 9141 break; 9142 } 9143 }, /[^\d]:/, /,/); 9144 9145 self.ontimestampmap && self.ontimestampmap({ 9146 "MPEGTS": settings.get("MPEGTS"), 9147 "LOCAL": settings.get("LOCAL") 9148 }); 9149 } 9150 9151 // 3.2 WebVTT metadata header syntax 9152 function parseHeader(input) { 9153 if (input.match(/X-TIMESTAMP-MAP/)) { 9154 // This line contains HLS X-TIMESTAMP-MAP metadata 9155 parseOptions(input, function(k, v) { 9156 switch(k) { 9157 case "X-TIMESTAMP-MAP": 9158 parseTimestampMap(v); 9159 break; 9160 } 9161 }, /=/); 9162 } else { 9163 parseOptions(input, function (k, v) { 9164 switch (k) { 9165 case "Region": 9166 // 3.3 WebVTT region metadata header syntax 9167 parseRegion(v); 9168 break; 9169 } 9170 }, /:/); 9171 } 9172 9173 } 9174 9175 // 5.1 WebVTT file parsing. 9176 try { 9177 var line; 9178 if (self.state === "INITIAL") { 9179 // We can't start parsing until we have the first line. 9180 if (!/\r\n|\n/.test(self.buffer)) { 9181 return this; 9182 } 9183 9184 line = collectNextLine(); 9185 9186 var m = line.match(/^WEBVTT([ \t].*)?$/); 9187 if (!m || !m[0]) { 9188 throw new ParsingError(ParsingError.Errors.BadSignature); 9189 } 9190 9191 self.state = "HEADER"; 9192 } 9193 9194 var alreadyCollectedLine = false; 9195 while (self.buffer) { 9196 // We can't parse a line until we have the full line. 9197 if (!/\r\n|\n/.test(self.buffer)) { 9198 return this; 9199 } 9200 9201 if (!alreadyCollectedLine) { 9202 line = collectNextLine(); 9203 } else { 9204 alreadyCollectedLine = false; 9205 } 9206 9207 switch (self.state) { 9208 case "HEADER": 9209 // 13-18 - Allow a header (metadata) under the WEBVTT line. 9210 if (/:/.test(line)) { 9211 parseHeader(line); 9212 } else if (!line) { 9213 // An empty line terminates the header and starts the body (cues). 9214 self.state = "ID"; 9215 } 9216 continue; 9217 case "NOTE": 9218 // Ignore NOTE blocks. 9219 if (!line) { 9220 self.state = "ID"; 9221 } 9222 continue; 9223 case "ID": 9224 // Check for the start of NOTE blocks. 9225 if (/^NOTE($|[ \t])/.test(line)) { 9226 self.state = "NOTE"; 9227 break; 9228 } 9229 // 19-29 - Allow any number of line terminators, then initialize new cue values. 9230 if (!line) { 9231 continue; 9232 } 9233 self.cue = new (self.vttjs.VTTCue || self.window.VTTCue)(0, 0, ""); 9234
vendor: 2,935 bytes, lines 9234-9324
9234self.state = "CUE"; 9235 // 30-39 - Check if self line contains an optional identifier or timing data. 9236 if (line.indexOf("-->") === -1) { 9237 self.cue.id = line; 9238 continue; 9239 } 9240 // Process line as start of a cue. 9241 /*falls through*/ 9242 case "CUE": 9243 // 40 - Collect cue timings and settings. 9244 try { 9245 parseCue(line, self.cue, self.regionList); 9246 } catch (e) { 9247 self.reportOrThrowError(e); 9248 // In case of an error ignore rest of the cue. 9249 self.cue = null; 9250 self.state = "BADCUE"; 9251 continue; 9252 } 9253 self.state = "CUETEXT"; 9254 continue; 9255 case "CUETEXT": 9256 var hasSubstring = line.indexOf("-->") !== -1; 9257 // 34 - If we have an empty line then report the cue. 9258 // 35 - If we have the special substring '-->' then report the cue, 9259 // but do not collect the line as we need to process the current 9260 // one as a new cue. 9261 if (!line || hasSubstring && (alreadyCollectedLine = true)) { 9262 // We are done parsing self cue. 9263 self.oncue && self.oncue(self.cue); 9264 self.cue = null; 9265 self.state = "ID"; 9266 continue; 9267 } 9268 if (self.cue.text) { 9269 self.cue.text += "\n"; 9270 } 9271 self.cue.text += line; 9272 continue; 9273 case "BADCUE": // BADCUE 9274 // 54-62 - Collect and discard the remaining cue. 9275 if (!line) { 9276 self.state = "ID"; 9277 } 9278 continue; 9279 } 9280 } 9281 } catch (e) { 9282 self.reportOrThrowError(e); 9283 9284 // If we are currently parsing a cue, report what we have. 9285 if (self.state === "CUETEXT" && self.cue && self.oncue) { 9286 self.oncue(self.cue); 9287 } 9288 self.cue = null; 9289 // Enter BADWEBVTT state if header was not parsed correctly otherwise 9290 // another exception occurred so enter BADCUE state. 9291 self.state = self.state === "INITIAL" ? "BADWEBVTT" : "BADCUE"; 9292 } 9293 return this; 9294 }, 9295 flush: function () { 9296 var self = this; 9297 try { 9298 // Finish decoding the stream. 9299 self.buffer += self.decoder.decode(); 9300 // Synthesize the end of the current cue or region. 9301 if (self.cue || self.state === "HEADER") { 9302 self.buffer += "\n\n"; 9303 self.parse(); 9304 } 9305 // If we've flushed, parsed, and we're still on the INITIAL state then 9306 // that means we don't have enough of the stream to parse the first 9307 // line. 9308 if (self.state === "INITIAL") { 9309 throw new ParsingError(ParsingError.Errors.BadSignature); 9310 } 9311 } catch(e) { 9312 self.reportOrThrowError(e); 9313 } 9314 self.onflush && self.onflush(); 9315 return this; 9316 } 9317}; 9318 9319var vtt$1 = WebVTT$1; 9320 9321/** 9322 * Copyright 2013 vtt.js Contributors 9323 * 9324 * Licensed under the Apache License, Version 2.0 (the "License");
9325 * you may not use this file except in compliance with the License. 9326 * You may obtain a copy of the License at 9327 * 9328 * http://www.apache.org/licenses/LICENSE-2.0 9329 * 9330 * Unless required by applicable law or agreed to in writing, software 9331 * distributed under the License is distributed on an "AS IS" BASIS, 9332 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9333 * See the License for the specific language governing permissions and 9334 * limitations under the License. 9335 */ 9336 9337var autoKeyword = "auto"; 9338var directionSetting = { 9339 "": true, 9340 "lr": true, 9341 "rl": true 9342}; 9343var alignSetting = { 9344 "start": true, 9345 "middle": true, 9346 "end": true, 9347 "left": true, 9348 "right": true 9349}; 9350 9351function findDirectionSetting(value) { 9352 if (typeof value !== "string") { 9353 return false; 9354 } 9355 var dir = directionSetting[value.toLowerCase()]; 9356 return dir ? value.toLowerCase() : false; 9357} 9358 9359function findAlignSetting(value) { 9360 if (typeof value !== "string") { 9361 return false; 9362 } 9363 var align = alignSetting[value.toLowerCase()]; 9364 return align ? value.toLowerCase() : false; 9365} 9366 9367function extend$1(obj) { 9368 var i = 1; 9369 for (; i < arguments.length; i++) { 9370 var cobj = arguments[i]; 9371 for (var p in cobj) { 9372 obj[p] = cobj[p]; 9373 } 9374 } 9375 9376 return obj; 9377} 9378 9379function VTTCue(startTime, endTime, text) { 9380 var cue = this; 9381 var isIE8 = (/MSIE\s8\.0/).test(navigator.userAgent); 9382 var baseObj = {}; 9383 9384 if (isIE8) { 9385 cue = document.createElement('custom'); 9386 } else { 9387 baseObj.enumerable = true; 9388 } 9389 9390 /** 9391 * Shim implementation specific properties. These properties are not in 9392 * the spec. 9393 */ 9394 9395 // Lets us know when the VTTCue's data has changed in such a way that we need 9396 // to recompute its display state. This lets us compute its display state 9397 // lazily. 9398 cue.hasBeenReset = false; 9399 9400 /** 9401 * VTTCue and TextTrackCue properties 9402 * http://dev.w3.org/html5/webvtt/#vttcue-interface 9403 */ 9404 9405 var _id = ""; 9406 var _pauseOnExit = false; 9407 var _startTime = startTime; 9408 var _endTime = endTime; 9409 var _text = text; 9410 var _region = null; 9411 var _vertical = ""; 9412 var _snapToLines = true; 9413 var _line = "auto"; 9414 var _lineAlign = "start"; 9415 var _position = 50; 9416 var _positionAlign = "middle"; 9417 var _size = 50; 9418 var _align = "middle"; 9419 9420 Object.defineProperty(cue, 9421 "id", extend$1({}, baseObj, { 9422 get: function() { 9423 return _id; 9424 }, 9425 set: function(value) { 9426 _id = "" + value; 9427 } 9428 })); 9429 9430 Object.defineProperty(cue, 9431 "pauseOnExit", extend$1({}, baseObj, { 9432 get: function() { 9433 return _pauseOnExit; 9434 }, 9435 set: function(value) { 9436 _pauseOnExit = !!value; 9437 } 9438 })); 9439 9440 Object.defineProperty(cue, 9441 "startTime", extend$1({}, baseObj, { 9442 get: function() { 9443 return _startTime; 9444 }, 9445 set: function(value) { 9446 if (typeof value !== "number") { 9447 throw new TypeError("Start time must be set to a number."); 9448 } 9449 _startTime = value; 9450 this.hasBeenReset = true; 9451 } 9452 })); 9453 9454 Object.defineProperty(cue, 9455 "endTime", extend$1({}, baseObj, { 9456 get: function() { 9457 return _endTime; 9458 }, 9459 set: function(value) { 9460 if (typeof value !== "number") { 9461 throw new TypeError("End time must be set to a number."); 9462 } 9463 _endTime = value; 9464 this.hasBeenReset = true; 9465 } 9466 })); 9467 9468 Object.defineProperty(cue, 9469 "text", extend$1({}, baseObj, { 9470 get: function() { 9471 return _text; 9472 }, 9473 set: function(value) { 9474 _text = "" + value; 9475 this.hasBeenReset = true; 9476 } 9477 })); 9478 9479 Object.defineProperty(cue, 9480 "region", extend$1({}, baseObj, { 9481 get: function() { 9482 return _region; 9483 }, 9484 set: function(value) { 9485 _region = value; 9486 this.hasBeenReset = true; 9487 } 9488 })); 9489 9490 Object.defineProperty(cue, 9491 "vertical", extend$1({}, baseObj, { 9492 get: function() { 9493 return _vertical; 9494 }, 9495 set: function(value) { 9496 var setting = findDirectionSetting(value); 9497 // Have to check for false because the setting an be an empty string. 9498 if (setting === false) { 9499 throw new SyntaxError("An invalid or illegal string was specified."); 9500 } 9501 _vertical = setting; 9502 this.hasBeenReset = true; 9503 } 9504 })); 9505 9506 Object.defineProperty(cue, 9507 "snapToLines", extend$1({}, baseObj, { 9508 get: function() { 9509 return _snapToLines; 9510 }, 9511 set: function(value) { 9512 _snapToLines = !!value; 9513 this.hasBeenReset = true; 9514 } 9515 })); 9516 9517 Object.defineProperty(cue, 9518 "line", extend$1({}, baseObj, { 9519 get: function() { 9520 return _line; 9521 }, 9522 set: function(value) {
vendor: 4,245 bytes, lines 9523-9688
9523 if (typeof value !== "number" && value !== autoKeyword) { 9524 throw new SyntaxError("An invalid number or illegal string was specified."); 9525 } 9526 _line = value; 9527 this.hasBeenReset = true; 9528 } 9529 })); 9530 9531 Object.defineProperty(cue, 9532 "lineAlign", extend$1({}, baseObj, { 9533 get: function() { 9534 return _lineAlign; 9535 }, 9536 set: function(value) { 9537 var setting = findAlignSetting(value); 9538 if (!setting) { 9539 throw new SyntaxError("An invalid or illegal string was specified."); 9540 } 9541 _lineAlign = setting; 9542 this.hasBeenReset = true; 9543 } 9544 })); 9545 9546 Object.defineProperty(cue, 9547 "position", extend$1({}, baseObj, { 9548 get: function() { 9549 return _position; 9550 }, 9551 set: function(value) { 9552 if (value < 0 || value > 100) { 9553 throw new Error("Position must be between 0 and 100."); 9554 } 9555 _position = value; 9556 this.hasBeenReset = true; 9557 } 9558 })); 9559 9560 Object.defineProperty(cue, 9561 "positionAlign", extend$1({}, baseObj, { 9562 get: function() { 9563 return _positionAlign; 9564 }, 9565 set: function(value) { 9566 var setting = findAlignSetting(value); 9567 if (!setting) { 9568 throw new SyntaxError("An invalid or illegal string was specified."); 9569 } 9570 _positionAlign = setting; 9571 this.hasBeenReset = true; 9572 } 9573 })); 9574 9575 Object.defineProperty(cue, 9576 "size", extend$1({}, baseObj, { 9577 get: function() { 9578 return _size; 9579 }, 9580 set: function(value) { 9581 if (value < 0 || value > 100) { 9582 throw new Error("Size must be between 0 and 100."); 9583 } 9584 _size = value; 9585 this.hasBeenReset = true; 9586 } 9587 })); 9588 9589 Object.defineProperty(cue, 9590 "align", extend$1({}, baseObj, { 9591 get: function() { 9592 return _align; 9593 }, 9594 set: function(value) { 9595 var setting = findAlignSetting(value); 9596 if (!setting) { 9597 throw new SyntaxError("An invalid or illegal string was specified."); 9598 } 9599 _align = setting; 9600 this.hasBeenReset = true; 9601 } 9602 })); 9603 9604 /** 9605 * Other <track> spec defined properties 9606 */ 9607 9608 // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#text-track-cue-display-state 9609 cue.displayState = undefined; 9610 9611 if (isIE8) { 9612 return cue; 9613 } 9614} 9615 9616/** 9617 * VTTCue methods 9618 */ 9619 9620VTTCue.prototype.getCueAsHTML = function() { 9621 // Assume WebVTT.convertCueToDOMTree is on the global. 9622 return WebVTT.convertCueToDOMTree(window, this.text); 9623}; 9624 9625var vttcue = VTTCue; 9626 9627/** 9628 * Copyright 2013 vtt.js Contributors 9629 * 9630 * Licensed under the Apache License, Version 2.0 (the "License"); 9631 * you may not use this file except in compliance with the License. 9632 * You may obtain a copy of the License at 9633 * 9634 * http://www.apache.org/licenses/LICENSE-2.0 9635 * 9636 * Unless required by applicable law or agreed to in writing, software 9637 * distributed under the License is distributed on an "AS IS" BASIS, 9638 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9639 * See the License for the specific language governing permissions and 9640 * limitations under the License. 9641 */ 9642 9643var scrollSetting = { 9644 "": true, 9645 "up": true 9646}; 9647 9648function findScrollSetting(value) { 9649 if (typeof value !== "string") { 9650 return false; 9651 } 9652 var scroll = scrollSetting[value.toLowerCase()]; 9653 return scroll ? value.toLowerCase() : false; 9654} 9655 9656function isValidPercentValue(value) { 9657 return typeof value === "number" && (value >= 0 && value <= 100); 9658} 9659 9660// VTTRegion shim http://dev.w3.org/html5/webvtt/#vttregion-interface 9661function VTTRegion() { 9662 var _width = 100; 9663 var _lines = 3; 9664 var _regionAnchorX = 0; 9665 var _regionAnchorY = 100; 9666 var _viewportAnchorX = 0; 9667 var _viewportAnchorY = 100; 9668 var _scroll = ""; 9669 9670 Object.defineProperties(this, { 9671 "width": { 9672 enumerable: true, 9673 get: function() { 9674 return _width; 9675 }, 9676 set: function(value) { 9677 if (!isValidPercentValue(value)) { 9678 throw new Error("Width must be between 0 and 100."); 9679 } 9680 _width = value; 9681 } 9682 }, 9683 "lines": { 9684 enumerable: true, 9685 get: function() { 9686 return _lines; 9687 }, 9688 set: function(value) {
vendor: 81,856 bytes, lines 9689-12693
9689 if (typeof value !== "number") { 9690 throw new TypeError("Lines must be set to a number."); 9691 } 9692 _lines = value; 9693 } 9694 }, 9695 "regionAnchorY": { 9696 enumerable: true, 9697 get: function() { 9698 return _regionAnchorY; 9699 }, 9700 set: function(value) { 9701 if (!isValidPercentValue(value)) { 9702 throw new Error("RegionAnchorX must be between 0 and 100."); 9703 } 9704 _regionAnchorY = value; 9705 } 9706 }, 9707 "regionAnchorX": { 9708 enumerable: true, 9709 get: function() { 9710 return _regionAnchorX; 9711 }, 9712 set: function(value) { 9713 if(!isValidPercentValue(value)) { 9714 throw new Error("RegionAnchorY must be between 0 and 100."); 9715 } 9716 _regionAnchorX = value; 9717 } 9718 }, 9719 "viewportAnchorY": { 9720 enumerable: true, 9721 get: function() { 9722 return _viewportAnchorY; 9723 }, 9724 set: function(value) { 9725 if (!isValidPercentValue(value)) { 9726 throw new Error("ViewportAnchorY must be between 0 and 100."); 9727 } 9728 _viewportAnchorY = value; 9729 } 9730 }, 9731 "viewportAnchorX": { 9732 enumerable: true, 9733 get: function() { 9734 return _viewportAnchorX; 9735 }, 9736 set: function(value) { 9737 if (!isValidPercentValue(value)) { 9738 throw new Error("ViewportAnchorX must be between 0 and 100."); 9739 } 9740 _viewportAnchorX = value; 9741 } 9742 }, 9743 "scroll": { 9744 enumerable: true, 9745 get: function() { 9746 return _scroll; 9747 }, 9748 set: function(value) { 9749 var setting = findScrollSetting(value); 9750 // Have to check for false as an empty string is a legal value. 9751 if (setting === false) { 9752 throw new SyntaxError("An invalid or illegal string was specified."); 9753 } 9754 _scroll = setting; 9755 } 9756 } 9757 }); 9758} 9759 9760var vttregion = VTTRegion; 9761 9762var browserIndex = createCommonjsModule(function (module) { 9763/** 9764 * Copyright 2013 vtt.js Contributors 9765 * 9766 * Licensed under the Apache License, Version 2.0 (the "License"); 9767 * you may not use this file except in compliance with the License. 9768 * You may obtain a copy of the License at 9769 * 9770 * http://www.apache.org/licenses/LICENSE-2.0 9771 * 9772 * Unless required by applicable law or agreed to in writing, software 9773 * distributed under the License is distributed on an "AS IS" BASIS, 9774 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9775 * See the License for the specific language governing permissions and 9776 * limitations under the License. 9777 */ 9778 9779// Default exports for Node. Export the extended versions of VTTCue and 9780// VTTRegion in Node since we likely want the capability to convert back and 9781// forth between JSON. If we don't then it's not that big of a deal since we're 9782// off browser. 9783 9784 9785 9786var vttjs = module.exports = { 9787 WebVTT: vtt$1, 9788 VTTCue: vttcue, 9789 VTTRegion: vttregion 9790}; 9791 9792window_1.vttjs = vttjs; 9793window_1.WebVTT = vttjs.WebVTT; 9794 9795var cueShim = vttjs.VTTCue; 9796var regionShim = vttjs.VTTRegion; 9797var nativeVTTCue = window_1.VTTCue; 9798var nativeVTTRegion = window_1.VTTRegion; 9799 9800vttjs.shim = function() { 9801 window_1.VTTCue = cueShim; 9802 window_1.VTTRegion = regionShim; 9803}; 9804 9805vttjs.restore = function() { 9806 window_1.VTTCue = nativeVTTCue; 9807 window_1.VTTRegion = nativeVTTRegion; 9808}; 9809 9810if (!window_1.VTTCue) { 9811 vttjs.shim(); 9812} 9813}); 9814 9815/** 9816 * @file tech.js 9817 */ 9818 9819/** 9820 * An Object containing a structure like: `{src: 'url', type: 'mimetype'}` or string 9821 * that just contains the src url alone. 9822 * * `var SourceObject = {src: 'http://ex.com/video.mp4', type: 'video/mp4'};` 9823 * `var SourceString = 'http://example.com/some-video.mp4';` 9824 * 9825 * @typedef {Object|string} Tech~SourceObject 9826 * 9827 * @property {string} src 9828 * The url to the source 9829 * 9830 * @property {string} type 9831 * The mime type of the source 9832 */ 9833 9834/** 9835 * A function used by {@link Tech} to create a new {@link TextTrack}. 9836 * 9837 * @private 9838 * 9839 * @param {Tech} self 9840 * An instance of the Tech class. 9841 * 9842 * @param {string} kind 9843 * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata) 9844 * 9845 * @param {string} [label] 9846 * Label to identify the text track 9847 * 9848 * @param {string} [language] 9849 * Two letter language abbreviation 9850 * 9851 * @param {Object} [options={}] 9852 * An object with additional text track options 9853 * 9854 * @return {TextTrack} 9855 * The text track that was created. 9856 */ 9857function createTrackHelper(self, kind, label, language) { 9858 var options = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : {}; 9859 9860 var tracks = self.textTracks(); 9861 9862 options.kind = kind; 9863 9864 if (label) { 9865 options.label = label; 9866 } 9867 if (language) { 9868 options.language = language; 9869 } 9870 options.tech = self; 9871 9872 var track = new ALL.text.TrackClass(options); 9873 9874 tracks.addTrack(track); 9875 9876 return track; 9877} 9878 9879/** 9880 * This is the base class for media playback technology controllers, such as 9881 * {@link Flash} and {@link HTML5} 9882 * 9883 * @extends Component 9884 */ 9885 9886var Tech = function (_Component) { 9887 inherits(Tech, _Component); 9888 9889 /** 9890 * Create an instance of this Tech. 9891 * 9892 * @param {Object} [options] 9893 * The key/value store of player options. 9894 * 9895 * @param {Component~ReadyCallback} ready 9896 * Callback function to call when the `HTML5` Tech is ready. 9897 */ 9898 function Tech() { 9899 var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; 9900 var ready = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : function () {}; 9901 classCallCheck(this, Tech); 9902 9903 // we don't want the tech to report user activity automatically. 9904 // This is done manually in addControlsListeners 9905 options.reportTouchActivity = false; 9906 9907 // keep track of whether the current source has played at all to 9908 // implement a very limited played() 9909 var _this = possibleConstructorReturn(this, _Component.call(this, null, options, ready)); 9910 9911 _this.hasStarted_ = false; 9912 _this.on('playing', function () { 9913 this.hasStarted_ = true; 9914 }); 9915 _this.on('loadstart', function () { 9916 this.hasStarted_ = false; 9917 }); 9918 9919 ALL.names.forEach(function (name) { 9920 var props = ALL[name]; 9921 9922 if (options && options[props.getterName]) { 9923 _this[props.privateName] = options[props.getterName]; 9924 } 9925 }); 9926 9927 // Manually track progress in cases where the browser/flash player doesn't report it. 9928 if (!_this.featuresProgressEvents) { 9929 _this.manualProgressOn(); 9930 } 9931 9932 // Manually track timeupdates in cases where the browser/flash player doesn't report it. 9933 if (!_this.featuresTimeupdateEvents) { 9934 _this.manualTimeUpdatesOn(); 9935 } 9936 9937 ['Text', 'Audio', 'Video'].forEach(function (track) { 9938 if (options['native' + track + 'Tracks'] === false) { 9939 _this['featuresNative' + track + 'Tracks'] = false; 9940 } 9941 }); 9942 9943 if (options.nativeCaptions === false || options.nativeTextTracks === false) { 9944 _this.featuresNativeTextTracks = false; 9945 } else if (options.nativeCaptions === true || options.nativeTextTracks === true) { 9946 _this.featuresNativeTextTracks = true; 9947 } 9948 9949 if (!_this.featuresNativeTextTracks) { 9950 _this.emulateTextTracks(); 9951 } 9952 9953 _this.autoRemoteTextTracks_ = new ALL.text.ListClass(); 9954 9955 _this.initTrackListeners(); 9956 9957 // Turn on component tap events only if not using native controls 9958 if (!options.nativeControlsForTouch) { 9959 _this.emitTapEvents(); 9960 } 9961 9962 if (_this.constructor) { 9963 _this.name_ = _this.constructor.name || 'Unknown Tech'; 9964 } 9965 return _this; 9966 } 9967 9968 /* Fallbacks for unsupported event types 9969 ================================================================================ */ 9970 9971 /** 9972 * Polyfill the `progress` event for browsers that don't support it natively. 9973 * 9974 * @see {@link Tech#trackProgress} 9975 */ 9976 9977 9978 Tech.prototype.manualProgressOn = function manualProgressOn() { 9979 this.on('durationchange', this.onDurationChange); 9980 9981 this.manualProgress = true; 9982 9983 // Trigger progress watching when a source begins loading 9984 this.one('ready', this.trackProgress); 9985 }; 9986 9987 /** 9988 * Turn off the polyfill for `progress` events that was created in 9989 * {@link Tech#manualProgressOn} 9990 */ 9991 9992 9993 Tech.prototype.manualProgressOff = function manualProgressOff() { 9994 this.manualProgress = false; 9995 this.stopTrackingProgress(); 9996 9997 this.off('durationchange', this.onDurationChange); 9998 }; 9999 10000 /** 10001 * This is used to trigger a `progress` event when the buffered percent changes. It 10002 * sets an interval function that will be called every 500 milliseconds to check if the 10003 * buffer end percent has changed. 10004 * 10005 * > This function is called by {@link Tech#manualProgressOn} 10006 * 10007 * @param {EventTarget~Event} event 10008 * The `ready` event that caused this to run. 10009 * 10010 * @listens Tech#ready 10011 * @fires Tech#progress 10012 */ 10013 10014 10015 Tech.prototype.trackProgress = function trackProgress(event) { 10016 this.stopTrackingProgress(); 10017 this.progressInterval = this.setInterval(bind(this, function () { 10018 // Don't trigger unless buffered amount is greater than last time 10019 10020 var numBufferedPercent = this.bufferedPercent(); 10021 10022 if (this.bufferedPercent_ !== numBufferedPercent) { 10023 /** 10024 * See {@link Player#progress} 10025 * 10026 * @event Tech#progress 10027 * @type {EventTarget~Event} 10028 */ 10029 this.trigger('progress'); 10030 } 10031 10032 this.bufferedPercent_ = numBufferedPercent; 10033 10034 if (numBufferedPercent === 1) { 10035 this.stopTrackingProgress(); 10036 } 10037 }), 500); 10038 }; 10039 10040 /** 10041 * Update our internal duration on a `durationchange` event by calling 10042 * {@link Tech#duration}. 10043 * 10044 * @param {EventTarget~Event} event 10045 * The `durationchange` event that caused this to run. 10046 * 10047 * @listens Tech#durationchange 10048 */ 10049 10050 10051 Tech.prototype.onDurationChange = function onDurationChange(event) { 10052 this.duration_ = this.duration(); 10053 }; 10054 10055 /** 10056 * Get and create a `TimeRange` object for buffering. 10057 * 10058 * @return {TimeRange} 10059 * The time range object that was created. 10060 */ 10061 10062 10063 Tech.prototype.buffered = function buffered() { 10064 return createTimeRanges(0, 0); 10065 }; 10066 10067 /** 10068 * Get the percentage of the current video that is currently buffered. 10069 * 10070 * @return {number} 10071 * A number from 0 to 1 that represents the decimal percentage of the 10072 * video that is buffered. 10073 * 10074 */ 10075 10076 10077 Tech.prototype.bufferedPercent = function bufferedPercent$$1() { 10078 return bufferedPercent(this.buffered(), this.duration_); 10079 }; 10080 10081 /** 10082 * Turn off the polyfill for `progress` events that was created in 10083 * {@link Tech#manualProgressOn} 10084 * Stop manually tracking progress events by clearing the interval that was set in 10085 * {@link Tech#trackProgress}. 10086 */ 10087 10088 10089 Tech.prototype.stopTrackingProgress = function stopTrackingProgress() { 10090 this.clearInterval(this.progressInterval); 10091 }; 10092 10093 /** 10094 * Polyfill the `timeupdate` event for browsers that don't support it. 10095 * 10096 * @see {@link Tech#trackCurrentTime} 10097 */ 10098 10099 10100 Tech.prototype.manualTimeUpdatesOn = function manualTimeUpdatesOn() { 10101 this.manualTimeUpdates = true; 10102 10103 this.on('play', this.trackCurrentTime); 10104 this.on('pause', this.stopTrackingCurrentTime); 10105 }; 10106 10107 /** 10108 * Turn off the polyfill for `timeupdate` events that was created in 10109 * {@link Tech#manualTimeUpdatesOn} 10110 */ 10111 10112 10113 Tech.prototype.manualTimeUpdatesOff = function manualTimeUpdatesOff() { 10114 this.manualTimeUpdates = false; 10115 this.stopTrackingCurrentTime(); 10116 this.off('play', this.trackCurrentTime); 10117 this.off('pause', this.stopTrackingCurrentTime); 10118 }; 10119 10120 /** 10121 * Sets up an interval function to track current time and trigger `timeupdate` every 10122 * 250 milliseconds. 10123 * 10124 * @listens Tech#play 10125 * @triggers Tech#timeupdate 10126 */ 10127 10128 10129 Tech.prototype.trackCurrentTime = function trackCurrentTime() { 10130 if (this.currentTimeInterval) { 10131 this.stopTrackingCurrentTime(); 10132 } 10133 this.currentTimeInterval = this.setInterval(function () { 10134 /** 10135 * Triggered at an interval of 250ms to indicated that time is passing in the video. 10136 * 10137 * @event Tech#timeupdate 10138 * @type {EventTarget~Event} 10139 */ 10140 this.trigger({ type: 'timeupdate', target: this, manuallyTriggered: true }); 10141 10142 // 42 = 24 fps // 250 is what Webkit uses // FF uses 15 10143 }, 250); 10144 }; 10145 10146 /** 10147 * Stop the interval function created in {@link Tech#trackCurrentTime} so that the 10148 * `timeupdate` event is no longer triggered. 10149 * 10150 * @listens {Tech#pause} 10151 */ 10152 10153 10154 Tech.prototype.stopTrackingCurrentTime = function stopTrackingCurrentTime() { 10155 this.clearInterval(this.currentTimeInterval); 10156 10157 // #1002 - if the video ends right before the next timeupdate would happen, 10158 // the progress bar won't make it all the way to the end 10159 this.trigger({ type: 'timeupdate', target: this, manuallyTriggered: true }); 10160 }; 10161 10162 /** 10163 * Turn off all event polyfills, clear the `Tech`s {@link AudioTrackList}, 10164 * {@link VideoTrackList}, and {@link TextTrackList}, and dispose of this Tech. 10165 * 10166 * @fires Component#dispose 10167 */ 10168 10169 10170 Tech.prototype.dispose = function dispose() { 10171 10172 // clear out all tracks because we can't reuse them between techs 10173 this.clearTracks(NORMAL.names); 10174 10175 // Turn off any manual progress or timeupdate tracking 10176 if (this.manualProgress) { 10177 this.manualProgressOff(); 10178 } 10179 10180 if (this.manualTimeUpdates) { 10181 this.manualTimeUpdatesOff(); 10182 } 10183 10184 _Component.prototype.dispose.call(this); 10185 }; 10186 10187 /** 10188 * Clear out a single `TrackList` or an array of `TrackLists` given their names. 10189 * 10190 * > Note: Techs without source handlers should call this between sources for `video` 10191 * & `audio` tracks. You don't want to use them between tracks! 10192 * 10193 * @param {string[]|string} types 10194 * TrackList names to clear, valid names are `video`, `audio`, and 10195 * `text`. 10196 */ 10197 10198 10199 Tech.prototype.clearTracks = function clearTracks(types) { 10200 var _this2 = this; 10201 10202 types = [].concat(types); 10203 // clear out all tracks because we can't reuse them between techs 10204 types.forEach(function (type) { 10205 var list = _this2[type + 'Tracks']() || []; 10206 var i = list.length; 10207 10208 while (i--) { 10209 var track = list[i]; 10210 10211 if (type === 'text') { 10212 _this2.removeRemoteTextTrack(track); 10213 } 10214 list.removeTrack(track); 10215 } 10216 }); 10217 }; 10218 10219 /** 10220 * Remove any TextTracks added via addRemoteTextTrack that are 10221 * flagged for automatic garbage collection 10222 */ 10223 10224 10225 Tech.prototype.cleanupAutoTextTracks = function cleanupAutoTextTracks() { 10226 var list = this.autoRemoteTextTracks_ || []; 10227 var i = list.length; 10228 10229 while (i--) { 10230 var track = list[i]; 10231 10232 this.removeRemoteTextTrack(track); 10233 } 10234 }; 10235 10236 /** 10237 * Reset the tech, which will removes all sources and reset the internal readyState. 10238 * 10239 * @abstract 10240 */ 10241 10242 10243 Tech.prototype.reset = function reset() {}; 10244 10245 /** 10246 * Get or set an error on the Tech. 10247 * 10248 * @param {MediaError} [err] 10249 * Error to set on the Tech 10250 * 10251 * @return {MediaError|null} 10252 * The current error object on the tech, or null if there isn't one. 10253 */ 10254 10255 10256 Tech.prototype.error = function error(err) { 10257 if (err !== undefined) { 10258 this.error_ = new MediaError(err); 10259 this.trigger('error'); 10260 } 10261 return this.error_; 10262 }; 10263 10264 /** 10265 * Returns the `TimeRange`s that have been played through for the current source. 10266 * 10267 * > NOTE: This implementation is incomplete. It does not track the played `TimeRange`. 10268 * It only checks wether the source has played at all or not. 10269 * 10270 * @return {TimeRange} 10271 * - A single time range if this video has played 10272 * - An empty set of ranges if not. 10273 */ 10274 10275 10276 Tech.prototype.played = function played() { 10277 if (this.hasStarted_) { 10278 return createTimeRanges(0, 0); 10279 } 10280 return createTimeRanges(); 10281 }; 10282 10283 /** 10284 * Causes a manual time update to occur if {@link Tech#manualTimeUpdatesOn} was 10285 * previously called. 10286 * 10287 * @fires Tech#timeupdate 10288 */ 10289 10290 10291 Tech.prototype.setCurrentTime = function setCurrentTime() { 10292 // improve the accuracy of manual timeupdates 10293 if (this.manualTimeUpdates) { 10294 /** 10295 * A manual `timeupdate` event. 10296 * 10297 * @event Tech#timeupdate 10298 * @type {EventTarget~Event} 10299 */ 10300 this.trigger({ type: 'timeupdate', target: this, manuallyTriggered: true }); 10301 } 10302 }; 10303 10304 /** 10305 * Turn on listeners for {@link VideoTrackList}, {@link {AudioTrackList}, and 10306 * {@link TextTrackList} events. 10307 * 10308 * This adds {@link EventTarget~EventListeners} for `addtrack`, and `removetrack`. 10309 * 10310 * @fires Tech#audiotrackchange 10311 * @fires Tech#videotrackchange 10312 * @fires Tech#texttrackchange 10313 */ 10314 10315 10316 Tech.prototype.initTrackListeners = function initTrackListeners() { 10317 var _this3 = this; 10318 10319 /** 10320 * Triggered when tracks are added or removed on the Tech {@link AudioTrackList} 10321 * 10322 * @event Tech#audiotrackchange 10323 * @type {EventTarget~Event} 10324 */ 10325 10326 /** 10327 * Triggered when tracks are added or removed on the Tech {@link VideoTrackList} 10328 * 10329 * @event Tech#videotrackchange 10330 * @type {EventTarget~Event} 10331 */ 10332 10333 /** 10334 * Triggered when tracks are added or removed on the Tech {@link TextTrackList} 10335 * 10336 * @event Tech#texttrackchange 10337 * @type {EventTarget~Event} 10338 */ 10339 NORMAL.names.forEach(function (name) { 10340 var props = NORMAL[name]; 10341 var trackListChanges = function trackListChanges() { 10342 _this3.trigger(name + 'trackchange'); 10343 }; 10344 10345 var tracks = _this3[props.getterName](); 10346 10347 tracks.addEventListener('removetrack', trackListChanges); 10348 tracks.addEventListener('addtrack', trackListChanges); 10349 10350 _this3.on('dispose', function () { 10351 tracks.removeEventListener('removetrack', trackListChanges); 10352 tracks.removeEventListener('addtrack', trackListChanges); 10353 }); 10354 }); 10355 }; 10356 10357 /** 10358 * Emulate TextTracks using vtt.js if necessary 10359 * 10360 * @fires Tech#vttjsloaded 10361 * @fires Tech#vttjserror 10362 */ 10363 10364 10365 Tech.prototype.addWebVttScript_ = function addWebVttScript_() { 10366 var _this4 = this; 10367 10368 if (window_1.WebVTT) { 10369 return; 10370 } 10371 10372 // Initially, Tech.el_ is a child of a dummy-div wait until the Component system 10373 // signals that the Tech is ready at which point Tech.el_ is part of the DOM 10374 // before inserting the WebVTT script 10375 if (document_1.body.contains(this.el())) { 10376 10377 // load via require if available and vtt.js script location was not passed in 10378 // as an option. novtt builds will turn the above require call into an empty object 10379 // which will cause this if check to always fail. 10380 if (!this.options_['vtt.js'] && isPlain(browserIndex) && Object.keys(browserIndex).length > 0) { 10381 this.trigger('vttjsloaded'); 10382 return; 10383 } 10384 10385 // load vtt.js via the script location option or the cdn of no location was 10386 // passed in 10387 var script = document_1.createElement('script'); 10388 10389 script.src = this.options_['vtt.js'] || 'https://vjs.zencdn.net/vttjs/0.12.4/vtt.min.js'; 10390 script.onload = function () { 10391 /** 10392 * Fired when vtt.js is loaded. 10393 * 10394 * @event Tech#vttjsloaded 10395 * @type {EventTarget~Event} 10396 */ 10397 _this4.trigger('vttjsloaded'); 10398 }; 10399 script.onerror = function () { 10400 /** 10401 * Fired when vtt.js was not loaded due to an error 10402 * 10403 * @event Tech#vttjsloaded 10404 * @type {EventTarget~Event} 10405 */ 10406 _this4.trigger('vttjserror'); 10407 }; 10408 this.on('dispose', function () { 10409 script.onload = null; 10410 script.onerror = null; 10411 }); 10412 // but have not loaded yet and we set it to true before the inject so that 10413 // we don't overwrite the injected window.WebVTT if it loads right away 10414 window_1.WebVTT = true; 10415 this.el().parentNode.appendChild(script); 10416 } else { 10417 this.ready(this.addWebVttScript_); 10418 } 10419 }; 10420 10421 /** 10422 * Emulate texttracks 10423 * 10424 */ 10425 10426 10427 Tech.prototype.emulateTextTracks = function emulateTextTracks() { 10428 var _this5 = this; 10429 10430 var tracks = this.textTracks(); 10431 var remoteTracks = this.remoteTextTracks(); 10432 var handleAddTrack = function handleAddTrack(e) { 10433 return tracks.addTrack(e.track); 10434 }; 10435 var handleRemoveTrack = function handleRemoveTrack(e) { 10436 return tracks.removeTrack(e.track); 10437 }; 10438 10439 remoteTracks.on('addtrack', handleAddTrack); 10440 remoteTracks.on('removetrack', handleRemoveTrack); 10441 10442 this.addWebVttScript_(); 10443 10444 var updateDisplay = function updateDisplay() { 10445 return _this5.trigger('texttrackchange'); 10446 }; 10447 10448 var textTracksChanges = function textTracksChanges() { 10449 updateDisplay(); 10450 10451 for (var i = 0; i < tracks.length; i++) { 10452 var track = tracks[i]; 10453 10454 track.removeEventListener('cuechange', updateDisplay); 10455 if (track.mode === 'showing') { 10456 track.addEventListener('cuechange', updateDisplay); 10457 } 10458 } 10459 }; 10460 10461 textTracksChanges(); 10462 tracks.addEventListener('change', textTracksChanges); 10463 tracks.addEventListener('addtrack', textTracksChanges); 10464 tracks.addEventListener('removetrack', textTracksChanges); 10465 10466 this.on('dispose', function () { 10467 remoteTracks.off('addtrack', handleAddTrack); 10468 remoteTracks.off('removetrack', handleRemoveTrack); 10469 tracks.removeEventListener('change', textTracksChanges); 10470 tracks.removeEventListener('addtrack', textTracksChanges); 10471 tracks.removeEventListener('removetrack', textTracksChanges); 10472 10473 for (var i = 0; i < tracks.length; i++) { 10474 var track = tracks[i]; 10475 10476 track.removeEventListener('cuechange', updateDisplay); 10477 } 10478 }); 10479 }; 10480 10481 /** 10482 * Create and returns a remote {@link TextTrack} object. 10483 * 10484 * @param {string} kind 10485 * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata) 10486 * 10487 * @param {string} [label] 10488 * Label to identify the text track 10489 * 10490 * @param {string} [language] 10491 * Two letter language abbreviation 10492 * 10493 * @return {TextTrack} 10494 * The TextTrack that gets created. 10495 */ 10496 10497 10498 Tech.prototype.addTextTrack = function addTextTrack(kind, label, language) { 10499 if (!kind) { 10500 throw new Error('TextTrack kind is required but was not provided'); 10501 } 10502 10503 return createTrackHelper(this, kind, label, language); 10504 }; 10505 10506 /** 10507 * Create an emulated TextTrack for use by addRemoteTextTrack 10508 * 10509 * This is intended to be overridden by classes that inherit from 10510 * Tech in order to create native or custom TextTracks. 10511 * 10512 * @param {Object} options 10513 * The object should contain the options to initialize the TextTrack with. 10514 * 10515 * @param {string} [options.kind] 10516 * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata). 10517 * 10518 * @param {string} [options.label]. 10519 * Label to identify the text track 10520 * 10521 * @param {string} [options.language] 10522 * Two letter language abbreviation. 10523 * 10524 * @return {HTMLTrackElement} 10525 * The track element that gets created. 10526 */ 10527 10528 10529 Tech.prototype.createRemoteTextTrack = function createRemoteTextTrack(options) { 10530 var track = mergeOptions(options, { 10531 tech: this 10532 }); 10533 10534 return new REMOTE.remoteTextEl.TrackClass(track); 10535 }; 10536 10537 /** 10538 * Creates a remote text track object and returns an html track element. 10539 * 10540 * > Note: This can be an emulated {@link HTMLTrackElement} or a native one. 10541 * 10542 * @param {Object} options 10543 * See {@link Tech#createRemoteTextTrack} for more detailed properties. 10544 * 10545 * @param {boolean} [manualCleanup=true] 10546 * - When false: the TextTrack will be automatically removed from the video 10547 * element whenever the source changes 10548 * - When True: The TextTrack will have to be cleaned up manually 10549 * 10550 * @return {HTMLTrackElement} 10551 * An Html Track Element. 10552 * 10553 * @deprecated The default functionality for this function will be equivalent 10554 * to "manualCleanup=false" in the future. The manualCleanup parameter will 10555 * also be removed. 10556 */ 10557 10558 10559 Tech.prototype.addRemoteTextTrack = function addRemoteTextTrack() { 10560 var _this6 = this; 10561 10562 var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; 10563 var manualCleanup = arguments[1]; 10564 10565 var htmlTrackElement = this.createRemoteTextTrack(options); 10566 10567 if (manualCleanup !== true && manualCleanup !== false) { 10568 // deprecation warning 10569 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'); 10570 manualCleanup = true; 10571 } 10572 10573 // store HTMLTrackElement and TextTrack to remote list 10574 this.remoteTextTrackEls().addTrackElement_(htmlTrackElement); 10575 this.remoteTextTracks().addTrack(htmlTrackElement.track); 10576 10577 if (manualCleanup !== true) { 10578 // create the TextTrackList if it doesn't exist 10579 this.ready(function () { 10580 return _this6.autoRemoteTextTracks_.addTrack(htmlTrackElement.track); 10581 }); 10582 } 10583 10584 return htmlTrackElement; 10585 }; 10586 10587 /** 10588 * Remove a remote text track from the remote `TextTrackList`. 10589 * 10590 * @param {TextTrack} track 10591 * `TextTrack` to remove from the `TextTrackList` 10592 */ 10593 10594 10595 Tech.prototype.removeRemoteTextTrack = function removeRemoteTextTrack(track) { 10596 var trackElement = this.remoteTextTrackEls().getTrackElementByTrack_(track); 10597 10598 // remove HTMLTrackElement and TextTrack from remote list 10599 this.remoteTextTrackEls().removeTrackElement_(trackElement); 10600 this.remoteTextTracks().removeTrack(track); 10601 this.autoRemoteTextTracks_.removeTrack(track); 10602 }; 10603 10604 /** 10605 * Gets available media playback quality metrics as specified by the W3C's Media 10606 * Playback Quality API. 10607 * 10608 * @see [Spec]{@link https://wicg.github.io/media-playback-quality} 10609 * 10610 * @return {Object} 10611 * An object with supported media playback quality metrics 10612 * 10613 * @abstract 10614 */ 10615 10616 10617 Tech.prototype.getVideoPlaybackQuality = function getVideoPlaybackQuality() { 10618 return {}; 10619 }; 10620 10621 /** 10622 * A method to set a poster from a `Tech`. 10623 * 10624 * @abstract 10625 */ 10626 10627 10628 Tech.prototype.setPoster = function setPoster() {}; 10629 10630 /** 10631 * A method to check for the presence of the 'playsinine' <video> attribute. 10632 * 10633 * @abstract 10634 */ 10635 10636 10637 Tech.prototype.playsinline = function playsinline() {}; 10638 10639 /** 10640 * A method to set or unset the 'playsinine' <video> attribute. 10641 * 10642 * @abstract 10643 */ 10644 10645 10646 Tech.prototype.setPlaysinline = function setPlaysinline() {}; 10647 10648 /* 10649 * Check if the tech can support the given mime-type. 10650 * 10651 * The base tech does not support any type, but source handlers might 10652 * overwrite this. 10653 * 10654 * @param {string} type 10655 * The mimetype to check for support 10656 * 10657 * @return {string} 10658 * 'probably', 'maybe', or empty string 10659 * 10660 * @see [Spec]{@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/canPlayType} 10661 * 10662 * @abstract 10663 */ 10664 10665 10666 Tech.prototype.canPlayType = function canPlayType() { 10667 return ''; 10668 }; 10669 10670 /** 10671 * Check if the type is supported by this tech. 10672 * 10673 * The base tech does not support any type, but source handlers might 10674 * overwrite this. 10675 * 10676 * @param {string} type 10677 * The media type to check 10678 * @return {string} Returns the native video element's response 10679 */ 10680 10681 10682 Tech.canPlayType = function canPlayType() { 10683 return ''; 10684 }; 10685 10686 /** 10687 * Check if the tech can support the given source 10688 * @param {Object} srcObj 10689 * The source object 10690 * @param {Object} options 10691 * The options passed to the tech 10692 * @return {string} 'probably', 'maybe', or '' (empty string) 10693 */ 10694 10695 10696 Tech.canPlaySource = function canPlaySource(srcObj, options) { 10697 return Tech.canPlayType(srcObj.type); 10698 }; 10699 10700 /* 10701 * Return whether the argument is a Tech or not. 10702 * Can be passed either a Class like `Html5` or a instance like `player.tech_` 10703 * 10704 * @param {Object} component 10705 * The item to check 10706 * 10707 * @return {boolean} 10708 * Whether it is a tech or not 10709 * - True if it is a tech 10710 * - False if it is not 10711 */ 10712 10713 10714 Tech.isTech = function isTech(component) { 10715 return component.prototype instanceof Tech || component instanceof Tech || component === Tech; 10716 }; 10717 10718 /** 10719 * Registers a `Tech` into a shared list for videojs. 10720 * 10721 * @param {string} name 10722 * Name of the `Tech` to register. 10723 * 10724 * @param {Object} tech 10725 * The `Tech` class to register. 10726 */ 10727 10728 10729 Tech.registerTech = function registerTech(name, tech) { 10730 if (!Tech.techs_) { 10731 Tech.techs_ = {}; 10732 } 10733 10734 if (!Tech.isTech(tech)) { 10735 throw new Error('Tech ' + name + ' must be a Tech'); 10736 } 10737 10738 if (!Tech.canPlayType) { 10739 throw new Error('Techs must have a static canPlayType method on them'); 10740 } 10741 if (!Tech.canPlaySource) { 10742 throw new Error('Techs must have a static canPlaySource method on them'); 10743 } 10744 10745 name = toTitleCase(name); 10746 10747 Tech.techs_[name] = tech; 10748 if (name !== 'Tech') { 10749 // camel case the techName for use in techOrder 10750 Tech.defaultTechOrder_.push(name); 10751 } 10752 return tech; 10753 }; 10754 10755 /** 10756 * Get a `Tech` from the shared list by name. 10757 * 10758 * @param {string} name 10759 * `camelCase` or `TitleCase` name of the Tech to get 10760 * 10761 * @return {Tech|undefined} 10762 * The `Tech` or undefined if there was no tech with the name requsted. 10763 */ 10764 10765 10766 Tech.getTech = function getTech(name) { 10767 if (!name) { 10768 return; 10769 } 10770 10771 name = toTitleCase(name); 10772 10773 if (Tech.techs_ && Tech.techs_[name]) { 10774 return Tech.techs_[name]; 10775 } 10776 10777 if (window_1 && window_1.videojs && window_1.videojs[name]) { 10778 log$1.warn('The ' + name + ' tech was added to the videojs object when it should be registered using videojs.registerTech(name, tech)'); 10779 return window_1.videojs[name]; 10780 } 10781 }; 10782 10783 return Tech; 10784}(Component); 10785 10786/** 10787 * Get the {@link VideoTrackList} 10788 * 10789 * @returns {VideoTrackList} 10790 * @method Tech.prototype.videoTracks 10791 */ 10792 10793/** 10794 * Get the {@link AudioTrackList} 10795 * 10796 * @returns {AudioTrackList} 10797 * @method Tech.prototype.audioTracks 10798 */ 10799 10800/** 10801 * Get the {@link TextTrackList} 10802 * 10803 * @returns {TextTrackList} 10804 * @method Tech.prototype.textTracks 10805 */ 10806 10807/** 10808 * Get the remote element {@link TextTrackList} 10809 * 10810 * @returns {TextTrackList} 10811 * @method Tech.prototype.remoteTextTracks 10812 */ 10813 10814/** 10815 * Get the remote element {@link HtmlTrackElementList} 10816 * 10817 * @returns {HtmlTrackElementList} 10818 * @method Tech.prototype.remoteTextTrackEls 10819 */ 10820 10821ALL.names.forEach(function (name) { 10822 var props = ALL[name]; 10823 10824 Tech.prototype[props.getterName] = function () { 10825 this[props.privateName] = this[props.privateName] || new props.ListClass(); 10826 return this[props.privateName]; 10827 }; 10828}); 10829 10830/** 10831 * List of associated text tracks 10832 * 10833 * @type {TextTrackList} 10834 * @private 10835 * @property Tech#textTracks_ 10836 */ 10837 10838/** 10839 * List of associated audio tracks. 10840 * 10841 * @type {AudioTrackList} 10842 * @private 10843 * @property Tech#audioTracks_ 10844 */ 10845 10846/** 10847 * List of associated video tracks. 10848 * 10849 * @type {VideoTrackList} 10850 * @private 10851 * @property Tech#videoTracks_ 10852 */ 10853 10854/** 10855 * Boolean indicating wether the `Tech` supports volume control. 10856 * 10857 * @type {boolean} 10858 * @default 10859 */ 10860Tech.prototype.featuresVolumeControl = true; 10861 10862/** 10863 * Boolean indicating wether the `Tech` support fullscreen resize control. 10864 * Resizing plugins using request fullscreen reloads the plugin 10865 * 10866 * @type {boolean} 10867 * @default 10868 */ 10869Tech.prototype.featuresFullscreenResize = false; 10870 10871/** 10872 * Boolean indicating wether the `Tech` supports changing the speed at which the video 10873 * plays. Examples: 10874 * - Set player to play 2x (twice) as fast 10875 * - Set player to play 0.5x (half) as fast 10876 * 10877 * @type {boolean} 10878 * @default 10879 */ 10880Tech.prototype.featuresPlaybackRate = false; 10881 10882/** 10883 * Boolean indicating wether the `Tech` supports the `progress` event. This is currently 10884 * not triggered by video-js-swf. This will be used to determine if 10885 * {@link Tech#manualProgressOn} should be called. 10886 * 10887 * @type {boolean} 10888 * @default 10889 */ 10890Tech.prototype.featuresProgressEvents = false; 10891 10892/** 10893 * Boolean indicating wether the `Tech` supports the `timeupdate` event. This is currently 10894 * not triggered by video-js-swf. This will be used to determine if 10895 * {@link Tech#manualTimeUpdates} should be called. 10896 * 10897 * @type {boolean} 10898 * @default 10899 */ 10900Tech.prototype.featuresTimeupdateEvents = false; 10901 10902/** 10903 * Boolean indicating wether the `Tech` supports the native `TextTrack`s. 10904 * This will help us integrate with native `TextTrack`s if the browser supports them. 10905 * 10906 * @type {boolean} 10907 * @default 10908 */ 10909Tech.prototype.featuresNativeTextTracks = false; 10910 10911/** 10912 * A functional mixin for techs that want to use the Source Handler pattern. 10913 * Source handlers are scripts for handling specific formats. 10914 * The source handler pattern is used for adaptive formats (HLS, DASH) that 10915 * manually load video data and feed it into a Source Buffer (Media Source Extensions) 10916 * Example: `Tech.withSourceHandlers.call(MyTech);` 10917 * 10918 * @param {Tech} _Tech 10919 * The tech to add source handler functions to. 10920 * 10921 * @mixes Tech~SourceHandlerAdditions 10922 */ 10923Tech.withSourceHandlers = function (_Tech) { 10924 10925 /** 10926 * Register a source handler 10927 * 10928 * @param {Function} handler 10929 * The source handler class 10930 * 10931 * @param {number} [index] 10932 * Register it at the following index 10933 */ 10934 _Tech.registerSourceHandler = function (handler, index) { 10935 var handlers = _Tech.sourceHandlers; 10936 10937 if (!handlers) { 10938 handlers = _Tech.sourceHandlers = []; 10939 } 10940 10941 if (index === undefined) { 10942 // add to the end of the list 10943 index = handlers.length; 10944 } 10945 10946 handlers.splice(index, 0, handler); 10947 }; 10948 10949 /** 10950 * Check if the tech can support the given type. Also checks the 10951 * Techs sourceHandlers. 10952 * 10953 * @param {string} type 10954 * The mimetype to check. 10955 * 10956 * @return {string} 10957 * 'probably', 'maybe', or '' (empty string) 10958 */ 10959 _Tech.canPlayType = function (type) { 10960 var handlers = _Tech.sourceHandlers || []; 10961 var can = void 0; 10962 10963 for (var i = 0; i < handlers.length; i++) { 10964 can = handlers[i].canPlayType(type); 10965 10966 if (can) { 10967 return can; 10968 } 10969 } 10970 10971 return ''; 10972 }; 10973 10974 /** 10975 * Returns the first source handler that supports the source. 10976 * 10977 * TODO: Answer question: should 'probably' be prioritized over 'maybe' 10978 * 10979 * @param {Tech~SourceObject} source 10980 * The source object 10981 * 10982 * @param {Object} options 10983 * The options passed to the tech 10984 * 10985 * @return {SourceHandler|null} 10986 * The first source handler that supports the source or null if 10987 * no SourceHandler supports the source 10988 */ 10989 _Tech.selectSourceHandler = function (source, options) { 10990 var handlers = _Tech.sourceHandlers || []; 10991 var can = void 0; 10992 10993 for (var i = 0; i < handlers.length; i++) { 10994 can = handlers[i].canHandleSource(source, options); 10995 10996 if (can) { 10997 return handlers[i]; 10998 } 10999 } 11000 11001 return null; 11002 }; 11003 11004 /** 11005 * Check if the tech can support the given source. 11006 * 11007 * @param {Tech~SourceObject} srcObj 11008 * The source object 11009 * 11010 * @param {Object} options 11011 * The options passed to the tech 11012 * 11013 * @return {string} 11014 * 'probably', 'maybe', or '' (empty string) 11015 */ 11016 _Tech.canPlaySource = function (srcObj, options) { 11017 var sh = _Tech.selectSourceHandler(srcObj, options); 11018 11019 if (sh) { 11020 return sh.canHandleSource(srcObj, options); 11021 } 11022 11023 return ''; 11024 }; 11025 11026 /** 11027 * When using a source handler, prefer its implementation of 11028 * any function normally provided by the tech. 11029 */ 11030 var deferrable = ['seekable', 'duration']; 11031 11032 /** 11033 * A wrapper around {@link Tech#seekable} that will call a `SourceHandler`s seekable 11034 * function if it exists, with a fallback to the Techs seekable function. 11035 * 11036 * @method _Tech.seekable 11037 */ 11038 11039 /** 11040 * A wrapper around {@link Tech#duration} that will call a `SourceHandler`s duration 11041 * function if it exists, otherwise it will fallback to the techs duration function. 11042 * 11043 * @method _Tech.duration 11044 */ 11045 11046 deferrable.forEach(function (fnName) { 11047 var originalFn = this[fnName]; 11048 11049 if (typeof originalFn !== 'function') { 11050 return; 11051 } 11052 11053 this[fnName] = function () { 11054 if (this.sourceHandler_ && this.sourceHandler_[fnName]) { 11055 return this.sourceHandler_[fnName].apply(this.sourceHandler_, arguments); 11056 } 11057 return originalFn.apply(this, arguments); 11058 }; 11059 }, _Tech.prototype); 11060 11061 /** 11062 * Create a function for setting the source using a source object 11063 * and source handlers. 11064 * Should never be called unless a source handler was found. 11065 * 11066 * @param {Tech~SourceObject} source 11067 * A source object with src and type keys 11068 */ 11069 _Tech.prototype.setSource = function (source) { 11070 var sh = _Tech.selectSourceHandler(source, this.options_); 11071 11072 if (!sh) { 11073 // Fall back to a native source hander when unsupported sources are 11074 // deliberately set 11075 if (_Tech.nativeSourceHandler) { 11076 sh = _Tech.nativeSourceHandler; 11077 } else { 11078 log$1.error('No source hander found for the current source.'); 11079 } 11080 } 11081 11082 // Dispose any existing source handler 11083 this.disposeSourceHandler(); 11084 this.off('dispose', this.disposeSourceHandler); 11085 11086 if (sh !== _Tech.nativeSourceHandler) { 11087 this.currentSource_ = source; 11088 } 11089 11090 this.sourceHandler_ = sh.handleSource(source, this, this.options_); 11091 this.on('dispose', this.disposeSourceHandler); 11092 }; 11093 11094 /** 11095 * Clean up any existing SourceHandlers and listeners when the Tech is disposed. 11096 * 11097 * @listens Tech#dispose 11098 */ 11099 _Tech.prototype.disposeSourceHandler = function () { 11100 // if we have a source and get another one 11101 // then we are loading something new 11102 // than clear all of our current tracks 11103 if (this.currentSource_) { 11104 this.clearTracks(['audio', 'video']); 11105 this.currentSource_ = null; 11106 } 11107 11108 // always clean up auto-text tracks 11109 this.cleanupAutoTextTracks(); 11110 11111 if (this.sourceHandler_) { 11112 11113 if (this.sourceHandler_.dispose) { 11114 this.sourceHandler_.dispose(); 11115 } 11116 11117 this.sourceHandler_ = null; 11118 } 11119 }; 11120}; 11121 11122// The base Tech class needs to be registered as a Component. It is the only 11123// Tech that can be registered as a Component. 11124Component.registerComponent('Tech', Tech); 11125Tech.registerTech('Tech', Tech); 11126 11127/** 11128 * A list of techs that should be added to techOrder on Players 11129 * 11130 * @private 11131 */ 11132Tech.defaultTechOrder_ = []; 11133 11134var middlewares = {}; 11135 11136function use(type, middleware) { 11137 middlewares[type] = middlewares[type] || []; 11138 middlewares[type].push(middleware); 11139} 11140 11141 11142 11143function setSource(player, src, next) { 11144 player.setTimeout(function () { 11145 return setSourceHelper(src, middlewares[src.type], next, player); 11146 }, 1); 11147} 11148 11149function setTech(middleware, tech) { 11150 middleware.forEach(function (mw) { 11151 return mw.setTech && mw.setTech(tech); 11152 }); 11153} 11154 11155function get$1(middleware, tech, method) { 11156 return middleware.reduceRight(middlewareIterator(method), tech[method]()); 11157} 11158 11159function set$1(middleware, tech, method, arg) { 11160 return tech[method](middleware.reduce(middlewareIterator(method), arg)); 11161} 11162 11163var allowedGetters = { 11164 buffered: 1, 11165 currentTime: 1, 11166 duration: 1, 11167 seekable: 1, 11168 played: 1 11169}; 11170 11171var allowedSetters = { 11172 setCurrentTime: 1 11173}; 11174 11175function middlewareIterator(method) { 11176 return function (value, mw) { 11177 if (mw[method]) { 11178 return mw[method](value); 11179 } 11180 11181 return value; 11182 }; 11183} 11184 11185function setSourceHelper() { 11186 var src = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; 11187 var middleware = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : []; 11188 var next = arguments[2]; 11189 var player = arguments[3]; 11190 var acc = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : []; 11191 var lastRun = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : false; 11192 var mwFactory = middleware[0], 11193 mwrest = middleware.slice(1); 11194 11195 // if mwFactory is a string, then we're at a fork in the road 11196 11197 if (typeof mwFactory === 'string') { 11198 setSourceHelper(src, middlewares[mwFactory], next, player, acc, lastRun); 11199 11200 // if we have an mwFactory, call it with the player to get the mw, 11201 // then call the mw's setSource method 11202 } else if (mwFactory) { 11203 var mw = mwFactory(player); 11204 11205 mw.setSource(assign({}, src), function (err, _src) { 11206 11207 // something happened, try the next middleware on the current level 11208 // make sure to use the old src 11209 if (err) { 11210 return setSourceHelper(src, mwrest, next, player, acc, lastRun); 11211 } 11212 11213 // we've succeeded, now we need to go deeper 11214 acc.push(mw); 11215 11216 // if it's the same time, continue does the current chain 11217 // otherwise, we want to go down the new chain 11218 setSourceHelper(_src, src.type === _src.type ? mwrest : middlewares[_src.type], next, player, acc, lastRun); 11219 }); 11220 } else if (mwrest.length) { 11221 setSourceHelper(src, mwrest, next, player, acc, lastRun); 11222 } else if (lastRun) { 11223 next(src, acc); 11224 } else { 11225 setSourceHelper(src, middlewares['*'], next, player, acc, true); 11226 } 11227} 11228 11229/** 11230 * @module filter-source 11231 */ 11232/** 11233 * Filter out single bad source objects or multiple source objects in an 11234 * array. Also flattens nested source object arrays into a 1 dimensional 11235 * array of source objects. 11236 * 11237 * @param {Tech~SourceObject|Tech~SourceObject[]} src 11238 * The src object to filter 11239 * 11240 * @return {Tech~SourceObject[]} 11241 * An array of sourceobjects containing only valid sources 11242 * 11243 * @private 11244 */ 11245var filterSource = function filterSource(src) { 11246 // traverse array 11247 if (Array.isArray(src)) { 11248 var newsrc = []; 11249 11250 src.forEach(function (srcobj) { 11251 srcobj = filterSource(srcobj); 11252 11253 if (Array.isArray(srcobj)) { 11254 newsrc = newsrc.concat(srcobj); 11255 } else if (isObject(srcobj)) { 11256 newsrc.push(srcobj); 11257 } 11258 }); 11259 11260 src = newsrc; 11261 } else if (typeof src === 'string' && src.trim()) { 11262 // convert string into object 11263 src = [{ src: src }]; 11264 } else if (isObject(src) && typeof src.src === 'string' && src.src && src.src.trim()) { 11265 // src is already valid 11266 src = [src]; 11267 } else { 11268 // invalid source, turn it into an empty array 11269 src = []; 11270 } 11271 11272 return src; 11273}; 11274 11275/** 11276 * @file loader.js 11277 */ 11278/** 11279 * The `MediaLoader` is the `Component` that decides which playback technology to load 11280 * when a player is initialized. 11281 * 11282 * @extends Component 11283 */ 11284 11285var MediaLoader = function (_Component) { 11286 inherits(MediaLoader, _Component); 11287 11288 /** 11289 * Create an instance of this class. 11290 * 11291 * @param {Player} player 11292 * The `Player` that this class should attach to. 11293 * 11294 * @param {Object} [options] 11295 * The key/value stroe of player options. 11296 * 11297 * @param {Component~ReadyCallback} [ready] 11298 * The function that is run when this component is ready. 11299 */ 11300 function MediaLoader(player, options, ready) { 11301 classCallCheck(this, MediaLoader); 11302 11303 // MediaLoader has no element 11304 var options_ = mergeOptions({ createEl: false }, options); 11305 11306 // If there are no sources when the player is initialized, 11307 // load the first supported playback technology. 11308 11309 var _this = possibleConstructorReturn(this, _Component.call(this, player, options_, ready)); 11310 11311 if (!options.playerOptions.sources || options.playerOptions.sources.length === 0) { 11312 for (var i = 0, j = options.playerOptions.techOrder; i < j.length; i++) { 11313 var techName = toTitleCase(j[i]); 11314 var tech = Tech.getTech(techName); 11315 11316 // Support old behavior of techs being registered as components. 11317 // Remove once that deprecated behavior is removed. 11318 if (!techName) { 11319 tech = Component.getComponent(techName); 11320 } 11321 11322 // Check if the browser supports this technology 11323 if (tech && tech.isSupported()) { 11324 player.loadTech_(techName); 11325 break; 11326 } 11327 } 11328 } else { 11329 // Loop through playback technologies (HTML5, Flash) and check for support. 11330 // Then load the best source. 11331 // A few assumptions here: 11332 // All playback technologies respect preload false. 11333 player.src(options.playerOptions.sources); 11334 } 11335 return _this; 11336 } 11337 11338 return MediaLoader; 11339}(Component); 11340 11341Component.registerComponent('MediaLoader', MediaLoader); 11342 11343/** 11344 * @file button.js 11345 */ 11346/** 11347 * Clickable Component which is clickable or keyboard actionable, 11348 * but is not a native HTML button. 11349 * 11350 * @extends Component 11351 */ 11352 11353var ClickableComponent = function (_Component) { 11354 inherits(ClickableComponent, _Component); 11355 11356 /** 11357 * Creates an instance of this class. 11358 * 11359 * @param {Player} player 11360 * The `Player` that this class should be attached to. 11361 * 11362 * @param {Object} [options] 11363 * The key/value store of player options. 11364 */ 11365 function ClickableComponent(player, options) { 11366 classCallCheck(this, ClickableComponent); 11367 11368 var _this = possibleConstructorReturn(this, _Component.call(this, player, options)); 11369 11370 _this.emitTapEvents(); 11371 11372 _this.enable(); 11373 return _this; 11374 } 11375 11376 /** 11377 * Create the `Component`s DOM element. 11378 * 11379 * @param {string} [tag=div] 11380 * The element's node type. 11381 * 11382 * @param {Object} [props={}] 11383 * An object of properties that should be set on the element. 11384 * 11385 * @param {Object} [attributes={}] 11386 * An object of attributes that should be set on the element. 11387 * 11388 * @return {Element} 11389 * The element that gets created. 11390 */ 11391 11392 11393 ClickableComponent.prototype.createEl = function createEl$$1() { 11394 var tag = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'div'; 11395 var props = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 11396 var attributes = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; 11397 11398 props = assign({ 11399 innerHTML: '<span aria-hidden="true" class="vjs-icon-placeholder"></span>', 11400 className: this.buildCSSClass(), 11401 tabIndex: 0 11402 }, props); 11403 11404 if (tag === 'button') { 11405 log$1.error('Creating a ClickableComponent with an HTML element of ' + tag + ' is not supported; use a Button instead.'); 11406 } 11407 11408 // Add ARIA attributes for clickable element which is not a native HTML button 11409 attributes = assign({ 11410 'role': 'button', 11411 11412 // let the screen reader user know that the text of the element may change 11413 'aria-live': 'polite' 11414 }, attributes); 11415 11416 this.tabIndex_ = props.tabIndex; 11417 11418 var el = _Component.prototype.createEl.call(this, tag, props, attributes); 11419 11420 this.createControlTextEl(el); 11421 11422 return el; 11423 }; 11424 11425 /** 11426 * Create a control text element on this `Component` 11427 * 11428 * @param {Element} [el] 11429 * Parent element for the control text. 11430 * 11431 * @return {Element} 11432 * The control text element that gets created. 11433 */ 11434 11435 11436 ClickableComponent.prototype.createControlTextEl = function createControlTextEl(el) { 11437 this.controlTextEl_ = createEl('span', { 11438 className: 'vjs-control-text' 11439 }); 11440 11441 if (el) { 11442 el.appendChild(this.controlTextEl_); 11443 } 11444 11445 this.controlText(this.controlText_, el); 11446 11447 return this.controlTextEl_; 11448 }; 11449 11450 /** 11451 * Get or set the localize text to use for the controls on the `Component`. 11452 * 11453 * @param {string} [text] 11454 * Control text for element. 11455 * 11456 * @param {Element} [el=this.el()] 11457 * Element to set the title on. 11458 * 11459 * @return {string} 11460 * - The control text when getting 11461 */ 11462 11463 11464 ClickableComponent.prototype.controlText = function controlText(text) { 11465 var el = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.el(); 11466 11467 if (!text) { 11468 return this.controlText_ || 'Need Text'; 11469 } 11470 11471 var localizedText = this.localize(text); 11472 11473 this.controlText_ = text; 11474 textContent(this.controlTextEl_, localizedText); 11475 if (!this.nonIconControl) { 11476 // Set title attribute if only an icon is shown 11477 el.setAttribute('title', localizedText); 11478 } 11479 }; 11480 11481 /** 11482 * Builds the default DOM `className`. 11483 * 11484 * @return {string} 11485 * The DOM `className` for this object. 11486 */ 11487 11488 11489 ClickableComponent.prototype.buildCSSClass = function buildCSSClass() { 11490 return 'vjs-control vjs-button ' + _Component.prototype.buildCSSClass.call(this); 11491 }; 11492 11493 /** 11494 * Enable this `Component`s element. 11495 */ 11496 11497 11498 ClickableComponent.prototype.enable = function enable() { 11499 if (!this.enabled_) { 11500 this.enabled_ = true; 11501 this.removeClass('vjs-disabled'); 11502 this.el_.setAttribute('aria-disabled', 'false'); 11503 if (typeof this.tabIndex_ !== 'undefined') { 11504 this.el_.setAttribute('tabIndex', this.tabIndex_); 11505 } 11506 this.on(['tap', 'click'], this.handleClick); 11507 this.on('focus', this.handleFocus); 11508 this.on('blur', this.handleBlur); 11509 } 11510 }; 11511 11512 /** 11513 * Disable this `Component`s element. 11514 */ 11515 11516 11517 ClickableComponent.prototype.disable = function disable() { 11518 this.enabled_ = false; 11519 this.addClass('vjs-disabled'); 11520 this.el_.setAttribute('aria-disabled', 'true'); 11521 if (typeof this.tabIndex_ !== 'undefined') { 11522 this.el_.removeAttribute('tabIndex'); 11523 } 11524 this.off(['tap', 'click'], this.handleClick); 11525 this.off('focus', this.handleFocus); 11526 this.off('blur', this.handleBlur); 11527 }; 11528 11529 /** 11530 * This gets called when a `ClickableComponent` gets: 11531 * - Clicked (via the `click` event, listening starts in the constructor) 11532 * - Tapped (via the `tap` event, listening starts in the constructor) 11533 * - The following things happen in order: 11534 * 1. {@link ClickableComponent#handleFocus} is called via a `focus` event on the 11535 * `ClickableComponent`. 11536 * 2. {@link ClickableComponent#handleFocus} adds a listener for `keydown` on using 11537 * {@link ClickableComponent#handleKeyPress}. 11538 * 3. `ClickableComponent` has not had a `blur` event (`blur` means that focus was lost). The user presses 11539 * the space or enter key. 11540 * 4. {@link ClickableComponent#handleKeyPress} calls this function with the `keydown` 11541 * event as a parameter. 11542 * 11543 * @param {EventTarget~Event} event 11544 * The `keydown`, `tap`, or `click` event that caused this function to be 11545 * called. 11546 * 11547 * @listens tap 11548 * @listens click 11549 * @abstract 11550 */ 11551 11552 11553 ClickableComponent.prototype.handleClick = function handleClick(event) {}; 11554 11555 /** 11556 * This gets called when a `ClickableComponent` gains focus via a `focus` event. 11557 * Turns on listening for `keydown` events. When they happen it 11558 * calls `this.handleKeyPress`. 11559 * 11560 * @param {EventTarget~Event} event 11561 * The `focus` event that caused this function to be called. 11562 * 11563 * @listens focus 11564 */ 11565 11566 11567 ClickableComponent.prototype.handleFocus = function handleFocus(event) { 11568 on(document_1, 'keydown', bind(this, this.handleKeyPress)); 11569 }; 11570 11571 /** 11572 * Called when this ClickableComponent has focus and a key gets pressed down. By 11573 * default it will call `this.handleClick` when the key is space or enter. 11574 * 11575 * @param {EventTarget~Event} event 11576 * The `keydown` event that caused this function to be called. 11577 * 11578 * @listens keydown 11579 */ 11580 11581 11582 ClickableComponent.prototype.handleKeyPress = function handleKeyPress(event) { 11583 11584 // Support Space (32) or Enter (13) key operation to fire a click event 11585 if (event.which === 32 || event.which === 13) { 11586 event.preventDefault(); 11587 this.trigger('click'); 11588 } else if (_Component.prototype.handleKeyPress) { 11589 11590 // Pass keypress handling up for unsupported keys 11591 _Component.prototype.handleKeyPress.call(this, event); 11592 } 11593 }; 11594 11595 /** 11596 * Called when a `ClickableComponent` loses focus. Turns off the listener for 11597 * `keydown` events. Which Stops `this.handleKeyPress` from getting called. 11598 * 11599 * @param {EventTarget~Event} event 11600 * The `blur` event that caused this function to be called. 11601 * 11602 * @listens blur 11603 */ 11604 11605 11606 ClickableComponent.prototype.handleBlur = function handleBlur(event) { 11607 off(document_1, 'keydown', bind(this, this.handleKeyPress)); 11608 }; 11609 11610 return ClickableComponent; 11611}(Component); 11612 11613Component.registerComponent('ClickableComponent', ClickableComponent); 11614 11615/** 11616 * @file poster-image.js 11617 */ 11618/** 11619 * A `ClickableComponent` that handles showing the poster image for the player. 11620 * 11621 * @extends ClickableComponent 11622 */ 11623 11624var PosterImage = function (_ClickableComponent) { 11625 inherits(PosterImage, _ClickableComponent); 11626 11627 /** 11628 * Create an instance of this class. 11629 * 11630 * @param {Player} player 11631 * The `Player` that this class should attach to. 11632 * 11633 * @param {Object} [options] 11634 * The key/value store of player options. 11635 */ 11636 function PosterImage(player, options) { 11637 classCallCheck(this, PosterImage); 11638 11639 var _this = possibleConstructorReturn(this, _ClickableComponent.call(this, player, options)); 11640 11641 _this.update(); 11642 player.on('posterchange', bind(_this, _this.update)); 11643 return _this; 11644 } 11645 11646 /** 11647 * Clean up and dispose of the `PosterImage`. 11648 */ 11649 11650 11651 PosterImage.prototype.dispose = function dispose() { 11652 this.player().off('posterchange', this.update); 11653 _ClickableComponent.prototype.dispose.call(this); 11654 }; 11655 11656 /** 11657 * Create the `PosterImage`s DOM element. 11658 * 11659 * @return {Element} 11660 * The element that gets created. 11661 */ 11662 11663 11664 PosterImage.prototype.createEl = function createEl$$1() { 11665 var el = createEl('div', { 11666 className: 'vjs-poster', 11667 11668 // Don't want poster to be tabbable. 11669 tabIndex: -1 11670 }); 11671 11672 // To ensure the poster image resizes while maintaining its original aspect 11673 // ratio, use a div with `background-size` when available. For browsers that 11674 // do not support `background-size` (e.g. IE8), fall back on using a regular 11675 // img element. 11676 if (!BACKGROUND_SIZE_SUPPORTED) { 11677 this.fallbackImg_ = createEl('img'); 11678 el.appendChild(this.fallbackImg_); 11679 } 11680 11681 return el; 11682 }; 11683 11684 /** 11685 * An {@link EventTarget~EventListener} for {@link Player#posterchange} events. 11686 * 11687 * @listens Player#posterchange 11688 * 11689 * @param {EventTarget~Event} [event] 11690 * The `Player#posterchange` event that triggered this function. 11691 */ 11692 11693 11694 PosterImage.prototype.update = function update(event) { 11695 var url = this.player().poster(); 11696 11697 this.setSrc(url); 11698 11699 // If there's no poster source we should display:none on this component 11700 // so it's not still clickable or right-clickable 11701 if (url) { 11702 this.show(); 11703 } else { 11704 this.hide(); 11705 } 11706 }; 11707 11708 /** 11709 * Set the source of the `PosterImage` depending on the display method. 11710 * 11711 * @param {string} url 11712 * The URL to the source for the `PosterImage`. 11713 */ 11714 11715 11716 PosterImage.prototype.setSrc = function setSrc(url) { 11717 if (this.fallbackImg_) { 11718 this.fallbackImg_.src = url; 11719 } else { 11720 var backgroundImage = ''; 11721 11722 // Any falsey values should stay as an empty string, otherwise 11723 // this will throw an extra error 11724 if (url) { 11725 backgroundImage = 'url("' + url + '")'; 11726 } 11727 11728 this.el_.style.backgroundImage = backgroundImage; 11729 } 11730 }; 11731 11732 /** 11733 * An {@link EventTarget~EventListener} for clicks on the `PosterImage`. See 11734 * {@link ClickableComponent#handleClick} for instances where this will be triggered. 11735 * 11736 * @listens tap 11737 * @listens click 11738 * @listens keydown 11739 * 11740 * @param {EventTarget~Event} event 11741 + The `click`, `tap` or `keydown` event that caused this function to be called. 11742 */ 11743 11744 11745 PosterImage.prototype.handleClick = function handleClick(event) { 11746 // We don't want a click to trigger playback when controls are disabled 11747 if (!this.player_.controls()) { 11748 return; 11749 } 11750 11751 if (this.player_.paused()) { 11752 this.player_.play(); 11753 } else { 11754 this.player_.pause(); 11755 } 11756 }; 11757 11758 return PosterImage; 11759}(ClickableComponent); 11760 11761Component.registerComponent('PosterImage', PosterImage); 11762 11763/** 11764 * @file text-track-display.js 11765 */ 11766var darkGray = '#222'; 11767var lightGray = '#ccc'; 11768var fontMap = { 11769 monospace: 'monospace', 11770 sansSerif: 'sans-serif', 11771 serif: 'serif', 11772 monospaceSansSerif: '"Andale Mono", "Lucida Console", monospace', 11773 monospaceSerif: '"Courier New", monospace', 11774 proportionalSansSerif: 'sans-serif', 11775 proportionalSerif: 'serif', 11776 casual: '"Comic Sans MS", Impact, fantasy', 11777 script: '"Monotype Corsiva", cursive', 11778 smallcaps: '"Andale Mono", "Lucida Console", monospace, sans-serif' 11779}; 11780 11781/** 11782 * Construct an rgba color from a given hex color code. 11783 * 11784 * @param {number} color 11785 * Hex number for color, like #f0e. 11786 * 11787 * @param {number} opacity 11788 * Value for opacity, 0.0 - 1.0. 11789 * 11790 * @return {string} 11791 * The rgba color that was created, like 'rgba(255, 0, 0, 0.3)'. 11792 * 11793 * @private 11794 */ 11795function constructColor(color, opacity) { 11796 return 'rgba(' + 11797 // color looks like "#f0e" 11798 parseInt(color[1] + color[1], 16) + ',' + parseInt(color[2] + color[2], 16) + ',' + parseInt(color[3] + color[3], 16) + ',' + opacity + ')'; 11799} 11800 11801/** 11802 * Try to update the style of a DOM element. Some style changes will throw an error, 11803 * particularly in IE8. Those should be noops. 11804 * 11805 * @param {Element} el 11806 * The DOM element to be styled. 11807 * 11808 * @param {string} style 11809 * The CSS property on the element that should be styled. 11810 * 11811 * @param {string} rule 11812 * The style rule that should be applied to the property. 11813 * 11814 * @private 11815 */ 11816function tryUpdateStyle(el, style, rule) { 11817 try { 11818 el.style[style] = rule; 11819 } catch (e) { 11820 11821 // Satisfies linter. 11822 return; 11823 } 11824} 11825 11826/** 11827 * The component for displaying text track cues. 11828 * 11829 * @extends Component 11830 */ 11831 11832var TextTrackDisplay = function (_Component) { 11833 inherits(TextTrackDisplay, _Component); 11834 11835 /** 11836 * Creates an instance of this class. 11837 * 11838 * @param {Player} player 11839 * The `Player` that this class should be attached to. 11840 * 11841 * @param {Object} [options] 11842 * The key/value store of player options. 11843 * 11844 * @param {Component~ReadyCallback} [ready] 11845 * The function to call when `TextTrackDisplay` is ready. 11846 */ 11847 function TextTrackDisplay(player, options, ready) { 11848 classCallCheck(this, TextTrackDisplay); 11849 11850 var _this = possibleConstructorReturn(this, _Component.call(this, player, options, ready)); 11851 11852 player.on('loadstart', bind(_this, _this.toggleDisplay)); 11853 player.on('texttrackchange', bind(_this, _this.updateDisplay)); 11854 player.on('loadstart', bind(_this, _this.preselectTrack)); 11855 11856 // This used to be called during player init, but was causing an error 11857 // if a track should show by default and the display hadn't loaded yet. 11858 // Should probably be moved to an external track loader when we support 11859 // tracks that don't need a display. 11860 player.ready(bind(_this, function () { 11861 if (player.tech_ && player.tech_.featuresNativeTextTracks) { 11862 this.hide(); 11863 return; 11864 } 11865 11866 player.on('fullscreenchange', bind(this, this.updateDisplay)); 11867 11868 var tracks = this.options_.playerOptions.tracks || []; 11869 11870 for (var i = 0; i < tracks.length; i++) { 11871 this.player_.addRemoteTextTrack(tracks[i], true); 11872 } 11873 11874 this.preselectTrack(); 11875 })); 11876 return _this; 11877 } 11878 11879 /** 11880 * Preselect a track following this precedence: 11881 * - matches the previously selected {@link TextTrack}'s language and kind 11882 * - matches the previously selected {@link TextTrack}'s language only 11883 * - is the first default captions track 11884 * - is the first default descriptions track 11885 * 11886 * @listens Player#loadstart 11887 */ 11888 11889 11890 TextTrackDisplay.prototype.preselectTrack = function preselectTrack() { 11891 var modes = { captions: 1, subtitles: 1 }; 11892 var trackList = this.player_.textTracks(); 11893 var userPref = this.player_.cache_.selectedLanguage; 11894 var firstDesc = void 0; 11895 var firstCaptions = void 0; 11896 var preferredTrack = void 0; 11897 11898 for (var i = 0; i < trackList.length; i++) { 11899 var track = trackList[i]; 11900 11901 if (userPref && userPref.enabled && userPref.language === track.language) { 11902 // Always choose the track that matches both language and kind 11903 if (track.kind === userPref.kind) { 11904 preferredTrack = track; 11905 // or choose the first track that matches language 11906 } else if (!preferredTrack) { 11907 preferredTrack = track; 11908 } 11909 11910 // clear everything if offTextTrackMenuItem was clicked 11911 } else if (userPref && !userPref.enabled) { 11912 preferredTrack = null; 11913 firstDesc = null; 11914 firstCaptions = null; 11915 } else if (track['default']) { 11916 if (track.kind === 'descriptions' && !firstDesc) { 11917 firstDesc = track; 11918 } else if (track.kind in modes && !firstCaptions) { 11919 firstCaptions = track; 11920 } 11921 } 11922 } 11923 11924 // The preferredTrack matches the user preference and takes 11925 // precendence over all the other tracks. 11926 // So, display the preferredTrack before the first default track 11927 // and the subtitles/captions track before the descriptions track 11928 if (preferredTrack) { 11929 preferredTrack.mode = 'showing'; 11930 } else if (firstCaptions) { 11931 firstCaptions.mode = 'showing'; 11932 } else if (firstDesc) { 11933 firstDesc.mode = 'showing'; 11934 } 11935 }; 11936 11937 /** 11938 * Turn display of {@link TextTrack}'s from the current state into the other state. 11939 * There are only two states: 11940 * - 'shown' 11941 * - 'hidden' 11942 * 11943 * @listens Player#loadstart 11944 */ 11945 11946 11947 TextTrackDisplay.prototype.toggleDisplay = function toggleDisplay() { 11948 if (this.player_.tech_ && this.player_.tech_.featuresNativeTextTracks) { 11949 this.hide(); 11950 } else { 11951 this.show(); 11952 } 11953 }; 11954 11955 /** 11956 * Create the {@link Component}'s DOM element. 11957 * 11958 * @return {Element} 11959 * The element that was created. 11960 */ 11961 11962 11963 TextTrackDisplay.prototype.createEl = function createEl() { 11964 return _Component.prototype.createEl.call(this, 'div', { 11965 className: 'vjs-text-track-display' 11966 }, { 11967 'aria-live': 'off', 11968 'aria-atomic': 'true' 11969 }); 11970 }; 11971 11972 /** 11973 * Clear all displayed {@link TextTrack}s. 11974 */ 11975 11976 11977 TextTrackDisplay.prototype.clearDisplay = function clearDisplay() { 11978 if (typeof window_1.WebVTT === 'function') { 11979 window_1.WebVTT.processCues(window_1, [], this.el_); 11980 } 11981 }; 11982 11983 /** 11984 * Update the displayed TextTrack when a either a {@link Player#texttrackchange} or 11985 * a {@link Player#fullscreenchange} is fired. 11986 * 11987 * @listens Player#texttrackchange 11988 * @listens Player#fullscreenchange 11989 */ 11990 11991 11992 TextTrackDisplay.prototype.updateDisplay = function updateDisplay() { 11993 var tracks = this.player_.textTracks(); 11994 11995 this.clearDisplay(); 11996 11997 // Track display prioritization model: if multiple tracks are 'showing', 11998 // display the first 'subtitles' or 'captions' track which is 'showing', 11999 // otherwise display the first 'descriptions' track which is 'showing' 12000 12001 var descriptionsTrack = null; 12002 var captionsSubtitlesTrack = null; 12003 var i = tracks.length; 12004 12005 while (i--) { 12006 var track = tracks[i]; 12007 12008 if (track.mode === 'showing') { 12009 if (track.kind === 'descriptions') { 12010 descriptionsTrack = track; 12011 } else { 12012 captionsSubtitlesTrack = track; 12013 } 12014 } 12015 } 12016 12017 if (captionsSubtitlesTrack) { 12018 if (this.getAttribute('aria-live') !== 'off') { 12019 this.setAttribute('aria-live', 'off'); 12020 } 12021 this.updateForTrack(captionsSubtitlesTrack); 12022 } else if (descriptionsTrack) { 12023 if (this.getAttribute('aria-live') !== 'assertive') { 12024 this.setAttribute('aria-live', 'assertive'); 12025 } 12026 this.updateForTrack(descriptionsTrack); 12027 } 12028 }; 12029 12030 /** 12031 * Add an {@link Texttrack} to to the {@link Tech}s {@link TextTrackList}. 12032 * 12033 * @param {TextTrack} track 12034 * Text track object to be added to the list. 12035 */ 12036 12037 12038 TextTrackDisplay.prototype.updateForTrack = function updateForTrack(track) { 12039 if (typeof window_1.WebVTT !== 'function' || !track.activeCues) { 12040 return; 12041 } 12042 12043 var overrides = this.player_.textTrackSettings.getValues(); 12044 var cues = []; 12045 12046 for (var _i = 0; _i < track.activeCues.length; _i++) { 12047 cues.push(track.activeCues[_i]); 12048 } 12049 12050 window_1.WebVTT.processCues(window_1, cues, this.el_); 12051 12052 var i = cues.length; 12053 12054 while (i--) { 12055 var cue = cues[i]; 12056 12057 if (!cue) { 12058 continue; 12059 } 12060 12061 var cueDiv = cue.displayState; 12062 12063 if (overrides.color) { 12064 cueDiv.firstChild.style.color = overrides.color; 12065 } 12066 if (overrides.textOpacity) { 12067 tryUpdateStyle(cueDiv.firstChild, 'color', constructColor(overrides.color || '#fff', overrides.textOpacity)); 12068 } 12069 if (overrides.backgroundColor) { 12070 cueDiv.firstChild.style.backgroundColor = overrides.backgroundColor; 12071 } 12072 if (overrides.backgroundOpacity) { 12073 tryUpdateStyle(cueDiv.firstChild, 'backgroundColor', constructColor(overrides.backgroundColor || '#000', overrides.backgroundOpacity)); 12074 } 12075 if (overrides.windowColor) { 12076 if (overrides.windowOpacity) { 12077 tryUpdateStyle(cueDiv, 'backgroundColor', constructColor(overrides.windowColor, overrides.windowOpacity)); 12078 } else { 12079 cueDiv.style.backgroundColor = overrides.windowColor; 12080 } 12081 } 12082 if (overrides.edgeStyle) { 12083 if (overrides.edgeStyle === 'dropshadow') { 12084 cueDiv.firstChild.style.textShadow = '2px 2px 3px ' + darkGray + ', 2px 2px 4px ' + darkGray + ', 2px 2px 5px ' + darkGray; 12085 } else if (overrides.edgeStyle === 'raised') { 12086 cueDiv.firstChild.style.textShadow = '1px 1px ' + darkGray + ', 2px 2px ' + darkGray + ', 3px 3px ' + darkGray; 12087 } else if (overrides.edgeStyle === 'depressed') { 12088 cueDiv.firstChild.style.textShadow = '1px 1px ' + lightGray + ', 0 1px ' + lightGray + ', -1px -1px ' + darkGray + ', 0 -1px ' + darkGray; 12089 } else if (overrides.edgeStyle === 'uniform') { 12090 cueDiv.firstChild.style.textShadow = '0 0 4px ' + darkGray + ', 0 0 4px ' + darkGray + ', 0 0 4px ' + darkGray + ', 0 0 4px ' + darkGray; 12091 } 12092 } 12093 if (overrides.fontPercent && overrides.fontPercent !== 1) { 12094 var fontSize = window_1.parseFloat(cueDiv.style.fontSize); 12095 12096 cueDiv.style.fontSize = fontSize * overrides.fontPercent + 'px'; 12097 cueDiv.style.height = 'auto'; 12098 cueDiv.style.top = 'auto'; 12099 cueDiv.style.bottom = '2px'; 12100 } 12101 if (overrides.fontFamily && overrides.fontFamily !== 'default') { 12102 if (overrides.fontFamily === 'small-caps') { 12103 cueDiv.firstChild.style.fontVariant = 'small-caps'; 12104 } else { 12105 cueDiv.firstChild.style.fontFamily = fontMap[overrides.fontFamily]; 12106 } 12107 } 12108 } 12109 }; 12110 12111 return TextTrackDisplay; 12112}(Component); 12113 12114Component.registerComponent('TextTrackDisplay', TextTrackDisplay); 12115 12116/** 12117 * @file loading-spinner.js 12118 */ 12119/** 12120 * A loading spinner for use during waiting/loading events. 12121 * 12122 * @extends Component 12123 */ 12124 12125var LoadingSpinner = function (_Component) { 12126 inherits(LoadingSpinner, _Component); 12127 12128 function LoadingSpinner() { 12129 classCallCheck(this, LoadingSpinner); 12130 return possibleConstructorReturn(this, _Component.apply(this, arguments)); 12131 } 12132 12133 /** 12134 * Create the `LoadingSpinner`s DOM element. 12135 * 12136 * @return {Element} 12137 * The dom element that gets created. 12138 */ 12139 LoadingSpinner.prototype.createEl = function createEl() { 12140 return _Component.prototype.createEl.call(this, 'div', { 12141 className: 'vjs-loading-spinner', 12142 dir: 'ltr' 12143 }); 12144 }; 12145 12146 return LoadingSpinner; 12147}(Component); 12148 12149Component.registerComponent('LoadingSpinner', LoadingSpinner); 12150 12151/** 12152 * @file button.js 12153 */ 12154/** 12155 * Base class for all buttons. 12156 * 12157 * @extends ClickableComponent 12158 */ 12159 12160var Button = function (_ClickableComponent) { 12161 inherits(Button, _ClickableComponent); 12162 12163 function Button() { 12164 classCallCheck(this, Button); 12165 return possibleConstructorReturn(this, _ClickableComponent.apply(this, arguments)); 12166 } 12167 12168 /** 12169 * Create the `Button`s DOM element. 12170 * 12171 * @param {string} [tag="button"] 12172 * The element's node type. This argument is IGNORED: no matter what 12173 * is passed, it will always create a `button` element. 12174 * 12175 * @param {Object} [props={}] 12176 * An object of properties that should be set on the element. 12177 * 12178 * @param {Object} [attributes={}] 12179 * An object of attributes that should be set on the element. 12180 * 12181 * @return {Element} 12182 * The element that gets created. 12183 */ 12184 Button.prototype.createEl = function createEl(tag) { 12185 var props = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 12186 var attributes = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; 12187 12188 tag = 'button'; 12189 12190 props = assign({ 12191 innerHTML: '<span aria-hidden="true" class="vjs-icon-placeholder"></span>', 12192 className: this.buildCSSClass() 12193 }, props); 12194 12195 // Add attributes for button element 12196 attributes = assign({ 12197 12198 // Necessary since the default button type is "submit" 12199 'type': 'button', 12200 12201 // let the screen reader user know that the text of the button may change 12202 'aria-live': 'polite' 12203 }, attributes); 12204 12205 var el = Component.prototype.createEl.call(this, tag, props, attributes); 12206 12207 this.createControlTextEl(el); 12208 12209 return el; 12210 }; 12211 12212 /** 12213 * Add a child `Component` inside of this `Button`. 12214 * 12215 * @param {string|Component} child 12216 * The name or instance of a child to add. 12217 * 12218 * @param {Object} [options={}] 12219 * The key/value store of options that will get passed to children of 12220 * the child. 12221 * 12222 * @return {Component} 12223 * The `Component` that gets added as a child. When using a string the 12224 * `Component` will get created by this process. 12225 * 12226 * @deprecated since version 5 12227 */ 12228 12229 12230 Button.prototype.addChild = function addChild(child) { 12231 var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 12232 12233 var className = this.constructor.name; 12234 12235 log$1.warn('Adding an actionable (user controllable) child to a Button (' + className + ') is not supported; use a ClickableComponent instead.'); 12236 12237 // Avoid the error message generated by ClickableComponent's addChild method 12238 return Component.prototype.addChild.call(this, child, options); 12239 }; 12240 12241 /** 12242 * Enable the `Button` element so that it can be activated or clicked. Use this with 12243 * {@link Button#disable}. 12244 */ 12245 12246 12247 Button.prototype.enable = function enable() { 12248 _ClickableComponent.prototype.enable.call(this); 12249 this.el_.removeAttribute('disabled'); 12250 }; 12251 12252 /** 12253 * Enable the `Button` element so that it cannot be activated or clicked. Use this with 12254 * {@link Button#enable}. 12255 */ 12256 12257 12258 Button.prototype.disable = function disable() { 12259 _ClickableComponent.prototype.disable.call(this); 12260 this.el_.setAttribute('disabled', 'disabled'); 12261 }; 12262 12263 /** 12264 * This gets called when a `Button` has focus and `keydown` is triggered via a key 12265 * press. 12266 * 12267 * @param {EventTarget~Event} event 12268 * The event that caused this function to get called. 12269 * 12270 * @listens keydown 12271 */ 12272 12273 12274 Button.prototype.handleKeyPress = function handleKeyPress(event) { 12275 12276 // Ignore Space (32) or Enter (13) key operation, which is handled by the browser for a button. 12277 if (event.which === 32 || event.which === 13) { 12278 return; 12279 } 12280 12281 // Pass keypress handling up for unsupported keys 12282 _ClickableComponent.prototype.handleKeyPress.call(this, event); 12283 }; 12284 12285 return Button; 12286}(ClickableComponent); 12287 12288Component.registerComponent('Button', Button); 12289 12290/** 12291 * @file big-play-button.js 12292 */ 12293/** 12294 * The initial play button that shows before the video has played. The hiding of the 12295 * `BigPlayButton` get done via CSS and `Player` states. 12296 * 12297 * @extends Button 12298 */ 12299 12300var BigPlayButton = function (_Button) { 12301 inherits(BigPlayButton, _Button); 12302 12303 function BigPlayButton(player, options) { 12304 classCallCheck(this, BigPlayButton); 12305 12306 var _this = possibleConstructorReturn(this, _Button.call(this, player, options)); 12307 12308 _this.mouseused_ = false; 12309 12310 _this.on('mousedown', _this.handleMouseDown); 12311 return _this; 12312 } 12313 12314 /** 12315 * Builds the default DOM `className`. 12316 * 12317 * @return {string} 12318 * The DOM `className` for this object. Always returns 'vjs-big-play-button'. 12319 */ 12320 12321 12322 BigPlayButton.prototype.buildCSSClass = function buildCSSClass() { 12323 return 'vjs-big-play-button'; 12324 }; 12325 12326 /** 12327 * This gets called when a `BigPlayButton` "clicked". See {@link ClickableComponent} 12328 * for more detailed information on what a click can be. 12329 * 12330 * @param {EventTarget~Event} event 12331 * The `keydown`, `tap`, or `click` event that caused this function to be 12332 * called. 12333 * 12334 * @listens tap 12335 * @listens click 12336 */ 12337 12338 12339 BigPlayButton.prototype.handleClick = function handleClick(event) { 12340 var playPromise = this.player_.play(); 12341 12342 // exit early if clicked via the mouse 12343 if (this.mouseused_ && event.clientX && event.clientY) { 12344 return; 12345 } 12346 12347 var cb = this.player_.getChild('controlBar'); 12348 var playToggle = cb && cb.getChild('playToggle'); 12349 12350 if (!playToggle) { 12351 this.player_.focus(); 12352 return; 12353 } 12354 12355 var playFocus = function playFocus() { 12356 return playToggle.focus(); 12357 }; 12358 12359 if (playPromise && playPromise.then) { 12360 var ignoreRejectedPlayPromise = function ignoreRejectedPlayPromise() {}; 12361 12362 playPromise.then(playFocus, ignoreRejectedPlayPromise); 12363 } else { 12364 this.setTimeout(playFocus, 1); 12365 } 12366 }; 12367 12368 BigPlayButton.prototype.handleKeyPress = function handleKeyPress(event) { 12369 this.mouseused_ = false; 12370 12371 _Button.prototype.handleKeyPress.call(this, event); 12372 }; 12373 12374 BigPlayButton.prototype.handleMouseDown = function handleMouseDown(event) { 12375 this.mouseused_ = true; 12376 }; 12377 12378 return BigPlayButton; 12379}(Button); 12380 12381/** 12382 * The text that should display over the `BigPlayButton`s controls. Added to for localization. 12383 * 12384 * @type {string} 12385 * @private 12386 */ 12387 12388 12389BigPlayButton.prototype.controlText_ = 'Play Video'; 12390 12391Component.registerComponent('BigPlayButton', BigPlayButton); 12392 12393/** 12394 * @file close-button.js 12395 */ 12396/** 12397 * The `CloseButton` is a `{@link Button}` that fires a `close` event when 12398 * it gets clicked. 12399 * 12400 * @extends Button 12401 */ 12402 12403var CloseButton = function (_Button) { 12404 inherits(CloseButton, _Button); 12405 12406 /** 12407 * Creates an instance of the this class. 12408 * 12409 * @param {Player} player 12410 * The `Player` that this class should be attached to. 12411 * 12412 * @param {Object} [options] 12413 * The key/value store of player options. 12414 */ 12415 function CloseButton(player, options) { 12416 classCallCheck(this, CloseButton); 12417 12418 var _this = possibleConstructorReturn(this, _Button.call(this, player, options)); 12419 12420 _this.controlText(options && options.controlText || _this.localize('Close')); 12421 return _this; 12422 } 12423 12424 /** 12425 * Builds the default DOM `className`. 12426 * 12427 * @return {string} 12428 * The DOM `className` for this object. 12429 */ 12430 12431 12432 CloseButton.prototype.buildCSSClass = function buildCSSClass() { 12433 return 'vjs-close-button ' + _Button.prototype.buildCSSClass.call(this); 12434 }; 12435 12436 /** 12437 * This gets called when a `CloseButton` gets clicked. See 12438 * {@link ClickableComponent#handleClick} for more information on when this will be 12439 * triggered 12440 * 12441 * @param {EventTarget~Event} event 12442 * The `keydown`, `tap`, or `click` event that caused this function to be 12443 * called. 12444 * 12445 * @listens tap 12446 * @listens click 12447 * @fires CloseButton#close 12448 */ 12449 12450 12451 CloseButton.prototype.handleClick = function handleClick(event) { 12452 12453 /** 12454 * Triggered when the a `CloseButton` is clicked. 12455 * 12456 * @event CloseButton#close 12457 * @type {EventTarget~Event} 12458 * 12459 * @property {boolean} [bubbles=false] 12460 * set to false so that the close event does not 12461 * bubble up to parents if there is no listener 12462 */ 12463 this.trigger({ type: 'close', bubbles: false }); 12464 }; 12465 12466 return CloseButton; 12467}(Button); 12468 12469Component.registerComponent('CloseButton', CloseButton); 12470 12471/** 12472 * @file play-toggle.js 12473 */ 12474/** 12475 * Button to toggle between play and pause. 12476 * 12477 * @extends Button 12478 */ 12479 12480var PlayToggle = function (_Button) { 12481 inherits(PlayToggle, _Button); 12482 12483 /** 12484 * Creates an instance of this class. 12485 * 12486 * @param {Player} player 12487 * The `Player` that this class should be attached to. 12488 * 12489 * @param {Object} [options] 12490 * The key/value store of player options. 12491 */ 12492 function PlayToggle(player, options) { 12493 classCallCheck(this, PlayToggle); 12494 12495 var _this = possibleConstructorReturn(this, _Button.call(this, player, options)); 12496 12497 _this.on(player, 'play', _this.handlePlay); 12498 _this.on(player, 'pause', _this.handlePause); 12499 _this.on(player, 'ended', _this.handleEnded); 12500 return _this; 12501 } 12502 12503 /** 12504 * Builds the default DOM `className`. 12505 * 12506 * @return {string} 12507 * The DOM `className` for this object. 12508 */ 12509 12510 12511 PlayToggle.prototype.buildCSSClass = function buildCSSClass() { 12512 return 'vjs-play-control ' + _Button.prototype.buildCSSClass.call(this); 12513 }; 12514 12515 /** 12516 * This gets called when an `PlayToggle` is "clicked". See 12517 * {@link ClickableComponent} for more detailed information on what a click can be. 12518 * 12519 * @param {EventTarget~Event} [event] 12520 * The `keydown`, `tap`, or `click` event that caused this function to be 12521 * called. 12522 * 12523 * @listens tap 12524 * @listens click 12525 */ 12526 12527 12528 PlayToggle.prototype.handleClick = function handleClick(event) { 12529 if (this.player_.paused()) { 12530 this.player_.play(); 12531 } else { 12532 this.player_.pause(); 12533 } 12534 }; 12535 12536 /** 12537 * Add the vjs-playing class to the element so it can change appearance. 12538 * 12539 * @param {EventTarget~Event} [event] 12540 * The event that caused this function to run. 12541 * 12542 * @listens Player#play 12543 */ 12544 12545 12546 PlayToggle.prototype.handlePlay = function handlePlay(event) { 12547 this.removeClass('vjs-ended'); 12548 this.removeClass('vjs-paused'); 12549 this.addClass('vjs-playing'); 12550 // change the button text to "Pause" 12551 this.controlText('Pause'); 12552 }; 12553 12554 /** 12555 * Add the vjs-paused class to the element so it can change appearance. 12556 * 12557 * @param {EventTarget~Event} [event] 12558 * The event that caused this function to run. 12559 * 12560 * @listens Player#pause 12561 */ 12562 12563 12564 PlayToggle.prototype.handlePause = function handlePause(event) { 12565 this.removeClass('vjs-playing'); 12566 this.addClass('vjs-paused'); 12567 // change the button text to "Play" 12568 this.controlText('Play'); 12569 }; 12570 12571 /** 12572 * Add the vjs-ended class to the element so it can change appearance 12573 * 12574 */ 12575 12576 12577 PlayToggle.prototype.handleEnded = function handleEnded(event) { 12578 this.removeClass('vjs-playing'); 12579 this.addClass('vjs-ended'); 12580 // change the button text to "Replay" 12581 this.controlText('Replay'); 12582 }; 12583 12584 return PlayToggle; 12585}(Button); 12586 12587/** 12588 * The text that should display over the `PlayToggle`s controls. Added for localization. 12589 * 12590 * @type {string} 12591 * @private 12592 */ 12593 12594 12595PlayToggle.prototype.controlText_ = 'Play'; 12596 12597Component.registerComponent('PlayToggle', PlayToggle); 12598 12599/** 12600 * @file format-time.js 12601 * @module Format-time 12602 */ 12603 12604/** 12605 * Format seconds as a time string, H:MM:SS or M:SS. Supplying a guide (in seconds) 12606 * will force a number of leading zeros to cover the length of the guide. 12607 * 12608 * @param {number} seconds 12609 * Number of seconds to be turned into a string 12610 * 12611 * @param {number} guide 12612 * Number (in seconds) to model the string after 12613 * 12614 * @return {string} 12615 * Time formatted as H:MM:SS or M:SS 12616 */ 12617function formatTime(seconds) { 12618 var guide = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : seconds; 12619 12620 seconds = seconds < 0 ? 0 : seconds; 12621 var s = Math.floor(seconds % 60); 12622 var m = Math.floor(seconds / 60 % 60); 12623 var h = Math.floor(seconds / 3600); 12624 var gm = Math.floor(guide / 60 % 60); 12625 var gh = Math.floor(guide / 3600); 12626 12627 // handle invalid times 12628 if (isNaN(seconds) || seconds === Infinity) { 12629 // '-' is false for all relational operators (e.g. <, >=) so this setting 12630 // will add the minimum number of fields specified by the guide 12631 h = m = s = '-'; 12632 } 12633 12634 // Check if we need to show hours 12635 h = h > 0 || gh > 0 ? h + ':' : ''; 12636 12637 // If hours are showing, we may need to add a leading zero. 12638 // Always show at least one digit of minutes. 12639 m = ((h || gm >= 10) && m < 10 ? '0' + m : m) + ':'; 12640 12641 // Check if leading zero is need for seconds 12642 s = s < 10 ? '0' + s : s; 12643 12644 return h + m + s; 12645} 12646 12647/** 12648 * @file current-time-display.js 12649 */ 12650/** 12651 * Displays the current time 12652 * 12653 * @extends Component 12654 */ 12655 12656var CurrentTimeDisplay = function (_Component) { 12657 inherits(CurrentTimeDisplay, _Component); 12658 12659 /** 12660 * Creates an instance of this class. 12661 * 12662 * @param {Player} player 12663 * The `Player` that this class should be attached to. 12664 * 12665 * @param {Object} [options] 12666 * The key/value store of player options. 12667 */ 12668 function CurrentTimeDisplay(player, options) { 12669 classCallCheck(this, CurrentTimeDisplay); 12670 12671 var _this = possibleConstructorReturn(this, _Component.call(this, player, options)); 12672 12673 _this.throttledUpdateContent = throttle(bind(_this, _this.updateContent), 25); 12674 _this.on(player, 'timeupdate', _this.throttledUpdateContent); 12675 return _this; 12676 } 12677 12678 /** 12679 * Create the `Component`'s DOM element 12680 * 12681 * @return {Element} 12682 * The element that was created. 12683 */ 12684 12685 12686 CurrentTimeDisplay.prototype.createEl = function createEl$$1() { 12687 var el = _Component.prototype.createEl.call(this, 'div', { 12688 className: 'vjs-current-time vjs-time-control vjs-control' 12689 }); 12690 12691 this.contentEl_ = createEl('div', { 12692 className: 'vjs-current-time-display' 12693 }, {
12694 // tell screen readers not to automatically read the time as it changes 12695 'aria-live': 'off' 12696 }, createEl('span', { 12697 className: 'vjs-control-text', 12698 textContent: this.localize('Current Time') 12699 })); 12700 12701 this.updateTextNode_(); 12702 el.appendChild(this.contentEl_); 12703 return el; 12704 }; 12705 12706 /** 12707 * Updates the "current time" text node with new content using the 12708 * contents of the `formattedTime_` property. 12709 * 12710 * @private 12711 */ 12712 12713 12714 CurrentTimeDisplay.prototype.updateTextNode_ = function updateTextNode_() { 12715 if (this.textNode_) { 12716 this.contentEl_.removeChild(this.textNode_); 12717 } 12718 this.textNode_ = document_1.createTextNode(' ' + (this.formattedTime_ || '0:00')); 12719 this.contentEl_.appendChild(this.textNode_); 12720 }; 12721 12722 /** 12723 * Update current time display 12724 * 12725 * @param {EventTarget~Event} [event] 12726 * The `timeupdate` event that caused this function to run. 12727 * 12728 * @listens Player#timeupdate 12729 */ 12730 12731 12732 CurrentTimeDisplay.prototype.updateContent = function updateContent(event) { 12733 // Allows for smooth scrubbing, when player can't keep up. 12734 var time = this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime(); 12735 var formattedTime = formatTime(time, this.player_.duration()); 12736 12737 if (formattedTime !== this.formattedTime_) { 12738 this.formattedTime_ = formattedTime; 12739 this.requestAnimationFrame(this.updateTextNode_); 12740 } 12741 }; 12742 12743 return CurrentTimeDisplay; 12744}(Component); 12745 12746Component.registerComponent('CurrentTimeDisplay', CurrentTimeDisplay); 12747 12748/** 12749 * @file duration-display.js 12750 */ 12751/** 12752 * Displays the duration 12753 * 12754 * @extends Component 12755 */ 12756 12757var DurationDisplay = function (_Component) { 12758 inherits(DurationDisplay, _Component); 12759 12760 /** 12761 * Creates an instance of this class. 12762 * 12763 * @param {Player} player 12764 * The `Player` that this class should be attached to. 12765 * 12766 * @param {Object} [options] 12767 * The key/value store of player options. 12768 */ 12769 function DurationDisplay(player, options) { 12770 classCallCheck(this, DurationDisplay); 12771 12772 var _this = possibleConstructorReturn(this, _Component.call(this, player, options)); 12773 12774 _this.throttledUpdateContent = throttle(bind(_this, _this.updateContent), 25); 12775 12776 _this.on(player, ['durationchange', 12777 12778 // Also listen for timeupdate and loadedmetadata because removing those 12779 // listeners could have broken dependent applications/libraries. These 12780 // can likely be removed for 7.0. 12781 'loadedmetadata', 'timeupdate'], _this.throttledUpdateContent); 12782 return _this; 12783 } 12784 12785 /** 12786 * Create the `Component`'s DOM element 12787 * 12788 * @return {Element} 12789 * The element that was created. 12790 */ 12791 12792 12793 DurationDisplay.prototype.createEl = function createEl$$1() { 12794 var el = _Component.prototype.createEl.call(this, 'div', { 12795 className: 'vjs-duration vjs-time-control vjs-control' 12796 }); 12797 12798 this.contentEl_ = createEl('div', { 12799 className: 'vjs-duration-display' 12800 }, { 12801 // tell screen readers not to automatically read the time as it changes 12802 'aria-live': 'off' 12803 }, createEl('span', { 12804 className: 'vjs-control-text', 12805 textContent: this.localize('Duration Time') 12806 })); 12807 12808 this.updateTextNode_(); 12809 el.appendChild(this.contentEl_); 12810 return el; 12811 }; 12812 12813 /** 12814 * Updates the "current time" text node with new content using the 12815 * contents of the `formattedTime_` property. 12816 * 12817 * @private 12818 */ 12819 12820 12821 DurationDisplay.prototype.updateTextNode_ = function updateTextNode_() { 12822 if (this.textNode_) { 12823 this.contentEl_.removeChild(this.textNode_); 12824 } 12825 this.textNode_ = document_1.createTextNode(' ' + (this.formattedTime_ || '0:00')); 12826 this.contentEl_.appendChild(this.textNode_); 12827 }; 12828 12829 /** 12830 * Update duration time display. 12831 * 12832 * @param {EventTarget~Event} [event] 12833 * The `durationchange`, `timeupdate`, or `loadedmetadata` event that caused 12834 * this function to be called. 12835 * 12836 * @listens Player#durationchange 12837 * @listens Player#timeupdate 12838 * @listens Player#loadedmetadata 12839 */ 12840 12841 12842 DurationDisplay.prototype.updateContent = function updateContent(event) { 12843 var duration = this.player_.duration(); 12844 12845 if (duration && this.duration_ !== duration) { 12846 this.duration_ = duration;
vendor: 183,937 bytes, lines 12847-19547
12847 this.formattedTime_ = formatTime(duration); 12848 this.requestAnimationFrame(this.updateTextNode_); 12849 } 12850 }; 12851 12852 return DurationDisplay; 12853}(Component); 12854 12855Component.registerComponent('DurationDisplay', DurationDisplay); 12856 12857/** 12858 * @file time-divider.js 12859 */ 12860/** 12861 * The separator between the current time and duration. 12862 * Can be hidden if it's not needed in the design. 12863 * 12864 * @extends Component 12865 */ 12866 12867var TimeDivider = function (_Component) { 12868 inherits(TimeDivider, _Component); 12869 12870 function TimeDivider() { 12871 classCallCheck(this, TimeDivider); 12872 return possibleConstructorReturn(this, _Component.apply(this, arguments)); 12873 } 12874 12875 /** 12876 * Create the component's DOM element 12877 * 12878 * @return {Element} 12879 * The element that was created. 12880 */ 12881 TimeDivider.prototype.createEl = function createEl() { 12882 return _Component.prototype.createEl.call(this, 'div', { 12883 className: 'vjs-time-control vjs-time-divider', 12884 innerHTML: '<div><span>/</span></div>' 12885 }); 12886 }; 12887 12888 return TimeDivider; 12889}(Component); 12890 12891Component.registerComponent('TimeDivider', TimeDivider); 12892 12893/** 12894 * @file remaining-time-display.js 12895 */ 12896/** 12897 * Displays the time left in the video 12898 * 12899 * @extends Component 12900 */ 12901 12902var RemainingTimeDisplay = function (_Component) { 12903 inherits(RemainingTimeDisplay, _Component); 12904 12905 /** 12906 * Creates an instance of this class. 12907 * 12908 * @param {Player} player 12909 * The `Player` that this class should be attached to. 12910 * 12911 * @param {Object} [options] 12912 * The key/value store of player options. 12913 */ 12914 function RemainingTimeDisplay(player, options) { 12915 classCallCheck(this, RemainingTimeDisplay); 12916 12917 var _this = possibleConstructorReturn(this, _Component.call(this, player, options)); 12918 12919 _this.throttledUpdateContent = throttle(bind(_this, _this.updateContent), 25); 12920 _this.on(player, ['timeupdate', 'durationchange'], _this.throttledUpdateContent); 12921 return _this; 12922 } 12923 12924 /** 12925 * Create the `Component`'s DOM element 12926 * 12927 * @return {Element} 12928 * The element that was created. 12929 */ 12930 12931 12932 RemainingTimeDisplay.prototype.createEl = function createEl$$1() { 12933 var el = _Component.prototype.createEl.call(this, 'div', { 12934 className: 'vjs-remaining-time vjs-time-control vjs-control' 12935 }); 12936 12937 this.contentEl_ = createEl('div', { 12938 className: 'vjs-remaining-time-display' 12939 }, { 12940 // tell screen readers not to automatically read the time as it changes 12941 'aria-live': 'off' 12942 }, createEl('span', { 12943 className: 'vjs-control-text', 12944 textContent: this.localize('Remaining Time') 12945 })); 12946 12947 this.updateTextNode_(); 12948 el.appendChild(this.contentEl_); 12949 return el; 12950 }; 12951 12952 /** 12953 * Updates the "remaining time" text node with new content using the 12954 * contents of the `formattedTime_` property. 12955 * 12956 * @private 12957 */ 12958 12959 12960 RemainingTimeDisplay.prototype.updateTextNode_ = function updateTextNode_() { 12961 if (this.textNode_) { 12962 this.contentEl_.removeChild(this.textNode_); 12963 } 12964 this.textNode_ = document_1.createTextNode(' -' + (this.formattedTime_ || '0:00')); 12965 this.contentEl_.appendChild(this.textNode_); 12966 }; 12967 12968 /** 12969 * Update remaining time display. 12970 * 12971 * @param {EventTarget~Event} [event] 12972 * The `timeupdate` or `durationchange` event that caused this to run. 12973 * 12974 * @listens Player#timeupdate 12975 * @listens Player#durationchange 12976 */ 12977 12978 12979 RemainingTimeDisplay.prototype.updateContent = function updateContent(event) { 12980 if (this.player_.duration()) { 12981 var formattedTime = formatTime(this.player_.remainingTime()); 12982 12983 if (formattedTime !== this.formattedTime_) { 12984 this.formattedTime_ = formattedTime; 12985 this.requestAnimationFrame(this.updateTextNode_); 12986 } 12987 } 12988 }; 12989 12990 return RemainingTimeDisplay; 12991}(Component); 12992 12993Component.registerComponent('RemainingTimeDisplay', RemainingTimeDisplay); 12994 12995/** 12996 * @file live-display.js 12997 */ 12998// TODO - Future make it click to snap to live 12999 13000/** 13001 * Displays the live indicator when duration is Infinity. 13002 * 13003 * @extends Component 13004 */ 13005 13006var LiveDisplay = function (_Component) { 13007 inherits(LiveDisplay, _Component); 13008 13009 /** 13010 * Creates an instance of this class. 13011 * 13012 * @param {Player} player 13013 * The `Player` that this class should be attached to. 13014 * 13015 * @param {Object} [options] 13016 * The key/value store of player options. 13017 */ 13018 function LiveDisplay(player, options) { 13019 classCallCheck(this, LiveDisplay); 13020 13021 var _this = possibleConstructorReturn(this, _Component.call(this, player, options)); 13022 13023 _this.updateShowing(); 13024 _this.on(_this.player(), 'durationchange', _this.updateShowing); 13025 return _this; 13026 } 13027 13028 /** 13029 * Create the `Component`'s DOM element 13030 * 13031 * @return {Element} 13032 * The element that was created. 13033 */ 13034 13035 13036 LiveDisplay.prototype.createEl = function createEl$$1() { 13037 var el = _Component.prototype.createEl.call(this, 'div', { 13038 className: 'vjs-live-control vjs-control' 13039 }); 13040 13041 this.contentEl_ = createEl('div', { 13042 className: 'vjs-live-display', 13043 innerHTML: '<span class="vjs-control-text">' + this.localize('Stream Type') + '</span>' + this.localize('LIVE') 13044 }, { 13045 'aria-live': 'off' 13046 }); 13047 13048 el.appendChild(this.contentEl_); 13049 return el; 13050 }; 13051 13052 /** 13053 * Check the duration to see if the LiveDisplay should be showing or not. Then show/hide 13054 * it accordingly 13055 * 13056 * @param {EventTarget~Event} [event] 13057 * The {@link Player#durationchange} event that caused this function to run. 13058 * 13059 * @listens Player#durationchange 13060 */ 13061 13062 13063 LiveDisplay.prototype.updateShowing = function updateShowing(event) { 13064 if (this.player().duration() === Infinity) { 13065 this.show(); 13066 } else { 13067 this.hide(); 13068 } 13069 }; 13070 13071 return LiveDisplay; 13072}(Component); 13073 13074Component.registerComponent('LiveDisplay', LiveDisplay); 13075 13076/** 13077 * @file slider.js 13078 */ 13079/** 13080 * The base functionality for a slider. Can be vertical or horizontal. 13081 * For instance the volume bar or the seek bar on a video is a slider. 13082 * 13083 * @extends Component 13084 */ 13085 13086var Slider = function (_Component) { 13087 inherits(Slider, _Component); 13088 13089 /** 13090 * Create an instance of this class 13091 * 13092 * @param {Player} player 13093 * The `Player` that this class should be attached to. 13094 * 13095 * @param {Object} [options] 13096 * The key/value store of player options. 13097 */ 13098 function Slider(player, options) { 13099 classCallCheck(this, Slider); 13100 13101 // Set property names to bar to match with the child Slider class is looking for 13102 var _this = possibleConstructorReturn(this, _Component.call(this, player, options)); 13103 13104 _this.bar = _this.getChild(_this.options_.barName); 13105 13106 // Set a horizontal or vertical class on the slider depending on the slider type 13107 _this.vertical(!!_this.options_.vertical); 13108 13109 _this.on('mousedown', _this.handleMouseDown); 13110 _this.on('touchstart', _this.handleMouseDown); 13111 _this.on('focus', _this.handleFocus); 13112 _this.on('blur', _this.handleBlur); 13113 _this.on('click', _this.handleClick); 13114 13115 _this.on(player, 'controlsvisible', _this.update); 13116 13117 if (_this.playerEvent) { 13118 _this.on(player, _this.playerEvent, _this.update); 13119 } 13120 return _this; 13121 } 13122 13123 /** 13124 * Create the `Button`s DOM element. 13125 * 13126 * @param {string} type 13127 * Type of element to create. 13128 * 13129 * @param {Object} [props={}] 13130 * List of properties in Object form. 13131 * 13132 * @param {Object} [attributes={}] 13133 * list of attributes in Object form. 13134 * 13135 * @return {Element} 13136 * The element that gets created. 13137 */ 13138 13139 13140 Slider.prototype.createEl = function createEl$$1(type) { 13141 var props = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 13142 var attributes = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; 13143 13144 // Add the slider element class to all sub classes 13145 props.className = props.className + ' vjs-slider'; 13146 props = assign({ 13147 tabIndex: 0 13148 }, props); 13149 13150 attributes = assign({ 13151 'role': 'slider', 13152 'aria-valuenow': 0, 13153 'aria-valuemin': 0, 13154 'aria-valuemax': 100, 13155 'tabIndex': 0 13156 }, attributes); 13157 13158 return _Component.prototype.createEl.call(this, type, props, attributes); 13159 }; 13160 13161 /** 13162 * Handle `mousedown` or `touchstart` events on the `Slider`. 13163 * 13164 * @param {EventTarget~Event} event 13165 * `mousedown` or `touchstart` event that triggered this function 13166 * 13167 * @listens mousedown 13168 * @listens touchstart 13169 * @fires Slider#slideractive 13170 */ 13171 13172 13173 Slider.prototype.handleMouseDown = function handleMouseDown(event) { 13174 var doc = this.bar.el_.ownerDocument; 13175 13176 event.preventDefault(); 13177 blockTextSelection(); 13178 13179 this.addClass('vjs-sliding'); 13180 /** 13181 * Triggered when the slider is in an active state 13182 * 13183 * @event Slider#slideractive 13184 * @type {EventTarget~Event} 13185 */ 13186 this.trigger('slideractive'); 13187 13188 this.on(doc, 'mousemove', this.handleMouseMove); 13189 this.on(doc, 'mouseup', this.handleMouseUp); 13190 this.on(doc, 'touchmove', this.handleMouseMove); 13191 this.on(doc, 'touchend', this.handleMouseUp); 13192 13193 this.handleMouseMove(event); 13194 }; 13195 13196 /** 13197 * Handle the `mousemove`, `touchmove`, and `mousedown` events on this `Slider`. 13198 * The `mousemove` and `touchmove` events will only only trigger this function during 13199 * `mousedown` and `touchstart`. This is due to {@link Slider#handleMouseDown} and 13200 * {@link Slider#handleMouseUp}. 13201 * 13202 * @param {EventTarget~Event} event 13203 * `mousedown`, `mousemove`, `touchstart`, or `touchmove` event that triggered 13204 * this function 13205 * 13206 * @listens mousemove 13207 * @listens touchmove 13208 */ 13209 13210 13211 Slider.prototype.handleMouseMove = function handleMouseMove(event) {}; 13212 13213 /** 13214 * Handle `mouseup` or `touchend` events on the `Slider`. 13215 * 13216 * @param {EventTarget~Event} event 13217 * `mouseup` or `touchend` event that triggered this function. 13218 * 13219 * @listens touchend 13220 * @listens mouseup 13221 * @fires Slider#sliderinactive 13222 */ 13223 13224 13225 Slider.prototype.handleMouseUp = function handleMouseUp() { 13226 var doc = this.bar.el_.ownerDocument; 13227 13228 unblockTextSelection(); 13229 13230 this.removeClass('vjs-sliding'); 13231 /** 13232 * Triggered when the slider is no longer in an active state. 13233 * 13234 * @event Slider#sliderinactive 13235 * @type {EventTarget~Event} 13236 */ 13237 this.trigger('sliderinactive'); 13238 13239 this.off(doc, 'mousemove', this.handleMouseMove); 13240 this.off(doc, 'mouseup', this.handleMouseUp); 13241 this.off(doc, 'touchmove', this.handleMouseMove); 13242 this.off(doc, 'touchend', this.handleMouseUp); 13243 13244 this.update(); 13245 }; 13246 13247 /** 13248 * Update the progress bar of the `Slider`. 13249 * 13250 * @returns {number} 13251 * The percentage of progress the progress bar represents as a 13252 * number from 0 to 1. 13253 */ 13254 13255 13256 Slider.prototype.update = function update() { 13257 13258 // In VolumeBar init we have a setTimeout for update that pops and update 13259 // to the end of the execution stack. The player is destroyed before then 13260 // update will cause an error 13261 if (!this.el_) { 13262 return; 13263 } 13264 13265 // If scrubbing, we could use a cached value to make the handle keep up 13266 // with the user's mouse. On HTML5 browsers scrubbing is really smooth, but 13267 // some flash players are slow, so we might want to utilize this later. 13268 // var progress = (this.player_.scrubbing()) ? this.player_.getCache().currentTime / this.player_.duration() : this.player_.currentTime() / this.player_.duration(); 13269 var progress = this.getPercent(); 13270 var bar = this.bar; 13271 13272 // If there's no bar... 13273 if (!bar) { 13274 return; 13275 } 13276 13277 // Protect against no duration and other division issues 13278 if (typeof progress !== 'number' || progress !== progress || progress < 0 || progress === Infinity) { 13279 progress = 0; 13280 } 13281 13282 // Convert to a percentage for setting 13283 var percentage = (progress * 100).toFixed(2) + '%'; 13284 var style = bar.el().style; 13285 13286 // Set the new bar width or height 13287 if (this.vertical()) { 13288 style.height = percentage; 13289 } else { 13290 style.width = percentage; 13291 } 13292 13293 return progress; 13294 }; 13295 13296 /** 13297 * Calculate distance for slider 13298 * 13299 * @param {EventTarget~Event} event 13300 * The event that caused this function to run. 13301 * 13302 * @return {number} 13303 * The current position of the Slider. 13304 * - postition.x for vertical `Slider`s 13305 * - postition.y for horizontal `Slider`s 13306 */ 13307 13308 13309 Slider.prototype.calculateDistance = function calculateDistance(event) { 13310 var position = getPointerPosition(this.el_, event); 13311 13312 if (this.vertical()) { 13313 return position.y; 13314 } 13315 return position.x; 13316 }; 13317 13318 /** 13319 * Handle a `focus` event on this `Slider`. 13320 * 13321 * @param {EventTarget~Event} event 13322 * The `focus` event that caused this function to run. 13323 * 13324 * @listens focus 13325 */ 13326 13327 13328 Slider.prototype.handleFocus = function handleFocus() { 13329 this.on(this.bar.el_.ownerDocument, 'keydown', this.handleKeyPress); 13330 }; 13331 13332 /** 13333 * Handle a `keydown` event on the `Slider`. Watches for left, rigth, up, and down 13334 * arrow keys. This function will only be called when the slider has focus. See 13335 * {@link Slider#handleFocus} and {@link Slider#handleBlur}. 13336 * 13337 * @param {EventTarget~Event} event 13338 * the `keydown` event that caused this function to run. 13339 * 13340 * @listens keydown 13341 */ 13342 13343 13344 Slider.prototype.handleKeyPress = function handleKeyPress(event) { 13345 // Left and Down Arrows 13346 if (event.which === 37 || event.which === 40) { 13347 event.preventDefault(); 13348 this.stepBack(); 13349 13350 // Up and Right Arrows 13351 } else if (event.which === 38 || event.which === 39) { 13352 event.preventDefault(); 13353 this.stepForward(); 13354 } 13355 }; 13356 13357 /** 13358 * Handle a `blur` event on this `Slider`. 13359 * 13360 * @param {EventTarget~Event} event 13361 * The `blur` event that caused this function to run. 13362 * 13363 * @listens blur 13364 */ 13365 13366 Slider.prototype.handleBlur = function handleBlur() { 13367 this.off(this.bar.el_.ownerDocument, 'keydown', this.handleKeyPress); 13368 }; 13369 13370 /** 13371 * Listener for click events on slider, used to prevent clicks 13372 * from bubbling up to parent elements like button menus. 13373 * 13374 * @param {Object} event 13375 * Event that caused this object to run 13376 */ 13377 13378 13379 Slider.prototype.handleClick = function handleClick(event) { 13380 event.stopImmediatePropagation(); 13381 event.preventDefault(); 13382 }; 13383 13384 /** 13385 * Get/set if slider is horizontal for vertical 13386 * 13387 * @param {boolean} [bool] 13388 * - true if slider is vertical, 13389 * - false is horizontal 13390 * 13391 * @return {boolean} 13392 * - true if slider is vertical, and getting 13393 * - false if the slider is horizontal, and getting 13394 */ 13395 13396 13397 Slider.prototype.vertical = function vertical(bool) { 13398 if (bool === undefined) { 13399 return this.vertical_ || false; 13400 } 13401 13402 this.vertical_ = !!bool; 13403 13404 if (this.vertical_) { 13405 this.addClass('vjs-slider-vertical'); 13406 } else { 13407 this.addClass('vjs-slider-horizontal'); 13408 } 13409 }; 13410 13411 return Slider; 13412}(Component); 13413 13414Component.registerComponent('Slider', Slider); 13415 13416/** 13417 * @file load-progress-bar.js 13418 */ 13419/** 13420 * Shows loading progress 13421 * 13422 * @extends Component 13423 */ 13424 13425var LoadProgressBar = function (_Component) { 13426 inherits(LoadProgressBar, _Component); 13427 13428 /** 13429 * Creates an instance of this class. 13430 * 13431 * @param {Player} player 13432 * The `Player` that this class should be attached to. 13433 * 13434 * @param {Object} [options] 13435 * The key/value store of player options. 13436 */ 13437 function LoadProgressBar(player, options) { 13438 classCallCheck(this, LoadProgressBar); 13439 13440 var _this = possibleConstructorReturn(this, _Component.call(this, player, options)); 13441 13442 _this.partEls_ = []; 13443 _this.on(player, 'progress', _this.update); 13444 return _this; 13445 } 13446 13447 /** 13448 * Create the `Component`'s DOM element 13449 * 13450 * @return {Element} 13451 * The element that was created. 13452 */ 13453 13454 13455 LoadProgressBar.prototype.createEl = function createEl$$1() { 13456 return _Component.prototype.createEl.call(this, 'div', { 13457 className: 'vjs-load-progress', 13458 innerHTML: '<span class="vjs-control-text"><span>' + this.localize('Loaded') + '</span>: 0%</span>' 13459 }); 13460 }; 13461 13462 /** 13463 * Update progress bar 13464 * 13465 * @param {EventTarget~Event} [event] 13466 * The `progress` event that caused this function to run. 13467 * 13468 * @listens Player#progress 13469 */ 13470 13471 13472 LoadProgressBar.prototype.update = function update(event) { 13473 var buffered = this.player_.buffered(); 13474 var duration = this.player_.duration(); 13475 var bufferedEnd = this.player_.bufferedEnd(); 13476 var children = this.partEls_; 13477 13478 // get the percent width of a time compared to the total end 13479 var percentify = function percentify(time, end) { 13480 // no NaN 13481 var percent = time / end || 0; 13482 13483 return (percent >= 1 ? 1 : percent) * 100 + '%'; 13484 }; 13485 13486 // update the width of the progress bar 13487 this.el_.style.width = percentify(bufferedEnd, duration); 13488 13489 // add child elements to represent the individual buffered time ranges 13490 for (var i = 0; i < buffered.length; i++) { 13491 var start = buffered.start(i); 13492 var end = buffered.end(i); 13493 var part = children[i]; 13494 13495 if (!part) { 13496 part = this.el_.appendChild(createEl()); 13497 children[i] = part; 13498 } 13499 13500 // set the percent based on the width of the progress bar (bufferedEnd) 13501 part.style.left = percentify(start, bufferedEnd); 13502 part.style.width = percentify(end - start, bufferedEnd); 13503 } 13504 13505 // remove unused buffered range elements 13506 for (var _i = children.length; _i > buffered.length; _i--) { 13507 this.el_.removeChild(children[_i - 1]); 13508 } 13509 children.length = buffered.length; 13510 }; 13511 13512 return LoadProgressBar; 13513}(Component); 13514 13515Component.registerComponent('LoadProgressBar', LoadProgressBar); 13516 13517/** 13518 * @file time-tooltip.js 13519 */ 13520/** 13521 * Time tooltips display a time above the progress bar. 13522 * 13523 * @extends Component 13524 */ 13525 13526var TimeTooltip = function (_Component) { 13527 inherits(TimeTooltip, _Component); 13528 13529 function TimeTooltip() { 13530 classCallCheck(this, TimeTooltip); 13531 return possibleConstructorReturn(this, _Component.apply(this, arguments)); 13532 } 13533 13534 /** 13535 * Create the time tooltip DOM element 13536 * 13537 * @return {Element} 13538 * The element that was created. 13539 */ 13540 TimeTooltip.prototype.createEl = function createEl$$1() { 13541 return _Component.prototype.createEl.call(this, 'div', { 13542 className: 'vjs-time-tooltip' 13543 }); 13544 }; 13545 13546 /** 13547 * Updates the position of the time tooltip relative to the `SeekBar`. 13548 * 13549 * @param {Object} seekBarRect 13550 * The `ClientRect` for the {@link SeekBar} element. 13551 * 13552 * @param {number} seekBarPoint 13553 * A number from 0 to 1, representing a horizontal reference point 13554 * from the left edge of the {@link SeekBar} 13555 */ 13556 13557 13558 TimeTooltip.prototype.update = function update(seekBarRect, seekBarPoint, content) { 13559 var tooltipRect = getBoundingClientRect(this.el_); 13560 var playerRect = getBoundingClientRect(this.player_.el()); 13561 var seekBarPointPx = seekBarRect.width * seekBarPoint; 13562 13563 // do nothing if either rect isn't available 13564 // for example, if the player isn't in the DOM for testing 13565 if (!playerRect || !tooltipRect) { 13566 return; 13567 } 13568 13569 // This is the space left of the `seekBarPoint` available within the bounds 13570 // of the player. We calculate any gap between the left edge of the player 13571 // and the left edge of the `SeekBar` and add the number of pixels in the 13572 // `SeekBar` before hitting the `seekBarPoint` 13573 var spaceLeftOfPoint = seekBarRect.left - playerRect.left + seekBarPointPx; 13574 13575 // This is the space right of the `seekBarPoint` available within the bounds 13576 // of the player. We calculate the number of pixels from the `seekBarPoint` 13577 // to the right edge of the `SeekBar` and add to that any gap between the 13578 // right edge of the `SeekBar` and the player. 13579 var spaceRightOfPoint = seekBarRect.width - seekBarPointPx + (playerRect.right - seekBarRect.right); 13580 13581 // This is the number of pixels by which the tooltip will need to be pulled 13582 // further to the right to center it over the `seekBarPoint`. 13583 var pullTooltipBy = tooltipRect.width / 2; 13584 13585 // Adjust the `pullTooltipBy` distance to the left or right depending on 13586 // the results of the space calculations above. 13587 if (spaceLeftOfPoint < pullTooltipBy) { 13588 pullTooltipBy += pullTooltipBy - spaceLeftOfPoint; 13589 } else if (spaceRightOfPoint < pullTooltipBy) { 13590 pullTooltipBy = spaceRightOfPoint; 13591 } 13592 13593 // Due to the imprecision of decimal/ratio based calculations and varying 13594 // rounding behaviors, there are cases where the spacing adjustment is off 13595 // by a pixel or two. This adds insurance to these calculations. 13596 if (pullTooltipBy < 0) { 13597 pullTooltipBy = 0; 13598 } else if (pullTooltipBy > tooltipRect.width) { 13599 pullTooltipBy = tooltipRect.width; 13600 } 13601 13602 this.el_.style.right = '-' + pullTooltipBy + 'px'; 13603 textContent(this.el_, content); 13604 }; 13605 13606 return TimeTooltip; 13607}(Component); 13608 13609Component.registerComponent('TimeTooltip', TimeTooltip); 13610 13611/** 13612 * @file play-progress-bar.js 13613 */ 13614/** 13615 * Used by {@link SeekBar} to display media playback progress as part of the 13616 * {@link ProgressControl}. 13617 * 13618 * @extends Component 13619 */ 13620 13621var PlayProgressBar = function (_Component) { 13622 inherits(PlayProgressBar, _Component); 13623 13624 function PlayProgressBar() { 13625 classCallCheck(this, PlayProgressBar); 13626 return possibleConstructorReturn(this, _Component.apply(this, arguments)); 13627 } 13628 13629 /** 13630 * Create the the DOM element for this class. 13631 * 13632 * @return {Element} 13633 * The element that was created. 13634 */ 13635 PlayProgressBar.prototype.createEl = function createEl() { 13636 return _Component.prototype.createEl.call(this, 'div', { 13637 className: 'vjs-play-progress vjs-slider-bar', 13638 innerHTML: '<span class="vjs-control-text"><span>' + this.localize('Progress') + '</span>: 0%</span>' 13639 }); 13640 }; 13641 13642 /** 13643 * Enqueues updates to its own DOM as well as the DOM of its 13644 * {@link TimeTooltip} child. 13645 * 13646 * @param {Object} seekBarRect 13647 * The `ClientRect` for the {@link SeekBar} element. 13648 * 13649 * @param {number} seekBarPoint 13650 * A number from 0 to 1, representing a horizontal reference point 13651 * from the left edge of the {@link SeekBar} 13652 */ 13653 13654 13655 PlayProgressBar.prototype.update = function update(seekBarRect, seekBarPoint) { 13656 var _this2 = this; 13657 13658 // If there is an existing rAF ID, cancel it so we don't over-queue. 13659 if (this.rafId_) { 13660 this.cancelAnimationFrame(this.rafId_); 13661 } 13662 13663 this.rafId_ = this.requestAnimationFrame(function () { 13664 var time = _this2.player_.scrubbing() ? _this2.player_.getCache().currentTime : _this2.player_.currentTime(); 13665 13666 var content = formatTime(time, _this2.player_.duration()); 13667 var timeTooltip = _this2.getChild('timeTooltip'); 13668 13669 if (timeTooltip) { 13670 timeTooltip.update(seekBarRect, seekBarPoint, content); 13671 } 13672 }); 13673 }; 13674 13675 return PlayProgressBar; 13676}(Component); 13677 13678/** 13679 * Default options for {@link PlayProgressBar}. 13680 * 13681 * @type {Object} 13682 * @private 13683 */ 13684 13685 13686PlayProgressBar.prototype.options_ = { 13687 children: [] 13688}; 13689 13690// Time tooltips should not be added to a player on mobile devices or IE8 13691if ((!IE_VERSION || IE_VERSION > 8) && !IS_IOS && !IS_ANDROID) { 13692 PlayProgressBar.prototype.options_.children.push('timeTooltip'); 13693} 13694 13695Component.registerComponent('PlayProgressBar', PlayProgressBar); 13696 13697/** 13698 * @file mouse-time-display.js 13699 */ 13700/** 13701 * The {@link MouseTimeDisplay} component tracks mouse movement over the 13702 * {@link ProgressControl}. It displays an indicator and a {@link TimeTooltip} 13703 * indicating the time which is represented by a given point in the 13704 * {@link ProgressControl}. 13705 * 13706 * @extends Component 13707 */ 13708 13709var MouseTimeDisplay = function (_Component) { 13710 inherits(MouseTimeDisplay, _Component); 13711 13712 /** 13713 * Creates an instance of this class. 13714 * 13715 * @param {Player} player 13716 * The {@link Player} that this class should be attached to. 13717 * 13718 * @param {Object} [options] 13719 * The key/value store of player options. 13720 */ 13721 function MouseTimeDisplay(player, options) { 13722 classCallCheck(this, MouseTimeDisplay); 13723 13724 var _this = possibleConstructorReturn(this, _Component.call(this, player, options)); 13725 13726 _this.update = throttle(bind(_this, _this.update), 25); 13727 return _this; 13728 } 13729 13730 /** 13731 * Create the DOM element for this class. 13732 * 13733 * @return {Element} 13734 * The element that was created. 13735 */ 13736 13737 13738 MouseTimeDisplay.prototype.createEl = function createEl() { 13739 return _Component.prototype.createEl.call(this, 'div', { 13740 className: 'vjs-mouse-display' 13741 }); 13742 }; 13743 13744 /** 13745 * Enqueues updates to its own DOM as well as the DOM of its 13746 * {@link TimeTooltip} child. 13747 * 13748 * @param {Object} seekBarRect 13749 * The `ClientRect` for the {@link SeekBar} element. 13750 * 13751 * @param {number} seekBarPoint 13752 * A number from 0 to 1, representing a horizontal reference point 13753 * from the left edge of the {@link SeekBar} 13754 */ 13755 13756 13757 MouseTimeDisplay.prototype.update = function update(seekBarRect, seekBarPoint) { 13758 var _this2 = this; 13759 13760 // If there is an existing rAF ID, cancel it so we don't over-queue. 13761 if (this.rafId_) { 13762 this.cancelAnimationFrame(this.rafId_); 13763 } 13764 13765 this.rafId_ = this.requestAnimationFrame(function () { 13766 var duration = _this2.player_.duration(); 13767 var content = formatTime(seekBarPoint * duration, duration); 13768 13769 _this2.el_.style.left = seekBarRect.width * seekBarPoint + 'px'; 13770 _this2.getChild('timeTooltip').update(seekBarRect, seekBarPoint, content); 13771 }); 13772 }; 13773 13774 return MouseTimeDisplay; 13775}(Component); 13776 13777/** 13778 * Default options for `MouseTimeDisplay` 13779 * 13780 * @type {Object} 13781 * @private 13782 */ 13783 13784 13785MouseTimeDisplay.prototype.options_ = { 13786 children: ['timeTooltip'] 13787}; 13788 13789Component.registerComponent('MouseTimeDisplay', MouseTimeDisplay); 13790 13791/** 13792 * @file seek-bar.js 13793 */ 13794// The number of seconds the `step*` functions move the timeline. 13795var STEP_SECONDS = 5; 13796 13797/** 13798 * Seek bar and container for the progress bars. Uses {@link PlayProgressBar} 13799 * as its `bar`. 13800 * 13801 * @extends Slider 13802 */ 13803 13804var SeekBar = function (_Slider) { 13805 inherits(SeekBar, _Slider); 13806 13807 /** 13808 * Creates an instance of this class. 13809 * 13810 * @param {Player} player 13811 * The `Player` that this class should be attached to. 13812 * 13813 * @param {Object} [options] 13814 * The key/value store of player options. 13815 */ 13816 function SeekBar(player, options) { 13817 classCallCheck(this, SeekBar); 13818 13819 var _this = possibleConstructorReturn(this, _Slider.call(this, player, options)); 13820 13821 _this.update = throttle(bind(_this, _this.update), 50); 13822 _this.on(player, ['timeupdate', 'ended'], _this.update); 13823 return _this; 13824 } 13825 13826 /** 13827 * Create the `Component`'s DOM element 13828 * 13829 * @return {Element} 13830 * The element that was created. 13831 */ 13832 13833 13834 SeekBar.prototype.createEl = function createEl$$1() { 13835 return _Slider.prototype.createEl.call(this, 'div', { 13836 className: 'vjs-progress-holder' 13837 }, { 13838 'aria-label': this.localize('Progress Bar') 13839 }); 13840 }; 13841 13842 /** 13843 * Update the seek bar's UI. 13844 * 13845 * @param {EventTarget~Event} [event] 13846 * The `timeupdate` or `ended` event that caused this to run. 13847 * 13848 * @listens Player#timeupdate 13849 * @listens Player#ended 13850 */ 13851 13852 13853 SeekBar.prototype.update = function update() { 13854 var percent = _Slider.prototype.update.call(this); 13855 var duration = this.player_.duration(); 13856 13857 // Allows for smooth scrubbing, when player can't keep up. 13858 var time = this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime(); 13859 13860 // machine readable value of progress bar (percentage complete) 13861 this.el_.setAttribute('aria-valuenow', (percent * 100).toFixed(2)); 13862 13863 // human readable value of progress bar (time complete) 13864 this.el_.setAttribute('aria-valuetext', this.localize('progress bar timing: currentTime={1} duration={2}', [formatTime(time, duration), formatTime(duration, duration)], '{1} of {2}')); 13865 13866 // Update the `PlayProgressBar`. 13867 this.bar.update(getBoundingClientRect(this.el_), percent); 13868 13869 return percent; 13870 }; 13871 13872 /** 13873 * Get the percentage of media played so far. 13874 * 13875 * @return {number} 13876 * The percentage of media played so far (0 to 1). 13877 */ 13878 13879 13880 SeekBar.prototype.getPercent = function getPercent() { 13881 13882 // Allows for smooth scrubbing, when player can't keep up. 13883 var time = this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime(); 13884 13885 var percent = time / this.player_.duration(); 13886 13887 return percent >= 1 ? 1 : percent; 13888 }; 13889 13890 /** 13891 * Handle mouse down on seek bar 13892 * 13893 * @param {EventTarget~Event} event 13894 * The `mousedown` event that caused this to run. 13895 * 13896 * @listens mousedown 13897 */ 13898 13899 13900 SeekBar.prototype.handleMouseDown = function handleMouseDown(event) { 13901 this.player_.scrubbing(true); 13902 13903 this.videoWasPlaying = !this.player_.paused(); 13904 this.player_.pause(); 13905 13906 _Slider.prototype.handleMouseDown.call(this, event); 13907 }; 13908 13909 /** 13910 * Handle mouse move on seek bar 13911 * 13912 * @param {EventTarget~Event} event 13913 * The `mousemove` event that caused this to run. 13914 * 13915 * @listens mousemove 13916 */ 13917 13918 13919 SeekBar.prototype.handleMouseMove = function handleMouseMove(event) { 13920 var newTime = this.calculateDistance(event) * this.player_.duration(); 13921 13922 // Don't let video end while scrubbing. 13923 if (newTime === this.player_.duration()) { 13924 newTime = newTime - 0.1; 13925 } 13926 13927 // Set new time (tell player to seek to new time) 13928 this.player_.currentTime(newTime); 13929 }; 13930 13931 /** 13932 * Handle mouse up on seek bar 13933 * 13934 * @param {EventTarget~Event} event 13935 * The `mouseup` event that caused this to run. 13936 * 13937 * @listens mouseup 13938 */ 13939 13940 13941 SeekBar.prototype.handleMouseUp = function handleMouseUp(event) { 13942 _Slider.prototype.handleMouseUp.call(this, event); 13943 13944 this.player_.scrubbing(false); 13945 if (this.videoWasPlaying) { 13946 this.player_.play(); 13947 } 13948 }; 13949 13950 /** 13951 * Move more quickly fast forward for keyboard-only users 13952 */ 13953 13954 13955 SeekBar.prototype.stepForward = function stepForward() { 13956 this.player_.currentTime(this.player_.currentTime() + STEP_SECONDS); 13957 }; 13958 13959 /** 13960 * Move more quickly rewind for keyboard-only users 13961 */ 13962 13963 13964 SeekBar.prototype.stepBack = function stepBack() { 13965 this.player_.currentTime(this.player_.currentTime() - STEP_SECONDS); 13966 }; 13967 13968 /** 13969 * Toggles the playback state of the player 13970 * This gets called when enter or space is used on the seekbar 13971 * 13972 * @param {EventTarget~Event} event 13973 * The `keydown` event that caused this function to be called 13974 * 13975 */ 13976 13977 13978 SeekBar.prototype.handleAction = function handleAction(event) { 13979 if (this.player_.paused()) { 13980 this.player_.play(); 13981 } else { 13982 this.player_.pause(); 13983 } 13984 }; 13985 13986 /** 13987 * Called when this SeekBar has focus and a key gets pressed down. By 13988 * default it will call `this.handleAction` when the key is space or enter. 13989 * 13990 * @param {EventTarget~Event} event 13991 * The `keydown` event that caused this function to be called. 13992 * 13993 * @listens keydown 13994 */ 13995 13996 13997 SeekBar.prototype.handleKeyPress = function handleKeyPress(event) { 13998 13999 // Support Space (32) or Enter (13) key operation to fire a click event 14000 if (event.which === 32 || event.which === 13) { 14001 event.preventDefault(); 14002 this.handleAction(event); 14003 } else if (_Slider.prototype.handleKeyPress) { 14004 14005 // Pass keypress handling up for unsupported keys 14006 _Slider.prototype.handleKeyPress.call(this, event); 14007 } 14008 }; 14009 14010 return SeekBar; 14011}(Slider); 14012 14013/** 14014 * Default options for the `SeekBar` 14015 * 14016 * @type {Object} 14017 * @private 14018 */ 14019 14020 14021SeekBar.prototype.options_ = { 14022 children: ['loadProgressBar', 'playProgressBar'], 14023 barName: 'playProgressBar' 14024}; 14025 14026// MouseTimeDisplay tooltips should not be added to a player on mobile devices or IE8 14027if ((!IE_VERSION || IE_VERSION > 8) && !IS_IOS && !IS_ANDROID) { 14028 SeekBar.prototype.options_.children.splice(1, 0, 'mouseTimeDisplay'); 14029} 14030 14031/** 14032 * Call the update event for this Slider when this event happens on the player. 14033 * 14034 * @type {string} 14035 */ 14036SeekBar.prototype.playerEvent = 'timeupdate'; 14037 14038Component.registerComponent('SeekBar', SeekBar); 14039 14040/** 14041 * @file progress-control.js 14042 */ 14043/** 14044 * The Progress Control component contains the seek bar, load progress, 14045 * and play progress. 14046 * 14047 * @extends Component 14048 */ 14049 14050var ProgressControl = function (_Component) { 14051 inherits(ProgressControl, _Component); 14052 14053 /** 14054 * Creates an instance of this class. 14055 * 14056 * @param {Player} player 14057 * The `Player` that this class should be attached to. 14058 * 14059 * @param {Object} [options] 14060 * The key/value store of player options. 14061 */ 14062 function ProgressControl(player, options) { 14063 classCallCheck(this, ProgressControl); 14064 14065 var _this = possibleConstructorReturn(this, _Component.call(this, player, options)); 14066 14067 _this.handleMouseMove = throttle(bind(_this, _this.handleMouseMove), 25); 14068 _this.on(_this.el_, 'mousemove', _this.handleMouseMove); 14069 14070 _this.throttledHandleMouseSeek = throttle(bind(_this, _this.handleMouseSeek), 25); 14071 _this.on(['mousedown', 'touchstart'], _this.handleMouseDown); 14072 return _this; 14073 } 14074 14075 /** 14076 * Create the `Component`'s DOM element 14077 * 14078 * @return {Element} 14079 * The element that was created. 14080 */ 14081 14082 14083 ProgressControl.prototype.createEl = function createEl$$1() { 14084 return _Component.prototype.createEl.call(this, 'div', { 14085 className: 'vjs-progress-control vjs-control' 14086 }); 14087 }; 14088 14089 /** 14090 * When the mouse moves over the `ProgressControl`, the pointer position 14091 * gets passed down to the `MouseTimeDisplay` component. 14092 * 14093 * @param {EventTarget~Event} event 14094 * The `mousemove` event that caused this function to run. 14095 * 14096 * @listen mousemove 14097 */ 14098 14099 14100 ProgressControl.prototype.handleMouseMove = function handleMouseMove(event) { 14101 var seekBar = this.getChild('seekBar'); 14102 var mouseTimeDisplay = seekBar.getChild('mouseTimeDisplay'); 14103 var seekBarEl = seekBar.el(); 14104 var seekBarRect = getBoundingClientRect(seekBarEl); 14105 var seekBarPoint = getPointerPosition(seekBarEl, event).x; 14106 14107 // The default skin has a gap on either side of the `SeekBar`. This means 14108 // that it's possible to trigger this behavior outside the boundaries of 14109 // the `SeekBar`. This ensures we stay within it at all times. 14110 if (seekBarPoint > 1) { 14111 seekBarPoint = 1; 14112 } else if (seekBarPoint < 0) { 14113 seekBarPoint = 0; 14114 } 14115 14116 if (mouseTimeDisplay) { 14117 mouseTimeDisplay.update(seekBarRect, seekBarPoint); 14118 } 14119 }; 14120 14121 /** 14122 * A throttled version of the {@link ProgressControl#handleMouseSeek} listener. 14123 * 14124 * @method ProgressControl#throttledHandleMouseSeek 14125 * @param {EventTarget~Event} event 14126 * The `mousemove` event that caused this function to run. 14127 * 14128 * @listen mousemove 14129 * @listen touchmove 14130 */ 14131 14132 /** 14133 * Handle `mousemove` or `touchmove` events on the `ProgressControl`. 14134 * 14135 * @param {EventTarget~Event} event 14136 * `mousedown` or `touchstart` event that triggered this function 14137 * 14138 * @listens mousemove 14139 * @listens touchmove 14140 */ 14141 14142 14143 ProgressControl.prototype.handleMouseSeek = function handleMouseSeek(event) { 14144 var seekBar = this.getChild('seekBar'); 14145 14146 seekBar.handleMouseMove(event); 14147 }; 14148 14149 /** 14150 * Handle `mousedown` or `touchstart` events on the `ProgressControl`. 14151 * 14152 * @param {EventTarget~Event} event 14153 * `mousedown` or `touchstart` event that triggered this function 14154 * 14155 * @listens mousedown 14156 * @listens touchstart 14157 */ 14158 14159 14160 ProgressControl.prototype.handleMouseDown = function handleMouseDown(event) { 14161 var doc = this.el_.ownerDocument; 14162 14163 this.on(doc, 'mousemove', this.throttledHandleMouseSeek); 14164 this.on(doc, 'touchmove', this.throttledHandleMouseSeek); 14165 this.on(doc, 'mouseup', this.handleMouseUp); 14166 this.on(doc, 'touchend', this.handleMouseUp); 14167 }; 14168 14169 /** 14170 * Handle `mouseup` or `touchend` events on the `ProgressControl`. 14171 * 14172 * @param {EventTarget~Event} event 14173 * `mouseup` or `touchend` event that triggered this function. 14174 * 14175 * @listens touchend 14176 * @listens mouseup 14177 */ 14178 14179 14180 ProgressControl.prototype.handleMouseUp = function handleMouseUp(event) { 14181 var doc = this.el_.ownerDocument; 14182 14183 this.off(doc, 'mousemove', this.throttledHandleMouseSeek); 14184 this.off(doc, 'touchmove', this.throttledHandleMouseSeek); 14185 this.off(doc, 'mouseup', this.handleMouseUp); 14186 this.off(doc, 'touchend', this.handleMouseUp); 14187 }; 14188 14189 return ProgressControl; 14190}(Component); 14191 14192/** 14193 * Default options for `ProgressControl` 14194 * 14195 * @type {Object} 14196 * @private 14197 */ 14198 14199 14200ProgressControl.prototype.options_ = { 14201 children: ['seekBar'] 14202}; 14203 14204Component.registerComponent('ProgressControl', ProgressControl); 14205 14206/** 14207 * @file fullscreen-toggle.js 14208 */ 14209/** 14210 * Toggle fullscreen video 14211 * 14212 * @extends Button 14213 */ 14214 14215var FullscreenToggle = function (_Button) { 14216 inherits(FullscreenToggle, _Button); 14217 14218 /** 14219 * Creates an instance of this class. 14220 * 14221 * @param {Player} player 14222 * The `Player` that this class should be attached to. 14223 * 14224 * @param {Object} [options] 14225 * The key/value store of player options. 14226 */ 14227 function FullscreenToggle(player, options) { 14228 classCallCheck(this, FullscreenToggle); 14229 14230 var _this = possibleConstructorReturn(this, _Button.call(this, player, options)); 14231 14232 _this.on(player, 'fullscreenchange', _this.handleFullscreenChange); 14233 return _this; 14234 } 14235 14236 /** 14237 * Builds the default DOM `className`. 14238 * 14239 * @return {string} 14240 * The DOM `className` for this object. 14241 */ 14242 14243 14244 FullscreenToggle.prototype.buildCSSClass = function buildCSSClass() { 14245 return 'vjs-fullscreen-control ' + _Button.prototype.buildCSSClass.call(this); 14246 }; 14247 14248 /** 14249 * Handles fullscreenchange on the player and change control text accordingly. 14250 * 14251 * @param {EventTarget~Event} [event] 14252 * The {@link Player#fullscreenchange} event that caused this function to be 14253 * called. 14254 * 14255 * @listens Player#fullscreenchange 14256 */ 14257 14258 14259 FullscreenToggle.prototype.handleFullscreenChange = function handleFullscreenChange(event) { 14260 if (this.player_.isFullscreen()) { 14261 this.controlText('Non-Fullscreen'); 14262 } else { 14263 this.controlText('Fullscreen'); 14264 } 14265 }; 14266 14267 /** 14268 * This gets called when an `FullscreenToggle` is "clicked". See 14269 * {@link ClickableComponent} for more detailed information on what a click can be. 14270 * 14271 * @param {EventTarget~Event} [event] 14272 * The `keydown`, `tap`, or `click` event that caused this function to be 14273 * called. 14274 * 14275 * @listens tap 14276 * @listens click 14277 */ 14278 14279 14280 FullscreenToggle.prototype.handleClick = function handleClick(event) { 14281 if (!this.player_.isFullscreen()) { 14282 this.player_.requestFullscreen(); 14283 } else { 14284 this.player_.exitFullscreen(); 14285 } 14286 }; 14287 14288 return FullscreenToggle; 14289}(Button); 14290 14291/** 14292 * The text that should display over the `FullscreenToggle`s controls. Added for localization. 14293 * 14294 * @type {string} 14295 * @private 14296 */ 14297 14298 14299FullscreenToggle.prototype.controlText_ = 'Fullscreen'; 14300 14301Component.registerComponent('FullscreenToggle', FullscreenToggle); 14302 14303/** 14304 * Check if volume control is supported and if it isn't hide the 14305 * `Component` that was passed using the `vjs-hidden` class. 14306 * 14307 * @param {Component} self 14308 * The component that should be hidden if volume is unsupported 14309 * 14310 * @param {Player} player 14311 * A reference to the player 14312 * 14313 * @private 14314 */ 14315var checkVolumeSupport = function checkVolumeSupport(self, player) { 14316 // hide volume controls when they're not supported by the current tech 14317 if (player.tech_ && !player.tech_.featuresVolumeControl) { 14318 self.addClass('vjs-hidden'); 14319 } 14320 14321 self.on(player, 'loadstart', function () { 14322 if (!player.tech_.featuresVolumeControl) { 14323 self.addClass('vjs-hidden'); 14324 } else { 14325 self.removeClass('vjs-hidden'); 14326 } 14327 }); 14328}; 14329 14330/** 14331 * @file volume-level.js 14332 */ 14333/** 14334 * Shows volume level 14335 * 14336 * @extends Component 14337 */ 14338 14339var VolumeLevel = function (_Component) { 14340 inherits(VolumeLevel, _Component); 14341 14342 function VolumeLevel() { 14343 classCallCheck(this, VolumeLevel); 14344 return possibleConstructorReturn(this, _Component.apply(this, arguments)); 14345 } 14346 14347 /** 14348 * Create the `Component`'s DOM element 14349 * 14350 * @return {Element} 14351 * The element that was created. 14352 */ 14353 VolumeLevel.prototype.createEl = function createEl() { 14354 return _Component.prototype.createEl.call(this, 'div', { 14355 className: 'vjs-volume-level', 14356 innerHTML: '<span class="vjs-control-text"></span>' 14357 }); 14358 }; 14359 14360 return VolumeLevel; 14361}(Component); 14362 14363Component.registerComponent('VolumeLevel', VolumeLevel); 14364 14365/** 14366 * @file volume-bar.js 14367 */ 14368// Required children 14369/** 14370 * The bar that contains the volume level and can be clicked on to adjust the level 14371 * 14372 * @extends Slider 14373 */ 14374 14375var VolumeBar = function (_Slider) { 14376 inherits(VolumeBar, _Slider); 14377 14378 /** 14379 * Creates an instance of this class. 14380 * 14381 * @param {Player} player 14382 * The `Player` that this class should be attached to. 14383 * 14384 * @param {Object} [options] 14385 * The key/value store of player options. 14386 */ 14387 function VolumeBar(player, options) { 14388 classCallCheck(this, VolumeBar); 14389 14390 var _this = possibleConstructorReturn(this, _Slider.call(this, player, options)); 14391 14392 _this.on('slideractive', _this.updateLastVolume_); 14393 _this.on(player, 'volumechange', _this.updateARIAAttributes); 14394 player.ready(function () { 14395 return _this.updateARIAAttributes(); 14396 }); 14397 return _this; 14398 } 14399 14400 /** 14401 * Create the `Component`'s DOM element 14402 * 14403 * @return {Element} 14404 * The element that was created. 14405 */ 14406 14407 14408 VolumeBar.prototype.createEl = function createEl() { 14409 return _Slider.prototype.createEl.call(this, 'div', { 14410 className: 'vjs-volume-bar vjs-slider-bar' 14411 }, { 14412 'aria-label': this.localize('Volume Level'), 14413 'aria-live': 'polite' 14414 }); 14415 }; 14416 14417 /** 14418 * Handle movement events on the {@link VolumeMenuButton}. 14419 * 14420 * @param {EventTarget~Event} event 14421 * The event that caused this function to run. 14422 * 14423 * @listens mousemove 14424 */ 14425 14426 14427 VolumeBar.prototype.handleMouseMove = function handleMouseMove(event) { 14428 this.checkMuted(); 14429 this.player_.volume(this.calculateDistance(event)); 14430 }; 14431 14432 /** 14433 * If the player is muted unmute it. 14434 */ 14435 14436 14437 VolumeBar.prototype.checkMuted = function checkMuted() { 14438 if (this.player_.muted()) { 14439 this.player_.muted(false); 14440 } 14441 }; 14442 14443 /** 14444 * Get percent of volume level 14445 * 14446 * @return {number} 14447 * Volume level percent as a decimal number. 14448 */ 14449 14450 14451 VolumeBar.prototype.getPercent = function getPercent() { 14452 if (this.player_.muted()) { 14453 return 0; 14454 } 14455 return this.player_.volume(); 14456 }; 14457 14458 /** 14459 * Increase volume level for keyboard users 14460 */ 14461 14462 14463 VolumeBar.prototype.stepForward = function stepForward() { 14464 this.checkMuted(); 14465 this.player_.volume(this.player_.volume() + 0.1); 14466 }; 14467 14468 /** 14469 * Decrease volume level for keyboard users 14470 */ 14471 14472 14473 VolumeBar.prototype.stepBack = function stepBack() { 14474 this.checkMuted(); 14475 this.player_.volume(this.player_.volume() - 0.1); 14476 }; 14477 14478 /** 14479 * Update ARIA accessibility attributes 14480 * 14481 * @param {EventTarget~Event} [event] 14482 * The `volumechange` event that caused this function to run. 14483 * 14484 * @listens Player#volumechange 14485 */ 14486 14487 14488 VolumeBar.prototype.updateARIAAttributes = function updateARIAAttributes(event) { 14489 var ariaValue = this.player_.muted() ? 0 : this.volumeAsPercentage_(); 14490 14491 this.el_.setAttribute('aria-valuenow', ariaValue); 14492 this.el_.setAttribute('aria-valuetext', ariaValue + '%'); 14493 }; 14494 14495 /** 14496 * Returns the current value of the player volume as a percentage 14497 * 14498 * @private 14499 */ 14500 14501 14502 VolumeBar.prototype.volumeAsPercentage_ = function volumeAsPercentage_() { 14503 return Math.round(this.player_.volume() * 100); 14504 }; 14505 14506 /** 14507 * When user starts dragging the VolumeBar, store the volume and listen for 14508 * the end of the drag. When the drag ends, if the volume was set to zero, 14509 * set lastVolume to the stored volume. 14510 * 14511 * @listens slideractive 14512 * @private 14513 */ 14514 14515 14516 VolumeBar.prototype.updateLastVolume_ = function updateLastVolume_() { 14517 var _this2 = this; 14518 14519 var volumeBeforeDrag = this.player_.volume(); 14520 14521 this.one('sliderinactive', function () { 14522 if (_this2.player_.volume() === 0) { 14523 _this2.player_.lastVolume_(volumeBeforeDrag); 14524 } 14525 }); 14526 }; 14527 14528 return VolumeBar; 14529}(Slider); 14530 14531/** 14532 * Default options for the `VolumeBar` 14533 * 14534 * @type {Object} 14535 * @private 14536 */ 14537 14538 14539VolumeBar.prototype.options_ = { 14540 children: ['volumeLevel'], 14541 barName: 'volumeLevel' 14542}; 14543 14544/** 14545 * Call the update event for this Slider when this event happens on the player. 14546 * 14547 * @type {string} 14548 */ 14549VolumeBar.prototype.playerEvent = 'volumechange'; 14550 14551Component.registerComponent('VolumeBar', VolumeBar); 14552 14553/** 14554 * @file volume-control.js 14555 */ 14556// Required children 14557/** 14558 * The component for controlling the volume level 14559 * 14560 * @extends Component 14561 */ 14562 14563var VolumeControl = function (_Component) { 14564 inherits(VolumeControl, _Component); 14565 14566 /** 14567 * Creates an instance of this class. 14568 * 14569 * @param {Player} player 14570 * The `Player` that this class should be attached to. 14571 * 14572 * @param {Object} [options={}] 14573 * The key/value store of player options. 14574 */ 14575 function VolumeControl(player) { 14576 var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 14577 classCallCheck(this, VolumeControl); 14578 14579 options.vertical = options.vertical || false; 14580 14581 // Pass the vertical option down to the VolumeBar if 14582 // the VolumeBar is turned on. 14583 if (typeof options.volumeBar === 'undefined' || isPlain(options.volumeBar)) { 14584 options.volumeBar = options.volumeBar || {}; 14585 options.volumeBar.vertical = options.vertical; 14586 } 14587 14588 // hide this control if volume support is missing 14589 var _this = possibleConstructorReturn(this, _Component.call(this, player, options)); 14590 14591 checkVolumeSupport(_this, player); 14592 14593 _this.throttledHandleMouseMove = throttle(bind(_this, _this.handleMouseMove), 25); 14594 14595 _this.on('mousedown', _this.handleMouseDown); 14596 _this.on('touchstart', _this.handleMouseDown); 14597 14598 // while the slider is active (the mouse has been pressed down and 14599 // is dragging) or in focus we do not want to hide the VolumeBar 14600 _this.on(_this.volumeBar, ['focus', 'slideractive'], function () { 14601 _this.volumeBar.addClass('vjs-slider-active'); 14602 _this.addClass('vjs-slider-active'); 14603 _this.trigger('slideractive'); 14604 }); 14605 14606 _this.on(_this.volumeBar, ['blur', 'sliderinactive'], function () { 14607 _this.volumeBar.removeClass('vjs-slider-active'); 14608 _this.removeClass('vjs-slider-active'); 14609 _this.trigger('sliderinactive'); 14610 }); 14611 return _this; 14612 } 14613 14614 /** 14615 * Create the `Component`'s DOM element 14616 * 14617 * @return {Element} 14618 * The element that was created. 14619 */ 14620 14621 14622 VolumeControl.prototype.createEl = function createEl() { 14623 var orientationClass = 'vjs-volume-horizontal'; 14624 14625 if (this.options_.vertical) { 14626 orientationClass = 'vjs-volume-vertical'; 14627 } 14628 14629 return _Component.prototype.createEl.call(this, 'div', { 14630 className: 'vjs-volume-control vjs-control ' + orientationClass 14631 }); 14632 }; 14633 14634 /** 14635 * Handle `mousedown` or `touchstart` events on the `VolumeControl`. 14636 * 14637 * @param {EventTarget~Event} event 14638 * `mousedown` or `touchstart` event that triggered this function 14639 * 14640 * @listens mousedown 14641 * @listens touchstart 14642 */ 14643 14644 14645 VolumeControl.prototype.handleMouseDown = function handleMouseDown(event) { 14646 var doc = this.el_.ownerDocument; 14647 14648 this.on(doc, 'mousemove', this.throttledHandleMouseMove); 14649 this.on(doc, 'touchmove', this.throttledHandleMouseMove); 14650 this.on(doc, 'mouseup', this.handleMouseUp); 14651 this.on(doc, 'touchend', this.handleMouseUp); 14652 }; 14653 14654 /** 14655 * Handle `mouseup` or `touchend` events on the `VolumeControl`. 14656 * 14657 * @param {EventTarget~Event} event 14658 * `mouseup` or `touchend` event that triggered this function. 14659 * 14660 * @listens touchend 14661 * @listens mouseup 14662 */ 14663 14664 14665 VolumeControl.prototype.handleMouseUp = function handleMouseUp(event) { 14666 var doc = this.el_.ownerDocument; 14667 14668 this.off(doc, 'mousemove', this.throttledHandleMouseMove); 14669 this.off(doc, 'touchmove', this.throttledHandleMouseMove); 14670 this.off(doc, 'mouseup', this.handleMouseUp); 14671 this.off(doc, 'touchend', this.handleMouseUp); 14672 }; 14673 14674 /** 14675 * Handle `mousedown` or `touchstart` events on the `VolumeControl`. 14676 * 14677 * @param {EventTarget~Event} event 14678 * `mousedown` or `touchstart` event that triggered this function 14679 * 14680 * @listens mousedown 14681 * @listens touchstart 14682 */ 14683 14684 14685 VolumeControl.prototype.handleMouseMove = function handleMouseMove(event) { 14686 this.volumeBar.handleMouseMove(event); 14687 }; 14688 14689 return VolumeControl; 14690}(Component); 14691 14692/** 14693 * Default options for the `VolumeControl` 14694 * 14695 * @type {Object} 14696 * @private 14697 */ 14698 14699 14700VolumeControl.prototype.options_ = { 14701 children: ['volumeBar'] 14702}; 14703 14704Component.registerComponent('VolumeControl', VolumeControl); 14705 14706/** 14707 * @file mute-toggle.js 14708 */ 14709/** 14710 * A button component for muting the audio. 14711 * 14712 * @extends Button 14713 */ 14714 14715var MuteToggle = function (_Button) { 14716 inherits(MuteToggle, _Button); 14717 14718 /** 14719 * Creates an instance of this class. 14720 * 14721 * @param {Player} player 14722 * The `Player` that this class should be attached to. 14723 * 14724 * @param {Object} [options] 14725 * The key/value store of player options. 14726 */ 14727 function MuteToggle(player, options) { 14728 classCallCheck(this, MuteToggle); 14729 14730 // hide this control if volume support is missing 14731 var _this = possibleConstructorReturn(this, _Button.call(this, player, options)); 14732 14733 checkVolumeSupport(_this, player); 14734 14735 _this.on(player, ['loadstart', 'volumechange'], _this.update); 14736 return _this; 14737 } 14738 14739 /** 14740 * Builds the default DOM `className`. 14741 * 14742 * @return {string} 14743 * The DOM `className` for this object. 14744 */ 14745 14746 14747 MuteToggle.prototype.buildCSSClass = function buildCSSClass() { 14748 return 'vjs-mute-control ' + _Button.prototype.buildCSSClass.call(this); 14749 }; 14750 14751 /** 14752 * This gets called when an `MuteToggle` is "clicked". See 14753 * {@link ClickableComponent} for more detailed information on what a click can be. 14754 * 14755 * @param {EventTarget~Event} [event] 14756 * The `keydown`, `tap`, or `click` event that caused this function to be 14757 * called. 14758 * 14759 * @listens tap 14760 * @listens click 14761 */ 14762 14763 14764 MuteToggle.prototype.handleClick = function handleClick(event) { 14765 var vol = this.player_.volume(); 14766 var lastVolume = this.player_.lastVolume_(); 14767 14768 if (vol === 0) { 14769 var volumeToSet = lastVolume < 0.1 ? 0.1 : lastVolume; 14770 14771 this.player_.volume(volumeToSet); 14772 this.player_.muted(false); 14773 } else { 14774 this.player_.muted(this.player_.muted() ? false : true); 14775 } 14776 }; 14777 14778 /** 14779 * Update the `MuteToggle` button based on the state of `volume` and `muted` 14780 * on the player. 14781 * 14782 * @param {EventTarget~Event} [event] 14783 * The {@link Player#loadstart} event if this function was called 14784 * through an event. 14785 * 14786 * @listens Player#loadstart 14787 * @listens Player#volumechange 14788 */ 14789 14790 14791 MuteToggle.prototype.update = function update(event) { 14792 this.updateIcon_(); 14793 this.updateControlText_(); 14794 }; 14795 14796 /** 14797 * Update the appearance of the `MuteToggle` icon. 14798 * 14799 * Possible states (given `level` variable below): 14800 * - 0: crossed out 14801 * - 1: zero bars of volume 14802 * - 2: one bar of volume 14803 * - 3: two bars of volume 14804 * 14805 * @private 14806 */ 14807 14808 14809 MuteToggle.prototype.updateIcon_ = function updateIcon_() { 14810 var vol = this.player_.volume(); 14811 var level = 3; 14812 14813 if (vol === 0 || this.player_.muted()) { 14814 level = 0; 14815 } else if (vol < 0.33) { 14816 level = 1; 14817 } else if (vol < 0.67) { 14818 level = 2; 14819 } 14820 14821 // TODO improve muted icon classes 14822 for (var i = 0; i < 4; i++) { 14823 removeClass(this.el_, 'vjs-vol-' + i); 14824 } 14825 addClass(this.el_, 'vjs-vol-' + level); 14826 }; 14827 14828 /** 14829 * If `muted` has changed on the player, update the control text 14830 * (`title` attribute on `vjs-mute-control` element and content of 14831 * `vjs-control-text` element). 14832 * 14833 * @private 14834 */ 14835 14836 14837 MuteToggle.prototype.updateControlText_ = function updateControlText_() { 14838 var soundOff = this.player_.muted() || this.player_.volume() === 0; 14839 var text = soundOff ? 'Unmute' : 'Mute'; 14840 14841 if (this.controlText() !== text) { 14842 this.controlText(text); 14843 } 14844 }; 14845 14846 return MuteToggle; 14847}(Button); 14848 14849/** 14850 * The text that should display over the `MuteToggle`s controls. Added for localization. 14851 * 14852 * @type {string} 14853 * @private 14854 */ 14855 14856 14857MuteToggle.prototype.controlText_ = 'Mute'; 14858 14859Component.registerComponent('MuteToggle', MuteToggle); 14860 14861/** 14862 * @file volume-control.js 14863 */ 14864// Required children 14865/** 14866 * A Component to contain the MuteToggle and VolumeControl so that 14867 * they can work together. 14868 * 14869 * @extends Component 14870 */ 14871 14872var VolumePanel = function (_Component) { 14873 inherits(VolumePanel, _Component); 14874 14875 /** 14876 * Creates an instance of this class. 14877 * 14878 * @param {Player} player 14879 * The `Player` that this class should be attached to. 14880 * 14881 * @param {Object} [options={}] 14882 * The key/value store of player options. 14883 */ 14884 function VolumePanel(player) { 14885 var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 14886 classCallCheck(this, VolumePanel); 14887 14888 if (typeof options.inline !== 'undefined') { 14889 options.inline = options.inline; 14890 } else { 14891 options.inline = true; 14892 } 14893 14894 // pass the inline option down to the VolumeControl as vertical if 14895 // the VolumeControl is on. 14896 if (typeof options.volumeControl === 'undefined' || isPlain(options.volumeControl)) { 14897 options.volumeControl = options.volumeControl || {}; 14898 options.volumeControl.vertical = !options.inline; 14899 } 14900 14901 // hide this control if volume support is missing 14902 var _this = possibleConstructorReturn(this, _Component.call(this, player, options)); 14903 14904 checkVolumeSupport(_this, player); 14905 14906 // while the slider is active (the mouse has been pressed down and 14907 // is dragging) or in focus we do not want to hide the VolumeBar 14908 _this.on(_this.volumeControl, ['slideractive'], _this.sliderActive_); 14909 _this.on(_this.muteToggle, 'focus', _this.sliderActive_); 14910 14911 _this.on(_this.volumeControl, ['sliderinactive'], _this.sliderInactive_); 14912 _this.on(_this.muteToggle, 'blur', _this.sliderInactive_); 14913 return _this; 14914 } 14915 14916 /** 14917 * Add vjs-slider-active class to the VolumePanel 14918 * 14919 * @listens VolumeControl#slideractive 14920 * @private 14921 */ 14922 14923 14924 VolumePanel.prototype.sliderActive_ = function sliderActive_() { 14925 this.addClass('vjs-slider-active'); 14926 }; 14927 14928 /** 14929 * Removes vjs-slider-active class to the VolumePanel 14930 * 14931 * @listens VolumeControl#sliderinactive 14932 * @private 14933 */ 14934 14935 14936 VolumePanel.prototype.sliderInactive_ = function sliderInactive_() { 14937 this.removeClass('vjs-slider-active'); 14938 }; 14939 14940 /** 14941 * Create the `Component`'s DOM element 14942 * 14943 * @return {Element} 14944 * The element that was created. 14945 */ 14946 14947 14948 VolumePanel.prototype.createEl = function createEl() { 14949 var orientationClass = 'vjs-volume-panel-horizontal'; 14950 14951 if (!this.options_.inline) { 14952 orientationClass = 'vjs-volume-panel-vertical'; 14953 } 14954 14955 return _Component.prototype.createEl.call(this, 'div', { 14956 className: 'vjs-volume-panel vjs-control ' + orientationClass 14957 }); 14958 }; 14959 14960 return VolumePanel; 14961}(Component); 14962 14963/** 14964 * Default options for the `VolumeControl` 14965 * 14966 * @type {Object} 14967 * @private 14968 */ 14969 14970 14971VolumePanel.prototype.options_ = { 14972 children: ['muteToggle', 'volumeControl'] 14973}; 14974 14975Component.registerComponent('VolumePanel', VolumePanel); 14976 14977/** 14978 * @file menu.js 14979 */ 14980/** 14981 * The Menu component is used to build popup menus, including subtitle and 14982 * captions selection menus. 14983 * 14984 * @extends Component 14985 */ 14986 14987var Menu = function (_Component) { 14988 inherits(Menu, _Component); 14989 14990 /** 14991 * Create an instance of this class. 14992 * 14993 * @param {Player} player 14994 * the player that this component should attach to 14995 * 14996 * @param {Object} [options] 14997 * Object of option names and values 14998 * 14999 */ 15000 function Menu(player, options) { 15001 classCallCheck(this, Menu); 15002 15003 var _this = possibleConstructorReturn(this, _Component.call(this, player, options)); 15004 15005 if (options) { 15006 _this.menuButton_ = options.menuButton; 15007 } 15008 15009 _this.focusedChild_ = -1; 15010 15011 _this.on('keydown', _this.handleKeyPress); 15012 return _this; 15013 } 15014 15015 /** 15016 * Add a {@link MenuItem} to the menu. 15017 * 15018 * @param {Object|string} component 15019 * The name or instance of the `MenuItem` to add. 15020 * 15021 */ 15022 15023 15024 Menu.prototype.addItem = function addItem(component) { 15025 this.addChild(component); 15026 component.on('click', bind(this, function (event) { 15027 // Unpress the associated MenuButton, and move focus back to it 15028 if (this.menuButton_) { 15029 this.menuButton_.unpressButton(); 15030 15031 // don't focus menu button if item is a caption settings item 15032 // because focus will move elsewhere and it logs an error on IE8 15033 if (component.name() !== 'CaptionSettingsMenuItem') { 15034 this.menuButton_.focus(); 15035 } 15036 } 15037 })); 15038 }; 15039 15040 /** 15041 * Create the `Menu`s DOM element. 15042 * 15043 * @return {Element} 15044 * the element that was created 15045 */ 15046 15047 15048 Menu.prototype.createEl = function createEl$$1() { 15049 var contentElType = this.options_.contentElType || 'ul'; 15050 15051 this.contentEl_ = createEl(contentElType, { 15052 className: 'vjs-menu-content' 15053 }); 15054 15055 this.contentEl_.setAttribute('role', 'menu'); 15056 15057 var el = _Component.prototype.createEl.call(this, 'div', { 15058 append: this.contentEl_, 15059 className: 'vjs-menu' 15060 }); 15061 15062 el.appendChild(this.contentEl_); 15063 15064 // Prevent clicks from bubbling up. Needed for Menu Buttons, 15065 // where a click on the parent is significant 15066 on(el, 'click', function (event) { 15067 event.preventDefault(); 15068 event.stopImmediatePropagation(); 15069 }); 15070 15071 return el; 15072 }; 15073 15074 /** 15075 * Handle a `keydown` event on this menu. This listener is added in the constructor. 15076 * 15077 * @param {EventTarget~Event} event 15078 * A `keydown` event that happened on the menu. 15079 * 15080 * @listens keydown 15081 */ 15082 15083 15084 Menu.prototype.handleKeyPress = function handleKeyPress(event) { 15085 // Left and Down Arrows 15086 if (event.which === 37 || event.which === 40) { 15087 event.preventDefault(); 15088 this.stepForward(); 15089 15090 // Up and Right Arrows 15091 } else if (event.which === 38 || event.which === 39) { 15092 event.preventDefault(); 15093 this.stepBack(); 15094 } 15095 }; 15096 15097 /** 15098 * Move to next (lower) menu item for keyboard users. 15099 */ 15100 15101 15102 Menu.prototype.stepForward = function stepForward() { 15103 var stepChild = 0; 15104 15105 if (this.focusedChild_ !== undefined) { 15106 stepChild = this.focusedChild_ + 1; 15107 } 15108 this.focus(stepChild); 15109 }; 15110 15111 /** 15112 * Move to previous (higher) menu item for keyboard users. 15113 */ 15114 15115 15116 Menu.prototype.stepBack = function stepBack() { 15117 var stepChild = 0; 15118 15119 if (this.focusedChild_ !== undefined) { 15120 stepChild = this.focusedChild_ - 1; 15121 } 15122 this.focus(stepChild); 15123 }; 15124 15125 /** 15126 * Set focus on a {@link MenuItem} in the `Menu`. 15127 * 15128 * @param {Object|string} [item=0] 15129 * Index of child item set focus on. 15130 */ 15131 15132 15133 Menu.prototype.focus = function focus() { 15134 var item = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0; 15135 15136 var children = this.children().slice(); 15137 var haveTitle = children.length && children[0].className && /vjs-menu-title/.test(children[0].className); 15138 15139 if (haveTitle) { 15140 children.shift(); 15141 } 15142 15143 if (children.length > 0) { 15144 if (item < 0) { 15145 item = 0; 15146 } else if (item >= children.length) { 15147 item = children.length - 1; 15148 } 15149 15150 this.focusedChild_ = item; 15151 15152 children[item].el_.focus(); 15153 } 15154 }; 15155 15156 return Menu; 15157}(Component); 15158 15159Component.registerComponent('Menu', Menu); 15160 15161/** 15162 * @file menu-button.js 15163 */ 15164/** 15165 * A `MenuButton` class for any popup {@link Menu}. 15166 * 15167 * @extends Component 15168 */ 15169 15170var MenuButton = function (_Component) { 15171 inherits(MenuButton, _Component); 15172 15173 /** 15174 * Creates an instance of this class. 15175 * 15176 * @param {Player} player 15177 * The `Player` that this class should be attached to. 15178 * 15179 * @param {Object} [options={}] 15180 * The key/value store of player options. 15181 */ 15182 function MenuButton(player) { 15183 var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 15184 classCallCheck(this, MenuButton); 15185 15186 var _this = possibleConstructorReturn(this, _Component.call(this, player, options)); 15187 15188 _this.menuButton_ = new Button(player, options); 15189 15190 _this.menuButton_.controlText(_this.controlText_); 15191 _this.menuButton_.el_.setAttribute('aria-haspopup', 'true'); 15192 15193 // Add buildCSSClass values to the button, not the wrapper 15194 var buttonClass = Button.prototype.buildCSSClass(); 15195 15196 _this.menuButton_.el_.className = _this.buildCSSClass() + ' ' + buttonClass; 15197 _this.menuButton_.removeClass('vjs-control'); 15198 15199 _this.addChild(_this.menuButton_); 15200 15201 _this.update(); 15202 15203 _this.enabled_ = true; 15204 15205 _this.on(_this.menuButton_, 'tap', _this.handleClick); 15206 _this.on(_this.menuButton_, 'click', _this.handleClick); 15207 _this.on(_this.menuButton_, 'focus', _this.handleFocus); 15208 _this.on(_this.menuButton_, 'blur', _this.handleBlur); 15209 15210 _this.on('keydown', _this.handleSubmenuKeyPress); 15211 return _this; 15212 } 15213 15214 /** 15215 * Update the menu based on the current state of its items. 15216 */ 15217 15218 15219 MenuButton.prototype.update = function update() { 15220 var menu = this.createMenu(); 15221 15222 if (this.menu) { 15223 this.removeChild(this.menu); 15224 } 15225 15226 this.menu = menu; 15227 this.addChild(menu); 15228 15229 /** 15230 * Track the state of the menu button 15231 * 15232 * @type {Boolean} 15233 * @private 15234 */ 15235 this.buttonPressed_ = false; 15236 this.menuButton_.el_.setAttribute('aria-expanded', 'false'); 15237 15238 if (this.items && this.items.length <= this.hideThreshold_) { 15239 this.hide(); 15240 } else { 15241 this.show(); 15242 } 15243 }; 15244 15245 /** 15246 * Create the menu and add all items to it. 15247 * 15248 * @return {Menu} 15249 * The constructed menu 15250 */ 15251 15252 15253 MenuButton.prototype.createMenu = function createMenu() { 15254 var menu = new Menu(this.player_, { menuButton: this }); 15255 15256 /** 15257 * Hide the menu if the number of items is less than or equal to this threshold. This defaults 15258 * to 0 and whenever we add items which can be hidden to the menu we'll increment it. We list 15259 * it here because every time we run `createMenu` we need to reset the value. 15260 * 15261 * @protected 15262 * @type {Number} 15263 */ 15264 this.hideThreshold_ = 0; 15265 15266 // Add a title list item to the top 15267 if (this.options_.title) { 15268 var title = createEl('li', { 15269 className: 'vjs-menu-title', 15270 innerHTML: toTitleCase(this.options_.title), 15271 tabIndex: -1 15272 }); 15273 15274 this.hideThreshold_ += 1; 15275 15276 menu.children_.unshift(title); 15277 prependTo(title, menu.contentEl()); 15278 } 15279 15280 this.items = this.createItems(); 15281 15282 if (this.items) { 15283 // Add menu items to the menu 15284 for (var i = 0; i < this.items.length; i++) { 15285 menu.addItem(this.items[i]); 15286 } 15287 } 15288 15289 return menu; 15290 }; 15291 15292 /** 15293 * Create the list of menu items. Specific to each subclass. 15294 * 15295 * @abstract 15296 */ 15297 15298 15299 MenuButton.prototype.createItems = function createItems() {}; 15300 15301 /** 15302 * Create the `MenuButtons`s DOM element. 15303 * 15304 * @return {Element} 15305 * The element that gets created. 15306 */ 15307 15308 15309 MenuButton.prototype.createEl = function createEl$$1() { 15310 return _Component.prototype.createEl.call(this, 'div', { 15311 className: this.buildWrapperCSSClass() 15312 }, {}); 15313 }; 15314 15315 /** 15316 * Allow sub components to stack CSS class names for the wrapper element 15317 * 15318 * @return {string} 15319 * The constructed wrapper DOM `className` 15320 */ 15321 15322 15323 MenuButton.prototype.buildWrapperCSSClass = function buildWrapperCSSClass() { 15324 var menuButtonClass = 'vjs-menu-button'; 15325 15326 // If the inline option is passed, we want to use different styles altogether. 15327 if (this.options_.inline === true) { 15328 menuButtonClass += '-inline'; 15329 } else { 15330 menuButtonClass += '-popup'; 15331 } 15332 15333 // TODO: Fix the CSS so that this isn't necessary 15334 var buttonClass = Button.prototype.buildCSSClass(); 15335 15336 return 'vjs-menu-button ' + menuButtonClass + ' ' + buttonClass + ' ' + _Component.prototype.buildCSSClass.call(this); 15337 }; 15338 15339 /** 15340 * Builds the default DOM `className`. 15341 * 15342 * @return {string} 15343 * The DOM `className` for this object. 15344 */ 15345 15346 15347 MenuButton.prototype.buildCSSClass = function buildCSSClass() { 15348 var menuButtonClass = 'vjs-menu-button'; 15349 15350 // If the inline option is passed, we want to use different styles altogether. 15351 if (this.options_.inline === true) { 15352 menuButtonClass += '-inline'; 15353 } else { 15354 menuButtonClass += '-popup'; 15355 } 15356 15357 return 'vjs-menu-button ' + menuButtonClass + ' ' + _Component.prototype.buildCSSClass.call(this); 15358 }; 15359 15360 /** 15361 * Get or set the localized control text that will be used for accessibility. 15362 * 15363 * > NOTE: This will come from the internal `menuButton_` element. 15364 * 15365 * @param {string} [text] 15366 * Control text for element. 15367 * 15368 * @param {Element} [el=this.menuButton_.el()] 15369 * Element to set the title on. 15370 * 15371 * @return {string} 15372 * - The control text when getting 15373 */ 15374 15375 15376 MenuButton.prototype.controlText = function controlText(text) { 15377 var el = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.menuButton_.el(); 15378 15379 return this.menuButton_.controlText(text, el); 15380 }; 15381 15382 /** 15383 * Handle a click on a `MenuButton`. 15384 * See {@link ClickableComponent#handleClick} for instances where this is called. 15385 * 15386 * @param {EventTarget~Event} event 15387 * The `keydown`, `tap`, or `click` event that caused this function to be 15388 * called. 15389 * 15390 * @listens tap 15391 * @listens click 15392 */ 15393 15394 15395 MenuButton.prototype.handleClick = function handleClick(event) { 15396 // When you click the button it adds focus, which will show the menu. 15397 // So we'll remove focus when the mouse leaves the button. Focus is needed 15398 // for tab navigation. 15399 15400 this.one(this.menu.contentEl(), 'mouseleave', bind(this, function (e) { 15401 this.unpressButton(); 15402 this.el_.blur(); 15403 })); 15404 if (this.buttonPressed_) { 15405 this.unpressButton(); 15406 } else { 15407 this.pressButton(); 15408 } 15409 }; 15410 15411 /** 15412 * Set the focus to the actual button, not to this element 15413 */ 15414 15415 15416 MenuButton.prototype.focus = function focus() { 15417 this.menuButton_.focus(); 15418 }; 15419 15420 /** 15421 * Remove the focus from the actual button, not this element 15422 */ 15423 15424 15425 MenuButton.prototype.blur = function blur() { 15426 this.menuButton_.blur(); 15427 }; 15428 15429 /** 15430 * This gets called when a `MenuButton` gains focus via a `focus` event. 15431 * Turns on listening for `keydown` events. When they happen it 15432 * calls `this.handleKeyPress`. 15433 * 15434 * @param {EventTarget~Event} event 15435 * The `focus` event that caused this function to be called. 15436 * 15437 * @listens focus 15438 */ 15439 15440 15441 MenuButton.prototype.handleFocus = function handleFocus() { 15442 on(document_1, 'keydown', bind(this, this.handleKeyPress)); 15443 }; 15444 15445 /** 15446 * Called when a `MenuButton` loses focus. Turns off the listener for 15447 * `keydown` events. Which Stops `this.handleKeyPress` from getting called. 15448 * 15449 * @param {EventTarget~Event} event 15450 * The `blur` event that caused this function to be called. 15451 * 15452 * @listens blur 15453 */ 15454 15455 15456 MenuButton.prototype.handleBlur = function handleBlur() { 15457 off(document_1, 'keydown', bind(this, this.handleKeyPress)); 15458 }; 15459 15460 /** 15461 * Handle tab, escape, down arrow, and up arrow keys for `MenuButton`. See 15462 * {@link ClickableComponent#handleKeyPress} for instances where this is called. 15463 * 15464 * @param {EventTarget~Event} event 15465 * The `keydown` event that caused this function to be called. 15466 * 15467 * @listens keydown 15468 */ 15469 15470 15471 MenuButton.prototype.handleKeyPress = function handleKeyPress(event) { 15472 15473 // Escape (27) key or Tab (9) key unpress the 'button' 15474 if (event.which === 27 || event.which === 9) { 15475 if (this.buttonPressed_) { 15476 this.unpressButton(); 15477 } 15478 // Don't preventDefault for Tab key - we still want to lose focus 15479 if (event.which !== 9) { 15480 event.preventDefault(); 15481 // Set focus back to the menu button's button 15482 this.menuButton_.el_.focus(); 15483 } 15484 // Up (38) key or Down (40) key press the 'button' 15485 } else if (event.which === 38 || event.which === 40) { 15486 if (!this.buttonPressed_) { 15487 this.pressButton(); 15488 event.preventDefault(); 15489 } 15490 } 15491 }; 15492 15493 /** 15494 * Handle a `keydown` event on a sub-menu. The listener for this is added in 15495 * the constructor. 15496 * 15497 * @param {EventTarget~Event} event 15498 * Key press event 15499 * 15500 * @listens keydown 15501 */ 15502 15503 15504 MenuButton.prototype.handleSubmenuKeyPress = function handleSubmenuKeyPress(event) { 15505 15506 // Escape (27) key or Tab (9) key unpress the 'button' 15507 if (event.which === 27 || event.which === 9) { 15508 if (this.buttonPressed_) { 15509 this.unpressButton(); 15510 } 15511 // Don't preventDefault for Tab key - we still want to lose focus 15512 if (event.which !== 9) { 15513 event.preventDefault(); 15514 // Set focus back to the menu button's button 15515 this.menuButton_.el_.focus(); 15516 } 15517 } 15518 }; 15519 15520 /** 15521 * Put the current `MenuButton` into a pressed state. 15522 */ 15523 15524 15525 MenuButton.prototype.pressButton = function pressButton() { 15526 if (this.enabled_) { 15527 this.buttonPressed_ = true; 15528 this.menu.lockShowing(); 15529 this.menuButton_.el_.setAttribute('aria-expanded', 'true'); 15530 // set the focus into the submenu 15531 this.menu.focus(); 15532 } 15533 }; 15534 15535 /** 15536 * Take the current `MenuButton` out of a pressed state. 15537 */ 15538 15539 15540 MenuButton.prototype.unpressButton = function unpressButton() { 15541 if (this.enabled_) { 15542 this.buttonPressed_ = false; 15543 this.menu.unlockShowing(); 15544 this.menuButton_.el_.setAttribute('aria-expanded', 'false'); 15545 } 15546 }; 15547 15548 /** 15549 * Disable the `MenuButton`. Don't allow it to be clicked. 15550 */ 15551 15552 15553 MenuButton.prototype.disable = function disable() { 15554 this.unpressButton(); 15555 15556 this.enabled_ = false; 15557 this.addClass('vjs-disabled'); 15558 15559 this.menuButton_.disable(); 15560 }; 15561 15562 /** 15563 * Enable the `MenuButton`. Allow it to be clicked. 15564 */ 15565 15566 15567 MenuButton.prototype.enable = function enable() { 15568 this.enabled_ = true; 15569 this.removeClass('vjs-disabled'); 15570 15571 this.menuButton_.enable(); 15572 }; 15573 15574 return MenuButton; 15575}(Component); 15576 15577Component.registerComponent('MenuButton', MenuButton); 15578 15579/** 15580 * @file track-button.js 15581 */ 15582/** 15583 * The base class for buttons that toggle specific track types (e.g. subtitles). 15584 * 15585 * @extends MenuButton 15586 */ 15587 15588var TrackButton = function (_MenuButton) { 15589 inherits(TrackButton, _MenuButton); 15590 15591 /** 15592 * Creates an instance of this class. 15593 * 15594 * @param {Player} player 15595 * The `Player` that this class should be attached to. 15596 * 15597 * @param {Object} [options] 15598 * The key/value store of player options. 15599 */ 15600 function TrackButton(player, options) { 15601 classCallCheck(this, TrackButton); 15602 15603 var tracks = options.tracks; 15604 15605 var _this = possibleConstructorReturn(this, _MenuButton.call(this, player, options)); 15606 15607 if (_this.items.length <= 1) { 15608 _this.hide(); 15609 } 15610 15611 if (!tracks) { 15612 return possibleConstructorReturn(_this); 15613 } 15614 15615 var updateHandler = bind(_this, _this.update); 15616 15617 tracks.addEventListener('removetrack', updateHandler); 15618 tracks.addEventListener('addtrack', updateHandler); 15619 _this.player_.on('ready', updateHandler); 15620 15621 _this.player_.on('dispose', function () { 15622 tracks.removeEventListener('removetrack', updateHandler); 15623 tracks.removeEventListener('addtrack', updateHandler); 15624 }); 15625 return _this; 15626 } 15627 15628 return TrackButton; 15629}(MenuButton); 15630 15631Component.registerComponent('TrackButton', TrackButton); 15632 15633/** 15634 * @file menu-item.js 15635 */ 15636/** 15637 * The component for a menu item. `<li>` 15638 * 15639 * @extends ClickableComponent 15640 */ 15641 15642var MenuItem = function (_ClickableComponent) { 15643 inherits(MenuItem, _ClickableComponent); 15644 15645 /** 15646 * Creates an instance of the this class. 15647 * 15648 * @param {Player} player 15649 * The `Player` that this class should be attached to. 15650 * 15651 * @param {Object} [options={}] 15652 * The key/value store of player options. 15653 * 15654 */ 15655 function MenuItem(player, options) { 15656 classCallCheck(this, MenuItem); 15657 15658 var _this = possibleConstructorReturn(this, _ClickableComponent.call(this, player, options)); 15659 15660 _this.selectable = options.selectable; 15661 15662 _this.selected(options.selected); 15663 15664 if (_this.selectable) { 15665 // TODO: May need to be either menuitemcheckbox or menuitemradio, 15666 // and may need logical grouping of menu items. 15667 _this.el_.setAttribute('role', 'menuitemcheckbox'); 15668 } else { 15669 _this.el_.setAttribute('role', 'menuitem'); 15670 } 15671 return _this; 15672 } 15673 15674 /** 15675 * Create the `MenuItem's DOM element 15676 * 15677 * @param {string} [type=li] 15678 * Element's node type, not actually used, always set to `li`. 15679 * 15680 * @param {Object} [props={}] 15681 * An object of properties that should be set on the element 15682 * 15683 * @param {Object} [attrs={}] 15684 * An object of attributes that should be set on the element 15685 * 15686 * @return {Element} 15687 * The element that gets created. 15688 */ 15689 15690 15691 MenuItem.prototype.createEl = function createEl(type, props, attrs) { 15692 // The control is textual, not just an icon 15693 this.nonIconControl = true; 15694 15695 return _ClickableComponent.prototype.createEl.call(this, 'li', assign({ 15696 className: 'vjs-menu-item', 15697 innerHTML: '<span class="vjs-menu-item-text">' + this.localize(this.options_.label) + '</span>', 15698 tabIndex: -1 15699 }, props), attrs); 15700 }; 15701 15702 /** 15703 * Any click on a `MenuItem` puts int into the selected state. 15704 * See {@link ClickableComponent#handleClick} for instances where this is called. 15705 * 15706 * @param {EventTarget~Event} event 15707 * The `keydown`, `tap`, or `click` event that caused this function to be 15708 * called. 15709 * 15710 * @listens tap 15711 * @listens click 15712 */ 15713 15714 15715 MenuItem.prototype.handleClick = function handleClick(event) { 15716 this.selected(true); 15717 }; 15718 15719 /** 15720 * Set the state for this menu item as selected or not. 15721 * 15722 * @param {boolean} selected 15723 * if the menu item is selected or not 15724 */ 15725 15726 15727 MenuItem.prototype.selected = function selected(_selected) { 15728 if (this.selectable) { 15729 if (_selected) { 15730 this.addClass('vjs-selected'); 15731 this.el_.setAttribute('aria-checked', 'true'); 15732 // aria-checked isn't fully supported by browsers/screen readers, 15733 // so indicate selected state to screen reader in the control text. 15734 this.controlText(', selected'); 15735 } else { 15736 this.removeClass('vjs-selected'); 15737 this.el_.setAttribute('aria-checked', 'false'); 15738 // Indicate un-selected state to screen reader 15739 // Note that a space clears out the selected state text 15740 this.controlText(' '); 15741 } 15742 } 15743 }; 15744 15745 return MenuItem; 15746}(ClickableComponent); 15747 15748Component.registerComponent('MenuItem', MenuItem); 15749 15750/** 15751 * @file text-track-menu-item.js 15752 */ 15753/** 15754 * The specific menu item type for selecting a language within a text track kind 15755 * 15756 * @extends MenuItem 15757 */ 15758 15759var TextTrackMenuItem = function (_MenuItem) { 15760 inherits(TextTrackMenuItem, _MenuItem); 15761 15762 /** 15763 * Creates an instance of this class. 15764 * 15765 * @param {Player} player 15766 * The `Player` that this class should be attached to. 15767 * 15768 * @param {Object} [options] 15769 * The key/value store of player options. 15770 */ 15771 function TextTrackMenuItem(player, options) { 15772 classCallCheck(this, TextTrackMenuItem); 15773 15774 var track = options.track; 15775 var tracks = player.textTracks(); 15776 15777 // Modify options for parent MenuItem class's init. 15778 options.label = track.label || track.language || 'Unknown'; 15779 options.selected = track.mode === 'showing'; 15780 15781 var _this = possibleConstructorReturn(this, _MenuItem.call(this, player, options)); 15782 15783 _this.track = track; 15784 var changeHandler = bind(_this, _this.handleTracksChange); 15785 var selectedLanguageChangeHandler = bind(_this, _this.handleSelectedLanguageChange); 15786 15787 player.on(['loadstart', 'texttrackchange'], changeHandler); 15788 tracks.addEventListener('change', changeHandler); 15789 tracks.addEventListener('selectedlanguagechange', selectedLanguageChangeHandler); 15790 _this.on('dispose', function () { 15791 tracks.removeEventListener('change', changeHandler); 15792 tracks.removeEventListener('selectedlanguagechange', selectedLanguageChangeHandler); 15793 }); 15794 15795 // iOS7 doesn't dispatch change events to TextTrackLists when an 15796 // associated track's mode changes. Without something like 15797 // Object.observe() (also not present on iOS7), it's not 15798 // possible to detect changes to the mode attribute and polyfill 15799 // the change event. As a poor substitute, we manually dispatch 15800 // change events whenever the controls modify the mode. 15801 if (tracks.onchange === undefined) { 15802 var event = void 0; 15803 15804 _this.on(['tap', 'click'], function () { 15805 if (_typeof(window_1.Event) !== 'object') { 15806 // Android 2.3 throws an Illegal Constructor error for window.Event 15807 try { 15808 event = new window_1.Event('change'); 15809 } catch (err) { 15810 // continue regardless of error 15811 } 15812 } 15813 15814 if (!event) { 15815 event = document_1.createEvent('Event'); 15816 event.initEvent('change', true, true); 15817 } 15818 15819 tracks.dispatchEvent(event); 15820 }); 15821 } 15822 return _this; 15823 } 15824 15825 /** 15826 * This gets called when an `TextTrackMenuItem` is "clicked". See 15827 * {@link ClickableComponent} for more detailed information on what a click can be. 15828 * 15829 * @param {EventTarget~Event} event 15830 * The `keydown`, `tap`, or `click` event that caused this function to be 15831 * called. 15832 * 15833 * @listens tap 15834 * @listens click 15835 */ 15836 15837 15838 TextTrackMenuItem.prototype.handleClick = function handleClick(event) { 15839 var kind = this.track.kind; 15840 var kinds = this.track.kinds; 15841 var tracks = this.player_.textTracks(); 15842 15843 if (!kinds) { 15844 kinds = [kind]; 15845 } 15846 15847 _MenuItem.prototype.handleClick.call(this, event); 15848 15849 if (!tracks) { 15850 return; 15851 } 15852 15853 for (var i = 0; i < tracks.length; i++) { 15854 var track = tracks[i]; 15855 15856 if (track === this.track && kinds.indexOf(track.kind) > -1) { 15857 if (track.mode !== 'showing') { 15858 track.mode = 'showing'; 15859 } 15860 } else if (track.mode !== 'disabled') { 15861 track.mode = 'disabled'; 15862 } 15863 } 15864 }; 15865 15866 /** 15867 * Handle text track list change 15868 * 15869 * @param {EventTarget~Event} event 15870 * The `change` event that caused this function to be called. 15871 * 15872 * @listens TextTrackList#change 15873 */ 15874 15875 15876 TextTrackMenuItem.prototype.handleTracksChange = function handleTracksChange(event) { 15877 this.selected(this.track.mode === 'showing'); 15878 }; 15879 15880 TextTrackMenuItem.prototype.handleSelectedLanguageChange = function handleSelectedLanguageChange(event) { 15881 if (this.track.mode === 'showing') { 15882 var selectedLanguage = this.player_.cache_.selectedLanguage; 15883 15884 // Don't replace the kind of track across the same language 15885 if (selectedLanguage && selectedLanguage.enabled && selectedLanguage.language === this.track.language && selectedLanguage.kind !== this.track.kind) { 15886 return; 15887 } 15888 15889 this.player_.cache_.selectedLanguage = { 15890 enabled: true, 15891 language: this.track.language, 15892 kind: this.track.kind 15893 }; 15894 } 15895 }; 15896 15897 return TextTrackMenuItem; 15898}(MenuItem); 15899 15900Component.registerComponent('TextTrackMenuItem', TextTrackMenuItem); 15901 15902/** 15903 * @file off-text-track-menu-item.js 15904 */ 15905/** 15906 * A special menu item for turning of a specific type of text track 15907 * 15908 * @extends TextTrackMenuItem 15909 */ 15910 15911var OffTextTrackMenuItem = function (_TextTrackMenuItem) { 15912 inherits(OffTextTrackMenuItem, _TextTrackMenuItem); 15913 15914 /** 15915 * Creates an instance of this class. 15916 * 15917 * @param {Player} player 15918 * The `Player` that this class should be attached to. 15919 * 15920 * @param {Object} [options] 15921 * The key/value store of player options. 15922 */ 15923 function OffTextTrackMenuItem(player, options) { 15924 classCallCheck(this, OffTextTrackMenuItem); 15925 15926 // Create pseudo track info 15927 // Requires options['kind'] 15928 options.track = { 15929 player: player, 15930 kind: options.kind, 15931 kinds: options.kinds, 15932 'default': false, 15933 mode: 'disabled' 15934 }; 15935 15936 if (!options.kinds) { 15937 options.kinds = [options.kind]; 15938 } 15939 15940 if (options.label) { 15941 options.track.label = options.label; 15942 } else { 15943 options.track.label = options.kinds.join(' and ') + ' off'; 15944 } 15945 15946 // MenuItem is selectable 15947 options.selectable = true; 15948 15949 var _this = possibleConstructorReturn(this, _TextTrackMenuItem.call(this, player, options)); 15950 15951 _this.selected(true); 15952 return _this; 15953 } 15954 15955 /** 15956 * Handle text track change 15957 * 15958 * @param {EventTarget~Event} event 15959 * The event that caused this function to run 15960 */ 15961 15962 15963 OffTextTrackMenuItem.prototype.handleTracksChange = function handleTracksChange(event) { 15964 var tracks = this.player().textTracks(); 15965 var selected = true; 15966 15967 for (var i = 0, l = tracks.length; i < l; i++) { 15968 var track = tracks[i]; 15969 15970 if (this.options_.kinds.indexOf(track.kind) > -1 && track.mode === 'showing') { 15971 selected = false; 15972 break; 15973 } 15974 } 15975 15976 this.selected(selected); 15977 }; 15978 15979 OffTextTrackMenuItem.prototype.handleSelectedLanguageChange = function handleSelectedLanguageChange(event) { 15980 var tracks = this.player().textTracks(); 15981 var allHidden = true; 15982 15983 for (var i = 0, l = tracks.length; i < l; i++) { 15984 var track = tracks[i]; 15985 15986 if (['captions', 'descriptions', 'subtitles'].indexOf(track.kind) > -1 && track.mode === 'showing') { 15987 allHidden = false; 15988 break; 15989 } 15990 } 15991 15992 if (allHidden) { 15993 this.player_.cache_.selectedLanguage = { 15994 enabled: false 15995 }; 15996 } 15997 }; 15998 15999 return OffTextTrackMenuItem; 16000}(TextTrackMenuItem); 16001 16002Component.registerComponent('OffTextTrackMenuItem', OffTextTrackMenuItem); 16003 16004/** 16005 * @file text-track-button.js 16006 */ 16007/** 16008 * The base class for buttons that toggle specific text track types (e.g. subtitles) 16009 * 16010 * @extends MenuButton 16011 */ 16012 16013var TextTrackButton = function (_TrackButton) { 16014 inherits(TextTrackButton, _TrackButton); 16015 16016 /** 16017 * Creates an instance of this class. 16018 * 16019 * @param {Player} player 16020 * The `Player` that this class should be attached to. 16021 * 16022 * @param {Object} [options={}] 16023 * The key/value store of player options. 16024 */ 16025 function TextTrackButton(player) { 16026 var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 16027 classCallCheck(this, TextTrackButton); 16028 16029 options.tracks = player.textTracks(); 16030 16031 return possibleConstructorReturn(this, _TrackButton.call(this, player, options)); 16032 } 16033 16034 /** 16035 * Create a menu item for each text track 16036 * 16037 * @param {TextTrackMenuItem[]} [items=[]] 16038 * Existing array of items to use during creation 16039 * 16040 * @return {TextTrackMenuItem[]} 16041 * Array of menu items that were created 16042 */ 16043 16044 16045 TextTrackButton.prototype.createItems = function createItems() { 16046 var items = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; 16047 var TrackMenuItem = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : TextTrackMenuItem; 16048 16049 16050 // Label is an overide for the [track] off label 16051 // USed to localise captions/subtitles 16052 var label = void 0; 16053 16054 if (this.label_) { 16055 label = this.label_ + ' off'; 16056 } 16057 // Add an OFF menu item to turn all tracks off 16058 items.push(new OffTextTrackMenuItem(this.player_, { 16059 kinds: this.kinds_, 16060 kind: this.kind_, 16061 label: label 16062 })); 16063 16064 this.hideThreshold_ += 1; 16065 16066 var tracks = this.player_.textTracks(); 16067 16068 if (!Array.isArray(this.kinds_)) { 16069 this.kinds_ = [this.kind_]; 16070 } 16071 16072 for (var i = 0; i < tracks.length; i++) { 16073 var track = tracks[i]; 16074 16075 // only add tracks that are of an appropriate kind and have a label 16076 if (this.kinds_.indexOf(track.kind) > -1) { 16077 16078 var item = new TrackMenuItem(this.player_, { 16079 track: track, 16080 // MenuItem is selectable 16081 selectable: true 16082 }); 16083 16084 item.addClass('vjs-' + track.kind + '-menu-item'); 16085 items.push(item); 16086 } 16087 } 16088 16089 return items; 16090 }; 16091 16092 return TextTrackButton; 16093}(TrackButton); 16094 16095Component.registerComponent('TextTrackButton', TextTrackButton); 16096 16097/** 16098 * @file chapters-track-menu-item.js 16099 */ 16100/** 16101 * The chapter track menu item 16102 * 16103 * @extends MenuItem 16104 */ 16105 16106var ChaptersTrackMenuItem = function (_MenuItem) { 16107 inherits(ChaptersTrackMenuItem, _MenuItem); 16108 16109 /** 16110 * Creates an instance of this class. 16111 * 16112 * @param {Player} player 16113 * The `Player` that this class should be attached to. 16114 * 16115 * @param {Object} [options] 16116 * The key/value store of player options. 16117 */ 16118 function ChaptersTrackMenuItem(player, options) { 16119 classCallCheck(this, ChaptersTrackMenuItem); 16120 16121 var track = options.track; 16122 var cue = options.cue; 16123 var currentTime = player.currentTime(); 16124 16125 // Modify options for parent MenuItem class's init. 16126 options.selectable = true; 16127 options.label = cue.text; 16128 options.selected = cue.startTime <= currentTime && currentTime < cue.endTime; 16129 16130 var _this = possibleConstructorReturn(this, _MenuItem.call(this, player, options)); 16131 16132 _this.track = track; 16133 _this.cue = cue; 16134 track.addEventListener('cuechange', bind(_this, _this.update)); 16135 return _this; 16136 } 16137 16138 /** 16139 * This gets called when an `ChaptersTrackMenuItem` is "clicked". See 16140 * {@link ClickableComponent} for more detailed information on what a click can be. 16141 * 16142 * @param {EventTarget~Event} [event] 16143 * The `keydown`, `tap`, or `click` event that caused this function to be 16144 * called. 16145 * 16146 * @listens tap 16147 * @listens click 16148 */ 16149 16150 16151 ChaptersTrackMenuItem.prototype.handleClick = function handleClick(event) { 16152 _MenuItem.prototype.handleClick.call(this); 16153 this.player_.currentTime(this.cue.startTime); 16154 this.update(this.cue.startTime); 16155 }; 16156 16157 /** 16158 * Update chapter menu item 16159 * 16160 * @param {EventTarget~Event} [event] 16161 * The `cuechange` event that caused this function to run. 16162 * 16163 * @listens TextTrack#cuechange 16164 */ 16165 16166 16167 ChaptersTrackMenuItem.prototype.update = function update(event) { 16168 var cue = this.cue; 16169 var currentTime = this.player_.currentTime(); 16170 16171 // vjs.log(currentTime, cue.startTime); 16172 this.selected(cue.startTime <= currentTime && currentTime < cue.endTime); 16173 }; 16174 16175 return ChaptersTrackMenuItem; 16176}(MenuItem); 16177 16178Component.registerComponent('ChaptersTrackMenuItem', ChaptersTrackMenuItem); 16179 16180/** 16181 * @file chapters-button.js 16182 */ 16183/** 16184 * The button component for toggling and selecting chapters 16185 * Chapters act much differently than other text tracks 16186 * Cues are navigation vs. other tracks of alternative languages 16187 * 16188 * @extends TextTrackButton 16189 */ 16190 16191var ChaptersButton = function (_TextTrackButton) { 16192 inherits(ChaptersButton, _TextTrackButton); 16193 16194 /** 16195 * Creates an instance of this class. 16196 * 16197 * @param {Player} player 16198 * The `Player` that this class should be attached to. 16199 * 16200 * @param {Object} [options] 16201 * The key/value store of player options. 16202 * 16203 * @param {Component~ReadyCallback} [ready] 16204 * The function to call when this function is ready. 16205 */ 16206 function ChaptersButton(player, options, ready) { 16207 classCallCheck(this, ChaptersButton); 16208 return possibleConstructorReturn(this, _TextTrackButton.call(this, player, options, ready)); 16209 } 16210 16211 /** 16212 * Builds the default DOM `className`. 16213 * 16214 * @return {string} 16215 * The DOM `className` for this object. 16216 */ 16217 16218 16219 ChaptersButton.prototype.buildCSSClass = function buildCSSClass() { 16220 return 'vjs-chapters-button ' + _TextTrackButton.prototype.buildCSSClass.call(this); 16221 }; 16222 16223 ChaptersButton.prototype.buildWrapperCSSClass = function buildWrapperCSSClass() { 16224 return 'vjs-chapters-button ' + _TextTrackButton.prototype.buildWrapperCSSClass.call(this); 16225 }; 16226 16227 /** 16228 * Update the menu based on the current state of its items. 16229 * 16230 * @param {EventTarget~Event} [event] 16231 * An event that triggered this function to run. 16232 * 16233 * @listens TextTrackList#addtrack 16234 * @listens TextTrackList#removetrack 16235 * @listens TextTrackList#change 16236 */ 16237 16238 16239 ChaptersButton.prototype.update = function update(event) { 16240 if (!this.track_ || event && (event.type === 'addtrack' || event.type === 'removetrack')) { 16241 this.setTrack(this.findChaptersTrack()); 16242 } 16243 _TextTrackButton.prototype.update.call(this); 16244 }; 16245 16246 /** 16247 * Set the currently selected track for the chapters button. 16248 * 16249 * @param {TextTrack} track 16250 * The new track to select. Nothing will change if this is the currently selected 16251 * track. 16252 */ 16253 16254 16255 ChaptersButton.prototype.setTrack = function setTrack(track) { 16256 if (this.track_ === track) { 16257 return; 16258 } 16259 16260 if (!this.updateHandler_) { 16261 this.updateHandler_ = this.update.bind(this); 16262 } 16263 16264 // here this.track_ refers to the old track instance 16265 if (this.track_) { 16266 var remoteTextTrackEl = this.player_.remoteTextTrackEls().getTrackElementByTrack_(this.track_); 16267 16268 if (remoteTextTrackEl) { 16269 remoteTextTrackEl.removeEventListener('load', this.updateHandler_); 16270 } 16271 16272 this.track_ = null; 16273 } 16274 16275 this.track_ = track; 16276 16277 // here this.track_ refers to the new track instance 16278 if (this.track_) { 16279 this.track_.mode = 'hidden'; 16280 16281 var _remoteTextTrackEl = this.player_.remoteTextTrackEls().getTrackElementByTrack_(this.track_); 16282 16283 if (_remoteTextTrackEl) { 16284 _remoteTextTrackEl.addEventListener('load', this.updateHandler_); 16285 } 16286 } 16287 }; 16288 16289 /** 16290 * Find the track object that is currently in use by this ChaptersButton 16291 * 16292 * @return {TextTrack|undefined} 16293 * The current track or undefined if none was found. 16294 */ 16295 16296 16297 ChaptersButton.prototype.findChaptersTrack = function findChaptersTrack() { 16298 var tracks = this.player_.textTracks() || []; 16299 16300 for (var i = tracks.length - 1; i >= 0; i--) { 16301 // We will always choose the last track as our chaptersTrack 16302 var track = tracks[i]; 16303 16304 if (track.kind === this.kind_) { 16305 return track; 16306 } 16307 } 16308 }; 16309 16310 /** 16311 * Get the caption for the ChaptersButton based on the track label. This will also 16312 * use the current tracks localized kind as a fallback if a label does not exist. 16313 * 16314 * @return {string} 16315 * The tracks current label or the localized track kind. 16316 */ 16317 16318 16319 ChaptersButton.prototype.getMenuCaption = function getMenuCaption() { 16320 if (this.track_ && this.track_.label) { 16321 return this.track_.label; 16322 } 16323 return this.localize(toTitleCase(this.kind_)); 16324 }; 16325 16326 /** 16327 * Create menu from chapter track 16328 * 16329 * @return {Menu} 16330 * New menu for the chapter buttons 16331 */ 16332 16333 16334 ChaptersButton.prototype.createMenu = function createMenu() { 16335 this.options_.title = this.getMenuCaption(); 16336 return _TextTrackButton.prototype.createMenu.call(this); 16337 }; 16338 16339 /** 16340 * Create a menu item for each text track 16341 * 16342 * @return {TextTrackMenuItem[]} 16343 * Array of menu items 16344 */ 16345 16346 16347 ChaptersButton.prototype.createItems = function createItems() { 16348 var items = []; 16349 16350 if (!this.track_) { 16351 return items; 16352 } 16353 16354 var cues = this.track_.cues; 16355 16356 if (!cues) { 16357 return items; 16358 } 16359 16360 for (var i = 0, l = cues.length; i < l; i++) { 16361 var cue = cues[i]; 16362 var mi = new ChaptersTrackMenuItem(this.player_, { track: this.track_, cue: cue }); 16363 16364 items.push(mi); 16365 } 16366 16367 return items; 16368 }; 16369 16370 return ChaptersButton; 16371}(TextTrackButton); 16372 16373/** 16374 * `kind` of TextTrack to look for to associate it with this menu. 16375 * 16376 * @type {string} 16377 * @private 16378 */ 16379 16380 16381ChaptersButton.prototype.kind_ = 'chapters'; 16382 16383/** 16384 * The text that should display over the `ChaptersButton`s controls. Added for localization. 16385 * 16386 * @type {string} 16387 * @private 16388 */ 16389ChaptersButton.prototype.controlText_ = 'Chapters'; 16390 16391Component.registerComponent('ChaptersButton', ChaptersButton); 16392 16393/** 16394 * @file descriptions-button.js 16395 */ 16396/** 16397 * The button component for toggling and selecting descriptions 16398 * 16399 * @extends TextTrackButton 16400 */ 16401 16402var DescriptionsButton = function (_TextTrackButton) { 16403 inherits(DescriptionsButton, _TextTrackButton); 16404 16405 /** 16406 * Creates an instance of this class. 16407 * 16408 * @param {Player} player 16409 * The `Player` that this class should be attached to. 16410 * 16411 * @param {Object} [options] 16412 * The key/value store of player options. 16413 * 16414 * @param {Component~ReadyCallback} [ready] 16415 * The function to call when this component is ready. 16416 */ 16417 function DescriptionsButton(player, options, ready) { 16418 classCallCheck(this, DescriptionsButton); 16419 16420 var _this = possibleConstructorReturn(this, _TextTrackButton.call(this, player, options, ready)); 16421 16422 var tracks = player.textTracks(); 16423 var changeHandler = bind(_this, _this.handleTracksChange); 16424 16425 tracks.addEventListener('change', changeHandler); 16426 _this.on('dispose', function () { 16427 tracks.removeEventListener('change', changeHandler); 16428 }); 16429 return _this; 16430 } 16431 16432 /** 16433 * Handle text track change 16434 * 16435 * @param {EventTarget~Event} event 16436 * The event that caused this function to run 16437 * 16438 * @listens TextTrackList#change 16439 */ 16440 16441 16442 DescriptionsButton.prototype.handleTracksChange = function handleTracksChange(event) { 16443 var tracks = this.player().textTracks(); 16444 var disabled = false; 16445 16446 // Check whether a track of a different kind is showing 16447 for (var i = 0, l = tracks.length; i < l; i++) { 16448 var track = tracks[i]; 16449 16450 if (track.kind !== this.kind_ && track.mode === 'showing') { 16451 disabled = true; 16452 break; 16453 } 16454 } 16455 16456 // If another track is showing, disable this menu button 16457 if (disabled) { 16458 this.disable(); 16459 } else { 16460 this.enable(); 16461 } 16462 }; 16463 16464 /** 16465 * Builds the default DOM `className`. 16466 * 16467 * @return {string} 16468 * The DOM `className` for this object. 16469 */ 16470 16471 16472 DescriptionsButton.prototype.buildCSSClass = function buildCSSClass() { 16473 return 'vjs-descriptions-button ' + _TextTrackButton.prototype.buildCSSClass.call(this); 16474 }; 16475 16476 DescriptionsButton.prototype.buildWrapperCSSClass = function buildWrapperCSSClass() { 16477 return 'vjs-descriptions-button ' + _TextTrackButton.prototype.buildWrapperCSSClass.call(this); 16478 }; 16479 16480 return DescriptionsButton; 16481}(TextTrackButton); 16482 16483/** 16484 * `kind` of TextTrack to look for to associate it with this menu. 16485 * 16486 * @type {string} 16487 * @private 16488 */ 16489 16490 16491DescriptionsButton.prototype.kind_ = 'descriptions'; 16492 16493/** 16494 * The text that should display over the `DescriptionsButton`s controls. Added for localization. 16495 * 16496 * @type {string} 16497 * @private 16498 */ 16499DescriptionsButton.prototype.controlText_ = 'Descriptions'; 16500 16501Component.registerComponent('DescriptionsButton', DescriptionsButton); 16502 16503/** 16504 * @file subtitles-button.js 16505 */ 16506/** 16507 * The button component for toggling and selecting subtitles 16508 * 16509 * @extends TextTrackButton 16510 */ 16511 16512var SubtitlesButton = function (_TextTrackButton) { 16513 inherits(SubtitlesButton, _TextTrackButton); 16514 16515 /** 16516 * Creates an instance of this class. 16517 * 16518 * @param {Player} player 16519 * The `Player` that this class should be attached to. 16520 * 16521 * @param {Object} [options] 16522 * The key/value store of player options. 16523 * 16524 * @param {Component~ReadyCallback} [ready] 16525 * The function to call when this component is ready. 16526 */ 16527 function SubtitlesButton(player, options, ready) { 16528 classCallCheck(this, SubtitlesButton); 16529 return possibleConstructorReturn(this, _TextTrackButton.call(this, player, options, ready)); 16530 } 16531 16532 /** 16533 * Builds the default DOM `className`. 16534 * 16535 * @return {string} 16536 * The DOM `className` for this object. 16537 */ 16538 16539 16540 SubtitlesButton.prototype.buildCSSClass = function buildCSSClass() { 16541 return 'vjs-subtitles-button ' + _TextTrackButton.prototype.buildCSSClass.call(this); 16542 }; 16543 16544 SubtitlesButton.prototype.buildWrapperCSSClass = function buildWrapperCSSClass() { 16545 return 'vjs-subtitles-button ' + _TextTrackButton.prototype.buildWrapperCSSClass.call(this); 16546 }; 16547 16548 return SubtitlesButton; 16549}(TextTrackButton); 16550 16551/** 16552 * `kind` of TextTrack to look for to associate it with this menu. 16553 * 16554 * @type {string} 16555 * @private 16556 */ 16557 16558 16559SubtitlesButton.prototype.kind_ = 'subtitles'; 16560 16561/** 16562 * The text that should display over the `SubtitlesButton`s controls. Added for localization. 16563 * 16564 * @type {string} 16565 * @private 16566 */ 16567SubtitlesButton.prototype.controlText_ = 'Subtitles'; 16568 16569Component.registerComponent('SubtitlesButton', SubtitlesButton); 16570 16571/** 16572 * @file caption-settings-menu-item.js 16573 */ 16574/** 16575 * The menu item for caption track settings menu 16576 * 16577 * @extends TextTrackMenuItem 16578 */ 16579 16580var CaptionSettingsMenuItem = function (_TextTrackMenuItem) { 16581 inherits(CaptionSettingsMenuItem, _TextTrackMenuItem); 16582 16583 /** 16584 * Creates an instance of this class. 16585 * 16586 * @param {Player} player 16587 * The `Player` that this class should be attached to. 16588 * 16589 * @param {Object} [options] 16590 * The key/value store of player options. 16591 */ 16592 function CaptionSettingsMenuItem(player, options) { 16593 classCallCheck(this, CaptionSettingsMenuItem); 16594 16595 options.track = { 16596 player: player, 16597 kind: options.kind, 16598 label: options.kind + ' settings', 16599 selectable: false, 16600 'default': false, 16601 mode: 'disabled' 16602 }; 16603 16604 // CaptionSettingsMenuItem has no concept of 'selected' 16605 options.selectable = false; 16606 16607 options.name = 'CaptionSettingsMenuItem'; 16608 16609 var _this = possibleConstructorReturn(this, _TextTrackMenuItem.call(this, player, options)); 16610 16611 _this.addClass('vjs-texttrack-settings'); 16612 _this.controlText(', opens ' + options.kind + ' settings dialog'); 16613 return _this; 16614 } 16615 16616 /** 16617 * This gets called when an `CaptionSettingsMenuItem` is "clicked". See 16618 * {@link ClickableComponent} for more detailed information on what a click can be. 16619 * 16620 * @param {EventTarget~Event} [event] 16621 * The `keydown`, `tap`, or `click` event that caused this function to be 16622 * called. 16623 * 16624 * @listens tap 16625 * @listens click 16626 */ 16627 16628 16629 CaptionSettingsMenuItem.prototype.handleClick = function handleClick(event) { 16630 this.player().getChild('textTrackSettings').open(); 16631 }; 16632 16633 return CaptionSettingsMenuItem; 16634}(TextTrackMenuItem); 16635 16636Component.registerComponent('CaptionSettingsMenuItem', CaptionSettingsMenuItem); 16637 16638/** 16639 * @file captions-button.js 16640 */ 16641/** 16642 * The button component for toggling and selecting captions 16643 * 16644 * @extends TextTrackButton 16645 */ 16646 16647var CaptionsButton = function (_TextTrackButton) { 16648 inherits(CaptionsButton, _TextTrackButton); 16649 16650 /** 16651 * Creates an instance of this class. 16652 * 16653 * @param {Player} player 16654 * The `Player` that this class should be attached to. 16655 * 16656 * @param {Object} [options] 16657 * The key/value store of player options. 16658 * 16659 * @param {Component~ReadyCallback} [ready] 16660 * The function to call when this component is ready. 16661 */ 16662 function CaptionsButton(player, options, ready) { 16663 classCallCheck(this, CaptionsButton); 16664 return possibleConstructorReturn(this, _TextTrackButton.call(this, player, options, ready)); 16665 } 16666 16667 /** 16668 * Builds the default DOM `className`. 16669 * 16670 * @return {string} 16671 * The DOM `className` for this object. 16672 */ 16673 16674 16675 CaptionsButton.prototype.buildCSSClass = function buildCSSClass() { 16676 return 'vjs-captions-button ' + _TextTrackButton.prototype.buildCSSClass.call(this); 16677 }; 16678 16679 CaptionsButton.prototype.buildWrapperCSSClass = function buildWrapperCSSClass() { 16680 return 'vjs-captions-button ' + _TextTrackButton.prototype.buildWrapperCSSClass.call(this); 16681 }; 16682 16683 /** 16684 * Create caption menu items 16685 * 16686 * @return {CaptionSettingsMenuItem[]} 16687 * The array of current menu items. 16688 */ 16689 16690 16691 CaptionsButton.prototype.createItems = function createItems() { 16692 var items = []; 16693 16694 if (!(this.player().tech_ && this.player().tech_.featuresNativeTextTracks)) { 16695 items.push(new CaptionSettingsMenuItem(this.player_, { kind: this.kind_ })); 16696 16697 this.hideThreshold_ += 1; 16698 } 16699 16700 return _TextTrackButton.prototype.createItems.call(this, items); 16701 }; 16702 16703 return CaptionsButton; 16704}(TextTrackButton); 16705 16706/** 16707 * `kind` of TextTrack to look for to associate it with this menu. 16708 * 16709 * @type {string} 16710 * @private 16711 */ 16712 16713 16714CaptionsButton.prototype.kind_ = 'captions'; 16715 16716/** 16717 * The text that should display over the `CaptionsButton`s controls. Added for localization. 16718 * 16719 * @type {string} 16720 * @private 16721 */ 16722CaptionsButton.prototype.controlText_ = 'Captions'; 16723 16724Component.registerComponent('CaptionsButton', CaptionsButton); 16725 16726/** 16727 * @file subs-caps-menu-item.js 16728 */ 16729/** 16730 * SubsCapsMenuItem has an [cc] icon to distinguish captions from subtitles 16731 * in the SubsCapsMenu. 16732 * 16733 * @extends TextTrackMenuItem 16734 */ 16735 16736var SubsCapsMenuItem = function (_TextTrackMenuItem) { 16737 inherits(SubsCapsMenuItem, _TextTrackMenuItem); 16738 16739 function SubsCapsMenuItem() { 16740 classCallCheck(this, SubsCapsMenuItem); 16741 return possibleConstructorReturn(this, _TextTrackMenuItem.apply(this, arguments)); 16742 } 16743 16744 SubsCapsMenuItem.prototype.createEl = function createEl(type, props, attrs) { 16745 var innerHTML = '<span class="vjs-menu-item-text">' + this.localize(this.options_.label); 16746 16747 if (this.options_.track.kind === 'captions') { 16748 innerHTML += '\n <span aria-hidden="true" class="vjs-icon-placeholder"></span>\n <span class="vjs-control-text"> ' + this.localize('Captions') + '</span>\n '; 16749 } 16750 16751 innerHTML += '</span>'; 16752 16753 var el = _TextTrackMenuItem.prototype.createEl.call(this, type, assign({ 16754 innerHTML: innerHTML 16755 }, props), attrs); 16756 16757 return el; 16758 }; 16759 16760 return SubsCapsMenuItem; 16761}(TextTrackMenuItem); 16762 16763Component.registerComponent('SubsCapsMenuItem', SubsCapsMenuItem); 16764 16765/** 16766 * @file sub-caps-button.js 16767 */ 16768/** 16769 * The button component for toggling and selecting captions and/or subtitles 16770 * 16771 * @extends TextTrackButton 16772 */ 16773 16774var SubsCapsButton = function (_TextTrackButton) { 16775 inherits(SubsCapsButton, _TextTrackButton); 16776 16777 function SubsCapsButton(player) { 16778 var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 16779 classCallCheck(this, SubsCapsButton); 16780 16781 // Although North America uses "captions" in most cases for 16782 // "captions and subtitles" other locales use "subtitles" 16783 var _this = possibleConstructorReturn(this, _TextTrackButton.call(this, player, options)); 16784 16785 _this.label_ = 'subtitles'; 16786 if (['en', 'en-us', 'en-ca', 'fr-ca'].indexOf(_this.player_.language_) > -1) { 16787 _this.label_ = 'captions'; 16788 } 16789 _this.menuButton_.controlText(toTitleCase(_this.label_)); 16790 return _this; 16791 } 16792 16793 /** 16794 * Builds the default DOM `className`. 16795 * 16796 * @return {string} 16797 * The DOM `className` for this object. 16798 */ 16799 16800 16801 SubsCapsButton.prototype.buildCSSClass = function buildCSSClass() { 16802 return 'vjs-subs-caps-button ' + _TextTrackButton.prototype.buildCSSClass.call(this); 16803 }; 16804 16805 SubsCapsButton.prototype.buildWrapperCSSClass = function buildWrapperCSSClass() { 16806 return 'vjs-subs-caps-button ' + _TextTrackButton.prototype.buildWrapperCSSClass.call(this); 16807 }; 16808 16809 /** 16810 * Create caption/subtitles menu items 16811 * 16812 * @return {CaptionSettingsMenuItem[]} 16813 * The array of current menu items. 16814 */ 16815 16816 16817 SubsCapsButton.prototype.createItems = function createItems() { 16818 var items = []; 16819 16820 if (!(this.player().tech_ && this.player().tech_.featuresNativeTextTracks)) { 16821 items.push(new CaptionSettingsMenuItem(this.player_, { kind: this.label_ })); 16822 16823 this.hideThreshold_ += 1; 16824 } 16825 16826 items = _TextTrackButton.prototype.createItems.call(this, items, SubsCapsMenuItem); 16827 return items; 16828 }; 16829 16830 return SubsCapsButton; 16831}(TextTrackButton); 16832 16833/** 16834 * `kind`s of TextTrack to look for to associate it with this menu. 16835 * 16836 * @type {array} 16837 * @private 16838 */ 16839 16840 16841SubsCapsButton.prototype.kinds_ = ['captions', 'subtitles']; 16842 16843/** 16844 * The text that should display over the `SubsCapsButton`s controls. 16845 * 16846 * 16847 * @type {string} 16848 * @private 16849 */ 16850SubsCapsButton.prototype.controlText_ = 'Subtitles'; 16851 16852Component.registerComponent('SubsCapsButton', SubsCapsButton); 16853 16854/** 16855 * @file audio-track-menu-item.js 16856 */ 16857/** 16858 * An {@link AudioTrack} {@link MenuItem} 16859 * 16860 * @extends MenuItem 16861 */ 16862 16863var AudioTrackMenuItem = function (_MenuItem) { 16864 inherits(AudioTrackMenuItem, _MenuItem); 16865 16866 /** 16867 * Creates an instance of this class. 16868 * 16869 * @param {Player} player 16870 * The `Player` that this class should be attached to. 16871 * 16872 * @param {Object} [options] 16873 * The key/value store of player options. 16874 */ 16875 function AudioTrackMenuItem(player, options) { 16876 classCallCheck(this, AudioTrackMenuItem); 16877 16878 var track = options.track; 16879 var tracks = player.audioTracks(); 16880 16881 // Modify options for parent MenuItem class's init. 16882 options.label = track.label || track.language || 'Unknown'; 16883 options.selected = track.enabled; 16884 16885 var _this = possibleConstructorReturn(this, _MenuItem.call(this, player, options)); 16886 16887 _this.track = track; 16888 16889 var changeHandler = bind(_this, _this.handleTracksChange); 16890 16891 tracks.addEventListener('change', changeHandler); 16892 _this.on('dispose', function () { 16893 tracks.removeEventListener('change', changeHandler); 16894 }); 16895 return _this; 16896 } 16897 16898 /** 16899 * This gets called when an `AudioTrackMenuItem is "clicked". See {@link ClickableComponent} 16900 * for more detailed information on what a click can be. 16901 * 16902 * @param {EventTarget~Event} [event] 16903 * The `keydown`, `tap`, or `click` event that caused this function to be 16904 * called. 16905 * 16906 * @listens tap 16907 * @listens click 16908 */ 16909 16910 16911 AudioTrackMenuItem.prototype.handleClick = function handleClick(event) { 16912 var tracks = this.player_.audioTracks(); 16913 16914 _MenuItem.prototype.handleClick.call(this, event); 16915 16916 for (var i = 0; i < tracks.length; i++) { 16917 var track = tracks[i]; 16918 16919 track.enabled = track === this.track; 16920 } 16921 }; 16922 16923 /** 16924 * Handle any {@link AudioTrack} change. 16925 * 16926 * @param {EventTarget~Event} [event] 16927 * The {@link AudioTrackList#change} event that caused this to run. 16928 * 16929 * @listens AudioTrackList#change 16930 */ 16931 16932 16933 AudioTrackMenuItem.prototype.handleTracksChange = function handleTracksChange(event) { 16934 this.selected(this.track.enabled); 16935 }; 16936 16937 return AudioTrackMenuItem; 16938}(MenuItem); 16939 16940Component.registerComponent('AudioTrackMenuItem', AudioTrackMenuItem); 16941 16942/** 16943 * @file audio-track-button.js 16944 */ 16945/** 16946 * The base class for buttons that toggle specific {@link AudioTrack} types. 16947 * 16948 * @extends TrackButton 16949 */ 16950 16951var AudioTrackButton = function (_TrackButton) { 16952 inherits(AudioTrackButton, _TrackButton); 16953 16954 /** 16955 * Creates an instance of this class. 16956 * 16957 * @param {Player} player 16958 * The `Player` that this class should be attached to. 16959 * 16960 * @param {Object} [options={}] 16961 * The key/value store of player options. 16962 */ 16963 function AudioTrackButton(player) { 16964 var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 16965 classCallCheck(this, AudioTrackButton); 16966 16967 options.tracks = player.audioTracks(); 16968 16969 return possibleConstructorReturn(this, _TrackButton.call(this, player, options)); 16970 } 16971 16972 /** 16973 * Builds the default DOM `className`. 16974 * 16975 * @return {string} 16976 * The DOM `className` for this object. 16977 */ 16978 16979 16980 AudioTrackButton.prototype.buildCSSClass = function buildCSSClass() { 16981 return 'vjs-audio-button ' + _TrackButton.prototype.buildCSSClass.call(this); 16982 }; 16983 16984 AudioTrackButton.prototype.buildWrapperCSSClass = function buildWrapperCSSClass() { 16985 return 'vjs-audio-button ' + _TrackButton.prototype.buildWrapperCSSClass.call(this); 16986 }; 16987 16988 /** 16989 * Create a menu item for each audio track 16990 * 16991 * @param {AudioTrackMenuItem[]} [items=[]] 16992 * An array of existing menu items to use. 16993 * 16994 * @return {AudioTrackMenuItem[]} 16995 * An array of menu items 16996 */ 16997 16998 16999 AudioTrackButton.prototype.createItems = function createItems() { 17000 var items = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; 17001 17002 // if there's only one audio track, there no point in showing it 17003 this.hideThreshold_ = 1; 17004 17005 var tracks = this.player_.audioTracks(); 17006 17007 for (var i = 0; i < tracks.length; i++) { 17008 var track = tracks[i]; 17009 17010 items.push(new AudioTrackMenuItem(this.player_, { 17011 track: track, 17012 // MenuItem is selectable 17013 selectable: true 17014 })); 17015 } 17016 17017 return items; 17018 }; 17019 17020 return AudioTrackButton; 17021}(TrackButton); 17022 17023/** 17024 * The text that should display over the `AudioTrackButton`s controls. Added for localization. 17025 * 17026 * @type {string} 17027 * @private 17028 */ 17029 17030 17031AudioTrackButton.prototype.controlText_ = 'Audio Track'; 17032Component.registerComponent('AudioTrackButton', AudioTrackButton); 17033 17034/** 17035 * @file playback-rate-menu-item.js 17036 */ 17037/** 17038 * The specific menu item type for selecting a playback rate. 17039 * 17040 * @extends MenuItem 17041 */ 17042 17043var PlaybackRateMenuItem = function (_MenuItem) { 17044 inherits(PlaybackRateMenuItem, _MenuItem); 17045 17046 /** 17047 * Creates an instance of this class. 17048 * 17049 * @param {Player} player 17050 * The `Player` that this class should be attached to. 17051 * 17052 * @param {Object} [options] 17053 * The key/value store of player options. 17054 */ 17055 function PlaybackRateMenuItem(player, options) { 17056 classCallCheck(this, PlaybackRateMenuItem); 17057 17058 var label = options.rate; 17059 var rate = parseFloat(label, 10); 17060 17061 // Modify options for parent MenuItem class's init. 17062 options.label = label; 17063 options.selected = rate === 1; 17064 options.selectable = true; 17065 17066 var _this = possibleConstructorReturn(this, _MenuItem.call(this, player, options)); 17067 17068 _this.label = label; 17069 _this.rate = rate; 17070 17071 _this.on(player, 'ratechange', _this.update); 17072 return _this; 17073 } 17074 17075 /** 17076 * This gets called when an `PlaybackRateMenuItem` is "clicked". See 17077 * {@link ClickableComponent} for more detailed information on what a click can be. 17078 * 17079 * @param {EventTarget~Event} [event] 17080 * The `keydown`, `tap`, or `click` event that caused this function to be 17081 * called. 17082 * 17083 * @listens tap 17084 * @listens click 17085 */ 17086 17087 17088 PlaybackRateMenuItem.prototype.handleClick = function handleClick(event) { 17089 _MenuItem.prototype.handleClick.call(this); 17090 this.player().playbackRate(this.rate); 17091 }; 17092 17093 /** 17094 * Update the PlaybackRateMenuItem when the playbackrate changes. 17095 * 17096 * @param {EventTarget~Event} [event] 17097 * The `ratechange` event that caused this function to run. 17098 * 17099 * @listens Player#ratechange 17100 */ 17101 17102 17103 PlaybackRateMenuItem.prototype.update = function update(event) { 17104 this.selected(this.player().playbackRate() === this.rate); 17105 }; 17106 17107 return PlaybackRateMenuItem; 17108}(MenuItem); 17109 17110/** 17111 * The text that should display over the `PlaybackRateMenuItem`s controls. Added for localization. 17112 * 17113 * @type {string} 17114 * @private 17115 */ 17116 17117 17118PlaybackRateMenuItem.prototype.contentElType = 'button'; 17119 17120Component.registerComponent('PlaybackRateMenuItem', PlaybackRateMenuItem); 17121 17122/** 17123 * @file playback-rate-menu-button.js 17124 */ 17125/** 17126 * The component for controlling the playback rate. 17127 * 17128 * @extends MenuButton 17129 */ 17130 17131var PlaybackRateMenuButton = function (_MenuButton) { 17132 inherits(PlaybackRateMenuButton, _MenuButton); 17133 17134 /** 17135 * Creates an instance of this class. 17136 * 17137 * @param {Player} player 17138 * The `Player` that this class should be attached to. 17139 * 17140 * @param {Object} [options] 17141 * The key/value store of player options. 17142 */ 17143 function PlaybackRateMenuButton(player, options) { 17144 classCallCheck(this, PlaybackRateMenuButton); 17145 17146 var _this = possibleConstructorReturn(this, _MenuButton.call(this, player, options)); 17147 17148 _this.updateVisibility(); 17149 _this.updateLabel(); 17150 17151 _this.on(player, 'loadstart', _this.updateVisibility); 17152 _this.on(player, 'ratechange', _this.updateLabel); 17153 return _this; 17154 } 17155 17156 /** 17157 * Create the `Component`'s DOM element 17158 * 17159 * @return {Element} 17160 * The element that was created. 17161 */ 17162 17163 17164 PlaybackRateMenuButton.prototype.createEl = function createEl$$1() { 17165 var el = _MenuButton.prototype.createEl.call(this); 17166 17167 this.labelEl_ = createEl('div', { 17168 className: 'vjs-playback-rate-value', 17169 innerHTML: '1x' 17170 }); 17171 17172 el.appendChild(this.labelEl_); 17173 17174 return el; 17175 }; 17176 17177 /** 17178 * Builds the default DOM `className`. 17179 * 17180 * @return {string} 17181 * The DOM `className` for this object. 17182 */ 17183 17184 17185 PlaybackRateMenuButton.prototype.buildCSSClass = function buildCSSClass() { 17186 return 'vjs-playback-rate ' + _MenuButton.prototype.buildCSSClass.call(this); 17187 }; 17188 17189 PlaybackRateMenuButton.prototype.buildWrapperCSSClass = function buildWrapperCSSClass() { 17190 return 'vjs-playback-rate ' + _MenuButton.prototype.buildWrapperCSSClass.call(this); 17191 }; 17192 17193 /** 17194 * Create the playback rate menu 17195 * 17196 * @return {Menu} 17197 * Menu object populated with {@link PlaybackRateMenuItem}s 17198 */ 17199 17200 17201 PlaybackRateMenuButton.prototype.createMenu = function createMenu() { 17202 var menu = new Menu(this.player()); 17203 var rates = this.playbackRates(); 17204 17205 if (rates) { 17206 for (var i = rates.length - 1; i >= 0; i--) { 17207 menu.addChild(new PlaybackRateMenuItem(this.player(), { rate: rates[i] + 'x' })); 17208 } 17209 } 17210 17211 return menu; 17212 }; 17213 17214 /** 17215 * Updates ARIA accessibility attributes 17216 */ 17217 17218 17219 PlaybackRateMenuButton.prototype.updateARIAAttributes = function updateARIAAttributes() { 17220 // Current playback rate 17221 this.el().setAttribute('aria-valuenow', this.player().playbackRate()); 17222 }; 17223 17224 /** 17225 * This gets called when an `PlaybackRateMenuButton` is "clicked". See 17226 * {@link ClickableComponent} for more detailed information on what a click can be. 17227 * 17228 * @param {EventTarget~Event} [event] 17229 * The `keydown`, `tap`, or `click` event that caused this function to be 17230 * called. 17231 * 17232 * @listens tap 17233 * @listens click 17234 */ 17235 17236 17237 PlaybackRateMenuButton.prototype.handleClick = function handleClick(event) { 17238 // select next rate option 17239 var currentRate = this.player().playbackRate(); 17240 var rates = this.playbackRates(); 17241 17242 // this will select first one if the last one currently selected 17243 var newRate = rates[0]; 17244 17245 for (var i = 0; i < rates.length; i++) { 17246 if (rates[i] > currentRate) { 17247 newRate = rates[i]; 17248 break; 17249 } 17250 } 17251 this.player().playbackRate(newRate); 17252 }; 17253 17254 /** 17255 * Get possible playback rates 17256 * 17257 * @return {Array} 17258 * All possible playback rates 17259 */ 17260 17261 17262 PlaybackRateMenuButton.prototype.playbackRates = function playbackRates() { 17263 return this.options_.playbackRates || this.options_.playerOptions && this.options_.playerOptions.playbackRates; 17264 }; 17265 17266 /** 17267 * Get whether playback rates is supported by the tech 17268 * and an array of playback rates exists 17269 * 17270 * @return {boolean} 17271 * Whether changing playback rate is supported 17272 */ 17273 17274 17275 PlaybackRateMenuButton.prototype.playbackRateSupported = function playbackRateSupported() { 17276 return this.player().tech_ && this.player().tech_.featuresPlaybackRate && this.playbackRates() && this.playbackRates().length > 0; 17277 }; 17278 17279 /** 17280 * Hide playback rate controls when they're no playback rate options to select 17281 * 17282 * @param {EventTarget~Event} [event] 17283 * The event that caused this function to run. 17284 * 17285 * @listens Player#loadstart 17286 */ 17287 17288 17289 PlaybackRateMenuButton.prototype.updateVisibility = function updateVisibility(event) { 17290 if (this.playbackRateSupported()) { 17291 this.removeClass('vjs-hidden'); 17292 } else { 17293 this.addClass('vjs-hidden'); 17294 } 17295 }; 17296 17297 /** 17298 * Update button label when rate changed 17299 * 17300 * @param {EventTarget~Event} [event] 17301 * The event that caused this function to run. 17302 * 17303 * @listens Player#ratechange 17304 */ 17305 17306 17307 PlaybackRateMenuButton.prototype.updateLabel = function updateLabel(event) { 17308 if (this.playbackRateSupported()) { 17309 this.labelEl_.innerHTML = this.player().playbackRate() + 'x'; 17310 } 17311 }; 17312 17313 return PlaybackRateMenuButton; 17314}(MenuButton); 17315 17316/** 17317 * The text that should display over the `FullscreenToggle`s controls. Added for localization. 17318 * 17319 * @type {string} 17320 * @private 17321 */ 17322 17323 17324PlaybackRateMenuButton.prototype.controlText_ = 'Playback Rate'; 17325 17326Component.registerComponent('PlaybackRateMenuButton', PlaybackRateMenuButton); 17327 17328/** 17329 * @file spacer.js 17330 */ 17331/** 17332 * Just an empty spacer element that can be used as an append point for plugins, etc. 17333 * Also can be used to create space between elements when necessary. 17334 * 17335 * @extends Component 17336 */ 17337 17338var Spacer = function (_Component) { 17339 inherits(Spacer, _Component); 17340 17341 function Spacer() { 17342 classCallCheck(this, Spacer); 17343 return possibleConstructorReturn(this, _Component.apply(this, arguments)); 17344 } 17345 17346 /** 17347 * Builds the default DOM `className`. 17348 * 17349 * @return {string} 17350 * The DOM `className` for this object. 17351 */ 17352 Spacer.prototype.buildCSSClass = function buildCSSClass() { 17353 return 'vjs-spacer ' + _Component.prototype.buildCSSClass.call(this); 17354 }; 17355 17356 /** 17357 * Create the `Component`'s DOM element 17358 * 17359 * @return {Element} 17360 * The element that was created. 17361 */ 17362 17363 17364 Spacer.prototype.createEl = function createEl() { 17365 return _Component.prototype.createEl.call(this, 'div', { 17366 className: this.buildCSSClass() 17367 }); 17368 }; 17369 17370 return Spacer; 17371}(Component); 17372 17373Component.registerComponent('Spacer', Spacer); 17374 17375/** 17376 * @file custom-control-spacer.js 17377 */ 17378/** 17379 * Spacer specifically meant to be used as an insertion point for new plugins, etc. 17380 * 17381 * @extends Spacer 17382 */ 17383 17384var CustomControlSpacer = function (_Spacer) { 17385 inherits(CustomControlSpacer, _Spacer); 17386 17387 function CustomControlSpacer() { 17388 classCallCheck(this, CustomControlSpacer); 17389 return possibleConstructorReturn(this, _Spacer.apply(this, arguments)); 17390 } 17391 17392 /** 17393 * Builds the default DOM `className`. 17394 * 17395 * @return {string} 17396 * The DOM `className` for this object. 17397 */ 17398 CustomControlSpacer.prototype.buildCSSClass = function buildCSSClass() { 17399 return 'vjs-custom-control-spacer ' + _Spacer.prototype.buildCSSClass.call(this); 17400 }; 17401 17402 /** 17403 * Create the `Component`'s DOM element 17404 * 17405 * @return {Element} 17406 * The element that was created. 17407 */ 17408 17409 17410 CustomControlSpacer.prototype.createEl = function createEl() { 17411 var el = _Spacer.prototype.createEl.call(this, { 17412 className: this.buildCSSClass() 17413 }); 17414 17415 // No-flex/table-cell mode requires there be some content 17416 // in the cell to fill the remaining space of the table. 17417 el.innerHTML = ' '; 17418 return el; 17419 }; 17420 17421 return CustomControlSpacer; 17422}(Spacer); 17423 17424Component.registerComponent('CustomControlSpacer', CustomControlSpacer); 17425 17426/** 17427 * @file control-bar.js 17428 */ 17429// Required children 17430/** 17431 * Container of main controls. 17432 * 17433 * @extends Component 17434 */ 17435 17436var ControlBar = function (_Component) { 17437 inherits(ControlBar, _Component); 17438 17439 function ControlBar() { 17440 classCallCheck(this, ControlBar); 17441 return possibleConstructorReturn(this, _Component.apply(this, arguments)); 17442 } 17443 17444 /** 17445 * Create the `Component`'s DOM element 17446 * 17447 * @return {Element} 17448 * The element that was created. 17449 */ 17450 ControlBar.prototype.createEl = function createEl() { 17451 return _Component.prototype.createEl.call(this, 'div', { 17452 className: 'vjs-control-bar', 17453 dir: 'ltr' 17454 }, { 17455 // The control bar is a group, but we don't aria-label it to avoid 17456 // over-announcing by JAWS 17457 role: 'group' 17458 }); 17459 }; 17460 17461 return ControlBar; 17462}(Component); 17463 17464/** 17465 * Default options for `ControlBar` 17466 * 17467 * @type {Object} 17468 * @private 17469 */ 17470 17471 17472ControlBar.prototype.options_ = { 17473 children: ['playToggle', 'volumePanel', 'currentTimeDisplay', 'timeDivider', 'durationDisplay', 'progressControl', 'liveDisplay', 'remainingTimeDisplay', 'customControlSpacer', 'playbackRateMenuButton', 'chaptersButton', 'descriptionsButton', 'subsCapsButton', 'audioTrackButton', 'fullscreenToggle'] 17474}; 17475 17476Component.registerComponent('ControlBar', ControlBar); 17477 17478/** 17479 * @file error-display.js 17480 */ 17481/** 17482 * A display that indicates an error has occurred. This means that the video 17483 * is unplayable. 17484 * 17485 * @extends ModalDialog 17486 */ 17487 17488var ErrorDisplay = function (_ModalDialog) { 17489 inherits(ErrorDisplay, _ModalDialog); 17490 17491 /** 17492 * Creates an instance of this class. 17493 * 17494 * @param {Player} player 17495 * The `Player` that this class should be attached to. 17496 * 17497 * @param {Object} [options] 17498 * The key/value store of player options. 17499 */ 17500 function ErrorDisplay(player, options) { 17501 classCallCheck(this, ErrorDisplay); 17502 17503 var _this = possibleConstructorReturn(this, _ModalDialog.call(this, player, options)); 17504 17505 _this.on(player, 'error', _this.open); 17506 return _this; 17507 } 17508 17509 /** 17510 * Builds the default DOM `className`. 17511 * 17512 * @return {string} 17513 * The DOM `className` for this object. 17514 * 17515 * @deprecated Since version 5. 17516 */ 17517 17518 17519 ErrorDisplay.prototype.buildCSSClass = function buildCSSClass() { 17520 return 'vjs-error-display ' + _ModalDialog.prototype.buildCSSClass.call(this); 17521 }; 17522 17523 /** 17524 * Gets the localized error message based on the `Player`s error. 17525 * 17526 * @return {string} 17527 * The `Player`s error message localized or an empty string. 17528 */ 17529 17530 17531 ErrorDisplay.prototype.content = function content() { 17532 var error = this.player().error(); 17533 17534 return error ? this.localize(error.message) : ''; 17535 }; 17536 17537 return ErrorDisplay; 17538}(ModalDialog); 17539 17540/** 17541 * The default options for an `ErrorDisplay`. 17542 * 17543 * @private 17544 */ 17545 17546 17547ErrorDisplay.prototype.options_ = mergeOptions(ModalDialog.prototype.options_, { 17548 pauseOnOpen: false, 17549 fillAlways: true, 17550 temporary: false, 17551 uncloseable: true 17552}); 17553 17554Component.registerComponent('ErrorDisplay', ErrorDisplay); 17555 17556/** 17557 * @file text-track-settings.js 17558 */ 17559var LOCAL_STORAGE_KEY = 'vjs-text-track-settings'; 17560 17561var COLOR_BLACK = ['#000', 'Black']; 17562var COLOR_BLUE = ['#00F', 'Blue']; 17563var COLOR_CYAN = ['#0FF', 'Cyan']; 17564var COLOR_GREEN = ['#0F0', 'Green']; 17565var COLOR_MAGENTA = ['#F0F', 'Magenta']; 17566var COLOR_RED = ['#F00', 'Red']; 17567var COLOR_WHITE = ['#FFF', 'White']; 17568var COLOR_YELLOW = ['#FF0', 'Yellow']; 17569 17570var OPACITY_OPAQUE = ['1', 'Opaque']; 17571var OPACITY_SEMI = ['0.5', 'Semi-Transparent']; 17572var OPACITY_TRANS = ['0', 'Transparent']; 17573 17574// Configuration for the various <select> elements in the DOM of this component. 17575// 17576// Possible keys include: 17577// 17578// `default`: 17579// The default option index. Only needs to be provided if not zero. 17580// `parser`: 17581// A function which is used to parse the value from the selected option in 17582// a customized way. 17583// `selector`: 17584// The selector used to find the associated <select> element. 17585var selectConfigs = { 17586 backgroundColor: { 17587 selector: '.vjs-bg-color > select', 17588 id: 'captions-background-color-%s', 17589 label: 'Color', 17590 options: [COLOR_BLACK, COLOR_WHITE, COLOR_RED, COLOR_GREEN, COLOR_BLUE, COLOR_YELLOW, COLOR_MAGENTA, COLOR_CYAN] 17591 }, 17592 17593 backgroundOpacity: { 17594 selector: '.vjs-bg-opacity > select', 17595 id: 'captions-background-opacity-%s', 17596 label: 'Transparency', 17597 options: [OPACITY_OPAQUE, OPACITY_SEMI, OPACITY_TRANS] 17598 }, 17599 17600 color: { 17601 selector: '.vjs-fg-color > select', 17602 id: 'captions-foreground-color-%s', 17603 label: 'Color', 17604 options: [COLOR_WHITE, COLOR_BLACK, COLOR_RED, COLOR_GREEN, COLOR_BLUE, COLOR_YELLOW, COLOR_MAGENTA, COLOR_CYAN] 17605 }, 17606 17607 edgeStyle: { 17608 selector: '.vjs-edge-style > select', 17609 id: '%s', 17610 label: 'Text Edge Style', 17611 options: [['none', 'None'], ['raised', 'Raised'], ['depressed', 'Depressed'], ['uniform', 'Uniform'], ['dropshadow', 'Dropshadow']] 17612 }, 17613 17614 fontFamily: { 17615 selector: '.vjs-font-family > select', 17616 id: 'captions-font-family-%s', 17617 label: 'Font Family', 17618 options: [['proportionalSansSerif', 'Proportional Sans-Serif'], ['monospaceSansSerif', 'Monospace Sans-Serif'], ['proportionalSerif', 'Proportional Serif'], ['monospaceSerif', 'Monospace Serif'], ['casual', 'Casual'], ['script', 'Script'], ['small-caps', 'Small Caps']] 17619 }, 17620 17621 fontPercent: { 17622 selector: '.vjs-font-percent > select', 17623 id: 'captions-font-size-%s', 17624 label: 'Font Size', 17625 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%']], 17626 'default': 2, 17627 parser: function parser(v) { 17628 return v === '1.00' ? null : Number(v); 17629 } 17630 }, 17631 17632 textOpacity: { 17633 selector: '.vjs-text-opacity > select', 17634 id: 'captions-foreground-opacity-%s', 17635 label: 'Transparency', 17636 options: [OPACITY_OPAQUE, OPACITY_SEMI] 17637 }, 17638 17639 // Options for this object are defined below. 17640 windowColor: { 17641 selector: '.vjs-window-color > select', 17642 id: 'captions-window-color-%s', 17643 label: 'Color' 17644 }, 17645 17646 // Options for this object are defined below. 17647 windowOpacity: { 17648 selector: '.vjs-window-opacity > select', 17649 id: 'captions-window-opacity-%s', 17650 label: 'Transparency', 17651 options: [OPACITY_TRANS, OPACITY_SEMI, OPACITY_OPAQUE] 17652 } 17653}; 17654 17655selectConfigs.windowColor.options = selectConfigs.backgroundColor.options; 17656 17657/** 17658 * Get the actual value of an option. 17659 * 17660 * @param {string} value 17661 * The value to get 17662 * 17663 * @param {Function} [parser] 17664 * Optional function to adjust the value. 17665 * 17666 * @return {Mixed} 17667 * - Will be `undefined` if no value exists 17668 * - Will be `undefined` if the given value is "none". 17669 * - Will be the actual value otherwise. 17670 * 17671 * @private 17672 */ 17673function parseOptionValue(value, parser) { 17674 if (parser) { 17675 value = parser(value); 17676 } 17677 17678 if (value && value !== 'none') { 17679 return value; 17680 } 17681} 17682 17683/** 17684 * Gets the value of the selected <option> element within a <select> element. 17685 * 17686 * @param {Element} el 17687 * the element to look in 17688 * 17689 * @param {Function} [parser] 17690 * Optional function to adjust the value. 17691 * 17692 * @return {Mixed} 17693 * - Will be `undefined` if no value exists 17694 * - Will be `undefined` if the given value is "none". 17695 * - Will be the actual value otherwise. 17696 * 17697 * @private 17698 */ 17699function getSelectedOptionValue(el, parser) { 17700 var value = el.options[el.options.selectedIndex].value; 17701 17702 return parseOptionValue(value, parser); 17703} 17704 17705/** 17706 * Sets the selected <option> element within a <select> element based on a 17707 * given value. 17708 * 17709 * @param {Element} el 17710 * The element to look in. 17711 * 17712 * @param {string} value 17713 * the property to look on. 17714 * 17715 * @param {Function} [parser] 17716 * Optional function to adjust the value before comparing. 17717 * 17718 * @private 17719 */ 17720function setSelectedOption(el, value, parser) { 17721 if (!value) { 17722 return; 17723 } 17724 17725 for (var i = 0; i < el.options.length; i++) { 17726 if (parseOptionValue(el.options[i].value, parser) === value) { 17727 el.selectedIndex = i; 17728 break; 17729 } 17730 } 17731} 17732 17733/** 17734 * Manipulate Text Tracks settings. 17735 * 17736 * @extends ModalDialog 17737 */ 17738 17739var TextTrackSettings = function (_ModalDialog) { 17740 inherits(TextTrackSettings, _ModalDialog); 17741 17742 /** 17743 * Creates an instance of this class. 17744 * 17745 * @param {Player} player 17746 * The `Player` that this class should be attached to. 17747 * 17748 * @param {Object} [options] 17749 * The key/value store of player options. 17750 */ 17751 function TextTrackSettings(player, options) { 17752 classCallCheck(this, TextTrackSettings); 17753 17754 options.temporary = false; 17755 17756 var _this = possibleConstructorReturn(this, _ModalDialog.call(this, player, options)); 17757 17758 _this.updateDisplay = bind(_this, _this.updateDisplay); 17759 17760 // fill the modal and pretend we have opened it 17761 _this.fill(); 17762 _this.hasBeenOpened_ = _this.hasBeenFilled_ = true; 17763 17764 _this.endDialog = createEl('p', { 17765 className: 'vjs-control-text', 17766 textContent: _this.localize('End of dialog window.') 17767 }); 17768 _this.el().appendChild(_this.endDialog); 17769 17770 _this.setDefaults(); 17771 17772 // Grab `persistTextTrackSettings` from the player options if not passed in child options 17773 if (options.persistTextTrackSettings === undefined) { 17774 _this.options_.persistTextTrackSettings = _this.options_.playerOptions.persistTextTrackSettings; 17775 } 17776 17777 _this.on(_this.$('.vjs-done-button'), 'click', function () { 17778 _this.saveSettings(); 17779 _this.close(); 17780 }); 17781 17782 _this.on(_this.$('.vjs-default-button'), 'click', function () { 17783 _this.setDefaults(); 17784 _this.updateDisplay(); 17785 }); 17786 17787 each(selectConfigs, function (config) { 17788 _this.on(_this.$(config.selector), 'change', _this.updateDisplay); 17789 }); 17790 17791 if (_this.options_.persistTextTrackSettings) { 17792 _this.restoreSettings(); 17793 } 17794 return _this; 17795 } 17796 17797 /** 17798 * Create a <select> element with configured options. 17799 * 17800 * @param {string} key 17801 * Configuration key to use during creation. 17802 * 17803 * @return {string} 17804 * An HTML string. 17805 * 17806 * @private 17807 */ 17808 17809 17810 TextTrackSettings.prototype.createElSelect_ = function createElSelect_(key) { 17811 var _this2 = this; 17812 17813 var legendId = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : ''; 17814 var type = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'label'; 17815 17816 var config = selectConfigs[key]; 17817 var id = config.id.replace('%s', this.id_); 17818 17819 return ['<' + type + ' id="' + id + '" class="' + (type === 'label' ? 'vjs-label' : '') + '">', this.localize(config.label), '</' + type + '>', '<select aria-labelledby="' + legendId + ' ' + id + '">'].concat(config.options.map(function (o) { 17820 var optionId = id + '-' + o[1]; 17821 17822 return ['<option id="' + optionId + '" value="' + o[0] + '" ', 'aria-labelledby="' + legendId + ' ' + id + ' ' + optionId + '">', _this2.localize(o[1]), '</option>'].join(''); 17823 })).concat('</select>').join(''); 17824 }; 17825 17826 /** 17827 * Create foreground color element for the component 17828 * 17829 * @return {string} 17830 * An HTML string. 17831 * 17832 * @private 17833 */ 17834 17835 17836 TextTrackSettings.prototype.createElFgColor_ = function createElFgColor_() { 17837 var legendId = 'captions-text-legend-' + this.id_; 17838 17839 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(''); 17840 }; 17841 17842 /** 17843 * Create background color element for the component 17844 * 17845 * @return {string} 17846 * An HTML string. 17847 * 17848 * @private 17849 */ 17850 17851 17852 TextTrackSettings.prototype.createElBgColor_ = function createElBgColor_() { 17853 var legendId = 'captions-background-' + this.id_; 17854 17855 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(''); 17856 }; 17857 17858 /** 17859 * Create window color element for the component 17860 * 17861 * @return {string} 17862 * An HTML string. 17863 * 17864 * @private 17865 */ 17866 17867 17868 TextTrackSettings.prototype.createElWinColor_ = function createElWinColor_() { 17869 var legendId = 'captions-window-' + this.id_; 17870 17871 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(''); 17872 }; 17873 17874 /** 17875 * Create color elements for the component 17876 * 17877 * @return {Element} 17878 * The element that was created 17879 * 17880 * @private 17881 */ 17882 17883 17884 TextTrackSettings.prototype.createElColors_ = function createElColors_() { 17885 return createEl('div', { 17886 className: 'vjs-track-settings-colors', 17887 innerHTML: [this.createElFgColor_(), this.createElBgColor_(), this.createElWinColor_()].join('') 17888 }); 17889 }; 17890 17891 /** 17892 * Create font elements for the component 17893 * 17894 * @return {Element} 17895 * The element that was created. 17896 * 17897 * @private 17898 */ 17899 17900 17901 TextTrackSettings.prototype.createElFont_ = function createElFont_() { 17902 return createEl('div', { 17903 className: 'vjs-track-settings-font">', 17904 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('') 17905 }); 17906 }; 17907 17908 /** 17909 * Create controls for the component 17910 * 17911 * @return {Element} 17912 * The element that was created. 17913 * 17914 * @private 17915 */ 17916 17917 17918 TextTrackSettings.prototype.createElControls_ = function createElControls_() { 17919 var defaultsDescription = this.localize('restore all settings to the default values'); 17920 17921 return createEl('div', { 17922 className: 'vjs-track-settings-controls', 17923 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('') 17924 }); 17925 }; 17926 17927 TextTrackSettings.prototype.content = function content() { 17928 return [this.createElColors_(), this.createElFont_(), this.createElControls_()]; 17929 }; 17930 17931 TextTrackSettings.prototype.label = function label() { 17932 return this.localize('Caption Settings Dialog'); 17933 }; 17934 17935 TextTrackSettings.prototype.description = function description() { 17936 return this.localize('Beginning of dialog window. Escape will cancel and close the window.'); 17937 }; 17938 17939 TextTrackSettings.prototype.buildCSSClass = function buildCSSClass() { 17940 return _ModalDialog.prototype.buildCSSClass.call(this) + ' vjs-text-track-settings'; 17941 }; 17942 17943 /** 17944 * Gets an object of text track settings (or null). 17945 * 17946 * @return {Object} 17947 * An object with config values parsed from the DOM or localStorage. 17948 */ 17949 17950 17951 TextTrackSettings.prototype.getValues = function getValues() { 17952 var _this3 = this; 17953 17954 return reduce(selectConfigs, function (accum, config, key) { 17955 var value = getSelectedOptionValue(_this3.$(config.selector), config.parser); 17956 17957 if (value !== undefined) { 17958 accum[key] = value; 17959 } 17960 17961 return accum; 17962 }, {}); 17963 }; 17964 17965 /** 17966 * Sets text track settings from an object of values. 17967 * 17968 * @param {Object} values 17969 * An object with config values parsed from the DOM or localStorage. 17970 */ 17971 17972 17973 TextTrackSettings.prototype.setValues = function setValues(values) { 17974 var _this4 = this; 17975 17976 each(selectConfigs, function (config, key) { 17977 setSelectedOption(_this4.$(config.selector), values[key], config.parser); 17978 }); 17979 }; 17980 17981 /** 17982 * Sets all `<select>` elements to their default values. 17983 */ 17984 17985 17986 TextTrackSettings.prototype.setDefaults = function setDefaults() { 17987 var _this5 = this; 17988 17989 each(selectConfigs, function (config) { 17990 var index = config.hasOwnProperty('default') ? config['default'] : 0; 17991 17992 _this5.$(config.selector).selectedIndex = index; 17993 }); 17994 }; 17995 17996 /** 17997 * Restore texttrack settings from localStorage 17998 */ 17999 18000 18001 TextTrackSettings.prototype.restoreSettings = function restoreSettings() { 18002 var values = void 0; 18003 18004 try { 18005 values = JSON.parse(window_1.localStorage.getItem(LOCAL_STORAGE_KEY)); 18006 } catch (err) { 18007 log$1.warn(err); 18008 } 18009 18010 if (values) { 18011 this.setValues(values); 18012 } 18013 }; 18014 18015 /** 18016 * Save text track settings to localStorage 18017 */ 18018 18019 18020 TextTrackSettings.prototype.saveSettings = function saveSettings() { 18021 if (!this.options_.persistTextTrackSettings) { 18022 return; 18023 } 18024 18025 var values = this.getValues(); 18026 18027 try { 18028 if (Object.keys(values).length) { 18029 window_1.localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(values)); 18030 } else { 18031 window_1.localStorage.removeItem(LOCAL_STORAGE_KEY); 18032 } 18033 } catch (err) { 18034 log$1.warn(err); 18035 } 18036 }; 18037 18038 /** 18039 * Update display of text track settings 18040 */ 18041 18042 18043 TextTrackSettings.prototype.updateDisplay = function updateDisplay() { 18044 var ttDisplay = this.player_.getChild('textTrackDisplay'); 18045 18046 if (ttDisplay) { 18047 ttDisplay.updateDisplay(); 18048 } 18049 }; 18050 18051 /** 18052 * conditionally blur the element and refocus the captions button 18053 * 18054 * @private 18055 */ 18056 18057 18058 TextTrackSettings.prototype.conditionalBlur_ = function conditionalBlur_() { 18059 this.previouslyActiveEl_ = null; 18060 this.off(document_1, 'keydown', this.handleKeyDown); 18061 18062 var cb = this.player_.controlBar; 18063 var subsCapsBtn = cb && cb.subsCapsButton; 18064 var ccBtn = cb && cb.captionsButton; 18065 18066 if (subsCapsBtn) { 18067 subsCapsBtn.focus(); 18068 } else if (ccBtn) { 18069 ccBtn.focus(); 18070 } 18071 }; 18072 18073 return TextTrackSettings; 18074}(ModalDialog); 18075 18076Component.registerComponent('TextTrackSettings', TextTrackSettings); 18077 18078var _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.']); 18079 18080/** 18081 * @file html5.js 18082 */ 18083/** 18084 * HTML5 Media Controller - Wrapper for HTML5 Media API 18085 * 18086 * @mixes Tech~SouceHandlerAdditions 18087 * @extends Tech 18088 */ 18089 18090var Html5 = function (_Tech) { 18091 inherits(Html5, _Tech); 18092 18093 /** 18094 * Create an instance of this Tech. 18095 * 18096 * @param {Object} [options] 18097 * The key/value store of player options. 18098 * 18099 * @param {Component~ReadyCallback} ready 18100 * Callback function to call when the `HTML5` Tech is ready. 18101 */ 18102 function Html5(options, ready) { 18103 classCallCheck(this, Html5); 18104 18105 var _this = possibleConstructorReturn(this, _Tech.call(this, options, ready)); 18106 18107 var source = options.source; 18108 var crossoriginTracks = false; 18109 18110 // Set the source if one is provided 18111 // 1) Check if the source is new (if not, we want to keep the original so playback isn't interrupted) 18112 // 2) Check to see if the network state of the tag was failed at init, and if so, reset the source 18113 // anyway so the error gets fired. 18114 if (source && (_this.el_.currentSrc !== source.src || options.tag && options.tag.initNetworkState_ === 3)) { 18115 _this.setSource(source); 18116 } else { 18117 _this.handleLateInit_(_this.el_); 18118 } 18119 18120 if (_this.el_.hasChildNodes()) { 18121 18122 var nodes = _this.el_.childNodes; 18123 var nodesLength = nodes.length; 18124 var removeNodes = []; 18125 18126 while (nodesLength--) { 18127 var node = nodes[nodesLength]; 18128 var nodeName = node.nodeName.toLowerCase(); 18129 18130 if (nodeName === 'track') { 18131 if (!_this.featuresNativeTextTracks) { 18132 // Empty video tag tracks so the built-in player doesn't use them also. 18133 // This may not be fast enough to stop HTML5 browsers from reading the tags 18134 // so we'll need to turn off any default tracks if we're manually doing 18135 // captions and subtitles. videoElement.textTracks 18136 removeNodes.push(node); 18137 } else { 18138 // store HTMLTrackElement and TextTrack to remote list 18139 _this.remoteTextTrackEls().addTrackElement_(node); 18140 _this.remoteTextTracks().addTrack(node.track); 18141 _this.textTracks().addTrack(node.track); 18142 if (!crossoriginTracks && !_this.el_.hasAttribute('crossorigin') && isCrossOrigin(node.src)) { 18143 crossoriginTracks = true; 18144 } 18145 } 18146 } 18147 } 18148 18149 for (var i = 0; i < removeNodes.length; i++) { 18150 _this.el_.removeChild(removeNodes[i]); 18151 } 18152 } 18153 18154 _this.proxyNativeTracks_(); 18155 if (_this.featuresNativeTextTracks && crossoriginTracks) { 18156 log$1.warn(tsml(_templateObject$2)); 18157 } 18158 18159 // prevent iOS Safari from disabling metadata text tracks during native playback 18160 _this.restoreMetadataTracksInIOSNativePlayer_(); 18161 18162 // Determine if native controls should be used 18163 // Our goal should be to get the custom controls on mobile solid everywhere 18164 // so we can remove this all together. Right now this will block custom 18165 // controls on touch enabled laptops like the Chrome Pixel 18166 if ((TOUCH_ENABLED || IS_IPHONE || IS_NATIVE_ANDROID) && options.nativeControlsForTouch === true) { 18167 _this.setControls(true); 18168 } 18169 18170 // on iOS, we want to proxy `webkitbeginfullscreen` and `webkitendfullscreen` 18171 // into a `fullscreenchange` event 18172 _this.proxyWebkitFullscreen_(); 18173 18174 _this.triggerReady(); 18175 return _this; 18176 } 18177 18178 /** 18179 * Dispose of `HTML5` media element and remove all tracks. 18180 */ 18181 18182 18183 Html5.prototype.dispose = function dispose() { 18184 Html5.disposeMediaElement(this.el_); 18185 // tech will handle clearing of the emulated track list 18186 _Tech.prototype.dispose.call(this); 18187 }; 18188 18189 /** 18190 * When a captions track is enabled in the iOS Safari native player, all other 18191 * tracks are disabled (including metadata tracks), which nulls all of their 18192 * associated cue points. This will restore metadata tracks to their pre-fullscreen 18193 * state in those cases so that cue points are not needlessly lost. 18194 * 18195 * @private 18196 */ 18197 18198 18199 Html5.prototype.restoreMetadataTracksInIOSNativePlayer_ = function restoreMetadataTracksInIOSNativePlayer_() { 18200 var textTracks = this.textTracks(); 18201 var metadataTracksPreFullscreenState = void 0; 18202 18203 // captures a snapshot of every metadata track's current state 18204 var takeMetadataTrackSnapshot = function takeMetadataTrackSnapshot() { 18205 metadataTracksPreFullscreenState = []; 18206 18207 for (var i = 0; i < textTracks.length; i++) { 18208 var track = textTracks[i]; 18209 18210 if (track.kind === 'metadata') { 18211 metadataTracksPreFullscreenState.push({ 18212 track: track, 18213 storedMode: track.mode 18214 }); 18215 } 18216 } 18217 }; 18218 18219 // snapshot each metadata track's initial state, and update the snapshot 18220 // each time there is a track 'change' event 18221 takeMetadataTrackSnapshot(); 18222 textTracks.addEventListener('change', takeMetadataTrackSnapshot); 18223 18224 var restoreTrackMode = function restoreTrackMode() { 18225 for (var i = 0; i < metadataTracksPreFullscreenState.length; i++) { 18226 var storedTrack = metadataTracksPreFullscreenState[i]; 18227 18228 if (storedTrack.track.mode === 'disabled' && storedTrack.track.mode !== storedTrack.storedMode) { 18229 storedTrack.track.mode = storedTrack.storedMode; 18230 } 18231 } 18232 // we only want this handler to be executed on the first 'change' event 18233 textTracks.removeEventListener('change', restoreTrackMode); 18234 }; 18235 18236 // when we enter fullscreen playback, stop updating the snapshot and 18237 // restore all track modes to their pre-fullscreen state 18238 this.on('webkitbeginfullscreen', function () { 18239 textTracks.removeEventListener('change', takeMetadataTrackSnapshot); 18240 18241 // remove the listener before adding it just in case it wasn't previously removed 18242 textTracks.removeEventListener('change', restoreTrackMode); 18243 textTracks.addEventListener('change', restoreTrackMode); 18244 }); 18245 18246 // start updating the snapshot again after leaving fullscreen 18247 this.on('webkitendfullscreen', function () { 18248 // remove the listener before adding it just in case it wasn't previously removed 18249 textTracks.removeEventListener('change', takeMetadataTrackSnapshot); 18250 textTracks.addEventListener('change', takeMetadataTrackSnapshot); 18251 18252 // remove the restoreTrackMode handler in case it wasn't triggered during fullscreen playback 18253 textTracks.removeEventListener('change', restoreTrackMode); 18254 }); 18255 }; 18256 18257 /** 18258 * Proxy all native track list events to our track lists if the browser we are playing 18259 * in supports that type of track list. 18260 * 18261 * @private 18262 */ 18263 18264 18265 Html5.prototype.proxyNativeTracks_ = function proxyNativeTracks_() { 18266 var _this2 = this; 18267 18268 NORMAL.names.forEach(function (name) { 18269 var props = NORMAL[name]; 18270 var elTracks = _this2.el()[props.getterName]; 18271 var techTracks = _this2[props.getterName](); 18272 18273 if (!_this2['featuresNative' + props.capitalName + 'Tracks'] || !elTracks || !elTracks.addEventListener) { 18274 return; 18275 } 18276 var listeners = { 18277 change: function change(e) { 18278 techTracks.trigger({ 18279 type: 'change', 18280 target: techTracks, 18281 currentTarget: techTracks, 18282 srcElement: techTracks 18283 }); 18284 }, 18285 addtrack: function addtrack(e) { 18286 techTracks.addTrack(e.track); 18287 }, 18288 removetrack: function removetrack(e) { 18289 techTracks.removeTrack(e.track); 18290 } 18291 }; 18292 var removeOldTracks = function removeOldTracks() { 18293 var removeTracks = []; 18294 18295 for (var i = 0; i < techTracks.length; i++) { 18296 var found = false; 18297 18298 for (var j = 0; j < elTracks.length; j++) { 18299 if (elTracks[j] === techTracks[i]) { 18300 found = true; 18301 break; 18302 } 18303 } 18304 18305 if (!found) { 18306 removeTracks.push(techTracks[i]); 18307 } 18308 } 18309 18310 while (removeTracks.length) { 18311 techTracks.removeTrack(removeTracks.shift()); 18312 } 18313 }; 18314 18315 Object.keys(listeners).forEach(function (eventName) { 18316 var listener = listeners[eventName]; 18317 18318 elTracks.addEventListener(eventName, listener); 18319 _this2.on('dispose', function (e) { 18320 return elTracks.removeEventListener(eventName, listener); 18321 }); 18322 }); 18323 18324 // Remove (native) tracks that are not used anymore 18325 _this2.on('loadstart', removeOldTracks); 18326 _this2.on('dispose', function (e) { 18327 return _this2.off('loadstart', removeOldTracks); 18328 }); 18329 }); 18330 }; 18331 18332 /** 18333 * Create the `Html5` Tech's DOM element. 18334 * 18335 * @return {Element} 18336 * The element that gets created. 18337 */ 18338 18339 18340 Html5.prototype.createEl = function createEl$$1() { 18341 var el = this.options_.tag; 18342 18343 // Check if this browser supports moving the element into the box. 18344 // On the iPhone video will break if you move the element, 18345 // So we have to create a brand new element. 18346 // If we ingested the player div, we do not need to move the media element. 18347 if (!el || !(this.options_.playerElIngest || this.movingMediaElementInDOM)) { 18348 18349 // If the original tag is still there, clone and remove it. 18350 if (el) { 18351 var clone = el.cloneNode(true); 18352 18353 if (el.parentNode) { 18354 el.parentNode.insertBefore(clone, el); 18355 } 18356 Html5.disposeMediaElement(el); 18357 el = clone; 18358 } else { 18359 el = document_1.createElement('video'); 18360 18361 // determine if native controls should be used 18362 var tagAttributes = this.options_.tag && getAttributes(this.options_.tag); 18363 var attributes = mergeOptions({}, tagAttributes); 18364 18365 if (!TOUCH_ENABLED || this.options_.nativeControlsForTouch !== true) { 18366 delete attributes.controls; 18367 } 18368 18369 setAttributes(el, assign(attributes, { 18370 id: this.options_.techId, 18371 'class': 'vjs-tech' 18372 })); 18373 } 18374 18375 el.playerId = this.options_.playerId; 18376 } 18377 18378 if (typeof this.options_.preload !== 'undefined') { 18379 setAttribute(el, 'preload', this.options_.preload); 18380 } 18381 18382 // Update specific tag settings, in case they were overridden 18383 // `autoplay` has to be *last* so that `muted` and `playsinline` are present 18384 // when iOS/Safari or other browsers attempt to autoplay. 18385 var settingsAttrs = ['loop', 'muted', 'playsinline', 'autoplay']; 18386 18387 for (var i = settingsAttrs.length - 1; i >= 0; i--) { 18388 var attr = settingsAttrs[i]; 18389 var value = this.options_[attr]; 18390 18391 if (typeof value !== 'undefined') { 18392 if (value) { 18393 setAttribute(el, attr, attr); 18394 } else { 18395 removeAttribute(el, attr); 18396 } 18397 el[attr] = value; 18398 } 18399 } 18400 18401 return el; 18402 }; 18403 18404 /** 18405 * This will be triggered if the loadstart event has already fired, before videojs was 18406 * ready. Two known examples of when this can happen are: 18407 * 1. If we're loading the playback object after it has started loading 18408 * 2. The media is already playing the (often with autoplay on) then 18409 * 18410 * This function will fire another loadstart so that videojs can catchup. 18411 * 18412 * @fires Tech#loadstart 18413 * 18414 * @return {undefined} 18415 * returns nothing. 18416 */ 18417 18418 18419 Html5.prototype.handleLateInit_ = function handleLateInit_(el) { 18420 if (el.networkState === 0 || el.networkState === 3) { 18421 // The video element hasn't started loading the source yet 18422 // or didn't find a source 18423 return; 18424 } 18425 18426 if (el.readyState === 0) { 18427 // NetworkState is set synchronously BUT loadstart is fired at the 18428 // end of the current stack, usually before setInterval(fn, 0). 18429 // So at this point we know loadstart may have already fired or is 18430 // about to fire, and either way the player hasn't seen it yet. 18431 // We don't want to fire loadstart prematurely here and cause a 18432 // double loadstart so we'll wait and see if it happens between now 18433 // and the next loop, and fire it if not. 18434 // HOWEVER, we also want to make sure it fires before loadedmetadata 18435 // which could also happen between now and the next loop, so we'll 18436 // watch for that also. 18437 var loadstartFired = false; 18438 var setLoadstartFired = function setLoadstartFired() { 18439 loadstartFired = true; 18440 }; 18441 18442 this.on('loadstart', setLoadstartFired); 18443 18444 var triggerLoadstart = function triggerLoadstart() { 18445 // We did miss the original loadstart. Make sure the player 18446 // sees loadstart before loadedmetadata 18447 if (!loadstartFired) { 18448 this.trigger('loadstart'); 18449 } 18450 }; 18451 18452 this.on('loadedmetadata', triggerLoadstart); 18453 18454 this.ready(function () { 18455 this.off('loadstart', setLoadstartFired); 18456 this.off('loadedmetadata', triggerLoadstart); 18457 18458 if (!loadstartFired) { 18459 // We did miss the original native loadstart. Fire it now. 18460 this.trigger('loadstart'); 18461 } 18462 }); 18463 18464 return; 18465 } 18466 18467 // From here on we know that loadstart already fired and we missed it. 18468 // The other readyState events aren't as much of a problem if we double 18469 // them, so not going to go to as much trouble as loadstart to prevent 18470 // that unless we find reason to. 18471 var eventsToTrigger = ['loadstart']; 18472 18473 // loadedmetadata: newly equal to HAVE_METADATA (1) or greater 18474 eventsToTrigger.push('loadedmetadata'); 18475 18476 // loadeddata: newly increased to HAVE_CURRENT_DATA (2) or greater 18477 if (el.readyState >= 2) { 18478 eventsToTrigger.push('loadeddata'); 18479 } 18480 18481 // canplay: newly increased to HAVE_FUTURE_DATA (3) or greater 18482 if (el.readyState >= 3) { 18483 eventsToTrigger.push('canplay'); 18484 } 18485 18486 // canplaythrough: newly equal to HAVE_ENOUGH_DATA (4) 18487 if (el.readyState >= 4) { 18488 eventsToTrigger.push('canplaythrough'); 18489 } 18490 18491 // We still need to give the player time to add event listeners 18492 this.ready(function () { 18493 eventsToTrigger.forEach(function (type) { 18494 this.trigger(type); 18495 }, this); 18496 }); 18497 }; 18498 18499 /** 18500 * Set current time for the `HTML5` tech. 18501 * 18502 * @param {number} seconds 18503 * Set the current time of the media to this. 18504 */ 18505 18506 18507 Html5.prototype.setCurrentTime = function setCurrentTime(seconds) { 18508 try { 18509 this.el_.currentTime = seconds; 18510 } catch (e) { 18511 log$1(e, 'Video is not ready. (Video.js)'); 18512 // this.warning(VideoJS.warnings.videoNotReady); 18513 } 18514 }; 18515 18516 /** 18517 * Get the current duration of the HTML5 media element. 18518 * 18519 * @return {number} 18520 * The duration of the media or 0 if there is no duration. 18521 */ 18522 18523 18524 Html5.prototype.duration = function duration() { 18525 var _this3 = this; 18526 18527 // Android Chrome will report duration as Infinity for VOD HLS until after 18528 // playback has started, which triggers the live display erroneously. 18529 // Return NaN if playback has not started and trigger a durationupdate once 18530 // the duration can be reliably known. 18531 if (this.el_.duration === Infinity && IS_ANDROID && IS_CHROME && this.el_.currentTime === 0) { 18532 // Wait for the first `timeupdate` with currentTime > 0 - there may be 18533 // several with 0 18534 var checkProgress = function checkProgress() { 18535 if (_this3.el_.currentTime > 0) { 18536 // Trigger durationchange for genuinely live video 18537 if (_this3.el_.duration === Infinity) { 18538 _this3.trigger('durationchange'); 18539 } 18540 _this3.off('timeupdate', checkProgress); 18541 } 18542 }; 18543 18544 this.on('timeupdate', checkProgress); 18545 return NaN; 18546 } 18547 return this.el_.duration || NaN; 18548 }; 18549 18550 /** 18551 * Get the current width of the HTML5 media element. 18552 * 18553 * @return {number} 18554 * The width of the HTML5 media element. 18555 */ 18556 18557 18558 Html5.prototype.width = function width() { 18559 return this.el_.offsetWidth; 18560 }; 18561 18562 /** 18563 * Get the current height of the HTML5 media element. 18564 * 18565 * @return {number} 18566 * The heigth of the HTML5 media element. 18567 */ 18568 18569 18570 Html5.prototype.height = function height() { 18571 return this.el_.offsetHeight; 18572 }; 18573 18574 /** 18575 * Proxy iOS `webkitbeginfullscreen` and `webkitendfullscreen` into 18576 * `fullscreenchange` event. 18577 * 18578 * @private 18579 * @fires fullscreenchange 18580 * @listens webkitendfullscreen 18581 * @listens webkitbeginfullscreen 18582 * @listens webkitbeginfullscreen 18583 */ 18584 18585 18586 Html5.prototype.proxyWebkitFullscreen_ = function proxyWebkitFullscreen_() { 18587 var _this4 = this; 18588 18589 if (!('webkitDisplayingFullscreen' in this.el_)) { 18590 return; 18591 } 18592 18593 var endFn = function endFn() { 18594 this.trigger('fullscreenchange', { isFullscreen: false }); 18595 }; 18596 18597 var beginFn = function beginFn() { 18598 if ('webkitPresentationMode' in this.el_ && this.el_.webkitPresentationMode !== 'picture-in-picture') { 18599 this.one('webkitendfullscreen', endFn); 18600 18601 this.trigger('fullscreenchange', { isFullscreen: true }); 18602 } 18603 }; 18604 18605 this.on('webkitbeginfullscreen', beginFn); 18606 this.on('dispose', function () { 18607 _this4.off('webkitbeginfullscreen', beginFn); 18608 _this4.off('webkitendfullscreen', endFn); 18609 }); 18610 }; 18611 18612 /** 18613 * Check if fullscreen is supported on the current playback device. 18614 * 18615 * @return {boolean} 18616 * - True if fullscreen is supported. 18617 * - False if fullscreen is not supported. 18618 */ 18619 18620 18621 Html5.prototype.supportsFullScreen = function supportsFullScreen() { 18622 if (typeof this.el_.webkitEnterFullScreen === 'function') { 18623 var userAgent = window_1.navigator && window_1.navigator.userAgent || ''; 18624 18625 // Seems to be broken in Chromium/Chrome && Safari in Leopard 18626 if (/Android/.test(userAgent) || !/Chrome|Mac OS X 10.5/.test(userAgent)) { 18627 return true; 18628 } 18629 } 18630 return false; 18631 }; 18632 18633 /** 18634 * Request that the `HTML5` Tech enter fullscreen. 18635 */ 18636 18637 18638 Html5.prototype.enterFullScreen = function enterFullScreen() { 18639 var video = this.el_; 18640 18641 if (video.paused && video.networkState <= video.HAVE_METADATA) { 18642 // attempt to prime the video element for programmatic access 18643 // this isn't necessary on the desktop but shouldn't hurt 18644 this.el_.play(); 18645 18646 // playing and pausing synchronously during the transition to fullscreen 18647 // can get iOS ~6.1 devices into a play/pause loop 18648 this.setTimeout(function () { 18649 video.pause(); 18650 video.webkitEnterFullScreen(); 18651 }, 0); 18652 } else { 18653 video.webkitEnterFullScreen(); 18654 } 18655 }; 18656 18657 /** 18658 * Request that the `HTML5` Tech exit fullscreen. 18659 */ 18660 18661 18662 Html5.prototype.exitFullScreen = function exitFullScreen() { 18663 this.el_.webkitExitFullScreen(); 18664 }; 18665 18666 /** 18667 * A getter/setter for the `Html5` Tech's source object. 18668 * > Note: Please use {@link Html5#setSource} 18669 * 18670 * @param {Tech~SourceObject} [src] 18671 * The source object you want to set on the `HTML5` techs element. 18672 * 18673 * @return {Tech~SourceObject|undefined} 18674 * - The current source object when a source is not passed in. 18675 * - undefined when setting 18676 * 18677 * @deprecated Since version 5. 18678 */ 18679 18680 18681 Html5.prototype.src = function src(_src) { 18682 if (_src === undefined) { 18683 return this.el_.src; 18684 } 18685 18686 // Setting src through `src` instead of `setSrc` will be deprecated 18687 this.setSrc(_src); 18688 }; 18689 18690 /** 18691 * Reset the tech by removing all sources and then calling 18692 * {@link Html5.resetMediaElement}. 18693 */ 18694 18695 18696 Html5.prototype.reset = function reset() { 18697 Html5.resetMediaElement(this.el_); 18698 }; 18699 18700 /** 18701 * Get the current source on the `HTML5` Tech. Falls back to returning the source from 18702 * the HTML5 media element. 18703 * 18704 * @return {Tech~SourceObject} 18705 * The current source object from the HTML5 tech. With a fallback to the 18706 * elements source. 18707 */ 18708 18709 18710 Html5.prototype.currentSrc = function currentSrc() { 18711 if (this.currentSource_) { 18712 return this.currentSource_.src; 18713 } 18714 return this.el_.currentSrc; 18715 }; 18716 18717 /** 18718 * Set controls attribute for the HTML5 media Element. 18719 * 18720 * @param {string} val 18721 * Value to set the controls attribute to 18722 */ 18723 18724 18725 Html5.prototype.setControls = function setControls(val) { 18726 this.el_.controls = !!val; 18727 }; 18728 18729 /** 18730 * Create and returns a remote {@link TextTrack} object. 18731 * 18732 * @param {string} kind 18733 * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata) 18734 * 18735 * @param {string} [label] 18736 * Label to identify the text track 18737 * 18738 * @param {string} [language] 18739 * Two letter language abbreviation 18740 * 18741 * @return {TextTrack} 18742 * The TextTrack that gets created. 18743 */ 18744 18745 18746 Html5.prototype.addTextTrack = function addTextTrack(kind, label, language) { 18747 if (!this.featuresNativeTextTracks) { 18748 return _Tech.prototype.addTextTrack.call(this, kind, label, language); 18749 } 18750 18751 return this.el_.addTextTrack(kind, label, language); 18752 }; 18753 18754 /** 18755 * Creates either native TextTrack or an emulated TextTrack depending 18756 * on the value of `featuresNativeTextTracks` 18757 * 18758 * @param {Object} options 18759 * The object should contain the options to intialize the TextTrack with. 18760 * 18761 * @param {string} [options.kind] 18762 * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata). 18763 * 18764 * @param {string} [options.label]. 18765 * Label to identify the text track 18766 * 18767 * @param {string} [options.language] 18768 * Two letter language abbreviation. 18769 * 18770 * @param {boolean} [options.default] 18771 * Default this track to on. 18772 * 18773 * @param {string} [options.id] 18774 * The internal id to assign this track. 18775 * 18776 * @param {string} [options.src] 18777 * A source url for the track. 18778 * 18779 * @return {HTMLTrackElement} 18780 * The track element that gets created. 18781 */ 18782 18783 18784 Html5.prototype.createRemoteTextTrack = function createRemoteTextTrack(options) { 18785 if (!this.featuresNativeTextTracks) { 18786 return _Tech.prototype.createRemoteTextTrack.call(this, options); 18787 } 18788 var htmlTrackElement = document_1.createElement('track'); 18789 18790 if (options.kind) { 18791 htmlTrackElement.kind = options.kind; 18792 } 18793 if (options.label) { 18794 htmlTrackElement.label = options.label; 18795 } 18796 if (options.language || options.srclang) { 18797 htmlTrackElement.srclang = options.language || options.srclang; 18798 } 18799 if (options['default']) { 18800 htmlTrackElement['default'] = options['default']; 18801 } 18802 if (options.id) { 18803 htmlTrackElement.id = options.id; 18804 } 18805 if (options.src) { 18806 htmlTrackElement.src = options.src; 18807 } 18808 18809 return htmlTrackElement; 18810 }; 18811 18812 /** 18813 * Creates a remote text track object and returns an html track element. 18814 * 18815 * @param {Object} options The object should contain values for 18816 * kind, language, label, and src (location of the WebVTT file) 18817 * @param {Boolean} [manualCleanup=true] if set to false, the TextTrack will be 18818 * automatically removed from the video element whenever the source changes 18819 * @return {HTMLTrackElement} An Html Track Element. 18820 * This can be an emulated {@link HTMLTrackElement} or a native one. 18821 * @deprecated The default value of the "manualCleanup" parameter will default 18822 * to "false" in upcoming versions of Video.js 18823 */ 18824 18825 18826 Html5.prototype.addRemoteTextTrack = function addRemoteTextTrack(options, manualCleanup) { 18827 var htmlTrackElement = _Tech.prototype.addRemoteTextTrack.call(this, options, manualCleanup); 18828 18829 if (this.featuresNativeTextTracks) { 18830 this.el().appendChild(htmlTrackElement); 18831 } 18832 18833 return htmlTrackElement; 18834 }; 18835 18836 /** 18837 * Remove remote `TextTrack` from `TextTrackList` object 18838 * 18839 * @param {TextTrack} track 18840 * `TextTrack` object to remove 18841 */ 18842 18843 18844 Html5.prototype.removeRemoteTextTrack = function removeRemoteTextTrack(track) { 18845 _Tech.prototype.removeRemoteTextTrack.call(this, track); 18846 18847 if (this.featuresNativeTextTracks) { 18848 var tracks = this.$$('track'); 18849 18850 var i = tracks.length; 18851 18852 while (i--) { 18853 if (track === tracks[i] || track === tracks[i].track) { 18854 this.el().removeChild(tracks[i]); 18855 } 18856 } 18857 } 18858 }; 18859 18860 /** 18861 * Gets available media playback quality metrics as specified by the W3C's Media 18862 * Playback Quality API. 18863 * 18864 * @see [Spec]{@link https://wicg.github.io/media-playback-quality} 18865 * 18866 * @return {Object} 18867 * An object with supported media playback quality metrics 18868 */ 18869 18870 18871 Html5.prototype.getVideoPlaybackQuality = function getVideoPlaybackQuality() { 18872 if (typeof this.el().getVideoPlaybackQuality === 'function') { 18873 return this.el().getVideoPlaybackQuality(); 18874 } 18875 18876 var videoPlaybackQuality = {}; 18877 18878 if (typeof this.el().webkitDroppedFrameCount !== 'undefined' && typeof this.el().webkitDecodedFrameCount !== 'undefined') { 18879 videoPlaybackQuality.droppedVideoFrames = this.el().webkitDroppedFrameCount; 18880 videoPlaybackQuality.totalVideoFrames = this.el().webkitDecodedFrameCount; 18881 } 18882 18883 if (window_1.performance && typeof window_1.performance.now === 'function') { 18884 videoPlaybackQuality.creationTime = window_1.performance.now(); 18885 } else if (window_1.performance && window_1.performance.timing && typeof window_1.performance.timing.navigationStart === 'number') { 18886 videoPlaybackQuality.creationTime = window_1.Date.now() - window_1.performance.timing.navigationStart; 18887 } 18888 18889 return videoPlaybackQuality; 18890 }; 18891 18892 return Html5; 18893}(Tech); 18894 18895/* HTML5 Support Testing ---------------------------------------------------- */ 18896 18897if (isReal()) { 18898 18899 /** 18900 * Element for testing browser HTML5 media capabilities 18901 * 18902 * @type {Element} 18903 * @constant 18904 * @private 18905 */ 18906 Html5.TEST_VID = document_1.createElement('video'); 18907 var track = document_1.createElement('track'); 18908 18909 track.kind = 'captions'; 18910 track.srclang = 'en'; 18911 track.label = 'English'; 18912 Html5.TEST_VID.appendChild(track); 18913} 18914 18915/** 18916 * Check if HTML5 media is supported by this browser/device. 18917 * 18918 * @return {boolean} 18919 * - True if HTML5 media is supported. 18920 * - False if HTML5 media is not supported. 18921 */ 18922Html5.isSupported = function () { 18923 // IE9 with no Media Player is a LIAR! (#984) 18924 try { 18925 Html5.TEST_VID.volume = 0.5; 18926 } catch (e) { 18927 return false; 18928 } 18929 18930 return !!(Html5.TEST_VID && Html5.TEST_VID.canPlayType); 18931}; 18932 18933/** 18934 * Check if the tech can support the given type 18935 * 18936 * @param {string} type 18937 * The mimetype to check 18938 * @return {string} 'probably', 'maybe', or '' (empty string) 18939 */ 18940Html5.canPlayType = function (type) { 18941 return Html5.TEST_VID.canPlayType(type); 18942}; 18943 18944/** 18945 * Check if the tech can support the given source 18946 * @param {Object} srcObj 18947 * The source object 18948 * @param {Object} options 18949 * The options passed to the tech 18950 * @return {string} 'probably', 'maybe', or '' (empty string) 18951 */ 18952Html5.canPlaySource = function (srcObj, options) { 18953 return Html5.canPlayType(srcObj.type); 18954}; 18955 18956/** 18957 * Check if the volume can be changed in this browser/device. 18958 * Volume cannot be changed in a lot of mobile devices. 18959 * Specifically, it can't be changed from 1 on iOS. 18960 * 18961 * @return {boolean} 18962 * - True if volume can be controlled 18963 * - False otherwise 18964 */ 18965Html5.canControlVolume = function () { 18966 // IE will error if Windows Media Player not installed #3315 18967 try { 18968 var volume = Html5.TEST_VID.volume; 18969 18970 Html5.TEST_VID.volume = volume / 2 + 0.1; 18971 return volume !== Html5.TEST_VID.volume; 18972 } catch (e) { 18973 return false; 18974 } 18975}; 18976 18977/** 18978 * Check if the playback rate can be changed in this browser/device. 18979 * 18980 * @return {boolean} 18981 * - True if playback rate can be controlled 18982 * - False otherwise 18983 */ 18984Html5.canControlPlaybackRate = function () { 18985 // Playback rate API is implemented in Android Chrome, but doesn't do anything 18986 // https://github.com/videojs/video.js/issues/3180 18987 if (IS_ANDROID && IS_CHROME && CHROME_VERSION < 58) { 18988 return false; 18989 } 18990 // IE will error if Windows Media Player not installed #3315 18991 try { 18992 var playbackRate = Html5.TEST_VID.playbackRate; 18993 18994 Html5.TEST_VID.playbackRate = playbackRate / 2 + 0.1; 18995 return playbackRate !== Html5.TEST_VID.playbackRate; 18996 } catch (e) { 18997 return false; 18998 } 18999}; 19000 19001/** 19002 * Check to see if native `TextTrack`s are supported by this browser/device. 19003 * 19004 * @return {boolean} 19005 * - True if native `TextTrack`s are supported. 19006 * - False otherwise 19007 */ 19008Html5.supportsNativeTextTracks = function () { 19009 return IS_ANY_SAFARI; 19010}; 19011 19012/** 19013 * Check to see if native `VideoTrack`s are supported by this browser/device 19014 * 19015 * @return {boolean} 19016 * - True if native `VideoTrack`s are supported. 19017 * - False otherwise 19018 */ 19019Html5.supportsNativeVideoTracks = function () { 19020 return !!(Html5.TEST_VID && Html5.TEST_VID.videoTracks); 19021}; 19022 19023/** 19024 * Check to see if native `AudioTrack`s are supported by this browser/device 19025 * 19026 * @return {boolean} 19027 * - True if native `AudioTrack`s are supported. 19028 * - False otherwise 19029 */ 19030Html5.supportsNativeAudioTracks = function () { 19031 return !!(Html5.TEST_VID && Html5.TEST_VID.audioTracks); 19032}; 19033 19034/** 19035 * An array of events available on the Html5 tech. 19036 * 19037 * @private 19038 * @type {Array} 19039 */ 19040Html5.Events = ['loadstart', 'suspend', 'abort', 'error', 'emptied', 'stalled', 'loadedmetadata', 'loadeddata', 'canplay', 'canplaythrough', 'playing', 'waiting', 'seeking', 'seeked', 'ended', 'durationchange', 'timeupdate', 'progress', 'play', 'pause', 'ratechange', 'resize', 'volumechange']; 19041 19042/** 19043 * Boolean indicating whether the `Tech` supports volume control. 19044 * 19045 * @type {boolean} 19046 * @default {@link Html5.canControlVolume} 19047 */ 19048Html5.prototype.featuresVolumeControl = Html5.canControlVolume(); 19049 19050/** 19051 * Boolean indicating whether the `Tech` supports changing the speed at which the media 19052 * plays. Examples: 19053 * - Set player to play 2x (twice) as fast 19054 * - Set player to play 0.5x (half) as fast 19055 * 19056 * @type {boolean} 19057 * @default {@link Html5.canControlPlaybackRate} 19058 */ 19059Html5.prototype.featuresPlaybackRate = Html5.canControlPlaybackRate(); 19060 19061/** 19062 * Boolean indicating whether the `HTML5` tech currently supports the media element 19063 * moving in the DOM. iOS breaks if you move the media element, so this is set this to 19064 * false there. Everywhere else this should be true. 19065 * 19066 * @type {boolean} 19067 * @default 19068 */ 19069Html5.prototype.movingMediaElementInDOM = !IS_IOS; 19070 19071// TODO: Previous comment: No longer appears to be used. Can probably be removed. 19072// Is this true? 19073/** 19074 * Boolean indicating whether the `HTML5` tech currently supports automatic media resize 19075 * when going into fullscreen. 19076 * 19077 * @type {boolean} 19078 * @default 19079 */ 19080Html5.prototype.featuresFullscreenResize = true; 19081 19082/** 19083 * Boolean indicating whether the `HTML5` tech currently supports the progress event. 19084 * If this is false, manual `progress` events will be triggred instead. 19085 * 19086 * @type {boolean} 19087 * @default 19088 */ 19089Html5.prototype.featuresProgressEvents = true; 19090 19091/** 19092 * Boolean indicating whether the `HTML5` tech currently supports the timeupdate event. 19093 * If this is false, manual `timeupdate` events will be triggred instead. 19094 * 19095 * @default 19096 */ 19097Html5.prototype.featuresTimeupdateEvents = true; 19098 19099/** 19100 * Boolean indicating whether the `HTML5` tech currently supports native `TextTrack`s. 19101 * 19102 * @type {boolean} 19103 * @default {@link Html5.supportsNativeTextTracks} 19104 */ 19105Html5.prototype.featuresNativeTextTracks = Html5.supportsNativeTextTracks(); 19106 19107/** 19108 * Boolean indicating whether the `HTML5` tech currently supports native `VideoTrack`s. 19109 * 19110 * @type {boolean} 19111 * @default {@link Html5.supportsNativeVideoTracks} 19112 */ 19113Html5.prototype.featuresNativeVideoTracks = Html5.supportsNativeVideoTracks(); 19114 19115/** 19116 * Boolean indicating whether the `HTML5` tech currently supports native `AudioTrack`s. 19117 * 19118 * @type {boolean} 19119 * @default {@link Html5.supportsNativeAudioTracks} 19120 */ 19121Html5.prototype.featuresNativeAudioTracks = Html5.supportsNativeAudioTracks(); 19122 19123// HTML5 Feature detection and Device Fixes --------------------------------- // 19124var canPlayType = Html5.TEST_VID && Html5.TEST_VID.constructor.prototype.canPlayType; 19125var mpegurlRE = /^application\/(?:x-|vnd\.apple\.)mpegurl/i; 19126var mp4RE = /^video\/mp4/i; 19127 19128Html5.patchCanPlayType = function () { 19129 19130 // Android 4.0 and above can play HLS to some extent but it reports being unable to do so 19131 if (ANDROID_VERSION >= 4.0 && !IS_FIREFOX) { 19132 Html5.TEST_VID.constructor.prototype.canPlayType = function (type) { 19133 if (type && mpegurlRE.test(type)) { 19134 return 'maybe'; 19135 } 19136 return canPlayType.call(this, type); 19137 }; 19138 19139 // Override Android 2.2 and less canPlayType method which is broken 19140 } else if (IS_OLD_ANDROID) { 19141 Html5.TEST_VID.constructor.prototype.canPlayType = function (type) { 19142 if (type && mp4RE.test(type)) { 19143 return 'maybe'; 19144 } 19145 return canPlayType.call(this, type); 19146 }; 19147 } 19148}; 19149 19150Html5.unpatchCanPlayType = function () { 19151 var r = Html5.TEST_VID.constructor.prototype.canPlayType; 19152 19153 Html5.TEST_VID.constructor.prototype.canPlayType = canPlayType; 19154 return r; 19155}; 19156 19157// by default, patch the media element 19158Html5.patchCanPlayType(); 19159 19160Html5.disposeMediaElement = function (el) { 19161 if (!el) { 19162 return; 19163 } 19164 19165 if (el.parentNode) { 19166 el.parentNode.removeChild(el); 19167 } 19168 19169 // remove any child track or source nodes to prevent their loading 19170 while (el.hasChildNodes()) { 19171 el.removeChild(el.firstChild); 19172 } 19173 19174 // remove any src reference. not setting `src=''` because that causes a warning 19175 // in firefox 19176 el.removeAttribute('src'); 19177 19178 // force the media element to update its loading state by calling load() 19179 // however IE on Windows 7N has a bug that throws an error so need a try/catch (#793) 19180 if (typeof el.load === 'function') { 19181 // wrapping in an iife so it's not deoptimized (#1060#discussion_r10324473) 19182 (function () { 19183 try { 19184 el.load(); 19185 } catch (e) { 19186 // not supported 19187 } 19188 })(); 19189 } 19190}; 19191 19192Html5.resetMediaElement = function (el) { 19193 if (!el) { 19194 return; 19195 } 19196 19197 var sources = el.querySelectorAll('source'); 19198 var i = sources.length; 19199 19200 while (i--) { 19201 el.removeChild(sources[i]); 19202 } 19203 19204 // remove any src reference. 19205 // not setting `src=''` because that throws an error 19206 el.removeAttribute('src'); 19207 19208 if (typeof el.load === 'function') { 19209 // wrapping in an iife so it's not deoptimized (#1060#discussion_r10324473) 19210 (function () { 19211 try { 19212 el.load(); 19213 } catch (e) { 19214 // satisfy linter 19215 } 19216 })(); 19217 } 19218}; 19219 19220/* Native HTML5 element property wrapping ----------------------------------- */ 19221// Wrap native boolean attributes with getters that check both property and attribute 19222// The list is as followed: 19223// muted, defaultMuted, autoplay, controls, loop, playsinline 19224[ 19225/** 19226 * Get the value of `muted` from the media element. `muted` indicates 19227 * that the volume for the media should be set to silent. This does not actually change 19228 * the `volume` attribute. 19229 * 19230 * @method Html5#muted 19231 * @return {boolean} 19232 * - True if the value of `volume` should be ignored and the audio set to silent. 19233 * - False if the value of `volume` should be used. 19234 * 19235 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-muted} 19236 */ 19237'muted', 19238 19239/** 19240 * Get the value of `defaultMuted` from the media element. `defaultMuted` indicates 19241 * whether the media should start muted or not. Only changes the default state of the 19242 * media. `muted` and `defaultMuted` can have different values. {@link Html5#muted} indicates the 19243 * current state. 19244 * 19245 * @method Html5#defaultMuted 19246 * @return {boolean} 19247 * - The value of `defaultMuted` from the media element. 19248 * - True indicates that the media should start muted. 19249 * - False indicates that the media should not start muted 19250 * 19251 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultmuted} 19252 */ 19253'defaultMuted', 19254 19255/** 19256 * Get the value of `autoplay` from the media element. `autoplay` indicates 19257 * that the media should start to play as soon as the page is ready. 19258 * 19259 * @method Html5#autoplay 19260 * @return {boolean} 19261 * - The value of `autoplay` from the media element. 19262 * - True indicates that the media should start as soon as the page loads. 19263 * - False indicates that the media should not start as soon as the page loads. 19264 * 19265 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-autoplay} 19266 */ 19267'autoplay', 19268 19269/** 19270 * Get the value of `controls` from the media element. `controls` indicates 19271 * whether the native media controls should be shown or hidden. 19272 * 19273 * @method Html5#controls 19274 * @return {boolean} 19275 * - The value of `controls` from the media element. 19276 * - True indicates that native controls should be showing. 19277 * - False indicates that native controls should be hidden. 19278 * 19279 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-controls} 19280 */ 19281'controls', 19282 19283/** 19284 * Get the value of `loop` from the media element. `loop` indicates 19285 * that the media should return to the start of the media and continue playing once 19286 * it reaches the end. 19287 * 19288 * @method Html5#loop 19289 * @return {boolean} 19290 * - The value of `loop` from the media element. 19291 * - True indicates that playback should seek back to start once 19292 * the end of a media is reached. 19293 * - False indicates that playback should not loop back to the start when the 19294 * end of the media is reached. 19295 * 19296 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-loop} 19297 */ 19298'loop', 19299 19300/** 19301 * Get the value of `playsinline` from the media element. `playsinline` indicates 19302 * to the browser that non-fullscreen playback is preferred when fullscreen 19303 * playback is the native default, such as in iOS Safari. 19304 * 19305 * @method Html5#playsinline 19306 * @return {boolean} 19307 * - The value of `playsinline` from the media element. 19308 * - True indicates that the media should play inline. 19309 * - False indicates that the media should not play inline. 19310 * 19311 * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline} 19312 */ 19313'playsinline'].forEach(function (prop) { 19314 Html5.prototype[prop] = function () { 19315 return this.el_[prop] || this.el_.hasAttribute(prop); 19316 }; 19317}); 19318 19319// Wrap native boolean attributes with setters that set both property and attribute 19320// The list is as followed: 19321// setMuted, setDefaultMuted, setAutoplay, setLoop, setPlaysinline 19322// setControls is special-cased above 19323[ 19324/** 19325 * Set the value of `muted` on the media element. `muted` indicates that the current 19326 * audio level should be silent. 19327 * 19328 * @method Html5#setMuted 19329 * @param {boolean} muted 19330 * - True if the audio should be set to silent 19331 * - False otherwise 19332 * 19333 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-muted} 19334 */ 19335'muted', 19336 19337/** 19338 * Set the value of `defaultMuted` on the media element. `defaultMuted` indicates that the current 19339 * audio level should be silent, but will only effect the muted level on intial playback.. 19340 * 19341 * @method Html5.prototype.setDefaultMuted 19342 * @param {boolean} defaultMuted 19343 * - True if the audio should be set to silent 19344 * - False otherwise 19345 * 19346 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultmuted} 19347 */ 19348'defaultMuted', 19349 19350/** 19351 * Set the value of `autoplay` on the media element. `autoplay` indicates 19352 * that the media should start to play as soon as the page is ready. 19353 * 19354 * @method Html5#setAutoplay 19355 * @param {boolean} autoplay 19356 * - True indicates that the media should start as soon as the page loads. 19357 * - False indicates that the media should not start as soon as the page loads. 19358 * 19359 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-autoplay} 19360 */ 19361'autoplay', 19362 19363/** 19364 * Set the value of `loop` on the media element. `loop` indicates 19365 * that the media should return to the start of the media and continue playing once 19366 * it reaches the end. 19367 * 19368 * @method Html5#setLoop 19369 * @param {boolean} loop 19370 * - True indicates that playback should seek back to start once 19371 * the end of a media is reached. 19372 * - False indicates that playback should not loop back to the start when the 19373 * end of the media is reached. 19374 * 19375 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-loop} 19376 */ 19377'loop', 19378 19379/** 19380 * Set the value of `playsinline` from the media element. `playsinline` indicates 19381 * to the browser that non-fullscreen playback is preferred when fullscreen 19382 * playback is the native default, such as in iOS Safari. 19383 * 19384 * @method Html5#setPlaysinline 19385 * @param {boolean} playsinline 19386 * - True indicates that the media should play inline. 19387 * - False indicates that the media should not play inline. 19388 * 19389 * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline} 19390 */ 19391'playsinline'].forEach(function (prop) { 19392 Html5.prototype['set' + toTitleCase(prop)] = function (v) { 19393 this.el_[prop] = v; 19394 19395 if (v) { 19396 this.el_.setAttribute(prop, prop); 19397 } else { 19398 this.el_.removeAttribute(prop); 19399 } 19400 }; 19401}); 19402 19403// Wrap native properties with a getter 19404// The list is as followed 19405// paused, currentTime, buffered, volume, poster, preload, error, seeking 19406// seekable, ended, playbackRate, defaultPlaybackRate, played, networkState 19407// readyState, videoWidth, videoHeight 19408[ 19409/** 19410 * Get the value of `paused` from the media element. `paused` indicates whether the media element 19411 * is currently paused or not. 19412 * 19413 * @method Html5#paused 19414 * @return {boolean} 19415 * The value of `paused` from the media element. 19416 * 19417 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-paused} 19418 */ 19419'paused', 19420 19421/** 19422 * Get the value of `currentTime` from the media element. `currentTime` indicates 19423 * the current second that the media is at in playback. 19424 * 19425 * @method Html5#currentTime 19426 * @return {number} 19427 * The value of `currentTime` from the media element. 19428 * 19429 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-currenttime} 19430 */ 19431'currentTime', 19432 19433/** 19434 * Get the value of `buffered` from the media element. `buffered` is a `TimeRange` 19435 * object that represents the parts of the media that are already downloaded and 19436 * available for playback. 19437 * 19438 * @method Html5#buffered 19439 * @return {TimeRange} 19440 * The value of `buffered` from the media element. 19441 * 19442 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-buffered} 19443 */ 19444'buffered', 19445 19446/** 19447 * Get the value of `volume` from the media element. `volume` indicates 19448 * the current playback volume of audio for a media. `volume` will be a value from 0 19449 * (silent) to 1 (loudest and default). 19450 * 19451 * @method Html5#volume 19452 * @return {number} 19453 * The value of `volume` from the media element. Value will be between 0-1. 19454 * 19455 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-a-volume} 19456 */ 19457'volume', 19458 19459/** 19460 * Get the value of `poster` from the media element. `poster` indicates 19461 * that the url of an image file that can/will be shown when no media data is available. 19462 * 19463 * @method Html5#poster 19464 * @return {string} 19465 * The value of `poster` from the media element. Value will be a url to an 19466 * image. 19467 * 19468 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-video-poster} 19469 */ 19470'poster', 19471 19472/** 19473 * Get the value of `preload` from the media element. `preload` indicates 19474 * what should download before the media is interacted with. It can have the following 19475 * values: 19476 * - none: nothing should be downloaded 19477 * - metadata: poster and the first few frames of the media may be downloaded to get 19478 * media dimensions and other metadata 19479 * - auto: allow the media and metadata for the media to be downloaded before 19480 * interaction 19481 * 19482 * @method Html5#preload 19483 * @return {string} 19484 * The value of `preload` from the media element. Will be 'none', 'metadata', 19485 * or 'auto'. 19486 * 19487 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-preload} 19488 */ 19489'preload', 19490 19491/** 19492 * Get the value of the `error` from the media element. `error` indicates any 19493 * MediaError that may have occured during playback. If error returns null there is no 19494 * current error. 19495 * 19496 * @method Html5#error 19497 * @return {MediaError|null} 19498 * The value of `error` from the media element. Will be `MediaError` if there 19499 * is a current error and null otherwise. 19500 * 19501 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-error} 19502 */ 19503'error', 19504 19505/** 19506 * Get the value of `seeking` from the media element. `seeking` indicates whether the 19507 * media is currently seeking to a new position or not. 19508 * 19509 * @method Html5#seeking 19510 * @return {boolean} 19511 * - The value of `seeking` from the media element. 19512 * - True indicates that the media is currently seeking to a new position. 19513 * - Flase indicates that the media is not seeking to a new position at this time. 19514 * 19515 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-seeking} 19516 */ 19517'seeking', 19518 19519/** 19520 * Get the value of `seekable` from the media element. `seekable` returns a 19521 * `TimeRange` object indicating ranges of time that can currently be `seeked` to. 19522 * 19523 * @method Html5#seekable 19524 * @return {TimeRange} 19525 * The value of `seekable` from the media element. A `TimeRange` object 19526 * indicating the current ranges of time that can be seeked to. 19527 * 19528 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-seekable} 19529 */ 19530'seekable', 19531 19532/** 19533 * Get the value of `ended` from the media element. `ended` indicates whether 19534 * the media has reached the end or not. 19535 * 19536 * @method Html5#ended 19537 * @return {boolean} 19538 * - The value of `ended` from the media element. 19539 * - True indicates that the media has ended. 19540 * - False indicates that the media has not ended. 19541 * 19542 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-ended} 19543 */ 19544'ended', 19545 19546/** 19547 * Get the value of `playbackRate` from the media element. `playbackRate` indicates
19548 * the rate at which the media is currently playing back. Examples: 19549 * - if playbackRate is set to 2, media will play twice as fast. 19550 * - if playbackRate is set to 0.5, media will play half as fast. 19551 * 19552 * @method Html5#playbackRate 19553 * @return {number} 19554 * The value of `playbackRate` from the media element. A number indicating 19555 * the current playback speed of the media, where 1 is normal speed. 19556 * 19557 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate} 19558 */ 19559'playbackRate', 19560 19561/** 19562 * Get the value of `defaultPlaybackRate` from the media element. `defaultPlaybackRate` indicates 19563 * the rate at which the media is currently playing back. This value will not indicate the current 19564 * `playbackRate` after playback has started, use {@link Html5#playbackRate} for that. 19565 * 19566 * Examples: 19567 * - if defaultPlaybackRate is set to 2, media will play twice as fast. 19568 * - if defaultPlaybackRate is set to 0.5, media will play half as fast. 19569 * 19570 * @method Html5.prototype.defaultPlaybackRate 19571 * @return {number} 19572 * The value of `defaultPlaybackRate` from the media element. A number indicating 19573 * the current playback speed of the media, where 1 is normal speed. 19574 * 19575 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate} 19576 */ 19577'defaultPlaybackRate', 19578 19579/** 19580 * Get the value of `played` from the media element. `played` returns a `TimeRange` 19581 * object representing points in the media timeline that have been played. 19582 * 19583 * @method Html5#played 19584 * @return {TimeRange} 19585 * The value of `played` from the media element. A `TimeRange` object indicating 19586 * the ranges of time that have been played. 19587 * 19588 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-played} 19589 */ 19590'played', 19591 19592/** 19593 * Get the value of `networkState` from the media element. `networkState` indicates 19594 * the current network state. It returns an enumeration from the following list: 19595 * - 0: NETWORK_EMPTY 19596 * - 1: NEWORK_IDLE 19597 * - 2: NETWORK_LOADING 19598 * - 3: NETWORK_NO_SOURCE 19599 * 19600 * @method Html5#networkState 19601 * @return {number} 19602 * The value of `networkState` from the media element. This will be a number 19603 * from the list in the description. 19604 * 19605 * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-networkstate} 19606 */ 19607'networkState', 19608 19609/** 19610 * Get the value of `readyState` from the media element. `readyState` indicates 19611 * the current state of the media element. It returns an enumeration from the 19612 * following list: 19613 * - 0: HAVE_NOTHING 19614 * - 1: HAVE_METADATA 19615 * - 2: HAVE_CURRENT_DATA 19616 * - 3: HAVE_FUTURE_DATA 19617 * - 4: HAVE_ENOUGH_DATA 19618 * 19619 * @method Html5#readyState 19620 * @return {number} 19621 * The value of `readyState` from the media element. This will be a number 19622 * from the list in the description. 19623 * 19624 * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#ready-states} 19625 */ 19626'readyState', 19627 19628/** 19629 * Get the value of `videoWidth` from the video element. `videoWidth` indicates 19630 * the current width of the video in css pixels. 19631 * 19632 * @method Html5#videoWidth 19633 * @return {number} 19634 * The value of `videoWidth` from the video element. This will be a number 19635 * in css pixels. 19636 * 19637 * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-video-videowidth} 19638 */ 19639'videoWidth', 19640 19641/** 19642 * Get the value of `videoHeight` from the video element. `videoHeigth` indicates 19643 * the current height of the video in css pixels. 19644 * 19645 * @method Html5#videoHeight 19646 * @return {number} 19647 * The value of `videoHeight` from the video element. This will be a number 19648 * in css pixels. 19649 * 19650 * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-video-videowidth} 19651 */ 19652'videoHeight'].forEach(function (prop) { 19653 Html5.prototype[prop] = function () { 19654 return this.el_[prop]; 19655 }; 19656}); 19657 19658// Wrap native properties with a setter in this format: 19659// set + toTitleCase(name) 19660// The list is as follows: 19661// setVolume, setSrc, setPoster, setPreload, setPlaybackRate, setDefaultPlaybackRate 19662[ 19663/** 19664 * Set the value of `volume` on the media element. `volume` indicates the current 19665 * audio level as a percentage in decimal form. This means that 1 is 100%, 0.5 is 50%, and 19666 * so on. 19667 * 19668 * @method Html5#setVolume 19669 * @param {number} percentAsDecimal 19670 * The volume percent as a decimal. Valid range is from 0-1. 19671 * 19672 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-a-volume} 19673 */ 19674'volume', 19675 19676/** 19677 * Set the value of `src` on the media element. `src` indicates the current 19678 * {@link Tech~SourceObject} for the media. 19679 * 19680 * @method Html5#setSrc 19681 * @param {Tech~SourceObject} src 19682 * The source object to set as the current source. 19683 * 19684 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-src} 19685 */ 19686'src', 19687 19688/** 19689 * Set the value of `poster` on the media element. `poster` is the url to 19690 * an image file that can/will be shown when no media data is available. 19691 * 19692 * @method Html5#setPoster 19693 * @param {string} poster 19694 * The url to an image that should be used as the `poster` for the media 19695 * element. 19696 * 19697 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-poster} 19698 */ 19699'poster', 19700 19701/** 19702 * Set the value of `preload` on the media element. `preload` indicates 19703 * what should download before the media is interacted with. It can have the following 19704 * values: 19705 * - none: nothing should be downloaded 19706 * - metadata: poster and the first few frames of the media may be downloaded to get 19707 * media dimensions and other metadata 19708 * - auto: allow the media and metadata for the media to be downloaded before 19709 * interaction 19710 * 19711 * @method Html5#setPreload 19712 * @param {string} preload 19713 * The value of `preload` to set on the media element. Must be 'none', 'metadata', 19714 * or 'auto'. 19715 * 19716 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-preload} 19717 */ 19718'preload', 19719 19720/** 19721 * Set the value of `playbackRate` on the media element. `playbackRate` indicates 19722 * the rate at which the media should play back. Examples: 19723 * - if playbackRate is set to 2, media will play twice as fast. 19724 * - if playbackRate is set to 0.5, media will play half as fast. 19725 * 19726 * @method Html5#setPlaybackRate 19727 * @return {number} 19728 * The value of `playbackRate` from the media element. A number indicating 19729 * the current playback speed of the media, where 1 is normal speed. 19730 * 19731 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate} 19732 */ 19733'playbackRate', 19734 19735/** 19736 * Set the value of `defaultPlaybackRate` on the media element. `defaultPlaybackRate` indicates 19737 * the rate at which the media should play back upon initial startup. Changing this value 19738 * after a video has started will do nothing. Instead you should used {@link Html5#setPlaybackRate}. 19739 * 19740 * Example Values: 19741 * - if playbackRate is set to 2, media will play twice as fast. 19742 * - if playbackRate is set to 0.5, media will play half as fast. 19743 * 19744 * @method Html5.prototype.setDefaultPlaybackRate 19745 * @return {number} 19746 * The value of `defaultPlaybackRate` from the media element. A number indicating 19747 * the current playback speed of the media, where 1 is normal speed. 19748 * 19749 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultplaybackrate} 19750 */ 19751'defaultPlaybackRate'].forEach(function (prop) { 19752 Html5.prototype['set' + toTitleCase(prop)] = function (v) { 19753 this.el_[prop] = v; 19754 }; 19755}); 19756 19757// wrap native functions with a function 19758// The list is as follows: 19759// pause, load play 19760[ 19761/** 19762 * A wrapper around the media elements `pause` function. This will call the `HTML5` 19763 * media elements `pause` function. 19764 * 19765 * @method Html5#pause 19766 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-pause} 19767 */ 19768'pause', 19769 19770/** 19771 * A wrapper around the media elements `load` function. This will call the `HTML5`s 19772 * media element `load` function. 19773 * 19774 * @method Html5#load 19775 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-load} 19776 */ 19777'load', 19778 19779/** 19780 * A wrapper around the media elements `play` function. This will call the `HTML5`s 19781 * media element `play` function. 19782 * 19783 * @method Html5#play 19784 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-play} 19785 */ 19786'play'].forEach(function (prop) { 19787 Html5.prototype[prop] = function () { 19788 return this.el_[prop](); 19789 }; 19790}); 19791 19792Tech.withSourceHandlers(Html5); 19793 19794/** 19795 * Native source handler for Html5, simply passes the source to the media element. 19796 * 19797 * @proprety {Tech~SourceObject} source 19798 * The source object 19799 * 19800 * @proprety {Html5} tech 19801 * The instance of the HTML5 tech. 19802 */ 19803Html5.nativeSourceHandler = {}; 19804 19805/** 19806 * Check if the media element can play the given mime type. 19807 * 19808 * @param {string} type 19809 * The mimetype to check 19810 * 19811 * @return {string} 19812 * 'probably', 'maybe', or '' (empty string) 19813 */ 19814Html5.nativeSourceHandler.canPlayType = function (type) { 19815 // IE9 on Windows 7 without MediaPlayer throws an error here 19816 // https://github.com/videojs/video.js/issues/519 19817 try {
19818 return Html5.TEST_VID.canPlayType(type); 19819 } catch (e) { 19820 return ''; 19821 } 19822}; 19823 19824/** 19825 * Check if the media element can handle a source natively. 19826 * 19827 * @param {Tech~SourceObject} source 19828 * The source object 19829 * 19830 * @param {Object} [options] 19831 * Options to be passed to the tech. 19832 * 19833 * @return {string} 19834 * 'probably', 'maybe', or '' (empty string). 19835 */ 19836Html5.nativeSourceHandler.canHandleSource = function (source, options) { 19837 19838 // If a type was provided we should rely on that 19839 if (source.type) { 19840 return Html5.nativeSourceHandler.canPlayType(source.type); 19841 19842 // If no type, fall back to checking 'video/[EXTENSION]' 19843 } else if (source.src) { 19844 var ext = getFileExtension(source.src); 19845 19846 return Html5.nativeSourceHandler.canPlayType('video/' + ext); 19847 } 19848 19849 return ''; 19850}; 19851 19852/** 19853 * Pass the source to the native media element. 19854 * 19855 * @param {Tech~SourceObject} source 19856 * The source object 19857 * 19858 * @param {Html5} tech 19859 * The instance of the Html5 tech 19860 * 19861 * @param {Object} [options] 19862 * The options to pass to the source 19863 */ 19864Html5.nativeSourceHandler.handleSource = function (source, tech, options) { 19865 tech.setSrc(source.src); 19866}; 19867 19868/** 19869 * A noop for the native dispose function, as cleanup is not needed. 19870 */ 19871Html5.nativeSourceHandler.dispose = function () {}; 19872 19873// Register the native source handler 19874Html5.registerSourceHandler(Html5.nativeSourceHandler); 19875 19876Tech.registerTech('Html5', Html5); 19877 19878var _templateObject$1 = taggedTemplateLiteralLoose(['\n Using the tech directly can be dangerous. I hope you know
19878what 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 ']); 19879 19880/** 19881 * @file player.js 19882 */ 19883// Subclasses Component 19884// The following imports are used only to ensure that the corresponding modules 19885// are always included in the video.js package. Importing the modules will 19886// execute them and they will register themselves with video.js. 19887// Import Html5 tech, at least for disposing the original video tag. 19888// The following tech events are simply re-triggered 19889// on the player when they happen 19890var TECH_EVENTS_RETRIGGER = [ 19891/** 19892 * Fired while the user agent is downloading media data. 19893 * 19894 * @event Player#progress 19895 * @type {EventTarget~Event} 19896 */ 19897/** 19898 * Retrigger the `progress` event that was triggered by the {@link Tech}. 19899 * 19900 * @private 19901 * @method Player#handleTechProgress_ 19902 * @fires Player#progress 19903 * @listens Tech#progress 19904 */ 19905'progress', 19906 19907/** 19908 * Fires when the loading of an audio/video is aborted. 19909 * 19910 * @event Player#abort 19911 * @type {EventTarget~Event} 19912 */ 19913/** 19914 * Retrigger the `abort` event that was triggered by the {@link Tech}. 19915 * 19916 * @private 19917 * @method Player#handleTechAbort_ 19918 * @fires Player#abort 19919 * @listens Tech#abort 19920 */ 19921'abort', 19922 19923/** 19924 * Fires when the browser is intentionally not getting media data. 19925 * 19926 * @event Player#suspend 19927 * @type {EventTarget~Event} 19928 */ 19929/** 19930 * Retrigger the `suspend` event that was triggered by the {@link Tech}. 19931 * 19932 * @private 19933 * @method Player#handleTechSuspend_ 19934 * @fires Player#suspend 19935 * @listens Tech#suspend 19936 */ 19937'suspend', 19938 19939/** 19940 * Fires when the current playlist is empty. 19941 * 19942 * @event Player#emptied 19943 * @type {EventTarget~Event} 19944 */ 19945/** 19946 * Retrigger the `emptied` event that was triggered by the {@link Tech}. 19947 * 19948 * @private 19949 * @method Player#handleTechEmptied_ 19950 * @fires Player#emptied 19951 * @listens Tech#emptied 19952 */ 19953'emptied', 19954/** 19955 * Fires when the browser is trying to get media data, but data is not available. 19956 * 19957 * @event Player#stalled 19958 * @type {EventTarget~Event} 19959 */ 19960/** 19961 * Retrigger the `stalled` event that was triggered by the {@link Tech}. 19962 * 19963 * @private 19964 * @method Player#handleTechStalled_ 19965 * @fires Player#stalled 19966 * @listens Tech#stalled 19967 */ 19968'stalled', 19969 19970/** 19971 * Fires when the browser has loaded meta data for the audio/video. 19972 * 19973 * @event Player#loadedmetadata 19974 * @type {EventTarget~Event} 19975 */ 19976/** 19977 * Retrigger the `stalled` event that was triggered by the {@link Tech}. 19978 * 19979 * @private 19980 * @method Player#handleTechLoadedmetadata_ 19981 * @fires Player#loadedmetadata 19982 * @listens Tech#loadedmetadata 19983 */ 19984'loadedmetadata', 19985 19986/** 19987 * Fires when the browser has loaded the current frame of the audio/video. 19988 * 19989 * @event Player#loadeddata 19990 * @type {event} 19991 */ 19992/** 19993 * Retrigger the `loadeddata` event that was triggered by the {@link Tech}. 19994 * 19995 * @private 19996 * @method Player#handleTechLoaddeddata_ 19997 * @fires Player#loadeddata 19998 * @listens Tech#loadeddata 19999 */ 20000'loadeddata', 20001 20002/** 20003 * Fires when the current playback position has changed. 20004 * 20005 * @event Player#timeupdate 20006 * @type {event} 20007 */ 20008/** 20009 * Retrigger the `timeupdate` event that was triggered by the {@link Tech}. 20010 * 20011 * @private 20012 * @method Player#handleTechTimeUpdate_ 20013 * @fires Player#timeupdate 20014 * @listens Tech#timeupdate 20015 */ 20016'timeupdate', 20017 20018/** 20019 * Fires when the playing speed of the audio/video is changed 20020 * 20021 * @event Player#ratechange 20022 * @type {event} 20023 */ 20024/** 20025 * Retrigger the `ratechange` event that was triggered by the {@link Tech}. 20026 * 20027 * @private 20028 * @method Player#handleTechRatechange_ 20029 * @fires Player#ratechange 20030 * @listens Tech#ratechange 20031 */ 20032'ratechange', 20033 20034/** 20035 * Fires when the video's intrinsic dimensions change 20036 * 20037 * @event Player#resize 20038 * @type {event} 20039 */ 20040/** 20041 * Retrigger the `resize` event that was triggered by the {@link Tech}. 20042 * 20043 * @private 20044 * @method Player#handleTechResize_ 20045 * @fires Player#resize 20046 * @listens Tech#resize 20047 */ 20048'resize', 20049 20050/** 20051 * Fires when the volume has been changed 20052 * 20053 * @event Player#volumechange 20054 * @type {event} 20055 */ 20056/** 20057 * Retrigger the `volumechange` event that was triggered by the {@link Tech}. 20058 * 20059 * @private 20060 * @method Player#handleTechVolumechange_ 20061 * @fires Player#volumechange 20062 * @listens Tech#volumechange 20063 */ 20064'volumechange', 20065 20066/** 20067 * Fires when the text track has been changed 20068 * 20069 * @event Player#texttrackchange 20070 * @type {event} 20071 */ 20072/** 20073 * Retrigger the `texttrackchange` event that was triggered by the {@link Tech}. 20074 * 20075 * @private 20076 * @method Player#handleTechTexttrackchange_ 20077 * @fires Player#texttrackchange 20078 * @listens Tech#texttrackchange 20079 */ 20080'texttrackchange']; 20081 20082/** 20083 * An instance of the `Player` class is created when any of the Video.js setup methods 20084 * are used to initialize a video. 20085 * 20086 * After an instance has been created it can be accessed globally in two ways: 20087 * 1. By calling `videojs('example_video_1');` 20088 * 2. By using it directly via `videojs.players.example_video_1;` 20089 * 20090 * @extends Component 20091 */ 20092 20093var Player = function (_Component) { 20094 inherits(Player, _Component); 20095 20096 /** 20097 * Create an instance of this class. 20098 * 20099 * @param {Element} tag
vendor: 22,814 bytes, lines 20100-20820
20100 * The original video DOM element used for configuring options. 20101 * 20102 * @param {Object} [options] 20103 * Object of option names and values. 20104 * 20105 * @param {Component~ReadyCallback} [ready] 20106 * Ready callback function. 20107 */ 20108 function Player(tag, options, ready) { 20109 classCallCheck(this, Player); 20110 20111 // Make sure tag ID exists 20112 tag.id = tag.id || 'vjs_video_' + newGUID(); 20113 20114 // Set Options 20115 // The options argument overrides options set in the video tag 20116 // which overrides globally set options. 20117 // This latter part coincides with the load order 20118 // (tag must exist before Player) 20119 options = assign(Player.getTagSettings(tag), options); 20120 20121 // Delay the initialization of children because we need to set up 20122 // player properties first, and can't use `this` before `super()` 20123 options.initChildren = false; 20124 20125 // Same with creating the element 20126 options.createEl = false; 20127 20128 // we don't want the player to report touch activity on itself 20129 // see enableTouchActivity in Component 20130 options.reportTouchActivity = false; 20131 20132 // If language is not set, get the closest lang attribute 20133 if (!options.language) { 20134 if (typeof tag.closest === 'function') { 20135 var closest = tag.closest('[lang]'); 20136 20137 if (closest) { 20138 options.language = closest.getAttribute('lang'); 20139 } 20140 } else { 20141 var element = tag; 20142 20143 while (element && element.nodeType === 1) { 20144 if (getAttributes(element).hasOwnProperty('lang')) { 20145 options.language = element.getAttribute('lang'); 20146 break; 20147 } 20148 element = element.parentNode; 20149 } 20150 } 20151 } 20152 20153 // Run base component initializing with new options 20154 20155 // Turn off API access because we're loading a new tech that might load asynchronously 20156 var _this = possibleConstructorReturn(this, _Component.call(this, null, options, ready)); 20157 20158 _this.isReady_ = false; 20159 20160 // if the global option object was accidentally blown away by 20161 // someone, bail early with an informative error 20162 if (!_this.options_ || !_this.options_.techOrder || !_this.options_.techOrder.length) { 20163 throw new Error('No techOrder specified. Did you overwrite ' + 'videojs.options instead of just changing the ' + 'properties you want to override?'); 20164 } 20165 20166 // Store the original tag used to set options 20167 _this.tag = tag; 20168 20169 // Store the tag attributes used to restore html5 element 20170 _this.tagAttributes = tag && getAttributes(tag); 20171 20172 // Update current language 20173 _this.language(_this.options_.language); 20174 20175 // Update Supported Languages 20176 if (options.languages) { 20177 // Normalise player option languages to lowercase 20178 var languagesToLower = {}; 20179 20180 Object.getOwnPropertyNames(options.languages).forEach(function (name$$1) { 20181 languagesToLower[name$$1.toLowerCase()] = options.languages[name$$1]; 20182 }); 20183 _this.languages_ = languagesToLower; 20184 } else { 20185 _this.languages_ = Player.prototype.options_.languages; 20186 } 20187 20188 // Cache for video property values. 20189 _this.cache_ = {}; 20190 20191 // Set poster 20192 _this.poster_ = options.poster || ''; 20193 20194 // Set controls 20195 _this.controls_ = !!options.controls; 20196 20197 // Set default values for lastVolume 20198 _this.cache_.lastVolume = 1; 20199 20200 // Original tag settings stored in options 20201 // now remove immediately so native controls don't flash. 20202 // May be turned back on by HTML5 tech if nativeControlsForTouch is true 20203 tag.controls = false; 20204 20205 /* 20206 * Store the internal state of scrubbing 20207 * 20208 * @private 20209 * @return {Boolean} True if the user is scrubbing 20210 */ 20211 _this.scrubbing_ = false; 20212 20213 _this.el_ = _this.createEl(); 20214 20215 // Make this an evented object and use `el_` as its event bus. 20216 evented(_this, { eventBusKey: 'el_' }); 20217 20218 // We also want to pass the original player options to each component and plugin 20219 // as well so they don't need to reach back into the player for options later. 20220 // We also need to do another copy of this.options_ so we don't end up with 20221 // an infinite loop. 20222 var playerOptionsCopy = mergeOptions(_this.options_); 20223 20224 // Load plugins 20225 if (options.plugins) { 20226 var plugins = options.plugins; 20227 20228 Object.keys(plugins).forEach(function (name$$1) { 20229 if (typeof this[name$$1] === 'function') { 20230 this[name$$1](plugins[name$$1]); 20231 } else { 20232 throw new Error('plugin "' + name$$1 + '" does not exist'); 20233 } 20234 }, _this); 20235 } 20236 20237 _this.options_.playerOptions = playerOptionsCopy; 20238 20239 _this.middleware_ = []; 20240 20241 _this.initChildren(); 20242 20243 // Set isAudio based on whether or not an audio tag was used 20244 _this.isAudio(tag.nodeName.toLowerCase() === 'audio'); 20245 20246 // Update controls className. Can't do this when the controls are initially 20247 // set because the element doesn't exist yet. 20248 if (_this.controls()) { 20249 _this.addClass('vjs-controls-enabled'); 20250 } else { 20251 _this.addClass('vjs-controls-disabled'); 20252 } 20253 20254 // Set ARIA label and region role depending on player type 20255 _this.el_.setAttribute('role', 'region'); 20256 if (_this.isAudio()) { 20257 _this.el_.setAttribute('aria-label', _this.localize('Audio Player')); 20258 } else { 20259 _this.el_.setAttribute('aria-label', _this.localize('Video Player')); 20260 } 20261 20262 if (_this.isAudio()) { 20263 _this.addClass('vjs-audio'); 20264 } 20265 20266 if (_this.flexNotSupported_()) { 20267 _this.addClass('vjs-no-flex'); 20268 } 20269 20270 // TODO: Make this smarter. Toggle user state between touching/mousing 20271 // using events, since devices can have both touch and mouse events. 20272 // if (browser.TOUCH_ENABLED) { 20273 // this.addClass('vjs-touch-enabled'); 20274 // } 20275 20276 // iOS Safari has broken hover handling 20277 if (!IS_IOS) { 20278 _this.addClass('vjs-workinghover'); 20279 } 20280 20281 // Make player easily findable by ID 20282 Player.players[_this.id_] = _this; 20283 20284 // Add a major version class to aid css in plugins 20285 var majorVersion = version.split('.')[0]; 20286 20287 _this.addClass('vjs-v' + majorVersion); 20288 20289 // When the player is first initialized, trigger activity so components 20290 // like the control bar show themselves if needed 20291 _this.userActive(true); 20292 _this.reportUserActivity(); 20293 _this.listenForUserActivity_(); 20294 20295 _this.on('fullscreenchange', _this.handleFullscreenChange_); 20296 _this.on('stageclick', _this.handleStageClick_); 20297 20298 _this.changingSrc_ = false; 20299 return _this; 20300 } 20301 20302 /** 20303 * Destroys the video player and does any necessary cleanup. 20304 * 20305 * This is especially helpful if you are dynamically adding and removing videos 20306 * to/from the DOM. 20307 * 20308 * @fires Player#dispose 20309 */ 20310 20311 20312 Player.prototype.dispose = function dispose() { 20313 /** 20314 * Called when the player is being disposed of. 20315 * 20316 * @event Player#dispose 20317 * @type {EventTarget~Event} 20318 */ 20319 this.trigger('dispose'); 20320 // prevent dispose from being called twice 20321 this.off('dispose'); 20322 20323 if (this.styleEl_ && this.styleEl_.parentNode) { 20324 this.styleEl_.parentNode.removeChild(this.styleEl_); 20325 } 20326 20327 // Kill reference to this player 20328 Player.players[this.id_] = null; 20329 20330 if (this.tag && this.tag.player) { 20331 this.tag.player = null; 20332 } 20333 20334 if (this.el_ && this.el_.player) { 20335 this.el_.player = null; 20336 } 20337 20338 if (this.tech_) { 20339 this.tech_.dispose(); 20340 } 20341 20342 _Component.prototype.dispose.call(this); 20343 }; 20344 20345 /** 20346 * Create the `Player`'s DOM element. 20347 * 20348 * @return {Element} 20349 * The DOM element that gets created. 20350 */ 20351 20352 20353 Player.prototype.createEl = function createEl$$1() { 20354 var tag = this.tag; 20355 var el = void 0; 20356 var playerElIngest = this.playerElIngest_ = tag.parentNode && tag.parentNode.hasAttribute && tag.parentNode.hasAttribute('data-vjs-player'); 20357 20358 if (playerElIngest) { 20359 el = this.el_ = tag.parentNode; 20360 } else { 20361 el = this.el_ = _Component.prototype.createEl.call(this, 'div'); 20362 } 20363 20364 // set tabindex to -1 so we could focus on the player element 20365 tag.setAttribute('tabindex', '-1'); 20366 20367 // Remove width/height attrs from tag so CSS can make it 100% width/height 20368 tag.removeAttribute('width'); 20369 tag.removeAttribute('height'); 20370 20371 // Copy over all the attributes from the tag, including ID and class 20372 // ID will now reference player box, not the video tag 20373 var attrs = getAttributes(tag); 20374 20375 Object.getOwnPropertyNames(attrs).forEach(function (attr) { 20376 // workaround so we don't totally break IE7 20377 // http://stackoverflow.com/questions/3653444/css-styles-not-applied-on-dynamic-elements-in-internet-explorer-7 20378 if (attr === 'class') { 20379 el.className += ' ' + attrs[attr]; 20380 } else { 20381 el.setAttribute(attr, attrs[attr]); 20382 } 20383 }); 20384 20385 // Update tag id/class for use as HTML5 playback tech 20386 // Might think we should do this after embedding in container so .vjs-tech class 20387 // doesn't flash 100% width/height, but class only applies with .video-js parent 20388 tag.playerId = tag.id; 20389 tag.id += '_html5_api'; 20390 tag.className = 'vjs-tech'; 20391 20392 // Make player findable on elements 20393 tag.player = el.player = this; 20394 // Default state of video is paused 20395 this.addClass('vjs-paused'); 20396 20397 // Add a style element in the player that we'll use to set the width/height 20398 // of the player in a way that's still overrideable by CSS, just like the 20399 // video element 20400 if (window_1.VIDEOJS_NO_DYNAMIC_STYLE !== true) { 20401 this.styleEl_ = createStyleElement('vjs-styles-dimensions'); 20402 var defaultsStyleEl = $('.vjs-styles-defaults'); 20403 var head = $('head'); 20404 20405 head.insertBefore(this.styleEl_, defaultsStyleEl ? defaultsStyleEl.nextSibling : head.firstChild); 20406 } 20407 20408 // Pass in the width/height/aspectRatio options which will update the style el 20409 this.width(this.options_.width); 20410 this.height(this.options_.height); 20411 this.fluid(this.options_.fluid); 20412 this.aspectRatio(this.options_.aspectRatio); 20413 20414 // Hide any links within the video/audio tag, because IE doesn't hide them completely. 20415 var links = tag.getElementsByTagName('a'); 20416 20417 for (var i = 0; i < links.length; i++) { 20418 var linkEl = links.item(i); 20419 20420 addClass(linkEl, 'vjs-hidden'); 20421 linkEl.setAttribute('hidden', 'hidden'); 20422 } 20423 20424 // insertElFirst seems to cause the networkState to flicker from 3 to 2, so 20425 // keep track of the original for later so we can know if the source originally failed 20426 tag.initNetworkState_ = tag.networkState; 20427 20428 // Wrap video tag in div (el/box) container 20429 if (tag.parentNode && !playerElIngest) { 20430 tag.parentNode.insertBefore(el, tag); 20431 } 20432 20433 // insert the tag as the first child of the player element 20434 // then manually add it to the children array so that this.addChild 20435 // will work properly for other components 20436 // 20437 // Breaks iPhone, fixed in HTML5 setup. 20438 prependTo(tag, el); 20439 this.children_.unshift(tag); 20440 20441 // Set lang attr on player to ensure CSS :lang() in consistent with player 20442 // if it's been set to something different to the doc 20443 this.el_.setAttribute('lang', this.language_); 20444 20445 this.el_ = el; 20446 20447 return el; 20448 }; 20449 20450 /** 20451 * A getter/setter for the `Player`'s width. Returns the player's configured value. 20452 * To get the current width use `currentWidth()`. 20453 * 20454 * @param {number} [value] 20455 * The value to set the `Player`'s width to. 20456 * 20457 * @return {number} 20458 * The current width of the `Player` when getting. 20459 */ 20460 20461 20462 Player.prototype.width = function width(value) { 20463 return this.dimension('width', value); 20464 }; 20465 20466 /** 20467 * A getter/setter for the `Player`'s height. Returns the player's configured value. 20468 * To get the current height use `currentheight()`. 20469 * 20470 * @param {number} [value] 20471 * The value to set the `Player`'s heigth to. 20472 * 20473 * @return {number} 20474 * The current height of the `Player` when getting. 20475 */ 20476 20477 20478 Player.prototype.height = function height(value) { 20479 return this.dimension('height', value); 20480 }; 20481 20482 /** 20483 * A getter/setter for the `Player`'s width & height. 20484 * 20485 * @param {string} dimension 20486 * This string can be: 20487 * - 'width' 20488 * - 'height' 20489 * 20490 * @param {number} [value] 20491 * Value for dimension specified in the first argument. 20492 * 20493 * @return {number} 20494 * The dimension arguments value when getting (width/height). 20495 */ 20496 20497 20498 Player.prototype.dimension = function dimension(_dimension, value) { 20499 var privDimension = _dimension + '_'; 20500 20501 if (value === undefined) { 20502 return this[privDimension] || 0; 20503 } 20504 20505 if (value === '') { 20506 // If an empty string is given, reset the dimension to be automatic 20507 this[privDimension] = undefined; 20508 } else { 20509 var parsedVal = parseFloat(value); 20510 20511 if (isNaN(parsedVal)) { 20512 log$1.error('Improper value "' + value + '" supplied for for ' + _dimension); 20513 return; 20514 } 20515 20516 this[privDimension] = parsedVal; 20517 } 20518 20519 this.updateStyleEl_(); 20520 }; 20521 20522 /** 20523 * A getter/setter/toggler for the vjs-fluid `className` on the `Player`. 20524 * 20525 * @param {boolean} [bool] 20526 * - A value of true adds the class. 20527 * - A value of false removes the class. 20528 * - No value will toggle the fluid class. 20529 * 20530 * @return {boolean|undefined} 20531 * - The value of fluid when getting. 20532 * - `undefined` when setting. 20533 */ 20534 20535 20536 Player.prototype.fluid = function fluid(bool) { 20537 if (bool === undefined) { 20538 return !!this.fluid_; 20539 } 20540 20541 this.fluid_ = !!bool; 20542 20543 if (bool) { 20544 this.addClass('vjs-fluid'); 20545 } else { 20546 this.removeClass('vjs-fluid'); 20547 } 20548 20549 this.updateStyleEl_(); 20550 }; 20551 20552 /** 20553 * Get/Set the aspect ratio 20554 * 20555 * @param {string} [ratio] 20556 * Aspect ratio for player 20557 * 20558 * @return {string|undefined} 20559 * returns the current aspect ratio when getting 20560 */ 20561 20562 /** 20563 * A getter/setter for the `Player`'s aspect ratio. 20564 * 20565 * @param {string} [ratio] 20566 * The value to set the `Player's aspect ratio to. 20567 * 20568 * @return {string|undefined} 20569 * - The current aspect ratio of the `Player` when getting. 20570 * - undefined when setting 20571 */ 20572 20573 20574 Player.prototype.aspectRatio = function aspectRatio(ratio) { 20575 if (ratio === undefined) { 20576 return this.aspectRatio_; 20577 } 20578 20579 // Check for width:height format 20580 if (!/^\d+\:\d+$/.test(ratio)) { 20581 throw new Error('Improper value supplied for aspect ratio. The format should be width:height, for example 16:9.'); 20582 } 20583 this.aspectRatio_ = ratio; 20584 20585 // We're assuming if you set an aspect ratio you want fluid mode, 20586 // because in fixed mode you could calculate width and height yourself. 20587 this.fluid(true); 20588 20589 this.updateStyleEl_(); 20590 }; 20591 20592 /** 20593 * Update styles of the `Player` element (height, width and aspect ratio). 20594 * 20595 * @private 20596 * @listens Tech#loadedmetadata 20597 */ 20598 20599 20600 Player.prototype.updateStyleEl_ = function updateStyleEl_() { 20601 if (window_1.VIDEOJS_NO_DYNAMIC_STYLE === true) { 20602 var _width = typeof this.width_ === 'number' ? this.width_ : this.options_.width; 20603 var _height = typeof this.height_ === 'number' ? this.height_ : this.options_.height; 20604 var techEl = this.tech_ && this.tech_.el(); 20605 20606 if (techEl) { 20607 if (_width >= 0) { 20608 techEl.width = _width; 20609 } 20610 if (_height >= 0) { 20611 techEl.height = _height; 20612 } 20613 } 20614 20615 return; 20616 } 20617 20618 var width = void 0; 20619 var height = void 0; 20620 var aspectRatio = void 0; 20621 var idClass = void 0; 20622 20623 // The aspect ratio is either used directly or to calculate width and height. 20624 if (this.aspectRatio_ !== undefined && this.aspectRatio_ !== 'auto') { 20625 // Use any aspectRatio that's been specifically set 20626 aspectRatio = this.aspectRatio_; 20627 } else if (this.videoWidth() > 0) { 20628 // Otherwise try to get the aspect ratio from the video metadata 20629 aspectRatio = this.videoWidth() + ':' + this.videoHeight(); 20630 } else { 20631 // Or use a default. The video element's is 2:1, but 16:9 is more common. 20632 aspectRatio = '16:9'; 20633 } 20634 20635 // Get the ratio as a decimal we can use to calculate dimensions 20636 var ratioParts = aspectRatio.split(':'); 20637 var ratioMultiplier = ratioParts[1] / ratioParts[0]; 20638 20639 if (this.width_ !== undefined) { 20640 // Use any width that's been specifically set 20641 width = this.width_; 20642 } else if (this.height_ !== undefined) { 20643 // Or calulate the width from the aspect ratio if a height has been set 20644 width = this.height_ / ratioMultiplier; 20645 } else { 20646 // Or use the video's metadata, or use the video el's default of 300 20647 width = this.videoWidth() || 300; 20648 } 20649 20650 if (this.height_ !== undefined) { 20651 // Use any height that's been specifically set 20652 height = this.height_; 20653 } else { 20654 // Otherwise calculate the height from the ratio and the width 20655 height = width * ratioMultiplier; 20656 } 20657 20658 // Ensure the CSS class is valid by starting with an alpha character 20659 if (/^[^a-zA-Z]/.test(this.id())) { 20660 idClass = 'dimensions-' + this.id(); 20661 } else { 20662 idClass = this.id() + '-dimensions'; 20663 } 20664 20665 // Ensure the right class is still on the player for the style element 20666 this.addClass(idClass); 20667 20668 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 '); 20669 }; 20670 20671 /** 20672 * Load/Create an instance of playback {@link Tech} including element 20673 * and API methods. Then append the `Tech` element in `Player` as a child. 20674 * 20675 * @param {string} techName 20676 * name of the playback technology 20677 * 20678 * @param {string} source 20679 * video source 20680 * 20681 * @private 20682 */ 20683 20684 20685 Player.prototype.loadTech_ = function loadTech_(techName, source) { 20686 var _this2 = this; 20687 20688 // Pause and remove current playback technology 20689 if (this.tech_) { 20690 this.unloadTech_(); 20691 } 20692 20693 var titleTechName = toTitleCase(techName); 20694 var camelTechName = techName.charAt(0).toLowerCase() + techName.slice(1); 20695 20696 // get rid of the HTML5 video tag as soon as we are using another tech 20697 if (titleTechName !== 'Html5' && this.tag) { 20698 Tech.getTech('Html5').disposeMediaElement(this.tag); 20699 this.tag.player = null; 20700 this.tag = null; 20701 } 20702 20703 this.techName_ = titleTechName; 20704 20705 // Turn off API access because we're loading a new tech that might load asynchronously 20706 this.isReady_ = false; 20707 20708 // Grab tech-specific options from player options and add source and parent element to use. 20709 var techOptions = { 20710 source: source, 20711 'nativeControlsForTouch': this.options_.nativeControlsForTouch, 20712 'playerId': this.id(), 20713 'techId': this.id() + '_' + titleTechName + '_api', 20714 'autoplay': this.options_.autoplay, 20715 'playsinline': this.options_.playsinline, 20716 'preload': this.options_.preload, 20717 'loop': this.options_.loop, 20718 'muted': this.options_.muted, 20719 'poster': this.poster(), 20720 'language': this.language(), 20721 'playerElIngest': this.playerElIngest_ || false, 20722 'vtt.js': this.options_['vtt.js'] 20723 }; 20724 20725 ALL.names.forEach(function (name$$1) { 20726 var props = ALL[name$$1]; 20727 20728 techOptions[props.getterName] = _this2[props.privateName]; 20729 }); 20730 20731 assign(techOptions, this.options_[titleTechName]); 20732 assign(techOptions, this.options_[camelTechName]); 20733 assign(techOptions, this.options_[techName.toLowerCase()]); 20734 20735 if (this.tag) { 20736 techOptions.tag = this.tag; 20737 } 20738 20739 if (source && source.src === this.cache_.src && this.cache_.currentTime > 0) { 20740 techOptions.startTime = this.cache_.currentTime; 20741 } 20742 20743 // Initialize tech instance 20744 var TechClass = Tech.getTech(techName); 20745 20746 if (!TechClass) { 20747 throw new Error('No Tech named \'' + titleTechName + '\' exists! \'' + titleTechName + '\' should be registered using videojs.registerTech()\''); 20748 } 20749 20750 this.tech_ = new TechClass(techOptions); 20751 20752 // player.triggerReady is always async, so don't need this to be async 20753 this.tech_.ready(bind(this, this.handleTechReady_), true); 20754 20755 textTrackConverter.jsonToTextTracks(this.textTracksJson_ || [], this.tech_); 20756 20757 // Listen to all HTML5-defined events and trigger them on the player 20758 TECH_EVENTS_RETRIGGER.forEach(function (event) { 20759 _this2.on(_this2.tech_, event, _this2['handleTech' + toTitleCase(event) + '_']); 20760 }); 20761 this.on(this.tech_, 'loadstart', this.handleTechLoadStart_); 20762 this.on(this.tech_, 'waiting', this.handleTechWaiting_); 20763 this.on(this.tech_, 'canplay', this.handleTechCanPlay_); 20764 this.on(this.tech_, 'canplaythrough', this.handleTechCanPlayThrough_); 20765 this.on(this.tech_, 'playing', this.handleTechPlaying_); 20766 this.on(this.tech_, 'ended', this.handleTechEnded_); 20767 this.on(this.tech_, 'seeking', this.handleTechSeeking_); 20768 this.on(this.tech_, 'seeked', this.handleTechSeeked_); 20769 this.on(this.tech_, 'play', this.handleTechPlay_); 20770 this.on(this.tech_, 'firstplay', this.handleTechFirstPlay_); 20771 this.on(this.tech_, 'pause', this.handleTechPause_); 20772 this.on(this.tech_, 'durationchange', this.handleTechDurationChange_); 20773 this.on(this.tech_, 'fullscreenchange', this.handleTechFullscreenChange_); 20774 this.on(this.tech_, 'error', this.handleTechError_); 20775 this.on(this.tech_, 'loadedmetadata', this.updateStyleEl_); 20776 this.on(this.tech_, 'posterchange', this.handleTechPosterChange_); 20777 this.on(this.tech_, 'textdata', this.handleTechTextData_); 20778 20779 this.usingNativeControls(this.techGet_('controls')); 20780 20781 if (this.controls() && !this.usingNativeControls()) { 20782 this.addTechControlsListeners_(); 20783 } 20784 20785 // Add the tech element in the DOM if it was not already there 20786 // Make sure to not insert the original video element if using Html5 20787 if (this.tech_.el().parentNode !== this.el() && (titleTechName !== 'Html5' || !this.tag)) { 20788 prependTo(this.tech_.el(), this.el()); 20789 } 20790 20791 // Get rid of the original video tag reference after the first tech is loaded 20792 if (this.tag) { 20793 this.tag.player = null; 20794 this.tag = null; 20795 } 20796 }; 20797 20798 /** 20799 * Unload and dispose of the current playback {@link Tech}. 20800 * 20801 * @private 20802 */ 20803 20804 20805 Player.prototype.unloadTech_ = function unloadTech_() { 20806 var _this3 = this; 20807 20808 // Save the current text tracks so that we can reuse the same text tracks with the next tech 20809 ALL.names.forEach(function (name$$1) { 20810 var props = ALL[name$$1]; 20811 20812 _this3[props.privateName] = _this3[props.getterName](); 20813 }); 20814 this.textTracksJson_ = textTrackConverter.textTracksToJson(this.tech_); 20815 20816 this.isReady_ = false; 20817 20818 this.tech_.dispose(); 20819 20820 this.tech_ = false;
20821 }; 20822 20823 /** 20824 * Return a reference to the current {@link Tech}. 20825 * It will print a warning by default about the danger of using the tech directly 20826 * but any argument that is passed in will silence the warning. 20827 * 20828 * @param {*} [safety] 20829 * Anything passed in to silence the warning 20830 * 20831 * @return {Tech} 20832 * The Tech 20833 */ 20834 20835 20836 Player.prototype.tech = function tech(safety) { 20837 if (safety === undefined) { 20838 log$1.warn(tsml(_templateObject$1)); 20839 } 20840 20841 return this.tech_; 20842 }; 20843 20844 /** 20845 * Set up click and touch listeners for the playback element 20846 * 20847 * - On desktops: a click on the video itself will toggle playback 20848 * - On mobile devices: a click on the video toggles controls 20849 * which is done by toggling the user state between active and 20850 * inactive 20851 * - A tap can signal that a user has become active or has become inactive 20852 * e.g. a quick tap on an iPhone movie should reveal the controls. Another 20853 * quick tap should hide them again (signaling the user is in an inactive 20854 * viewing state) 20855 * - In addition to this, we still want the user to be considered inactive after 20856 * a few seconds of inactivity. 20857 * 20858 * > Note: the only part of iOS interaction we can't mimic with this setup 20859 * is a touch and hold on the video element counting as activity in order to 20860 * keep the controls showing, but that shouldn't be an issue. A touch and hold 20861 * on any controls will still keep the user active 20862 * 20863 * @private 20864 */ 20865 20866 20867 Player.prototype.addTechControlsListeners_ = function addTechControlsListeners_() { 20868 // Make sure to remove all the previous listeners in case we are called multiple times. 20869 this.removeTechControlsListeners_(); 20870 20871 // Some browsers (Chrome & IE) don't trigger a click on a flash swf, but do 20872 // trigger mousedown/up. 20873 // http://stackoverflow.com/questions/1444562/javascript-onclick-event-over-flash-object 20874 // Any touch events are set to block the mousedown event from happening 20875 this.on(this.tech_, 'mousedown', this.handleTechClick_); 20876 20877 // If the controls were hidden we don't want that to change without a tap event 20878 // so we'll check if the controls were already showing before reporting user 20879 // activity 20880 this.on(this.tech_, 'touchstart', this.handleTechTouchStart_); 20881 this.on(this.tech_, 'touchmove', this.handleTechTouchMove_); 20882 this.on(this.tech_, 'touchend', this.handleTechTouchEnd_); 20883 20884 // The tap listener needs to come after the touchend listener because the tap 20885 // listener cancels out any reportedUserActivity when setting userActive(false) 20886 this.on(this.tech_, 'tap', this.handleTechTap_); 20887 }; 20888 20889 /** 20890 * Remove the listeners used for click and tap controls. This is needed for 20891 * toggling to controls disabled, where a tap/touch should do nothing. 20892 * 20893 * @private 20894 */ 20895 20896 20897 Player.prototype.removeTechControlsListeners_ = function removeTechControlsListeners_() { 20898 // We don't want to just use `this.off()` because there might be other needed 20899 // listeners added by techs that extend this. 20900 this.off(this.tech_, 'tap', this.handleTechTap_); 20901 this.off(this.tech_, 'touchstart', this.handleTechTouchStart_); 20902 this.off(this.tech_, 'touchmove', this.handleTechTouchMove_); 20903 this.off(this.tech_, 'touchend', this.handleTechTouchEnd_); 20904 this.off(this.tech_, 'mousedown', this.handleTechClick_); 20905 }; 20906 20907 /** 20908 * Player waits for the tech to be ready 20909 * 20910 * @private 20911 */ 20912 20913 20914 Player.prototype.handleTechReady_ = function handleTechReady_() { 20915 this.triggerReady(); 20916 20917 // Keep the same volume as before 20918 if (this.cache_.volume) { 20919 this.techCall_('setVolume', this.cache_.volume); 20920 } 20921 20922 // Look if the tech found a higher resolution poster while loading 20923 this.handleTechPosterChange_(); 20924 20925 // Update the duration if available 20926 this.handleTechDurationChange_(); 20927 20928 // Chrome and Safari both have issues with autoplay. 20929 // In Safari (5.1.1), when we move the video element into the container div, autoplay doesn't work. 20930 // In Chrome (15), if you have autoplay + a poster + no controls, the video gets hidden (but audio plays) 20931 // This fixes both issues. Need to wait for API, so it updates displays correctly 20932 if ((this.src() || this.currentSrc()) && this.tag && this.options_.autoplay && this.paused()) {
vendor: 13,063 bytes, lines 20933-21473
20933 try { 20934 // Chrome Fix. Fixed in Chrome v16. 20935 delete this.tag.poster; 20936 } catch (e) { 20937 log$1('deleting tag.poster throws in some browsers', e); 20938 } 20939 } 20940 }; 20941 20942 /** 20943 * Retrigger the `loadstart` event that was triggered by the {@link Tech}. This 20944 * function will also trigger {@link Player#firstplay} if it is the first loadstart 20945 * for a video. 20946 * 20947 * @fires Player#loadstart 20948 * @fires Player#firstplay 20949 * @listens Tech#loadstart 20950 * @private 20951 */ 20952 20953 20954 Player.prototype.handleTechLoadStart_ = function handleTechLoadStart_() { 20955 // TODO: Update to use `emptied` event instead. See #1277. 20956 20957 this.removeClass('vjs-ended'); 20958 this.removeClass('vjs-seeking'); 20959 20960 // reset the error state 20961 this.error(null); 20962 20963 // If it's already playing we want to trigger a firstplay event now. 20964 // The firstplay event relies on both the play and loadstart events 20965 // which can happen in any order for a new source 20966 if (!this.paused()) { 20967 /** 20968 * Fired when the user agent begins looking for media data 20969 * 20970 * @event Player#loadstart 20971 * @type {EventTarget~Event} 20972 */ 20973 this.trigger('loadstart'); 20974 this.trigger('firstplay'); 20975 } else { 20976 // reset the hasStarted state 20977 this.hasStarted(false); 20978 this.trigger('loadstart'); 20979 } 20980 }; 20981 20982 /** 20983 * Add/remove the vjs-has-started class 20984 * 20985 * @fires Player#firstplay 20986 * 20987 * @param {boolean} hasStarted 20988 * - true: adds the class 20989 * - false: remove the class 20990 * 20991 * @return {boolean} 20992 * the boolean value of hasStarted 20993 */ 20994 20995 20996 Player.prototype.hasStarted = function hasStarted(_hasStarted) { 20997 if (_hasStarted !== undefined) { 20998 // only update if this is a new value 20999 if (this.hasStarted_ !== _hasStarted) { 21000 this.hasStarted_ = _hasStarted; 21001 if (_hasStarted) { 21002 this.addClass('vjs-has-started'); 21003 // trigger the firstplay event if this newly has played 21004 this.trigger('firstplay'); 21005 } else { 21006 this.removeClass('vjs-has-started'); 21007 } 21008 } 21009 return; 21010 } 21011 return !!this.hasStarted_; 21012 }; 21013 21014 /** 21015 * Fired whenever the media begins or resumes playback 21016 * 21017 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-play} 21018 * @fires Player#play 21019 * @listens Tech#play 21020 * @private 21021 */ 21022 21023 21024 Player.prototype.handleTechPlay_ = function handleTechPlay_() { 21025 this.removeClass('vjs-ended'); 21026 this.removeClass('vjs-paused'); 21027 this.addClass('vjs-playing'); 21028 21029 // hide the poster when the user hits play 21030 this.hasStarted(true); 21031 /** 21032 * Triggered whenever an {@link Tech#play} event happens. Indicates that 21033 * playback has started or resumed. 21034 * 21035 * @event Player#play 21036 * @type {EventTarget~Event} 21037 */ 21038 this.trigger('play'); 21039 }; 21040 21041 /** 21042 * Retrigger the `waiting` event that was triggered by the {@link Tech}. 21043 * 21044 * @fires Player#waiting 21045 * @listens Tech#waiting 21046 * @private 21047 */ 21048 21049 21050 Player.prototype.handleTechWaiting_ = function handleTechWaiting_() { 21051 var _this4 = this; 21052 21053 this.addClass('vjs-waiting'); 21054 /** 21055 * A readyState change on the DOM element has caused playback to stop. 21056 * 21057 * @event Player#waiting 21058 * @type {EventTarget~Event} 21059 */ 21060 this.trigger('waiting'); 21061 this.one('timeupdate', function () { 21062 return _this4.removeClass('vjs-waiting'); 21063 }); 21064 }; 21065 21066 /** 21067 * Retrigger the `canplay` event that was triggered by the {@link Tech}. 21068 * > Note: This is not consistent between browsers. See #1351 21069 * 21070 * @fires Player#canplay 21071 * @listens Tech#canplay 21072 * @private 21073 */ 21074 21075 21076 Player.prototype.handleTechCanPlay_ = function handleTechCanPlay_() { 21077 this.removeClass('vjs-waiting'); 21078 /** 21079 * The media has a readyState of HAVE_FUTURE_DATA or greater. 21080 * 21081 * @event Player#canplay 21082 * @type {EventTarget~Event} 21083 */ 21084 this.trigger('canplay'); 21085 }; 21086 21087 /** 21088 * Retrigger the `canplaythrough` event that was triggered by the {@link Tech}. 21089 * 21090 * @fires Player#canplaythrough 21091 * @listens Tech#canplaythrough 21092 * @private 21093 */ 21094 21095 21096 Player.prototype.handleTechCanPlayThrough_ = function handleTechCanPlayThrough_() { 21097 this.removeClass('vjs-waiting'); 21098 /** 21099 * The media has a readyState of HAVE_ENOUGH_DATA or greater. This means that the 21100 * entire media file can be played without buffering. 21101 * 21102 * @event Player#canplaythrough 21103 * @type {EventTarget~Event} 21104 */ 21105 this.trigger('canplaythrough'); 21106 }; 21107 21108 /** 21109 * Retrigger the `playing` event that was triggered by the {@link Tech}. 21110 * 21111 * @fires Player#playing 21112 * @listens Tech#playing 21113 * @private 21114 */ 21115 21116 21117 Player.prototype.handleTechPlaying_ = function handleTechPlaying_() { 21118 this.removeClass('vjs-waiting'); 21119 /** 21120 * The media is no longer blocked from playback, and has started playing. 21121 * 21122 * @event Player#playing 21123 * @type {EventTarget~Event} 21124 */ 21125 this.trigger('playing'); 21126 }; 21127 21128 /** 21129 * Retrigger the `seeking` event that was triggered by the {@link Tech}. 21130 * 21131 * @fires Player#seeking 21132 * @listens Tech#seeking 21133 * @private 21134 */ 21135 21136 21137 Player.prototype.handleTechSeeking_ = function handleTechSeeking_() { 21138 this.addClass('vjs-seeking'); 21139 /** 21140 * Fired whenever the player is jumping to a new time 21141 * 21142 * @event Player#seeking 21143 * @type {EventTarget~Event} 21144 */ 21145 this.trigger('seeking'); 21146 }; 21147 21148 /** 21149 * Retrigger the `seeked` event that was triggered by the {@link Tech}. 21150 * 21151 * @fires Player#seeked 21152 * @listens Tech#seeked 21153 * @private 21154 */ 21155 21156 21157 Player.prototype.handleTechSeeked_ = function handleTechSeeked_() { 21158 this.removeClass('vjs-seeking'); 21159 /** 21160 * Fired when the player has finished jumping to a new time 21161 * 21162 * @event Player#seeked 21163 * @type {EventTarget~Event} 21164 */ 21165 this.trigger('seeked'); 21166 }; 21167 21168 /** 21169 * Retrigger the `firstplay` event that was triggered by the {@link Tech}. 21170 * 21171 * @fires Player#firstplay 21172 * @listens Tech#firstplay 21173 * @deprecated As of 6.0 firstplay event is deprecated. 21174 * @deprecated As of 6.0 passing the `starttime` option to the player and the firstplay event are deprecated. 21175 * @private 21176 */ 21177 21178 21179 Player.prototype.handleTechFirstPlay_ = function handleTechFirstPlay_() { 21180 // If the first starttime attribute is specified 21181 // then we will start at the given offset in seconds 21182 if (this.options_.starttime) { 21183 log$1.warn('Passing the `starttime` option to the player will be deprecated in 6.0'); 21184 this.currentTime(this.options_.starttime); 21185 } 21186 21187 this.addClass('vjs-has-started'); 21188 /** 21189 * Fired the first time a video is played. Not part of the HLS spec, and this is 21190 * probably not the best implementation yet, so use sparingly. If you don't have a 21191 * reason to prevent playback, use `myPlayer.one('play');` instead. 21192 * 21193 * @event Player#firstplay 21194 * @deprecated As of 6.0 firstplay event is deprecated. 21195 * @type {EventTarget~Event} 21196 */ 21197 this.trigger('firstplay'); 21198 }; 21199 21200 /** 21201 * Retrigger the `pause` event that was triggered by the {@link Tech}. 21202 * 21203 * @fires Player#pause 21204 * @listens Tech#pause 21205 * @private 21206 */ 21207 21208 21209 Player.prototype.handleTechPause_ = function handleTechPause_() { 21210 this.removeClass('vjs-playing'); 21211 this.addClass('vjs-paused'); 21212 /** 21213 * Fired whenever the media has been paused 21214 * 21215 * @event Player#pause 21216 * @type {EventTarget~Event} 21217 */ 21218 this.trigger('pause'); 21219 }; 21220 21221 /** 21222 * Retrigger the `ended` event that was triggered by the {@link Tech}. 21223 * 21224 * @fires Player#ended 21225 * @listens Tech#ended 21226 * @private 21227 */ 21228 21229 21230 Player.prototype.handleTechEnded_ = function handleTechEnded_() { 21231 this.addClass('vjs-ended'); 21232 if (this.options_.loop) { 21233 this.currentTime(0); 21234 this.play(); 21235 } else if (!this.paused()) { 21236 this.pause(); 21237 } 21238 21239 /** 21240 * Fired when the end of the media resource is reached (currentTime == duration) 21241 * 21242 * @event Player#ended 21243 * @type {EventTarget~Event} 21244 */ 21245 this.trigger('ended'); 21246 }; 21247 21248 /** 21249 * Fired when the duration of the media resource is first known or changed 21250 * 21251 * @listens Tech#durationchange 21252 * @private 21253 */ 21254 21255 21256 Player.prototype.handleTechDurationChange_ = function handleTechDurationChange_() { 21257 this.duration(this.techGet_('duration')); 21258 }; 21259 21260 /** 21261 * Handle a click on the media element to play/pause 21262 * 21263 * @param {EventTarget~Event} event 21264 * the event that caused this function to trigger 21265 * 21266 * @listens Tech#mousedown 21267 * @private 21268 */ 21269 21270 21271 Player.prototype.handleTechClick_ = function handleTechClick_(event) { 21272 // We're using mousedown to detect clicks thanks to Flash, but mousedown 21273 // will also be triggered with right-clicks, so we need to prevent that 21274 if (event.button !== 0) { 21275 return; 21276 } 21277 21278 // When controls are disabled a click should not toggle playback because 21279 // the click is considered a control 21280 if (this.controls()) { 21281 if (this.paused()) { 21282 this.play(); 21283 } else { 21284 this.pause(); 21285 } 21286 } 21287 }; 21288 21289 /** 21290 * Handle a tap on the media element. It will toggle the user 21291 * activity state, which hides and shows the controls. 21292 * 21293 * @listens Tech#tap 21294 * @private 21295 */ 21296 21297 21298 Player.prototype.handleTechTap_ = function handleTechTap_() { 21299 this.userActive(!this.userActive()); 21300 }; 21301 21302 /** 21303 * Handle touch to start 21304 * 21305 * @listens Tech#touchstart 21306 * @private 21307 */ 21308 21309 21310 Player.prototype.handleTechTouchStart_ = function handleTechTouchStart_() { 21311 this.userWasActive = this.userActive(); 21312 }; 21313 21314 /** 21315 * Handle touch to move 21316 * 21317 * @listens Tech#touchmove 21318 * @private 21319 */ 21320 21321 21322 Player.prototype.handleTechTouchMove_ = function handleTechTouchMove_() { 21323 if (this.userWasActive) { 21324 this.reportUserActivity(); 21325 } 21326 }; 21327 21328 /** 21329 * Handle touch to end 21330 * 21331 * @param {EventTarget~Event} event 21332 * the touchend event that triggered 21333 * this function 21334 * 21335 * @listens Tech#touchend 21336 * @private 21337 */ 21338 21339 21340 Player.prototype.handleTechTouchEnd_ = function handleTechTouchEnd_(event) { 21341 // Stop the mouse events from also happening 21342 event.preventDefault(); 21343 }; 21344 21345 /** 21346 * Fired when the player switches in or out of fullscreen mode 21347 * 21348 * @private 21349 * @listens Player#fullscreenchange 21350 */ 21351 21352 21353 Player.prototype.handleFullscreenChange_ = function handleFullscreenChange_() { 21354 if (this.isFullscreen()) { 21355 this.addClass('vjs-fullscreen'); 21356 } else { 21357 this.removeClass('vjs-fullscreen'); 21358 } 21359 }; 21360 21361 /** 21362 * native click events on the SWF aren't triggered on IE11, Win8.1RT 21363 * use stageclick events triggered from inside the SWF instead 21364 * 21365 * @private 21366 * @listens stageclick 21367 */ 21368 21369 21370 Player.prototype.handleStageClick_ = function handleStageClick_() { 21371 this.reportUserActivity(); 21372 }; 21373 21374 /** 21375 * Handle Tech Fullscreen Change 21376 * 21377 * @param {EventTarget~Event} event 21378 * the fullscreenchange event that triggered this function 21379 * 21380 * @param {Object} data 21381 * the data that was sent with the event 21382 * 21383 * @private 21384 * @listens Tech#fullscreenchange 21385 * @fires Player#fullscreenchange 21386 */ 21387 21388 21389 Player.prototype.handleTechFullscreenChange_ = function handleTechFullscreenChange_(event, data) { 21390 if (data) { 21391 this.isFullscreen(data.isFullscreen); 21392 } 21393 /** 21394 * Fired when going in and out of fullscreen. 21395 * 21396 * @event Player#fullscreenchange 21397 * @type {EventTarget~Event} 21398 */ 21399 this.trigger('fullscreenchange'); 21400 }; 21401 21402 /** 21403 * Fires when an error occurred during the loading of an audio/video. 21404 * 21405 * @private 21406 * @listens Tech#error 21407 */ 21408 21409 21410 Player.prototype.handleTechError_ = function handleTechError_() { 21411 var error = this.tech_.error(); 21412 21413 this.error(error); 21414 }; 21415 21416 /** 21417 * Retrigger the `textdata` event that was triggered by the {@link Tech}. 21418 * 21419 * @fires Player#textdata 21420 * @listens Tech#textdata 21421 * @private 21422 */ 21423 21424 21425 Player.prototype.handleTechTextData_ = function handleTechTextData_() { 21426 var data = null; 21427 21428 if (arguments.length > 1) { 21429 data = arguments[1]; 21430 } 21431 21432 /** 21433 * Fires when we get a textdata event from tech 21434 * 21435 * @event Player#textdata 21436 * @type {EventTarget~Event} 21437 */ 21438 this.trigger('textdata', data); 21439 }; 21440 21441 /** 21442 * Get object for cached values. 21443 * 21444 * @return {Object} 21445 * get the current object cache 21446 */ 21447 21448 21449 Player.prototype.getCache = function getCache() { 21450 return this.cache_; 21451 }; 21452 21453 /** 21454 * Pass values to the playback tech 21455 * 21456 * @param {string} [method] 21457 * the method to call 21458 * 21459 * @param {Object} arg 21460 * the argument to pass 21461 * 21462 * @private 21463 */ 21464 21465 21466 Player.prototype.techCall_ = function techCall_(method, arg) { 21467 // If it's not ready yet, call method when it is 21468 21469 this.ready(function () { 21470 if (method in allowedSetters) { 21471 return set$1(this.middleware_, this.tech_, method, arg); 21472 } 21473
21474 try { 21475 if (this.tech_) { 21476 this.tech_[method](arg); 21477 } 21478 } catch (e) { 21479 log$1(e); 21480 throw e; 21481 } 21482 }, true); 21483 }; 21484 21485 /** 21486 * Get calls can't wait for the tech, and sometimes don't need to. 21487 * 21488 * @param {string} method 21489 * Tech method 21490 * 21491 * @return {Function|undefined} 21492 * the method or undefined 21493 * 21494 * @private 21495 */ 21496 21497 21498 Player.prototype.techGet_ = function techGet_(method) { 21499 if (this.tech_ && this.tech_.isReady_) { 21500 21501 if (method in allowedGetters) { 21502 return get$1(this.middleware_, this.tech_, method); 21503 } 21504 21505 // Flash likes to die and reload when you hide or reposition it. 21506 // In these cases the object methods go away and we get errors. 21507 // When that happens we'll catch the errors and inform tech that it's not ready any more. 21508 try { 21509 return this.tech_[method](); 21510 } catch (e) { 21511 // When building additional tech libs, an expected method may not be defined yet 21512 if (this.tech_[method] === undefined) { 21513 log$1('Video.js: ' + method + ' method not defined for ' + this.techName_ + ' playback technology.', e); 21514 21515 // When a method isn't available on the object it throws a TypeError 21516 } else if (e.name === 'TypeError') { 21517 log$1('Video.js: ' + method + ' unavailable on ' + this.techName_ + ' playback technology element.', e); 21518 this.tech_.isReady_ = false; 21519 } else { 21520 log$1(e); 21521 } 21522 throw e; 21523 } 21524 } 21525 21526 return; 21527 }; 21528 21529 /** 21530 * start media playback 21531 * 21532 * @return {Promise|undefined} 21533 * Returns a `Promise` if the browser returns one, for most browsers this will 21534 * return undefined. 21535 */ 21536 21537 21538 Player.prototype.play = function play() { 21539 if (this.changingSrc_) { 21540 this.ready(function () { 21541 var retval = this.techGet_('play'); 21542 21543 // silence errors (unhandled promise from play) 21544 if (retval !== undefined && typeof retval.then === 'function') { 21545 retval.then(null, function (e) {}); 21546 } 21547 }); 21548 21549 // Only calls the tech's play if we already have a src loaded 21550 } else if (this.isReady_ && (this.src() || this.currentSrc())) { 21551 return this.techGet_('play'); 21552 } else { 21553 this.ready(function () { 21554 this.tech_.one('loadstart', function () { 21555 var retval = this.play(); 21556 21557 // silence errors (unhandled promise from play) 21558 if (retval !== undefined && typeof retval.then === 'function') { 21559 retval.then(null, function (e) {}); 21560 } 21561 }); 21562 }); 21563 } 21564 }; 21565 21566 /** 21567 * Pause the video playback 21568 * 21569 * @return {Player} 21570 * A reference to the player object this function was called on 21571 */ 21572 21573 21574 Player.prototype.pause = function pause() { 21575 this.techCall_('pause'); 21576 }; 21577 21578 /** 21579 * Check if the player is paused or has yet to play 21580 * 21581 * @return {boolean} 21582 * - false: if the media is currently playing 21583 * - true: if media is not currently playing 21584 */ 21585 21586 21587 Player.prototype.paused = function paused() { 21588 // The initial state of paused should be true (in Safari it's actually false) 21589 return this.techGet_('paused') === false ? false : true; 21590 }; 21591 21592 /** 21593 * Get a TimeRange object representing the current ranges of time that the user 21594 * has played. 21595 * 21596 * @return {TimeRange} 21597 * A time range object that represents all the increments of time that have 21598 * been played. 21599 */ 21600 21601 21602 Player.prototype.played = function played() { 21603 return this.techGet_('played') || createTimeRanges(0, 0); 21604 }; 21605 21606 /** 21607 * Returns whether or not the user is "scrubbing". Scrubbing is 21608 * when the user has clicked the progress bar handle and is 21609 * dragging it along the progress bar. 21610 * 21611 * @param {boolean} [isScrubbing] 21612 * wether the user is or is not scrubbing 21613 * 21614 * @return {boolean} 21615 * The value of scrubbing when getting 21616 */ 21617 21618 21619 Player.prototype.scrubbing = function scrubbing(isScrubbing) { 21620 if (typeof isScrubbing === 'undefined') { 21621 return this.scrubbing_; 21622 } 21623 this.scrubbing_ = !!isScrubbing; 21624 21625 if (isScrubbing) { 21626 this.addClass('vjs-scrubbing'); 21627 } else { 21628 this.removeClass('vjs-scrubbing'); 21629 } 21630 }; 21631 21632 /** 21633 * Get or set the current time (in seconds) 21634 * 21635 * @param {number|string} [seconds] 21636 * The time to seek to in seconds 21637 * 21638 * @return {number} 21639 * - the current time in seconds when getting 21640 */ 21641 21642 21643 Player.prototype.currentTime = function currentTime(seconds) { 21644 if (typeof seconds !== 'undefined') { 21645 this.techCall_('setCurrentTime', seconds); 21646 return; 21647 } 21648 21649 // cache last currentTime and return. default to 0 seconds 21650 // 21651 // Caching the currentTime is meant to prevent a massive amount of reads on the tech's
vendor: 5,097 bytes, lines 21652-21840
21652 // currentTime when scrubbing, but may not provide much performance benefit afterall. 21653 // Should be tested. Also something has to read the actual current time or the cache will 21654 // never get updated. 21655 this.cache_.currentTime = this.techGet_('currentTime') || 0; 21656 return this.cache_.currentTime; 21657 }; 21658 21659 /** 21660 * Normally gets the length in time of the video in seconds; 21661 * in all but the rarest use cases an argument will NOT be passed to the method 21662 * 21663 * > **NOTE**: The video must have started loading before the duration can be 21664 * known, and in the case of Flash, may not be known until the video starts 21665 * playing. 21666 * 21667 * @fires Player#durationchange 21668 * 21669 * @param {number} [seconds] 21670 * The duration of the video to set in seconds 21671 * 21672 * @return {number} 21673 * - The duration of the video in seconds when getting 21674 */ 21675 21676 21677 Player.prototype.duration = function duration(seconds) { 21678 if (seconds === undefined) { 21679 // return NaN if the duration is not known 21680 return this.cache_.duration !== undefined ? this.cache_.duration : NaN; 21681 } 21682 21683 seconds = parseFloat(seconds); 21684 21685 // Standardize on Inifity for signaling video is live 21686 if (seconds < 0) { 21687 seconds = Infinity; 21688 } 21689 21690 if (seconds !== this.cache_.duration) { 21691 // Cache the last set value for optimized scrubbing (esp. Flash) 21692 this.cache_.duration = seconds; 21693 21694 if (seconds === Infinity) { 21695 this.addClass('vjs-live'); 21696 } else { 21697 this.removeClass('vjs-live'); 21698 } 21699 /** 21700 * @event Player#durationchange 21701 * @type {EventTarget~Event} 21702 */ 21703 this.trigger('durationchange'); 21704 } 21705 }; 21706 21707 /** 21708 * Calculates how much time is left in the video. Not part 21709 * of the native video API. 21710 * 21711 * @return {number} 21712 * The time remaining in seconds 21713 */ 21714 21715 21716 Player.prototype.remainingTime = function remainingTime() { 21717 return this.duration() - this.currentTime(); 21718 }; 21719 21720 // 21721 // Kind of like an array of portions of the video that have been downloaded. 21722 21723 /** 21724 * Get a TimeRange object with an array of the times of the video 21725 * that have been downloaded. If you just want the percent of the 21726 * video that's been downloaded, use bufferedPercent. 21727 * 21728 * @see [Buffered Spec]{@link http://dev.w3.org/html5/spec/video.html#dom-media-buffered} 21729 * 21730 * @return {TimeRange} 21731 * A mock TimeRange object (following HTML spec) 21732 */ 21733 21734 21735 Player.prototype.buffered = function buffered() { 21736 var buffered = this.techGet_('buffered'); 21737 21738 if (!buffered || !buffered.length) { 21739 buffered = createTimeRanges(0, 0); 21740 } 21741 21742 return buffered; 21743 }; 21744 21745 /** 21746 * Get the percent (as a decimal) of the video that's been downloaded. 21747 * This method is not a part of the native HTML video API. 21748 * 21749 * @return {number} 21750 * A decimal between 0 and 1 representing the percent 21751 * that is bufferred 0 being 0% and 1 being 100% 21752 */ 21753 21754 21755 Player.prototype.bufferedPercent = function bufferedPercent$$1() { 21756 return bufferedPercent(this.buffered(), this.duration()); 21757 }; 21758 21759 /** 21760 * Get the ending time of the last buffered time range 21761 * This is used in the progress bar to encapsulate all time ranges. 21762 * 21763 * @return {number} 21764 * The end of the last buffered time range 21765 */ 21766 21767 21768 Player.prototype.bufferedEnd = function bufferedEnd() { 21769 var buffered = this.buffered(); 21770 var duration = this.duration(); 21771 var end = buffered.end(buffered.length - 1); 21772 21773 if (end > duration) { 21774 end = duration; 21775 } 21776 21777 return end; 21778 }; 21779 21780 /** 21781 * Get or set the current volume of the media 21782 * 21783 * @param {number} [percentAsDecimal] 21784 * The new volume as a decimal percent: 21785 * - 0 is muted/0%/off 21786 * - 1.0 is 100%/full 21787 * - 0.5 is half volume or 50% 21788 * 21789 * @return {number} 21790 * The current volume as a percent when getting 21791 */ 21792 21793 21794 Player.prototype.volume = function volume(percentAsDecimal) { 21795 var vol = void 0; 21796 21797 if (percentAsDecimal !== undefined) { 21798 // Force value to between 0 and 1 21799 vol = Math.max(0, Math.min(1, parseFloat(percentAsDecimal))); 21800 this.cache_.volume = vol; 21801 this.techCall_('setVolume', vol); 21802 21803 if (vol > 0) { 21804 this.lastVolume_(vol); 21805 } 21806 21807 return; 21808 } 21809 21810 // Default to 1 when returning current volume. 21811 vol = parseFloat(this.techGet_('volume')); 21812 return isNaN(vol) ? 1 : vol; 21813 }; 21814 21815 /** 21816 * Get the current muted state, or turn mute on or off 21817 * 21818 * @param {boolean} [muted] 21819 * - true to mute 21820 * - false to unmute 21821 * 21822 * @return {boolean} 21823 * - true if mute is on and getting 21824 * - false if mute is off and getting 21825 */ 21826 21827 21828 Player.prototype.muted = function muted(_muted) { 21829 if (_muted !== undefined) { 21830 this.techCall_('setMuted', _muted); 21831 return; 21832 } 21833 return this.techGet_('muted') || false; 21834 }; 21835 21836 /** 21837 * Get the current defaultMuted state, or turn defaultMuted on or off. defaultMuted 21838 * indicates the state of muted on intial playback. 21839 * 21840 * ```js
21841 * var myPlayer = videojs('some-player-id'); 21842 * 21843 * myPlayer.src("http://www.example.com/path/to/video.mp4"); 21844 * 21845 * // get, should be false 21846 * console.log(myPlayer.defaultMuted()); 21847 * // set to true 21848 * myPlayer.defaultMuted(true); 21849 * // get should be true 21850 * console.log(myPlayer.defaultMuted()); 21851 * ``` 21852 * 21853 * @param {boolean} [defaultMuted] 21854 * - true to mute 21855 * - false to unmute 21856 * 21857 * @return {boolean|Player} 21858 * - true if defaultMuted is on and getting 21859 * - false if defaultMuted is off and getting 21860 * - A reference to the current player when setting 21861 */ 21862 21863 21864 Player.prototype.defaultMuted = function defaultMuted(_defaultMuted) { 21865 if (_defaultMuted !== undefined) { 21866 return this.techCall_('setDefaultMuted', _defaultMuted); 21867 } 21868 return this.techGet_('defaultMuted') || false; 21869 }; 21870 21871 /** 21872 * Get the last volume, or set it 21873 * 21874 * @param {number} [percentAsDecimal] 21875 * The new last volume as a decimal percent: 21876 * - 0 is muted/0%/off 21877 * - 1.0 is 100%/full 21878 * - 0.5 is half volume or 50% 21879 * 21880 * @return {number} 21881 * the current value of lastVolume as a percent when getting 21882 * 21883 * @private 21884 */ 21885 21886 21887 Player.prototype.lastVolume_ = function lastVolume_(percentAsDecimal) { 21888 if (percentAsDecimal !== undefined && percentAsDecimal !== 0) { 21889 this.cache_.lastVolume = percentAsDecimal; 21890 return; 21891 } 21892 return this.cache_.lastVolume; 21893 }; 21894 21895 /** 21896 * Check if current tech can support native fullscreen 21897 * (e.g. with built in controls like iOS, so not our flash swf) 21898 * 21899 * @return {boolean} 21900 * if native fullscreen is supported 21901 */ 21902 21903 21904 Player.prototype.supportsFullScreen = function supportsFullScreen() { 21905 return this.techGet_('supportsFullScreen') || false; 21906 }; 21907 21908 /** 21909 * Check if the player is in fullscreen mode or tell the player that it 21910 * is or is not in fullscreen mode. 21911 * 21912 * > NOTE: As of the latest HTML5 spec, isFullscreen is no longer an official 21913 * property and instead document.fullscreenElement is used. But isFullscreen is 21914 * still a valuable property for internal player workings. 21915 * 21916 * @param {boolean} [isFS] 21917 * Set the players current fullscreen state 21918 * 21919 * @return {boolean} 21920 * - true if fullscreen is on and getting 21921 * - false if fullscreen is off and getting 21922 */ 21923 21924 21925 Player.prototype.isFullscreen = function isFullscreen(isFS) { 21926 if (isFS !== undefined) { 21927 this.isFullscreen_ = !!isFS; 21928 return; 21929 } 21930 return !!this.isFullscreen_; 21931 }; 21932 21933 /** 21934 * Increase the size of the video to full screen 21935 * In some browsers, full screen is not supported natively, so it enters 21936 * "full window mode", where the video fills the browser window. 21937 * In browsers and devices that support native full screen, sometimes the 21938 * browser's default controls will be shown, and not the Video.js custom skin. 21939 * This includes most mobile devices (iOS, Android) and older versions of 21940 * Safari. 21941 * 21942 * @fires Player#fullscreenchange 21943 */ 21944 21945 21946 Player.prototype.requestFullscreen = function requestFullscreen() { 21947 var fsApi = FullscreenApi; 21948 21949 this.isFullscreen(true); 21950 21951 if (fsApi.requestFullscreen) { 21952 // the browser supports going fullscreen at the element level so we can 21953 // take the controls fullscreen as well as the video 21954 21955 // Trigger fullscreenchange event after change 21956 // We have to specifically add this each time, and remove 21957 // when canceling fullscreen. Otherwise if there's multiple 21958 // players on a page, they would all be reacting to the same fullscreen 21959 // events 21960 on(document_1, fsApi.fullscreenchange, bind(this, function documentFullscreenChange(e) { 21961 this.isFullscreen(document_1[fsApi.fullscreenElement]); 21962 21963 // If cancelling fullscreen, remove event listener. 21964 if (this.isFullscreen() === false) { 21965 off(document_1, fsApi.fullscreenchange, documentFullscreenChange); 21966 } 21967 /** 21968 * @event Player#fullscreenchange 21969 * @type {EventTarget~Event} 21970 */ 21971 this.trigger('fullscreenchange'); 21972 })); 21973 21974 this.el_[fsApi.requestFullscreen](); 21975 } else if (this.tech_.supportsFullScreen()) { 21976 // we can't take the video.js controls fullscreen but we can go fullscreen 21977 // with native controls 21978 this.techCall_('enterFullScreen'); 21979 } else { 21980 // fullscreen isn't supported so we'll just stretch the video element to 21981 // fill the viewport 21982 this.enterFullWindow(); 21983 /** 21984 * @event Player#fullscreenchange 21985 * @type {EventTarget~Event} 21986 */ 21987 this.trigger('fullscreenchange'); 21988 } 21989 }; 21990 21991 /** 21992 * Return the video to its normal size after having been in full screen mode 21993 * 21994 * @fires Player#fullscreenchange 21995 */ 21996 21997 21998 Player.prototype.exitFullscreen = function exitFullscreen() { 21999 var fsApi = FullscreenApi; 22000 22001 this.isFullscreen(false); 22002 22003 // Check for browser element fullscreen support 22004 if (fsApi.requestFullscreen) { 22005 document_1[fsApi.exitFullscreen](); 22006 } else if (this.tech_.supportsFullScreen()) { 22007 this.techCall_('exitFullScreen'); 22008 } else { 22009 this.exitFullWindow(); 22010 /** 22011 * @event Player#fullscreenchange 22012 * @type {EventTarget~Event} 22013 */ 22014 this.trigger('fullscreenchange'); 22015 } 22016 }; 22017 22018 /** 22019 * When fullscreen isn't supported we can stretch the 22020 * video container to as wide as the browser will let us. 22021 * 22022 * @fires Player#enterFullWindow 22023 */ 22024 22025 22026 Player.prototype.enterFullWindow = function enterFullWindow() { 22027 this.isFullWindow = true; 22028 22029 // Storing original doc overflow value to return to when fullscreen is off 22030 this.docOrigOverflow = document_1.documentElement.style.overflow; 22031 22032 // Add listener for esc key to exit fullscreen 22033 on(document_1, 'keydown', bind(this, this.fullWindowOnEscKey)); 22034 22035 // Hide any scroll bars 22036 document_1.documentElement.style.overflow = 'hidden'; 22037 22038 // Apply fullscreen styles 22039 addClass(document_1.body, 'vjs-full-window'); 22040 22041 /** 22042 * @event Player#enterFullWindow 22043 * @type {EventTarget~Event} 22044 */ 22045 this.trigger('enterFullWindow'); 22046 }; 22047 22048 /** 22049 * Check for call to either exit full window or 22050 * full screen on ESC key 22051 * 22052 * @param {string} event 22053 * Event to check for key press 22054 */ 22055 22056 22057 Player.prototype.fullWindowOnEscKey = function fullWindowOnEscKey(event) { 22058 if (event.keyCode === 27) { 22059 if (this.isFullscreen() === true) { 22060 this.exitFullscreen(); 22061 } else { 22062 this.exitFullWindow(); 22063 } 22064 } 22065 }; 22066 22067 /** 22068 * Exit full window 22069 * 22070 * @fires Player#exitFullWindow 22071 */ 22072 22073 22074 Player.prototype.exitFullWindow = function exitFullWindow() { 22075 this.isFullWindow = false;
vendor: 4,466 bytes, lines 22076-22224
22076 off(document_1, 'keydown', this.fullWindowOnEscKey); 22077 22078 // Unhide scroll bars. 22079 document_1.documentElement.style.overflow = this.docOrigOverflow; 22080 22081 // Remove fullscreen styles 22082 removeClass(document_1.body, 'vjs-full-window'); 22083 22084 // Resize the box, controller, and poster to original sizes 22085 // this.positionAll(); 22086 /** 22087 * @event Player#exitFullWindow 22088 * @type {EventTarget~Event} 22089 */ 22090 this.trigger('exitFullWindow'); 22091 }; 22092 22093 /** 22094 * Check whether the player can play a given mimetype 22095 * 22096 * @see https://www.w3.org/TR/2011/WD-html5-20110113/video.html#dom-navigator-canplaytype 22097 * 22098 * @param {string} type 22099 * The mimetype to check 22100 * 22101 * @return {string} 22102 * 'probably', 'maybe', or '' (empty string) 22103 */ 22104 22105 22106 Player.prototype.canPlayType = function canPlayType(type) { 22107 var can = void 0; 22108 22109 // Loop through each playback technology in the options order 22110 for (var i = 0, j = this.options_.techOrder; i < j.length; i++) { 22111 var techName = j[i]; 22112 var tech = Tech.getTech(techName); 22113 22114 // Support old behavior of techs being registered as components. 22115 // Remove once that deprecated behavior is removed. 22116 if (!tech) { 22117 tech = Component.getComponent(techName); 22118 } 22119 22120 // Check if the current tech is defined before continuing 22121 if (!tech) { 22122 log$1.error('The "' + techName + '" tech is undefined. Skipped browser support check for that tech.'); 22123 continue; 22124 } 22125 22126 // Check if the browser supports this technology 22127 if (tech.isSupported()) { 22128 can = tech.canPlayType(type); 22129 22130 if (can) { 22131 return can; 22132 } 22133 } 22134 } 22135 22136 return ''; 22137 }; 22138 22139 /** 22140 * Select source based on tech-order or source-order 22141 * Uses source-order selection if `options.sourceOrder` is truthy. Otherwise, 22142 * defaults to tech-order selection 22143 * 22144 * @param {Array} sources 22145 * The sources for a media asset 22146 * 22147 * @return {Object|boolean} 22148 * Object of source and tech order or false 22149 */ 22150 22151 22152 Player.prototype.selectSource = function selectSource(sources) { 22153 var _this5 = this; 22154 22155 // Get only the techs specified in `techOrder` that exist and are supported by the 22156 // current platform 22157 var techs = this.options_.techOrder.map(function (techName) { 22158 return [techName, Tech.getTech(techName)]; 22159 }).filter(function (_ref) { 22160 var techName = _ref[0], 22161 tech = _ref[1]; 22162 22163 // Check if the current tech is defined before continuing 22164 if (tech) { 22165 // Check if the browser supports this technology 22166 return tech.isSupported(); 22167 } 22168 22169 log$1.error('The "' + techName + '" tech is undefined. Skipped browser support check for that tech.'); 22170 return false; 22171 }); 22172 22173 // Iterate over each `innerArray` element once per `outerArray` element and execute 22174 // `tester` with both. If `tester` returns a non-falsy value, exit early and return 22175 // that value. 22176 var findFirstPassingTechSourcePair = function findFirstPassingTechSourcePair(outerArray, innerArray, tester) { 22177 var found = void 0; 22178 22179 outerArray.some(function (outerChoice) { 22180 return innerArray.some(function (innerChoice) { 22181 found = tester(outerChoice, innerChoice); 22182 22183 if (found) { 22184 return true; 22185 } 22186 }); 22187 }); 22188 22189 return found; 22190 }; 22191 22192 var foundSourceAndTech = void 0; 22193 var flip = function flip(fn) { 22194 return function (a, b) { 22195 return fn(b, a); 22196 }; 22197 }; 22198 var finder = function finder(_ref2, source) { 22199 var techName = _ref2[0], 22200 tech = _ref2[1]; 22201 22202 if (tech.canPlaySource(source, _this5.options_[techName.toLowerCase()])) { 22203 return { source: source, tech: techName }; 22204 } 22205 }; 22206 22207 // Depending on the truthiness of `options.sourceOrder`, we swap the order of techs and sources 22208 // to select from them based on their priority. 22209 if (this.options_.sourceOrder) { 22210 // Source-first ordering 22211 foundSourceAndTech = findFirstPassingTechSourcePair(sources, techs, flip(finder)); 22212 } else { 22213 // Tech-first ordering 22214 foundSourceAndTech = findFirstPassingTechSourcePair(techs, sources, finder); 22215 } 22216 22217 return foundSourceAndTech || false; 22218 }; 22219 22220 /** 22221 * Get or set the video source. 22222 * 22223 * @param {Tech~SourceObject|Tech~SourceObject[]|string} [source] 22224 * A SourceObject, an array of SourceObjects, or a string referencing
22225 * a URL to a media source. It is _highly recommended_ that an object 22226 * or array of objects is used here, so that source selection 22227 * algorithms can take the `type` into account. 22228 * 22229 * If not provided, this method acts as a getter. 22230 * 22231 * @return {string|undefined} 22232 * If the `source` argument is missing, returns the current source 22233 * URL. Otherwise, returns nothing/undefined. 22234 */ 22235 22236 22237 Player.prototype.src = function src(source) { 22238 var _this6 = this; 22239 22240 // getter usage 22241 if (typeof source === 'undefined') { 22242 return this.cache_.src; 22243 } 22244 // filter out invalid sources and turn our source into 22245 // an array of source objects 22246 var sources = filterSource(source); 22247 22248 // if a source was passed in then it is invalid because 22249 // it was filtered to a zero length Array. So we have to 22250 // show an error 22251 if (!sources.length) { 22252 this.setTimeout(function () { 22253 this.error({ code: 4, message: this.localize(this.options_.notSupportedMessage) }); 22254 }, 0); 22255 return; 22256 } 22257 22258 // intial sources 22259 this.cache_.sources = sources; 22260 this.changingSrc_ = true; 22261 22262 // intial source 22263 this.cache_.source = sources[0]; 22264 22265 // middlewareSource is the source after it has been changed by middleware 22266 setSource(this, sources[0], function (middlewareSource, mws) { 22267 _this6.middleware_ = mws; 22268 22269 var err = _this6.src_(middlewareSource); 22270 22271 if (err) { 22272 if (sources.length > 1) { 22273 return _this6.src(sources.slice(1)); 22274 } 22275 22276 // We need to wrap this in a timeout to give folks a chance to add error event handlers 22277 _this6.setTimeout(function () { 22278 this.error({ code: 4, message: this.localize(this.options_.notSupportedMessage) }); 22279 }, 0); 22280 22281 // we could not find an appropriate tech, but let's still notify the delegate that this is it 22282 // this needs a better comment about why this is needed 22283 _this6.triggerReady(); 22284 22285 return; 22286 } 22287 22288 _this6.changingSrc_ = false; 22289 // video element listed source 22290 _this6.cache_.src = middlewareSource.src; 22291 22292 setTech(mws, _this6.tech_); 22293 }); 22294 }; 22295 22296 /** 22297 * Set the source object on the tech, returns a boolean that indicates wether 22298 * there is a tech that can play the source or not 22299 * 22300 * @param {Tech~SourceObject} source 22301 * The source object to set on the Tech 22302 * 22303 * @return {Boolean} 22304 * - True if there is no Tech to playback this source 22305 * - False otherwise 22306 * 22307 * @private 22308 */ 22309 22310 22311 Player.prototype.src_ = function src_(source) { 22312 var sourceTech = this.selectSource([source]); 22313 22314 if (!sourceTech) { 22315 return true; 22316 } 22317 22318 if (!titleCaseEquals(sourceTech.tech, this.techName_)) { 22319 this.changingSrc_ = true; 22320 22321 // load this technology with the chosen source 22322 this.loadTech_(sourceTech.tech, sourceTech.source); 22323 return false; 22324 } 22325 22326 // wait until the tech is ready to set the source 22327 this.ready(function () { 22328 22329 // The setSource tech method was added with source handlers 22330 // so older techs won't support it 22331 // We need to check the direct prototype for the case where subclasses 22332 // of the tech do not support source handlers 22333 if (this.tech_.constructor.prototype.hasOwnProperty('setSource')) { 22334 this.techCall_('setSource', source); 22335 } else { 22336 this.techCall_('src', source.src); 22337 } 22338 22339 if (this.options_.preload === 'auto') { 22340 this.load(); 22341 } 22342 22343 // Set the source synchronously if possible (#2326) 22344 }, true); 22345 22346 return false; 22347 }; 22348 22349 /** 22350 * Begin loading the src data. 22351 */ 22352 22353 22354 Player.prototype.load = function load() { 22355 this.techCall_('load'); 22356 }; 22357 22358 /** 22359 * Reset the player. Loads the first tech in the techOrder, 22360 * and calls `reset` on the tech`. 22361 */ 22362 22363 22364 Player.prototype.reset = function reset() { 22365 this.loadTech_(this.options_.techOrder[0], null); 22366 this.techCall_('reset'); 22367 }; 22368 22369 /** 22370 * Returns all of the current source objects. 22371 * 22372 * @return {Tech~SourceObject[]} 22373 * The current source objects 22374 */ 22375 22376 22377 Player.prototype.currentSources = function currentSources() { 22378 var source = this.currentSource(); 22379 var sources = []; 22380 22381 // assume `{}` or `{ src }` 22382 if (Object.keys(source).length !== 0) { 22383 sources.push(source); 22384 } 22385 22386 return this.cache_.sources || sources; 22387 }; 22388 22389 /** 22390 * Returns the current source object. 22391 * 22392 * @return {Tech~SourceObject} 22393 * The current source object 22394 */ 22395 22396 22397 Player.prototype.currentSource = function currentSource() { 22398 return this.cache_.source || {}; 22399 }; 22400 22401 /** 22402 * Returns the fully qualified URL of the current source value e.g. http://mysite.com/video.mp4 22403 * Can be used in conjuction with `currentType` to assist in rebuilding the current source object. 22404 * 22405 * @return {string} 22406 * The current source 22407 */ 22408 22409 22410 Player.prototype.currentSrc = function currentSrc() { 22411 return this.currentSource() && this.currentSource().src || ''; 22412 }; 22413 22414 /** 22415 * Get the current source type e.g. video/mp4 22416 * This can allow you rebuild the current source object so that you could load the same 22417 * source and tech later 22418 * 22419 * @return {string} 22420 * The source MIME type 22421 */ 22422 22423 22424 Player.prototype.currentType = function currentType() { 22425 return this.currentSource() && this.currentSource().type || ''; 22426 }; 22427 22428 /** 22429 * Get or set the preload attribute 22430 * 22431 * @param {boolean} [value] 22432 * - true means that we should preload 22433 * - false maens that we should not preload 22434 * 22435 * @return {string} 22436 * The preload attribute value when getting 22437 */ 22438 22439 22440 Player.prototype.preload = function preload(value) { 22441 if (value !== undefined) { 22442 this.techCall_('setPreload', value); 22443 this.options_.preload = value; 22444 return; 22445 } 22446 return this.techGet_('preload'); 22447 }; 22448 22449 /** 22450 * Get or set the autoplay attribute. 22451 * 22452 * @param {boolean} [value] 22453 * - true means that we should autoplay 22454 * - false means that we should not autoplay 22455 * 22456 * @return {string} 22457 * The current value of autoplay when getting 22458 */ 22459 22460 22461 Player.prototype.autoplay = function autoplay(value) { 22462 if (value !== undefined) { 22463 this.techCall_('setAutoplay', value); 22464 this.options_.autoplay = value; 22465 return; 22466 } 22467 return this.techGet_('autoplay', value); 22468 }; 22469 22470 /** 22471 * Set or unset the playsinline attribute. 22472 * Playsinline tells the browser that non-fullscreen playback is preferred. 22473 * 22474 * @param {boolean} [value] 22475 * - true means that we should try to play inline by default 22476 * - false means that we should use the browser's default playback mode, 22477 * which in most cases is inline. iOS Safari is a notable exception 22478 * and plays fullscreen by default. 22479 * 22480 * @return {string|Player} 22481 * - the current value of playsinline 22482 * - the player when setting 22483 * 22484 * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline} 22485 */ 22486 22487 22488 Player.prototype.playsinline = function playsinline(value) { 22489 if (value !== undefined) { 22490 this.techCall_('setPlaysinline', value); 22491 this.options_.playsinline = value; 22492 return this; 22493 } 22494 return this.techGet_('playsinline'); 22495 }; 22496 22497 /** 22498 * Get or set the loop attribute on the video element. 22499 * 22500 * @param {boolean} [value] 22501 * - true means that we should loop the video 22502 * - false means that we should not loop the video 22503 * 22504 * @return {string} 22505 * The current value of loop when getting 22506 */ 22507 22508 22509 Player.prototype.loop = function loop(value) { 22510 if (value !== undefined) { 22511 this.techCall_('setLoop', value); 22512 this.options_.loop = value; 22513 return; 22514 } 22515 return this.techGet_('loop'); 22516 }; 22517 22518 /** 22519 * Get or set the poster image source url 22520 * 22521 * @fires Player#posterchange 22522 * 22523 * @param {string} [src] 22524 * Poster image source URL 22525 * 22526 * @return {string} 22527 * The current value of poster when getting 22528 */ 22529 22530 22531 Player.prototype.poster = function poster(src) { 22532 if (src === undefined) { 22533 return this.poster_; 22534 } 22535 22536 // The correct way to remove a poster is to set as an empty string 22537 // other falsey values will throw errors 22538 if (!src) { 22539 src = ''; 22540 } 22541 22542 // update the internal poster variable 22543 this.poster_ = src; 22544 22545 // update the tech's poster 22546 this.techCall_('setPoster', src); 22547 22548 // alert components that the poster has been set 22549 /** 22550 * This event fires when the poster image is changed on the player. 22551 * 22552 * @event Player#posterchange 22553 * @type {EventTarget~Event} 22554 */ 22555 this.trigger('posterchange'); 22556 }; 22557 22558 /** 22559 * Some techs (e.g. YouTube) can provide a poster source in an 22560 * asynchronous way. We want the poster component to use this 22561 * poster source so that it covers up the tech's controls. 22562 * (YouTube's play button). However we only want to use this 22563 * source if the player user hasn't set a poster through 22564 * the normal APIs. 22565 * 22566 * @fires Player#posterchange 22567 * @listens Tech#posterchange 22568 * @private 22569 */ 22570 22571 22572 Player.prototype.handleTechPosterChange_ = function handleTechPosterChange_() { 22573 if (!this.poster_ && this.tech_ && this.tech_.poster) { 22574 this.poster_ = this.tech_.poster() || ''; 22575 22576 // Let components know the poster has changed 22577 this.trigger('posterchange'); 22578 } 22579 }; 22580 22581 /** 22582 * Get or set whether or not the controls are showing. 22583 * 22584 * @fires Player#controlsenabled 22585 * 22586 * @param {boolean} [bool] 22587 * - true to turn controls on 22588 * - false to turn controls off 22589 * 22590 * @return {boolean} 22591 * The current value of controls when getting 22592 */ 22593 22594 22595 Player.prototype.controls = function controls(bool) { 22596 if (bool !== undefined) { 22597 bool = !!bool; 22598 22599 // Don't trigger a change event unless it actually changed 22600 if (this.controls_ !== bool) { 22601 this.controls_ = bool; 22602 22603 if (this.usingNativeControls()) { 22604 this.techCall_('setControls', bool); 22605 } 22606 22607 if (bool) { 22608 this.removeClass('vjs-controls-disabled'); 22609 this.addClass('vjs-controls-enabled'); 22610 /** 22611 * @event Player#controlsenabled 22612 * @type {EventTarget~Event} 22613 */ 22614 this.trigger('controlsenabled'); 22615 22616 if (!this.usingNativeControls()) {
22617 this.addTechControlsListeners_(); 22618 } 22619 } else { 22620 this.removeClass('vjs-controls-enabled'); 22621 this.addClass('vjs-controls-disabled'); 22622 /** 22623 * @event Player#controlsdisabled 22624 * @type {EventTarget~Event} 22625 */ 22626 this.trigger('controlsdisabled'); 22627 22628 if (!this.usingNativeControls()) { 22629 this.removeTechControlsListeners_(); 22630 } 22631 } 22632 } 22633 return; 22634 } 22635 return !!this.controls_; 22636 }; 22637 22638 /** 22639 * Toggle native controls on/off. Native controls are the controls built into 22640 * devices (e.g. default iPhone controls), Flash, or other techs 22641 * (e.g. Vimeo Controls) 22642 * **This should only be set by the current tech, because only the tech knows 22643 * if it can support native controls** 22644 * 22645 * @fires Player#usingnativecontrols 22646 * @fires Player#usingcustomcontrols 22647 * 22648 * @param {boolean} [bool] 22649 * - true to turn native controls on 22650 * - false to turn native controls off 22651 * 22652 * @return {boolean} 22653 * The current value of native controls when getting 22654 */ 22655 22656 22657 Player.prototype.usingNativeControls = function usingNativeControls(bool) { 22658 if (bool !== undefined) { 22659 bool = !!bool; 22660 22661 // Don't trigger a change event unless it actually changed 22662 if (this.usingNativeControls_ !== bool) { 22663 this.usingNativeControls_ = bool; 22664 if (bool) { 22665 this.addClass('vjs-using-native-controls'); 22666 22667 /** 22668 * player is using the native device controls 22669 * 22670 * @event Player#usingnativecontrols 22671 * @type {EventTarget~Event} 22672 */ 22673 this.trigger('usingnativecontrols'); 22674 } else { 22675 this.removeClass('vjs-using-native-controls'); 22676 22677 /** 22678 * player is using the custom HTML controls 22679 * 22680 * @event Player#usingcustomcontrols 22681 * @type {EventTarget~Event} 22682 */ 22683 this.trigger('usingcustomcontrols'); 22684 } 22685 } 22686 return; 22687 } 22688 return !!this.usingNativeControls_; 22689 }; 22690 22691 /** 22692 * Set or get the current MediaError 22693 * 22694 * @fires Player#error 22695 * 22696 * @param {MediaError|string|number} [err] 22697 * A MediaError or a string/number to be turned 22698 * into a MediaError 22699 * 22700 * @return {MediaError|null} 22701 * The current MediaError when getting (or null) 22702 */ 22703 22704 22705 Player.prototype.error = function error(err) { 22706 if (err === undefined) { 22707 return this.error_ || null; 22708 } 22709 22710 // restoring to default 22711 if (err === null) { 22712 this.error_ = err; 22713 this.removeClass('vjs-error'); 22714 if (this.errorDisplay) { 22715 this.errorDisplay.close(); 22716 } 22717 return; 22718 } 22719 22720 this.error_ = new MediaError(err); 22721 22722 // add the vjs-error classname to the player 22723 this.addClass('vjs-error'); 22724 22725 // log the name of the error type and any message 22726 // ie8 just logs "[object object]" if you just log the error object 22727 log$1.error('(CODE:' + this.error_.code + ' ' + MediaError.errorTypes[this.error_.code] + ')', this.error_.message, this.error_); 22728 22729 /** 22730 * @event Player#error 22731 * @type {EventTarget~Event} 22732 */ 22733 this.trigger('error'); 22734 22735 return; 22736 }; 22737 22738 /** 22739 * Report user activity 22740 * 22741 * @param {Object} event 22742 * Event object 22743 */ 22744 22745 22746 Player.prototype.reportUserActivity = function reportUserActivity(event) { 22747 this.userActivity_ = true; 22748 }; 22749 22750 /** 22751 * Get/set if user is active 22752 * 22753 * @fires Player#useractive 22754 * @fires Player#userinactive 22755 * 22756 * @param {boolean} [bool] 22757 * - true if the user is active 22758 * - false if the user is inactive 22759 * 22760 * @return {boolean} 22761 * The current value of userActive when getting 22762 */ 22763 22764 22765 Player.prototype.userActive = function userActive(bool) { 22766 if (bool !== undefined) { 22767 bool = !!bool; 22768 if (bool !== this.userActive_) { 22769 this.userActive_ = bool; 22770 if (bool) { 22771 // If the user was inactive and is now active we want to reset the 22772 // inactivity timer 22773 this.userActivity_ = true; 22774 this.removeClass('vjs-user-inactive'); 22775 this.addClass('vjs-user-active'); 22776 /** 22777 * @event Player#useractive 22778 * @type {EventTarget~Event} 22779 */ 22780 this.trigger('useractive'); 22781 } else { 22782 // We're switching the state to inactive manually, so erase any other 22783 // activity 22784 this.userActivity_ = false;
vendor: 12,911 bytes, lines 22785-23204
22785 22786 // Chrome/Safari/IE have bugs where when you change the cursor it can 22787 // trigger a mousemove event. This causes an issue when you're hiding 22788 // the cursor when the user is inactive, and a mousemove signals user 22789 // activity. Making it impossible to go into inactive mode. Specifically 22790 // this happens in fullscreen when we really need to hide the cursor. 22791 // 22792 // When this gets resolved in ALL browsers it can be removed 22793 // https://code.google.com/p/chromium/issues/detail?id=103041 22794 if (this.tech_) { 22795 this.tech_.one('mousemove', function (e) { 22796 e.stopPropagation(); 22797 e.preventDefault(); 22798 }); 22799 } 22800 22801 this.removeClass('vjs-user-active'); 22802 this.addClass('vjs-user-inactive'); 22803 /** 22804 * @event Player#userinactive 22805 * @type {EventTarget~Event} 22806 */ 22807 this.trigger('userinactive'); 22808 } 22809 } 22810 return; 22811 } 22812 return this.userActive_; 22813 }; 22814 22815 /** 22816 * Listen for user activity based on timeout value 22817 * 22818 * @private 22819 */ 22820 22821 22822 Player.prototype.listenForUserActivity_ = function listenForUserActivity_() { 22823 var mouseInProgress = void 0; 22824 var lastMoveX = void 0; 22825 var lastMoveY = void 0; 22826 var handleActivity = bind(this, this.reportUserActivity); 22827 22828 var handleMouseMove = function handleMouseMove(e) { 22829 // #1068 - Prevent mousemove spamming 22830 // Chrome Bug: https://code.google.com/p/chromium/issues/detail?id=366970 22831 if (e.screenX !== lastMoveX || e.screenY !== lastMoveY) { 22832 lastMoveX = e.screenX; 22833 lastMoveY = e.screenY; 22834 handleActivity(); 22835 } 22836 }; 22837 22838 var handleMouseDown = function handleMouseDown() { 22839 handleActivity(); 22840 // For as long as the they are touching the device or have their mouse down, 22841 // we consider them active even if they're not moving their finger or mouse. 22842 // So we want to continue to update that they are active 22843 this.clearInterval(mouseInProgress); 22844 // Setting userActivity=true now and setting the interval to the same time 22845 // as the activityCheck interval (250) should ensure we never miss the 22846 // next activityCheck 22847 mouseInProgress = this.setInterval(handleActivity, 250); 22848 }; 22849 22850 var handleMouseUp = function handleMouseUp(event) { 22851 handleActivity(); 22852 // Stop the interval that maintains activity if the mouse/touch is down 22853 this.clearInterval(mouseInProgress); 22854 }; 22855 22856 // Any mouse movement will be considered user activity 22857 this.on('mousedown', handleMouseDown); 22858 this.on('mousemove', handleMouseMove); 22859 this.on('mouseup', handleMouseUp); 22860 22861 // Listen for keyboard navigation 22862 // Shouldn't need to use inProgress interval because of key repeat 22863 this.on('keydown', handleActivity); 22864 this.on('keyup', handleActivity); 22865 22866 // Run an interval every 250 milliseconds instead of stuffing everything into 22867 // the mousemove/touchmove function itself, to prevent performance degradation. 22868 // `this.reportUserActivity` simply sets this.userActivity_ to true, which 22869 // then gets picked up by this loop 22870 // http://ejohn.org/blog/learning-from-twitter/ 22871 var inactivityTimeout = void 0; 22872 22873 this.setInterval(function () { 22874 // Check to see if mouse/touch activity has happened 22875 if (this.userActivity_) { 22876 // Reset the activity tracker 22877 this.userActivity_ = false; 22878 22879 // If the user state was inactive, set the state to active 22880 this.userActive(true); 22881 22882 // Clear any existing inactivity timeout to start the timer over 22883 this.clearTimeout(inactivityTimeout); 22884 22885 var timeout = this.options_.inactivityTimeout; 22886 22887 if (timeout > 0) { 22888 // In <timeout> milliseconds, if no more activity has occurred the 22889 // user will be considered inactive 22890 inactivityTimeout = this.setTimeout(function () { 22891 // Protect against the case where the inactivityTimeout can trigger just 22892 // before the next user activity is picked up by the activity check loop 22893 // causing a flicker 22894 if (!this.userActivity_) { 22895 this.userActive(false); 22896 } 22897 }, timeout); 22898 } 22899 } 22900 }, 250); 22901 }; 22902 22903 /** 22904 * Gets or sets the current playback rate. A playback rate of 22905 * 1.0 represents normal speed and 0.5 would indicate half-speed 22906 * playback, for instance. 22907 * 22908 * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-playbackrate 22909 * 22910 * @param {number} [rate] 22911 * New playback rate to set. 22912 * 22913 * @return {number} 22914 * The current playback rate when getting or 1.0 22915 */ 22916 22917 22918 Player.prototype.playbackRate = function playbackRate(rate) { 22919 if (rate !== undefined) { 22920 this.techCall_('setPlaybackRate', rate); 22921 return; 22922 } 22923 22924 if (this.tech_ && this.tech_.featuresPlaybackRate) { 22925 return this.techGet_('playbackRate'); 22926 } 22927 return 1.0; 22928 }; 22929 22930 /** 22931 * Gets or sets the current default playback rate. A default playback rate of 22932 * 1.0 represents normal speed and 0.5 would indicate half-speed playback, for instance. 22933 * defaultPlaybackRate will only represent what the intial playbackRate of a video was, not 22934 * not the current playbackRate. 22935 * 22936 * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-defaultplaybackrate 22937 * 22938 * @param {number} [rate] 22939 * New default playback rate to set. 22940 * 22941 * @return {number|Player} 22942 * - The default playback rate when getting or 1.0 22943 * - the player when setting 22944 */ 22945 22946 22947 Player.prototype.defaultPlaybackRate = function defaultPlaybackRate(rate) { 22948 if (rate !== undefined) { 22949 return this.techCall_('setDefaultPlaybackRate', rate); 22950 } 22951 22952 if (this.tech_ && this.tech_.featuresPlaybackRate) { 22953 return this.techGet_('defaultPlaybackRate'); 22954 } 22955 return 1.0; 22956 }; 22957 22958 /** 22959 * Gets or sets the audio flag 22960 * 22961 * @param {boolean} bool 22962 * - true signals that this is an audio player 22963 * - false signals that this is not an audio player 22964 * 22965 * @return {boolean} 22966 * The current value of isAudio when getting 22967 */ 22968 22969 22970 Player.prototype.isAudio = function isAudio(bool) { 22971 if (bool !== undefined) { 22972 this.isAudio_ = !!bool; 22973 return; 22974 } 22975 22976 return !!this.isAudio_; 22977 }; 22978 22979 /** 22980 * A helper method for adding a {@link TextTrack} to our 22981 * {@link TextTrackList}. 22982 * 22983 * In addition to the W3C settings we allow adding additional info through options. 22984 * 22985 * @see http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-addtexttrack 22986 * 22987 * @param {string} [kind] 22988 * the kind of TextTrack you are adding 22989 * 22990 * @param {string} [label] 22991 * the label to give the TextTrack label 22992 * 22993 * @param {string} [language] 22994 * the language to set on the TextTrack 22995 * 22996 * @return {TextTrack|undefined} 22997 * the TextTrack that was added or undefined 22998 * if there is no tech 22999 */ 23000 23001 23002 Player.prototype.addTextTrack = function addTextTrack(kind, label, language) { 23003 if (this.tech_) { 23004 return this.tech_.addTextTrack(kind, label, language); 23005 } 23006 }; 23007 23008 /** 23009 * Create a remote {@link TextTrack} and an {@link HTMLTrackElement}. It will 23010 * automatically removed from the video element whenever the source changes, unless 23011 * manualCleanup is set to false. 23012 * 23013 * @param {Object} options 23014 * Options to pass to {@link HTMLTrackElement} during creation. See 23015 * {@link HTMLTrackElement} for object properties that you should use. 23016 * 23017 * @param {boolean} [manualCleanup=true] if set to false, the TextTrack will be 23018 * 23019 * @return {HtmlTrackElement} 23020 * the HTMLTrackElement that was created and added 23021 * to the HtmlTrackElementList and the remote 23022 * TextTrackList 23023 * 23024 * @deprecated The default value of the "manualCleanup" parameter will default 23025 * to "false" in upcoming versions of Video.js 23026 */ 23027 23028 23029 Player.prototype.addRemoteTextTrack = function addRemoteTextTrack(options, manualCleanup) { 23030 if (this.tech_) { 23031 return this.tech_.addRemoteTextTrack(options, manualCleanup); 23032 } 23033 }; 23034 23035 /** 23036 * Remove a remote {@link TextTrack} from the respective 23037 * {@link TextTrackList} and {@link HtmlTrackElementList}. 23038 * 23039 * @param {Object} track 23040 * Remote {@link TextTrack} to remove 23041 * 23042 * @return {undefined} 23043 * does not return anything 23044 */ 23045 23046 23047 Player.prototype.removeRemoteTextTrack = function removeRemoteTextTrack() { 23048 var _ref3 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, 23049 _ref3$track = _ref3.track, 23050 track = _ref3$track === undefined ? arguments[0] : _ref3$track; 23051 23052 // destructure the input into an object with a track argument, defaulting to arguments[0] 23053 // default the whole argument to an empty object if nothing was passed in 23054 23055 if (this.tech_) { 23056 return this.tech_.removeRemoteTextTrack(track); 23057 } 23058 }; 23059 23060 /** 23061 * Gets available media playback quality metrics as specified by the W3C's Media 23062 * Playback Quality API. 23063 * 23064 * @see [Spec]{@link https://wicg.github.io/media-playback-quality} 23065 * 23066 * @return {Object|undefined} 23067 * An object with supported media playback quality metrics or undefined if there 23068 * is no tech or the tech does not support it. 23069 */ 23070 23071 23072 Player.prototype.getVideoPlaybackQuality = function getVideoPlaybackQuality() { 23073 return this.techGet_('getVideoPlaybackQuality'); 23074 }; 23075 23076 /** 23077 * Get video width 23078 * 23079 * @return {number} 23080 * current video width 23081 */ 23082 23083 23084 Player.prototype.videoWidth = function videoWidth() { 23085 return this.tech_ && this.tech_.videoWidth && this.tech_.videoWidth() || 0; 23086 }; 23087 23088 /** 23089 * Get video height 23090 * 23091 * @return {number} 23092 * current video height 23093 */ 23094 23095 23096 Player.prototype.videoHeight = function videoHeight() { 23097 return this.tech_ && this.tech_.videoHeight && this.tech_.videoHeight() || 0; 23098 }; 23099 23100 /** 23101 * The player's language code 23102 * NOTE: The language should be set in the player options if you want the 23103 * the controls to be built with a specific language. Changing the lanugage 23104 * later will not update controls text. 23105 * 23106 * @param {string} [code] 23107 * the language code to set the player to 23108 * 23109 * @return {string} 23110 * The current language code when getting 23111 */ 23112 23113 23114 Player.prototype.language = function language(code) { 23115 if (code === undefined) { 23116 return this.language_; 23117 } 23118 23119 this.language_ = String(code).toLowerCase(); 23120 }; 23121 23122 /** 23123 * Get the player's language dictionary 23124 * Merge every time, because a newly added plugin might call videojs.addLanguage() at any time 23125 * Languages specified directly in the player options have precedence 23126 * 23127 * @return {Array} 23128 * An array of of supported languages 23129 */ 23130 23131 23132 Player.prototype.languages = function languages() { 23133 return mergeOptions(Player.prototype.options_.languages, this.languages_); 23134 }; 23135 23136 /** 23137 * returns a JavaScript object reperesenting the current track 23138 * information. **DOES not return it as JSON** 23139 * 23140 * @return {Object} 23141 * Object representing the current of track info 23142 */ 23143 23144 23145 Player.prototype.toJSON = function toJSON() { 23146 var options = mergeOptions(this.options_); 23147 var tracks = options.tracks; 23148 23149 options.tracks = []; 23150 23151 for (var i = 0; i < tracks.length; i++) { 23152 var track = tracks[i]; 23153 23154 // deep merge tracks and null out player so no circular references 23155 track = mergeOptions(track); 23156 track.player = undefined; 23157 options.tracks[i] = track; 23158 } 23159 23160 return options; 23161 }; 23162 23163 /** 23164 * Creates a simple modal dialog (an instance of the {@link ModalDialog} 23165 * component) that immediately overlays the player with arbitrary 23166 * content and removes itself when closed. 23167 * 23168 * @param {string|Function|Element|Array|null} content 23169 * Same as {@link ModalDialog#content}'s param of the same name. 23170 * The most straight-forward usage is to provide a string or DOM 23171 * element. 23172 * 23173 * @param {Object} [options] 23174 * Extra options which will be passed on to the {@link ModalDialog}. 23175 * 23176 * @return {ModalDialog} 23177 * the {@link ModalDialog} that was created 23178 */ 23179 23180 23181 Player.prototype.createModal = function createModal(content, options) { 23182 var _this7 = this; 23183 23184 options = options || {}; 23185 options.content = content || ''; 23186 23187 var modal = new ModalDialog(this, options); 23188 23189 this.addChild(modal); 23190 modal.on('dispose', function () { 23191 _this7.removeChild(modal); 23192 }); 23193 23194 modal.open(); 23195 return modal; 23196 }; 23197 23198 /** 23199 * Gets tag settings 23200 * 23201 * @param {Element} tag 23202 * The player tag 23203 * 23204 * @return {Object}
vendor: 4,271 bytes, lines 23205-23375
23205 * An object containing all of the settings 23206 * for a player tag 23207 */ 23208 23209 23210 Player.getTagSettings = function getTagSettings(tag) { 23211 var baseOptions = { 23212 sources: [], 23213 tracks: [] 23214 }; 23215 23216 var tagOptions = getAttributes(tag); 23217 var dataSetup = tagOptions['data-setup']; 23218 23219 if (hasClass(tag, 'vjs-fluid')) { 23220 tagOptions.fluid = true; 23221 } 23222 23223 // Check if data-setup attr exists. 23224 if (dataSetup !== null) { 23225 // Parse options JSON 23226 // If empty string, make it a parsable json object. 23227 var _safeParseTuple = tuple(dataSetup || '{}'), 23228 err = _safeParseTuple[0], 23229 data = _safeParseTuple[1]; 23230 23231 if (err) { 23232 log$1.error(err); 23233 } 23234 assign(tagOptions, data); 23235 } 23236 23237 assign(baseOptions, tagOptions); 23238 23239 // Get tag children settings 23240 if (tag.hasChildNodes()) { 23241 var children = tag.childNodes; 23242 23243 for (var i = 0, j = children.length; i < j; i++) { 23244 var child = children[i]; 23245 // Change case needed: http://ejohn.org/blog/nodename-case-sensitivity/ 23246 var childName = child.nodeName.toLowerCase(); 23247 23248 if (childName === 'source') { 23249 baseOptions.sources.push(getAttributes(child)); 23250 } else if (childName === 'track') { 23251 baseOptions.tracks.push(getAttributes(child)); 23252 } 23253 } 23254 } 23255 23256 return baseOptions; 23257 }; 23258 23259 /** 23260 * Determine wether or not flexbox is supported 23261 * 23262 * @return {boolean} 23263 * - true if flexbox is supported 23264 * - false if flexbox is not supported 23265 */ 23266 23267 23268 Player.prototype.flexNotSupported_ = function flexNotSupported_() { 23269 var elem = document_1.createElement('i'); 23270 23271 // Note: We don't actually use flexBasis (or flexOrder), but it's one of the more 23272 // common flex features that we can rely on when checking for flex support. 23273 return !('flexBasis' in elem.style || 'webkitFlexBasis' in elem.style || 'mozFlexBasis' in elem.style || 'msFlexBasis' in elem.style || 23274 // IE10-specific (2012 flex spec) 23275 'msFlexOrder' in elem.style); 23276 }; 23277 23278 return Player; 23279}(Component); 23280 23281/** 23282 * Get the {@link VideoTrackList} 23283 * @link https://html.spec.whatwg.org/multipage/embedded-content.html#videotracklist 23284 * 23285 * @return {VideoTrackList} 23286 * the current video track list 23287 * 23288 * @method Player.prototype.videoTracks 23289 */ 23290 23291/** 23292 * Get the {@link AudioTrackList} 23293 * @link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotracklist 23294 * 23295 * @return {AudioTrackList} 23296 * the current audio track list 23297 * 23298 * @method Player.prototype.audioTracks 23299 */ 23300 23301/** 23302 * Get the {@link TextTrackList} 23303 * 23304 * @link http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-texttracks 23305 * 23306 * @return {TextTrackList} 23307 * the current text track list 23308 * 23309 * @method Player.prototype.textTracks 23310 */ 23311 23312/** 23313 * Get the remote {@link TextTrackList} 23314 * 23315 * @return {TextTrackList} 23316 * The current remote text track list 23317 * 23318 * @method Player.prototype.remoteTextTracks 23319 */ 23320 23321/** 23322 * Get the remote {@link HtmlTrackElementList} tracks. 23323 * 23324 * @return {HtmlTrackElementList} 23325 * The current remote text track element list 23326 * 23327 * @method Player.prototype.remoteTextTrackEls 23328 */ 23329 23330ALL.names.forEach(function (name$$1) { 23331 var props = ALL[name$$1]; 23332 23333 Player.prototype[props.getterName] = function () { 23334 if (this.tech_) { 23335 return this.tech_[props.getterName](); 23336 } 23337 23338 // if we have not yet loadTech_, we create {video,audio,text}Tracks_ 23339 // these will be passed to the tech during loading 23340 this[props.privateName] = this[props.privateName] || new props.ListClass(); 23341 return this[props.privateName]; 23342 }; 23343}); 23344 23345/** 23346 * Global player list 23347 * 23348 * @type {Object} 23349 */ 23350Player.players = {}; 23351 23352var navigator$1 = window_1.navigator; 23353 23354/* 23355 * Player instance options, surfaced using options 23356 * options = Player.prototype.options_ 23357 * Make changes in options, not here. 23358 * 23359 * @type {Object} 23360 * @private 23361 */ 23362Player.prototype.options_ = { 23363 // Default order of fallback technology 23364 techOrder: Tech.defaultTechOrder_, 23365 23366 html5: {}, 23367 flash: {}, 23368 23369 // default inactivity timeout 23370 inactivityTimeout: 2000, 23371 23372 // default playback rates 23373 playbackRates: [], 23374 // Add playback rate selection by adding rates 23375 // 'playbackRates': [0.5, 1, 1.5, 2],
vendor: 10,750 bytes, lines 23376-23753
23376 23377 // Included control sets 23378 children: ['mediaLoader', 'posterImage', 'textTrackDisplay', 'loadingSpinner', 'bigPlayButton', 'controlBar', 'errorDisplay', 'textTrackSettings'], 23379 23380 language: navigator$1 && (navigator$1.languages && navigator$1.languages[0] || navigator$1.userLanguage || navigator$1.language) || 'en', 23381 23382 // locales and their language translations 23383 languages: {}, 23384 23385 // Default message to show when a video cannot be played. 23386 notSupportedMessage: 'No compatible source was found for this media.' 23387}; 23388 23389[ 23390/** 23391 * Returns whether or not the player is in the "ended" state. 23392 * 23393 * @return {Boolean} True if the player is in the ended state, false if not. 23394 * @method Player#ended 23395 */ 23396'ended', 23397/** 23398 * Returns whether or not the player is in the "seeking" state. 23399 * 23400 * @return {Boolean} True if the player is in the seeking state, false if not. 23401 * @method Player#seeking 23402 */ 23403'seeking', 23404/** 23405 * Returns the TimeRanges of the media that are currently available 23406 * for seeking to. 23407 * 23408 * @return {TimeRanges} the seekable intervals of the media timeline 23409 * @method Player#seekable 23410 */ 23411'seekable', 23412/** 23413 * Returns the current state of network activity for the element, from 23414 * the codes in the list below. 23415 * - NETWORK_EMPTY (numeric value 0) 23416 * The element has not yet been initialised. All attributes are in 23417 * their initial states. 23418 * - NETWORK_IDLE (numeric value 1) 23419 * The element's resource selection algorithm is active and has 23420 * selected a resource, but it is not actually using the network at 23421 * this time. 23422 * - NETWORK_LOADING (numeric value 2) 23423 * The user agent is actively trying to download data. 23424 * - NETWORK_NO_SOURCE (numeric value 3) 23425 * The element's resource selection algorithm is active, but it has 23426 * not yet found a resource to use. 23427 * 23428 * @see https://html.spec.whatwg.org/multipage/embedded-content.html#network-states 23429 * @return {number} the current network activity state 23430 * @method Player#networkState 23431 */ 23432'networkState', 23433/** 23434 * Returns a value that expresses the current state of the element 23435 * with respect to rendering the current playback position, from the 23436 * codes in the list below. 23437 * - HAVE_NOTHING (numeric value 0) 23438 * No information regarding the media resource is available. 23439 * - HAVE_METADATA (numeric value 1) 23440 * Enough of the resource has been obtained that the duration of the 23441 * resource is available. 23442 * - HAVE_CURRENT_DATA (numeric value 2) 23443 * Data for the immediate current playback position is available. 23444 * - HAVE_FUTURE_DATA (numeric value 3) 23445 * Data for the immediate current playback position is available, as 23446 * well as enough data for the user agent to advance the current 23447 * playback position in the direction of playback. 23448 * - HAVE_ENOUGH_DATA (numeric value 4) 23449 * The user agent estimates that enough data is available for 23450 * playback to proceed uninterrupted. 23451 * 23452 * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-readystate 23453 * @return {number} the current playback rendering state 23454 * @method Player#readyState 23455 */ 23456'readyState'].forEach(function (fn) { 23457 Player.prototype[fn] = function () { 23458 return this.techGet_(fn); 23459 }; 23460}); 23461 23462TECH_EVENTS_RETRIGGER.forEach(function (event) { 23463 Player.prototype['handleTech' + toTitleCase(event) + '_'] = function () { 23464 return this.trigger(event); 23465 }; 23466}); 23467 23468/** 23469 * Fired when the player has initial duration and dimension information 23470 * 23471 * @event Player#loadedmetadata 23472 * @type {EventTarget~Event} 23473 */ 23474 23475/** 23476 * Fired when the player has downloaded data at the current playback position 23477 * 23478 * @event Player#loadeddata 23479 * @type {EventTarget~Event} 23480 */ 23481 23482/** 23483 * Fired when the current playback position has changed * 23484 * During playback this is fired every 15-250 milliseconds, depending on the 23485 * playback technology in use. 23486 * 23487 * @event Player#timeupdate 23488 * @type {EventTarget~Event} 23489 */ 23490 23491/** 23492 * Fired when the volume changes 23493 * 23494 * @event Player#volumechange 23495 * @type {EventTarget~Event} 23496 */ 23497 23498/** 23499 * Reports whether or not a player has a plugin available. 23500 * 23501 * This does not report whether or not the plugin has ever been initialized 23502 * on this player. For that, [usingPlugin]{@link Player#usingPlugin}. 23503 * 23504 * @method Player#hasPlugin 23505 * @param {string} name 23506 * The name of a plugin. 23507 * 23508 * @return {boolean} 23509 * Whether or not this player has the requested plugin available. 23510 */ 23511 23512/** 23513 * Reports whether or not a player is using a plugin by name. 23514 * 23515 * For basic plugins, this only reports whether the plugin has _ever_ been 23516 * initialized on this player. 23517 * 23518 * @method Player#usingPlugin 23519 * @param {string} name 23520 * The name of a plugin. 23521 * 23522 * @return {boolean} 23523 * Whether or not this player is using the requested plugin. 23524 */ 23525 23526Component.registerComponent('Player', Player); 23527 23528/** 23529 * @file plugin.js 23530 */ 23531/** 23532 * The base plugin name. 23533 * 23534 * @private 23535 * @constant 23536 * @type {string} 23537 */ 23538var BASE_PLUGIN_NAME = 'plugin'; 23539 23540/** 23541 * The key on which a player's active plugins cache is stored. 23542 * 23543 * @private 23544 * @constant 23545 * @type {string} 23546 */ 23547var PLUGIN_CACHE_KEY = 'activePlugins_'; 23548 23549/** 23550 * Stores registered plugins in a private space. 23551 * 23552 * @private 23553 * @type {Object} 23554 */ 23555var pluginStorage = {}; 23556 23557/** 23558 * Reports whether or not a plugin has been registered. 23559 * 23560 * @private 23561 * @param {string} name 23562 * The name of a plugin. 23563 * 23564 * @returns {boolean} 23565 * Whether or not the plugin has been registered. 23566 */ 23567var pluginExists = function pluginExists(name) { 23568 return pluginStorage.hasOwnProperty(name); 23569}; 23570 23571/** 23572 * Get a single registered plugin by name. 23573 * 23574 * @private 23575 * @param {string} name 23576 * The name of a plugin. 23577 * 23578 * @returns {Function|undefined} 23579 * The plugin (or undefined). 23580 */ 23581var getPlugin = function getPlugin(name) { 23582 return pluginExists(name) ? pluginStorage[name] : undefined; 23583}; 23584 23585/** 23586 * Marks a plugin as "active" on a player. 23587 * 23588 * Also, ensures that the player has an object for tracking active plugins. 23589 * 23590 * @private 23591 * @param {Player} player 23592 * A Video.js player instance. 23593 * 23594 * @param {string} name 23595 * The name of a plugin. 23596 */ 23597var markPluginAsActive = function markPluginAsActive(player, name) { 23598 player[PLUGIN_CACHE_KEY] = player[PLUGIN_CACHE_KEY] || {}; 23599 player[PLUGIN_CACHE_KEY][name] = true; 23600}; 23601 23602/** 23603 * Triggers a pair of plugin setup events. 23604 * 23605 * @private 23606 * @param {Player} player 23607 * A Video.js player instance. 23608 * 23609 * @param {Plugin~PluginEventHash} hash 23610 * A plugin event hash. 23611 * 23612 * @param {Boolean} [before] 23613 * If true, prefixes the event name with "before". In other words, 23614 * use this to trigger "beforepluginsetup" instead of "pluginsetup". 23615 */ 23616var triggerSetupEvent = function triggerSetupEvent(player, hash, before) { 23617 var eventName = (before ? 'before' : '') + 'pluginsetup'; 23618 23619 player.trigger(eventName, hash); 23620 player.trigger(eventName + ':' + hash.name, hash); 23621}; 23622 23623/** 23624 * Takes a basic plugin function and returns a wrapper function which marks 23625 * on the player that the plugin has been activated. 23626 * 23627 * @private 23628 * @param {string} name 23629 * The name of the plugin. 23630 * 23631 * @param {Function} plugin 23632 * The basic plugin. 23633 * 23634 * @returns {Function} 23635 * A wrapper function for the given plugin. 23636 */ 23637var createBasicPlugin = function createBasicPlugin(name, plugin) { 23638 var basicPluginWrapper = function basicPluginWrapper() { 23639 23640 // We trigger the "beforepluginsetup" and "pluginsetup" events on the player 23641 // regardless, but we want the hash to be consistent with the hash provided 23642 // for advanced plugins. 23643 // 23644 // The only potentially counter-intuitive thing here is the `instance` in 23645 // the "pluginsetup" event is the value returned by the `plugin` function. 23646 triggerSetupEvent(this, { name: name, plugin: plugin, instance: null }, true); 23647 23648 var instance = plugin.apply(this, arguments); 23649 23650 markPluginAsActive(this, name); 23651 triggerSetupEvent(this, { name: name, plugin: plugin, instance: instance }); 23652 23653 return instance; 23654 }; 23655 23656 Object.keys(plugin).forEach(function (prop) { 23657 basicPluginWrapper[prop] = plugin[prop]; 23658 }); 23659 23660 return basicPluginWrapper; 23661}; 23662 23663/** 23664 * Takes a plugin sub-class and returns a factory function for generating 23665 * instances of it. 23666 * 23667 * This factory function will replace itself with an instance of the requested 23668 * sub-class of Plugin. 23669 * 23670 * @private 23671 * @param {string} name 23672 * The name of the plugin. 23673 * 23674 * @param {Plugin} PluginSubClass 23675 * The advanced plugin. 23676 * 23677 * @returns {Function} 23678 */ 23679var createPluginFactory = function createPluginFactory(name, PluginSubClass) { 23680 23681 // Add a `name` property to the plugin prototype so that each plugin can 23682 // refer to itself by name. 23683 PluginSubClass.prototype.name = name; 23684 23685 return function () { 23686 triggerSetupEvent(this, { name: name, plugin: PluginSubClass, instance: null }, true); 23687 23688 for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { 23689 args[_key] = arguments[_key]; 23690 } 23691 23692 var instance = new (Function.prototype.bind.apply(PluginSubClass, [null].concat([this].concat(args))))(); 23693 23694 // The plugin is replaced by a function that returns the current instance. 23695 this[name] = function () { 23696 return instance; 23697 }; 23698 23699 triggerSetupEvent(this, instance.getEventHash()); 23700 23701 return instance; 23702 }; 23703}; 23704 23705/** 23706 * Parent class for all advanced plugins. 23707 * 23708 * @mixes module:evented~EventedMixin 23709 * @mixes module:stateful~StatefulMixin 23710 * @fires Player#beforepluginsetup 23711 * @fires Player#beforepluginsetup:$name 23712 * @fires Player#pluginsetup 23713 * @fires Player#pluginsetup:$name 23714 * @listens Player#dispose 23715 * @throws {Error} 23716 * If attempting to instantiate the base {@link Plugin} class 23717 * directly instead of via a sub-class. 23718 */ 23719 23720var Plugin = function () { 23721 23722 /** 23723 * Creates an instance of this class. 23724 * 23725 * Sub-classes should call `super` to ensure plugins are properly initialized. 23726 * 23727 * @param {Player} player 23728 * A Video.js player instance. 23729 */ 23730 function Plugin(player) { 23731 classCallCheck(this, Plugin); 23732 23733 if (this.constructor === Plugin) { 23734 throw new Error('Plugin must be sub-classed; not directly instantiated.'); 23735 } 23736 23737 this.player = player; 23738 23739 // Make this object evented, but remove the added `trigger` method so we 23740 // use the prototype version instead. 23741 evented(this); 23742 delete this.trigger; 23743 23744 stateful(this, this.constructor.defaultState); 23745 markPluginAsActive(player, this.name); 23746 23747 // Auto-bind the dispose method so we can use it as a listener and unbind 23748 // it later easily. 23749 this.dispose = bind(this, this.dispose); 23750 23751 // If the player is disposed, dispose the plugin. 23752 player.on('dispose', this.dispose); 23753 }
vendor: 7,496 bytes, lines 23753-24029
23753 23754 23755 /** 23756 * Each event triggered by plugins includes a hash of additional data with 23757 * conventional properties. 23758 * 23759 * This returns that object or mutates an existing hash. 23760 * 23761 * @param {Object} [hash={}] 23762 * An object to be used as event an event hash. 23763 * 23764 * @returns {Plugin~PluginEventHash} 23765 * An event hash object with provided properties mixed-in. 23766 */ 23767 23768 23769 Plugin.prototype.getEventHash = function getEventHash() { 23770 var hash = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; 23771 23772 hash.name = this.name; 23773 hash.plugin = this.constructor; 23774 hash.instance = this; 23775 return hash; 23776 }; 23777 23778 /** 23779 * Triggers an event on the plugin object and overrides 23780 * {@link module:evented~EventedMixin.trigger|EventedMixin.trigger}. 23781 * 23782 * @param {string|Object} event 23783 * An event type or an object with a type property. 23784 * 23785 * @param {Object} [hash={}] 23786 * Additional data hash to merge with a 23787 * {@link Plugin~PluginEventHash|PluginEventHash}. 23788 * 23789 * @returns {boolean} 23790 * Whether or not default was prevented. 23791 */ 23792 23793 23794 Plugin.prototype.trigger = function trigger$$1(event) { 23795 var hash = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 23796 23797 return trigger(this.eventBusEl_, event, this.getEventHash(hash)); 23798 }; 23799 23800 /** 23801 * Handles "statechanged" events on the plugin. No-op by default, override by 23802 * subclassing. 23803 * 23804 * @abstract 23805 * @param {Event} e 23806 * An event object provided by a "statechanged" event. 23807 * 23808 * @param {Object} e.changes 23809 * An object describing changes that occurred with the "statechanged" 23810 * event. 23811 */ 23812 23813 23814 Plugin.prototype.handleStateChanged = function handleStateChanged(e) {}; 23815 23816 /** 23817 * Disposes a plugin. 23818 * 23819 * Subclasses can override this if they want, but for the sake of safety, 23820 * it's probably best to subscribe the "dispose" event. 23821 * 23822 * @fires Plugin#dispose 23823 */ 23824 23825 23826 Plugin.prototype.dispose = function dispose() { 23827 var name = this.name, 23828 player = this.player; 23829 23830 /** 23831 * Signals that a advanced plugin is about to be disposed. 23832 * 23833 * @event Plugin#dispose 23834 * @type {EventTarget~Event} 23835 */ 23836 23837 this.trigger('dispose'); 23838 this.off(); 23839 player.off('dispose', this.dispose); 23840 23841 // Eliminate any possible sources of leaking memory by clearing up 23842 // references between the player and the plugin instance and nulling out 23843 // the plugin's state and replacing methods with a function that throws. 23844 player[PLUGIN_CACHE_KEY][name] = false; 23845 this.player = this.state = null; 23846 23847 // Finally, replace the plugin name on the player with a new factory 23848 // function, so that the plugin is ready to be set up again. 23849 player[name] = createPluginFactory(name, pluginStorage[name]); 23850 }; 23851 23852 /** 23853 * Determines if a plugin is a basic plugin (i.e. not a sub-class of `Plugin`). 23854 * 23855 * @param {string|Function} plugin 23856 * If a string, matches the name of a plugin. If a function, will be 23857 * tested directly. 23858 * 23859 * @returns {boolean} 23860 * Whether or not a plugin is a basic plugin. 23861 */ 23862 23863 23864 Plugin.isBasic = function isBasic(plugin) { 23865 var p = typeof plugin === 'string' ? getPlugin(plugin) : plugin; 23866 23867 return typeof p === 'function' && !Plugin.prototype.isPrototypeOf(p.prototype); 23868 }; 23869 23870 /** 23871 * Register a Video.js plugin. 23872 * 23873 * @param {string} name 23874 * The name of the plugin to be registered. Must be a string and 23875 * must not match an existing plugin or a method on the `Player` 23876 * prototype. 23877 * 23878 * @param {Function} plugin 23879 * A sub-class of `Plugin` or a function for basic plugins. 23880 * 23881 * @returns {Function} 23882 * For advanced plugins, a factory function for that plugin. For 23883 * basic plugins, a wrapper function that initializes the plugin. 23884 */ 23885 23886 23887 Plugin.registerPlugin = function registerPlugin(name, plugin) { 23888 if (typeof name !== 'string') { 23889 throw new Error('Illegal plugin name, "' + name + '", must be a string, was ' + (typeof name === 'undefined' ? 'undefined' : _typeof(name)) + '.'); 23890 } 23891 23892 if (pluginExists(name)) { 23893 log$1.warn('A plugin named "' + name + '" already exists. You may want to avoid re-registering plugins!'); 23894 } else if (Player.prototype.hasOwnProperty(name)) { 23895 throw new Error('Illegal plugin name, "' + name + '", cannot share a name with an existing player method!'); 23896 } 23897 23898 if (typeof plugin !== 'function') { 23899 throw new Error('Illegal plugin for "' + name + '", must be a function, was ' + (typeof plugin === 'undefined' ? 'undefined' : _typeof(plugin)) + '.'); 23900 } 23901 23902 pluginStorage[name] = plugin; 23903 23904 // Add a player prototype method for all sub-classed plugins (but not for 23905 // the base Plugin class). 23906 if (name !== BASE_PLUGIN_NAME) { 23907 if (Plugin.isBasic(plugin)) { 23908 Player.prototype[name] = createBasicPlugin(name, plugin); 23909 } else { 23910 Player.prototype[name] = createPluginFactory(name, plugin); 23911 } 23912 } 23913 23914 return plugin; 23915 }; 23916 23917 /** 23918 * De-register a Video.js plugin. 23919 * 23920 * @param {string} name 23921 * The name of the plugin to be deregistered. 23922 */ 23923 23924 23925 Plugin.deregisterPlugin = function deregisterPlugin(name) { 23926 if (name === BASE_PLUGIN_NAME) { 23927 throw new Error('Cannot de-register base plugin.'); 23928 } 23929 if (pluginExists(name)) { 23930 delete pluginStorage[name]; 23931 delete Player.prototype[name]; 23932 } 23933 }; 23934 23935 /** 23936 * Gets an object containing multiple Video.js plugins. 23937 * 23938 * @param {Array} [names] 23939 * If provided, should be an array of plugin names. Defaults to _all_ 23940 * plugin names. 23941 * 23942 * @returns {Object|undefined} 23943 * An object containing plugin(s) associated with their name(s) or 23944 * `undefined` if no matching plugins exist). 23945 */ 23946 23947 23948 Plugin.getPlugins = function getPlugins() { 23949 var names = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : Object.keys(pluginStorage); 23950 23951 var result = void 0; 23952 23953 names.forEach(function (name) { 23954 var plugin = getPlugin(name); 23955 23956 if (plugin) { 23957 result = result || {}; 23958 result[name] = plugin; 23959 } 23960 }); 23961 23962 return result; 23963 }; 23964 23965 /** 23966 * Gets a plugin's version, if available 23967 * 23968 * @param {string} name 23969 * The name of a plugin. 23970 * 23971 * @returns {string} 23972 * The plugin's version or an empty string. 23973 */ 23974 23975 23976 Plugin.getPluginVersion = function getPluginVersion(name) { 23977 var plugin = getPlugin(name); 23978 23979 return plugin && plugin.VERSION || ''; 23980 }; 23981 23982 return Plugin; 23983}(); 23984 23985/** 23986 * Gets a plugin by name if it exists. 23987 * 23988 * @static 23989 * @method getPlugin 23990 * @memberOf Plugin 23991 * @param {string} name 23992 * The name of a plugin. 23993 * 23994 * @returns {Function|undefined} 23995 * The plugin (or `undefined`). 23996 */ 23997 23998 23999Plugin.getPlugin = getPlugin; 24000 24001/** 24002 * The name of the base plugin class as it is registered. 24003 * 24004 * @type {string} 24005 */ 24006Plugin.BASE_PLUGIN_NAME = BASE_PLUGIN_NAME; 24007 24008Plugin.registerPlugin(BASE_PLUGIN_NAME, Plugin); 24009 24010/** 24011 * Documented in player.js 24012 * 24013 * @ignore 24014 */ 24015Player.prototype.usingPlugin = function (name) { 24016 return !!this[PLUGIN_CACHE_KEY] && this[PLUGIN_CACHE_KEY][name] === true; 24017}; 24018 24019/** 24020 * Documented in player.js 24021 * 24022 * @ignore 24023 */ 24024Player.prototype.hasPlugin = function (name) { 24025 return !!pluginExists(name); 24026}; 24027 24028/** 24029 *
vendor: 3,442 bytes, lines 24029-24162
24029Signals that a plugin is about to be set up on a player. 24030 * 24031 * @event Player#beforepluginsetup 24032 * @type {Plugin~PluginEventHash} 24033 */ 24034 24035/** 24036 * Signals that a plugin is about to be set up on a player - by name. The name 24037 * is the name of the plugin. 24038 * 24039 * @event Player#beforepluginsetup:$name 24040 * @type {Plugin~PluginEventHash} 24041 */ 24042 24043/** 24044 * Signals that a plugin has just been set up on a player. 24045 * 24046 * @event Player#pluginsetup 24047 * @type {Plugin~PluginEventHash} 24048 */ 24049 24050/** 24051 * Signals that a plugin has just been set up on a player - by name. The name 24052 * is the name of the plugin. 24053 * 24054 * @event Player#pluginsetup:$name 24055 * @type {Plugin~PluginEventHash} 24056 */ 24057 24058/** 24059 * @typedef {Object} Plugin~PluginEventHash 24060 * 24061 * @property {string} instance 24062 * For basic plugins, the return value of the plugin function. For 24063 * advanced plugins, the plugin instance on which the event is fired. 24064 * 24065 * @property {string} name 24066 * The name of the plugin. 24067 * 24068 * @property {string} plugin 24069 * For basic plugins, the plugin function. For advanced plugins, the 24070 * plugin class/constructor. 24071 */ 24072 24073/** 24074 * @file extend.js 24075 * @module extend 24076 */ 24077 24078/** 24079 * A combination of node inherits and babel's inherits (after transpile). 24080 * Both work the same but node adds `super_` to the subClass 24081 * and Bable adds the superClass as __proto__. Both seem useful. 24082 * 24083 * @param {Object} subClass 24084 * The class to inherit to 24085 * 24086 * @param {Object} superClass 24087 * The class to inherit from 24088 * 24089 * @private 24090 */ 24091var _inherits = function _inherits(subClass, superClass) { 24092 if (typeof superClass !== 'function' && superClass !== null) { 24093 throw new TypeError('Super expression must either be null or a function, not ' + (typeof superClass === 'undefined' ? 'undefined' : _typeof(superClass))); 24094 } 24095 24096 subClass.prototype = Object.create(superClass && superClass.prototype, { 24097 constructor: { 24098 value: subClass, 24099 enumerable: false, 24100 writable: true, 24101 configurable: true 24102 } 24103 }); 24104 24105 if (superClass) { 24106 // node 24107 subClass.super_ = superClass; 24108 } 24109}; 24110 24111/** 24112 * Function for subclassing using the same inheritance that 24113 * videojs uses internally 24114 * 24115 * @static 24116 * @const 24117 * 24118 * @param {Object} superClass 24119 * The class to inherit from 24120 * 24121 * @param {Object} [subClassMethods={}] 24122 * The class to inherit to 24123 * 24124 * @return {Object} 24125 * The new object with subClassMethods that inherited superClass. 24126 */ 24127var extendFn = function extendFn(superClass) { 24128 var subClassMethods = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 24129 24130 var subClass = function subClass() { 24131 superClass.apply(this, arguments); 24132 }; 24133 24134 var methods = {}; 24135 24136 if ((typeof subClassMethods === 'undefined' ? 'undefined' : _typeof(subClassMethods)) === 'object') { 24137 if (subClassMethods.constructor !== Object.prototype.constructor) { 24138 subClass = subClassMethods.constructor; 24139 } 24140 methods = subClassMethods; 24141 } else if (typeof subClassMethods === 'function') { 24142 subClass = subClassMethods; 24143 } 24144 24145 _inherits(subClass, superClass); 24146 24147 // Extend subObj's prototype with functions and other properties from props 24148 for (var name in methods) { 24149 if (methods.hasOwnProperty(name)) { 24150 subClass.prototype[name] = methods[name]; 24151 } 24152 } 24153 24154 return subClass; 24155}; 24156 24157/** 24158 * @file video.js 24159 * @module videojs 24160 */ 24161// Include the built-in techs 24162// HTML5 Element Shim for IE8
24163if (typeof HTMLVideoElement === 'undefined' && isReal()) { 24164 document_1.createElement('video'); 24165 document_1.createElement('audio'); 24166 document_1.createElement('track'); 24167} 24168 24169/** 24170 * Doubles as the main function for users to create a player instance and also 24171 * the main library object. 24172 * The `videojs` function can be used to initialize or retrieve a player. 24173 * 24174 * @param {string|Element} id 24175 * Video element or video element ID 24176 * 24177 * @param {Object} [options] 24178 * Optional options object for config/settings 24179 * 24180 * @param {Component~ReadyCallback} [ready] 24181 * Optional ready callback 24182 * 24183 * @return {Player} 24184 * A player instance 24185 */ 24186function videojs(id, options, ready) { 24187 var tag = void 0; 24188 24189 // Allow for element or ID to be passed in 24190 // String ID 24191 if (typeof id === 'string') { 24192 var players = videojs.getPlayers(); 24193 24194 // Adjust for jQuery ID syntax 24195 if (id.indexOf('#') === 0) { 24196 id = id.slice(1); 24197 } 24198 24199 // If a player instance has already been created for this ID return it. 24200 if (players[id]) { 24201 24202 // If options or ready function are passed, warn 24203 if (options) { 24204 log$1.warn('Player "' + id + '" is already initialised. Options will not be applied.'); 24205 } 24206 24207 if (ready) { 24208 players[id].ready(ready); 24209 } 24210 24211 return players[id]; 24212 } 24213 24214 // Otherwise get element for ID 24215 tag = $('#' + id); 24216 24217 // ID is a media element 24218 } else { 24219 tag = id; 24220 } 24221 24222 // Check for a useable element 24223 // re: nodeName, could be a box div also 24224 if (!tag || !tag.nodeName) { 24225 throw new TypeError('The element or ID supplied is not valid. (videojs)'); 24226 } 24227 24228 // Element may have a player attr referring to an already created player instance. 24229 // If so return that otherwise set up a new player below 24230 if (tag.player || Player.players[tag.playerId]) { 24231 return tag.player || Player.players[tag.playerId]; 24232 } 24233 24234 options = options || {}; 24235 24236 videojs.hooks('beforesetup').forEach(function (hookFunction) { 24237 var opts = hookFunction(tag, mergeOptions(options)); 24238 24239 if (!isObject(opts) || Array.isArray(opts)) { 24240 log$1.error('please return an object in beforesetup hooks'); 24241 return; 24242 } 24243 24244 options = mergeOptions(options, opts); 24245 }); 24246 24247 var PlayerComponent = Component.getComponent('Player'); 24248 // If not, set up a new player 24249 var player = new PlayerComponent(tag, options, ready); 24250 24251 videojs.hooks('setup').forEach(function (hookFunction) { 24252 return hookFunction(player); 24253 }); 24254 24255 return player; 24256} 24257 24258/** 24259 * An Object that contains lifecycle hooks as keys which point to an array 24260 * of functions that are run when a lifecycle is triggered 24261 */ 24262videojs.hooks_ = {}; 24263 24264/** 24265 * Get a list of hooks for a specific lifecycle 24266 * @function videojs.hooks 24267 * 24268 * @param {string} type 24269 * the lifecyle to get hooks from 24270 * 24271 * @param {Function} [fn] 24272 * Optionally add a hook to the lifecycle that your are getting. 24273 * 24274 * @return {Array} 24275 * an array of hooks, or an empty array if there are none. 24276 */ 24277videojs.hooks = function (type, fn) { 24278 videojs.hooks_[type] = videojs.hooks_[type] || []; 24279 if (fn) { 24280 videojs.hooks_[type] = videojs.hooks_[type].concat(fn); 24281 } 24282 return videojs.hooks_[type]; 24283}; 24284 24285/** 24286 * Add a function hook to a specific videojs lifecycle. 24287 * 24288 * @param {string} type 24289 * the lifecycle to hook the function to. 24290 * 24291 * @param {Function|Function[]} 24292 * The function or array of functions to attach. 24293 */ 24294videojs.hook = function (type, fn) { 24295 videojs.hooks(type, fn); 24296}; 24297 24298/** 24299 * Remove a hook from a specific videojs lifecycle. 24300 * 24301 * @param {string} type 24302 * the lifecycle that the function hooked to 24303 * 24304 * @param {Function} fn 24305 * The hooked function to remove 24306 * 24307 * @return {boolean} 24308 * The function that was removed or undef 24309 */ 24310videojs.removeHook = function (type, fn) { 24311 var index = videojs.hooks(type).indexOf(fn); 24312 24313 if (index <= -1) { 24314 return false; 24315 } 24316 24317 videojs.hooks_[type] = videojs.hooks_[type].slice(); 24318 videojs.hooks_[type].splice(index, 1); 24319 24320 return true; 24321}; 24322 24323// Add default styles
vendor: 4,925 bytes, lines 24324-24508
24324if (window_1.VIDEOJS_NO_DYNAMIC_STYLE !== true && isReal()) { 24325 var style = $('.vjs-styles-defaults'); 24326 24327 if (!style) { 24328 style = createStyleElement('vjs-styles-defaults'); 24329 var head = $('head'); 24330 24331 if (head) { 24332 head.insertBefore(style, head.firstChild); 24333 } 24334 setTextContent(style, '\n .video-js {\n width: 300px;\n height: 150px;\n }\n\n .vjs-fluid {\n padding-top: 56.25%\n }\n '); 24335 } 24336} 24337 24338// Run Auto-load players 24339// You have to wait at least once in case this script is loaded after your 24340// video in the DOM (weird behavior only with minified version) 24341autoSetupTimeout(1, videojs); 24342 24343/** 24344 * Current software version. Follows semver. 24345 * 24346 * @type {string} 24347 */ 24348videojs.VERSION = version; 24349 24350/** 24351 * The global options object. These are the settings that take effect 24352 * if no overrides are specified when the player is created. 24353 * 24354 * @type {Object} 24355 */ 24356videojs.options = Player.prototype.options_; 24357 24358/** 24359 * Get an object with the currently created players, keyed by player ID 24360 * 24361 * @return {Object} 24362 * The created players 24363 */ 24364videojs.getPlayers = function () { 24365 return Player.players; 24366}; 24367 24368/** 24369 * Expose players object. 24370 * 24371 * @memberOf videojs 24372 * @property {Object} players 24373 */ 24374videojs.players = Player.players; 24375 24376/** 24377 * Get a component class object by name 24378 * 24379 * @borrows Component.getComponent as videojs.getComponent 24380 */ 24381videojs.getComponent = Component.getComponent; 24382 24383/** 24384 * Register a component so it can referred to by name. Used when adding to other 24385 * components, either through addChild `component.addChild('myComponent')` or through 24386 * default children options `{ children: ['myComponent'] }`. 24387 * 24388 * > NOTE: You could also just initialize the component before adding. 24389 * `component.addChild(new MyComponent());` 24390 * 24391 * @param {string} name 24392 * The class name of the component 24393 * 24394 * @param {Component} comp 24395 * The component class 24396 * 24397 * @return {Component} 24398 * The newly registered component 24399 */ 24400videojs.registerComponent = function (name$$1, comp) { 24401 if (Tech.isTech(comp)) { 24402 log$1.warn('The ' + name$$1 + ' tech was registered as a component. It should instead be registered using videojs.registerTech(name, tech)'); 24403 } 24404 24405 Component.registerComponent.call(Component, name$$1, comp); 24406}; 24407 24408/** 24409 * Get a Tech class object by name 24410 * 24411 * @borrows Tech.getTech as videojs.getTech 24412 */ 24413videojs.getTech = Tech.getTech; 24414 24415/** 24416 * Register a Tech so it can referred to by name. 24417 * This is used in the tech order for the player. 24418 * 24419 * @borrows Tech.registerTech as videojs.registerTech 24420 */ 24421videojs.registerTech = Tech.registerTech; 24422 24423videojs.use = use; 24424 24425/** 24426 * A suite of browser and device tests from {@link browser}. 24427 * 24428 * @type {Object} 24429 * @private 24430 */ 24431videojs.browser = browser; 24432 24433/** 24434 * Whether or not the browser supports touch events. Included for backward 24435 * compatibility with 4.x, but deprecated. Use `videojs.browser.TOUCH_ENABLED` 24436 * instead going forward. 24437 * 24438 * @deprecated since version 5.0 24439 * @type {boolean} 24440 */ 24441videojs.TOUCH_ENABLED = TOUCH_ENABLED; 24442 24443/** 24444 * Subclass an existing class 24445 * Mimics ES6 subclassing with the `extend` keyword 24446 * 24447 * @borrows extend:extendFn as videojs.extend 24448 */ 24449videojs.extend = extendFn; 24450 24451/** 24452 * Merge two options objects recursively 24453 * Performs a deep merge like lodash.merge but **only merges plain objects** 24454 * (not arrays, elements, anything else) 24455 * Other values will be copied directly from the second object. 24456 * 24457 * @borrows merge-options:mergeOptions as videojs.mergeOptions 24458 */ 24459videojs.mergeOptions = mergeOptions; 24460 24461/** 24462 * Change the context (this) of a function 24463 * 24464 * > NOTE: as of v5.0 we require an ES5 shim, so you should use the native 24465 * `function() {}.bind(newContext);` instead of this. 24466 * 24467 * @borrows fn:bind as videojs.bind 24468 */ 24469videojs.bind = bind; 24470 24471/** 24472 * Register a Video.js plugin. 24473 * 24474 * @borrows plugin:registerPlugin as videojs.registerPlugin 24475 * @method registerPlugin 24476 * 24477 * @param {string} name 24478 * The name of the plugin to be registered. Must be a string and 24479 * must not match an existing plugin or a method on the `Player` 24480 * prototype. 24481 * 24482 * @param {Function} plugin 24483 * A sub-class of `Plugin` or a function for basic plugins. 24484 * 24485 * @return {Function} 24486 * For advanced plugins, a factory function for that plugin. For 24487 * basic plugins, a wrapper function that initializes the plugin. 24488 */ 24489videojs.registerPlugin = Plugin.registerPlugin; 24490 24491/** 24492 * Deprecated method to register a plugin with Video.js 24493 * 24494 * @deprecated 24495 * videojs.plugin() is deprecated; use videojs.registerPlugin() instead 24496 * 24497 * @param {string} name 24498 * The plugin name 24499 * 24500 * @param {Plugin|Function} plugin 24501 * The plugin sub-class or function 24502 */ 24503videojs.plugin = function (name$$1, plugin) { 24504 log$1.warn('videojs.plugin() is deprecated; use videojs.registerPlugin() instead'); 24505 return Plugin.registerPlugin(name$$1, plugin); 24506}; 24507 24508/**
24509 * Gets an object containing multiple Video.js plugins. 24510 * 24511 * @param {Array} [names] 24512 * If provided, should be an array of plugin names. Defaults to _all_ 24513 * plugin names. 24514 * 24515 * @return {Object|undefined} 24516 * An object containing plugin(s) associated with their name(s) or 24517 * `undefined` if no matching plugins exist). 24518 */ 24519videojs.getPlugins = Plugin.getPlugins; 24520 24521/** 24522 * Gets a plugin by name if it exists. 24523 * 24524 * @param {string} name 24525 * The name of a plugin. 24526 * 24527 * @return {Function|undefined} 24528 * The plugin (or `undefined`). 24529 */ 24530videojs.getPlugin = Plugin.getPlugin; 24531 24532/** 24533 * Gets a plugin's version, if available 24534 * 24535 * @param {string} name 24536 * The name of a plugin. 24537 * 24538 * @return {string} 24539 * The plugin's version or an empty string. 24540 */ 24541videojs.getPluginVersion = Plugin.getPluginVersion; 24542 24543/** 24544 * Adding languages so that they're available to all players. 24545 * Example: `videojs.addLanguage('es', { 'Hello': 'Hola' });` 24546 * 24547 * @param {string} code 24548 * The language code or dictionary property 24549 * 24550 * @param {Object} data 24551 * The data values to be translated 24552 * 24553 * @return {Object} 24554 * The resulting language dictionary object 24555 */ 24556videojs.addLanguage = function (code, data) { 24557 var _mergeOptions; 24558 24559 code = ('' + code).toLowerCase(); 24560 24561 videojs.options.languages = mergeOptions(videojs.options.languages, (_mergeOptions = {}, _mergeOptions[code] = data, _mergeOptions)); 24562 24563 return videojs.options.languages[code]; 24564}; 24565 24566/** 24567 * Log messages 24568 * 24569 * @borrows log:log as videojs.log 24570 */ 24571videojs.log = log$1; 24572 24573/** 24574 * Creates an emulated TimeRange object. 24575 * 24576 * @borrows time-ranges:createTimeRanges as videojs.createTimeRange 24577 */ 24578/** 24579 * @borrows time-ranges:createTimeRanges as videojs.createTimeRanges 24580 */ 24581videojs.createTimeRange = videojs.createTimeRanges = createTimeRanges; 24582 24583/** 24584 * Format seconds as a time string, H:MM:SS or M:SS 24585 * Supplying a guide (in seconds) will force a number of leading zeros 24586 * to cover the length of the guide 24587 * 24588 * @borrows format-time:formatTime as videojs.formatTime 24589 */ 24590videojs.formatTime = formatTime; 24591 24592/** 24593 * Resolve and parse the elements of a URL 24594 * 24595 * @borrows url:parseUrl as videojs.parseUrl 24596 */ 24597videojs.parseUrl = parseUrl; 24598 24599/** 24600 * Returns whether the url passed is a cross domain request or not. 24601 * 24602 * @borrows url:isCrossOrigin as videojs.isCrossOrigin 24603 */ 24604videojs.isCrossOrigin = isCrossOrigin; 24605 24606/** 24607 * Event target class. 24608 * 24609 * @borrows EventTarget as videojs.EventTarget 24610 */ 24611videojs.EventTarget = EventTarget; 24612 24613/** 24614 * Add an event listener to element 24615 * It stores the handler function in a separate cache object 24616 * and adds a generic handler to the element's event, 24617 * along with a unique id (guid) to the element. 24618 * 24619 * @borrows events:on as videojs.on 24620 */ 24621videojs.on = on; 24622 24623/** 24624 * Trigger a listener only once for an event 24625 * 24626 * @borrows events:one as videojs.one 24627 */ 24628videojs.one = one; 24629 24630/** 24631 * Removes event listeners from an element 24632 * 24633 * @borrows events:off as videojs.off 24634 */ 24635videojs.off = off; 24636 24637/** 24638 * Trigger an event for an element 24639 * 24640 * @borrows events:trigger as videojs.trigger 24641 */ 24642videojs.trigger = trigger; 24643 24644/** 24645 * A cross-browser XMLHttpRequest wrapper. Here's a simple example: 24646 * 24647 * @param {Object} options 24648 * settings for the request. 24649 * 24650 * @return {XMLHttpRequest|XDomainRequest} 24651 * The request object. 24652 * 24653 * @see https://github.com/Raynos/xhr 24654 */ 24655videojs.xhr = xhr; 24656 24657/** 24658 * TextTrack class 24659 * 24660 * @borrows TextTrack as videojs.TextTrack 24661 */ 24662videojs.TextTrack = TextTrack; 24663 24664/** 24665 * export the AudioTrack class so that source handlers can create 24666 * AudioTracks and then add them to the players AudioTrackList 24667 * 24668 * @borrows AudioTrack as videojs.AudioTrack 24669 */ 24670videojs.AudioTrack = AudioTrack; 24671 24672/** 24673 * export the VideoTrack class so that source handlers can create 24674 * VideoTracks and then add them to the players VideoTrackList 24675 * 24676 * @borrows VideoTrack as videojs.VideoTrack 24677 */ 24678videojs.VideoTrack = VideoTrack; 24679 24680/** 24681 * Determines, via duck typing, whether or not a value is a DOM element. 24682 * 24683 * @borrows dom:isEl as videojs.isEl 24684 * @deprecated Use videojs.dom.isEl() instead 24685 */ 24686
24687/** 24688 * Determines, via duck typing, whether or not a value is a text node. 24689 * 24690 * @borrows dom:isTextNode as videojs.isTextNode 24691 * @deprecated Use videojs.dom.isTextNode() instead 24692 */ 24693 24694/** 24695 * Creates an element and applies properties. 24696 * 24697 * @borrows dom:createEl as videojs.createEl 24698 * @deprecated Use videojs.dom.createEl() instead 24699 */ 24700 24701/** 24702 * Check if an element has a CSS class 24703 * 24704 * @borrows dom:hasElClass as videojs.hasClass 24705 * @deprecated Use videojs.dom.hasClass() instead 24706 */ 24707 24708/** 24709 * Add a CSS class name to an element 24710 * 24711 * @borrows dom:addElClass as videojs.addClass 24712 * @deprecated Use videojs.dom.addClass() instead 24713 */ 24714 24715/** 24716 * Remove a CSS class name from an element 24717 * 24718 * @borrows dom:removeElClass as videojs.removeClass 24719 * @deprecated Use videojs.dom.removeClass() instead 24720 */ 24721 24722/** 24723 * Adds or removes a CSS class name on an element depending on an optional 24724 * condition or the presence/absence of the class name. 24725 * 24726 * @borrows dom:toggleElClass as videojs.toggleClass 24727 * @deprecated Use videojs.dom.toggleClass() instead 24728 */ 24729 24730/** 24731 * Apply attributes to an HTML element. 24732 * 24733 * @borrows dom:setElAttributes as videojs.setAttribute 24734 * @deprecated Use videojs.dom.setAttributes() instead 24735 */ 24736 24737/** 24738 * Get an element's attribute values, as defined on the HTML tag 24739 * Attributes are not the same as properties. They're defined on the tag 24740 * or with setAttribute (which shouldn't be used with HTML) 24741 * This will return true or false for boolean attributes. 24742 * 24743 * @borrows dom:getElAttributes as videojs.getAttributes 24744 * @deprecated Use videojs.dom.getAttributes() instead 24745 */ 24746 24747/** 24748 * Empties the contents of an element. 24749 * 24750 * @borrows dom:emptyEl as videojs.emptyEl 24751 * @deprecated Use videojs.dom.emptyEl() instead 24752 */ 24753 24754/** 24755 * Normalizes and appends content to an element. 24756 * 24757 * The content for an element can be passed in multiple types and 24758 * combinations, whose behavior is as follows: 24759 * 24760 * - String 24761 * Normalized into a text node. 24762 * 24763 * - Element, TextNode 24764 * Passed through. 24765 * 24766 * - Array 24767 * A one-dimensional array of strings, elements, nodes, or functions (which 24768 * return single strings, elements, or nodes). 24769 * 24770 * - Function 24771 * If the sole argument, is expected to produce a string, element, 24772 * node, or array. 24773 * 24774 * @borrows dom:appendContents as videojs.appendContet 24775 * @deprecated Use videojs.dom.appendContent() instead 24776 */ 24777 24778/** 24779 * Normalizes and inserts content into an element; this is identical to 24780 * `appendContent()`, except it empties the element first. 24781 * 24782 * The content for an element can be passed in multiple types and 24783 * combinations, whose behavior is as follows: 24784 * 24785 * - String 24786 * Normalized into a text node. 24787 * 24788 * - Element, TextNode 24789 * Passed through. 24790 * 24791 * - Array 24792 * A one-dimensional array of strings, elements, nodes, or functions (which 24793 * return single strings, elements, or nodes). 24794 * 24795 * - Function 24796 * If the sole argument, is expected to produce a string, element, 24797 * node, or array. 24798 * 24799 * @borrows dom:insertContent as videojs.insertContent 24800 * @deprecated Use videojs.dom.insertContent() instead 24801 */ 24802['isEl', 'isTextNode', 'createEl', 'hasClass', 'addClass', 'removeClass', 'toggleClass', 'setAttributes', 'getAttributes', 'emptyEl', 'appendContent', 'insertContent'].forEach(function (k) { 24803 videojs[k] = function () { 24804 log$1.warn('videojs.' + k + '() is deprecated; use videojs.dom.' + k + '() instead'); 24805 return Dom[k].apply(null, arguments); 24806 }; 24807}); 24808 24809/** 24810 * A safe getComputedStyle with an IE8 fallback. 24811 * 24812 * This is because in Firefox, if the player is loaded in an iframe with `display:none`, 24813 * then `getComputedStyle` returns `null`, so, we do a null-check to make sure 24814 * that the player doesn't break in these cases. 24815 * See https://bugzilla.mozilla.org/show_bug.cgi?id=548397 for more details. 24816 * 24817 * @borrows computed-style:computedStyle as videojs.computedStyle 24818 */ 24819videojs.computedStyle = computedStyle; 24820 24821/** 24822 * Export the Dom utilities for use in external plugins 24823 * and Tech's 24824 */ 24825videojs.dom = Dom; 24826 24827/** 24828 * Export the Url utilities for use in external plugins 24829 * and Tech's 24830 */ 24831videojs.url = Url; 24832 24833return videojs; 24834 24835})));
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.