plyr
Version:
A simple, accessible and customizable HTML5, YouTube and Vimeo media player
1,674 lines (1,321 loc) • 303 kB
JavaScript
typeof navigator === "object" && (function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define('Plyr', factory) :
(global = global || self, global.Plyr = factory());
}(this, function () { 'use strict';
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
function _defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
function _createClass(Constructor, protoProps, staticProps) {
if (protoProps) _defineProperties(Constructor.prototype, protoProps);
if (staticProps) _defineProperties(Constructor, staticProps);
return Constructor;
}
function _defineProperty(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
}
function _slicedToArray(arr, i) {
return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest();
}
function _toConsumableArray(arr) {
return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _nonIterableSpread();
}
function _arrayWithoutHoles(arr) {
if (Array.isArray(arr)) {
for (var i = 0, arr2 = new Array(arr.length); i < arr.length; i++) arr2[i] = arr[i];
return arr2;
}
}
function _arrayWithHoles(arr) {
if (Array.isArray(arr)) return arr;
}
function _iterableToArray(iter) {
if (Symbol.iterator in Object(iter) || Object.prototype.toString.call(iter) === "[object Arguments]") return Array.from(iter);
}
function _iterableToArrayLimit(arr, i) {
var _arr = [];
var _n = true;
var _d = false;
var _e = undefined;
try {
for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) {
_arr.push(_s.value);
if (i && _arr.length === i) break;
}
} catch (err) {
_d = true;
_e = err;
} finally {
try {
if (!_n && _i["return"] != null) _i["return"]();
} finally {
if (_d) throw _e;
}
}
return _arr;
}
function _nonIterableSpread() {
throw new TypeError("Invalid attempt to spread non-iterable instance");
}
function _nonIterableRest() {
throw new TypeError("Invalid attempt to destructure non-iterable instance");
}
var defaults = {
addCSS: true,
// Add CSS to the element to improve usability (required here or in your CSS!)
thumbWidth: 15,
// The width of the thumb handle
watch: true // Watch for new elements that match a string target
};
// Element matches a selector
function matches(element, selector) {
function match() {
return Array.from(document.querySelectorAll(selector)).includes(this);
}
var matches = match;
return matches.call(element, selector);
}
// Trigger event
function trigger(element, type) {
if (!element || !type) {
return;
} // Create and dispatch the event
var event = new Event(type); // Dispatch the event
element.dispatchEvent(event);
}
// ==========================================================================
// Type checking utils
// ==========================================================================
var getConstructor = function getConstructor(input) {
return input !== null && typeof input !== 'undefined' ? input.constructor : null;
};
var instanceOf = function instanceOf(input, constructor) {
return Boolean(input && constructor && input instanceof constructor);
};
var isNullOrUndefined = function isNullOrUndefined(input) {
return input === null || typeof input === 'undefined';
};
var isObject = function isObject(input) {
return getConstructor(input) === Object;
};
var isNumber = function isNumber(input) {
return getConstructor(input) === Number && !Number.isNaN(input);
};
var isString = function isString(input) {
return getConstructor(input) === String;
};
var isBoolean = function isBoolean(input) {
return getConstructor(input) === Boolean;
};
var isFunction = function isFunction(input) {
return getConstructor(input) === Function;
};
var isArray = function isArray(input) {
return Array.isArray(input);
};
var isNodeList = function isNodeList(input) {
return instanceOf(input, NodeList);
};
var isElement = function isElement(input) {
return instanceOf(input, Element);
};
var isEvent = function isEvent(input) {
return instanceOf(input, Event);
};
var isEmpty = function isEmpty(input) {
return isNullOrUndefined(input) || (isString(input) || isArray(input) || isNodeList(input)) && !input.length || isObject(input) && !Object.keys(input).length;
};
var is = {
nullOrUndefined: isNullOrUndefined,
object: isObject,
number: isNumber,
string: isString,
boolean: isBoolean,
function: isFunction,
array: isArray,
nodeList: isNodeList,
element: isElement,
event: isEvent,
empty: isEmpty
};
// Get the number of decimal places
function getDecimalPlaces(value) {
var match = "".concat(value).match(/(?:\.(\d+))?(?:[eE]([+-]?\d+))?$/);
if (!match) {
return 0;
}
return Math.max(0, // Number of digits right of decimal point.
(match[1] ? match[1].length : 0) - ( // Adjust for scientific notation.
match[2] ? +match[2] : 0));
} // Round to the nearest step
function round(number, step) {
if (step < 1) {
var places = getDecimalPlaces(step);
return parseFloat(number.toFixed(places));
}
return Math.round(number / step) * step;
}
var RangeTouch =
/*#__PURE__*/
function () {
/**
* Setup a new instance
* @param {String|Element} target
* @param {Object} options
*/
function RangeTouch(target, options) {
_classCallCheck(this, RangeTouch);
if (is.element(target)) {
// An Element is passed, use it directly
this.element = target;
} else if (is.string(target)) {
// A CSS Selector is passed, fetch it from the DOM
this.element = document.querySelector(target);
}
if (!is.element(this.element) || !is.empty(this.element.rangeTouch)) {
return;
}
this.config = Object.assign({}, defaults, options);
this.init();
}
_createClass(RangeTouch, [{
key: "init",
value: function init() {
// Bail if not a touch enabled device
if (!RangeTouch.enabled) {
return;
} // Add useful CSS
if (this.config.addCSS) {
// TODO: Restore original values on destroy
this.element.style.userSelect = 'none';
this.element.style.webKitUserSelect = 'none';
this.element.style.touchAction = 'manipulation';
}
this.listeners(true);
this.element.rangeTouch = this;
}
}, {
key: "destroy",
value: function destroy() {
// Bail if not a touch enabled device
if (!RangeTouch.enabled) {
return;
}
this.listeners(false);
this.element.rangeTouch = null;
}
}, {
key: "listeners",
value: function listeners(toggle) {
var _this = this;
var method = toggle ? 'addEventListener' : 'removeEventListener'; // Listen for events
['touchstart', 'touchmove', 'touchend'].forEach(function (type) {
_this.element[method](type, function (event) {
return _this.set(event);
}, false);
});
}
/**
* Get the value based on touch position
* @param {Event} event
*/
}, {
key: "get",
value: function get(event) {
if (!RangeTouch.enabled || !is.event(event)) {
return null;
}
var input = event.target;
var touch = event.changedTouches[0];
var min = parseFloat(input.getAttribute('min')) || 0;
var max = parseFloat(input.getAttribute('max')) || 100;
var step = parseFloat(input.getAttribute('step')) || 1;
var delta = max - min; // Calculate percentage
var percent;
var clientRect = input.getBoundingClientRect();
var thumbWidth = 100 / clientRect.width * (this.config.thumbWidth / 2) / 100; // Determine left percentage
percent = 100 / clientRect.width * (touch.clientX - clientRect.left); // Don't allow outside bounds
if (percent < 0) {
percent = 0;
} else if (percent > 100) {
percent = 100;
} // Factor in the thumb offset
if (percent < 50) {
percent -= (100 - percent * 2) * thumbWidth;
} else if (percent > 50) {
percent += (percent - 50) * 2 * thumbWidth;
} // Find the closest step to the mouse position
return min + round(delta * (percent / 100), step);
}
/**
* Update range value based on position
* @param {Event} event
*/
}, {
key: "set",
value: function set(event) {
if (!RangeTouch.enabled || !is.event(event) || event.target.disabled) {
return;
} // Prevent text highlight on iOS
event.preventDefault(); // Set value
event.target.value = this.get(event); // Trigger event
trigger(event.target, event.type === 'touchend' ? 'change' : 'input');
}
}], [{
key: "setup",
/**
* Setup multiple instances
* @param {String|Element|NodeList|Array} target
* @param {Object} options
*/
value: function setup(target) {
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
var targets = null;
if (is.empty(target) || is.string(target)) {
targets = Array.from(document.querySelectorAll(is.string(target) ? target : 'input[type="range"]'));
} else if (is.element(target)) {
targets = [target];
} else if (is.nodeList(target)) {
targets = Array.from(target);
} else if (is.array(target)) {
targets = target.filter(is.element);
}
if (is.empty(targets)) {
return null;
}
var config = Object.assign({}, defaults, options);
if (is.string(target) && config.watch) {
// Create an observer instance
var observer = new MutationObserver(function (mutations) {
Array.from(mutations).forEach(function (mutation) {
Array.from(mutation.addedNodes).forEach(function (node) {
if (!is.element(node) || !matches(node, target)) {
return;
} // eslint-disable-next-line no-unused-vars
var range = new RangeTouch(node, config);
});
});
}); // Pass in the target node, as well as the observer options
observer.observe(document.body, {
childList: true,
subtree: true
});
}
return targets.map(function (t) {
return new RangeTouch(t, options);
});
}
}, {
key: "enabled",
get: function get() {
return 'ontouchstart' in document.documentElement;
}
}]);
return RangeTouch;
}();
// ==========================================================================
// Type checking utils
// ==========================================================================
var getConstructor$1 = function getConstructor(input) {
return input !== null && typeof input !== 'undefined' ? input.constructor : null;
};
var instanceOf$1 = function instanceOf(input, constructor) {
return Boolean(input && constructor && input instanceof constructor);
};
var isNullOrUndefined$1 = function isNullOrUndefined(input) {
return input === null || typeof input === 'undefined';
};
var isObject$1 = function isObject(input) {
return getConstructor$1(input) === Object;
};
var isNumber$1 = function isNumber(input) {
return getConstructor$1(input) === Number && !Number.isNaN(input);
};
var isString$1 = function isString(input) {
return getConstructor$1(input) === String;
};
var isBoolean$1 = function isBoolean(input) {
return getConstructor$1(input) === Boolean;
};
var isFunction$1 = function isFunction(input) {
return getConstructor$1(input) === Function;
};
var isArray$1 = function isArray(input) {
return Array.isArray(input);
};
var isWeakMap = function isWeakMap(input) {
return instanceOf$1(input, WeakMap);
};
var isNodeList$1 = function isNodeList(input) {
return instanceOf$1(input, NodeList);
};
var isElement$1 = function isElement(input) {
return instanceOf$1(input, Element);
};
var isTextNode = function isTextNode(input) {
return getConstructor$1(input) === Text;
};
var isEvent$1 = function isEvent(input) {
return instanceOf$1(input, Event);
};
var isKeyboardEvent = function isKeyboardEvent(input) {
return instanceOf$1(input, KeyboardEvent);
};
var isCue = function isCue(input) {
return instanceOf$1(input, window.TextTrackCue) || instanceOf$1(input, window.VTTCue);
};
var isTrack = function isTrack(input) {
return instanceOf$1(input, TextTrack) || !isNullOrUndefined$1(input) && isString$1(input.kind);
};
var isPromise = function isPromise(input) {
return instanceOf$1(input, Promise);
};
var isEmpty$1 = function isEmpty(input) {
return isNullOrUndefined$1(input) || (isString$1(input) || isArray$1(input) || isNodeList$1(input)) && !input.length || isObject$1(input) && !Object.keys(input).length;
};
var isUrl = function isUrl(input) {
// Accept a URL object
if (instanceOf$1(input, window.URL)) {
return true;
} // Must be string from here
if (!isString$1(input)) {
return false;
} // Add the protocol if required
var string = input;
if (!input.startsWith('http://') || !input.startsWith('https://')) {
string = "http://".concat(input);
}
try {
return !isEmpty$1(new URL(string).hostname);
} catch (e) {
return false;
}
};
var is$1 = {
nullOrUndefined: isNullOrUndefined$1,
object: isObject$1,
number: isNumber$1,
string: isString$1,
boolean: isBoolean$1,
function: isFunction$1,
array: isArray$1,
weakMap: isWeakMap,
nodeList: isNodeList$1,
element: isElement$1,
textNode: isTextNode,
event: isEvent$1,
keyboardEvent: isKeyboardEvent,
cue: isCue,
track: isTrack,
promise: isPromise,
url: isUrl,
empty: isEmpty$1
};
// ==========================================================================
var transitionEndEvent = function () {
var element = document.createElement('span');
var events = {
WebkitTransition: 'webkitTransitionEnd',
MozTransition: 'transitionend',
OTransition: 'oTransitionEnd otransitionend',
transition: 'transitionend'
};
var type = Object.keys(events).find(function (event) {
return element.style[event] !== undefined;
});
return is$1.string(type) ? events[type] : false;
}(); // Force repaint of element
function repaint(element, delay) {
setTimeout(function () {
try {
// eslint-disable-next-line no-param-reassign
element.hidden = true; // eslint-disable-next-line no-unused-expressions
element.offsetHeight; // eslint-disable-next-line no-param-reassign
element.hidden = false;
} catch (e) {// Do nothing
}
}, delay);
}
// ==========================================================================
// Browser sniffing
// Unfortunately, due to mixed support, UA sniffing is required
// ==========================================================================
var browser = {
isIE:
/* @cc_on!@ */
!!document.documentMode,
isEdge: window.navigator.userAgent.includes('Edge'),
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)
};
// ==========================================================================
// https://github.com/WICG/EventListenerOptions/blob/gh-pages/explainer.md
// https://www.youtube.com/watch?v=NPM6172J22g
var supportsPassiveListeners = function () {
// Test via a getter in the options object to see if the passive property is accessed
var supported = false;
try {
var options = Object.defineProperty({}, 'passive', {
get: function get() {
supported = true;
return null;
}
});
window.addEventListener('test', null, options);
window.removeEventListener('test', null, options);
} catch (e) {// Do nothing
}
return supported;
}(); // Toggle event listener
function toggleListener(element, event, callback) {
var _this = this;
var toggle = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
var passive = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : true;
var capture = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : false;
// Bail if no element, event, or callback
if (!element || !('addEventListener' in element) || is$1.empty(event) || !is$1.function(callback)) {
return;
} // Allow multiple events
var events = event.split(' '); // Build options
// Default to just the capture boolean for browsers with no passive listener support
var options = capture; // If passive events listeners are supported
if (supportsPassiveListeners) {
options = {
// Whether the listener can be passive (i.e. default never prevented)
passive: passive,
// Whether the listener is a capturing listener or not
capture: capture
};
} // If a single node is passed, bind the event listener
events.forEach(function (type) {
if (_this && _this.eventListeners && toggle) {
// Cache event listener
_this.eventListeners.push({
element: element,
type: type,
callback: callback,
options: options
});
}
element[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.length > 2 ? arguments[2] : undefined;
var passive = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : true;
var capture = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false;
toggleListener.call(this, 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.length > 2 ? arguments[2] : undefined;
var passive = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : true;
var capture = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false;
toggleListener.call(this, element, events, callback, false, passive, capture);
} // Bind once-only event handler
function once(element) {
var _this2 = this;
var events = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';
var callback = arguments.length > 2 ? arguments[2] : undefined;
var passive = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : true;
var capture = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false;
var onceCallback = function onceCallback() {
off(element, events, onceCallback, passive, capture);
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
callback.apply(_this2, args);
};
toggleListener.call(this, element, events, onceCallback, true, passive, capture);
} // Trigger event
function triggerEvent(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$1.element(element) || is$1.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);
} // Unbind all cached event listeners
function unbindListeners() {
if (this && this.eventListeners) {
this.eventListeners.forEach(function (item) {
var element = item.element,
type = item.type,
callback = item.callback,
options = item.options;
element.removeEventListener(type, callback, options);
});
this.eventListeners = [];
}
} // Run method when / if player is ready
function ready() {
var _this3 = this;
return new Promise(function (resolve) {
return _this3.ready ? setTimeout(resolve, 0) : on.call(_this3, _this3.elements.container, 'ready', resolve);
}).then(function () {});
}
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 = new 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$1.object(source)) {
return target;
}
Object.keys(source).forEach(function (key) {
if (is$1.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(void 0, [target].concat(sources));
}
function wrap(elements, wrapper) {
// Convert `elements` to an array, if necessary.
var targets = elements.length ? elements : [elements]; // Loops backwards to prevent having to clone the wrapper on the
// first element (see `child` below).
Array.from(targets).reverse().forEach(function (element, index) {
var child = index > 0 ? wrapper.cloneNode(true) : wrapper; // Cache the current parent and sibling.
var parent = element.parentNode;
var sibling = element.nextSibling; // Wrap the element (is automatically removed from its current
// parent).
child.appendChild(element); // If the element had a sibling, insert the wrapper before
// the sibling to maintain the HTML structure; otherwise, just
// append it to the parent.
if (sibling) {
parent.insertBefore(child, sibling);
} else {
parent.appendChild(child);
}
});
} // Set attributes
function setAttributes(element, attributes) {
if (!is$1.element(element) || is$1.empty(attributes)) {
return;
} // Assume null and undefined attributes should be left out,
// Setting them would otherwise convert them to "null" and "undefined"
Object.entries(attributes).filter(function (_ref) {
var _ref2 = _slicedToArray(_ref, 2),
value = _ref2[1];
return !is$1.nullOrUndefined(value);
}).forEach(function (_ref3) {
var _ref4 = _slicedToArray(_ref3, 2),
key = _ref4[0],
value = _ref4[1];
return element.setAttribute(key, value);
});
} // Create a DocumentFragment
function createElement(type, attributes, text) {
// Create a new <element>
var element = document.createElement(type); // Set all passed attributes
if (is$1.object(attributes)) {
setAttributes(element, attributes);
} // Add text node
if (is$1.string(text)) {
element.innerText = text;
} // Return built element
return element;
} // Inaert an element after another
function insertAfter(element, target) {
if (!is$1.element(element) || !is$1.element(target)) {
return;
}
target.parentNode.insertBefore(element, target.nextSibling);
} // Insert a DocumentFragment
function insertElement(type, parent, attributes, text) {
if (!is$1.element(parent)) {
return;
}
parent.appendChild(createElement(type, attributes, text));
} // Remove element(s)
function removeElement(element) {
if (is$1.nodeList(element) || is$1.array(element)) {
Array.from(element).forEach(removeElement);
return;
}
if (!is$1.element(element) || !is$1.element(element.parentNode)) {
return;
}
element.parentNode.removeChild(element);
} // Remove all child elements
function emptyElement(element) {
if (!is$1.element(element)) {
return;
}
var length = element.childNodes.length;
while (length > 0) {
element.removeChild(element.lastChild);
length -= 1;
}
} // Replace element
function replaceElement(newChild, oldChild) {
if (!is$1.element(oldChild) || !is$1.element(oldChild.parentNode) || !is$1.element(newChild)) {
return null;
}
oldChild.parentNode.replaceChild(newChild, oldChild);
return newChild;
} // Get an attribute object from a string selector
function getAttributesFromSelector(sel, existingAttributes) {
// For example:
// '.test' to { class: 'test' }
// '#test' to { id: 'test' }
// '[data-test="test"]' to { 'data-test': 'test' }
if (!is$1.string(sel) || is$1.empty(sel)) {
return {};
}
var attributes = {};
var existing = extend({}, 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 _parts = _slicedToArray(parts, 1),
key = _parts[0];
var value = parts.length > 1 ? parts[1].replace(/["']/g, '') : ''; // Get the first character
var start = selector.charAt(0);
switch (start) {
case '.':
// Add to existing classname
if (is$1.string(existing.class)) {
attributes.class = "".concat(existing.class, " ").concat(className);
} else {
attributes.class = className;
}
break;
case '#':
// ID selector
attributes.id = selector.replace('#', '');
break;
case '[':
// Attribute selector
attributes[key] = value;
break;
default:
break;
}
});
return extend(existing, attributes);
} // Toggle hidden
function toggleHidden(element, hidden) {
if (!is$1.element(element)) {
return;
}
var hide = hidden;
if (!is$1.boolean(hide)) {
hide = !element.hidden;
} // eslint-disable-next-line no-param-reassign
element.hidden = hide;
} // Mirror Element.classList.toggle, with IE compatibility for "force" argument
function toggleClass(element, className, force) {
if (is$1.nodeList(element)) {
return Array.from(element).map(function (e) {
return toggleClass(e, className, force);
});
}
if (is$1.element(element)) {
var method = 'toggle';
if (typeof force !== 'undefined') {
method = force ? 'add' : 'remove';
}
element.classList[method](className);
return element.classList.contains(className);
}
return false;
} // Has class name
function hasClass(element, className) {
return is$1.element(element) && element.classList.contains(className);
} // Element matches selector
function matches$1(element, selector) {
function match() {
return Array.from(document.querySelectorAll(selector)).includes(this);
}
var method = match;
return method.call(element, selector);
} // Find all elements
function getElements(selector) {
return this.elements.container.querySelectorAll(selector);
} // Find a single element
function getElement(selector) {
return this.elements.container.querySelector(selector);
} // Trap focus inside container
function trapFocus() {
var element = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
var toggle = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
if (!is$1.element(element)) {
return;
}
var focusable = getElements.call(this, 'button:not(:disabled), input:not(:disabled), [tabindex]');
var first = focusable[0];
var last = focusable[focusable.length - 1];
var trap = function trap(event) {
// Bail if not tab key or not fullscreen
if (event.key !== 'Tab' || event.keyCode !== 9) {
return;
} // Get the current focused element
var focused = document.activeElement;
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();
}
};
toggleListener.call(this, this.elements.container, 'keydown', trap, toggle, false);
} // Set focus and tab focus class
function setFocus() {
var element = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
var tabFocus = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
if (!is$1.element(element)) {
return;
} // Set regular focus
element.focus({
preventScroll: true
}); // If we want to mimic keyboard focus via tab
if (tabFocus) {
toggleClass(element, this.config.classNames.tabFocus);
}
}
var defaultCodecs = {
'audio/ogg': 'vorbis',
'audio/wav': '1',
'video/webm': 'vp8, vorbis',
'video/mp4': 'avc1.42E01E, mp4a.40.2',
'video/ogg': 'theora'
}; // Check for feature support
var support = {
// Basic support
audio: 'canPlayType' in document.createElement('audio'),
video: 'canPlayType' in document.createElement('video'),
// Check for support
// Basic functionality vs full UI
check: function check(type, provider, playsinline) {
var canPlayInline = browser.isIPhone && playsinline && support.playsinline;
var api = support[type] || provider !== 'html5';
var ui = api && support.rangeInput && (type !== 'video' || !browser.isIPhone || canPlayInline);
return {
api: api,
ui: ui
};
},
// Picture-in-picture support
// Safari & Chrome only currently
pip: function () {
if (browser.isIPhone) {
return false;
} // Safari
// https://developer.apple.com/documentation/webkitjs/adding_picture_in_picture_to_your_safari_media_controls
if (is$1.function(createElement('video').webkitSetPresentationMode)) {
return true;
} // Chrome
// https://developers.google.com/web/updates/2018/10/watch-video-using-picture-in-picture
if (document.pictureInPictureEnabled && !createElement('video').disablePictureInPicture) {
return true;
}
return false;
}(),
// Airplay support
// Safari only currently
airplay: is$1.function(window.WebKitPlaybackTargetAvailabilityEvent),
// Inline playback support
// https://webkit.org/blog/6784/new-video-policies-for-ios/
playsinline: 'playsInline' in document.createElement('video'),
// Check for mime type support against a player instance
// Credits: http://diveintohtml5.info/everything.html
// Related: http://www.leanbackplayer.com/test/h5mt.html
mime: function mime(input) {
if (is$1.empty(input)) {
return false;
}
var _input$split = input.split('/'),
_input$split2 = _slicedToArray(_input$split, 1),
mediaType = _input$split2[0];
var type = input; // Verify we're using HTML5 and there's no media type mismatch
if (!this.isHTML5 || mediaType !== this.type) {
return false;
} // Add codec if required
if (Object.keys(defaultCodecs).includes(type)) {
type += "; codecs=\"".concat(defaultCodecs[input], "\"");
}
try {
return Boolean(type && this.media.canPlayType(type).replace(/no/, ''));
} catch (e) {
return false;
}
},
// Check for textTracks support
textTracks: 'textTracks' in document.createElement('video'),
// <input type="range"> Sliders
rangeInput: function () {
var range = document.createElement('input');
range.type = 'range';
return range.type === 'range';
}(),
// Touch
// NOTE: Remember a device can be mouse + touch enabled so we check on first touch event
touch: 'ontouchstart' in document.documentElement,
// Detect transitions support
transitions: transitionEndEvent !== false,
// Reduced motion iOS & MacOS setting
// https://webkit.org/blog/7551/responsive-design-for-motion/
reducedMotion: 'matchMedia' in window && window.matchMedia('(prefers-reduced-motion)').matches
};
function validateRatio(input) {
if (!is$1.array(input) && (!is$1.string(input) || !input.includes(':'))) {
return false;
}
var ratio = is$1.array(input) ? input : input.split(':');
return ratio.map(Number).every(is$1.number);
}
function reduceAspectRatio(ratio) {
if (!is$1.array(ratio) || !ratio.every(is$1.number)) {
return null;
}
var _ratio = _slicedToArray(ratio, 2),
width = _ratio[0],
height = _ratio[1];
var getDivider = function getDivider(w, h) {
return h === 0 ? w : getDivider(h, w % h);
};
var divider = getDivider(width, height);
return [width / divider, height / divider];
}
function getAspectRatio(input) {
var parse = function parse(ratio) {
return validateRatio(ratio) ? ratio.split(':').map(Number) : null;
}; // Try provided ratio
var ratio = parse(input); // Get from config
if (ratio === null) {
ratio = parse(this.config.ratio);
} // Get from embed
if (ratio === null && !is$1.empty(this.embed) && is$1.array(this.embed.ratio)) {
ratio = this.embed.ratio;
} // Get from HTML5 video
if (ratio === null && this.isHTML5) {
var _this$media = this.media,
videoWidth = _this$media.videoWidth,
videoHeight = _this$media.videoHeight;
ratio = reduceAspectRatio([videoWidth, videoHeight]);
}
return ratio;
} // Set aspect ratio for responsive container
function setAspectRatio(input) {
if (!this.isVideo) {
return {};
}
var ratio = getAspectRatio.call(this, input);
var _ref = is$1.array(ratio) ? ratio : [0, 0],
_ref2 = _slicedToArray(_ref, 2),
w = _ref2[0],
h = _ref2[1];
var padding = 100 / w * h;
this.elements.wrapper.style.paddingBottom = "".concat(padding, "%"); // For Vimeo we have an extra <div> to hide the standard controls and UI
if (this.isVimeo && this.supported.ui) {
var height = 240;
var offset = (height - padding) / (height / 50);
this.media.style.transform = "translateY(-".concat(offset, "%)");
} else if (this.isHTML5) {
this.elements.wrapper.classList.toggle(this.config.classNames.videoFixedRatio, ratio !== null);
}
return {
padding: padding,
ratio: ratio
};
}
// ==========================================================================
var html5 = {
getSources: function getSources() {
var _this = this;
if (!this.isHTML5) {
return [];
}
var sources = Array.from(this.media.querySelectorAll('source')); // Filter out unsupported sources (if type is specified)
return sources.filter(function (source) {
var type = source.getAttribute('type');
if (is$1.empty(type)) {
return true;
}
return support.mime.call(_this, type);
});
},
// Get quality levels
getQualityOptions: function getQualityOptions() {
// Get sizes from <source> elements
return html5.getSources.call(this).map(function (source) {
return Number(source.getAttribute('size'));
}).filter(Boolean);
},
extend: function extend() {
if (!this.isHTML5) {
return;
}
var player = this; // Set aspect ratio if fixed
if (!is$1.empty(this.config.ratio)) {
setAspectRatio.call(player);
} // Quality
Object.defineProperty(player.media, 'quality', {
get: function get() {
// Get sources
var sources = html5.getSources.call(player);
var source = sources.find(function (s) {
return s.getAttribute('src') === player.source;
}); // Return size, if match is found
return source && Number(source.getAttribute('size'));
},
set: function set(input) {
// Get sources
var sources = html5.getSources.call(player); // Get first match for requested size
var source = sources.find(function (s) {
return Number(s.getAttribute('size')) === input;
}); // No matching source found
if (!source) {
return;
} // Get current state
var _player$media = player.media,
currentTime = _player$media.currentTime,
paused = _player$media.paused,
preload = _player$media.preload,
readyState = _player$media.readyState; // Set new source
player.media.src = source.getAttribute('src'); // Prevent loading if preload="none" and the current source isn't loaded (#1044)
if (preload !== 'none' || readyState) {
// Restore time
player.once('loadedmetadata', function () {
player.currentTime = currentTime; // Resume playing
if (!paused) {
player.play();
}
}); // Load new source
player.media.load();
} // Trigger change event
triggerEvent.call(player, player.media, 'qualitychange', false, {
quality: input
});
}
});
},
// Cancel current network requests
// See https://github.com/sampotts/plyr/issues/174
cancelRequests: function cancelRequests() {
if (!this.isHTML5) {
return;
} // Remove child sources
removeElement(html5.getSources.call(this)); // Set blank video src attribute
// This is to prevent a MEDIA_ERR_SRC_NOT_SUPPORTED error
// Info: http://stackoverflow.com/questions/32231579/how-to-properly-dispose-of-an-html5-video-and-close-socket-or-connection
this.media.setAttribute('src', this.config.blankVideo); // Load the new empty source
// This will cancel existing requests
// See https://github.com/sampotts/plyr/issues/174
this.media.load(); // Debugging
this.debug.log('Cancelled network requests');
}
};
// ==========================================================================
function dedupe(array) {
if (!is$1.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$1.array(array) || !array.length) {
return null;
}
return array.reduce(function (prev, curr) {
return Math.abs(curr - value) < Math.abs(prev - value) ? curr : prev;
});
}
// ==========================================================================
function generateId(prefix) {
return "".concat(prefix, "-").concat(Math.floor(Math.random() * 10000));
} // Format string
function format(input) {
for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
args[_key - 1] = arguments[_key];
}
if (is$1.empty(input)) {
return input;
}
return input.toString().replace(/{(\d+)}/g, function (match, i) {
return args[i].toString();
});
} // 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 resources = {
pip: 'PIP',
airplay: 'AirPlay',
html5: 'HTML5',
vimeo: 'Vimeo',
youtube: 'YouTube'
};
var i18n = {
get: function get() {
var key = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
var config = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
if (is$1.empty(key) || is$1.empty(config)) {
return '';
}
var string = getDeep(config.i18n, key);
if (is$1.empty(string)) {
if (Object.keys(resources).includes(key)) {
return resources[key];
}
return '';
}
var replace = {
'{seektime}': config.seekTime,
'{title}': config.title
};
Object.entries(replace).forEach(function (_ref) {
var _ref2 = _slicedToArray(_ref, 2),
k = _ref2[0],
v = _ref2[1];
string = replaceAll(string, k, v);
});
return string;
}
};
var Storage =
/*#__PURE__*/
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(key) {
if (!Storage.supported || !this.enabled) {
return null;
}
var store = window.localStorage.getItem(this.key);
if (is$1.empty(store)) {
return null;
}
var json = JSON.parse(store);
return is$1.string(key) && key.length ? json[key] : json;
}
}, {
key: "set",
value: function set(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$1.object(object)) {
return;
} // Get current storage
var storage = this.get(); // Default to empty object
if (is$1.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() {
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;
}();
// ==========================================================================
// Fetch wrapper
// Using XHR to avoid issues with older browsers
// ==========================================================================
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.status);
});
request.open('GET', url, true); // Set the required response type
request.responseType = responseType;
request.send();
} catch (e) {
reject(e);
}
});
}
// ==========================================================================
function loadSprite(url, id) {
if (!is$1.string(url)) {
return;
}
var prefix = 'cache';
var hasId = is$1.string(id);
var isCached = false;
var exists = function exists() {
return document.getElementById(id) !== null;
};
var update = function update(container, data) {
// eslint-disable-next-line no-param-reassign
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("".concat(prefix, "-").concat(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$1.empty(result)) {
return;
}
if (useStorage) {
window.localStorage.setItem("".concat(prefix, "-").concat(id), JSON.stringify({
content: result
}));
}
update(container, result);
}).catch(function () {});
}
}
// ==========================================================================
var getHours = function getHours(value) {
return Math.trunc(value / 60 / 60 % 60, 10);
};
var getMinutes = function getMinutes(value) {
return Math.trunc(value / 60 % 60, 10);
};
var getSeconds = function getSeconds(value) {
return Math.trunc(value % 60, 10);
}; // Format time to UI friendly string
funct