UNPKG

video.js

Version:

An HTML5 video player that supports HLS and DASH with a common API and skin.

1,753 lines (1,531 loc) 893 kB
/** * @license * Video.js 7.11.4 <http://videojs.com/> * Copyright Brightcove, Inc. <https://www.brightcove.com/> * Available under Apache License Version 2.0 * <https://github.com/videojs/video.js/blob/main/LICENSE> * * Includes vtt.js <https://github.com/mozilla/vtt.js> * Available under Apache License Version 2.0 * <https://github.com/mozilla/vtt.js/blob/main/LICENSE> */ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : (global = global || self, global.videojs = factory()); }(this, (function () { 'use strict'; var version = "7.11.4"; var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}; function createCommonjsModule(fn, module) { return module = { exports: {} }, fn(module, module.exports), module.exports; } var win; if (typeof window !== "undefined") { win = window; } else if (typeof commonjsGlobal !== "undefined") { win = commonjsGlobal; } else if (typeof self !== "undefined") { win = self; } else { win = {}; } var window_1 = win; var minDoc = {}; var topLevel = typeof commonjsGlobal !== 'undefined' ? commonjsGlobal : typeof window !== 'undefined' ? window : {}; var doccy; if (typeof document !== 'undefined') { doccy = document; } else { doccy = topLevel['__GLOBAL_DOCUMENT_CACHE@4']; if (!doccy) { doccy = topLevel['__GLOBAL_DOCUMENT_CACHE@4'] = minDoc; } } var document_1 = doccy; /** * @file create-logger.js * @module create-logger */ var history = []; /** * Log messages to the console and history based on the type of message * * @private * @param {string} type * The name of the console method to use. * * @param {Array} args * The arguments to be passed to the matching console method. */ var LogByTypeFactory = function LogByTypeFactory(name, log) { return function (type, level, args) { var lvl = log.levels[level]; var lvlRegExp = new RegExp("^(" + lvl + ")$"); if (type !== 'log') { // Add the type to the front of the message when it's not "log". args.unshift(type.toUpperCase() + ':'); } // Add console prefix after adding to history. args.unshift(name + ':'); // Add a clone of the args at this point to history. if (history) { history.push([].concat(args)); // only store 1000 history entries var splice = history.length - 1000; history.splice(0, splice > 0 ? splice : 0); } // If there's no console then don't try to output messages, but they will // still be stored in history. if (!window_1.console) { return; } // Was setting these once outside of this function, but containing them // in the function makes it easier to test cases where console doesn't exist // when the module is executed. var fn = window_1.console[type]; if (!fn && type === 'debug') { // Certain browsers don't have support for console.debug. For those, we // should default to the closest comparable log. fn = window_1.console.info || window_1.console.log; } // Bail out if there's no console or if this type is not allowed by the // current logging level. if (!fn || !lvl || !lvlRegExp.test(type)) { return; } fn[Array.isArray(args) ? 'apply' : 'call'](window_1.console, args); }; }; function createLogger(name) { // This is the private tracking variable for logging level. var level = 'info'; // the curried logByType bound to the specific log and history var logByType; /** * Logs plain debug messages. Similar to `console.log`. * * Due to [limitations](https://github.com/jsdoc3/jsdoc/issues/955#issuecomment-313829149) * of our JSDoc template, we cannot properly document this as both a function * and a namespace, so its function signature is documented here. * * #### Arguments * ##### *args * Mixed[] * * Any combination of values that could be passed to `console.log()`. * * #### Return Value * * `undefined` * * @namespace * @param {Mixed[]} args * One or more messages or objects that should be logged. */ var log = function log() { for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } logByType('log', level, args); }; // This is the logByType helper that the logging methods below use logByType = LogByTypeFactory(name, log); /** * Create a new sublogger which chains the old name to the new name. * * For example, doing `videojs.log.createLogger('player')` and then using that logger will log the following: * ```js * mylogger('foo'); * // > VIDEOJS: player: foo * ``` * * @param {string} name * The name to add call the new logger * @return {Object} */ log.createLogger = function (subname) { return createLogger(name + ': ' + subname); }; /** * Enumeration of available logging levels, where the keys are the level names * and the values are `|`-separated strings containing logging methods allowed * in that logging level. These strings are used to create a regular expression * matching the function name being called. * * Levels provided by Video.js are: * * - `off`: Matches no calls. Any value that can be cast to `false` will have * this effect. The most restrictive. * - `all`: Matches only Video.js-provided functions (`debug`, `log`, * `log.warn`, and `log.error`). * - `debug`: Matches `log.debug`, `log`, `log.warn`, and `log.error` calls. * - `info` (default): Matches `log`, `log.warn`, and `log.error` calls. * - `warn`: Matches `log.warn` and `log.error` calls. * - `error`: Matches only `log.error` calls. * * @type {Object} */ log.levels = { all: 'debug|log|warn|error', off: '', debug: 'debug|log|warn|error', info: 'log|warn|error', warn: 'warn|error', error: 'error', DEFAULT: level }; /** * Get or set the current logging level. * * If a string matching a key from {@link module:log.levels} is provided, acts * as a setter. * * @param {string} [lvl] * Pass a valid level to set a new logging level. * * @return {string} * The current logging level. */ log.level = function (lvl) { if (typeof lvl === 'string') { if (!log.levels.hasOwnProperty(lvl)) { throw new Error("\"" + lvl + "\" in not a valid log level"); } level = lvl; } return level; }; /** * Returns an array containing everything that has been logged to the history. * * This array is a shallow clone of the internal history record. However, its * contents are _not_ cloned; so, mutating objects inside this array will * mutate them in history. * * @return {Array} */ log.history = function () { return history ? [].concat(history) : []; }; /** * Allows you to filter the history by the given logger name * * @param {string} fname * The name to filter by * * @return {Array} * The filtered list to return */ log.history.filter = function (fname) { return (history || []).filter(function (historyItem) { // if the first item in each historyItem includes `fname`, then it's a match return new RegExp(".*" + fname + ".*").test(historyItem[0]); }); }; /** * Clears the internal history tracking, but does not prevent further history * tracking. */ log.history.clear = function () { if (history) { history.length = 0; } }; /** * Disable history tracking if it is currently enabled. */ log.history.disable = function () { if (history !== null) { history.length = 0; history = null; } }; /** * Enable history tracking if it is currently disabled. */ log.history.enable = function () { if (history === null) { history = []; } }; /** * Logs error messages. Similar to `console.error`. * * @param {Mixed[]} args * One or more messages or objects that should be logged as an error */ log.error = function () { for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { args[_key2] = arguments[_key2]; } return logByType('error', level, args); }; /** * Logs warning messages. Similar to `console.warn`. * * @param {Mixed[]} args * One or more messages or objects that should be logged as a warning. */ log.warn = function () { for (var _len3 = arguments.length, args = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) { args[_key3] = arguments[_key3]; } return logByType('warn', level, args); }; /** * Logs debug messages. Similar to `console.debug`, but may also act as a comparable * log if `console.debug` is not available * * @param {Mixed[]} args * One or more messages or objects that should be logged as debug. */ log.debug = function () { for (var _len4 = arguments.length, args = new Array(_len4), _key4 = 0; _key4 < _len4; _key4++) { args[_key4] = arguments[_key4]; } return logByType('debug', level, args); }; return log; } /** * @file log.js * @module log */ var log = createLogger('VIDEOJS'); var createLogger$1 = log.createLogger; var _extends_1 = createCommonjsModule(function (module) { function _extends() { module.exports = _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } module.exports = _extends; }); /** * @file obj.js * @module obj */ /** * @callback obj:EachCallback * * @param {Mixed} value * The current key for the object that is being iterated over. * * @param {string} key * The current key-value for object that is being iterated over */ /** * @callback obj:ReduceCallback * * @param {Mixed} accum * The value that is accumulating over the reduce loop. * * @param {Mixed} value * The current key for the object that is being iterated over. * * @param {string} key * The current key-value for object that is being iterated over * * @return {Mixed} * The new accumulated value. */ var toString = Object.prototype.toString; /** * Get the keys of an Object * * @param {Object} * The Object to get the keys from * * @return {string[]} * An array of the keys from the object. Returns an empty array if the * object passed in was invalid or had no keys. * * @private */ var keys = function keys(object) { return isObject(object) ? Object.keys(object) : []; }; /** * Array-like iteration for objects. * * @param {Object} object * The object to iterate over * * @param {obj:EachCallback} fn * The callback function which is called for each key in the object. */ function each(object, fn) { keys(object).forEach(function (key) { return fn(object[key], key); }); } /** * Array-like reduce for objects. * * @param {Object} object * The Object that you want to reduce. * * @param {Function} fn * A callback function which is called for each key in the object. It * receives the accumulated value and the per-iteration value and key * as arguments. * * @param {Mixed} [initial = 0] * Starting value * * @return {Mixed} * The final accumulated value. */ function reduce(object, fn, initial) { if (initial === void 0) { initial = 0; } return keys(object).reduce(function (accum, key) { return fn(accum, object[key], key); }, initial); } /** * Object.assign-style object shallow merge/extend. * * @param {Object} target * @param {Object} ...sources * @return {Object} */ function assign(target) { for (var _len = arguments.length, sources = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { sources[_key - 1] = arguments[_key]; } if (Object.assign) { return _extends_1.apply(void 0, [target].concat(sources)); } sources.forEach(function (source) { if (!source) { return; } each(source, function (value, key) { target[key] = value; }); }); return target; } /** * Returns whether a value is an object of any kind - including DOM nodes, * arrays, regular expressions, etc. Not functions, though. * * This avoids the gotcha where using `typeof` on a `null` value * results in `'object'`. * * @param {Object} value * @return {boolean} */ function isObject(value) { return !!value && typeof value === 'object'; } /** * Returns whether an object appears to be a "plain" object - that is, a * direct instance of `Object`. * * @param {Object} value * @return {boolean} */ function isPlain(value) { return isObject(value) && toString.call(value) === '[object Object]' && value.constructor === Object; } /** * @file computed-style.js * @module computed-style */ /** * A safe getComputedStyle. * * This is needed because in Firefox, if the player is loaded in an iframe with * `display:none`, then `getComputedStyle` returns `null`, so, we do a * null-check to make sure that the player doesn't break in these cases. * * @function * @param {Element} el * The element you want the computed style of * * @param {string} prop * The property name you want * * @see https://bugzilla.mozilla.org/show_bug.cgi?id=548397 */ function computedStyle(el, prop) { if (!el || !prop) { return ''; } if (typeof window_1.getComputedStyle === 'function') { var computedStyleValue = window_1.getComputedStyle(el); return computedStyleValue ? computedStyleValue.getPropertyValue(prop) || computedStyleValue[prop] : ''; } return ''; } /** * @file dom.js * @module dom */ /** * Detect if a value is a string with any non-whitespace characters. * * @private * @param {string} str * The string to check * * @return {boolean} * Will be `true` if the string is non-blank, `false` otherwise. * */ function isNonBlankString(str) { // we use str.trim as it will trim any whitespace characters // from the front or back of non-whitespace characters. aka // Any string that contains non-whitespace characters will // still contain them after `trim` but whitespace only strings // will have a length of 0, failing this check. return typeof str === 'string' && Boolean(str.trim()); } /** * Throws an error if the passed string has whitespace. This is used by * class methods to be relatively consistent with the classList API. * * @private * @param {string} str * The string to check for whitespace. * * @throws {Error} * Throws an error if there is whitespace in the string. */ function throwIfWhitespace(str) { // str.indexOf instead of regex because str.indexOf is faster performance wise. if (str.indexOf(' ') >= 0) { throw new Error('class has illegal whitespace characters'); } } /** * Produce a regular expression for matching a className within an elements className. * * @private * @param {string} className * The className to generate the RegExp for. * * @return {RegExp} * The RegExp that will check for a specific `className` in an elements * className. */ function classRegExp(className) { return new RegExp('(^|\\s)' + className + '($|\\s)'); } /** * Whether the current DOM interface appears to be real (i.e. not simulated). * * @return {boolean} * Will be `true` if the DOM appears to be real, `false` otherwise. */ function isReal() { // Both document and window will never be undefined thanks to `global`. return document_1 === window_1.document; } /** * Determines, via duck typing, whether or not a value is a DOM element. * * @param {Mixed} value * The value to check. * * @return {boolean} * Will be `true` if the value is a DOM element, `false` otherwise. */ function isEl(value) { return isObject(value) && value.nodeType === 1; } /** * Determines if the current DOM is embedded in an iframe. * * @return {boolean} * Will be `true` if the DOM is embedded in an iframe, `false` * otherwise. */ function isInFrame() { // We need a try/catch here because Safari will throw errors when attempting // to get either `parent` or `self` try { return window_1.parent !== window_1.self; } catch (x) { return true; } } /** * Creates functions to query the DOM using a given method. * * @private * @param {string} method * The method to create the query with. * * @return {Function} * The query method */ function createQuerier(method) { return function (selector, context) { if (!isNonBlankString(selector)) { return document_1[method](null); } if (isNonBlankString(context)) { context = document_1.querySelector(context); } var ctx = isEl(context) ? context : document_1; return ctx[method] && ctx[method](selector); }; } /** * Creates an element and applies properties, attributes, and inserts content. * * @param {string} [tagName='div'] * Name of tag to be created. * * @param {Object} [properties={}] * Element properties to be applied. * * @param {Object} [attributes={}] * Element attributes to be applied. * * @param {module:dom~ContentDescriptor} content * A content descriptor object. * * @return {Element} * The element that was created. */ function createEl(tagName, properties, attributes, content) { if (tagName === void 0) { tagName = 'div'; } if (properties === void 0) { properties = {}; } if (attributes === void 0) { attributes = {}; } var el = document_1.createElement(tagName); Object.getOwnPropertyNames(properties).forEach(function (propName) { var val = properties[propName]; // See #2176 // We originally were accepting both properties and attributes in the // same object, but that doesn't work so well. if (propName.indexOf('aria-') !== -1 || propName === 'role' || propName === 'type') { log.warn('Setting attributes in the second argument of createEl()\n' + 'has been deprecated. Use the third argument instead.\n' + ("createEl(type, properties, attributes). Attempting to set " + propName + " to " + val + ".")); el.setAttribute(propName, val); // Handle textContent since it's not supported everywhere and we have a // method for it. } else if (propName === 'textContent') { textContent(el, val); } else if (el[propName] !== val || propName === 'tabIndex') { el[propName] = val; } }); Object.getOwnPropertyNames(attributes).forEach(function (attrName) { el.setAttribute(attrName, attributes[attrName]); }); if (content) { appendContent(el, content); } return el; } /** * Injects text into an element, replacing any existing contents entirely. * * @param {Element} el * The element to add text content into * * @param {string} text * The text content to add. * * @return {Element} * The element with added text content. */ function textContent(el, text) { if (typeof el.textContent === 'undefined') { el.innerText = text; } else { el.textContent = text; } return el; } /** * Insert an element as the first child node of another * * @param {Element} child * Element to insert * * @param {Element} parent * Element to insert child into */ function prependTo(child, parent) { if (parent.firstChild) { parent.insertBefore(child, parent.firstChild); } else { parent.appendChild(child); } } /** * Check if an element has a class name. * * @param {Element} element * Element to check * * @param {string} classToCheck * Class name to check for * * @return {boolean} * Will be `true` if the element has a class, `false` otherwise. * * @throws {Error} * Throws an error if `classToCheck` has white space. */ function hasClass(element, classToCheck) { throwIfWhitespace(classToCheck); if (element.classList) { return element.classList.contains(classToCheck); } return classRegExp(classToCheck).test(element.className); } /** * Add a class name to an element. * * @param {Element} element * Element to add class name to. * * @param {string} classToAdd * Class name to add. * * @return {Element} * The DOM element with the added class name. */ function addClass(element, classToAdd) { if (element.classList) { element.classList.add(classToAdd); // Don't need to `throwIfWhitespace` here because `hasElClass` will do it // in the case of classList not being supported. } else if (!hasClass(element, classToAdd)) { element.className = (element.className + ' ' + classToAdd).trim(); } return element; } /** * Remove a class name from an element. * * @param {Element} element * Element to remove a class name from. * * @param {string} classToRemove * Class name to remove * * @return {Element} * The DOM element with class name removed. */ function removeClass(element, classToRemove) { if (element.classList) { element.classList.remove(classToRemove); } else { throwIfWhitespace(classToRemove); element.className = element.className.split(/\s+/).filter(function (c) { return c !== classToRemove; }).join(' '); } return element; } /** * The callback definition for toggleClass. * * @callback module:dom~PredicateCallback * @param {Element} element * The DOM element of the Component. * * @param {string} classToToggle * The `className` that wants to be toggled * * @return {boolean|undefined} * If `true` is returned, the `classToToggle` will be added to the * `element`. If `false`, the `classToToggle` will be removed from * the `element`. If `undefined`, the callback will be ignored. */ /** * Adds or removes a class name to/from an element depending on an optional * condition or the presence/absence of the class name. * * @param {Element} element * The element to toggle a class name on. * * @param {string} classToToggle * The class that should be toggled. * * @param {boolean|module:dom~PredicateCallback} [predicate] * See the return value for {@link module:dom~PredicateCallback} * * @return {Element} * The element with a class that has been toggled. */ function toggleClass(element, classToToggle, predicate) { // This CANNOT use `classList` internally because IE11 does not support the // second parameter to the `classList.toggle()` method! Which is fine because // `classList` will be used by the add/remove functions. var has = hasClass(element, classToToggle); if (typeof predicate === 'function') { predicate = predicate(element, classToToggle); } if (typeof predicate !== 'boolean') { predicate = !has; } // If the necessary class operation matches the current state of the // element, no action is required. if (predicate === has) { return; } if (predicate) { addClass(element, classToToggle); } else { removeClass(element, classToToggle); } return element; } /** * Apply attributes to an HTML element. * * @param {Element} el * Element to add attributes to. * * @param {Object} [attributes] * Attributes to be applied. */ function setAttributes(el, attributes) { Object.getOwnPropertyNames(attributes).forEach(function (attrName) { var attrValue = attributes[attrName]; if (attrValue === null || typeof attrValue === 'undefined' || attrValue === false) { el.removeAttribute(attrName); } else { el.setAttribute(attrName, attrValue === true ? '' : attrValue); } }); } /** * Get an element's attribute values, as defined on the HTML tag. * * Attributes are not the same as properties. They're defined on the tag * or with setAttribute. * * @param {Element} tag * Element from which to get tag attributes. * * @return {Object} * All attributes of the element. Boolean attributes will be `true` or * `false`, others will be strings. */ function getAttributes(tag) { var obj = {}; // known boolean attributes // we can check for matching boolean properties, but not all browsers // and not all tags know about these attributes, so, we still want to check them manually var knownBooleans = ',' + 'autoplay,controls,playsinline,loop,muted,default,defaultMuted' + ','; if (tag && tag.attributes && tag.attributes.length > 0) { var attrs = tag.attributes; for (var i = attrs.length - 1; i >= 0; i--) { var attrName = attrs[i].name; var attrVal = attrs[i].value; // check for known booleans // the matching element property will return a value for typeof if (typeof tag[attrName] === 'boolean' || knownBooleans.indexOf(',' + attrName + ',') !== -1) { // the value of an included boolean attribute is typically an empty // string ('') which would equal false if we just check for a false value. // we also don't want support bad code like autoplay='false' attrVal = attrVal !== null ? true : false; } obj[attrName] = attrVal; } } return obj; } /** * Get the value of an element's attribute. * * @param {Element} el * A DOM element. * * @param {string} attribute * Attribute to get the value of. * * @return {string} * The value of the attribute. */ function getAttribute(el, attribute) { return el.getAttribute(attribute); } /** * Set the value of an element's attribute. * * @param {Element} el * A DOM element. * * @param {string} attribute * Attribute to set. * * @param {string} value * Value to set the attribute to. */ function setAttribute(el, attribute, value) { el.setAttribute(attribute, value); } /** * Remove an element's attribute. * * @param {Element} el * A DOM element. * * @param {string} attribute * Attribute to remove. */ function removeAttribute(el, attribute) { el.removeAttribute(attribute); } /** * Attempt to block the ability to select text. */ function blockTextSelection() { document_1.body.focus(); document_1.onselectstart = function () { return false; }; } /** * Turn off text selection blocking. */ function unblockTextSelection() { document_1.onselectstart = function () { return true; }; } /** * Identical to the native `getBoundingClientRect` function, but ensures that * the method is supported at all (it is in all browsers we claim to support) * and that the element is in the DOM before continuing. * * This wrapper function also shims properties which are not provided by some * older browsers (namely, IE8). * * Additionally, some browsers do not support adding properties to a * `ClientRect`/`DOMRect` object; so, we shallow-copy it with the standard * properties (except `x` and `y` which are not widely supported). This helps * avoid implementations where keys are non-enumerable. * * @param {Element} el * Element whose `ClientRect` we want to calculate. * * @return {Object|undefined} * Always returns a plain object - or `undefined` if it cannot. */ function getBoundingClientRect(el) { if (el && el.getBoundingClientRect && el.parentNode) { var rect = el.getBoundingClientRect(); var result = {}; ['bottom', 'height', 'left', 'right', 'top', 'width'].forEach(function (k) { if (rect[k] !== undefined) { result[k] = rect[k]; } }); if (!result.height) { result.height = parseFloat(computedStyle(el, 'height')); } if (!result.width) { result.width = parseFloat(computedStyle(el, 'width')); } return result; } } /** * Represents the position of a DOM element on the page. * * @typedef {Object} module:dom~Position * * @property {number} left * Pixels to the left. * * @property {number} top * Pixels from the top. */ /** * Get the position of an element in the DOM. * * Uses `getBoundingClientRect` technique from John Resig. * * @see http://ejohn.org/blog/getboundingclientrect-is-awesome/ * * @param {Element} el * Element from which to get offset. * * @return {module:dom~Position} * The position of the element that was passed in. */ function findPosition(el) { if (!el || el && !el.offsetParent) { return { left: 0, top: 0, width: 0, height: 0 }; } var width = el.offsetWidth; var height = el.offsetHeight; var left = 0; var top = 0; do { left += el.offsetLeft; top += el.offsetTop; el = el.offsetParent; } while (el); return { left: left, top: top, width: width, height: height }; } /** * Represents x and y coordinates for a DOM element or mouse pointer. * * @typedef {Object} module:dom~Coordinates * * @property {number} x * x coordinate in pixels * * @property {number} y * y coordinate in pixels */ /** * Get the pointer position within an element. * * The base on the coordinates are the bottom left of the element. * * @param {Element} el * Element on which to get the pointer position on. * * @param {EventTarget~Event} event * Event object. * * @return {module:dom~Coordinates} * A coordinates object corresponding to the mouse position. * */ function getPointerPosition(el, event) { var position = {}; var boxTarget = findPosition(event.target); var box = findPosition(el); var boxW = box.width; var boxH = box.height; var offsetY = event.offsetY - (box.top - boxTarget.top); var offsetX = event.offsetX - (box.left - boxTarget.left); if (event.changedTouches) { offsetX = event.changedTouches[0].pageX - box.left; offsetY = event.changedTouches[0].pageY + box.top; } position.y = 1 - Math.max(0, Math.min(1, offsetY / boxH)); position.x = Math.max(0, Math.min(1, offsetX / boxW)); return position; } /** * Determines, via duck typing, whether or not a value is a text node. * * @param {Mixed} value * Check if this value is a text node. * * @return {boolean} * Will be `true` if the value is a text node, `false` otherwise. */ function isTextNode(value) { return isObject(value) && value.nodeType === 3; } /** * Empties the contents of an element. * * @param {Element} el * The element to empty children from * * @return {Element} * The element with no children */ function emptyEl(el) { while (el.firstChild) { el.removeChild(el.firstChild); } return el; } /** * This is a mixed value that describes content to be injected into the DOM * via some method. It can be of the following types: * * Type | Description * -----------|------------- * `string` | The value will be normalized into a text node. * `Element` | The value will be accepted as-is. * `TextNode` | The value will be accepted as-is. * `Array` | A one-dimensional array of strings, elements, text nodes, or functions. These functions should return a string, element, or text node (any other return value, like an array, will be ignored). * `Function` | A function, which is expected to return a string, element, text node, or array - any of the other possible values described above. This means that a content descriptor could be a function that returns an array of functions, but those second-level functions must return strings, elements, or text nodes. * * @typedef {string|Element|TextNode|Array|Function} module:dom~ContentDescriptor */ /** * Normalizes content for eventual insertion into the DOM. * * This allows a wide range of content definition methods, but helps protect * from falling into the trap of simply writing to `innerHTML`, which could * be an XSS concern. * * The content for an element can be passed in multiple types and * combinations, whose behavior is as follows: * * @param {module:dom~ContentDescriptor} content * A content descriptor value. * * @return {Array} * All of the content that was passed in, normalized to an array of * elements or text nodes. */ function normalizeContent(content) { // First, invoke content if it is a function. If it produces an array, // that needs to happen before normalization. if (typeof content === 'function') { content = content(); } // Next up, normalize to an array, so one or many items can be normalized, // filtered, and returned. return (Array.isArray(content) ? content : [content]).map(function (value) { // First, invoke value if it is a function to produce a new value, // which will be subsequently normalized to a Node of some kind. if (typeof value === 'function') { value = value(); } if (isEl(value) || isTextNode(value)) { return value; } if (typeof value === 'string' && /\S/.test(value)) { return document_1.createTextNode(value); } }).filter(function (value) { return value; }); } /** * Normalizes and appends content to an element. * * @param {Element} el * Element to append normalized content to. * * @param {module:dom~ContentDescriptor} content * A content descriptor value. * * @return {Element} * The element with appended normalized content. */ function appendContent(el, content) { normalizeContent(content).forEach(function (node) { return el.appendChild(node); }); return el; } /** * Normalizes and inserts content into an element; this is identical to * `appendContent()`, except it empties the element first. * * @param {Element} el * Element to insert normalized content into. * * @param {module:dom~ContentDescriptor} content * A content descriptor value. * * @return {Element} * The element with inserted normalized content. */ function insertContent(el, content) { return appendContent(emptyEl(el), content); } /** * Check if an event was a single left click. * * @param {EventTarget~Event} event * Event object. * * @return {boolean} * Will be `true` if a single left click, `false` otherwise. */ function isSingleLeftClick(event) { // Note: if you create something draggable, be sure to // call it on both `mousedown` and `mousemove` event, // otherwise `mousedown` should be enough for a button if (event.button === undefined && event.buttons === undefined) { // Why do we need `buttons` ? // Because, middle mouse sometimes have this: // e.button === 0 and e.buttons === 4 // Furthermore, we want to prevent combination click, something like // HOLD middlemouse then left click, that would be // e.button === 0, e.buttons === 5 // just `button` is not gonna work // Alright, then what this block does ? // this is for chrome `simulate mobile devices` // I want to support this as well return true; } if (event.button === 0 && event.buttons === undefined) { // Touch screen, sometimes on some specific device, `buttons` // doesn't have anything (safari on ios, blackberry...) return true; } // `mouseup` event on a single left click has // `button` and `buttons` equal to 0 if (event.type === 'mouseup' && event.button === 0 && event.buttons === 0) { return true; } if (event.button !== 0 || event.buttons !== 1) { // This is the reason we have those if else block above // if any special case we can catch and let it slide // we do it above, when get to here, this definitely // is-not-left-click return false; } return true; } /** * Finds a single DOM element matching `selector` within the optional * `context` of another DOM element (defaulting to `document`). * * @param {string} selector * A valid CSS selector, which will be passed to `querySelector`. * * @param {Element|String} [context=document] * A DOM element within which to query. Can also be a selector * string in which case the first matching element will be used * as context. If missing (or no element matches selector), falls * back to `document`. * * @return {Element|null} * The element that was found or null. */ var $ = createQuerier('querySelector'); /** * Finds a all DOM elements matching `selector` within the optional * `context` of another DOM element (defaulting to `document`). * * @param {string} selector * A valid CSS selector, which will be passed to `querySelectorAll`. * * @param {Element|String} [context=document] * A DOM element within which to query. Can also be a selector * string in which case the first matching element will be used * as context. If missing (or no element matches selector), falls * back to `document`. * * @return {NodeList} * A element list of elements that were found. Will be empty if none * were found. * */ var $$ = createQuerier('querySelectorAll'); var Dom = /*#__PURE__*/Object.freeze({ __proto__: null, isReal: isReal, isEl: isEl, isInFrame: isInFrame, createEl: createEl, textContent: textContent, prependTo: prependTo, hasClass: hasClass, addClass: addClass, removeClass: removeClass, toggleClass: toggleClass, setAttributes: setAttributes, getAttributes: getAttributes, getAttribute: getAttribute, setAttribute: setAttribute, removeAttribute: removeAttribute, blockTextSelection: blockTextSelection, unblockTextSelection: unblockTextSelection, getBoundingClientRect: getBoundingClientRect, findPosition: findPosition, getPointerPosition: getPointerPosition, isTextNode: isTextNode, emptyEl: emptyEl, normalizeContent: normalizeContent, appendContent: appendContent, insertContent: insertContent, isSingleLeftClick: isSingleLeftClick, $: $, $$: $$ }); /** * @file setup.js - Functions for setting up a player without * user interaction based on the data-setup `attribute` of the video tag. * * @module setup */ var _windowLoaded = false; var videojs; /** * Set up any tags that have a data-setup `attribute` when the player is started. */ var autoSetup = function autoSetup() { // Protect against breakage in non-browser environments and check global autoSetup option. if (!isReal() || videojs.options.autoSetup === false) { return; } var vids = Array.prototype.slice.call(document_1.getElementsByTagName('video')); var audios = Array.prototype.slice.call(document_1.getElementsByTagName('audio')); var divs = Array.prototype.slice.call(document_1.getElementsByTagName('video-js')); var mediaEls = vids.concat(audios, divs); // Check if any media elements exist if (mediaEls && mediaEls.length > 0) { for (var i = 0, e = mediaEls.length; i < e; i++) { var mediaEl = mediaEls[i]; // Check if element exists, has getAttribute func. if (mediaEl && mediaEl.getAttribute) { // Make sure this player hasn't already been set up. if (mediaEl.player === undefined) { var options = mediaEl.getAttribute('data-setup'); // Check if data-setup attr exists. // We only auto-setup if they've added the data-setup attr. if (options !== null) { // Create new video.js instance. videojs(mediaEl); } } // If getAttribute isn't defined, we need to wait for the DOM. } else { autoSetupTimeout(1); break; } } // No videos were found, so keep looping unless page is finished loading. } else if (!_windowLoaded) { autoSetupTimeout(1); } }; /** * Wait until the page is loaded before running autoSetup. This will be called in * autoSetup if `hasLoaded` returns false. * * @param {number} wait * How long to wait in ms * * @param {module:videojs} [vjs] * The videojs library function */ function autoSetupTimeout(wait, vjs) { if (vjs) { videojs = vjs; } window_1.setTimeout(autoSetup, wait); } /** * Used to set the internal tracking of window loaded state to true. * * @private */ function setWindowLoaded() { _windowLoaded = true; window_1.removeEventListener('load', setWindowLoaded); } if (isReal()) { if (document_1.readyState === 'complete') { setWindowLoaded(); } else { /** * Listen for the load event on window, and set _windowLoaded to true. * * We use a standard event listener here to avoid incrementing the GUID * before any players are created. * * @listens load */ window_1.addEventListener('load', setWindowLoaded); } } /** * @file stylesheet.js * @module stylesheet */ /** * Create a DOM syle element given a className for it. * * @param {string} className * The className to add to the created style element. * * @return {Element} * The element that was created. */ var createStyleElement = function createStyleElement(className) { var style = document_1.createElement('style'); style.className = className; return style; }; /** * Add text to a DOM element. * * @param {Element} el * The Element to add text content to. * * @param {string} content * The text to add to the element. */ var setTextContent = function setTextContent(el, content) { if (el.styleSheet) { el.styleSheet.cssText = content; } else { el.textContent = content; } }; /** * @file guid.js * @module guid */ // Default value for GUIDs. This allows us to reset the GUID counter in tests. // // The initial GUID is 3 because some users have come to rely on the first // default player ID ending up as `vjs_video_3`. // // See: https://github.com/videojs/video.js/pull/6216 var _initialGuid = 3; /** * Unique ID for an element or function * * @type {Number} */ var _guid = _initialGuid; /** * Get a unique auto-incrementing ID by number that has not been returned before. * * @return {number} * A new unique ID. */ function newGUID() { return _guid++; } /** * @file dom-data.js * @module dom-data */ var FakeWeakMap; if (!window_1.WeakMap) { FakeWeakMap = /*#__PURE__*/function () { function FakeWeakMap() { this.vdata = 'vdata' + Math.floor(window_1.performance && window_1.performance.now() || Date.now()); this.data = {}; } var _proto = FakeWeakMap.prototype; _proto.set = function set(key, value) { var access = key[this.vdata] || newGUID(); if (!key[this.vdata]) { key[this.vdata] = access; } this.data[access] = value; return this; }; _proto.get = function get(key) { var access = key[this.vdata]; // we have data, return it if (access) { return this.data[access]; } // we don't have data, return nothing. // return undefined explicitly as that's the contract for this method log('We have no data for this element', key); return undefined; }; _proto.has = function has(key) { var access = key[this.vdata]; return access in this.data; }; _proto["delete"] = function _delete(key) { var access = key[this.vdata]; if (access) { delete this.data[access]; delete key[this.vdata]; } }; return FakeWeakMap; }(); } /** * Element Data Store. * * Allows for binding data to an element without putting it directly on the * element. Ex. Event listeners are stored here. * (also from jsninja.com, slightly modified and updated for closure compiler) * * @type {Object} * @private */ var DomData = window_1.WeakMap ? new WeakMap() : new FakeWeakMap(); /** * @file events.js. An Event System (John Resig - Secrets of a JS Ninja http://jsninja.com/) * (Original book version wasn't completely usable, so fixed some things and made Closure Compiler compatible) * This should work very similarly to jQuery's events, however it's based off the book version which isn't as * robust as jquery's, so there's probably some differences. * * @file events.js * @module events */ /** * Clean up the listener cache and dispatchers * * @param {Element|Object} elem * Element to clean up * * @param {string} type * Type of event to clean up */ function _cleanUpEvents(elem, type) { if (!DomData.has(elem)) { return; } var data = DomData.get(elem); // Remove the events of a particular type if there are none left if (data.handlers[type].length === 0) { delete data.handlers[type]; // data.handlers[type] = null; // Setting to null was causing an error with data.handlers // Remove the meta-handler from the element if (elem.removeEventListener) { elem.removeEventListener(type, data.dispatcher, false); } else if (elem.detachEvent) { elem.detachEvent('on' + type, data.dispatcher); } } // Remove the events object if there are no types left if (Object.getOwnPropertyNames(data.handlers).length <= 0) { delete data.handlers; delete data.dispatcher; delete data.disabled; } // Finally remove the element data if there is no data left if (Object.getOwnPropertyNames(data).length === 0) { DomData["delete"](elem); } } /** * Loops through an array of event types and calls the requested method for each type. * * @param {Function} fn * The event method we want to use. * * @param {Element|Object} elem * Element or object to bind listeners to * * @param {string} type * Type of event to bind to. * * @param {EventTarget~EventListener} callback * Event listener. */ function _handleMultipleEvents(fn, elem, types, callback) { types.forEach(function (type) { // Call the event method for each one of the types fn(elem, type, callback); }); } /** * Fix a native event to have standard property values * * @param {Object} event * Event object to fix. * * @return {Object} * Fixed event object. */ function fixEvent(event) { if (event.fixed_) { return event; } function returnTrue() { return true; } function returnFalse() { return false; } // Test if fixing up is needed // Used to check if !event.stopPropagation instead of isPropagationStopped // But native events