diff --git a/dist/plyr.js b/dist/plyr.js index 4dc8a6e2..29264940 100644 --- a/dist/plyr.js +++ b/dist/plyr.js @@ -4,305 +4,181 @@ typeof navigator === "object" && (function (global, factory) { (global.Plyr = factory()); }(this, (function () { 'use strict'; -var commonjsGlobal = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}; +// ========================================================================== +// Type checking utils +// ========================================================================== -function createCommonjsModule(fn, module) { - return module = { exports: {} }, fn(module, module.exports), module.exports; -} - -var loadjs_umd = createCommonjsModule(function (module, exports) { -(function(root, factory) { - if (typeof undefined === 'function' && undefined.amd) { - undefined([], factory); - } else { - module.exports = factory(); - } -}(commonjsGlobal, function() { -/** - * Global dependencies. - * @global {Object} document - DOM - */ - -var devnull = function() {}, - bundleIdCache = {}, - bundleResultCache = {}, - bundleCallbackQueue = {}; - - -/** - * Subscribe to bundle load event. - * @param {string[]} bundleIds - Bundle ids - * @param {Function} callbackFn - The callback function - */ -function subscribe(bundleIds, callbackFn) { - // listify - bundleIds = bundleIds.push ? bundleIds : [bundleIds]; - - var depsNotFound = [], - i = bundleIds.length, - numWaiting = i, - fn, - bundleId, - r, - q; - - // define callback function - fn = function (bundleId, pathsNotFound) { - if (pathsNotFound.length) depsNotFound.push(bundleId); - - numWaiting--; - if (!numWaiting) callbackFn(depsNotFound); - }; - - // register callback - while (i--) { - bundleId = bundleIds[i]; - - // execute callback if in result cache - r = bundleResultCache[bundleId]; - if (r) { - fn(bundleId, r); - continue; - } - - // add to callback queue - q = bundleCallbackQueue[bundleId] = bundleCallbackQueue[bundleId] || []; - q.push(fn); - } -} - - -/** - * Publish bundle load event. - * @param {string} bundleId - Bundle id - * @param {string[]} pathsNotFound - List of files not found - */ -function publish(bundleId, pathsNotFound) { - // exit if id isn't defined - if (!bundleId) return; - - var q = bundleCallbackQueue[bundleId]; - - // cache result - bundleResultCache[bundleId] = pathsNotFound; - - // exit if queue is empty - if (!q) return; - - // empty callback queue - while (q.length) { - q[0](bundleId, pathsNotFound); - q.splice(0, 1); - } -} - - -/** - * Execute callbacks. - * @param {Object or Function} args - The callback args - * @param {string[]} depsNotFound - List of dependencies not found - */ -function executeCallbacks(args, depsNotFound) { - // accept function as argument - if (args.call) args = {success: args}; - - // success and error callbacks - if (depsNotFound.length) (args.error || devnull)(depsNotFound); - else (args.success || devnull)(args); -} - - -/** - * Load individual file. - * @param {string} path - The file path - * @param {Function} callbackFn - The callback function - */ -function loadFile(path, callbackFn, args, numTries) { - var doc = document, - async = args.async, - maxTries = (args.numRetries || 0) + 1, - beforeCallbackFn = args.before || devnull, - pathStripped = path.replace(/^(css|img)!/, ''), - isCss, - e; - - numTries = numTries || 0; - - if (/(^css!|\.css$)/.test(path)) { - isCss = true; - - // css - e = doc.createElement('link'); - e.rel = 'stylesheet'; - e.href = pathStripped; //.replace(/^css!/, ''); // remove "css!" prefix - } else if (/(^img!|\.(png|gif|jpg|svg)$)/.test(path)) { - // image - e = doc.createElement('img'); - e.src = pathStripped; - } else { - // javascript - e = doc.createElement('script'); - e.src = path; - e.async = async === undefined ? true : async; - } - - e.onload = e.onerror = e.onbeforeload = function (ev) { - var result = ev.type[0]; - - // Note: The following code isolates IE using `hideFocus` and treats empty - // stylesheets as failures to get around lack of onerror support - if (isCss && 'hideFocus' in e) { - try { - if (!e.sheet.cssText.length) result = 'e'; - } catch (x) { - // sheets objects created from load errors don't allow access to - // `cssText` - result = 'e'; - } - } - - // handle retries in case of load failure - if (result == 'e') { - // increment counter - numTries += 1; - - // exit function and try again - if (numTries < maxTries) { - return loadFile(path, callbackFn, args, numTries); - } - } - - // execute callback - callbackFn(path, result, ev.defaultPrevented); - }; - - // add to document (unless callback returns `false`) - if (beforeCallbackFn(path, e) !== false) doc.head.appendChild(e); -} - - -/** - * Load multiple files. - * @param {string[]} paths - The file paths - * @param {Function} callbackFn - The callback function - */ -function loadFiles(paths, callbackFn, args) { - // listify paths - paths = paths.push ? paths : [paths]; - - var numWaiting = paths.length, - x = numWaiting, - pathsNotFound = [], - fn, - i; - - // define callback function - fn = function(path, result, defaultPrevented) { - // handle error - if (result == 'e') pathsNotFound.push(path); - - // handle beforeload event. If defaultPrevented then that means the load - // will be blocked (ex. Ghostery/ABP on Safari) - if (result == 'b') { - if (defaultPrevented) pathsNotFound.push(path); - else return; - } - - numWaiting--; - if (!numWaiting) callbackFn(pathsNotFound); - }; - - // load scripts - for (i=0; i < x; i++) loadFile(paths[i], fn, args); -} - - -/** - * Initiate script load and register bundle. - * @param {(string|string[])} paths - The file paths - * @param {(string|Function)} [arg1] - The bundleId or success callback - * @param {Function} [arg2] - The success or error callback - * @param {Function} [arg3] - The error callback - */ -function loadjs(paths, arg1, arg2) { - var bundleId, - args; - - // bundleId (if string) - if (arg1 && arg1.trim) bundleId = arg1; - - // args (default is {}) - args = (bundleId ? arg2 : arg1) || {}; - - // throw error if bundle is already defined - if (bundleId) { - if (bundleId in bundleIdCache) { - throw "LoadJS"; - } else { - bundleIdCache[bundleId] = true; - } - } - - // load scripts - loadFiles(paths, function (pathsNotFound) { - // execute callbacks - executeCallbacks(args, pathsNotFound); - - // publish bundle load event - publish(bundleId, pathsNotFound); - }, args); -} - - -/** - * Execute callbacks when dependencies have been satisfied. - * @param {(string|string[])} deps - List of bundle ids - * @param {Object} args - success/error arguments - */ -loadjs.ready = function ready(deps, args) { - // subscribe to bundle load event - subscribe(deps, function (depsNotFound) { - // execute callbacks - executeCallbacks(args, depsNotFound); - }); - - return loadjs; +var getConstructor = function getConstructor(input) { + return input !== null && typeof input !== 'undefined' ? input.constructor : null; }; - -/** - * Manually satisfy bundle dependencies. - * @param {string} bundleId - The bundle id - */ -loadjs.done = function done(bundleId) { - publish(bundleId, []); +var instanceOf = function instanceOf(input, constructor) { + return Boolean(input && constructor && input instanceof constructor); }; - -/** - * Reset loadjs dependencies statuses - */ -loadjs.reset = function reset() { - bundleIdCache = {}; - bundleResultCache = {}; - bundleCallbackQueue = {}; +var is = { + object: function object(input) { + return getConstructor(input) === Object; + }, + number: function number(input) { + return getConstructor(input) === Number && !Number.isNaN(input); + }, + string: function string(input) { + return getConstructor(input) === String; + }, + boolean: function boolean(input) { + return getConstructor(input) === Boolean; + }, + function: function _function(input) { + return getConstructor(input) === Function; + }, + array: function array(input) { + return !is.nullOrUndefined(input) && Array.isArray(input); + }, + weakMap: function weakMap(input) { + return instanceOf(input, WeakMap); + }, + nodeList: function nodeList(input) { + return instanceOf(input, NodeList); + }, + element: function element(input) { + return instanceOf(input, Element); + }, + textNode: function textNode(input) { + return getConstructor(input) === Text; + }, + event: function event(input) { + return instanceOf(input, Event); + }, + cue: function cue(input) { + return instanceOf(input, window.TextTrackCue) || instanceOf(input, window.VTTCue); + }, + track: function track(input) { + return instanceOf(input, TextTrack) || !is.nullOrUndefined(input) && is.string(input.kind); + }, + url: function url(input) { + return !is.nullOrUndefined(input) && /(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-/]))?/.test(input); + }, + nullOrUndefined: function nullOrUndefined(input) { + return input === null || typeof input === 'undefined'; + }, + empty: function empty(input) { + return is.nullOrUndefined(input) || (is.string(input) || is.array(input) || is.nodeList(input)) && !input.length || is.object(input) && !Object.keys(input).length; + } }; +// ========================================================================== -/** - * Determine if bundle has already been defined - * @param String} bundleId - The bundle id - */ -loadjs.isDefined = function isDefined(bundleId) { - return bundleId in bundleIdCache; -}; +// Check for passive event listener support +// https://github.com/WICG/EventListenerOptions/blob/gh-pages/explainer.md +// https://www.youtube.com/watch?v=NPM6172J22g +var supportsPassiveListeners = function () { + // Test via a getter in the options object to see if the passive property is accessed + var supported = false; + try { + var options = Object.defineProperty({}, 'passive', { + get: function get() { + supported = true; + return null; + } + }); + window.addEventListener('test', null, options); + window.removeEventListener('test', null, options); + } catch (e) { + // Do nothing + } + return supported; +}(); -// export -return loadjs; +// Toggle event listener +function toggleListener(elements, event, callback) { + var toggle = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false; + var passive = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : true; + var capture = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : false; -})); -}); + // Bail if no elemetns, event, or callback + if (is.empty(elements) || is.empty(event) || !is.function(callback)) { + return; + } + + // If a nodelist is passed, call itself on each node + if (is.nodeList(elements) || is.array(elements)) { + // Create listener for each node + Array.from(elements).forEach(function (element) { + if (element instanceof Node) { + toggleListener.call(null, element, event, callback, toggle, passive, capture); + } + }); + + return; + } + + // Allow multiple events + var events = event.split(' '); + + // Build options + // Default to just the capture boolean for browsers with no passive listener support + var options = capture; + + // If passive events listeners are supported + if (supportsPassiveListeners) { + options = { + // Whether the listener can be passive (i.e. default never prevented) + passive: passive, + // Whether the listener is a capturing listener or not + capture: capture + }; + } + + // If a single node is passed, bind the event listener + events.forEach(function (type) { + elements[toggle ? 'addEventListener' : 'removeEventListener'](type, callback, options); + }); +} + +// Bind event handler +function on(element) { + var events = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : ''; + var callback = arguments[2]; + var passive = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : true; + var capture = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false; + + toggleListener(element, events, callback, true, passive, capture); +} + +// Unbind event handler +function off(element) { + var events = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : ''; + var callback = arguments[2]; + var passive = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : true; + var capture = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false; + + toggleListener(element, events, callback, false, passive, capture); +} + +// Trigger event +function trigger(element) { + var type = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : ''; + var bubbles = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false; + var detail = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {}; + + // Bail if no element + if (!is.element(element) || is.empty(type)) { + return; + } + + // Create and dispatch the event + var event = new CustomEvent(type, { + bubbles: bubbles, + detail: Object.assign({}, detail, { + plyr: this + }) + }); + + // Dispatch the event + element.dispatchEvent(event); +} var classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { @@ -381,1089 +257,352 @@ var slicedToArray = function () { }; }(); -var toConsumableArray = function (arr) { - if (Array.isArray(arr)) { - for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) arr2[i] = arr[i]; +// ========================================================================== - return arr2; - } else { - return Array.from(arr); - } -}; +// Wrap an element +function wrap(elements, wrapper) { + // Convert `elements` to an array, if necessary. + var targets = elements.length ? elements : [elements]; + + // Loops backwards to prevent having to clone the wrapper on the + // first element (see `child` below). + Array.from(targets).reverse().forEach(function (element, index) { + var child = index > 0 ? wrapper.cloneNode(true) : wrapper; + + // Cache the current parent and sibling. + var parent = element.parentNode; + var sibling = element.nextSibling; + + // Wrap the element (is automatically removed from its current + // parent). + child.appendChild(element); + + // If the element had a sibling, insert the wrapper before + // the sibling to maintain the HTML structure; otherwise, just + // append it to the parent. + if (sibling) { + parent.insertBefore(child, sibling); + } else { + parent.appendChild(child); + } + }); +} + +// Set attributes +function setAttributes(element, attributes) { + if (!is.element(element) || is.empty(attributes)) { + return; + } + + Object.entries(attributes).forEach(function (_ref) { + var _ref2 = slicedToArray(_ref, 2), + key = _ref2[0], + value = _ref2[1]; + + element.setAttribute(key, value); + }); +} + +// Create a DocumentFragment +function createElement(type, attributes, text) { + // Create a new + var element = document.createElement(type); + + // Set all passed attributes + if (is.object(attributes)) { + setAttributes(element, attributes); + } + + // Add text node + if (is.string(text)) { + element.innerText = text; + } + + // Return built element + return element; +} + +// Inaert an element after another +function insertAfter(element, target) { + target.parentNode.insertBefore(element, target.nextSibling); +} + +// Insert a DocumentFragment +function insertElement(type, parent, attributes, text) { + // Inject the new + parent.appendChild(createElement(type, attributes, text)); +} + +// Remove element(s) +function removeElement(element) { + if (is.nodeList(element) || is.array(element)) { + Array.from(element).forEach(removeElement); + return; + } + + if (!is.element(element) || !is.element(element.parentNode)) { + return; + } + + element.parentNode.removeChild(element); +} + +// Remove all child elements +function emptyElement(element) { + var length = element.childNodes.length; + + + while (length > 0) { + element.removeChild(element.lastChild); + length -= 1; + } +} + +// Replace element +function replaceElement(newChild, oldChild) { + if (!is.element(oldChild) || !is.element(oldChild.parentNode) || !is.element(newChild)) { + return null; + } + + oldChild.parentNode.replaceChild(newChild, oldChild); + + return newChild; +} + +// Get an attribute object from a string selector +function getAttributesFromSelector(sel, existingAttributes) { + // For example: + // '.test' to { class: 'test' } + // '#test' to { id: 'test' } + // '[data-test="test"]' to { 'data-test': 'test' } + + if (!is.string(sel) || is.empty(sel)) { + return {}; + } + + var attributes = {}; + var existing = existingAttributes; + + sel.split(',').forEach(function (s) { + // Remove whitespace + var selector = s.trim(); + var className = selector.replace('.', ''); + var stripped = selector.replace(/[[\]]/g, ''); + + // Get the parts and value + var parts = stripped.split('='); + var key = parts[0]; + var value = parts.length > 1 ? parts[1].replace(/["']/g, '') : ''; + + // Get the first character + var start = selector.charAt(0); + + switch (start) { + case '.': + // Add to existing classname + if (is.object(existing) && is.string(existing.class)) { + existing.class += ' ' + className; + } + + attributes.class = className; + break; + + case '#': + // ID selector + attributes.id = selector.replace('#', ''); + break; + + case '[': + // Attribute selector + attributes[key] = value; + + break; + + default: + break; + } + }); + + return attributes; +} + +// Toggle hidden +function toggleHidden(element, hidden) { + if (!is.element(element)) { + return; + } + + var hide = hidden; + + if (!is.boolean(hide)) { + hide = !element.hasAttribute('hidden'); + } + + if (hide) { + element.setAttribute('hidden', ''); + } else { + element.removeAttribute('hidden'); + } +} + +// Mirror Element.classList.toggle, with IE compatibility for "force" argument +function toggleClass(element, className, force) { + if (is.element(element)) { + var method = 'toggle'; + if (typeof force !== 'undefined') { + method = force ? 'add' : 'remove'; + } + + element.classList[method](className); + return element.classList.contains(className); + } + + return null; +} + +// Has class name +function hasClass(element, className) { + return is.element(element) && element.classList.contains(className); +} + +// Element matches selector +function matches(element, selector) { + var prototype = { Element: Element }; + + function match() { + return Array.from(document.querySelectorAll(selector)).includes(this); + } + + var matches = prototype.matches || prototype.webkitMatchesSelector || prototype.mozMatchesSelector || prototype.msMatchesSelector || match; + + return matches.call(element, selector); +} + +// Find all elements +function getElements(selector) { + return this.elements.container.querySelectorAll(selector); +} + +// Find a single element +function getElement(selector) { + return this.elements.container.querySelector(selector); +} + +// Get the focused element +function getFocusElement() { + var focused = document.activeElement; + + if (!focused || focused === document.body) { + focused = null; + } else { + focused = document.querySelector(':focus'); + } + + return focused; +} + +// Trap focus inside container +function trapFocus() { + var element = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null; + var toggle = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; + + if (!is.element(element)) { + return; + } + + var focusable = getElements.call(this, 'button:not(:disabled), input:not(:disabled), [tabindex]'); + var first = focusable[0]; + var last = focusable[focusable.length - 1]; + + var trap = function trap(event) { + // Bail if not tab key or not fullscreen + if (event.key !== 'Tab' || event.keyCode !== 9) { + return; + } + + // Get the current focused element + var focused = getFocusElement(); + + if (focused === last && !event.shiftKey) { + // Move focus to first element that can be tabbed if Shift isn't used + first.focus(); + event.preventDefault(); + } else if (focused === first && event.shiftKey) { + // Move focus to last element that can be tabbed if Shift is used + last.focus(); + event.preventDefault(); + } + }; + + if (toggle) { + on(this.elements.container, 'keydown', trap, false); + } else { + off(this.elements.container, 'keydown', trap, false); + } +} + +// Toggle aria-pressed state on a toggle button +// http://www.ssbbartgroup.com/blog/how-not-to-misuse-aria-states-properties-and-roles +function toggleState(element, input) { + // If multiple elements passed + if (is.array(element) || is.nodeList(element)) { + Array.from(element).forEach(function (target) { + return toggleState(target, input); + }); + return; + } + + // Bail if no target + if (!is.element(element)) { + return; + } + + // Get state + var pressed = element.getAttribute('aria-pressed') === 'true'; + var state = is.boolean(input) ? input : !pressed; + + // Set the attribute on target + element.setAttribute('aria-pressed', state); +} // ========================================================================== -var Storage = function () { - function Storage(player) { - classCallCheck(this, Storage); +var transitionEndEvent = function () { + var element = document.createElement('span'); - this.enabled = player.config.storage.enabled; - this.key = player.config.storage.key; - } + var events = { + WebkitTransition: 'webkitTransitionEnd', + MozTransition: 'transitionend', + OTransition: 'oTransitionEnd otransitionend', + transition: 'transitionend' + }; - // Check for actual support (see if we can use it) + var type = Object.keys(events).find(function (event) { + return element.style[event] !== undefined; + }); - - createClass(Storage, [{ - key: 'get', - value: function get$$1(key) { - if (!Storage.supported || !this.enabled) { - return null; - } - - var store = window.localStorage.getItem(this.key); - - if (utils.is.empty(store)) { - return null; - } - - var json = JSON.parse(store); - - return utils.is.string(key) && key.length ? json[key] : json; - } - }, { - key: 'set', - value: function set$$1(object) { - // Bail if we don't have localStorage support or it's disabled - if (!Storage.supported || !this.enabled) { - return; - } - - // Can only store objectst - if (!utils.is.object(object)) { - return; - } - - // Get current storage - var storage = this.get(); - - // Default to empty object - if (utils.is.empty(storage)) { - storage = {}; - } - - // Update the working copy of the values - utils.extend(storage, object); - - // Update storage - window.localStorage.setItem(this.key, JSON.stringify(storage)); - } - }], [{ - key: 'supported', - get: function get$$1() { - try { - if (!('localStorage' in window)) { - return false; - } - - var test = '___test'; - - // Try to use it (it might be disabled, e.g. user is in private mode) - // see: https://github.com/sampotts/plyr/issues/131 - window.localStorage.setItem(test, test); - window.localStorage.removeItem(test); - - return true; - } catch (e) { - return false; - } - } - }]); - return Storage; + return is.string(type) ? events[type] : false; }(); -// ========================================================================== -// Plyr supported types and providers -// ========================================================================== - -var providers = { - html5: 'html5', - youtube: 'youtube', - vimeo: 'vimeo' -}; - -var types = { - audio: 'audio', - video: 'video' -}; +// Force repaint of element +function repaint(element) { + setTimeout(function () { + toggleHidden(element, true); + element.offsetHeight; // eslint-disable-line + toggleHidden(element, false); + }, 0); +} // ========================================================================== - -var utils = { - // Check variable types - is: { - object: function object(input) { - return utils.getConstructor(input) === Object; - }, - number: function number(input) { - return utils.getConstructor(input) === Number && !Number.isNaN(input); - }, - string: function string(input) { - return utils.getConstructor(input) === String; - }, - boolean: function boolean(input) { - return utils.getConstructor(input) === Boolean; - }, - function: function _function(input) { - return utils.getConstructor(input) === Function; - }, - array: function array(input) { - return !utils.is.nullOrUndefined(input) && Array.isArray(input); - }, - weakMap: function weakMap(input) { - return utils.is.instanceof(input, WeakMap); - }, - nodeList: function nodeList(input) { - return utils.is.instanceof(input, NodeList); - }, - element: function element(input) { - return utils.is.instanceof(input, Element); - }, - textNode: function textNode(input) { - return utils.getConstructor(input) === Text; - }, - event: function event(input) { - return utils.is.instanceof(input, Event); - }, - cue: function cue(input) { - return utils.is.instanceof(input, window.TextTrackCue) || utils.is.instanceof(input, window.VTTCue); - }, - track: function track(input) { - return utils.is.instanceof(input, TextTrack) || !utils.is.nullOrUndefined(input) && utils.is.string(input.kind); - }, - url: function url(input) { - return !utils.is.nullOrUndefined(input) && /(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-/]))?/.test(input); - }, - nullOrUndefined: function nullOrUndefined(input) { - return input === null || typeof input === 'undefined'; - }, - empty: function empty(input) { - return utils.is.nullOrUndefined(input) || (utils.is.string(input) || utils.is.array(input) || utils.is.nodeList(input)) && !input.length || utils.is.object(input) && !Object.keys(input).length; - }, - instanceof: function _instanceof$$1(input, constructor) { - return Boolean(input && constructor && input instanceof constructor); - } - }, - - getConstructor: function getConstructor(input) { - return !utils.is.nullOrUndefined(input) ? input.constructor : null; - }, - - - // Unfortunately, due to mixed support, UA sniffing is required - getBrowser: function getBrowser() { - return { - isIE: /* @cc_on!@ */false || !!document.documentMode, - isWebkit: 'WebkitAppearance' in document.documentElement.style && !/Edge/.test(navigator.userAgent), - isIPhone: /(iPhone|iPod)/gi.test(navigator.platform), - isIos: /(iPad|iPhone|iPod)/gi.test(navigator.platform) - }; - }, - - - // Fetch wrapper - // Using XHR to avoid issues with older browsers - fetch: function fetch(url) { - var responseType = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'text'; - - return new Promise(function (resolve, reject) { - try { - var request = new XMLHttpRequest(); - - // Check for CORS support - if (!('withCredentials' in request)) { - return; - } - - request.addEventListener('load', function () { - if (responseType === 'text') { - try { - resolve(JSON.parse(request.responseText)); - } catch (e) { - resolve(request.responseText); - } - } else { - resolve(request.response); - } - }); - - request.addEventListener('error', function () { - throw new Error(request.statusText); - }); - - request.open('GET', url, true); - - // Set the required response type - request.responseType = responseType; - - request.send(); - } catch (e) { - reject(e); - } - }); - }, - - - // Load image avoiding xhr/fetch CORS issues - // Server status can't be obtained this way unfortunately, so this uses "naturalWidth" to determine if the image has loaded. - // By default it checks if it is at least 1px, but you can add a second argument to change this. - loadImage: function loadImage(src) { - var minWidth = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1; - - return new Promise(function (resolve, reject) { - var image = new Image(); - var handler = function handler() { - delete image.onload; - delete image.onerror; - (image.naturalWidth >= minWidth ? resolve : reject)(image); - }; - Object.assign(image, { onload: handler, onerror: handler, src: src }); - }); - }, - - - // Load an external script - loadScript: function loadScript(url) { - return new Promise(function (resolve, reject) { - loadjs_umd(url, { - success: resolve, - error: reject - }); - }); - }, - - - // Load an external SVG sprite - loadSprite: function loadSprite(url, id) { - if (!utils.is.string(url)) { - return; - } - - var prefix = 'cache'; - var hasId = utils.is.string(id); - var isCached = false; - - var exists = function exists() { - return document.getElementById(id) !== null; - }; - - var update = function update(container, data) { - container.innerHTML = data; - - // Check again incase of race condition - if (hasId && exists()) { - return; - } - - // Inject the SVG to the body - document.body.insertAdjacentElement('afterbegin', container); - }; - - // Only load once if ID set - if (!hasId || !exists()) { - var useStorage = Storage.supported; - - // Create container - var container = document.createElement('div'); - utils.toggleHidden(container, true); - - if (hasId) { - container.setAttribute('id', id); - } - - // Check in cache - if (useStorage) { - var cached = window.localStorage.getItem(prefix + '-' + id); - isCached = cached !== null; - - if (isCached) { - var data = JSON.parse(cached); - update(container, data.content); - } - } - - // Get the sprite - utils.fetch(url).then(function (result) { - if (utils.is.empty(result)) { - return; - } - - if (useStorage) { - window.localStorage.setItem(prefix + '-' + id, JSON.stringify({ - content: result - })); - } - - update(container, result); - }).catch(function () {}); - } - }, - - - // Generate a random ID - generateId: function generateId(prefix) { - return prefix + '-' + Math.floor(Math.random() * 10000); - }, - - - // Wrap an element - wrap: function wrap(elements, wrapper) { - // Convert `elements` to an array, if necessary. - var targets = elements.length ? elements : [elements]; - - // Loops backwards to prevent having to clone the wrapper on the - // first element (see `child` below). - Array.from(targets).reverse().forEach(function (element, index) { - var child = index > 0 ? wrapper.cloneNode(true) : wrapper; - - // Cache the current parent and sibling. - var parent = element.parentNode; - var sibling = element.nextSibling; - - // Wrap the element (is automatically removed from its current - // parent). - child.appendChild(element); - - // If the element had a sibling, insert the wrapper before - // the sibling to maintain the HTML structure; otherwise, just - // append it to the parent. - if (sibling) { - parent.insertBefore(child, sibling); - } else { - parent.appendChild(child); - } - }); - }, - - - // Create a DocumentFragment - createElement: function createElement(type, attributes, text) { - // Create a new - var element = document.createElement(type); - - // Set all passed attributes - if (utils.is.object(attributes)) { - utils.setAttributes(element, attributes); - } - - // Add text node - if (utils.is.string(text)) { - element.innerText = text; - } - - // Return built element - return element; - }, - - - // Inaert an element after another - insertAfter: function insertAfter(element, target) { - target.parentNode.insertBefore(element, target.nextSibling); - }, - - - // Insert a DocumentFragment - insertElement: function insertElement(type, parent, attributes, text) { - // Inject the new - parent.appendChild(utils.createElement(type, attributes, text)); - }, - - - // Remove element(s) - removeElement: function removeElement(element) { - if (utils.is.nodeList(element) || utils.is.array(element)) { - Array.from(element).forEach(utils.removeElement); - return; - } - - if (!utils.is.element(element) || !utils.is.element(element.parentNode)) { - return; - } - - element.parentNode.removeChild(element); - }, - - - // Remove all child elements - emptyElement: function emptyElement(element) { - var length = element.childNodes.length; - - - while (length > 0) { - element.removeChild(element.lastChild); - length -= 1; - } - }, - - - // Replace element - replaceElement: function replaceElement(newChild, oldChild) { - if (!utils.is.element(oldChild) || !utils.is.element(oldChild.parentNode) || !utils.is.element(newChild)) { - return null; - } - - oldChild.parentNode.replaceChild(newChild, oldChild); - - return newChild; - }, - - - // Set attributes - setAttributes: function setAttributes(element, attributes) { - if (!utils.is.element(element) || utils.is.empty(attributes)) { - return; - } - - Object.entries(attributes).forEach(function (_ref) { - var _ref2 = slicedToArray(_ref, 2), - key = _ref2[0], - value = _ref2[1]; - - element.setAttribute(key, value); - }); - }, - - - // Get an attribute object from a string selector - getAttributesFromSelector: function getAttributesFromSelector(sel, existingAttributes) { - // For example: - // '.test' to { class: 'test' } - // '#test' to { id: 'test' } - // '[data-test="test"]' to { 'data-test': 'test' } - - if (!utils.is.string(sel) || utils.is.empty(sel)) { - return {}; - } - - var attributes = {}; - var existing = existingAttributes; - - sel.split(',').forEach(function (s) { - // Remove whitespace - var selector = s.trim(); - var className = selector.replace('.', ''); - var stripped = selector.replace(/[[\]]/g, ''); - - // Get the parts and value - var parts = stripped.split('='); - var key = parts[0]; - var value = parts.length > 1 ? parts[1].replace(/["']/g, '') : ''; - - // Get the first character - var start = selector.charAt(0); - - switch (start) { - case '.': - // Add to existing classname - if (utils.is.object(existing) && utils.is.string(existing.class)) { - existing.class += ' ' + className; - } - - attributes.class = className; - break; - - case '#': - // ID selector - attributes.id = selector.replace('#', ''); - break; - - case '[': - // Attribute selector - attributes[key] = value; - - break; - - default: - break; - } - }); - - return attributes; - }, - - - // Toggle hidden - toggleHidden: function toggleHidden(element, hidden) { - if (!utils.is.element(element)) { - return; - } - - var hide = hidden; - - if (!utils.is.boolean(hide)) { - hide = !element.hasAttribute('hidden'); - } - - if (hide) { - element.setAttribute('hidden', ''); - } else { - element.removeAttribute('hidden'); - } - }, - - - // Mirror Element.classList.toggle, with IE compatibility for "force" argument - toggleClass: function toggleClass(element, className, force) { - if (utils.is.element(element)) { - var method = 'toggle'; - if (typeof force !== 'undefined') { - method = force ? 'add' : 'remove'; - } - - element.classList[method](className); - return element.classList.contains(className); - } - - return null; - }, - - - // Has class name - hasClass: function hasClass(element, className) { - return utils.is.element(element) && element.classList.contains(className); - }, - - - // Element matches selector - matches: function matches(element, selector) { - var prototype = { Element: Element }; - - function match() { - return Array.from(document.querySelectorAll(selector)).includes(this); - } - - var matches = prototype.matches || prototype.webkitMatchesSelector || prototype.mozMatchesSelector || prototype.msMatchesSelector || match; - - return matches.call(element, selector); - }, - - - // Find all elements - getElements: function getElements(selector) { - return this.elements.container.querySelectorAll(selector); - }, - - - // Find a single element - getElement: function getElement(selector) { - return this.elements.container.querySelector(selector); - }, - - - // Get the focused element - getFocusElement: function getFocusElement() { - var focused = document.activeElement; - - if (!focused || focused === document.body) { - focused = null; - } else { - focused = document.querySelector(':focus'); - } - - return focused; - }, - - - // Trap focus inside container - trapFocus: function trapFocus() { - var element = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null; - var toggle = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; - - if (!utils.is.element(element)) { - return; - } - - var focusable = utils.getElements.call(this, 'button:not(:disabled), input:not(:disabled), [tabindex]'); - var first = focusable[0]; - var last = focusable[focusable.length - 1]; - - var trap = function trap(event) { - // Bail if not tab key or not fullscreen - if (event.key !== 'Tab' || event.keyCode !== 9) { - return; - } - - // Get the current focused element - var focused = utils.getFocusElement(); - - if (focused === last && !event.shiftKey) { - // Move focus to first element that can be tabbed if Shift isn't used - first.focus(); - event.preventDefault(); - } else if (focused === first && event.shiftKey) { - // Move focus to last element that can be tabbed if Shift is used - last.focus(); - event.preventDefault(); - } - }; - - if (toggle) { - utils.on(this.elements.container, 'keydown', trap, false); - } else { - utils.off(this.elements.container, 'keydown', trap, false); - } - }, - - - // Toggle event listener - toggleListener: function toggleListener(elements, event, callback) { - var toggle = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false; - var passive = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : true; - var capture = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : false; - - // Bail if no elemetns, event, or callback - if (utils.is.empty(elements) || utils.is.empty(event) || !utils.is.function(callback)) { - return; - } - - // If a nodelist is passed, call itself on each node - if (utils.is.nodeList(elements) || utils.is.array(elements)) { - // Create listener for each node - Array.from(elements).forEach(function (element) { - if (element instanceof Node) { - utils.toggleListener.call(null, element, event, callback, toggle, passive, capture); - } - }); - - return; - } - - // Allow multiple events - var events = event.split(' '); - - // Build options - // Default to just the capture boolean for browsers with no passive listener support - var options = capture; - - // If passive events listeners are supported - if (support.passiveListeners) { - options = { - // Whether the listener can be passive (i.e. default never prevented) - passive: passive, - // Whether the listener is a capturing listener or not - capture: capture - }; - } - - // If a single node is passed, bind the event listener - events.forEach(function (type) { - elements[toggle ? 'addEventListener' : 'removeEventListener'](type, callback, options); - }); - }, - - - // Bind event handler - on: function on(element) { - var events = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : ''; - var callback = arguments[2]; - var passive = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : true; - var capture = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false; - - utils.toggleListener(element, events, callback, true, passive, capture); - }, - - - // Unbind event handler - off: function off(element) { - var events = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : ''; - var callback = arguments[2]; - var passive = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : true; - var capture = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false; - - utils.toggleListener(element, events, callback, false, passive, capture); - }, - - - // Trigger event - dispatchEvent: function dispatchEvent(element) { - var type = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : ''; - var bubbles = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false; - var detail = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {}; - - // Bail if no element - if (!utils.is.element(element) || utils.is.empty(type)) { - return; - } - - // Create and dispatch the event - var event = new CustomEvent(type, { - bubbles: bubbles, - detail: Object.assign({}, detail, { - plyr: this - }) - }); - - // Dispatch the event - element.dispatchEvent(event); - }, - - - // Toggle aria-pressed state on a toggle button - // http://www.ssbbartgroup.com/blog/how-not-to-misuse-aria-states-properties-and-roles - toggleState: function toggleState(element, input) { - // If multiple elements passed - if (utils.is.array(element) || utils.is.nodeList(element)) { - Array.from(element).forEach(function (target) { - return utils.toggleState(target, input); - }); - return; - } - - // Bail if no target - if (!utils.is.element(element)) { - return; - } - - // Get state - var pressed = element.getAttribute('aria-pressed') === 'true'; - var state = utils.is.boolean(input) ? input : !pressed; - - // Set the attribute on target - element.setAttribute('aria-pressed', state); - }, - - - // Format string - format: function format(input) { - for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { - args[_key - 1] = arguments[_key]; - } - - if (utils.is.empty(input)) { - return input; - } - - return input.toString().replace(/{(\d+)}/g, function (match, i) { - return utils.is.string(args[i]) ? args[i] : ''; - }); - }, - - - // Get percentage - getPercentage: function getPercentage(current, max) { - if (current === 0 || max === 0 || Number.isNaN(current) || Number.isNaN(max)) { - return 0; - } - - return (current / max * 100).toFixed(2); - }, - - - // Time helpers - getHours: function getHours(value) { - return parseInt(value / 60 / 60 % 60, 10); - }, - getMinutes: function getMinutes(value) { - return parseInt(value / 60 % 60, 10); - }, - getSeconds: function getSeconds(value) { - return parseInt(value % 60, 10); - }, - - - // Format time to UI friendly string - formatTime: function formatTime() { - var time = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0; - var displayHours = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; - var inverted = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false; - - // Bail if the value isn't a number - if (!utils.is.number(time)) { - return utils.formatTime(null, displayHours, inverted); - } - - // Format time component to add leading zero - var format = function format(value) { - return ('0' + value).slice(-2); - }; - - // Breakdown to hours, mins, secs - var hours = utils.getHours(time); - var mins = utils.getMinutes(time); - var secs = utils.getSeconds(time); - - // Do we need to display hours? - if (displayHours || hours > 0) { - hours = hours + ':'; - } else { - hours = ''; - } - - // Render - return '' + (inverted ? '-' : '') + hours + format(mins) + ':' + format(secs); - }, - - - // Replace all occurances of a string in a string - replaceAll: function replaceAll() { - var input = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ''; - var find = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : ''; - var replace = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : ''; - - return input.replace(new RegExp(find.toString().replace(/([.*+?^=!:${}()|[\]/\\])/g, '\\$1'), 'g'), replace.toString()); - }, - - - // Convert to title case - toTitleCase: function toTitleCase() { - var input = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ''; - - return input.toString().replace(/\w\S*/g, function (text) { - return text.charAt(0).toUpperCase() + text.substr(1).toLowerCase(); - }); - }, - - - // Convert string to pascalCase - toPascalCase: function toPascalCase() { - var input = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ''; - - var string = input.toString(); - - // Convert kebab case - string = utils.replaceAll(string, '-', ' '); - - // Convert snake case - string = utils.replaceAll(string, '_', ' '); - - // Convert to title case - string = utils.toTitleCase(string); - - // Convert to pascal case - return utils.replaceAll(string, ' ', ''); - }, - - - // Convert string to pascalCase - toCamelCase: function toCamelCase() { - var input = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ''; - - var string = input.toString(); - - // Convert to pascal case - string = utils.toPascalCase(string); - - // Convert first character to lowercase - return string.charAt(0).toLowerCase() + string.slice(1); - }, - - - // Deep extend destination object with N more objects - extend: function extend() { - var target = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; - - for (var _len2 = arguments.length, sources = Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) { - sources[_key2 - 1] = arguments[_key2]; - } - - if (!sources.length) { - return target; - } - - var source = sources.shift(); - - if (!utils.is.object(source)) { - return target; - } - - Object.keys(source).forEach(function (key) { - if (utils.is.object(source[key])) { - if (!Object.keys(target).includes(key)) { - Object.assign(target, defineProperty({}, key, {})); - } - - utils.extend(target[key], source[key]); - } else { - Object.assign(target, defineProperty({}, key, source[key])); - } - }); - - return utils.extend.apply(utils, [target].concat(toConsumableArray(sources))); - }, - - - // Remove duplicates in an array - dedupe: function dedupe(array) { - if (!utils.is.array(array)) { - return array; - } - - return array.filter(function (item, index) { - return array.indexOf(item) === index; - }); - }, - - - // Clone nested objects - cloneDeep: function cloneDeep(object) { - return JSON.parse(JSON.stringify(object)); - }, - - - // Get a nested value in an object - getDeep: function getDeep(object, path) { - return path.split('.').reduce(function (obj, key) { - return obj && obj[key]; - }, object); - }, - - - // Get the closest value in an array - closest: function closest(array, value) { - if (!utils.is.array(array) || !array.length) { - return null; - } - - return array.reduce(function (prev, curr) { - return Math.abs(curr - value) < Math.abs(prev - value) ? curr : prev; - }); - }, - - - // Get the provider for a given URL - getProviderByUrl: function getProviderByUrl(url) { - // YouTube - if (/^(https?:\/\/)?(www\.)?(youtube\.com|youtu\.?be)\/.+$/.test(url)) { - return providers.youtube; - } - - // Vimeo - if (/^https?:\/\/player.vimeo.com\/video\/\d{0,9}(?=\b|\/)/.test(url)) { - return providers.vimeo; - } - - return null; - }, - - - // Parse YouTube ID from URL - parseYouTubeId: function parseYouTubeId(url) { - if (utils.is.empty(url)) { - return null; - } - - var regex = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|&v=)([^#&?]*).*/; - return url.match(regex) ? RegExp.$2 : url; - }, - - - // Parse Vimeo ID from URL - parseVimeoId: function parseVimeoId(url) { - if (utils.is.empty(url)) { - return null; - } - - if (utils.is.number(Number(url))) { - return url; - } - - var regex = /^.*(vimeo.com\/|video\/)(\d+).*/; - return url.match(regex) ? RegExp.$2 : url; - }, - - - // Convert a URL to a location object - parseUrl: function parseUrl(url) { - var parser = document.createElement('a'); - parser.href = url; - return parser; - }, - - - // Get URL query parameters - getUrlParams: function getUrlParams(input) { - var search = input; - - // Parse URL if needed - if (input.startsWith('http://') || input.startsWith('https://')) { - var _utils$parseUrl = utils.parseUrl(input); - - search = _utils$parseUrl.search; - } - - if (utils.is.empty(search)) { - return null; - } - - var hashes = search.slice(search.indexOf('?') + 1).split('&'); - - return hashes.reduce(function (params, hash) { - var _hash$split = hash.split('='), - _hash$split2 = slicedToArray(_hash$split, 2), - key = _hash$split2[0], - val = _hash$split2[1]; - - return Object.assign(params, defineProperty({}, key, decodeURIComponent(val))); - }, {}); - }, - - - // Convert object to URL parameters - buildUrlParams: function buildUrlParams(input) { - if (!utils.is.object(input)) { - return ''; - } - - return Object.keys(input).map(function (key) { - return encodeURIComponent(key) + '=' + encodeURIComponent(input[key]); - }).join('&'); - }, - - - // Remove HTML from a string - stripHTML: function stripHTML(source) { - var fragment = document.createDocumentFragment(); - var element = document.createElement('div'); - fragment.appendChild(element); - element.innerHTML = source; - return fragment.firstChild.innerText; - }, - - - // Like outerHTML, but also works for DocumentFragment - getHTML: function getHTML(element) { - var wrapper = document.createElement('div'); - wrapper.appendChild(element); - return wrapper.innerHTML; - }, - - - // Get aspect ratio for dimensions - getAspectRatio: function getAspectRatio(width, height) { - var getRatio = function getRatio(w, h) { - return h === 0 ? w : getRatio(h, w % h); - }; - var ratio = getRatio(width, height); - return width / ratio + ':' + height / ratio; - }, - - - // Get the transition end event - get transitionEndEvent() { - var element = document.createElement('span'); - - var events = { - WebkitTransition: 'webkitTransitionEnd', - MozTransition: 'transitionend', - OTransition: 'oTransitionEnd otransitionend', - transition: 'transitionend' - }; - - var type = Object.keys(events).find(function (event) { - return element.style[event] !== undefined; - }); - - return utils.is.string(type) ? events[type] : false; - }, - - // Force repaint of element - repaint: function repaint(element) { - setTimeout(function () { - utils.toggleHidden(element, true); - element.offsetHeight; // eslint-disable-line - utils.toggleHidden(element, false); - }, 0); - } +// Browser sniffing +// Unfortunately, due to mixed support, UA sniffing is required +// ========================================================================== + +var browser = { + isIE: /* @cc_on!@ */false || !!document.documentMode, + isWebkit: 'WebkitAppearance' in document.documentElement.style && !/Edge/.test(navigator.userAgent), + isIPhone: /(iPhone|iPod)/gi.test(navigator.platform), + isIos: /(iPad|iPhone|iPod)/gi.test(navigator.platform) }; // ========================================================================== @@ -1479,7 +618,6 @@ var support = { check: function check(type, provider, playsinline) { var api = false; var ui = false; - var browser = utils.getBrowser(); var canPlayInline = browser.isIPhone && playsinline && support.playsinline; switch (provider + ':' + type) { @@ -1514,13 +652,12 @@ var support = { // Picture-in-picture support // Safari only currently pip: function () { - var browser = utils.getBrowser(); - return !browser.isIPhone && utils.is.function(utils.createElement('video').webkitSetPresentationMode); + return !browser.isIPhone && is.function(createElement('video').webkitSetPresentationMode); }(), // Airplay support // Safari only currently - airplay: utils.is.function(window.WebKitPlaybackTargetAvailabilityEvent), + airplay: is.function(window.WebKitPlaybackTargetAvailabilityEvent), // Inline playback support // https://webkit.org/blog/6784/new-video-policies-for-ios/ @@ -1535,7 +672,7 @@ var support = { try { // Bail if no checking function - if (!this.isHTML5 || !utils.is.function(media.canPlayType)) { + if (!this.isHTML5 || !is.function(media.canPlayType)) { return false; } @@ -1586,28 +723,6 @@ var support = { // Check for textTracks support textTracks: 'textTracks' in document.createElement('video'), - // Check for passive event listener support - // https://github.com/WICG/EventListenerOptions/blob/gh-pages/explainer.md - // https://www.youtube.com/watch?v=NPM6172J22g - passiveListeners: function () { - // Test via a getter in the options object to see if the passive property is accessed - var supported = false; - try { - var options = Object.defineProperty({}, 'passive', { - get: function get() { - supported = true; - return null; - } - }); - window.addEventListener('test', null, options); - window.removeEventListener('test', null, options); - } catch (e) { - // Do nothing - } - - return supported; - }(), - // Sliders rangeInput: function () { var range = document.createElement('input'); @@ -1620,7 +735,7 @@ var support = { touch: 'ontouchstart' in document.documentElement, // Detect transitions support - transitions: utils.transitionEndEvent !== false, + transitions: transitionEndEvent !== false, // Reduced motion iOS & MacOS setting // https://webkit.org/blog/7551/responsive-design-for-motion/ @@ -1629,6 +744,30 @@ var support = { // ========================================================================== +// Remove duplicates in an array +function dedupe(array) { + if (!is.array(array)) { + return array; + } + + return array.filter(function (item, index) { + return array.indexOf(item) === index; + }); +} + +// Get the closest value in an array +function closest(array, value) { + if (!is.array(array) || !array.length) { + return null; + } + + return array.reduce(function (prev, curr) { + return Math.abs(curr - value) < Math.abs(prev - value) ? curr : prev; + }); +} + +// ========================================================================== + var html5 = { getSources: function getSources() { if (!this.isHTML5) { @@ -1648,22 +787,22 @@ var html5 = { // Get sources var sources = html5.getSources.call(this); - if (utils.is.empty(sources)) { + if (is.empty(sources)) { return null; } // Get with size attribute var sizes = Array.from(sources).filter(function (source) { - return !utils.is.empty(source.getAttribute('size')); + return !is.empty(source.getAttribute('size')); }); // If none, bail - if (utils.is.empty(sizes)) { + if (is.empty(sizes)) { return null; } // Reduce to unique list - return utils.dedupe(sizes.map(function (source) { + return dedupe(sizes.map(function (source) { return Number(source.getAttribute('size')); })); }, @@ -1680,50 +819,50 @@ var html5 = { // Get sources var sources = html5.getSources.call(player); - if (utils.is.empty(sources)) { + if (is.empty(sources)) { return null; } - var matches = Array.from(sources).filter(function (source) { + var matches$$1 = Array.from(sources).filter(function (source) { return source.getAttribute('src') === player.source; }); - if (utils.is.empty(matches)) { + if (is.empty(matches$$1)) { return null; } - return Number(matches[0].getAttribute('size')); + return Number(matches$$1[0].getAttribute('size')); }, set: function set(input) { // Get sources var sources = html5.getSources.call(player); - if (utils.is.empty(sources)) { + if (is.empty(sources)) { return; } // Get matches for requested size - var matches = Array.from(sources).filter(function (source) { + var matches$$1 = Array.from(sources).filter(function (source) { return Number(source.getAttribute('size')) === input; }); // No matches for requested size - if (utils.is.empty(matches)) { + if (is.empty(matches$$1)) { return; } // Get supported sources - var supported = matches.filter(function (source) { + var supported = matches$$1.filter(function (source) { return support.mime.call(player, source.getAttribute('type')); }); // No supported sources - if (utils.is.empty(supported)) { + if (is.empty(supported)) { return; } // Trigger change event - utils.dispatchEvent.call(player, player.media, 'qualityrequested', false, { + trigger.call(player, player.media, 'qualityrequested', false, { quality: input }); @@ -1751,7 +890,7 @@ var html5 = { } // Trigger change event - utils.dispatchEvent.call(player, player.media, 'qualitychange', false, { + trigger.call(player, player.media, 'qualitychange', false, { quality: input }); } @@ -1767,7 +906,7 @@ var html5 = { } // Remove child sources - utils.removeElement(html5.getSources()); + removeElement(html5.getSources()); // Set blank video src attribute // This is to prevent a MEDIA_ERR_SRC_NOT_SUPPORTED error @@ -1786,18 +925,162 @@ var html5 = { // ========================================================================== +// Clone nested objects +function cloneDeep(object) { + return JSON.parse(JSON.stringify(object)); +} + +// Get a nested value in an object +function getDeep(object, path) { + return path.split('.').reduce(function (obj, key) { + return obj && obj[key]; + }, object); +} + +// Deep extend destination object with N more objects +function extend() { + var target = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + + for (var _len = arguments.length, sources = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { + sources[_key - 1] = arguments[_key]; + } + + if (!sources.length) { + return target; + } + + var source = sources.shift(); + + if (!is.object(source)) { + return target; + } + + Object.keys(source).forEach(function (key) { + if (is.object(source[key])) { + if (!Object.keys(target).includes(key)) { + Object.assign(target, defineProperty({}, key, {})); + } + + extend(target[key], source[key]); + } else { + Object.assign(target, defineProperty({}, key, source[key])); + } + }); + + return extend.apply(undefined, [target].concat(sources)); +} + +// ========================================================================== + +// Generate a random ID +function generateId(prefix) { + return prefix + '-' + Math.floor(Math.random() * 10000); +} + +// Format string +function format(input) { + for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { + args[_key - 1] = arguments[_key]; + } + + if (is.empty(input)) { + return input; + } + + return input.toString().replace(/{(\d+)}/g, function (match, i) { + return is.string(args[i]) ? args[i] : ''; + }); +} + +// Get percentage +function getPercentage(current, max) { + if (current === 0 || max === 0 || Number.isNaN(current) || Number.isNaN(max)) { + return 0; + } + + return (current / max * 100).toFixed(2); +} + +// Replace all occurances of a string in a string +function replaceAll() { + var input = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ''; + var find = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : ''; + var replace = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : ''; + + return input.replace(new RegExp(find.toString().replace(/([.*+?^=!:${}()|[\]/\\])/g, '\\$1'), 'g'), replace.toString()); +} + +// Convert to title case +function toTitleCase() { + var input = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ''; + + return input.toString().replace(/\w\S*/g, function (text) { + return text.charAt(0).toUpperCase() + text.substr(1).toLowerCase(); + }); +} + +// Convert string to pascalCase +function toPascalCase() { + var input = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ''; + + var string = input.toString(); + + // Convert kebab case + string = replaceAll(string, '-', ' '); + + // Convert snake case + string = replaceAll(string, '_', ' '); + + // Convert to title case + string = toTitleCase(string); + + // Convert to pascal case + return replaceAll(string, ' ', ''); +} + +// Convert string to pascalCase +function toCamelCase() { + var input = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ''; + + var string = input.toString(); + + // Convert to pascal case + string = toPascalCase(string); + + // Convert first character to lowercase + return string.charAt(0).toLowerCase() + string.slice(1); +} + +// Remove HTML from a string +function stripHTML(source) { + var fragment = document.createDocumentFragment(); + var element = document.createElement('div'); + fragment.appendChild(element); + element.innerHTML = source; + return fragment.firstChild.innerText; +} + +// Like outerHTML, but also works for DocumentFragment +function getHTML(element) { + var wrapper = document.createElement('div'); + wrapper.appendChild(element); + return wrapper.innerHTML; +} + +// ========================================================================== + var i18n = { get: function get$$1() { var key = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ''; var config = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - if (utils.is.empty(key) || utils.is.empty(config)) { + if (is.empty(key) || is.empty(config)) { return ''; } - var string = utils.getDeep(config.i18n, key); + var string = getDeep(config.i18n, key); - if (utils.is.empty(string)) { + if (is.empty(string)) { return ''; } @@ -1811,7 +1094,7 @@ var i18n = { key = _ref2[0], value = _ref2[1]; - string = utils.replaceAll(string, key, value); + string = replaceAll(string, key, value); }); return string; @@ -1820,11 +1103,201 @@ var i18n = { // ========================================================================== -// Sniff out the browser -var browser = utils.getBrowser(); +var Storage = function () { + function Storage(player) { + classCallCheck(this, Storage); + + this.enabled = player.config.storage.enabled; + this.key = player.config.storage.key; + } + + // Check for actual support (see if we can use it) + + + createClass(Storage, [{ + key: 'get', + value: function get$$1(key) { + if (!Storage.supported || !this.enabled) { + return null; + } + + var store = window.localStorage.getItem(this.key); + + if (is.empty(store)) { + return null; + } + + var json = JSON.parse(store); + + return is.string(key) && key.length ? json[key] : json; + } + }, { + key: 'set', + value: function set$$1(object) { + // Bail if we don't have localStorage support or it's disabled + if (!Storage.supported || !this.enabled) { + return; + } + + // Can only store objectst + if (!is.object(object)) { + return; + } + + // Get current storage + var storage = this.get(); + + // Default to empty object + if (is.empty(storage)) { + storage = {}; + } + + // Update the working copy of the values + extend(storage, object); + + // Update storage + window.localStorage.setItem(this.key, JSON.stringify(storage)); + } + }], [{ + key: 'supported', + get: function get$$1() { + try { + if (!('localStorage' in window)) { + return false; + } + + var test = '___test'; + + // Try to use it (it might be disabled, e.g. user is in private mode) + // see: https://github.com/sampotts/plyr/issues/131 + window.localStorage.setItem(test, test); + window.localStorage.removeItem(test); + + return true; + } catch (e) { + return false; + } + } + }]); + return Storage; +}(); + +// ========================================================================== + +// Load an external SVG sprite +function loadSprite(url, id) { + if (!is.string(url)) { + return; + } + + var prefix = 'cache'; + var hasId = is.string(id); + var isCached = false; + + var exists = function exists() { + return document.getElementById(id) !== null; + }; + + var update = function update(container, data) { + container.innerHTML = data; + + // Check again incase of race condition + if (hasId && exists()) { + return; + } + + // Inject the SVG to the body + document.body.insertAdjacentElement('afterbegin', container); + }; + + // Only load once if ID set + if (!hasId || !exists()) { + var useStorage = Storage.supported; + + // Create container + var container = document.createElement('div'); + container.setAttribute('hidden', ''); + + if (hasId) { + container.setAttribute('id', id); + } + + // Check in cache + if (useStorage) { + var cached = window.localStorage.getItem(prefix + '-' + id); + isCached = cached !== null; + + if (isCached) { + var data = JSON.parse(cached); + update(container, data.content); + } + } + + // Get the sprite + fetch(url).then(function (result) { + if (is.empty(result)) { + return; + } + + if (useStorage) { + window.localStorage.setItem(prefix + '-' + id, JSON.stringify({ + content: result + })); + } + + update(container, result); + }).catch(function () {}); + } +} + +// ========================================================================== + +// Time helpers +var getHours = function getHours(value) { + return parseInt(value / 60 / 60 % 60, 10); +}; +var getMinutes = function getMinutes(value) { + return parseInt(value / 60 % 60, 10); +}; +var getSeconds = function getSeconds(value) { + return parseInt(value % 60, 10); +}; + +// Format time to UI friendly string +function formatTime() { + var time = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0; + var displayHours = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; + var inverted = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false; + + // Bail if the value isn't a number + if (!is.number(time)) { + return formatTime(null, displayHours, inverted); + } + + // Format time component to add leading zero + var format = function format(value) { + return ('0' + value).slice(-2); + }; + + // Breakdown to hours, mins, secs + var hours = getHours(time); + var mins = getMinutes(time); + var secs = getSeconds(time); + + // Do we need to display hours? + if (displayHours || hours > 0) { + hours = hours + ':'; + } else { + hours = ''; + } + + // Render + return '' + (inverted ? '-' : '') + hours + format(mins) + ':' + format(secs); +} + +// ========================================================================== var controls = { - // Get icon URL getIconUrl: function getIconUrl() { var url = new URL(this.config.iconUrl, window.location); @@ -1841,41 +1314,41 @@ var controls = { // TODO: Allow settings menus with custom controls findElements: function findElements() { try { - this.elements.controls = utils.getElement.call(this, this.config.selectors.controls.wrapper); + this.elements.controls = getElement.call(this, this.config.selectors.controls.wrapper); // Buttons this.elements.buttons = { - play: utils.getElements.call(this, this.config.selectors.buttons.play), - pause: utils.getElement.call(this, this.config.selectors.buttons.pause), - restart: utils.getElement.call(this, this.config.selectors.buttons.restart), - rewind: utils.getElement.call(this, this.config.selectors.buttons.rewind), - fastForward: utils.getElement.call(this, this.config.selectors.buttons.fastForward), - mute: utils.getElement.call(this, this.config.selectors.buttons.mute), - pip: utils.getElement.call(this, this.config.selectors.buttons.pip), - airplay: utils.getElement.call(this, this.config.selectors.buttons.airplay), - settings: utils.getElement.call(this, this.config.selectors.buttons.settings), - captions: utils.getElement.call(this, this.config.selectors.buttons.captions), - fullscreen: utils.getElement.call(this, this.config.selectors.buttons.fullscreen) + play: getElements.call(this, this.config.selectors.buttons.play), + pause: getElement.call(this, this.config.selectors.buttons.pause), + restart: getElement.call(this, this.config.selectors.buttons.restart), + rewind: getElement.call(this, this.config.selectors.buttons.rewind), + fastForward: getElement.call(this, this.config.selectors.buttons.fastForward), + mute: getElement.call(this, this.config.selectors.buttons.mute), + pip: getElement.call(this, this.config.selectors.buttons.pip), + airplay: getElement.call(this, this.config.selectors.buttons.airplay), + settings: getElement.call(this, this.config.selectors.buttons.settings), + captions: getElement.call(this, this.config.selectors.buttons.captions), + fullscreen: getElement.call(this, this.config.selectors.buttons.fullscreen) }; // Progress - this.elements.progress = utils.getElement.call(this, this.config.selectors.progress); + this.elements.progress = getElement.call(this, this.config.selectors.progress); // Inputs this.elements.inputs = { - seek: utils.getElement.call(this, this.config.selectors.inputs.seek), - volume: utils.getElement.call(this, this.config.selectors.inputs.volume) + seek: getElement.call(this, this.config.selectors.inputs.seek), + volume: getElement.call(this, this.config.selectors.inputs.volume) }; // Display this.elements.display = { - buffer: utils.getElement.call(this, this.config.selectors.display.buffer), - currentTime: utils.getElement.call(this, this.config.selectors.display.currentTime), - duration: utils.getElement.call(this, this.config.selectors.display.duration) + buffer: getElement.call(this, this.config.selectors.display.buffer), + currentTime: getElement.call(this, this.config.selectors.display.currentTime), + duration: getElement.call(this, this.config.selectors.display.duration) }; // Seek tooltip - if (utils.is.element(this.elements.progress)) { + if (is.element(this.elements.progress)) { this.elements.display.seekTooltip = this.elements.progress.querySelector('.' + this.config.classNames.tooltip); } @@ -1900,7 +1373,7 @@ var controls = { // Create var icon = document.createElementNS(namespace, 'svg'); - utils.setAttributes(icon, utils.extend(attributes, { + setAttributes(icon, extend(attributes, { role: 'presentation', focusable: 'false' })); @@ -1949,21 +1422,21 @@ var controls = { attributes.class = this.config.classNames.hidden; } - return utils.createElement('span', attributes, text); + return createElement('span', attributes, text); }, // Create a badge createBadge: function createBadge(text) { - if (utils.is.empty(text)) { + if (is.empty(text)) { return null; } - var badge = utils.createElement('span', { + var badge = createElement('span', { class: this.config.classNames.menu.value }); - badge.appendChild(utils.createElement('span', { + badge.appendChild(createElement('span', { class: this.config.classNames.menu.badge }, text)); @@ -1973,9 +1446,9 @@ var controls = { // Create a
to hide the standard controls and UI setAspectRatio: function setAspectRatio(input) { - var ratio = utils.is.string(input) ? input.split(':') : this.config.ratio.split(':'); + var ratio = is.string(input) ? input.split(':') : this.config.ratio.split(':'); var padding = 100 / ratio[0] * ratio[1]; this.elements.wrapper.style.paddingBottom = padding + '%'; @@ -5229,34 +5169,34 @@ var vimeo = { gesture: 'media', playsinline: !this.config.fullscreen.iosNative }; - var params = utils.buildUrlParams(options); + var params = buildUrlParams(options); // Get the source URL or ID var source = player.media.getAttribute('src'); // Get from
if needed - if (utils.is.empty(source)) { + if (is.empty(source)) { source = player.media.getAttribute(player.config.attributes.embed.id); } - var id = utils.parseVimeoId(source); + var id = parseId(source); // Build an iframe - var iframe = utils.createElement('iframe'); - var src = utils.format(player.config.urls.vimeo.iframe, id, params); + var iframe = createElement('iframe'); + var src = format(player.config.urls.vimeo.iframe, id, params); iframe.setAttribute('src', src); iframe.setAttribute('allowfullscreen', ''); iframe.setAttribute('allowtransparency', ''); iframe.setAttribute('allow', 'autoplay'); // Inject the package - var wrapper = utils.createElement('div', { class: player.config.classNames.embedContainer }); + var wrapper = createElement('div', { class: player.config.classNames.embedContainer }); wrapper.appendChild(iframe); - player.media = utils.replaceElement(wrapper, player.media); + player.media = replaceElement(wrapper, player.media); // Get poster image - utils.fetch(utils.format(player.config.urls.vimeo.api, id), 'json').then(function (response) { - if (utils.is.empty(response)) { + fetch$1(format(player.config.urls.vimeo.api, id), 'json').then(function (response) { + if (is.empty(response)) { return; } @@ -5321,7 +5261,7 @@ var vimeo = { // Set seeking state and trigger event media.seeking = true; - utils.dispatchEvent.call(player, media, 'seeking'); + trigger.call(player, media, 'seeking'); // If paused, mute until seek is complete Promise.resolve(restorePause && embed.setVolume(0)) @@ -5351,7 +5291,7 @@ var vimeo = { set: function set(input) { player.embed.setPlaybackRate(input).then(function () { speed = input; - utils.dispatchEvent.call(player, player.media, 'ratechange'); + trigger.call(player, player.media, 'ratechange'); }).catch(function (error) { // Hide menu item (and menu if empty) if (error.name === 'Error') { @@ -5371,7 +5311,7 @@ var vimeo = { set: function set(input) { player.embed.setVolume(input).then(function () { volume = input; - utils.dispatchEvent.call(player, player.media, 'volumechange'); + trigger.call(player, player.media, 'volumechange'); }); } }); @@ -5384,11 +5324,11 @@ var vimeo = { return muted; }, set: function set(input) { - var toggle = utils.is.boolean(input) ? input : false; + var toggle = is.boolean(input) ? input : false; player.embed.setVolume(toggle ? 0 : player.config.volume).then(function () { muted = toggle; - utils.dispatchEvent.call(player, player.media, 'volumechange'); + trigger.call(player, player.media, 'volumechange'); }); } }); @@ -5401,7 +5341,7 @@ var vimeo = { return loop; }, set: function set(input) { - var toggle = utils.is.boolean(input) ? input : player.config.loop.active; + var toggle = is.boolean(input) ? input : player.config.loop.active; player.embed.setLoop(toggle).then(function () { loop = toggle; @@ -5432,7 +5372,7 @@ var vimeo = { // Set aspect ratio based on video size Promise.all([player.embed.getVideoWidth(), player.embed.getVideoHeight()]).then(function (dimensions) { - var ratio = utils.getAspectRatio(dimensions[0], dimensions[1]); + var ratio = getAspectRatio(dimensions[0], dimensions[1]); vimeo.setAspectRatio.call(_this2, ratio); }); @@ -5450,13 +5390,13 @@ var vimeo = { // Get current time player.embed.getCurrentTime().then(function (value) { currentTime = value; - utils.dispatchEvent.call(player, player.media, 'timeupdate'); + trigger.call(player, player.media, 'timeupdate'); }); // Get duration player.embed.getDuration().then(function (value) { player.media.duration = value; - utils.dispatchEvent.call(player, player.media, 'durationchange'); + trigger.call(player, player.media, 'durationchange'); }); // Get captions @@ -5470,7 +5410,7 @@ var vimeo = { cues = _ref$cues === undefined ? [] : _ref$cues; var strippedCues = cues.map(function (cue) { - return utils.stripHTML(cue.text); + return stripHTML(cue.text); }); captions.updateCues.call(player, strippedCues); }); @@ -5480,11 +5420,11 @@ var vimeo = { player.embed.getPaused().then(function (paused) { assurePlaybackState.call(player, !paused); if (!paused) { - utils.dispatchEvent.call(player, player.media, 'playing'); + trigger.call(player, player.media, 'playing'); } }); - if (utils.is.element(player.embed.element) && player.supported.ui) { + if (is.element(player.embed.element) && player.supported.ui) { var frame = player.embed.element; // Fix keyboard focus issues @@ -5495,7 +5435,7 @@ var vimeo = { player.embed.on('play', function () { assurePlaybackState.call(player, true); - utils.dispatchEvent.call(player, player.media, 'playing'); + trigger.call(player, player.media, 'playing'); }); player.embed.on('pause', function () { @@ -5505,16 +5445,16 @@ var vimeo = { player.embed.on('timeupdate', function (data) { player.media.seeking = false; currentTime = data.seconds; - utils.dispatchEvent.call(player, player.media, 'timeupdate'); + trigger.call(player, player.media, 'timeupdate'); }); player.embed.on('progress', function (data) { player.media.buffered = data.percent; - utils.dispatchEvent.call(player, player.media, 'progress'); + trigger.call(player, player.media, 'progress'); // Check all loaded if (parseInt(data.percent, 10) === 1) { - utils.dispatchEvent.call(player, player.media, 'canplaythrough'); + trigger.call(player, player.media, 'canplaythrough'); } // Get duration as if we do it before load, it gives an incorrect value @@ -5522,24 +5462,24 @@ var vimeo = { player.embed.getDuration().then(function (value) { if (value !== player.media.duration) { player.media.duration = value; - utils.dispatchEvent.call(player, player.media, 'durationchange'); + trigger.call(player, player.media, 'durationchange'); } }); }); player.embed.on('seeked', function () { player.media.seeking = false; - utils.dispatchEvent.call(player, player.media, 'seeked'); + trigger.call(player, player.media, 'seeked'); }); player.embed.on('ended', function () { player.media.paused = true; - utils.dispatchEvent.call(player, player.media, 'ended'); + trigger.call(player, player.media, 'ended'); }); player.embed.on('error', function (detail) { player.media.error = detail; - utils.dispatchEvent.call(player, player.media, 'error'); + trigger.call(player, player.media, 'error'); }); // Rebuild UI @@ -5551,6 +5491,16 @@ var vimeo = { // ========================================================================== +// Parse YouTube ID from URL +function parseId$1(url) { + if (is.empty(url)) { + return null; + } + + var regex = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|&v=)([^#&?]*).*/; + return url.match(regex) ? RegExp.$2 : url; +} + // Standardise YouTube quality unit function mapQualityUnit(input) { switch (input) { @@ -5602,11 +5552,11 @@ function mapQualityUnit(input) { } function mapQualityUnits(levels) { - if (utils.is.empty(levels)) { + if (is.empty(levels)) { return levels; } - return utils.dedupe(levels.map(function (level) { + return dedupe(levels.map(function (level) { return mapQualityUnit(level); })); } @@ -5618,7 +5568,7 @@ function assurePlaybackState$1(play) { } if (this.media.paused === play) { this.media.paused = !play; - utils.dispatchEvent.call(this, this.media, play ? 'play' : 'pause'); + trigger.call(this, this.media, play ? 'play' : 'pause'); } } @@ -5627,17 +5577,17 @@ var youtube = { var _this = this; // Add embed class for responsive - utils.toggleClass(this.elements.wrapper, this.config.classNames.embed, true); + toggleClass(this.elements.wrapper, this.config.classNames.embed, true); // Set aspect ratio youtube.setAspectRatio.call(this); // Setup API - if (utils.is.object(window.YT) && utils.is.function(window.YT.Player)) { + if (is.object(window.YT) && is.function(window.YT.Player)) { youtube.ready.call(this); } else { // Load the API - utils.loadScript(this.config.urls.youtube.sdk).catch(function (error) { + loadScript(this.config.urls.youtube.sdk).catch(function (error) { _this.debug.warn('YouTube API failed to load', error); }); @@ -5667,11 +5617,11 @@ var youtube = { // Try via undocumented API method first // This method disappears now and then though... // https://github.com/sampotts/plyr/issues/709 - if (utils.is.function(this.embed.getVideoData)) { + if (is.function(this.embed.getVideoData)) { var _embed$getVideoData = this.embed.getVideoData(), title = _embed$getVideoData.title; - if (utils.is.empty(title)) { + if (is.empty(title)) { this.config.title = title; ui.setTitle.call(this); return; @@ -5680,11 +5630,11 @@ var youtube = { // Or via Google API var key = this.config.keys.google; - if (utils.is.string(key) && !utils.is.empty(key)) { - var url = utils.format(this.config.urls.youtube.api, videoId, key); + if (is.string(key) && !is.empty(key)) { + var url = format(this.config.urls.youtube.api, videoId, key); - utils.fetch(url).then(function (result) { - if (utils.is.object(result)) { + fetch$1(url).then(function (result) { + if (is.object(result)) { _this2.config.title = result.items[0].snippet.title; ui.setTitle.call(_this2); } @@ -5706,7 +5656,7 @@ var youtube = { // Ignore already setup (race condition) var currentId = player.media.getAttribute('id'); - if (!utils.is.empty(currentId) && currentId.startsWith('youtube-')) { + if (!is.empty(currentId) && currentId.startsWith('youtube-')) { return; } @@ -5714,28 +5664,28 @@ var youtube = { var source = player.media.getAttribute('src'); // Get from
if needed - if (utils.is.empty(source)) { + if (is.empty(source)) { source = player.media.getAttribute(this.config.attributes.embed.id); } // Replace the