typeof navigator === "object" && 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"); } // ========================================================================== // 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 isWeakMap = function isWeakMap(input) { return instanceOf(input, WeakMap); }; var isNodeList = function isNodeList(input) { return instanceOf(input, NodeList); }; var isElement = function isElement(input) { return instanceOf(input, Element); }; var isTextNode = function isTextNode(input) { return getConstructor(input) === Text; }; var isEvent = function isEvent(input) { return instanceOf(input, Event); }; var isKeyboardEvent = function isKeyboardEvent(input) { return instanceOf(input, KeyboardEvent); }; var isCue = function isCue(input) { return instanceOf(input, window.TextTrackCue) || instanceOf(input, window.VTTCue); }; var isTrack = function isTrack(input) { return instanceOf(input, TextTrack) || !isNullOrUndefined(input) && isString(input.kind); }; var isPromise = function isPromise(input) { return instanceOf(input, Promise); }; var isEmpty = function isEmpty(input) { return isNullOrUndefined(input) || (isString(input) || isArray(input) || isNodeList(input)) && !input.length || isObject(input) && !Object.keys(input).length; }; var isUrl = function isUrl(input) { // Accept a URL object if (instanceOf(input, window.URL)) { return true; } // Must be string from here if (!isString(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(new URL(string).hostname); } catch (e) { return false; } }; var is = { nullOrUndefined: isNullOrUndefined, object: isObject, number: isNumber, string: isString, boolean: isBoolean, function: isFunction, array: isArray, weakMap: isWeakMap, nodeList: isNodeList, element: isElement, textNode: isTextNode, event: isEvent, keyboardEvent: isKeyboardEvent, cue: isCue, track: isTrack, promise: isPromise, url: isUrl, empty: isEmpty }; // ========================================================================== // 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.empty(event) || !is.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.element(element) || is.empty(type)) { return; } // Create and dispatch the event var event = new CustomEvent(type, { bubbles: bubbles, detail: Object.assign({}, detail, { plyr: this }) }); // Dispatch the event element.dispatchEvent(event); } // 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 wrap(elements, wrapper) { // Convert `elements` to an array, if necessary. var targets = elements.length ? elements : [elements]; // Loops backwards to prevent having to clone the wrapper on the // first element (see `child` below). Array.from(targets).reverse().forEach(function (element, index) { var child = index > 0 ? wrapper.cloneNode(true) : wrapper; // Cache the current parent and sibling. var parent = element.parentNode; var sibling = element.nextSibling; // Wrap the element (is automatically removed from its current // parent). child.appendChild(element); // If the element had a sibling, insert the wrapper before // the sibling to maintain the HTML structure; otherwise, just // append it to the parent. if (sibling) { parent.insertBefore(child, sibling); } else { parent.appendChild(child); } }); } // Set attributes function setAttributes(element, attributes) { if (!is.element(element) || is.empty(attributes)) { return; } // 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.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 var element = document.createElement(type); // Set all passed attributes if (is.object(attributes)) { setAttributes(element, attributes); } // Add text node if (is.string(text)) { element.innerText = text; } // Return built element return element; } // Inaert an element after another function insertAfter(element, target) { if (!is.element(element) || !is.element(target)) { return; } target.parentNode.insertBefore(element, target.nextSibling); } // Insert a DocumentFragment function insertElement(type, parent, attributes, text) { if (!is.element(parent)) { return; } parent.appendChild(createElement(type, attributes, text)); } // Remove element(s) function removeElement(element) { if (is.nodeList(element) || is.array(element)) { Array.from(element).forEach(removeElement); return; } if (!is.element(element) || !is.element(element.parentNode)) { return; } element.parentNode.removeChild(element); } // Remove all child elements function emptyElement(element) { if (!is.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.element(oldChild) || !is.element(oldChild.parentNode) || !is.element(newChild)) { return null; } oldChild.parentNode.replaceChild(newChild, oldChild); return newChild; } // Get an attribute object from a string selector function getAttributesFromSelector(sel, existingAttributes) { // For example: // '.test' to { class: 'test' } // '#test' to { id: 'test' } // '[data-test="test"]' to { 'data-test': 'test' } if (!is.string(sel) || is.empty(sel)) { return {}; } var attributes = {}; var existing = existingAttributes; sel.split(',').forEach(function (s) { // Remove whitespace var selector = s.trim(); var className = selector.replace('.', ''); var stripped = selector.replace(/[[\]]/g, ''); // Get the parts and value var parts = stripped.split('='); var key = parts[0]; var value = parts.length > 1 ? parts[1].replace(/["']/g, '') : ''; // Get the first character var start = selector.charAt(0); switch (start) { case '.': // Add to existing classname if (is.object(existing) && is.string(existing.class)) { existing.class += " ".concat(className); } attributes.class = className; break; case '#': // ID selector attributes.id = selector.replace('#', ''); break; case '[': // Attribute selector attributes[key] = value; break; default: break; } }); return attributes; } // Toggle hidden function toggleHidden(element, hidden) { if (!is.element(element)) { return; } var hide = hidden; if (!is.boolean(hide)) { hide = !element.hidden; } if (hide) { element.setAttribute('hidden', ''); } else { element.removeAttribute('hidden'); } } // Mirror Element.classList.toggle, with IE compatibility for "force" argument function toggleClass(element, className, force) { if (is.nodeList(element)) { return Array.from(element).map(function (e) { return toggleClass(e, className, force); }); } if (is.element(element)) { var method = 'toggle'; if (typeof force !== 'undefined') { method = force ? 'add' : 'remove'; } element.classList[method](className); return element.classList.contains(className); } return false; } // Has class name function hasClass(element, className) { return is.element(element) && element.classList.contains(className); } // Element matches selector function matches(element, selector) { function match() { return Array.from(document.querySelectorAll(selector)).includes(this); } var matches = match; return matches.call(element, selector); } // Find all elements function getElements(selector) { return this.elements.container.querySelectorAll(selector); } // Find a single element function getElement(selector) { return this.elements.container.querySelector(selector); } // Trap focus inside container function trapFocus() { var element = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null; var toggle = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; if (!is.element(element)) { return; } var focusable = getElements.call(this, 'button:not(:disabled), input:not(:disabled), [tabindex]'); var first = focusable[0]; var last = focusable[focusable.length - 1]; var trap = function trap(event) { // Bail if not tab key or not fullscreen if (event.key !== 'Tab' || event.keyCode !== 9) { return; } // Get the current focused element var focused = 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.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 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.string(type) ? events[type] : false; }(); // Force repaint of element function repaint(element) { setTimeout(function () { try { toggleHidden(element, true); element.offsetHeight; // eslint-disable-line toggleHidden(element, false); } catch (e) {// Do nothing } }, 0); } // ========================================================================== // 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) }; 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.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.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.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'), // 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 }; // ========================================================================== 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.empty(type)) { return true; } return support.mime.call(_this, type); }); }, // Get quality levels getQualityOptions: function getQualityOptions() { // Get sizes from 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; // Quality Object.defineProperty(player.media, 'quality', { get: function get() { // Get sources var sources = html5.getSources.call(player); var source = sources.find(function (source) { return source.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 (source) { return Number(source.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.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; }); } 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.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(void 0, [target].concat(sources)); } // ========================================================================== 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.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.empty(key) || is.empty(config)) { return ''; } var string = getDeep(config.i18n, key); if (is.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), key = _ref2[0], value = _ref2[1]; string = replaceAll(string, key, value); }); 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.empty(store)) { return null; } var json = JSON.parse(store); return is.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.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() { 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.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("".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.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 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".concat(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 = "".concat(hours, ":"); } else { hours = ''; } // Render return "".concat(inverted && time > 0 ? '-' : '').concat(hours).concat(format(mins), ":").concat(format(secs)); } var controls = { // Get icon URL getIconUrl: function getIconUrl() { var url = new URL(this.config.iconUrl, window.location); var cors = url.host !== window.location.host || browser.isIE && !window.svg4everybody; return { url: this.config.iconUrl, cors: cors }; }, // Find the UI controls findElements: function findElements() { try { this.elements.controls = getElement.call(this, this.config.selectors.controls.wrapper); // Buttons this.elements.buttons = { 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 = getElement.call(this, this.config.selectors.progress); // Inputs this.elements.inputs = { seek: getElement.call(this, this.config.selectors.inputs.seek), volume: getElement.call(this, this.config.selectors.inputs.volume) }; // Display this.elements.display = { 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 (is.element(this.elements.progress)) { this.elements.display.seekTooltip = this.elements.progress.querySelector(".".concat(this.config.classNames.tooltip)); } return true; } catch (error) { // Log it this.debug.warn('It looks like there is a problem with your custom controls HTML', error); // Restore native video controls this.toggleNativeControls(true); return false; } }, // Create icon createIcon: function createIcon(type, attributes) { var namespace = 'http://www.w3.org/2000/svg'; var iconUrl = controls.getIconUrl.call(this); var iconPath = "".concat(!iconUrl.cors ? iconUrl.url : '', "#").concat(this.config.iconPrefix); // Create var icon = document.createElementNS(namespace, 'svg'); setAttributes(icon, extend(attributes, { role: 'presentation', focusable: 'false' })); // Create the to reference sprite var use = document.createElementNS(namespace, 'use'); var path = "".concat(iconPath, "-").concat(type); // Set `href` attributes // https://github.com/sampotts/plyr/issues/460 // https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/xlink:href if ('href' in use) { use.setAttributeNS('http://www.w3.org/1999/xlink', 'href', path); } // Always set the older attribute even though it's "deprecated" (it'll be around for ages) use.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', path); // Add to icon.appendChild(use); return icon; }, // Create hidden text label createLabel: function createLabel(key) { var attr = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; var text = i18n.get(key, this.config); var attributes = Object.assign({}, attr, { class: [attr.class, this.config.classNames.hidden].filter(Boolean).join(' ') }); return createElement('span', attributes, text); }, // Create a badge createBadge: function createBadge(text) { if (is.empty(text)) { return null; } var badge = createElement('span', { class: this.config.classNames.menu.value }); badge.appendChild(createElement('span', { class: this.config.classNames.menu.badge }, text)); return badge; }, // Create a
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, "%)"); } return { padding: padding, ratio: ratio }; } var Listeners = /*#__PURE__*/ function () { function Listeners(player) { _classCallCheck(this, Listeners); this.player = player; this.lastKey = null; this.focusTimer = null; this.lastKeyDown = null; this.handleKey = this.handleKey.bind(this); this.toggleMenu = this.toggleMenu.bind(this); this.setTabFocus = this.setTabFocus.bind(this); this.firstTouch = this.firstTouch.bind(this); } // Handle key presses _createClass(Listeners, [{ key: "handleKey", value: function handleKey(event) { var player = this.player; var elements = player.elements; var code = event.keyCode ? event.keyCode : event.which; var pressed = event.type === 'keydown'; var repeat = pressed && code === this.lastKey; // Bail if a modifier key is set if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) { return; } // If the event is bubbled from the media element // Firefox doesn't get the keycode for whatever reason if (!is.number(code)) { return; } // Seek by the number keys var seekByKey = function seekByKey() { // Divide the max duration into 10th's and times by the number value player.currentTime = player.duration / 10 * (code - 48); }; // Handle the key on keydown // Reset on keyup if (pressed) { // 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 = document.activeElement; if (is.element(focused)) { var editable = player.config.selectors.editable; var seek = elements.inputs.seek; if (focused !== seek && matches(focused, editable)) { return; } if (event.which === 32 && matches(focused, 'button, [role^="menuitem"]')) { return; } } // Which keycodes should we prevent default var preventDefault = [32, 37, 38, 39, 40, 48, 49, 50, 51, 52, 53, 54, 56, 57, 67, 70, 73, 75, 76, 77, 79]; // If the code is found prevent default (e.g. prevent scrolling for arrows) if (preventDefault.includes(code)) { event.preventDefault(); event.stopPropagation(); } switch (code) { case 48: case 49: case 50: case 51: case 52: case 53: case 54: case 55: case 56: case 57: // 0-9 if (!repeat) { seekByKey(); } break; case 32: case 75: // Space and K key if (!repeat) { player.togglePlay(); } break; case 38: // Arrow up player.increaseVolume(0.1); break; case 40: // Arrow down player.decreaseVolume(0.1); break; case 77: // M key if (!repeat) { player.muted = !player.muted; } break; case 39: // Arrow forward player.forward(); break; case 37: // Arrow back player.rewind(); break; case 70: // F key player.fullscreen.toggle(); break; case 67: // C key if (!repeat) { player.toggleCaptions(); } break; case 76: // L key player.loop = !player.loop; break; /* case 73: this.setLoop('start'); break; case 76: this.setLoop(); break; case 79: this.setLoop('end'); break; */ default: break; } // Escape is handle natively when in full screen // So we only need to worry about non native if (code === 27 && !player.fullscreen.usingNative && player.fullscreen.active) { player.fullscreen.toggle(); } // Store last code for next cycle this.lastKey = code; } else { this.lastKey = null; } } // Toggle menu }, { key: "toggleMenu", value: function toggleMenu(event) { controls.toggleMenu.call(this.player, event); } // Device is touch enabled }, { key: "firstTouch", value: function firstTouch() { var player = this.player; var elements = player.elements; player.touch = true; // Add touch class toggleClass(elements.container, player.config.classNames.isTouch, true); } }, { key: "setTabFocus", value: function setTabFocus(event) { var player = this.player; var elements = player.elements; clearTimeout(this.focusTimer); // Ignore any key other than tab if (event.type === 'keydown' && event.which !== 9) { return; } // Store reference to event timeStamp if (event.type === 'keydown') { this.lastKeyDown = event.timeStamp; } // Remove current classes var removeCurrent = function removeCurrent() { var className = player.config.classNames.tabFocus; var current = getElements.call(player, ".".concat(className)); toggleClass(current, className, false); }; // Determine if a key was pressed to trigger this event var wasKeyDown = event.timeStamp - this.lastKeyDown <= 20; // Ignore focus events if a key was pressed prior if (event.type === 'focus' && !wasKeyDown) { return; } // Remove all current removeCurrent(); // Delay the adding of classname until the focus has changed // This event fires before the focusin event this.focusTimer = setTimeout(function () { var focused = document.activeElement; // Ignore if current focus element isn't inside the player if (!elements.container.contains(focused)) { return; } toggleClass(document.activeElement, player.config.classNames.tabFocus, true); }, 10); } // Global window & document listeners }, { key: "global", value: function global() { var toggle = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true; var player = this.player; // Keyboard shortcuts if (player.config.keyboard.global) { toggleListener.call(player, window, 'keydown keyup', this.handleKey, toggle, false); } // Click anywhere closes menu toggleListener.call(player, document.body, 'click', this.toggleMenu, toggle); // Detect touch by events once.call(player, document.body, 'touchstart', this.firstTouch); // Tab focus detection toggleListener.call(player, document.body, 'keydown focus blur', this.setTabFocus, toggle, false, true); } // Container listeners }, { key: "container", value: function container() { var player = this.player; var config = player.config, elements = player.elements, timers = player.timers; // Keyboard shortcuts if (!config.keyboard.global && config.keyboard.focused) { on.call(player, elements.container, 'keydown keyup', this.handleKey, false); } // Toggle controls on mouse events and entering fullscreen on.call(player, elements.container, 'mousemove mouseleave touchstart touchmove enterfullscreen exitfullscreen', function (event) { var controls$$1 = elements.controls; // Remove button states for fullscreen if (controls$$1 && event.type === 'enterfullscreen') { controls$$1.pressed = false; controls$$1.hover = false; } // Show, then hide after a timeout unless another control event occurs var show = ['touchstart', 'touchmove', 'mousemove'].includes(event.type); var delay = 0; if (show) { ui.toggleControls.call(player, true); // Use longer timeout for touch devices delay = player.touch ? 3000 : 2000; } // Clear timer clearTimeout(timers.controls); // Set new timer to prevent flicker when seeking timers.controls = setTimeout(function () { return ui.toggleControls.call(player, false); }, delay); }); // Force edge to repaint on exit fullscreen // TODO: Fix weird bug where Edge doesn't re-draw when exiting fullscreen /* if (browser.isEdge) { on.call(player, elements.container, 'exitfullscreen', () => { setTimeout(() => repaint(elements.container), 100); }); } */ // Set a gutter for Vimeo var setGutter = function setGutter(ratio, padding, toggle) { if (!player.isVimeo) { return; } var target = player.elements.wrapper.firstChild; var _ratio$split$map = ratio.split(':').map(Number), _ratio$split$map2 = _slicedToArray(_ratio$split$map, 2), height = _ratio$split$map2[1]; var _player$embed$ratio$s = player.embed.ratio.split(':').map(Number), _player$embed$ratio$s2 = _slicedToArray(_player$embed$ratio$s, 2), videoWidth = _player$embed$ratio$s2[0], videoHeight = _player$embed$ratio$s2[1]; target.style.maxWidth = toggle ? "".concat(height / videoHeight * videoWidth, "px") : null; target.style.margin = toggle ? '0 auto' : null; }; // Resize on fullscreen change var setPlayerSize = function setPlayerSize(measure) { // If we don't need to measure the viewport if (!measure) { return setAspectRatio.call(player); } var rect = elements.container.getBoundingClientRect(); var width = rect.width, height = rect.height; return setAspectRatio.call(player, "".concat(width, ":").concat(height)); }; var resized = function resized() { window.clearTimeout(timers.resized); timers.resized = window.setTimeout(setPlayerSize, 50); }; on.call(player, elements.container, 'enterfullscreen exitfullscreen', function (event) { var _player$fullscreen = player.fullscreen, target = _player$fullscreen.target, usingNative = _player$fullscreen.usingNative; // Ignore for iOS native if (!player.isEmbed || target !== elements.container) { return; } var isEnter = event.type === 'enterfullscreen'; // Set the player size when entering fullscreen to viewport size var _setPlayerSize = setPlayerSize(isEnter), padding = _setPlayerSize.padding, ratio = _setPlayerSize.ratio; // Set Vimeo gutter setGutter(ratio, padding, isEnter); // If not using native fullscreen, we need to check for resizes of viewport if (!usingNative) { if (isEnter) { on.call(player, window, 'resize', resized); } else { off.call(player, window, 'resize', resized); } } }); } // Listen for media events }, { key: "media", value: function media() { var _this = this; var player = this.player; var elements = player.elements; // Time change on media on.call(player, player.media, 'timeupdate seeking seeked', function (event) { return controls.timeUpdate.call(player, event); }); // Display duration on.call(player, player.media, 'durationchange loadeddata loadedmetadata', function (event) { return controls.durationUpdate.call(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 on.call(player, player.media, 'canplay loadeddata', function () { toggleHidden(elements.volume, !player.hasAudio); toggleHidden(elements.buttons.mute, !player.hasAudio); }); // Handle the media finishing on.call(player, player.media, 'ended', function () { // Show poster on end if (player.isHTML5 && player.isVideo && player.config.resetOnEnd) { // Restart player.restart(); } }); // Check for buffer progress on.call(player, player.media, 'progress playing seeking seeked', function (event) { return controls.updateProgress.call(player, event); }); // Handle volume changes on.call(player, player.media, 'volumechange', function (event) { return controls.updateVolume.call(player, event); }); // Handle play/pause on.call(player, player.media, 'playing play pause ended emptied timeupdate', function (event) { return ui.checkPlaying.call(player, event); }); // Loading state on.call(player, player.media, 'waiting canplay seeked playing', function (event) { return ui.checkLoading.call(player, event); }); // Click video if (player.supported.ui && player.config.clickToPlay && !player.isAudio) { // Re-fetch the wrapper var wrapper = getElement.call(player, ".".concat(player.config.classNames.video)); // Bail if there's no wrapper (this should never happen) if (!is.element(wrapper)) { return; } // On click play, pause or restart on.call(player, elements.container, 'click', function (event) { var targets = [elements.container, wrapper]; // Ignore if click if not container or in video wrapper if (!targets.includes(event.target) && !wrapper.contains(event.target)) { return; } // Touch devices will just show controls (if hidden) if (player.touch && player.config.hideControls) { return; } if (player.ended) { _this.proxy(event, player.restart, 'restart'); _this.proxy(event, player.play, 'play'); } else { _this.proxy(event, player.togglePlay, 'play'); } }); } // Disable right click if (player.supported.ui && player.config.disableContextMenu) { on.call(player, elements.wrapper, 'contextmenu', function (event) { event.preventDefault(); }, false); } // Volume change on.call(player, player.media, 'volumechange', function () { // Save to storage player.storage.set({ volume: player.volume, muted: player.muted }); }); // Speed change on.call(player, player.media, 'ratechange', function () { // Update UI controls.updateSetting.call(player, 'speed'); // Save to storage player.storage.set({ speed: player.speed }); }); // Quality change on.call(player, player.media, 'qualitychange', function (event) { // Update UI controls.updateSetting.call(player, 'quality', null, event.detail.quality); }); // Update download link when ready and if quality changes on.call(player, player.media, 'ready qualitychange', function () { controls.setDownloadLink.call(player); }); // Proxy events to container // Bubble up key events for Edge var proxyEvents = player.config.events.concat(['keyup', 'keydown']).join(' '); on.call(player, player.media, proxyEvents, function (event) { var _event$detail = event.detail, detail = _event$detail === void 0 ? {} : _event$detail; // Get error details from media if (event.type === 'error') { detail = player.media.error; } triggerEvent.call(player, elements.container, event.type, true, detail); }); } // Run default and custom handlers }, { key: "proxy", value: function proxy(event, defaultHandler, customHandlerKey) { var player = this.player; var customHandler = player.config.listeners[customHandlerKey]; var hasCustomHandler = is.function(customHandler); var returned = true; // Execute custom handler if (hasCustomHandler) { returned = customHandler.call(player, event); } // Only call default handler if not prevented in custom handler if (returned && is.function(defaultHandler)) { defaultHandler.call(player, event); } } // Trigger custom and default handlers }, { key: "bind", value: function bind(element, type, defaultHandler, customHandlerKey) { var _this2 = this; var passive = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : true; var player = this.player; var customHandler = player.config.listeners[customHandlerKey]; var hasCustomHandler = is.function(customHandler); on.call(player, element, type, function (event) { return _this2.proxy(event, defaultHandler, customHandlerKey); }, passive && !hasCustomHandler); } // Listen for control events }, { key: "controls", value: function controls$$1() { var _this3 = this; var player = this.player; var elements = player.elements; // IE doesn't support input event, so we fallback to change var inputEvent = browser.isIE ? 'change' : 'input'; // Play/pause toggle if (elements.buttons.play) { Array.from(elements.buttons.play).forEach(function (button) { _this3.bind(button, 'click', player.togglePlay, 'play'); }); } // Pause this.bind(elements.buttons.restart, 'click', player.restart, 'restart'); // Rewind this.bind(elements.buttons.rewind, 'click', player.rewind, 'rewind'); // Rewind this.bind(elements.buttons.fastForward, 'click', player.forward, 'fastForward'); // Mute toggle this.bind(elements.buttons.mute, 'click', function () { player.muted = !player.muted; }, 'mute'); // Captions toggle this.bind(elements.buttons.captions, 'click', function () { return player.toggleCaptions(); }); // Download this.bind(elements.buttons.download, 'click', function () { triggerEvent.call(player, player.media, 'download'); }, 'download'); // Fullscreen toggle this.bind(elements.buttons.fullscreen, 'click', function () { player.fullscreen.toggle(); }, 'fullscreen'); // Picture-in-Picture this.bind(elements.buttons.pip, 'click', function () { player.pip = 'toggle'; }, 'pip'); // Airplay this.bind(elements.buttons.airplay, 'click', player.airplay, 'airplay'); // Settings menu - click toggle this.bind(elements.buttons.settings, 'click', function (event) { // Prevent the document click listener closing the menu event.stopPropagation(); controls.toggleMenu.call(player, event); }); // Settings menu - keyboard toggle // We have to bind to keyup otherwise Firefox triggers a click when a keydown event handler shifts focus // https://bugzilla.mozilla.org/show_bug.cgi?id=1220143 this.bind(elements.buttons.settings, 'keyup', function (event) { var code = event.which; // We only care about space and return if (![13, 32].includes(code)) { return; } // Because return triggers a click anyway, all we need to do is set focus if (code === 13) { controls.focusFirstMenuItem.call(player, null, true); return; } // Prevent scroll event.preventDefault(); // Prevent playing video (Firefox) event.stopPropagation(); // Toggle menu controls.toggleMenu.call(player, event); }, null, false // Can't be passive as we're preventing default ); // Escape closes menu this.bind(elements.settings.menu, 'keydown', function (event) { if (event.which === 27) { controls.toggleMenu.call(player, event); } }); // Set range input alternative "value", which matches the tooltip time (#954) this.bind(elements.inputs.seek, 'mousedown mousemove', function (event) { var rect = elements.progress.getBoundingClientRect(); var percent = 100 / rect.width * (event.pageX - rect.left); event.currentTarget.setAttribute('seek-value', percent); }); // Pause while seeking this.bind(elements.inputs.seek, 'mousedown mouseup keydown keyup touchstart touchend', function (event) { var seek = event.currentTarget; var code = event.keyCode ? event.keyCode : event.which; var attribute = 'play-on-seeked'; if (is.keyboardEvent(event) && code !== 39 && code !== 37) { return; } // Record seek time so we can prevent hiding controls for a few seconds after seek player.lastSeekTime = Date.now(); // Was playing before? var play = seek.hasAttribute(attribute); // Done seeking var done = ['mouseup', 'touchend', 'keyup'].includes(event.type); // If we're done seeking and it was playing, resume playback if (play && done) { seek.removeAttribute(attribute); player.play(); } else if (!done && player.playing) { seek.setAttribute(attribute, ''); player.pause(); } }); // Fix range inputs on iOS // Super weird iOS bug where after you interact with an , // it takes over further interactions on the page. This is a hack if (browser.isIos) { var inputs = getElements.call(player, 'input[type="range"]'); Array.from(inputs).forEach(function (input) { return _this3.bind(input, inputEvent, function (event) { return repaint(event.target); }); }); } // Seek this.bind(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 (is.empty(seekTo)) { seekTo = seek.value; } seek.removeAttribute('seek-value'); player.currentTime = seekTo / seek.max * player.duration; }, 'seek'); // Seek tooltip this.bind(elements.progress, 'mouseenter mouseleave mousemove', function (event) { return controls.updateSeekTooltip.call(player, event); }); // Preview thumbnails plugin // TODO: Really need to work on some sort of plug-in wide event bus or pub-sub for this this.bind(elements.progress, 'mousemove touchmove', function (event) { var previewThumbnails = player.previewThumbnails; if (previewThumbnails && previewThumbnails.loaded) { previewThumbnails.startMove(event); } }); // Hide thumbnail preview - on mouse click, mouse leave, and video play/seek. All four are required, e.g., for buffering this.bind(elements.progress, 'mouseleave click', function () { var previewThumbnails = player.previewThumbnails; if (previewThumbnails && previewThumbnails.loaded) { previewThumbnails.endMove(false, true); } }); // Show scrubbing preview this.bind(elements.progress, 'mousedown touchstart', function (event) { var previewThumbnails = player.previewThumbnails; if (previewThumbnails && previewThumbnails.loaded) { previewThumbnails.startScrubbing(event); } }); this.bind(elements.progress, 'mouseup touchend', function (event) { var previewThumbnails = player.previewThumbnails; if (previewThumbnails && previewThumbnails.loaded) { previewThumbnails.endScrubbing(event); } }); // Polyfill for lower fill in for webkit if (browser.isWebkit) { Array.from(getElements.call(player, 'input[type="range"]')).forEach(function (element) { _this3.bind(element, 'input', function (event) { return controls.updateRangeFill.call(player, event.target); }); }); } // Current time invert // Only if one time element is used for both currentTime and duration if (player.config.toggleInvert && !is.element(elements.display.duration)) { this.bind(elements.display.currentTime, 'click', function () { // Do nothing if we're at the start if (player.currentTime === 0) { return; } player.config.invertTime = !player.config.invertTime; controls.timeUpdate.call(player); }); } // Volume this.bind(elements.inputs.volume, inputEvent, function (event) { player.volume = event.target.value; }, 'volume'); // Update controls.hover state (used for ui.toggleControls to avoid hiding when interacting) this.bind(elements.controls, 'mouseenter mouseleave', function (event) { elements.controls.hover = !player.touch && event.type === 'mouseenter'; }); // Update controls.pressed state (used for ui.toggleControls to avoid hiding when interacting) this.bind(elements.controls, 'mousedown mouseup touchstart touchend touchcancel', function (event) { elements.controls.pressed = ['mousedown', 'touchstart'].includes(event.type); }); // Show controls when they receive focus (e.g., when using keyboard tab key) this.bind(elements.controls, 'focusin', function () { var config = player.config, elements = player.elements, timers = player.timers; // Skip transition to prevent focus from scrolling the parent element toggleClass(elements.controls, config.classNames.noTransition, true); // Toggle ui.toggleControls.call(player, true); // Restore transition setTimeout(function () { toggleClass(elements.controls, config.classNames.noTransition, false); }, 0); // Delay a little more for mouse users var delay = _this3.touch ? 3000 : 4000; // Clear timer clearTimeout(timers.controls); // Hide again after delay timers.controls = setTimeout(function () { return ui.toggleControls.call(player, false); }, delay); }); // Mouse wheel for volume this.bind(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; // Get delta from event. Invert if `inverted` is true var _map = [event.deltaX, -event.deltaY].map(function (value) { return inverted ? -value : value; }), _map2 = _slicedToArray(_map, 2), x = _map2[0], y = _map2[1]; // Using the biggest delta, normalize to 1 or -1 (or 0 if no delta) var direction = Math.sign(Math.abs(x) > Math.abs(y) ? x : y); // Change the volume by 2% player.increaseVolume(direction / 50); // Don't break page scrolling at max and min var volume = player.media.volume; if (direction === 1 && volume < 1 || direction === -1 && volume > 0) { event.preventDefault(); } }, 'volume', false); } }]); 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) { { 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` (unless error is Code:18 SecurityError) if (x.code != 18) 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 }); }); } 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; } // Set playback state and trigger change (only on actual change) function assurePlaybackState(play) { if (play && !this.embed.hasPlayed) { this.embed.hasPlayed = true; } if (this.media.paused === play) { this.media.paused = !play; triggerEvent.call(this, this.media, play ? 'play' : 'pause'); } } var vimeo = { setup: function setup() { var _this = this; // Add embed class for responsive toggleClass(this.elements.wrapper, this.config.classNames.embed, true); // Set intial ratio setAspectRatio.call(this); // Load the API if not already 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); }); } else { vimeo.ready.call(this); } }, // API Ready ready: function ready$$1() { var _this2 = this; var player = this; var config = player.config.vimeo; // Get Vimeo params for the iframe var params = buildUrlParams(extend({}, { loop: player.config.loop.active, autoplay: player.autoplay, muted: player.muted, gesture: 'media', playsinline: !this.config.fullscreen.iosNative }, config)); // Get the source URL or ID var source = player.media.getAttribute('src'); // Get from
if needed if (is.empty(source)) { source = player.media.getAttribute(player.config.attributes.embed.id); } var id = parseId(source); // Build an iframe 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'); // Get poster, if already set var poster = player.poster; // Inject the package var wrapper = createElement('div', { poster: poster, class: player.config.classNames.embedContainer }); wrapper.appendChild(iframe); player.media = replaceElement(wrapper, player.media); // Get poster image fetch(format(player.config.urls.vimeo.api, id), 'json').then(function (response) { if (is.empty(response)) { return; } // Get the URL for thumbnail var url = new URL(response[0].thumbnail_large); // Get original image url.pathname = "".concat(url.pathname.split('_')[0], ".jpg"); // Set and show poster ui.setPoster.call(player, url.href).catch(function () {}); }); // Setup instance // https://github.com/vimeo/player.js player.embed = new window.Vimeo.Player(iframe, { autopause: player.config.autopause, muted: player.muted }); player.media.paused = true; player.media.currentTime = 0; // Disable native text track rendering if (player.supported.ui) { player.embed.disableTextTrack(); } // Create a faux HTML5 API using the Vimeo API player.media.play = function () { assurePlaybackState.call(player, true); return player.embed.play(); }; player.media.pause = function () { assurePlaybackState.call(player, false); return player.embed.pause(); }; player.media.stop = function () { player.pause(); player.currentTime = 0; }; // Seeking var currentTime = player.media.currentTime; Object.defineProperty(player.media, 'currentTime', { get: function get() { return currentTime; }, set: function set(time) { // Vimeo will automatically play on seek if the video hasn't been played before // Get current paused state and volume etc var embed = player.embed, media = player.media, paused = player.paused, volume = player.volume; var restorePause = paused && !embed.hasPlayed; // Set seeking state and trigger event media.seeking = true; triggerEvent.call(player, media, 'seeking'); // If paused, mute until seek is complete Promise.resolve(restorePause && embed.setVolume(0)) // Seek .then(function () { return embed.setCurrentTime(time); }) // Restore paused .then(function () { return restorePause && embed.pause(); }) // Restore volume .then(function () { return restorePause && embed.setVolume(volume); }).catch(function () {// Do nothing }); } }); // Playback speed var speed = player.config.speed.selected; Object.defineProperty(player.media, 'playbackRate', { get: function get() { return speed; }, set: function set(input) { player.embed.setPlaybackRate(input).then(function () { speed = input; triggerEvent.call(player, player.media, 'ratechange'); }).catch(function (error) { // Hide menu item (and menu if empty) if (error.name === 'Error') { controls.setSpeedMenu.call(player, []); } }); } }); // Volume var volume = player.config.volume; Object.defineProperty(player.media, 'volume', { get: function get() { return volume; }, set: function set(input) { player.embed.setVolume(input).then(function () { volume = input; triggerEvent.call(player, player.media, 'volumechange'); }); } }); // Muted var muted = player.config.muted; Object.defineProperty(player.media, 'muted', { get: function get() { return muted; }, set: function set(input) { var toggle = is.boolean(input) ? input : false; player.embed.setVolume(toggle ? 0 : player.config.volume).then(function () { muted = toggle; triggerEvent.call(player, player.media, 'volumechange'); }); } }); // Loop var loop = player.config.loop; Object.defineProperty(player.media, 'loop', { get: function get() { return loop; }, set: function set(input) { var toggle = is.boolean(input) ? input : player.config.loop.active; player.embed.setLoop(toggle).then(function () { loop = toggle; }); } }); // Source var currentSrc; player.embed.getVideoUrl().then(function (value) { currentSrc = value; controls.setDownloadLink.call(player); }).catch(function (error) { _this2.debug.warn(error); }); Object.defineProperty(player.media, 'currentSrc', { get: function get() { return currentSrc; } }); // Ended Object.defineProperty(player.media, 'ended', { get: function get() { return player.currentTime === player.duration; } }); // Set aspect ratio based on video size Promise.all([player.embed.getVideoWidth(), player.embed.getVideoHeight()]).then(function (dimensions) { var _dimensions = _slicedToArray(dimensions, 2), width = _dimensions[0], height = _dimensions[1]; player.embed.ratio = "".concat(width, ":").concat(height); setAspectRatio.call(_this2, player.embed.ratio); }); // Set autopause player.embed.setAutopause(player.config.autopause).then(function (state) { player.config.autopause = state; }); // Get title player.embed.getVideoTitle().then(function (title) { player.config.title = title; ui.setTitle.call(_this2); }); // Get current time player.embed.getCurrentTime().then(function (value) { currentTime = value; triggerEvent.call(player, player.media, 'timeupdate'); }); // Get duration player.embed.getDuration().then(function (value) { player.media.duration = value; triggerEvent.call(player, player.media, 'durationchange'); }); // Get captions player.embed.getTextTracks().then(function (tracks) { player.media.textTracks = tracks; captions.setup.call(player); }); player.embed.on('cuechange', function (_ref) { var _ref$cues = _ref.cues, cues = _ref$cues === void 0 ? [] : _ref$cues; var strippedCues = cues.map(function (cue) { return stripHTML(cue.text); }); captions.updateCues.call(player, strippedCues); }); player.embed.on('loaded', function () { // Assure state and events are updated on autoplay player.embed.getPaused().then(function (paused) { assurePlaybackState.call(player, !paused); if (!paused) { triggerEvent.call(player, player.media, 'playing'); } }); if (is.element(player.embed.element) && player.supported.ui) { var frame = player.embed.element; // Fix keyboard focus issues // https://github.com/sampotts/plyr/issues/317 frame.setAttribute('tabindex', -1); } }); player.embed.on('play', function () { assurePlaybackState.call(player, true); triggerEvent.call(player, player.media, 'playing'); }); player.embed.on('pause', function () { assurePlaybackState.call(player, false); }); player.embed.on('timeupdate', function (data) { player.media.seeking = false; currentTime = data.seconds; triggerEvent.call(player, player.media, 'timeupdate'); }); player.embed.on('progress', function (data) { player.media.buffered = data.percent; triggerEvent.call(player, player.media, 'progress'); // Check all loaded if (parseInt(data.percent, 10) === 1) { triggerEvent.call(player, player.media, 'canplaythrough'); } // Get duration as if we do it before load, it gives an incorrect value // https://github.com/sampotts/plyr/issues/891 player.embed.getDuration().then(function (value) { if (value !== player.media.duration) { player.media.duration = value; triggerEvent.call(player, player.media, 'durationchange'); } }); }); player.embed.on('seeked', function () { player.media.seeking = false; triggerEvent.call(player, player.media, 'seeked'); }); player.embed.on('ended', function () { player.media.paused = true; triggerEvent.call(player, player.media, 'ended'); }); player.embed.on('error', function (detail) { player.media.error = detail; triggerEvent.call(player, player.media, 'error'); }); // Rebuild UI setTimeout(function () { return ui.build.call(player); }, 0); } }; // ========================================================================== 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; } // Set playback state and trigger change (only on actual change) function assurePlaybackState$1(play) { if (play && !this.embed.hasPlayed) { this.embed.hasPlayed = true; } if (this.media.paused === play) { this.media.paused = !play; triggerEvent.call(this, this.media, play ? 'play' : 'pause'); } } var youtube = { setup: function setup() { var _this = this; // Add embed class for responsive toggleClass(this.elements.wrapper, this.config.classNames.embed, true); // Set aspect ratio setAspectRatio.call(this); // Setup API if (is.object(window.YT) && is.function(window.YT.Player)) { youtube.ready.call(this); } else { // Load the API loadScript(this.config.urls.youtube.sdk).catch(function (error) { _this.debug.warn('YouTube API failed to load', error); }); // Setup callback for the API // YouTube has it's own system of course... window.onYouTubeReadyCallbacks = window.onYouTubeReadyCallbacks || []; // Add to queue window.onYouTubeReadyCallbacks.push(function () { youtube.ready.call(_this); }); // Set callback to process queue window.onYouTubeIframeAPIReady = function () { window.onYouTubeReadyCallbacks.forEach(function (callback) { callback(); }); }; } }, // Get the media title getTitle: function getTitle(videoId) { var _this2 = this; // Try via undocumented API method first // This method disappears now and then though... // https://github.com/sampotts/plyr/issues/709 if (is.function(this.embed.getVideoData)) { var _this$embed$getVideoD = this.embed.getVideoData(), title = _this$embed$getVideoD.title; if (is.empty(title)) { this.config.title = title; ui.setTitle.call(this); return; } } // Or via Google API var key = this.config.keys.google; if (is.string(key) && !is.empty(key)) { var url = format(this.config.urls.youtube.api, videoId, key); fetch(url).then(function (result) { if (is.object(result)) { _this2.config.title = result.items[0].snippet.title; ui.setTitle.call(_this2); } }).catch(function () {}); } }, // API ready ready: function ready$$1() { var player = this; // Ignore already setup (race condition) var currentId = player.media.getAttribute('id'); if (!is.empty(currentId) && currentId.startsWith('youtube-')) { return; } // Get the source URL or ID var source = player.media.getAttribute('src'); // Get from
if needed if (is.empty(source)) { source = player.media.getAttribute(this.config.attributes.embed.id); } // Replace the