diff options
Diffstat (limited to 'src/js/plyr.js')
-rw-r--r-- | src/js/plyr.js | 573 |
1 files changed, 237 insertions, 336 deletions
diff --git a/src/js/plyr.js b/src/js/plyr.js index c2a1d6e3..927eb33d 100644 --- a/src/js/plyr.js +++ b/src/js/plyr.js @@ -1,24 +1,32 @@ // ========================================================================== // Plyr -// plyr.js v3.3.6 +// plyr.js v3.4.8 // https://github.com/sampotts/plyr // License: The MIT License (MIT) // ========================================================================== import captions from './captions'; +import defaults from './config/defaults'; +import { pip } from './config/states'; +import { getProviderByUrl, providers, types } from './config/types'; import Console from './console'; import controls from './controls'; -import defaults from './defaults'; import Fullscreen from './fullscreen'; import Listeners from './listeners'; import media from './media'; import Ads from './plugins/ads'; +import PreviewThumbnails from './plugins/previewThumbnails'; import source from './source'; import Storage from './storage'; import support from './support'; -import { providers, types } from './types'; import ui from './ui'; -import utils from './utils'; +import { closest } from './utils/arrays'; +import { createElement, hasClass, removeElement, replaceElement, toggleClass, wrap } from './utils/elements'; +import { off, on, once, triggerEvent, unbindListeners } from './utils/events'; +import is from './utils/is'; +import loadSprite from './utils/loadSprite'; +import { cloneDeep, extend } from './utils/objects'; +import { parseUrl } from './utils/urls'; // Private properties // TODO: Use a WeakMap for private globals @@ -41,20 +49,21 @@ class Plyr { this.media = target; // String selector passed - if (utils.is.string(this.media)) { + if (is.string(this.media)) { this.media = document.querySelectorAll(this.media); } // jQuery, NodeList or Array passed, use first element - if ((window.jQuery && this.media instanceof jQuery) || utils.is.nodeList(this.media) || utils.is.array(this.media)) { + if ((window.jQuery && this.media instanceof jQuery) || is.nodeList(this.media) || is.array(this.media)) { // eslint-disable-next-line this.media = this.media[0]; } // Set config - this.config = utils.extend( + this.config = extend( {}, defaults, + Plyr.defaults, options || {}, (() => { try { @@ -68,22 +77,24 @@ class Plyr { // Elements cache this.elements = { container: null, + captions: null, buttons: {}, display: {}, progress: {}, inputs: {}, settings: { + popup: null, menu: null, - panes: {}, - tabs: {}, + panels: {}, + buttons: {}, }, - captions: null, }; // Captions this.captions = { active: null, - currentTrack: null, + currentTrack: -1, + meta: new WeakMap(), }; // Fullscreen @@ -95,7 +106,6 @@ class Plyr { this.options = { speed: [], quality: [], - captions: [], }; // Debugging @@ -107,7 +117,7 @@ class Plyr { this.debug.log('Support', support); // We need an element to setup - if (utils.is.nullOrUndefined(this.media) || !utils.is.element(this.media)) { + if (is.nullOrUndefined(this.media) || !is.element(this.media)) { this.debug.error('Setup failed: no suitable element passed'); return; } @@ -143,7 +153,6 @@ class Plyr { // Embed properties let iframe = null; let url = null; - let params = null; // Different setup based on type switch (type) { @@ -152,10 +161,10 @@ class Plyr { iframe = this.media.querySelector('iframe'); // <iframe> type - if (utils.is.element(iframe)) { + if (is.element(iframe)) { // Detect provider - url = iframe.getAttribute('src'); - this.provider = utils.getProviderByUrl(url); + url = parseUrl(iframe.getAttribute('src')); + this.provider = getProviderByUrl(url.toString()); // Rework elements this.elements.container = this.media; @@ -165,24 +174,21 @@ class Plyr { this.elements.container.className = ''; // Get attributes from URL and set config - params = utils.getUrlParams(url); - if (!utils.is.empty(params)) { - const truthy = [ - '1', - 'true', - ]; - - if (truthy.includes(params.autoplay)) { + if (url.search.length) { + const truthy = ['1', 'true']; + + if (truthy.includes(url.searchParams.get('autoplay'))) { this.config.autoplay = true; } - if (truthy.includes(params.loop)) { + if (truthy.includes(url.searchParams.get('loop'))) { this.config.loop.active = true; } // TODO: replace fullscreen.iosNative with this playsinline config option // YouTube requires the playsinline in the URL if (this.isYouTube) { - this.config.playsinline = truthy.includes(params.playsinline); + this.config.playsinline = truthy.includes(url.searchParams.get('playsinline')); + this.config.youtube.hl = url.searchParams.get('hl'); // TODO: Should this be setting language? } else { this.config.playsinline = true; } @@ -196,7 +202,7 @@ class Plyr { } // Unsupported or missing provider - if (utils.is.empty(this.provider) || !Object.keys(providers).includes(this.provider)) { + if (is.empty(this.provider) || !Object.keys(providers).includes(this.provider)) { this.debug.error('Setup failed: Invalid provider'); return; } @@ -218,7 +224,7 @@ class Plyr { if (this.media.hasAttribute('autoplay')) { this.config.autoplay = true; } - if (this.media.hasAttribute('playsinline')) { + if (this.media.hasAttribute('playsinline') || this.media.hasAttribute('webkit-playsinline')) { this.config.playsinline = true; } if (this.media.hasAttribute('muted')) { @@ -244,6 +250,8 @@ class Plyr { return; } + this.eventListeners = []; + // Create listeners this.listeners = new Listeners(this); @@ -254,14 +262,11 @@ class Plyr { this.media.plyr = this; // Wrap media - if (!utils.is.element(this.elements.container)) { - this.elements.container = utils.createElement('div'); - utils.wrap(this.media, this.elements.container); + if (!is.element(this.elements.container)) { + this.elements.container = createElement('div', { tabindex: 0 }); + wrap(this.media, this.elements.container); } - // Allow focus to be captured - this.elements.container.setAttribute('tabindex', 0); - // Add style hook ui.addStyleHook.call(this); @@ -270,7 +275,7 @@ class Plyr { // Listen for events if debugging if (this.config.debug) { - utils.on(this.elements.container, this.config.events.join(' '), event => { + on.call(this, this.elements.container, this.config.events.join(' '), event => { this.debug.log(`event: ${event.type}`); }); } @@ -291,12 +296,22 @@ class Plyr { this.fullscreen = new Fullscreen(this); // Setup ads if provided - this.ads = new Ads(this); + if (this.config.ads.enabled) { + this.ads = new Ads(this); + } // Autoplay if required if (this.config.autoplay) { this.play(); } + + // Seek time will be recorded (in listeners.js) so we can prevent hiding controls for a few seconds after seek + this.lastSeekTime = 0; + + // Setup preview thumbnails if enabled + if (this.config.previewThumbnails.enabled) { + this.previewThumbnails = new PreviewThumbnails(this); + } } // --------------------------------------- @@ -309,18 +324,23 @@ class Plyr { get isHTML5() { return Boolean(this.provider === providers.html5); } + get isEmbed() { return Boolean(this.isYouTube || this.isVimeo); } + get isYouTube() { return Boolean(this.provider === providers.youtube); } + get isVimeo() { return Boolean(this.provider === providers.vimeo); } + get isVideo() { return Boolean(this.type === types.video); } + get isAudio() { return Boolean(this.type === types.audio); } @@ -329,7 +349,7 @@ class Plyr { * Play the media, or play the advertisement (if they are not blocked) */ play() { - if (!utils.is.function(this.media.play)) { + if (!is.function(this.media.play)) { return null; } @@ -341,7 +361,7 @@ class Plyr { * Pause the media */ pause() { - if (!this.playing || !utils.is.function(this.media.pause)) { + if (!this.playing || !is.function(this.media.pause)) { return; } @@ -382,7 +402,7 @@ class Plyr { */ togglePlay(input) { // Toggle based on current state if nothing passed - const toggle = utils.is.boolean(input) ? input : !this.playing; + const toggle = is.boolean(input) ? input : !this.playing; if (toggle) { this.play(); @@ -398,7 +418,7 @@ class Plyr { if (this.isHTML5) { this.pause(); this.restart(); - } else if (utils.is.function(this.media.stop)) { + } else if (is.function(this.media.stop)) { this.media.stop(); } } @@ -415,7 +435,7 @@ class Plyr { * @param {number} seekTime - how far to rewind in seconds. Defaults to the config.seekTime */ rewind(seekTime) { - this.currentTime = this.currentTime - (utils.is.number(seekTime) ? seekTime : this.config.seekTime); + this.currentTime = this.currentTime - (is.number(seekTime) ? seekTime : this.config.seekTime); } /** @@ -423,7 +443,7 @@ class Plyr { * @param {number} seekTime - how far to fast forward in seconds. Defaults to the config.seekTime */ forward(seekTime) { - this.currentTime = this.currentTime + (utils.is.number(seekTime) ? seekTime : this.config.seekTime); + this.currentTime = this.currentTime + (is.number(seekTime) ? seekTime : this.config.seekTime); } /** @@ -431,21 +451,16 @@ class Plyr { * @param {number} input - where to seek to in seconds. Defaults to 0 (the start) */ set currentTime(input) { - let targetTime = 0; - - if (utils.is.number(input)) { - targetTime = input; + // Bail if media duration isn't available yet + if (!this.duration) { + return; } - // Normalise targetTime - if (targetTime < 0) { - targetTime = 0; - } else if (targetTime > this.duration) { - targetTime = this.duration; - } + // Validate input + const inputIsValid = is.number(input) && input > 0; // Set - this.media.currentTime = targetTime; + this.media.currentTime = inputIsValid ? Math.min(input, this.duration) : 0; // Logging this.debug.log(`Seeking to ${this.currentTime} seconds`); @@ -465,7 +480,7 @@ class Plyr { const { buffered } = this.media; // YouTube / Vimeo return a float between 0-1 - if (utils.is.number(buffered)) { + if (is.number(buffered)) { return buffered; } @@ -493,11 +508,12 @@ class Plyr { // Faux duration set via config const fauxDuration = parseFloat(this.config.duration); - // True duration - const realDuration = this.media ? Number(this.media.duration) : 0; + // Media duration can be NaN or Infinity before the media has loaded + const realDuration = (this.media || {}).duration; + const duration = !is.number(realDuration) || realDuration === Infinity ? 0 : realDuration; - // If custom duration is funky, use regular duration - return !Number.isNaN(fauxDuration) ? fauxDuration : realDuration; + // If config duration is funky, use regular duration + return fauxDuration || duration; } /** @@ -509,17 +525,17 @@ class Plyr { const max = 1; const min = 0; - if (utils.is.string(volume)) { + if (is.string(volume)) { volume = Number(volume); } // Load volume from storage if no value specified - if (!utils.is.number(volume)) { + if (!is.number(volume)) { volume = this.storage.get('volume'); } // Use config if all else fails - if (!utils.is.number(volume)) { + if (!is.number(volume)) { ({ volume } = this.config); } @@ -539,7 +555,7 @@ class Plyr { this.media.volume = volume; // If muted, and we're increasing volume manually, reset muted state - if (!utils.is.empty(value) && this.muted && volume > 0) { + if (!is.empty(value) && this.muted && volume > 0) { this.muted = false; } } @@ -557,7 +573,7 @@ class Plyr { */ increaseVolume(step) { const volume = this.media.muted ? 0 : this.volume; - this.volume = volume + (utils.is.number(step) ? step : 1); + this.volume = volume + (is.number(step) ? step : 0); } /** @@ -565,8 +581,7 @@ class Plyr { * @param {boolean} step - How much to decrease by (between 0 and 1) */ decreaseVolume(step) { - const volume = this.media.muted ? 0 : this.volume; - this.volume = volume - (utils.is.number(step) ? step : 1); + this.increaseVolume(-step); } /** @@ -577,12 +592,12 @@ class Plyr { let toggle = mute; // Load muted state from storage - if (!utils.is.boolean(toggle)) { + if (!is.boolean(toggle)) { toggle = this.storage.get('muted'); } // Use config if all else fails - if (!utils.is.boolean(toggle)) { + if (!is.boolean(toggle)) { toggle = this.config.muted; } @@ -628,15 +643,15 @@ class Plyr { set speed(input) { let speed = null; - if (utils.is.number(input)) { + if (is.number(input)) { speed = input; } - if (!utils.is.number(speed)) { + if (!is.number(speed)) { speed = this.storage.get('speed'); } - if (!utils.is.number(speed)) { + if (!is.number(speed)) { speed = this.config.speed.selected; } @@ -673,39 +688,41 @@ class Plyr { * @param {number} input - Quality level */ set quality(input) { - let quality = null; - - if (!utils.is.empty(input)) { - quality = Number(input); - } + const config = this.config.quality; + const options = this.options.quality; - if (!utils.is.number(quality) || quality === 0) { - quality = this.storage.get('quality'); + if (!options.length) { + return; } - if (!utils.is.number(quality)) { - quality = this.config.quality.selected; - } + let quality = [ + !is.empty(input) && Number(input), + this.storage.get('quality'), + config.selected, + config.default, + ].find(is.number); - if (!utils.is.number(quality)) { - quality = this.config.quality.default; - } + let updateStorage = true; - if (!this.options.quality.length) { - return; - } + if (!options.includes(quality)) { + const value = closest(options, quality); + this.debug.warn(`Unsupported quality option: ${quality}, using ${value} instead`); + quality = value; - if (!this.options.quality.includes(quality)) { - const closest = utils.closest(this.options.quality, quality); - this.debug.warn(`Unsupported quality option: ${quality}, using ${closest} instead`); - quality = closest; + // Don't update storage if quality is not supported + updateStorage = false; } // Update config - this.config.quality.selected = quality; + config.selected = quality; // Set quality this.media.quality = quality; + + // Save to storage + if (updateStorage) { + this.storage.set({ quality }); + } } /** @@ -721,7 +738,7 @@ class Plyr { * @param {boolean} input - Whether to loop or not */ set loop(input) { - const toggle = utils.is.boolean(input) ? input : this.config.loop.active; + const toggle = is.boolean(input) ? input : this.config.loop.active; this.config.loop.active = toggle; this.media.loop = toggle; @@ -792,6 +809,15 @@ class Plyr { } /** + * Get a download URL (either source or custom) + */ + get download() { + const { download } = this.config.urls; + + return is.url(download) ? download : this.source; + } + + /** * Set the poster image for a video * @param {input} - the URL for the new poster image */ @@ -801,10 +827,7 @@ class Plyr { return; } - if (utils.is.string(input)) { - this.media.setAttribute('poster', input); - ui.setPoster.call(this); - } + ui.setPoster.call(this, input, false).catch(() => {}); } /** @@ -823,7 +846,7 @@ class Plyr { * @param {boolean} input - Whether to autoplay or not */ set autoplay(input) { - const toggle = utils.is.boolean(input) ? input : this.config.autoplay; + const toggle = is.boolean(input) ? input : this.config.autoplay; this.config.autoplay = toggle; } @@ -839,88 +862,39 @@ class Plyr { * @param {boolean} input - Whether to enable captions */ toggleCaptions(input) { - // If there's no full support - if (!this.supported.ui) { - return; - } - - // If the method is called without parameter, toggle based on current value - const show = utils.is.boolean(input) ? input : !this.elements.container.classList.contains(this.config.classNames.captions.active); - - // Nothing to change... - if (this.captions.active === show) { - return; - } - - // Set global - this.captions.active = show; - - // Toggle state - utils.toggleState(this.elements.buttons.captions, this.captions.active); + captions.toggle.call(this, input, false); + } - // Add class hook - utils.toggleClass(this.elements.container, this.config.classNames.captions.active, this.captions.active); + /** + * Set the caption track by index + * @param {number} - Caption index + */ + set currentTrack(input) { + captions.set.call(this, input, false); + } - // Trigger an event - utils.dispatchEvent.call(this, this.media, this.captions.active ? 'captionsenabled' : 'captionsdisabled'); + /** + * Get the current caption track index (-1 if disabled) + */ + get currentTrack() { + const { toggled, currentTrack } = this.captions; + return toggled ? currentTrack : -1; } /** - * Set the captions language + * Set the wanted language for captions + * Since tracks can be added later it won't update the actual caption track until there is a matching track * @param {string} - Two character ISO language code (e.g. EN, FR, PT, etc) */ set language(input) { - // Nothing specified - if (!utils.is.string(input)) { - return; - } - - // If empty string is passed, assume disable captions - if (utils.is.empty(input)) { - this.toggleCaptions(false); - return; - } - - // Normalize - const language = input.toLowerCase(); - - // Check for support - if (!this.options.captions.includes(language)) { - this.debug.warn(`Unsupported language option: ${language}`); - return; - } - - // Ensure captions are enabled - this.toggleCaptions(true); - - // Enabled only - if (language === 'enabled') { - return; - } - - // If nothing to change, bail - if (this.language === language) { - return; - } - - // Update config - this.captions.language = language; - - // Clear caption - captions.setText.call(this, null); - - // Update captions - captions.setLanguage.call(this); - - // Trigger an event - utils.dispatchEvent.call(this, this.media, 'languagechange'); + captions.setLanguage.call(this, input, false); } /** - * Get the current captions language + * Get the current track's language */ get language() { - return this.captions.language; + return (captions.getCurrentTrack.call(this) || {}).language; } /** @@ -929,21 +903,28 @@ class Plyr { * TODO: detect outside changes */ set pip(input) { - const states = { - pip: 'picture-in-picture', - inline: 'inline', - }; - // Bail if no support if (!support.pip) { return; } // Toggle based on current state if not passed - const toggle = utils.is.boolean(input) ? input : this.pip === states.inline; + const toggle = is.boolean(input) ? input : !this.pip; // Toggle based on current state - this.media.webkitSetPresentationMode(toggle ? states.pip : states.inline); + // Safari + if (is.function(this.media.webkitSetPresentationMode)) { + this.media.webkitSetPresentationMode(toggle ? pip.active : pip.inactive); + } + + // Chrome + if (is.function(this.media.requestPictureInPicture)) { + if (!this.pip && toggle) { + this.media.requestPictureInPicture(); + } else if (this.pip && !toggle) { + document.exitPictureInPicture(); + } + } } /** @@ -954,7 +935,13 @@ class Plyr { return null; } - return this.media.webkitPresentationMode; + // Safari + if (!is.empty(this.media.webkitPresentationMode)) { + return this.media.webkitPresentationMode === pip.active; + } + + // Chrome + return this.media === document.pictureInPictureElement; } /** @@ -970,119 +957,35 @@ class Plyr { /** * Toggle the player controls - * @param {boolean} toggle - Whether to show the controls + * @param {boolean} [toggle] - Whether to show the controls */ toggleControls(toggle) { - // We need controls of course... - if (!utils.is.element(this.elements.controls)) { - return; - } - - // Don't hide if no UI support or it's audio - if (!this.supported.ui || this.isAudio) { - return; - } + // Don't toggle if missing UI support or if it's audio + if (this.supported.ui && !this.isAudio) { + // Get state before change + const isHidden = hasClass(this.elements.container, this.config.classNames.hideControls); - let delay = 0; - let show = toggle; - let isEnterFullscreen = false; - - // Get toggle state if not set - if (!utils.is.boolean(toggle)) { - if (utils.is.event(toggle)) { - // Is the enter fullscreen event - isEnterFullscreen = toggle.type === 'enterfullscreen'; - - // Events that show the controls - const showEvents = [ - 'touchstart', - 'touchmove', - 'mouseenter', - 'mousemove', - 'focusin', - ]; - - // Events that delay hiding - const delayEvents = [ - 'touchmove', - 'touchend', - 'mousemove', - ]; - - // Whether to show controls - show = showEvents.includes(toggle.type); - - // Delay hiding on move events - if (delayEvents.includes(toggle.type)) { - delay = 2000; - } - - // Delay a little more for keyboard users - if (!this.touch && toggle.type === 'focusin') { - delay = 3000; - utils.toggleClass(this.elements.controls, this.config.classNames.noTransition, true); - } - } else { - show = utils.hasClass(this.elements.container, this.config.classNames.hideControls); - } - } + // Negate the argument if not undefined since adding the class to hides the controls + const force = typeof toggle === 'undefined' ? undefined : !toggle; - // Clear timer on every call - clearTimeout(this.timers.controls); + // Apply and get updated state + const hiding = toggleClass(this.elements.container, this.config.classNames.hideControls, force); - // If the mouse is not over the controls, set a timeout to hide them - if (show || this.paused || this.loading) { - // Check if controls toggled - const toggled = utils.toggleClass(this.elements.container, this.config.classNames.hideControls, false); - - // Trigger event - if (toggled) { - utils.dispatchEvent.call(this, this.media, 'controlsshown'); + // Close menu + if (hiding && this.config.controls.includes('settings') && !is.empty(this.config.settings)) { + controls.toggleMenu.call(this, false); } - // Always show controls when paused or if touch - if (this.paused || this.loading) { - return; + // Trigger event on change + if (hiding !== isHidden) { + const eventName = hiding ? 'controlshidden' : 'controlsshown'; + triggerEvent.call(this, this.media, eventName); } - // Delay for hiding on touch - if (this.touch) { - delay = 3000; - } + return !hiding; } - // If toggle is false or if we're playing (regardless of toggle), - // then set the timer to hide the controls - if (!show || this.playing) { - this.timers.controls = setTimeout(() => { - // We need controls of course... - if (!utils.is.element(this.elements.controls)) { - return; - } - - // If the mouse is over the controls (and not entering fullscreen), bail - if ((this.elements.controls.pressed || this.elements.controls.hover) && !isEnterFullscreen) { - return; - } - - // Restore transition behaviour - if (!utils.hasClass(this.elements.container, this.config.classNames.hideControls)) { - utils.toggleClass(this.elements.controls, this.config.classNames.noTransition, false); - } - - // Set hideControls class - const toggled = utils.toggleClass(this.elements.container, this.config.classNames.hideControls, this.config.hideControls); - - // Trigger event and close menu - if (toggled) { - utils.dispatchEvent.call(this, this.media, 'controlshidden'); - - if (this.config.controls.includes('settings') && !utils.is.empty(this.config.settings)) { - controls.toggleMenu.call(this, false); - } - } - }, delay); - } + return false; } /** @@ -1091,7 +994,16 @@ class Plyr { * @param {function} callback - Callback for when event occurs */ on(event, callback) { - utils.on(this.elements.container, event, callback); + on.call(this, this.elements.container, event, callback); + } + + /** + * Add event listeners once + * @param {string} event - Event type + * @param {function} callback - Callback for when event occurs + */ + once(event, callback) { + once.call(this, this.elements.container, event, callback); } /** @@ -1100,7 +1012,7 @@ class Plyr { * @param {function} callback - Callback for when event occurs */ off(event, callback) { - utils.off(this.elements.container, event, callback); + off(this.elements.container, event, callback); } /** @@ -1126,10 +1038,10 @@ class Plyr { if (soft) { if (Object.keys(this.elements).length) { // Remove elements - utils.removeElement(this.elements.buttons.play); - utils.removeElement(this.elements.captions); - utils.removeElement(this.elements.controls); - utils.removeElement(this.elements.wrapper); + removeElement(this.elements.buttons.play); + removeElement(this.elements.captions); + removeElement(this.elements.controls); + removeElement(this.elements.wrapper); // Clear for GC this.elements.buttons.play = null; @@ -1139,21 +1051,21 @@ class Plyr { } // Callback - if (utils.is.function(callback)) { + if (is.function(callback)) { callback(); } } else { // Unbind listeners - this.listeners.clear(); + unbindListeners.call(this); // Replace the container with the original element provided - utils.replaceElement(this.elements.original, this.elements.container); + replaceElement(this.elements.original, this.elements.container); // Event - utils.dispatchEvent.call(this, this.elements.original, 'destroyed', true); + triggerEvent.call(this, this.elements.original, 'destroyed', true); // Callback - if (utils.is.function(callback)) { + if (is.function(callback)) { callback.call(this.elements.original); } @@ -1171,50 +1083,37 @@ class Plyr { // Stop playback this.stop(); - // Type specific stuff - switch (`${this.provider}:${this.type}`) { - case 'html5:video': - case 'html5:audio': - // Clear timeout - clearTimeout(this.timers.loading); - - // Restore native video controls - ui.toggleNativeControls.call(this, true); - - // Clean up - done(); - - break; - - case 'youtube:video': - // Clear timers - clearInterval(this.timers.buffering); - clearInterval(this.timers.playing); - - // Destroy YouTube API - if (this.embed !== null && utils.is.function(this.embed.destroy)) { - this.embed.destroy(); - } - - // Clean up - done(); - - break; - - case 'vimeo:video': - // Destroy Vimeo API - // then clean up (wait, to prevent postmessage errors) - if (this.embed !== null) { - this.embed.unload().then(done); - } - - // Vimeo does not always return - setTimeout(done, 200); + // Provider specific stuff + if (this.isHTML5) { + // Clear timeout + clearTimeout(this.timers.loading); + + // Restore native video controls + ui.toggleNativeControls.call(this, true); + + // Clean up + done(); + } else if (this.isYouTube) { + // Clear timers + clearInterval(this.timers.buffering); + clearInterval(this.timers.playing); + + // Destroy YouTube API + if (this.embed !== null && is.function(this.embed.destroy)) { + this.embed.destroy(); + } - break; + // Clean up + done(); + } else if (this.isVimeo) { + // Destroy Vimeo API + // then clean up (wait, to prevent postmessage errors) + if (this.embed !== null) { + this.embed.unload().then(done); + } - default: - break; + // Vimeo does not always return + setTimeout(done, 200); } } @@ -1242,7 +1141,7 @@ class Plyr { * @param {string} [id] - Unique ID */ static loadSprite(url, id) { - return utils.loadSprite(url, id); + return loadSprite(url, id); } /** @@ -1253,15 +1152,15 @@ class Plyr { static setup(selector, options = {}) { let targets = null; - if (utils.is.string(selector)) { + if (is.string(selector)) { targets = Array.from(document.querySelectorAll(selector)); - } else if (utils.is.nodeList(selector)) { + } else if (is.nodeList(selector)) { targets = Array.from(selector); - } else if (utils.is.array(selector)) { - targets = selector.filter(i => utils.is.element(i)); + } else if (is.array(selector)) { + targets = selector.filter(is.element); } - if (utils.is.empty(targets)) { + if (is.empty(targets)) { return null; } @@ -1269,4 +1168,6 @@ class Plyr { } } +Plyr.defaults = cloneDeep(defaults); + export default Plyr; |