diff options
Diffstat (limited to 'src/js/plyr.js')
-rw-r--r-- | src/js/plyr.js | 297 |
1 files changed, 134 insertions, 163 deletions
diff --git a/src/js/plyr.js b/src/js/plyr.js index 0786334d..d4b63874 100644 --- a/src/js/plyr.js +++ b/src/js/plyr.js @@ -6,9 +6,10 @@ // ========================================================================== import captions from './captions'; +import defaults from './config/defaults'; +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'; @@ -16,9 +17,14 @@ import Ads from './plugins/ads'; 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,18 +47,18 @@ 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, @@ -108,7 +114,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; } @@ -144,7 +150,6 @@ class Plyr { // Embed properties let iframe = null; let url = null; - let params = null; // Different setup based on type switch (type) { @@ -153,10 +158,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; @@ -166,24 +171,20 @@ 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.searchParams.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')); } else { this.config.playsinline = true; } @@ -197,7 +198,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; } @@ -245,6 +246,8 @@ class Plyr { return; } + this.eventListeners = []; + // Create listeners this.listeners = new Listeners(this); @@ -255,14 +258,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'); + wrap(this.media, this.elements.container); } - // Allow focus to be captured - this.elements.container.setAttribute('tabindex', 0); - // Add style hook ui.addStyleHook.call(this); @@ -271,7 +271,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}`); }); } @@ -330,7 +330,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; } @@ -342,7 +342,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; } @@ -383,7 +383,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(); @@ -399,7 +399,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(); } } @@ -416,7 +416,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); } /** @@ -424,7 +424,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); } /** @@ -438,7 +438,7 @@ class Plyr { } // Validate input - const inputIsValid = utils.is.number(input) && input > 0; + const inputIsValid = is.number(input) && input > 0; // Set this.media.currentTime = inputIsValid ? Math.min(input, this.duration) : 0; @@ -461,7 +461,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; } @@ -505,17 +505,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); } @@ -535,7 +535,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; } } @@ -553,7 +553,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 : 1); } /** @@ -562,7 +562,7 @@ class Plyr { */ decreaseVolume(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 : 1); } /** @@ -573,12 +573,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; } @@ -624,15 +624,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; } @@ -669,36 +669,31 @@ class Plyr { * @param {number} input - Quality level */ set quality(input) { - let quality = null; - - if (!utils.is.empty(input)) { - quality = Number(input); - } - - if (!utils.is.number(quality)) { - quality = this.storage.get('quality'); - } - - if (!utils.is.number(quality)) { - quality = this.config.quality.selected; - } - - if (!utils.is.number(quality)) { - quality = this.config.quality.default; - } + const config = this.config.quality; + const options = this.options.quality; - if (!this.options.quality.length) { + if (!options.length) { return; } - 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; + let quality = [ + !is.empty(input) && Number(input), + this.storage.get('quality'), + config.selected, + config.default, + ].find(is.number); + + if (!options.includes(quality)) { + const value = closest(options, quality); + this.debug.warn(`Unsupported quality option: ${quality}, using ${value} instead`); + quality = value; } + // Trigger request event + triggerEvent.call(this, this.media, 'qualityrequested', false, { quality }); + // Update config - this.config.quality.selected = quality; + config.selected = quality; // Set quality this.media.quality = quality; @@ -717,7 +712,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; @@ -797,7 +792,7 @@ class Plyr { return; } - ui.setPoster.call(this, input); + ui.setPoster.call(this, input, false).catch(() => {}); } /** @@ -816,7 +811,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; } @@ -832,25 +827,7 @@ 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 active = utils.is.boolean(input) ? input : !this.elements.container.classList.contains(this.config.classNames.captions.active); - - // Toggle state - utils.toggleState(this.elements.buttons.captions, active); - - // Add class hook - utils.toggleClass(this.elements.container, this.config.classNames.captions.active, active); - - // Update state and trigger event - if (active !== this.captions.active) { - this.captions.active = active; - utils.dispatchEvent.call(this, this.media, this.captions.active ? 'captionsenabled' : 'captionsdisabled'); - } + captions.toggle.call(this, input, false); } /** @@ -858,15 +835,15 @@ class Plyr { * @param {number} - Caption index */ set currentTrack(input) { - captions.set.call(this, input); + captions.set.call(this, input, false); } /** * Get the current caption track index (-1 if disabled) */ get currentTrack() { - const { active, currentTrack } = this.captions; - return active ? currentTrack : -1; + const { toggled, currentTrack } = this.captions; + return toggled ? currentTrack : -1; } /** @@ -875,7 +852,7 @@ class Plyr { * @param {string} - Two character ISO language code (e.g. EN, FR, PT, etc) */ set language(input) { - captions.setLanguage.call(this, input); + captions.setLanguage.call(this, input, false); } /** @@ -902,7 +879,7 @@ class Plyr { } // 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 === states.inline; // Toggle based on current state this.media.webkitSetPresentationMode(toggle ? states.pip : states.inline); @@ -938,22 +915,22 @@ class Plyr { // Don't toggle if missing UI support or if it's audio if (this.supported.ui && !this.isAudio) { // Get state before change - const isHidden = utils.hasClass(this.elements.container, this.config.classNames.hideControls); + const isHidden = 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; // Apply and get updated state - const hiding = utils.toggleClass(this.elements.container, this.config.classNames.hideControls, force); + const hiding = toggleClass(this.elements.container, this.config.classNames.hideControls, force); // Close menu - if (hiding && this.config.controls.includes('settings') && !utils.is.empty(this.config.settings)) { + if (hiding && this.config.controls.includes('settings') && !is.empty(this.config.settings)) { controls.toggleMenu.call(this, false); } // Trigger event on change if (hiding !== isHidden) { const eventName = hiding ? 'controlshidden' : 'controlsshown'; - utils.dispatchEvent.call(this, this.media, eventName); + triggerEvent.call(this, this.media, eventName); } return !hiding; } @@ -966,16 +943,23 @@ 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); } - /** * Remove event listeners * @param {string} event - Event type * @param {function} callback - Callback for when event occurs */ off(event, callback) { - utils.off(this.elements.container, event, callback); + off(this.elements.container, event, callback); } /** @@ -1001,10 +985,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; @@ -1014,21 +998,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); } @@ -1046,50 +1030,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); } } @@ -1117,7 +1088,7 @@ class Plyr { * @param {string} [id] - Unique ID */ static loadSprite(url, id) { - return utils.loadSprite(url, id); + return loadSprite(url, id); } /** @@ -1128,15 +1099,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(utils.is.element); + } else if (is.array(selector)) { + targets = selector.filter(is.element); } - if (utils.is.empty(targets)) { + if (is.empty(targets)) { return null; } @@ -1144,6 +1115,6 @@ class Plyr { } } -Plyr.defaults = utils.cloneDeep(defaults); +Plyr.defaults = cloneDeep(defaults); export default Plyr; |