diff options
Diffstat (limited to 'dist/plyr.js')
-rw-r--r-- | dist/plyr.js | 3630 |
1 files changed, 1789 insertions, 1841 deletions
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 : {}; - -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); +// ========================================================================== +// Type checking utils +// ========================================================================== - numWaiting--; - if (!numWaiting) callbackFn(depsNotFound); - }; +var getConstructor = function getConstructor(input) { + return input !== null && typeof input !== 'undefined' ? input.constructor : null; +}; - // register callback - while (i--) { - bundleId = bundleIds[i]; +var instanceOf = function instanceOf(input, constructor) { + return Boolean(input && constructor && input instanceof constructor); +}; - // execute callback if in result cache - r = bundleResultCache[bundleId]; - if (r) { - fn(bundleId, r); - continue; +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; } +}; - // 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; +// 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 + } - // 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; - } + return supported; +}(); - e.onload = e.onerror = e.onbeforeload = function (ev) { - var result = ev.type[0]; +// 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; - // 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'; - } + // Bail if no elemetns, event, or callback + if (is.empty(elements) || is.empty(event) || !is.function(callback)) { + return; } - // handle retries in case of load failure - if (result == 'e') { - // increment counter - numTries += 1; + // 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); + } + }); - // exit function and try again - if (numTries < maxTries) { - return loadFile(path, callbackFn, args, numTries); - } + return; } - // 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; + // Allow multiple events + var events = event.split(' '); - // define callback function - fn = function(path, result, defaultPrevented) { - // handle error - if (result == 'e') pathsNotFound.push(path); + // Build options + // Default to just the capture boolean for browsers with no passive listener support + var options = capture; - // 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; + // 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 + }; } - numWaiting--; - if (!numWaiting) callbackFn(pathsNotFound); - }; - - // load scripts - for (i=0; i < x; i++) loadFile(paths[i], fn, args); + // 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; -/** - * 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); + 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; -/** - * 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; -}; - - -/** - * Manually satisfy bundle dependencies. - * @param {string} bundleId - The bundle id - */ -loadjs.done = function done(bundleId) { - publish(bundleId, []); -}; - - -/** - * Reset loadjs dependencies statuses - */ -loadjs.reset = function reset() { - bundleIdCache = {}; - bundleResultCache = {}; - bundleCallbackQueue = {}; -}; - + toggleListener(element, events, callback, false, passive, capture); +} -/** - * Determine if bundle has already been defined - * @param String} bundleId - The bundle id - */ -loadjs.isDefined = function isDefined(bundleId) { - return bundleId in bundleIdCache; -}; +// 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; + } -// export -return loadjs; + // 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); - } -}; - // ========================================================================== -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 (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; -}(); - -// ========================================================================== -// Plyr supported types and providers -// ========================================================================== - -var providers = { - html5: 'html5', - youtube: 'youtube', - vimeo: 'vimeo' -}; - -var types = { - audio: 'audio', - video: 'video' -}; - -// ========================================================================== - -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 <element> - 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 <element> - 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', ''); +// 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 { - 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); + parent.appendChild(child); } + }); +} - return null; - }, - - - // Has class name - hasClass: function hasClass(element, className) { - return utils.is.element(element) && element.classList.contains(className); - }, - +// Set attributes +function setAttributes(element, attributes) { + if (!is.element(element) || is.empty(attributes)) { + return; + } - // Element matches selector - matches: function matches(element, selector) { - var prototype = { Element: Element }; + Object.entries(attributes).forEach(function (_ref) { + var _ref2 = slicedToArray(_ref, 2), + key = _ref2[0], + value = _ref2[1]; - function match() { - return Array.from(document.querySelectorAll(selector)).includes(this); - } + element.setAttribute(key, value); + }); +} - var matches = prototype.matches || prototype.webkitMatchesSelector || prototype.mozMatchesSelector || prototype.msMatchesSelector || match; +// Create a DocumentFragment +function createElement(type, attributes, text) { + // Create a new <element> + var element = document.createElement(type); - return matches.call(element, selector); - }, + // Set all passed attributes + if (is.object(attributes)) { + setAttributes(element, attributes); + } + // Add text node + if (is.string(text)) { + element.innerText = text; + } - // Find all elements - getElements: function getElements(selector) { - return this.elements.container.querySelectorAll(selector); - }, + // Return built element + return element; +} +// Inaert an element after another +function insertAfter(element, target) { + target.parentNode.insertBefore(element, target.nextSibling); +} - // Find a single element - getElement: function getElement(selector) { - return this.elements.container.querySelector(selector); - }, +// Insert a DocumentFragment +function insertElement(type, parent, attributes, text) { + // Inject the new <element> + 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; + } - // Get the focused element - getFocusElement: function getFocusElement() { - var focused = document.activeElement; + if (!is.element(element) || !is.element(element.parentNode)) { + return; + } - if (!focused || focused === document.body) { - focused = null; - } else { - focused = document.querySelector(':focus'); - } + element.parentNode.removeChild(element); +} - return focused; - }, +// Remove all child elements +function emptyElement(element) { + var length = element.childNodes.length; - // 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; + while (length > 0) { + element.removeChild(element.lastChild); + length -= 1; + } +} - if (!utils.is.element(element)) { - return; - } +// Replace element +function replaceElement(newChild, oldChild) { + if (!is.element(oldChild) || !is.element(oldChild.parentNode) || !is.element(newChild)) { + return null; + } - var focusable = utils.getElements.call(this, 'button:not(:disabled), input:not(:disabled), [tabindex]'); - var first = focusable[0]; - var last = focusable[focusable.length - 1]; + oldChild.parentNode.replaceChild(newChild, oldChild); - var trap = function trap(event) { - // Bail if not tab key or not fullscreen - if (event.key !== 'Tab' || event.keyCode !== 9) { - return; - } + return newChild; +} - // Get the current focused element - var focused = utils.getFocusElement(); +// 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 (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 (!is.string(sel) || is.empty(sel)) { + return {}; + } - if (toggle) { - utils.on(this.elements.container, 'keydown', trap, false); - } else { - utils.off(this.elements.container, 'keydown', trap, false); - } - }, + 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, ''); - // 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; + // Get the parts and value + var parts = stripped.split('='); + var key = parts[0]; + var value = parts.length > 1 ? parts[1].replace(/["']/g, '') : ''; - // Bail if no elemetns, event, or callback - if (utils.is.empty(elements) || utils.is.empty(event) || !utils.is.function(callback)) { - return; - } + // Get the first character + var start = selector.charAt(0); - // 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); + switch (start) { + case '.': + // Add to existing classname + if (is.object(existing) && is.string(existing.class)) { + existing.class += ' ' + className; } - }); - 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]; - } + attributes.class = className; + break; - if (utils.is.empty(input)) { - return input; - } + case '#': + // ID selector + attributes.id = selector.replace('#', ''); + break; - return input.toString().replace(/{(\d+)}/g, function (match, i) { - return utils.is.string(args[i]) ? args[i] : ''; - }); - }, + case '[': + // Attribute selector + attributes[key] = value; + break; - // Get percentage - getPercentage: function getPercentage(current, max) { - if (current === 0 || max === 0 || Number.isNaN(current) || Number.isNaN(max)) { - return 0; + default: + break; } + }); - 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); - }, - + return attributes; +} - // 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; +// Toggle hidden +function toggleHidden(element, hidden) { + if (!is.element(element)) { + return; + } - // Bail if the value isn't a number - if (!utils.is.number(time)) { - return utils.formatTime(null, displayHours, inverted); - } + var hide = hidden; - // Format time component to add leading zero - var format = function format(value) { - return ('0' + value).slice(-2); - }; + if (!is.boolean(hide)) { + hide = !element.hasAttribute('hidden'); + } - // Breakdown to hours, mins, secs - var hours = utils.getHours(time); - var mins = utils.getMinutes(time); - var secs = utils.getSeconds(time); + if (hide) { + element.setAttribute('hidden', ''); + } else { + element.removeAttribute('hidden'); + } +} - // Do we need to display hours? - if (displayHours || hours > 0) { - hours = hours + ':'; - } else { - hours = ''; +// 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'; } - // 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] : ''; + element.classList[method](className); + return element.classList.contains(className); + } - var string = input.toString(); + return null; +} - // Convert kebab case - string = utils.replaceAll(string, '-', ' '); +// Has class name +function hasClass(element, className) { + return is.element(element) && element.classList.contains(className); +} - // Convert snake case - string = utils.replaceAll(string, '_', ' '); +// Element matches selector +function matches(element, selector) { + var prototype = { Element: Element }; - // Convert to title case - string = utils.toTitleCase(string); + function match() { + return Array.from(document.querySelectorAll(selector)).includes(this); + } - // Convert to pascal case - return utils.replaceAll(string, ' ', ''); - }, + var matches = prototype.matches || prototype.webkitMatchesSelector || prototype.mozMatchesSelector || prototype.msMatchesSelector || match; + return matches.call(element, selector); +} - // Convert string to pascalCase - toCamelCase: function toCamelCase() { - var input = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ''; +// Find all elements +function getElements(selector) { + return this.elements.container.querySelectorAll(selector); +} - var string = input.toString(); +// Find a single element +function getElement(selector) { + return this.elements.container.querySelector(selector); +} - // Convert to pascal case - string = utils.toPascalCase(string); +// Get the focused element +function getFocusElement() { + var focused = document.activeElement; - // Convert first character to lowercase - return string.charAt(0).toLowerCase() + string.slice(1); - }, + if (!focused || focused === document.body) { + focused = null; + } else { + focused = document.querySelector(':focus'); + } + return focused; +} - // Deep extend destination object with N more objects - extend: function extend() { - var target = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; +// 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; - 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; - } + if (!is.element(element)) { + return; + } - var source = sources.shift(); + var focusable = getElements.call(this, 'button:not(:disabled), input:not(:disabled), [tabindex]'); + var first = focusable[0]; + var last = focusable[focusable.length - 1]; - if (!utils.is.object(source)) { - return target; + var trap = function trap(event) { + // Bail if not tab key or not fullscreen + if (event.key !== 'Tab' || event.keyCode !== 9) { + return; } - 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))); - }, + // Get the current focused element + var focused = getFocusElement(); - - // Remove duplicates in an array - dedupe: function dedupe(array) { - if (!utils.is.array(array)) { - return array; + 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(); } + }; - 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; - } + if (toggle) { + on(this.elements.container, 'keydown', trap, false); + } else { + off(this.elements.container, 'keydown', trap, false); + } +} - return array.reduce(function (prev, curr) { - return Math.abs(curr - value) < Math.abs(prev - value) ? curr : prev; +// 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); }); - }, - - - // 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('&'); - }, - + return; + } - // 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; - }, + // 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; - // Like outerHTML, but also works for DocumentFragment - getHTML: function getHTML(element) { - var wrapper = document.createElement('div'); - wrapper.appendChild(element); - return wrapper.innerHTML; - }, + // Set the attribute on target + element.setAttribute('aria-pressed', state); +} +// ========================================================================== - // 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; - }, +var transitionEndEvent = function () { + var element = document.createElement('span'); + var events = { + WebkitTransition: 'webkitTransitionEnd', + MozTransition: 'transitionend', + OTransition: 'oTransitionEnd otransitionend', + transition: 'transitionend' + }; - // Get the transition end event - get transitionEndEvent() { - var element = document.createElement('span'); + var type = Object.keys(events).find(function (event) { + return element.style[event] !== undefined; + }); - var events = { - WebkitTransition: 'webkitTransitionEnd', - MozTransition: 'transitionend', - OTransition: 'oTransitionEnd otransitionend', - transition: 'transitionend' - }; + return is.string(type) ? events[type] : false; +}(); - var type = Object.keys(events).find(function (event) { - return element.style[event] !== undefined; - }); +// Force repaint of element +function repaint(element) { + setTimeout(function () { + toggleHidden(element, true); + element.offsetHeight; // eslint-disable-line + toggleHidden(element, false); + }, 0); +} - return utils.is.string(type) ? events[type] : false; - }, +// ========================================================================== +// Browser sniffing +// Unfortunately, due to mixed support, UA sniffing is required +// ========================================================================== - // Force repaint of element - repaint: function repaint(element) { - setTimeout(function () { - utils.toggleHidden(element, true); - element.offsetHeight; // eslint-disable-line - utils.toggleHidden(element, false); - }, 0); - } +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; - }(), - // <input type="range"> 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 <source> 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); -var controls = { + 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 <svg> 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 <button> createButton: function createButton(buttonType, attr) { - var button = utils.createElement('button'); + var button = createElement('button'); var attributes = Object.assign({}, attr); - var type = utils.toCamelCase(buttonType); + var type = toCamelCase(buttonType); var toggle = false; var label = void 0; @@ -2059,13 +1532,13 @@ var controls = { } // Merge attributes - utils.extend(attributes, utils.getAttributesFromSelector(this.config.selectors.buttons[type], attributes)); + extend(attributes, getAttributesFromSelector(this.config.selectors.buttons[type], attributes)); - utils.setAttributes(button, attributes); + setAttributes(button, attributes); // We have multiple play buttons if (type === 'play') { - if (!utils.is.array(this.elements.buttons[type])) { + if (!is.array(this.elements.buttons[type])) { this.elements.buttons[type] = []; } @@ -2081,14 +1554,14 @@ var controls = { // Create an <input type='range'> createRange: function createRange(type, attributes) { // Seek label - var label = utils.createElement('label', { + var label = createElement('label', { for: attributes.id, id: attributes.id + '-label', class: this.config.classNames.hidden }, i18n.get(type, this.config)); // Seek input - var input = utils.createElement('input', utils.extend(utils.getAttributesFromSelector(this.config.selectors.inputs[type]), { + var input = createElement('input', extend(getAttributesFromSelector(this.config.selectors.inputs[type]), { type: 'range', min: 0, max: 100, @@ -2117,7 +1590,7 @@ var controls = { // Create a <progress> createProgress: function createProgress(type, attributes) { - var progress = utils.createElement('progress', utils.extend(utils.getAttributesFromSelector(this.config.selectors.display[type]), { + var progress = createElement('progress', extend(getAttributesFromSelector(this.config.selectors.display[type]), { min: 0, max: 100, value: 0, @@ -2127,7 +1600,7 @@ var controls = { // Create the label inside if (type !== 'volume') { - progress.appendChild(utils.createElement('span', null, '0')); + progress.appendChild(createElement('span', null, '0')); var suffix = ''; switch (type) { @@ -2154,9 +1627,9 @@ var controls = { // Create time display createTime: function createTime(type) { - var attributes = utils.getAttributesFromSelector(this.config.selectors.display[type]); + var attributes = getAttributesFromSelector(this.config.selectors.display[type]); - var container = utils.createElement('div', utils.extend(attributes, { + var container = createElement('div', extend(attributes, { class: 'plyr__time ' + attributes.class, 'aria-label': i18n.get(type, this.config) }), '00:00'); @@ -2179,13 +1652,13 @@ var controls = { _ref$checked = _ref.checked, checked = _ref$checked === undefined ? false : _ref$checked; - var item = utils.createElement('li'); + var item = createElement('li'); - var label = utils.createElement('label', { + var label = createElement('label', { class: this.config.classNames.control }); - var radio = utils.createElement('input', utils.extend(utils.getAttributesFromSelector(this.config.selectors.inputs[type]), { + var radio = createElement('input', extend(getAttributesFromSelector(this.config.selectors.inputs[type]), { type: 'radio', name: 'plyr-' + type, value: value, @@ -2193,13 +1666,13 @@ var controls = { class: 'plyr__sr-only' })); - var faux = utils.createElement('span', { hidden: '' }); + var faux = createElement('span', { hidden: '' }); label.appendChild(radio); label.appendChild(faux); label.insertAdjacentHTML('beforeend', title); - if (utils.is.element(badge)) { + if (is.element(badge)) { label.appendChild(badge); } @@ -2215,15 +1688,15 @@ var controls = { var inverted = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false; // Bail if there's no element to display or the value isn't a number - if (!utils.is.element(target) || !utils.is.number(time)) { + if (!is.element(target) || !is.number(time)) { return; } // Always display hours if duration is over an hour - var forceHours = utils.getHours(this.duration) > 0; + var forceHours = getHours(this.duration) > 0; // eslint-disable-next-line no-param-reassign - target.innerText = utils.formatTime(time, forceHours, inverted); + target.innerText = formatTime(time, forceHours, inverted); }, @@ -2234,13 +1707,13 @@ var controls = { } // Update range - if (utils.is.element(this.elements.inputs.volume)) { + if (is.element(this.elements.inputs.volume)) { controls.setRange.call(this, this.elements.inputs.volume, this.muted ? 0 : this.volume); } // Update mute state - if (utils.is.element(this.elements.buttons.mute)) { - utils.toggleState(this.elements.buttons.mute, this.muted || this.volume === 0); + if (is.element(this.elements.buttons.mute)) { + toggleState(this.elements.buttons.mute, this.muted || this.volume === 0); } }, @@ -2249,7 +1722,7 @@ var controls = { setRange: function setRange(target) { var value = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; - if (!utils.is.element(target)) { + if (!is.element(target)) { return; } @@ -2265,23 +1738,23 @@ var controls = { updateProgress: function updateProgress(event) { var _this = this; - if (!this.supported.ui || !utils.is.event(event)) { + if (!this.supported.ui || !is.event(event)) { return; } var value = 0; var setProgress = function setProgress(target, input) { - var value = utils.is.number(input) ? input : 0; - var progress = utils.is.element(target) ? target : _this.elements.display.buffer; + var value = is.number(input) ? input : 0; + var progress = is.element(target) ? target : _this.elements.display.buffer; // Update value and label - if (utils.is.element(progress)) { + if (is.element(progress)) { progress.value = value; // Update text label inside var label = progress.getElementsByTagName('span')[0]; - if (utils.is.element(label)) { + if (is.element(label)) { label.childNodes[0].nodeValue = value; } } @@ -2293,7 +1766,7 @@ var controls = { case 'timeupdate': case 'seeking': case 'seeked': - value = utils.getPercentage(this.currentTime, this.duration); + value = getPercentage(this.currentTime, this.duration); // Set seek range value only if it's a 'natural' time event if (event.type === 'timeupdate') { @@ -2319,10 +1792,10 @@ var controls = { // Webkit polyfill for lower fill range updateRangeFill: function updateRangeFill(target) { // Get range from event if event passed - var range = utils.is.event(target) ? target.target : target; + var range = is.event(target) ? target.target : target; // Needs to be a valid <input type='range'> - if (!utils.is.element(range) || range.getAttribute('type') !== 'range') { + if (!is.element(range) || range.getAttribute('type') !== 'range') { return; } @@ -2344,7 +1817,7 @@ var controls = { var _this2 = this; // Bail if setting not true - if (!this.config.tooltips.seek || !utils.is.element(this.elements.inputs.seek) || !utils.is.element(this.elements.display.seekTooltip) || this.duration === 0) { + if (!this.config.tooltips.seek || !is.element(this.elements.inputs.seek) || !is.element(this.elements.display.seekTooltip) || this.duration === 0) { return; } @@ -2354,7 +1827,7 @@ var controls = { var visible = this.config.classNames.tooltip + '--visible'; var toggle = function toggle(_toggle) { - utils.toggleClass(_this2.elements.display.seekTooltip, visible, _toggle); + toggleClass(_this2.elements.display.seekTooltip, visible, _toggle); }; // Hide on touch @@ -2364,9 +1837,9 @@ var controls = { } // Determine percentage, if already visible - if (utils.is.event(event)) { + if (is.event(event)) { percent = 100 / clientRect.width * (event.pageX - clientRect.left); - } else if (utils.hasClass(this.elements.display.seekTooltip, visible)) { + } else if (hasClass(this.elements.display.seekTooltip, visible)) { percent = parseFloat(this.elements.display.seekTooltip.style.left, 10); } else { return; @@ -2387,7 +1860,7 @@ var controls = { // Show/hide the tooltip // If the event is a moues in/out and percentage is inside bounds - if (utils.is.event(event) && ['mouseenter', 'mouseleave'].includes(event.type)) { + if (is.event(event) && ['mouseenter', 'mouseleave'].includes(event.type)) { toggle(event.type === 'mouseenter'); } }, @@ -2396,7 +1869,7 @@ var controls = { // Handle time change event timeUpdate: function timeUpdate(event) { // Only invert if only one time element is displayed and used for both duration and currentTime - var invert = !utils.is.element(this.elements.display.duration) && this.config.invertTime; + var invert = !is.element(this.elements.display.duration) && this.config.invertTime; // Duration controls.updateTimeDisplay.call(this, this.elements.display.currentTime, invert ? this.duration - this.currentTime : this.currentTime, invert); @@ -2419,7 +1892,7 @@ var controls = { } // If there's a spot to display duration - var hasDuration = utils.is.element(this.elements.display.duration); + var hasDuration = is.element(this.elements.display.duration); // If there's only one time display, display duration there if (!hasDuration && this.config.displayDuration && this.paused) { @@ -2438,7 +1911,7 @@ var controls = { // Hide/show a tab toggleTab: function toggleTab(setting, toggle) { - utils.toggleHidden(this.elements.settings.tabs[setting], !toggle); + toggleHidden(this.elements.settings.tabs[setting], !toggle); }, @@ -2448,7 +1921,7 @@ var controls = { var _this3 = this; // Menu required - if (!utils.is.element(this.elements.settings.panes.quality)) { + if (!is.element(this.elements.settings.panes.quality)) { return; } @@ -2456,14 +1929,14 @@ var controls = { var list = this.elements.settings.panes.quality.querySelector('ul'); // Set options if passed and filter based on config - if (utils.is.array(options)) { + if (is.array(options)) { this.options.quality = options.filter(function (quality) { return _this3.config.quality.options.includes(quality); }); } // Toggle the pane and tab - var toggle = !utils.is.empty(this.options.quality) && this.options.quality.length > 1; + var toggle = !is.empty(this.options.quality) && this.options.quality.length > 1; controls.toggleTab.call(this, type, toggle); // Check if we need to toggle the parent @@ -2475,7 +1948,7 @@ var controls = { } // Empty the menu - utils.emptyElement(list); + emptyElement(list); // Get the badge HTML for HD, 4K etc var getBadge = function getBadge(quality) { @@ -2513,7 +1986,7 @@ var controls = { return value === 1 ? i18n.get('normal', this.config) : value + '×'; case 'quality': - if (utils.is.number(value)) { + if (is.number(value)) { var label = i18n.get('qualityLabel.' + value, this.config); if (!label.length) { @@ -2523,7 +1996,7 @@ var controls = { return label; } - return utils.toTitleCase(value); + return toTitleCase(value); case 'captions': return captions.getLabel.call(this); @@ -2546,15 +2019,15 @@ var controls = { break; default: - value = !utils.is.empty(input) ? input : this[setting]; + value = !is.empty(input) ? input : this[setting]; // Get default - if (utils.is.empty(value)) { + if (is.empty(value)) { value = this.config[setting].default; } // Unsupported value - if (!utils.is.empty(this.options[setting]) && !this.options[setting].includes(value)) { + if (!is.empty(this.options[setting]) && !this.options[setting].includes(value)) { this.debug.warn('Unsupported value of \'' + value + '\' for ' + setting); return; } @@ -2569,12 +2042,12 @@ var controls = { } // Get the list if we need to - if (!utils.is.element(list)) { + if (!is.element(list)) { list = pane && pane.querySelector('ul'); } // If there's no list it means it's not been rendered... - if (!utils.is.element(list)) { + if (!is.element(list)) { return; } @@ -2585,7 +2058,7 @@ var controls = { // Find the radio option and check it var target = list && list.querySelector('input[value="' + value + '"]'); - if (utils.is.element(target)) { + if (is.element(target)) { target.checked = true; } }, @@ -2594,24 +2067,24 @@ var controls = { // Set the looping options /* setLoopMenu() { // Menu required - if (!utils.is.element(this.elements.settings.panes.loop)) { + if (!is.element(this.elements.settings.panes.loop)) { return; } const options = ['start', 'end', 'all', 'reset']; const list = this.elements.settings.panes.loop.querySelector('ul'); // Show the pane and tab - utils.toggleHidden(this.elements.settings.tabs.loop, false); - utils.toggleHidden(this.elements.settings.panes.loop, false); + toggleHidden(this.elements.settings.tabs.loop, false); + toggleHidden(this.elements.settings.panes.loop, false); // Toggle the pane and tab - const toggle = !utils.is.empty(this.loop.options); + const toggle = !is.empty(this.loop.options); controls.toggleTab.call(this, 'loop', toggle); // Empty the menu - utils.emptyElement(list); + emptyElement(list); options.forEach(option => { - const item = utils.createElement('li'); - const button = utils.createElement( + const item = createElement('li'); + const button = createElement( 'button', - utils.extend(utils.getAttributesFromSelector(this.config.selectors.buttons.loop), { + extend(getAttributesFromSelector(this.config.selectors.buttons.loop), { type: 'button', class: this.config.classNames.control, 'data-plyr-loop-action': option, @@ -2643,7 +2116,7 @@ var controls = { controls.toggleTab.call(this, type, tracks.length); // Empty the menu - utils.emptyElement(list); + emptyElement(list); // Check if we need to toggle the parent controls.checkMenu.call(this); @@ -2691,14 +2164,14 @@ var controls = { } // Menu required - if (!utils.is.element(this.elements.settings.panes.speed)) { + if (!is.element(this.elements.settings.panes.speed)) { return; } var type = 'speed'; // Set the speed options - if (utils.is.array(options)) { + if (is.array(options)) { this.options.speed = options; } else if (this.isHTML5 || this.isVimeo) { this.options.speed = [0.5, 0.75, 1, 1.25, 1.5, 1.75, 2]; @@ -2710,7 +2183,7 @@ var controls = { }); // Toggle the pane and tab - var toggle = !utils.is.empty(this.options.speed) && this.options.speed.length > 1; + var toggle = !is.empty(this.options.speed) && this.options.speed.length > 1; controls.toggleTab.call(this, type, toggle); // Check if we need to toggle the parent @@ -2725,7 +2198,7 @@ var controls = { var list = this.elements.settings.panes.speed.querySelector('ul'); // Empty the menu - utils.emptyElement(list); + emptyElement(list); // Create items this.options.speed.forEach(function (speed) { @@ -2745,11 +2218,11 @@ var controls = { checkMenu: function checkMenu() { var tabs = this.elements.settings.tabs; - var visible = !utils.is.empty(tabs) && Object.values(tabs).some(function (tab) { + var visible = !is.empty(tabs) && Object.values(tabs).some(function (tab) { return !tab.hidden; }); - utils.toggleHidden(this.elements.settings.menu, !visible); + toggleHidden(this.elements.settings.menu, !visible); }, @@ -2760,14 +2233,14 @@ var controls = { var button = this.elements.buttons.settings; // Menu and button are required - if (!utils.is.element(form) || !utils.is.element(button)) { + if (!is.element(form) || !is.element(button)) { return; } - var show = utils.is.boolean(event) ? event : utils.is.element(form) && form.hasAttribute('hidden'); + var show = is.boolean(event) ? event : is.element(form) && form.hasAttribute('hidden'); - if (utils.is.event(event)) { - var isMenuItem = utils.is.element(form) && form.contains(event.target); + if (is.event(event)) { + var isMenuItem = is.element(form) && form.contains(event.target); var isButton = event.target === this.elements.buttons.settings; // If the click was inside the form or if the click @@ -2784,13 +2257,13 @@ var controls = { } // Set form and button attributes - if (utils.is.element(button)) { + if (is.element(button)) { button.setAttribute('aria-expanded', show); } - if (utils.is.element(form)) { - utils.toggleHidden(form, !show); - utils.toggleClass(this.elements.container, this.config.classNames.menu.open, show); + if (is.element(form)) { + toggleHidden(form, !show); + toggleClass(this.elements.container, this.config.classNames.menu.open, show); if (show) { form.removeAttribute('tabindex'); @@ -2822,7 +2295,7 @@ var controls = { var height = clone.scrollHeight; // Remove from the DOM - utils.removeElement(clone); + removeElement(clone); return { width: width, @@ -2839,7 +2312,7 @@ var controls = { var pane = document.getElementById(target); // Nothing to show, bail - if (!utils.is.element(pane)) { + if (!is.element(pane)) { return; } @@ -2880,11 +2353,11 @@ var controls = { container.style.height = ''; // Only listen once - utils.off(container, utils.transitionEndEvent, restore); + off(container, transitionEndEvent, restore); }; // Listen for the transition finishing and restore auto height/width - utils.on(container, utils.transitionEndEvent, restore); + on(container, transitionEndEvent, restore); // Set dimensions to target container.style.width = size.width + 'px'; @@ -2892,13 +2365,13 @@ var controls = { } // Set attributes on current tab - utils.toggleHidden(current, true); + toggleHidden(current, true); current.setAttribute('tabindex', -1); // Set attributes on target - utils.toggleHidden(pane, false); + toggleHidden(pane, false); - var tabs = utils.getElements.call(this, '[aria-controls="' + target + '"]'); + var tabs = getElements.call(this, '[aria-controls="' + target + '"]'); Array.from(tabs).forEach(function (tab) { tab.setAttribute('aria-expanded', true); }); @@ -2915,12 +2388,12 @@ var controls = { var _this6 = this; // Do nothing if we want no controls - if (utils.is.empty(this.config.controls)) { + if (is.empty(this.config.controls)) { return null; } // Create the container - var container = utils.createElement('div', utils.getAttributesFromSelector(this.config.selectors.controls.wrapper)); + var container = createElement('div', getAttributesFromSelector(this.config.selectors.controls.wrapper)); // Restart button if (this.config.controls.includes('restart')) { @@ -2944,7 +2417,7 @@ var controls = { // Progress if (this.config.controls.includes('progress')) { - var progress = utils.createElement('div', utils.getAttributesFromSelector(this.config.selectors.progress)); + var progress = createElement('div', getAttributesFromSelector(this.config.selectors.progress)); // Seek range slider var seek = controls.createRange.call(this, 'seek', { @@ -2960,7 +2433,7 @@ var controls = { // Seek tooltip if (this.config.tooltips.seek) { - var tooltip = utils.createElement('span', { + var tooltip = createElement('span', { class: this.config.classNames.tooltip }, '00:00'); @@ -2989,7 +2462,7 @@ var controls = { // Volume range control if (this.config.controls.includes('volume')) { - var volume = utils.createElement('div', { + var volume = createElement('div', { class: 'plyr__volume' }); @@ -3001,7 +2474,7 @@ var controls = { }; // Create the volume range slider - var range = controls.createRange.call(this, 'volume', utils.extend(attributes, { + var range = controls.createRange.call(this, 'volume', extend(attributes, { id: 'plyr-volume-' + data.id })); volume.appendChild(range.label); @@ -3018,8 +2491,8 @@ var controls = { } // Settings button / menu - if (this.config.controls.includes('settings') && !utils.is.empty(this.config.settings)) { - var menu = utils.createElement('div', { + if (this.config.controls.includes('settings') && !is.empty(this.config.settings)) { + var menu = createElement('div', { class: 'plyr__menu', hidden: '' }); @@ -3031,7 +2504,7 @@ var controls = { 'aria-expanded': false })); - var form = utils.createElement('form', { + var form = createElement('form', { class: 'plyr__menu__container', id: 'plyr-settings-' + data.id, hidden: '', @@ -3040,27 +2513,27 @@ var controls = { tabindex: -1 }); - var inner = utils.createElement('div'); + var inner = createElement('div'); - var home = utils.createElement('div', { + var home = createElement('div', { id: 'plyr-settings-' + data.id + '-home', 'aria-labelled-by': 'plyr-settings-toggle-' + data.id, role: 'tabpanel' }); // Create the tab list - var tabs = utils.createElement('ul', { + var tabs = createElement('ul', { role: 'tablist' }); // Build the tabs this.config.settings.forEach(function (type) { - var tab = utils.createElement('li', { + var tab = createElement('li', { role: 'tab', hidden: '' }); - var button = utils.createElement('button', utils.extend(utils.getAttributesFromSelector(_this6.config.selectors.buttons.settings), { + var button = createElement('button', extend(getAttributesFromSelector(_this6.config.selectors.buttons.settings), { type: 'button', class: _this6.config.classNames.control + ' ' + _this6.config.classNames.control + '--forward', id: 'plyr-settings-' + data.id + '-' + type + '-tab', @@ -3069,7 +2542,7 @@ var controls = { 'aria-expanded': false }), i18n.get(type, _this6.config)); - var value = utils.createElement('span', { + var value = createElement('span', { class: _this6.config.classNames.menu.value }); @@ -3088,7 +2561,7 @@ var controls = { // Build the panes this.config.settings.forEach(function (type) { - var pane = utils.createElement('div', { + var pane = createElement('div', { id: 'plyr-settings-' + data.id + '-' + type, hidden: '', 'aria-labelled-by': 'plyr-settings-' + data.id + '-' + type + '-tab', @@ -3096,7 +2569,7 @@ var controls = { tabindex: -1 }); - var back = utils.createElement('button', { + var back = createElement('button', { type: 'button', class: _this6.config.classNames.control + ' ' + _this6.config.classNames.control + '--back', 'aria-haspopup': true, @@ -3106,7 +2579,7 @@ var controls = { pane.appendChild(back); - var options = utils.createElement('ul'); + var options = createElement('ul'); pane.appendChild(options); inner.appendChild(pane); @@ -3164,7 +2637,7 @@ var controls = { // Only load external sprite using AJAX if (icon.cors) { - utils.loadSprite(icon.url, 'sprite-plyr'); + loadSprite(icon.url, 'sprite-plyr'); } } @@ -3183,10 +2656,10 @@ var controls = { }; var update = true; - if (utils.is.string(this.config.controls) || utils.is.element(this.config.controls)) { + if (is.string(this.config.controls) || is.element(this.config.controls)) { // String or HTMLElement passed as the option container = this.config.controls; - } else if (utils.is.function(this.config.controls)) { + } else if (is.function(this.config.controls)) { // A custom function to build controls // The function can return a HTMLElement or String container = this.config.controls.call(this, props); @@ -3213,7 +2686,7 @@ var controls = { key = _ref3[0], value = _ref3[1]; - result = utils.replaceAll(result, '{' + key + '}', value); + result = replaceAll(result, '{' + key + '}', value); }); return result; @@ -3221,9 +2694,9 @@ var controls = { // Update markup if (update) { - if (utils.is.string(this.config.controls)) { + if (is.string(this.config.controls)) { container = replace(container); - } else if (utils.is.element(container)) { + } else if (is.element(container)) { container.innerHTML = replace(container.innerHTML); } } @@ -3232,39 +2705,39 @@ var controls = { var target = void 0; // Inject to custom location - if (utils.is.string(this.config.selectors.controls.container)) { + if (is.string(this.config.selectors.controls.container)) { target = document.querySelector(this.config.selectors.controls.container); } // Inject into the container by default - if (!utils.is.element(target)) { + if (!is.element(target)) { target = this.elements.container; } // Inject controls HTML - if (utils.is.element(container)) { + if (is.element(container)) { target.appendChild(container); } else if (container) { target.insertAdjacentHTML('beforeend', container); } // Find the elements if need be - if (!utils.is.element(this.elements.controls)) { + if (!is.element(this.elements.controls)) { controls.findElements.call(this); } // Edge sometimes doesn't finish the paint so force a redraw if (window.navigator.userAgent.includes('Edge')) { - utils.repaint(target); + repaint(target); } // Setup tooltips if (this.config.tooltips.controls) { - var labels = utils.getElements.call(this, [this.config.selectors.controls.wrapper, ' ', this.config.selectors.labels, ' .', this.config.classNames.hidden].join('')); + var labels = getElements.call(this, [this.config.selectors.controls.wrapper, ' ', this.config.selectors.labels, ' .', this.config.classNames.hidden].join('')); Array.from(labels).forEach(function (label) { - utils.toggleClass(label, _this7.config.classNames.hidden, false); - utils.toggleClass(label, _this7.config.classNames.tooltip, true); + toggleClass(label, _this7.config.classNames.hidden, false); + toggleClass(label, _this7.config.classNames.tooltip, true); label.setAttribute('role', 'tooltip'); }); } @@ -3272,6 +2745,95 @@ var controls = { }; // ========================================================================== +// Fetch wrapper +// Using XHR to avoid issues with older browsers +// ========================================================================== + +function fetch$1(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); + } + }); +} + +// ========================================================================== + +/** + * Parse a string to a URL object + * @param {string} input - the URL to be parsed + * @param {boolean} safe - failsafe parsing + */ +function parseUrl(input) { + var safe = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; + + var url = input; + + if (safe) { + var parser = document.createElement('a'); + parser.href = url; + url = parser.href; + } + + try { + return new URL(url); + } catch (e) { + return null; + } +} + +// Convert object to URLSearchParams +function buildUrlParams(input) { + if (!is.object(input)) { + return ''; + } + + var params = new URLSearchParams(); + + Object.entries(input).forEach(function (_ref) { + var _ref2 = slicedToArray(_ref, 2), + key = _ref2[0], + value = _ref2[1]; + + params.set(key, value); + }); + + return params; +} + +// ========================================================================== var captions = { // Setup captions @@ -3284,7 +2846,7 @@ var captions = { // Only Vimeo and HTML5 video supported at this point if (!this.isVideo || this.isYouTube || this.isHTML5 && !support.textTracks) { // Clear menu and hide - if (utils.is.array(this.config.controls) && this.config.controls.includes('settings') && this.config.settings.includes('captions')) { + if (is.array(this.config.controls) && this.config.controls.includes('settings') && this.config.settings.includes('captions')) { controls.setCaptionsMenu.call(this); } @@ -3292,15 +2854,12 @@ var captions = { } // Inject the container - if (!utils.is.element(this.elements.captions)) { - this.elements.captions = utils.createElement('div', utils.getAttributesFromSelector(this.config.selectors.captions)); + if (!is.element(this.elements.captions)) { + this.elements.captions = createElement('div', getAttributesFromSelector(this.config.selectors.captions)); - utils.insertAfter(this.elements.captions, this.elements.wrapper); + insertAfter(this.elements.captions, this.elements.wrapper); } - // Get browser info - var browser = utils.getBrowser(); - // Fix IE captions if CORS is used // Fetch captions and inject as blobs instead (data URIs not supported!) if (browser.isIE && window.URL) { @@ -3308,13 +2867,13 @@ var captions = { Array.from(elements).forEach(function (track) { var src = track.getAttribute('src'); - var href = utils.parseUrl(src); + var url = parseUrl(src); - if (href.hostname !== window.location.href.hostname && ['http:', 'https:'].includes(href.protocol)) { - utils.fetch(src, 'blob').then(function (blob) { + if (url !== null && url.hostname !== window.location.href.hostname && ['http:', 'https:'].includes(url.protocol)) { + fetch$1(src, 'blob').then(function (blob) { track.setAttribute('src', window.URL.createObjectURL(blob)); }).catch(function () { - utils.removeElement(track); + removeElement(track); }); } }); @@ -3324,7 +2883,7 @@ var captions = { var active = this.storage.get('captions'); // Otherwise fall back to the default config - if (!utils.is.boolean(active)) { + if (!is.boolean(active)) { active = this.config.captions.active; } @@ -3343,7 +2902,7 @@ var captions = { // Watch changes to textTracks and update captions menu if (this.isHTML5) { var trackEvents = this.config.captions.update ? 'addtrack removetrack' : 'removetrack'; - utils.on(this.media.textTracks, trackEvents, captions.update.bind(this)); + on(this.media.textTracks, trackEvents, captions.update.bind(this)); } // Update available languages in list next tick (the event must not be triggered before the listeners) @@ -3374,7 +2933,7 @@ var captions = { track.mode = 'hidden'; // Add event listener for cue changes - utils.on(track, 'cuechange', function () { + on(track, 'cuechange', function () { return captions.updateCues.call(_this); }); }); @@ -3393,7 +2952,7 @@ var captions = { } // Enable or disable captions based on track length - utils.toggleClass(this.elements.container, this.config.classNames.captions.enabled, !utils.is.empty(tracks)); + toggleClass(this.elements.container, this.config.classNames.captions.enabled, !is.empty(tracks)); // Update available languages in list if ((this.config.controls || []).includes('settings') && this.config.settings.includes('captions')) { @@ -3412,7 +2971,7 @@ var captions = { return; } - if (!utils.is.number(index)) { + if (!is.number(index)) { this.debug.warn('Invalid caption argument', index); return; } @@ -3445,7 +3004,7 @@ var captions = { } // Trigger event - utils.dispatchEvent.call(this, this.media, 'languagechange'); + trigger.call(this, this.media, 'languagechange'); } if (this.isHTML5 && this.isVideo) { @@ -3461,7 +3020,7 @@ var captions = { setLanguage: function setLanguage(language) { var show = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; - if (!utils.is.string(language)) { + if (!is.string(language)) { this.debug.warn('Invalid language argument', language); return; } @@ -3518,16 +3077,16 @@ var captions = { getLabel: function getLabel(track) { var currentTrack = track; - if (!utils.is.track(currentTrack) && support.textTracks && this.captions.active) { + if (!is.track(currentTrack) && support.textTracks && this.captions.active) { currentTrack = captions.getCurrentTrack.call(this); } - if (utils.is.track(currentTrack)) { - if (!utils.is.empty(currentTrack.label)) { + if (is.track(currentTrack)) { + if (!is.empty(currentTrack.label)) { return currentTrack.label; } - if (!utils.is.empty(currentTrack.language)) { + if (!is.empty(currentTrack.language)) { return track.language.toUpperCase(); } @@ -3546,13 +3105,13 @@ var captions = { return; } - if (!utils.is.element(this.elements.captions)) { + if (!is.element(this.elements.captions)) { this.debug.warn('No captions element to render to'); return; } // Only accept array or empty input - if (!utils.is.nullOrUndefined(input) && !Array.isArray(input)) { + if (!is.nullOrUndefined(input) && !Array.isArray(input)) { this.debug.warn('updateCues: Invalid input', input); return; } @@ -3564,7 +3123,7 @@ var captions = { var track = captions.getCurrentTrack.call(this); cues = Array.from((track || {}).activeCues || []).map(function (cue) { return cue.getCueAsHTML(); - }).map(utils.getHTML); + }).map(getHTML); } // Set new caption text @@ -3575,58 +3134,18 @@ var captions = { if (changed) { // Empty the container and create a new child element - utils.emptyElement(this.elements.captions); - var caption = utils.createElement('span', utils.getAttributesFromSelector(this.config.selectors.caption)); + emptyElement(this.elements.captions); + var caption = createElement('span', getAttributesFromSelector(this.config.selectors.caption)); caption.innerHTML = content; this.elements.captions.appendChild(caption); // Trigger event - utils.dispatchEvent.call(this, this.media, 'cuechange'); + trigger.call(this, this.media, 'cuechange'); } } }; // ========================================================================== -// Console wrapper -// ========================================================================== - -var noop = function noop() {}; - -var Console = function () { - function Console() { - var enabled = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; - classCallCheck(this, Console); - - this.enabled = window.console && enabled; - - if (this.enabled) { - this.log('Debugging enabled'); - } - } - - createClass(Console, [{ - key: 'log', - get: function get$$1() { - // eslint-disable-next-line no-console - return this.enabled ? Function.prototype.bind.call(console.log, console) : noop; - } - }, { - key: 'warn', - get: function get$$1() { - // eslint-disable-next-line no-console - return this.enabled ? Function.prototype.bind.call(console.warn, console) : noop; - } - }, { - key: 'error', - get: function get$$1() { - // eslint-disable-next-line no-console - return this.enabled ? Function.prototype.bind.call(console.error, console) : noop; - } - }]); - return Console; -}(); - -// ========================================================================== // Plyr default config // ========================================================================== @@ -3961,8 +3480,79 @@ var defaults$1 = { }; // ========================================================================== +// Plyr supported types and providers +// ========================================================================== + +var providers = { + html5: 'html5', + youtube: 'youtube', + vimeo: 'vimeo' +}; + +var types = { + audio: 'audio', + video: 'video' +}; + +/** + * Get provider by URL + * @param {string} url + */ +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; +} + +// ========================================================================== +// Console wrapper +// ========================================================================== + +var noop = function noop() {}; + +var Console = function () { + function Console() { + var enabled = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; + classCallCheck(this, Console); + + this.enabled = window.console && enabled; + + if (this.enabled) { + this.log('Debugging enabled'); + } + } -var browser$1 = utils.getBrowser(); + createClass(Console, [{ + key: 'log', + get: function get$$1() { + // eslint-disable-next-line no-console + return this.enabled ? Function.prototype.bind.call(console.log, console) : noop; + } + }, { + key: 'warn', + get: function get$$1() { + // eslint-disable-next-line no-console + return this.enabled ? Function.prototype.bind.call(console.warn, console) : noop; + } + }, { + key: 'error', + get: function get$$1() { + // eslint-disable-next-line no-console + return this.enabled ? Function.prototype.bind.call(console.error, console) : noop; + } + }]); + return Console; +}(); + +// ========================================================================== function onChange() { if (!this.enabled) { @@ -3971,16 +3561,16 @@ function onChange() { // Update toggle button var button = this.player.elements.buttons.fullscreen; - if (utils.is.element(button)) { - utils.toggleState(button, this.active); + if (is.element(button)) { + toggleState(button, this.active); } // Trigger an event - utils.dispatchEvent.call(this.player, this.target, this.active ? 'enterfullscreen' : 'exitfullscreen', true); + trigger.call(this.player, this.target, this.active ? 'enterfullscreen' : 'exitfullscreen', true); // Trap focus in container - if (!browser$1.isIos) { - utils.trapFocus.call(this.player, this.target, this.active); + if (!browser.isIos) { + trapFocus.call(this.player, this.target, this.active); } } @@ -4001,7 +3591,7 @@ function toggleFallback() { document.body.style.overflow = toggle ? 'hidden' : ''; // Toggle class hook - utils.toggleClass(this.target, this.player.config.classNames.fullscreen.fallback, toggle); + toggleClass(this.target, this.player.config.classNames.fullscreen.fallback, toggle); // Toggle button and fire events onChange.call(this); @@ -4025,15 +3615,15 @@ var Fullscreen = function () { // Register event listeners // Handle event (incase user presses escape etc) - utils.on(document, this.prefix === 'ms' ? 'MSFullscreenChange' : this.prefix + 'fullscreenchange', function () { + on(document, this.prefix === 'ms' ? 'MSFullscreenChange' : this.prefix + 'fullscreenchange', function () { // TODO: Filter for target?? onChange.call(_this); }); // Fullscreen toggle on double click - utils.on(this.player.elements.container, 'dblclick', function (event) { + on(this.player.elements.container, 'dblclick', function (event) { // Ignore double click in controls - if (utils.is.element(_this.player.elements.controls) && _this.player.elements.controls.contains(event.target)) { + if (is.element(_this.player.elements.controls) && _this.player.elements.controls.contains(event.target)) { return; } @@ -4060,7 +3650,7 @@ var Fullscreen = function () { } // Add styling hook to show button - utils.toggleClass(this.player.elements.container, this.player.config.classNames.fullscreen.enabled, this.enabled); + toggleClass(this.player.elements.container, this.player.config.classNames.fullscreen.enabled, this.enabled); } // Make an element fullscreen @@ -4073,7 +3663,7 @@ var Fullscreen = function () { } // iOS native fullscreen doesn't need the request step - if (browser$1.isIos && this.player.config.fullscreen.iosNative) { + if (browser.isIos && this.player.config.fullscreen.iosNative) { if (this.player.playing) { this.target.webkitEnterFullscreen(); } @@ -4081,7 +3671,7 @@ var Fullscreen = function () { toggleFallback.call(this, true); } else if (!this.prefix) { this.target.requestFullscreen(); - } else if (!utils.is.empty(this.prefix)) { + } else if (!is.empty(this.prefix)) { this.target[this.prefix + 'Request' + this.property](); } } @@ -4096,14 +3686,14 @@ var Fullscreen = function () { } // iOS native fullscreen - if (browser$1.isIos && this.player.config.fullscreen.iosNative) { + if (browser.isIos && this.player.config.fullscreen.iosNative) { this.target.webkitExitFullscreen(); this.player.play(); } else if (!Fullscreen.native) { toggleFallback.call(this, false); } else if (!this.prefix) { (document.cancelFullScreen || document.exitFullscreen).call(document); - } else if (!utils.is.empty(this.prefix)) { + } else if (!is.empty(this.prefix)) { var action = this.prefix === 'moz' ? 'Cancel' : 'Exit'; document['' + this.prefix + action + this.property](); } @@ -4140,7 +3730,7 @@ var Fullscreen = function () { // Fallback using classname if (!Fullscreen.native) { - return utils.hasClass(this.target, this.player.config.classNames.fullscreen.fallback); + return hasClass(this.target, this.player.config.classNames.fullscreen.fallback); } var element = !this.prefix ? document.fullscreenElement : document['' + this.prefix + this.property + 'Element']; @@ -4153,7 +3743,7 @@ var Fullscreen = function () { }, { key: 'target', get: function get$$1() { - return browser$1.isIos && this.player.config.fullscreen.iosNative ? this.player.media : this.player.elements.container; + return browser.isIos && this.player.config.fullscreen.iosNative ? this.player.media : this.player.elements.container; } }], [{ key: 'native', @@ -4167,7 +3757,7 @@ var Fullscreen = function () { key: 'prefix', get: function get$$1() { // No prefix - if (utils.is.function(document.exitFullscreen)) { + if (is.function(document.exitFullscreen)) { return ''; } @@ -4176,7 +3766,7 @@ var Fullscreen = function () { var prefixes = ['webkit', 'moz', 'ms']; prefixes.some(function (pre) { - if (utils.is.function(document[pre + 'ExitFullscreen']) || utils.is.function(document[pre + 'CancelFullScreen'])) { + if (is.function(document[pre + 'ExitFullscreen']) || is.function(document[pre + 'CancelFullScreen'])) { value = pre; return true; } @@ -4196,14 +3786,33 @@ var Fullscreen = function () { }(); // ========================================================================== +// 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 +// ========================================================================== + +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); + }; -// Sniff out the browser -var browser$2 = utils.getBrowser(); + Object.assign(image, { onload: handler, onerror: handler, src: src }); + }); +} + +// ========================================================================== var ui = { addStyleHook: function addStyleHook() { - utils.toggleClass(this.elements.container, this.config.selectors.container.replace('.', ''), true); - utils.toggleClass(this.elements.container, this.config.classNames.uiSupported, this.supported.ui); + toggleClass(this.elements.container, this.config.selectors.container.replace('.', ''), true); + toggleClass(this.elements.container, this.config.classNames.uiSupported, this.supported.ui); }, @@ -4239,7 +3848,7 @@ var ui = { } // Inject custom controls if not present - if (!utils.is.element(this.elements.controls)) { + if (!is.element(this.elements.controls)) { // Inject custom controls controls.inject.call(this); @@ -4280,23 +3889,23 @@ var ui = { ui.checkPlaying.call(this); // Check for picture-in-picture support - utils.toggleClass(this.elements.container, this.config.classNames.pip.supported, support.pip && this.isHTML5 && this.isVideo); + toggleClass(this.elements.container, this.config.classNames.pip.supported, support.pip && this.isHTML5 && this.isVideo); // Check for airplay support - utils.toggleClass(this.elements.container, this.config.classNames.airplay.supported, support.airplay && this.isHTML5); + toggleClass(this.elements.container, this.config.classNames.airplay.supported, support.airplay && this.isHTML5); // Add iOS class - utils.toggleClass(this.elements.container, this.config.classNames.isIos, browser$2.isIos); + toggleClass(this.elements.container, this.config.classNames.isIos, browser.isIos); // Add touch class - utils.toggleClass(this.elements.container, this.config.classNames.isTouch, this.touch); + toggleClass(this.elements.container, this.config.classNames.isTouch, this.touch); // Ready for API calls this.ready = true; // Ready event at end of execution stack setTimeout(function () { - utils.dispatchEvent.call(_this, _this.media, 'ready'); + trigger.call(_this, _this.media, 'ready'); }, 0); // Set the title @@ -4321,7 +3930,7 @@ var ui = { var label = i18n.get('play', this.config); // If there's a media title set, use that for the label - if (utils.is.string(this.config.title) && !utils.is.empty(this.config.title)) { + if (is.string(this.config.title) && !is.empty(this.config.title)) { label += ', ' + this.config.title; // Set container label @@ -4329,7 +3938,7 @@ var ui = { } // If there's a play button, set label - if (utils.is.nodeList(this.elements.buttons.play)) { + if (is.nodeList(this.elements.buttons.play)) { Array.from(this.elements.buttons.play).forEach(function (button) { button.setAttribute('aria-label', label); }); @@ -4338,14 +3947,14 @@ var ui = { // Set iframe title // https://github.com/sampotts/plyr/issues/124 if (this.isEmbed) { - var iframe = utils.getElement.call(this, 'iframe'); + var iframe = getElement.call(this, 'iframe'); - if (!utils.is.element(iframe)) { + if (!is.element(iframe)) { return; } // Default to media type - var title = !utils.is.empty(this.config.title) ? this.config.title : 'video'; + var title = !is.empty(this.config.title) ? this.config.title : 'video'; var format = i18n.get('frameTitle', this.config); iframe.setAttribute('title', format.replace('{title}', title)); @@ -4355,7 +3964,7 @@ var ui = { // Toggle poster togglePoster: function togglePoster(enable) { - utils.toggleClass(this.elements.container, this.config.classNames.posterEnabled, enable); + toggleClass(this.elements.container, this.config.classNames.posterEnabled, enable); }, @@ -4367,12 +3976,12 @@ var ui = { this.media.setAttribute('poster', poster); // Bail if element is missing - if (!utils.is.element(this.elements.poster)) { + if (!is.element(this.elements.poster)) { return Promise.reject(); } // Load the image, and set poster if successful - var loadPromise = utils.loadImage(poster).then(function () { + var loadPromise = loadImage(poster).then(function () { _this2.elements.poster.style.backgroundImage = 'url(\'' + poster + '\')'; Object.assign(_this2.elements.poster.style, { backgroundImage: 'url(\'' + poster + '\')', @@ -4396,15 +4005,15 @@ var ui = { // Check playing state checkPlaying: function checkPlaying(event) { // Class hooks - utils.toggleClass(this.elements.container, this.config.classNames.playing, this.playing); - utils.toggleClass(this.elements.container, this.config.classNames.paused, this.paused); - utils.toggleClass(this.elements.container, this.config.classNames.stopped, this.stopped); + toggleClass(this.elements.container, this.config.classNames.playing, this.playing); + toggleClass(this.elements.container, this.config.classNames.paused, this.paused); + toggleClass(this.elements.container, this.config.classNames.stopped, this.stopped); // Set ARIA state - utils.toggleState(this.elements.buttons.play, this.playing); + toggleState(this.elements.buttons.play, this.playing); // Only update controls on non timeupdate events - if (utils.is.event(event) && event.type === 'timeupdate') { + if (is.event(event) && event.type === 'timeupdate') { return; } @@ -4425,7 +4034,7 @@ var ui = { // Timer to prevent flicker when seeking this.timers.loading = setTimeout(function () { // Update progress bar loading class state - utils.toggleClass(_this3.elements.container, _this3.config.classNames.loading, _this3.loading); + toggleClass(_this3.elements.container, _this3.config.classNames.loading, _this3.loading); // Update controls visibility ui.toggleControls.call(_this3); @@ -4447,9 +4056,6 @@ var ui = { // ========================================================================== -// Sniff out the browser -var browser$3 = utils.getBrowser(); - var Listeners = function () { function Listeners(player) { classCallCheck(this, Listeners); @@ -4481,7 +4087,7 @@ var Listeners = function () { // If the event is bubbled from the media element // Firefox doesn't get the keycode for whatever reason - if (!utils.is.number(code)) { + if (!is.number(code)) { return; } @@ -4500,8 +4106,8 @@ var Listeners = function () { // Check focused element // and if the focused element is not editable (e.g. text input) // and any that accept key input http://webaim.org/techniques/keyboard/ - var focused = utils.getFocusElement(); - if (utils.is.element(focused) && focused !== this.player.elements.inputs.seek && utils.matches(focused, this.player.config.selectors.editable)) { + var focused = getFocusElement(); + if (is.element(focused) && focused !== this.player.elements.inputs.seek && matches(focused, this.player.config.selectors.editable)) { return; } @@ -4623,10 +4229,10 @@ var Listeners = function () { this.player.touch = true; // Add touch class - utils.toggleClass(this.player.elements.container, this.player.config.classNames.isTouch, true); + toggleClass(this.player.elements.container, this.player.config.classNames.isTouch, true); // Clean up - utils.off(document.body, 'touchstart', this.firstTouch); + off(document.body, 'touchstart', this.firstTouch); } // Global window & document listeners @@ -4638,14 +4244,14 @@ var Listeners = function () { // Keyboard shortcuts if (this.player.config.keyboard.global) { - utils.toggleListener(window, 'keydown keyup', this.handleKey, toggle, false); + toggleListener(window, 'keydown keyup', this.handleKey, toggle, false); } // Click anywhere closes menu - utils.toggleListener(document.body, 'click', this.toggleMenu, toggle); + toggleListener(document.body, 'click', this.toggleMenu, toggle); // Detect touch by events - utils.on(document.body, 'touchstart', this.firstTouch); + on(document.body, 'touchstart', this.firstTouch); } // Container listeners @@ -4657,17 +4263,17 @@ var Listeners = function () { // Keyboard shortcuts if (!this.player.config.keyboard.global && this.player.config.keyboard.focused) { - utils.on(this.player.elements.container, 'keydown keyup', this.handleKey, false); + on(this.player.elements.container, 'keydown keyup', this.handleKey, false); } // Detect tab focus // Remove class on blur/focusout - utils.on(this.player.elements.container, 'focusout', function (event) { - utils.toggleClass(event.target, _this2.player.config.classNames.tabFocus, false); + on(this.player.elements.container, 'focusout', function (event) { + toggleClass(event.target, _this2.player.config.classNames.tabFocus, false); }); // Add classname to tabbed elements - utils.on(this.player.elements.container, 'keydown', function (event) { + on(this.player.elements.container, 'keydown', function (event) { if (event.keyCode !== 9) { return; } @@ -4675,12 +4281,12 @@ var Listeners = function () { // Delay the adding of classname until the focus has changed // This event fires before the focusin event setTimeout(function () { - utils.toggleClass(utils.getFocusElement(), _this2.player.config.classNames.tabFocus, true); + toggleClass(getFocusElement(), _this2.player.config.classNames.tabFocus, true); }, 0); }); // Toggle controls on mouse events and entering fullscreen - utils.on(this.player.elements.container, 'mousemove mouseleave touchstart touchmove enterfullscreen exitfullscreen', function (event) { + on(this.player.elements.container, 'mousemove mouseleave touchstart touchmove enterfullscreen exitfullscreen', function (event) { var controls$$1 = _this2.player.elements.controls; // Remove button states for fullscreen @@ -4718,24 +4324,24 @@ var Listeners = function () { var _this3 = this; // Time change on media - utils.on(this.player.media, 'timeupdate seeking seeked', function (event) { + on(this.player.media, 'timeupdate seeking seeked', function (event) { return controls.timeUpdate.call(_this3.player, event); }); // Display duration - utils.on(this.player.media, 'durationchange loadeddata loadedmetadata', function (event) { + on(this.player.media, 'durationchange loadeddata loadedmetadata', function (event) { return controls.durationUpdate.call(_this3.player, event); }); // Check for audio tracks on load // We can't use `loadedmetadata` as it doesn't seem to have audio tracks at that point - utils.on(this.player.media, 'loadeddata', function () { - utils.toggleHidden(_this3.player.elements.volume, !_this3.player.hasAudio); - utils.toggleHidden(_this3.player.elements.buttons.mute, !_this3.player.hasAudio); + on(this.player.media, 'loadeddata canplay', function () { + toggleHidden(_this3.player.elements.volume, !_this3.player.hasAudio); + toggleHidden(_this3.player.elements.buttons.mute, !_this3.player.hasAudio); }); // Handle the media finishing - utils.on(this.player.media, 'ended', function () { + on(this.player.media, 'ended', function () { // Show poster on end if (_this3.player.isHTML5 && _this3.player.isVideo && _this3.player.config.resetOnEnd) { // Restart @@ -4744,28 +4350,28 @@ var Listeners = function () { }); // Check for buffer progress - utils.on(this.player.media, 'progress playing seeking seeked', function (event) { + on(this.player.media, 'progress playing seeking seeked', function (event) { return controls.updateProgress.call(_this3.player, event); }); // Handle volume changes - utils.on(this.player.media, 'volumechange', function (event) { + on(this.player.media, 'volumechange', function (event) { return controls.updateVolume.call(_this3.player, event); }); // Handle play/pause - utils.on(this.player.media, 'playing play pause ended emptied timeupdate', function (event) { + on(this.player.media, 'playing play pause ended emptied timeupdate', function (event) { return ui.checkPlaying.call(_this3.player, event); }); // Loading state - utils.on(this.player.media, 'waiting canplay seeked playing', function (event) { + on(this.player.media, 'waiting canplay seeked playing', function (event) { return ui.checkLoading.call(_this3.player, event); }); // If autoplay, then load advertisement if required // TODO: Show some sort of loading state while the ad manager loads else there's a delay before ad shows - utils.on(this.player.media, 'playing', function () { + on(this.player.media, 'playing', function () { if (!_this3.player.ads) { return; } @@ -4784,15 +4390,15 @@ var Listeners = function () { // Click video if (this.player.supported.ui && this.player.config.clickToPlay && !this.player.isAudio) { // Re-fetch the wrapper - var wrapper = utils.getElement.call(this.player, '.' + this.player.config.classNames.video); + var wrapper = getElement.call(this.player, '.' + this.player.config.classNames.video); // Bail if there's no wrapper (this should never happen) - if (!utils.is.element(wrapper)) { + if (!is.element(wrapper)) { return; } // On click play, pause ore restart - utils.on(wrapper, 'click', function () { + on(wrapper, 'click', function () { // Touch devices will just show controls (if we're hiding controls) if (_this3.player.config.hideControls && _this3.player.touch && !_this3.player.paused) { return; @@ -4811,19 +4417,19 @@ var Listeners = function () { // Disable right click if (this.player.supported.ui && this.player.config.disableContextMenu) { - utils.on(this.player.elements.wrapper, 'contextmenu', function (event) { + on(this.player.elements.wrapper, 'contextmenu', function (event) { event.preventDefault(); }, false); } // Volume change - utils.on(this.player.media, 'volumechange', function () { + on(this.player.media, 'volumechange', function () { // Save to storage _this3.player.storage.set({ volume: _this3.player.volume, muted: _this3.player.muted }); }); // Speed change - utils.on(this.player.media, 'ratechange', function () { + on(this.player.media, 'ratechange', function () { // Update UI controls.updateSetting.call(_this3.player, 'speed'); @@ -4832,19 +4438,19 @@ var Listeners = function () { }); // Quality request - utils.on(this.player.media, 'qualityrequested', function (event) { + on(this.player.media, 'qualityrequested', function (event) { // Save to storage _this3.player.storage.set({ quality: event.detail.quality }); }); // Quality change - utils.on(this.player.media, 'qualitychange', function (event) { + on(this.player.media, 'qualitychange', function (event) { // Update UI controls.updateSetting.call(_this3.player, 'quality', null, event.detail.quality); }); // Caption language change - utils.on(this.player.media, 'languagechange', function () { + on(this.player.media, 'languagechange', function () { // Update UI controls.updateSetting.call(_this3.player, 'captions'); @@ -4853,7 +4459,7 @@ var Listeners = function () { }); // Captions toggle - utils.on(this.player.media, 'captionsenabled captionsdisabled', function () { + on(this.player.media, 'captionsenabled captionsdisabled', function () { // Update UI controls.updateSetting.call(_this3.player, 'captions'); @@ -4863,7 +4469,7 @@ var Listeners = function () { // Proxy events to container // Bubble up key events for Edge - utils.on(this.player.media, this.player.config.events.concat(['keyup', 'keydown']).join(' '), function (event) { + on(this.player.media, this.player.config.events.concat(['keyup', 'keydown']).join(' '), function (event) { var _event$detail = event.detail, detail = _event$detail === undefined ? {} : _event$detail; @@ -4873,7 +4479,7 @@ var Listeners = function () { detail = _this3.player.media.error; } - utils.dispatchEvent.call(_this3.player, _this3.player.elements.container, event.type, true, detail); + trigger.call(_this3.player, _this3.player.elements.container, event.type, true, detail); }); } @@ -4885,12 +4491,12 @@ var Listeners = function () { var _this4 = this; // IE doesn't support input event, so we fallback to change - var inputEvent = browser$3.isIE ? 'change' : 'input'; + var inputEvent = browser.isIE ? 'change' : 'input'; // Run default and custom handlers var proxy = function proxy(event, defaultHandler, customHandlerKey) { var customHandler = _this4.player.config.listeners[customHandlerKey]; - var hasCustomHandler = utils.is.function(customHandler); + var hasCustomHandler = is.function(customHandler); var returned = true; // Execute custom handler @@ -4899,63 +4505,63 @@ var Listeners = function () { } // Only call default handler if not prevented in custom handler - if (returned && utils.is.function(defaultHandler)) { + if (returned && is.function(defaultHandler)) { defaultHandler.call(_this4.player, event); } }; // Trigger custom and default handlers - var on = function on(element, type, defaultHandler, customHandlerKey) { + var bind = function bind(element, type, defaultHandler, customHandlerKey) { var passive = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : true; var customHandler = _this4.player.config.listeners[customHandlerKey]; - var hasCustomHandler = utils.is.function(customHandler); + var hasCustomHandler = is.function(customHandler); - utils.on(element, type, function (event) { + on(element, type, function (event) { return proxy(event, defaultHandler, customHandlerKey); }, passive && !hasCustomHandler); }; // Play/pause toggle - on(this.player.elements.buttons.play, 'click', this.player.togglePlay, 'play'); + bind(this.player.elements.buttons.play, 'click', this.player.togglePlay, 'play'); // Pause - on(this.player.elements.buttons.restart, 'click', this.player.restart, 'restart'); + bind(this.player.elements.buttons.restart, 'click', this.player.restart, 'restart'); // Rewind - on(this.player.elements.buttons.rewind, 'click', this.player.rewind, 'rewind'); + bind(this.player.elements.buttons.rewind, 'click', this.player.rewind, 'rewind'); // Rewind - on(this.player.elements.buttons.fastForward, 'click', this.player.forward, 'fastForward'); + bind(this.player.elements.buttons.fastForward, 'click', this.player.forward, 'fastForward'); // Mute toggle - on(this.player.elements.buttons.mute, 'click', function () { + bind(this.player.elements.buttons.mute, 'click', function () { _this4.player.muted = !_this4.player.muted; }, 'mute'); // Captions toggle - on(this.player.elements.buttons.captions, 'click', this.player.toggleCaptions); + bind(this.player.elements.buttons.captions, 'click', this.player.toggleCaptions); // Fullscreen toggle - on(this.player.elements.buttons.fullscreen, 'click', function () { + bind(this.player.elements.buttons.fullscreen, 'click', function () { _this4.player.fullscreen.toggle(); }, 'fullscreen'); // Picture-in-Picture - on(this.player.elements.buttons.pip, 'click', function () { + bind(this.player.elements.buttons.pip, 'click', function () { _this4.player.pip = 'toggle'; }, 'pip'); // Airplay - on(this.player.elements.buttons.airplay, 'click', this.player.airplay, 'airplay'); + bind(this.player.elements.buttons.airplay, 'click', this.player.airplay, 'airplay'); // Settings menu - on(this.player.elements.buttons.settings, 'click', function (event) { + bind(this.player.elements.buttons.settings, 'click', function (event) { controls.toggleMenu.call(_this4.player, event); }); // Settings menu - on(this.player.elements.settings.form, 'click', function (event) { + bind(this.player.elements.settings.form, 'click', function (event) { event.stopPropagation(); // Go back to home tab on click @@ -4965,17 +4571,17 @@ var Listeners = function () { }; // Settings menu items - use event delegation as items are added/removed - if (utils.matches(event.target, _this4.player.config.selectors.inputs.language)) { + if (matches(event.target, _this4.player.config.selectors.inputs.language)) { proxy(event, function () { _this4.player.currentTrack = Number(event.target.value); showHomeTab(); }, 'language'); - } else if (utils.matches(event.target, _this4.player.config.selectors.inputs.quality)) { + } else if (matches(event.target, _this4.player.config.selectors.inputs.quality)) { proxy(event, function () { _this4.player.quality = event.target.value; showHomeTab(); }, 'quality'); - } else if (utils.matches(event.target, _this4.player.config.selectors.inputs.speed)) { + } else if (matches(event.target, _this4.player.config.selectors.inputs.speed)) { proxy(event, function () { _this4.player.speed = parseFloat(event.target.value); showHomeTab(); @@ -4987,14 +4593,14 @@ var Listeners = function () { }); // Set range input alternative "value", which matches the tooltip time (#954) - on(this.player.elements.inputs.seek, 'mousedown mousemove', function (event) { + bind(this.player.elements.inputs.seek, 'mousedown mousemove', function (event) { var clientRect = _this4.player.elements.progress.getBoundingClientRect(); var percent = 100 / clientRect.width * (event.pageX - clientRect.left); event.currentTarget.setAttribute('seek-value', percent); }); // Pause while seeking - on(this.player.elements.inputs.seek, 'mousedown mouseup keydown keyup touchstart touchend', function (event) { + bind(this.player.elements.inputs.seek, 'mousedown mouseup keydown keyup touchstart touchend', function (event) { var seek = event.currentTarget; var code = event.keyCode ? event.keyCode : event.which; @@ -5020,13 +4626,13 @@ var Listeners = function () { }); // Seek - on(this.player.elements.inputs.seek, inputEvent, function (event) { + bind(this.player.elements.inputs.seek, inputEvent, function (event) { var seek = event.currentTarget; // If it exists, use seek-value instead of "value" for consistency with tooltip time (#954) var seekTo = seek.getAttribute('seek-value'); - if (utils.is.empty(seekTo)) { + if (is.empty(seekTo)) { seekTo = seek.value; } @@ -5037,8 +4643,8 @@ var Listeners = function () { // Current time invert // Only if one time element is used for both currentTime and duration - if (this.player.config.toggleInvert && !utils.is.element(this.player.elements.display.duration)) { - on(this.player.elements.display.currentTime, 'click', function () { + if (this.player.config.toggleInvert && !is.element(this.player.elements.display.duration)) { + bind(this.player.elements.display.currentTime, 'click', function () { // Do nothing if we're at the start if (_this4.player.currentTime === 0) { return; @@ -5051,34 +4657,34 @@ var Listeners = function () { } // Volume - on(this.player.elements.inputs.volume, inputEvent, function (event) { + bind(this.player.elements.inputs.volume, inputEvent, function (event) { _this4.player.volume = event.target.value; }, 'volume'); // Polyfill for lower fill in <input type="range"> for webkit - if (browser$3.isWebkit) { - on(utils.getElements.call(this.player, 'input[type="range"]'), 'input', function (event) { + if (browser.isWebkit) { + bind(getElements.call(this.player, 'input[type="range"]'), 'input', function (event) { controls.updateRangeFill.call(_this4.player, event.target); }); } // Seek tooltip - on(this.player.elements.progress, 'mouseenter mouseleave mousemove', function (event) { + bind(this.player.elements.progress, 'mouseenter mouseleave mousemove', function (event) { return controls.updateSeekTooltip.call(_this4.player, event); }); // Update controls.hover state (used for ui.toggleControls to avoid hiding when interacting) - on(this.player.elements.controls, 'mouseenter mouseleave', function (event) { + bind(this.player.elements.controls, 'mouseenter mouseleave', function (event) { _this4.player.elements.controls.hover = !_this4.player.touch && event.type === 'mouseenter'; }); // Update controls.pressed state (used for ui.toggleControls to avoid hiding when interacting) - on(this.player.elements.controls, 'mousedown mouseup touchstart touchend touchcancel', function (event) { + bind(this.player.elements.controls, 'mousedown mouseup touchstart touchend touchcancel', function (event) { _this4.player.elements.controls.pressed = ['mousedown', 'touchstart'].includes(event.type); }); // Focus in/out on controls - on(this.player.elements.controls, 'focusin focusout', function (event) { + bind(this.player.elements.controls, 'focusin focusout', function (event) { var _player = _this4.player, config = _player.config, elements = _player.elements, @@ -5086,7 +4692,7 @@ var Listeners = function () { // Skip transition to prevent focus from scrolling the parent element - utils.toggleClass(elements.controls, config.classNames.noTransition, event.type === 'focusin'); + toggleClass(elements.controls, config.classNames.noTransition, event.type === 'focusin'); // Toggle ui.toggleControls.call(_this4.player, event.type === 'focusin'); @@ -5095,7 +4701,7 @@ var Listeners = function () { if (event.type === 'focusin') { // Restore transition setTimeout(function () { - utils.toggleClass(elements.controls, config.classNames.noTransition, false); + toggleClass(elements.controls, config.classNames.noTransition, false); }, 0); // Delay a little more for keyboard users @@ -5111,7 +4717,7 @@ var Listeners = function () { }); // Mouse wheel for volume - on(this.player.elements.inputs.volume, 'wheel', function (event) { + bind(this.player.elements.inputs.volume, 'wheel', function (event) { // Detect "natural" scroll - suppored on OS X Safari only // Other browsers on OS X will be inverted until support improves var inverted = event.webkitDirectionInvertedFromDevice; @@ -5158,8 +4764,342 @@ var Listeners = function () { return Listeners; }(); +var commonjsGlobal = 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 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; +}; + + +/** + * Manually satisfy bundle dependencies. + * @param {string} bundleId - The bundle id + */ +loadjs.done = function done(bundleId) { + publish(bundleId, []); +}; + + +/** + * Reset loadjs dependencies statuses + */ +loadjs.reset = function reset() { + bundleIdCache = {}; + bundleResultCache = {}; + bundleCallbackQueue = {}; +}; + + +/** + * Determine if bundle has already been defined + * @param String} bundleId - The bundle id + */ +loadjs.isDefined = function isDefined(bundleId) { + return bundleId in bundleIdCache; +}; + + +// export +return loadjs; + +})); +}); + +// ========================================================================== + +function loadScript(url) { + return new Promise(function (resolve, reject) { + loadjs_umd(url, { + success: resolve, + error: reject + }); + }); +} + // ========================================================================== +// Parse Vimeo ID from URL +function parseId(url) { + if (is.empty(url)) { + return null; + } + + if (is.number(Number(url))) { + return url; + } + + var regex = /^.*(vimeo.com\/|video\/)(\d+).*/; + return url.match(regex) ? RegExp.$2 : url; +} + +// Get aspect ratio for dimensions +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; +} + // Set playback state and trigger change (only on actual change) function assurePlaybackState(play) { if (play && !this.embed.hasPlayed) { @@ -5167,7 +5107,7 @@ function assurePlaybackState(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'); } } @@ -5176,14 +5116,14 @@ var vimeo = { 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 intial ratio vimeo.setAspectRatio.call(this); // Load the API if not already - if (!utils.is.object(window.Vimeo)) { - utils.loadScript(this.config.urls.vimeo.sdk).then(function () { + if (!is.object(window.Vimeo)) { + loadScript(this.config.urls.vimeo.sdk).then(function () { vimeo.ready.call(_this); }).catch(function (error) { _this.debug.warn('Vimeo API failed to load', error); @@ -5197,7 +5137,7 @@ var vimeo = { // Set aspect ratio // For Vimeo we have an extra 300% height <div> 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 <div> 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 <div> if needed - if (utils.is.empty(source)) { + if (is.empty(source)) { source = player.media.getAttribute(this.config.attributes.embed.id); } // Replace the <iframe> with a <div> due to YouTube API issues - var videoId = utils.parseYouTubeId(source); - var id = utils.generateId(player.provider); - var container = utils.createElement('div', { id: id }); - player.media = utils.replaceElement(container, player.media); + var videoId = parseId$1(source); + var id = generateId(player.provider); + var container = createElement('div', { id: id }); + player.media = replaceElement(container, player.media); // Set poster image - var posterSrc = function posterSrc(format) { - return 'https://img.youtube.com/vi/' + videoId + '/' + format + 'default.jpg'; + var posterSrc = function posterSrc(format$$1) { + return 'https://img.youtube.com/vi/' + videoId + '/' + format$$1 + 'default.jpg'; }; // Check thumbnail images in order of quality, but reject fallback thumbnails (120px wide) - utils.loadImage(posterSrc('maxres'), 121) // Higest quality and unpadded + loadImage(posterSrc('maxres'), 121) // Higest quality and unpadded .catch(function () { - return utils.loadImage(posterSrc('sd'), 121); + return loadImage(posterSrc('sd'), 121); }) // 480p padded 4:3 .catch(function () { - return utils.loadImage(posterSrc('hq')); + return loadImage(posterSrc('hq')); }) // 360p padded 4:3. Always exists .then(function (image) { return ui.setPoster.call(player, image.src); @@ -5772,7 +5722,7 @@ var youtube = { onError: function onError(event) { // If we've already fired an error, don't do it again // YouTube fires onError twice - if (utils.is.object(player.media.error)) { + if (is.object(player.media.error)) { return; } @@ -5806,10 +5756,10 @@ var youtube = { player.media.error = detail; - utils.dispatchEvent.call(player, player.media, 'error'); + trigger.call(player, player.media, 'error'); }, onPlaybackQualityChange: function onPlaybackQualityChange() { - utils.dispatchEvent.call(player, player.media, 'qualitychange', false, { + trigger.call(player, player.media, 'qualitychange', false, { quality: player.media.quality }); }, @@ -5820,7 +5770,7 @@ var youtube = { // Get current speed player.media.playbackRate = instance.getPlaybackRate(); - utils.dispatchEvent.call(player, player.media, 'ratechange'); + trigger.call(player, player.media, 'ratechange'); }, onReady: function onReady(event) { // Get the instance @@ -5861,7 +5811,7 @@ var youtube = { // Set seeking state and trigger event player.media.seeking = true; - utils.dispatchEvent.call(player, player.media, 'seeking'); + trigger.call(player, player.media, 'seeking'); // Seek after events sent instance.seekTo(time); @@ -5890,7 +5840,7 @@ var youtube = { instance.setPlaybackQuality(mapQualityUnit(quality)); // Trigger request event - utils.dispatchEvent.call(player, player.media, 'qualityrequested', false, { + trigger.call(player, player.media, 'qualityrequested', false, { quality: quality }); } @@ -5906,7 +5856,7 @@ var youtube = { set: function set(input) { volume = input; instance.setVolume(volume * 100); - utils.dispatchEvent.call(player, player.media, 'volumechange'); + trigger.call(player, player.media, 'volumechange'); } }); @@ -5918,10 +5868,10 @@ var youtube = { return muted; }, set: function set(input) { - var toggle = utils.is.boolean(input) ? input : muted; + var toggle = is.boolean(input) ? input : muted; muted = toggle; instance[toggle ? 'mute' : 'unMute'](); - utils.dispatchEvent.call(player, player.media, 'volumechange'); + trigger.call(player, player.media, 'volumechange'); } }); @@ -5947,8 +5897,8 @@ var youtube = { player.media.setAttribute('tabindex', -1); } - utils.dispatchEvent.call(player, player.media, 'timeupdate'); - utils.dispatchEvent.call(player, player.media, 'durationchange'); + trigger.call(player, player.media, 'timeupdate'); + trigger.call(player, player.media, 'durationchange'); // Reset timer clearInterval(player.timers.buffering); @@ -5960,7 +5910,7 @@ var youtube = { // Trigger progress only when we actually buffer something if (player.media.lastBuffered === null || player.media.lastBuffered < player.media.buffered) { - utils.dispatchEvent.call(player, player.media, 'progress'); + trigger.call(player, player.media, 'progress'); } // Set last buffer point @@ -5971,7 +5921,7 @@ var youtube = { clearInterval(player.timers.buffering); // Trigger event - utils.dispatchEvent.call(player, player.media, 'canplaythrough'); + trigger.call(player, player.media, 'canplaythrough'); } }, 200); @@ -5992,7 +5942,7 @@ var youtube = { if (seeked) { // Unset seeking and fire seeked event player.media.seeking = false; - utils.dispatchEvent.call(player, player.media, 'seeked'); + trigger.call(player, player.media, 'seeked'); } // Handle events @@ -6005,11 +5955,11 @@ var youtube = { switch (event.data) { case -1: // Update scrubber - utils.dispatchEvent.call(player, player.media, 'timeupdate'); + trigger.call(player, player.media, 'timeupdate'); // Get loaded % from YouTube player.media.buffered = instance.getVideoLoadedFraction(); - utils.dispatchEvent.call(player, player.media, 'progress'); + trigger.call(player, player.media, 'progress'); break; @@ -6022,7 +5972,7 @@ var youtube = { instance.stopVideo(); instance.playVideo(); } else { - utils.dispatchEvent.call(player, player.media, 'ended'); + trigger.call(player, player.media, 'ended'); } break; @@ -6034,11 +5984,11 @@ var youtube = { } else { assurePlaybackState$1.call(player, true); - utils.dispatchEvent.call(player, player.media, 'playing'); + trigger.call(player, player.media, 'playing'); // Poll to get playback progress player.timers.playing = setInterval(function () { - utils.dispatchEvent.call(player, player.media, 'timeupdate'); + trigger.call(player, player.media, 'timeupdate'); }, 50); // Check duration again due to YouTube bug @@ -6046,7 +5996,7 @@ var youtube = { // https://code.google.com/p/gdata-issues/issues/detail?id=8690 if (player.media.duration !== instance.getDuration()) { player.media.duration = instance.getDuration(); - utils.dispatchEvent.call(player, player.media, 'durationchange'); + trigger.call(player, player.media, 'durationchange'); } // Get quality @@ -6068,7 +6018,7 @@ var youtube = { break; } - utils.dispatchEvent.call(player, player.elements.container, 'statechange', false, { + trigger.call(player, player.elements.container, 'statechange', false, { code: event.data }); } @@ -6089,29 +6039,29 @@ var media = { } // Add type class - utils.toggleClass(this.elements.container, this.config.classNames.type.replace('{0}', this.type), true); + toggleClass(this.elements.container, this.config.classNames.type.replace('{0}', this.type), true); // Add provider class - utils.toggleClass(this.elements.container, this.config.classNames.provider.replace('{0}', this.provider), true); + toggleClass(this.elements.container, this.config.classNames.provider.replace('{0}', this.provider), true); // Add video class for embeds // This will require changes if audio embeds are added if (this.isEmbed) { - utils.toggleClass(this.elements.container, this.config.classNames.type.replace('{0}', 'video'), true); + toggleClass(this.elements.container, this.config.classNames.type.replace('{0}', 'video'), true); } // Inject the player wrapper if (this.isVideo) { // Create the wrapper div - this.elements.wrapper = utils.createElement('div', { + this.elements.wrapper = createElement('div', { class: this.config.classNames.video }); // Wrap the video in a container - utils.wrap(this.media, this.elements.wrapper); + wrap(this.media, this.elements.wrapper); // Faux poster container - this.elements.poster = utils.createElement('div', { + this.elements.poster = createElement('div', { class: this.config.classNames.poster }); @@ -6189,8 +6139,8 @@ var Ads = function () { if (this.enabled) { // Check if the Google IMA3 SDK is loaded or load it ourselves - if (!utils.is.object(window.google) || !utils.is.object(window.google.ima)) { - utils.loadScript(this.player.config.urls.googleIMA.sdk).then(function () { + if (!is.object(window.google) || !is.object(window.google.ima)) { + loadScript(this.player.config.urls.googleIMA.sdk).then(function () { _this2.ready(); }).catch(function () { // Script failed to load or is blocked @@ -6243,7 +6193,7 @@ var Ads = function () { */ value: function setupIMA() { // Create the container for our advertisements - this.elements.container = utils.createElement('div', { + this.elements.container = createElement('div', { class: this.player.config.classNames.ads }); this.player.elements.container.appendChild(this.elements.container); @@ -6328,7 +6278,7 @@ var Ads = function () { } var update = function update() { - var time = utils.formatTime(Math.max(_this5.manager.getRemainingTime(), 0)); + var time = formatTime(Math.max(_this5.manager.getRemainingTime(), 0)); var label = i18n.get('advertisement', _this5.player.config) + ' - ' + time; _this5.elements.container.setAttribute('data-badge-text', label); }; @@ -6361,14 +6311,14 @@ var Ads = function () { this.cuePoints = this.manager.getCuePoints(); // Add advertisement cue's within the time line if available - if (!utils.is.empty(this.cuePoints)) { + if (!is.empty(this.cuePoints)) { this.cuePoints.forEach(function (cuePoint) { if (cuePoint !== 0 && cuePoint !== -1 && cuePoint < _this6.player.duration) { var seekElement = _this6.player.elements.progress; - if (utils.is.element(seekElement)) { + if (is.element(seekElement)) { var cuePercentage = 100 / _this6.player.duration * cuePoint; - var cue = utils.createElement('span', { + var cue = createElement('span', { class: _this6.player.config.classNames.cues }); @@ -6425,7 +6375,7 @@ var Ads = function () { // Proxy event var dispatchEvent = function dispatchEvent(type) { var event = 'ads' + type.replace(/_/g, '').toLowerCase(); - utils.dispatchEvent.call(_this7.player, _this7.player.media, event); + trigger.call(_this7.player, _this7.player.media, event); }; switch (event.type) { @@ -6561,7 +6511,7 @@ var Ads = function () { this.player.on('seeked', function () { var seekedTime = _this8.player.currentTime; - if (utils.is.empty(_this8.cuePoints)) { + if (is.empty(_this8.cuePoints)) { return; } @@ -6714,7 +6664,7 @@ var Ads = function () { }, { key: 'trigger', - value: function trigger(event) { + value: function trigger$$1(event) { var _this11 = this; for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { @@ -6723,9 +6673,9 @@ var Ads = function () { var handlers = this.events[event]; - if (utils.is.array(handlers)) { + if (is.array(handlers)) { handlers.forEach(function (handler) { - if (utils.is.function(handler)) { + if (is.function(handler)) { handler.apply(_this11, args); } }); @@ -6741,8 +6691,8 @@ var Ads = function () { }, { key: 'on', - value: function on(event, callback) { - if (!utils.is.array(this.events[event])) { + value: function on$$1(event, callback) { + if (!is.array(this.events[event])) { this.events[event] = []; } @@ -6781,7 +6731,7 @@ var Ads = function () { }, { key: 'clearSafetyTimer', value: function clearSafetyTimer(from) { - if (!utils.is.nullOrUndefined(this.safetyTimer)) { + if (!is.nullOrUndefined(this.safetyTimer)) { this.player.debug.log('Safety timer cleared from: ' + from); clearTimeout(this.safetyTimer); @@ -6791,7 +6741,7 @@ var Ads = function () { }, { key: 'enabled', get: function get$$1() { - return this.player.isVideo && this.player.config.ads.enabled && !utils.is.empty(this.publisherId); + return this.player.isVideo && this.player.config.ads.enabled && !is.empty(this.publisherId); } }, { key: 'tagUrl', @@ -6808,7 +6758,7 @@ var Ads = function () { var base = 'https://go.aniview.com/api/adserver6/vast/'; - return base + '?' + utils.buildUrlParams(params); + return base + '?' + buildUrlParams(params); } }]); return Ads; @@ -6821,13 +6771,13 @@ var source = { insertElements: function insertElements(type, attributes) { var _this = this; - if (utils.is.string(attributes)) { - utils.insertElement(type, this.media, { + if (is.string(attributes)) { + insertElement(type, this.media, { src: attributes }); - } else if (utils.is.array(attributes)) { + } else if (is.array(attributes)) { attributes.forEach(function (attribute) { - utils.insertElement(type, _this.media, attribute); + insertElement(type, _this.media, attribute); }); } }, @@ -6838,7 +6788,7 @@ var source = { change: function change(input) { var _this2 = this; - if (!utils.is.object(input) || !('sources' in input) || !input.sources.length) { + if (!is.object(input) || !('sources' in input) || !input.sources.length) { this.debug.warn('Invalid source format'); return; } @@ -6852,17 +6802,17 @@ var source = { _this2.options.quality = []; // Remove elements - utils.removeElement(_this2.media); + removeElement(_this2.media); _this2.media = null; // Reset class name - if (utils.is.element(_this2.elements.container)) { + if (is.element(_this2.elements.container)) { _this2.elements.container.removeAttribute('class'); } // Set the type and provider _this2.type = input.type; - _this2.provider = !utils.is.empty(input.sources[0].provider) ? input.sources[0].provider : providers.html5; + _this2.provider = !is.empty(input.sources[0].provider) ? input.sources[0].provider : providers.html5; // Check for support _this2.supported = support.check(_this2.type, _this2.provider, _this2.config.playsinline); @@ -6870,16 +6820,16 @@ var source = { // Create new markup switch (_this2.provider + ':' + _this2.type) { case 'html5:video': - _this2.media = utils.createElement('video'); + _this2.media = createElement('video'); break; case 'html5:audio': - _this2.media = utils.createElement('audio'); + _this2.media = createElement('audio'); break; case 'youtube:video': case 'vimeo:video': - _this2.media = utils.createElement('div', { + _this2.media = createElement('div', { src: input.sources[0].src }); break; @@ -6892,7 +6842,7 @@ var source = { _this2.elements.container.appendChild(_this2.media); // Autoplay the new source? - if (utils.is.boolean(input.autoplay)) { + if (is.boolean(input.autoplay)) { _this2.config.autoplay = input.autoplay; } @@ -6904,7 +6854,7 @@ var source = { if (_this2.config.autoplay) { _this2.media.setAttribute('autoplay', ''); } - if (!utils.is.empty(input.poster)) { + if (!is.empty(input.poster)) { _this2.poster = input.poster; } if (_this2.config.loop.active) { @@ -6983,18 +6933,18 @@ var Plyr = function () { this.media = target; // String selector passed - if (utils.is.string(this.media)) { + if (is.string(this.media)) { this.media = document.querySelectorAll(this.media); } // jQuery, NodeList or Array passed, use first element - if (window.jQuery && this.media instanceof jQuery || utils.is.nodeList(this.media) || utils.is.array(this.media)) { + if (window.jQuery && this.media instanceof jQuery || is.nodeList(this.media) || is.array(this.media)) { // eslint-disable-next-line this.media = this.media[0]; } // Set config - this.config = utils.extend({}, defaults$1, Plyr.defaults, options || {}, function () { + this.config = extend({}, defaults$1, Plyr.defaults, options || {}, function () { try { return JSON.parse(_this.media.getAttribute('data-plyr-config')); } catch (e) { @@ -7044,7 +6994,7 @@ var Plyr = function () { this.debug.log('Support', support); // We need an element to setup - if (utils.is.nullOrUndefined(this.media) || !utils.is.element(this.media)) { + if (is.nullOrUndefined(this.media) || !is.element(this.media)) { this.debug.error('Setup failed: no suitable element passed'); return; } @@ -7080,7 +7030,6 @@ var Plyr = function () { // Embed properties var iframe = null; var url = null; - var params = null; // Different setup based on type switch (type) { @@ -7089,10 +7038,10 @@ var Plyr = function () { iframe = this.media.querySelector('iframe'); // <iframe> type - if (utils.is.element(iframe)) { + if (is.element(iframe)) { // Detect provider - url = iframe.getAttribute('src'); - this.provider = utils.getProviderByUrl(url); + url = parseUrl(iframe.getAttribute('src')); + this.provider = getProviderByUrl(url.toString()); // Rework elements this.elements.container = this.media; @@ -7102,21 +7051,20 @@ var Plyr = function () { this.elements.container.className = ''; // Get attributes from URL and set config - params = utils.getUrlParams(url); - if (!utils.is.empty(params)) { + if (!url.searchParams) { var truthy = ['1', 'true']; - if (truthy.includes(params.autoplay)) { + if (truthy.includes(url.searchParams.get('autoplay'))) { this.config.autoplay = true; } - if (truthy.includes(params.loop)) { + if (truthy.includes(url.searchParams.get('loop'))) { this.config.loop.active = true; } // TODO: replace fullscreen.iosNative with this playsinline config option // YouTube requires the playsinline in the URL if (this.isYouTube) { - this.config.playsinline = truthy.includes(params.playsinline); + this.config.playsinline = truthy.includes(url.searchParams.get('playsinline')); } else { this.config.playsinline = true; } @@ -7130,7 +7078,7 @@ var Plyr = function () { } // Unsupported or missing provider - if (utils.is.empty(this.provider) || !Object.keys(providers).includes(this.provider)) { + if (is.empty(this.provider) || !Object.keys(providers).includes(this.provider)) { this.debug.error('Setup failed: Invalid provider'); return; } @@ -7188,9 +7136,9 @@ var Plyr = function () { this.media.plyr = this; // Wrap media - if (!utils.is.element(this.elements.container)) { - this.elements.container = utils.createElement('div'); - utils.wrap(this.media, this.elements.container); + if (!is.element(this.elements.container)) { + this.elements.container = createElement('div'); + wrap(this.media, this.elements.container); } // Allow focus to be captured @@ -7204,7 +7152,7 @@ var Plyr = function () { // Listen for events if debugging if (this.config.debug) { - utils.on(this.elements.container, this.config.events.join(' '), function (event) { + on(this.elements.container, this.config.events.join(' '), function (event) { _this.debug.log('event: ' + event.type); }); } @@ -7250,7 +7198,7 @@ var Plyr = function () { * Play the media, or play the advertisement (if they are not blocked) */ value: function play() { - if (!utils.is.function(this.media.play)) { + if (!is.function(this.media.play)) { return null; } @@ -7265,7 +7213,7 @@ var Plyr = function () { }, { key: 'pause', value: function pause() { - if (!this.playing || !utils.is.function(this.media.pause)) { + if (!this.playing || !is.function(this.media.pause)) { return; } @@ -7286,7 +7234,7 @@ var Plyr = function () { */ value: function togglePlay(input) { // Toggle based on current state if nothing passed - var toggle = utils.is.boolean(input) ? input : !this.playing; + var toggle = is.boolean(input) ? input : !this.playing; if (toggle) { this.play(); @@ -7305,7 +7253,7 @@ var Plyr = function () { if (this.isHTML5) { this.pause(); this.restart(); - } else if (utils.is.function(this.media.stop)) { + } else if (is.function(this.media.stop)) { this.media.stop(); } } @@ -7328,7 +7276,7 @@ var Plyr = function () { }, { key: 'rewind', value: function rewind(seekTime) { - this.currentTime = this.currentTime - (utils.is.number(seekTime) ? seekTime : this.config.seekTime); + this.currentTime = this.currentTime - (is.number(seekTime) ? seekTime : this.config.seekTime); } /** @@ -7339,7 +7287,7 @@ var Plyr = function () { }, { key: 'forward', value: function forward(seekTime) { - this.currentTime = this.currentTime + (utils.is.number(seekTime) ? seekTime : this.config.seekTime); + this.currentTime = this.currentTime + (is.number(seekTime) ? seekTime : this.config.seekTime); } /** @@ -7357,7 +7305,7 @@ var Plyr = function () { */ value: function increaseVolume(step) { var volume = this.media.muted ? 0 : this.volume; - this.volume = volume + (utils.is.number(step) ? step : 1); + this.volume = volume + (is.number(step) ? step : 1); } /** @@ -7369,7 +7317,7 @@ var Plyr = function () { key: 'decreaseVolume', value: function decreaseVolume(step) { var volume = this.media.muted ? 0 : this.volume; - this.volume = volume - (utils.is.number(step) ? step : 1); + this.volume = volume - (is.number(step) ? step : 1); } /** @@ -7392,18 +7340,18 @@ var Plyr = function () { } // If the method is called without parameter, toggle based on current value - var active = utils.is.boolean(input) ? input : !this.elements.container.classList.contains(this.config.classNames.captions.active); + var active = is.boolean(input) ? input : !this.elements.container.classList.contains(this.config.classNames.captions.active); // Toggle state - utils.toggleState(this.elements.buttons.captions, active); + toggleState(this.elements.buttons.captions, active); // Add class hook - utils.toggleClass(this.elements.container, this.config.classNames.captions.active, active); + toggleClass(this.elements.container, this.config.classNames.captions.active, active); // Update state and trigger event if (active !== this.captions.active) { this.captions.active = active; - utils.dispatchEvent.call(this, this.media, this.captions.active ? 'captionsenabled' : 'captionsdisabled'); + trigger.call(this, this.media, this.captions.active ? 'captionsenabled' : 'captionsdisabled'); } } @@ -7438,22 +7386,22 @@ var Plyr = function () { // Don't toggle if missing UI support or if it's audio if (this.supported.ui && !this.isAudio) { // Get state before change - var isHidden = utils.hasClass(this.elements.container, this.config.classNames.hideControls); + var isHidden = hasClass(this.elements.container, this.config.classNames.hideControls); // Negate the argument if not undefined since adding the class to hides the controls var force = typeof toggle === 'undefined' ? undefined : !toggle; // Apply and get updated state - var hiding = utils.toggleClass(this.elements.container, this.config.classNames.hideControls, force); + var hiding = toggleClass(this.elements.container, this.config.classNames.hideControls, force); // Close menu - if (hiding && this.config.controls.includes('settings') && !utils.is.empty(this.config.settings)) { + if (hiding && this.config.controls.includes('settings') && !is.empty(this.config.settings)) { controls.toggleMenu.call(this, false); } // Trigger event on change if (hiding !== isHidden) { var eventName = hiding ? 'controlshidden' : 'controlsshown'; - utils.dispatchEvent.call(this, this.media, eventName); + trigger.call(this, this.media, eventName); } return !hiding; } @@ -7468,8 +7416,8 @@ var Plyr = function () { }, { key: 'on', - value: function on(event, callback) { - utils.on(this.elements.container, event, callback); + value: function on$$1(event, callback) { + on(this.elements.container, event, callback); } /** @@ -7480,8 +7428,8 @@ var Plyr = function () { }, { key: 'off', - value: function off(event, callback) { - utils.off(this.elements.container, event, callback); + value: function off$$1(event, callback) { + off(this.elements.container, event, callback); } /** @@ -7514,10 +7462,10 @@ var Plyr = function () { if (soft) { if (Object.keys(_this2.elements).length) { // Remove elements - utils.removeElement(_this2.elements.buttons.play); - utils.removeElement(_this2.elements.captions); - utils.removeElement(_this2.elements.controls); - utils.removeElement(_this2.elements.wrapper); + removeElement(_this2.elements.buttons.play); + removeElement(_this2.elements.captions); + removeElement(_this2.elements.controls); + removeElement(_this2.elements.wrapper); // Clear for GC _this2.elements.buttons.play = null; @@ -7527,7 +7475,7 @@ var Plyr = function () { } // Callback - if (utils.is.function(callback)) { + if (is.function(callback)) { callback(); } } else { @@ -7535,13 +7483,13 @@ var Plyr = function () { _this2.listeners.clear(); // Replace the container with the original element provided - utils.replaceElement(_this2.elements.original, _this2.elements.container); + replaceElement(_this2.elements.original, _this2.elements.container); // Event - utils.dispatchEvent.call(_this2, _this2.elements.original, 'destroyed', true); + trigger.call(_this2, _this2.elements.original, 'destroyed', true); // Callback - if (utils.is.function(callback)) { + if (is.function(callback)) { callback.call(_this2.elements.original); } @@ -7580,7 +7528,7 @@ var Plyr = function () { clearInterval(this.timers.playing); // Destroy YouTube API - if (this.embed !== null && utils.is.function(this.embed.destroy)) { + if (this.embed !== null && is.function(this.embed.destroy)) { this.embed.destroy(); } @@ -7698,7 +7646,7 @@ var Plyr = function () { } // Validate input - var inputIsValid = utils.is.number(input) && input > 0; + var inputIsValid = is.number(input) && input > 0; // Set this.media.currentTime = inputIsValid ? Math.min(input, this.duration) : 0; @@ -7726,7 +7674,7 @@ var Plyr = function () { // YouTube / Vimeo return a float between 0-1 - if (utils.is.number(buffered)) { + if (is.number(buffered)) { return buffered; } @@ -7779,17 +7727,17 @@ var Plyr = function () { var max = 1; var min = 0; - if (utils.is.string(volume)) { + if (is.string(volume)) { volume = Number(volume); } // Load volume from storage if no value specified - if (!utils.is.number(volume)) { + if (!is.number(volume)) { volume = this.storage.get('volume'); } // Use config if all else fails - if (!utils.is.number(volume)) { + if (!is.number(volume)) { volume = this.config.volume; } @@ -7809,7 +7757,7 @@ var Plyr = function () { this.media.volume = volume; // If muted, and we're increasing volume manually, reset muted state - if (!utils.is.empty(value) && this.muted && volume > 0) { + if (!is.empty(value) && this.muted && volume > 0) { this.muted = false; } } @@ -7827,12 +7775,12 @@ var Plyr = function () { var toggle = mute; // Load muted state from storage - if (!utils.is.boolean(toggle)) { + if (!is.boolean(toggle)) { toggle = this.storage.get('muted'); } // Use config if all else fails - if (!utils.is.boolean(toggle)) { + if (!is.boolean(toggle)) { toggle = this.config.muted; } @@ -7881,15 +7829,15 @@ var Plyr = function () { set: function set$$1(input) { var speed = null; - if (utils.is.number(input)) { + if (is.number(input)) { speed = input; } - if (!utils.is.number(speed)) { + if (!is.number(speed)) { speed = this.storage.get('speed'); } - if (!utils.is.number(speed)) { + if (!is.number(speed)) { speed = this.config.speed.selected; } @@ -7932,19 +7880,19 @@ var Plyr = function () { set: function set$$1(input) { var quality = null; - if (!utils.is.empty(input)) { + if (!is.empty(input)) { quality = Number(input); } - if (!utils.is.number(quality)) { + if (!is.number(quality)) { quality = this.storage.get('quality'); } - if (!utils.is.number(quality)) { + if (!is.number(quality)) { quality = this.config.quality.selected; } - if (!utils.is.number(quality)) { + if (!is.number(quality)) { quality = this.config.quality.default; } @@ -7953,9 +7901,9 @@ var Plyr = function () { } if (!this.options.quality.includes(quality)) { - var closest = utils.closest(this.options.quality, quality); - this.debug.warn('Unsupported quality option: ' + quality + ', using ' + closest + ' instead'); - quality = closest; + var value = closest(this.options.quality, quality); + this.debug.warn('Unsupported quality option: ' + quality + ', using ' + value + ' instead'); + quality = value; } // Update config @@ -7982,7 +7930,7 @@ var Plyr = function () { }, { key: 'loop', set: function set$$1(input) { - var toggle = utils.is.boolean(input) ? input : this.config.loop.active; + var toggle = is.boolean(input) ? input : this.config.loop.active; this.config.loop.active = toggle; this.media.loop = toggle; @@ -8088,7 +8036,7 @@ var Plyr = function () { }, { key: 'autoplay', set: function set$$1(input) { - var toggle = utils.is.boolean(input) ? input : this.config.autoplay; + var toggle = is.boolean(input) ? input : this.config.autoplay; this.config.autoplay = toggle; } @@ -8157,7 +8105,7 @@ var Plyr = function () { } // Toggle based on current state if not passed - var toggle = utils.is.boolean(input) ? input : this.pip === states.inline; + var toggle = is.boolean(input) ? input : this.pip === states.inline; // Toggle based on current state this.media.webkitSetPresentationMode(toggle ? states.pip : states.inline); @@ -8189,7 +8137,7 @@ var Plyr = function () { }, { key: 'loadSprite', value: function loadSprite(url, id) { - return utils.loadSprite(url, id); + return loadScript(url, id); } /** @@ -8205,15 +8153,15 @@ var Plyr = function () { var targets = null; - if (utils.is.string(selector)) { + if (is.string(selector)) { targets = Array.from(document.querySelectorAll(selector)); - } else if (utils.is.nodeList(selector)) { + } else if (is.nodeList(selector)) { targets = Array.from(selector); - } else if (utils.is.array(selector)) { - targets = selector.filter(utils.is.element); + } else if (is.array(selector)) { + targets = selector.filter(is.element); } - if (utils.is.empty(targets)) { + if (is.empty(targets)) { return null; } @@ -8225,7 +8173,7 @@ var Plyr = function () { return Plyr; }(); -Plyr.defaults = utils.cloneDeep(defaults$1); +Plyr.defaults = cloneDeep(defaults$1); return Plyr; |