diff options
author | Sam Potts <sam@potts.es> | 2018-05-06 00:49:12 +1000 |
---|---|---|
committer | Sam Potts <sam@potts.es> | 2018-05-06 00:49:12 +1000 |
commit | 9ebc2719d31e39b822eda42c2eb3272330e9fc5d (patch) | |
tree | 4f9c6d2eaea36caa689e9d852ce8e1490c631183 /src | |
parent | 5ca769807e773d6d6a884771ca8864e4db8c1376 (diff) | |
download | plyr-9ebc2719d31e39b822eda42c2eb3272330e9fc5d.tar.lz plyr-9ebc2719d31e39b822eda42c2eb3272330e9fc5d.tar.xz plyr-9ebc2719d31e39b822eda42c2eb3272330e9fc5d.zip |
v3.3.0
Diffstat (limited to 'src')
-rw-r--r-- | src/js/captions.js | 4 | ||||
-rw-r--r-- | src/js/controls.js | 87 | ||||
-rw-r--r-- | src/js/defaults.js | 20 | ||||
-rw-r--r-- | src/js/listeners.js | 21 | ||||
-rw-r--r-- | src/js/media.js | 37 | ||||
-rw-r--r-- | src/js/plugins/ads.js | 9 | ||||
-rw-r--r-- | src/js/plugins/vimeo.js | 27 | ||||
-rw-r--r-- | src/js/plugins/youtube.js | 9 | ||||
-rw-r--r-- | src/js/plyr.js | 56 | ||||
-rw-r--r-- | src/js/plyr.polyfilled.js | 4 | ||||
-rw-r--r-- | src/js/ui.js | 38 | ||||
-rw-r--r-- | src/js/utils.js | 72 | ||||
-rw-r--r-- | src/sass/base.scss | 1 | ||||
-rw-r--r-- | src/sass/components/poster.scss | 22 | ||||
-rw-r--r-- | src/sass/plyr.scss | 1 |
15 files changed, 247 insertions, 161 deletions
diff --git a/src/js/captions.js b/src/js/captions.js index c6618fda..e0692dcf 100644 --- a/src/js/captions.js +++ b/src/js/captions.js @@ -3,10 +3,10 @@ // TODO: Create as class // ========================================================================== -import support from './support'; -import utils from './utils'; import controls from './controls'; import i18n from './i18n'; +import support from './support'; +import utils from './utils'; const captions = { // Setup captions diff --git a/src/js/controls.js b/src/js/controls.js index 7ddcefef..5b16e950 100644 --- a/src/js/controls.js +++ b/src/js/controls.js @@ -2,12 +2,12 @@ // Plyr controls // ========================================================================== -import support from './support'; -import utils from './utils'; -import ui from './ui'; -import i18n from './i18n'; import captions from './captions'; import html5 from './html5'; +import i18n from './i18n'; +import support from './support'; +import ui from './ui'; +import utils from './utils'; // Sniff out the browser const browser = utils.getBrowser(); @@ -37,17 +37,74 @@ const controls = { // Get icon URL getIconUrl() { + const url = new URL(this.config.iconUrl, window.location); + const cors = url.host !== window.location.host || (browser.isIE && !window.svg4everybody); + return { url: this.config.iconUrl, - absolute: this.config.iconUrl.indexOf('http') === 0 || (browser.isIE && !window.svg4everybody), + cors, }; }, + // Find the UI controls and store references in custom controls + // TODO: Allow settings menus with custom controls + findElements() { + try { + this.elements.controls = utils.getElement.call(this, this.config.selectors.controls.wrapper); + + // Buttons + this.elements.buttons = { + play: utils.getElements.call(this, this.config.selectors.buttons.play), + pause: utils.getElement.call(this, this.config.selectors.buttons.pause), + restart: utils.getElement.call(this, this.config.selectors.buttons.restart), + rewind: utils.getElement.call(this, this.config.selectors.buttons.rewind), + fastForward: utils.getElement.call(this, this.config.selectors.buttons.fastForward), + mute: utils.getElement.call(this, this.config.selectors.buttons.mute), + pip: utils.getElement.call(this, this.config.selectors.buttons.pip), + airplay: utils.getElement.call(this, this.config.selectors.buttons.airplay), + settings: utils.getElement.call(this, this.config.selectors.buttons.settings), + captions: utils.getElement.call(this, this.config.selectors.buttons.captions), + fullscreen: utils.getElement.call(this, this.config.selectors.buttons.fullscreen), + }; + + // Progress + this.elements.progress = utils.getElement.call(this, this.config.selectors.progress); + + // Inputs + this.elements.inputs = { + seek: utils.getElement.call(this, this.config.selectors.inputs.seek), + volume: utils.getElement.call(this, this.config.selectors.inputs.volume), + }; + + // Display + this.elements.display = { + buffer: utils.getElement.call(this, this.config.selectors.display.buffer), + currentTime: utils.getElement.call(this, this.config.selectors.display.currentTime), + duration: utils.getElement.call(this, this.config.selectors.display.duration), + }; + + // Seek tooltip + if (utils.is.element(this.elements.progress)) { + this.elements.display.seekTooltip = this.elements.progress.querySelector(`.${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 <svg> icon createIcon(type, attributes) { const namespace = 'http://www.w3.org/2000/svg'; const iconUrl = controls.getIconUrl.call(this); - const iconPath = `${!iconUrl.absolute ? iconUrl.url : ''}#${this.config.iconPrefix}`; + const iconPath = `${!iconUrl.cors ? iconUrl.url : ''}#${this.config.iconPrefix}`; // Create <svg> const icon = document.createElementNS(namespace, 'svg'); @@ -840,11 +897,9 @@ const controls = { }, // Toggle Menu - showTab(event) { + showTab(target = '') { const { menu } = this.elements.settings; - const tab = event.target; - const show = tab.getAttribute('aria-expanded') === 'false'; - const pane = document.getElementById(tab.getAttribute('aria-controls')); + const pane = document.getElementById(target); // Nothing to show, bail if (!utils.is.element(pane)) { @@ -907,8 +962,12 @@ const controls = { current.setAttribute('tabindex', -1); // Set attributes on target - utils.toggleHidden(pane, !show); - tab.setAttribute('aria-expanded', show); + utils.toggleHidden(pane, false); + + const tabs = utils.getElements.call(this, `[aria-controls="${target}"]`); + Array.from(tabs).forEach(tab => { + tab.setAttribute('aria-expanded', true); + }); pane.removeAttribute('tabindex'); // Focus the first item @@ -1183,7 +1242,7 @@ const controls = { const icon = controls.getIconUrl.call(this); // Only load external sprite using AJAX - if (icon.absolute) { + if (icon.cors) { utils.loadSprite(icon.url, 'sprite-plyr'); } } @@ -1269,7 +1328,7 @@ const controls = { // Find the elements if need be if (!utils.is.element(this.elements.controls)) { - utils.findElements.call(this); + controls.findElements.call(this); } // Edge sometimes doesn't finish the paint so force a redraw diff --git a/src/js/defaults.js b/src/js/defaults.js index 418b60ae..7857644b 100644 --- a/src/js/defaults.js +++ b/src/js/defaults.js @@ -47,8 +47,8 @@ const defaults = { // Auto hide the controls hideControls: true, - // Revert to poster on finish (HTML5 - will cause reload) - showPosterOnEnd: false, + // Reset to start when playback ended + resetOnEnd: false, // Disable the standard context menu disableContextMenu: true, @@ -56,7 +56,7 @@ const defaults = { // Sprite (for icons) loadSprite: true, iconPrefix: 'plyr', - iconUrl: 'https://cdn.plyr.io/3.2.4/plyr.svg', + iconUrl: 'https://cdn.plyr.io/3.3.0/plyr.svg', // Blank video (used to prevent errors on source change) blankVideo: 'https://cdn.plyr.io/static/blank.mp4', @@ -192,13 +192,17 @@ const defaults = { // URLs urls: { vimeo: { - api: 'https://player.vimeo.com/api/player.js', + sdk: 'https://player.vimeo.com/api/player.js', + iframe: 'https://player.vimeo.com/video/{0}?{1}', + api: 'https://vimeo.com/api/v2/video/{0}.json', }, youtube: { - api: 'https://www.youtube.com/iframe_api', + sdk: 'https://www.youtube.com/iframe_api', + api: 'https://www.googleapis.com/youtube/v3/videos?id={0}&key={1}&fields=items(snippet(title))&part=snippet', + poster: 'https://img.youtube.com/vi/{0}/maxresdefault.jpg,https://img.youtube.com/vi/{0}/hqdefault.jpg', }, googleIMA: { - api: 'https://imasdk.googleapis.com/js/sdkloader/ima3.js', + sdk: 'https://imasdk.googleapis.com/js/sdkloader/ima3.js', }, }, @@ -324,12 +328,14 @@ const defaults = { classNames: { video: 'plyr__video-wrapper', embed: 'plyr__video-embed', + poster: 'plyr__poster', ads: 'plyr__ads', control: 'plyr__control', type: 'plyr--{0}', provider: 'plyr--{0}', - stopped: 'plyr--stopped', playing: 'plyr--playing', + paused: 'plyr--paused', + stopped: 'plyr--stopped', loading: 'plyr--loading', error: 'plyr--has-error', hover: 'plyr--hover', diff --git a/src/js/listeners.js b/src/js/listeners.js index 2664e827..f4e9ade3 100644 --- a/src/js/listeners.js +++ b/src/js/listeners.js @@ -2,9 +2,9 @@ // Plyr Event Listeners // ========================================================================== -import utils from './utils'; import controls from './controls'; import ui from './ui'; +import utils from './utils'; // Sniff out the browser const browser = utils.getBrowser(); @@ -265,12 +265,9 @@ class Listeners { // Handle the media finishing utils.on(this.player.media, 'ended', () => { // Show poster on end - if (this.player.isHTML5 && this.player.isVideo && this.player.config.showPosterOnEnd) { + if (this.player.isHTML5 && this.player.isVideo && this.player.config.resetOnEnd) { // Restart this.player.restart(); - - // Re-load media - this.player.media.load(); } }); @@ -281,7 +278,7 @@ class Listeners { utils.on(this.player.media, 'volumechange', event => ui.updateVolume.call(this.player, event)); // Handle play/pause - utils.on(this.player.media, 'playing play pause ended emptied', event => ui.checkPlaying.call(this.player, event)); + utils.on(this.player.media, 'playing play pause ended emptied timeupdate', event => ui.checkPlaying.call(this.player, event)); // Loading state utils.on(this.player.media, 'waiting canplay seeked playing', event => ui.checkLoading.call(this.player, event)); @@ -492,12 +489,19 @@ class Listeners { on(this.player.elements.settings.form, 'click', event => { event.stopPropagation(); + // Go back to home tab on click + const showHomeTab = () => { + const id = `plyr-settings-${this.player.id}-home`; + controls.showTab.call(this.player, id); + }; + // Settings menu items - use event delegation as items are added/removed if (utils.matches(event.target, this.player.config.selectors.inputs.language)) { proxy( event, () => { this.player.language = event.target.value; + showHomeTab(); }, 'language', ); @@ -506,6 +510,7 @@ class Listeners { event, () => { this.player.quality = event.target.value; + showHomeTab(); }, 'quality', ); @@ -514,11 +519,13 @@ class Listeners { event, () => { this.player.speed = parseFloat(event.target.value); + showHomeTab(); }, 'speed', ); } else { - controls.showTab.call(this.player, event); + const tab = event.target; + controls.showTab.call(this.player, tab.getAttribute('aria-controls')); } }); diff --git a/src/js/media.js b/src/js/media.js index bba2c62b..99fc5e85 100644 --- a/src/js/media.js +++ b/src/js/media.js @@ -2,15 +2,10 @@ // Plyr Media // ========================================================================== -import support from './support'; -import utils from './utils'; -import youtube from './plugins/youtube'; -import vimeo from './plugins/vimeo'; import html5 from './html5'; -import ui from './ui'; - -// Sniff out the browser -const browser = utils.getBrowser(); +import vimeo from './plugins/vimeo'; +import youtube from './plugins/youtube'; +import utils from './utils'; const media = { // Setup media @@ -33,23 +28,6 @@ const media = { utils.toggleClass(this.elements.container, this.config.classNames.type.replace('{0}', 'video'), true); } - if (this.supported.ui) { - // Check for picture-in-picture support - utils.toggleClass(this.elements.container, this.config.classNames.pip.supported, support.pip && this.isHTML5 && this.isVideo); - - // Check for airplay support - utils.toggleClass(this.elements.container, this.config.classNames.airplay.supported, support.airplay && this.isHTML5); - - // If there's no autoplay attribute, assume the video is stopped and add state class - utils.toggleClass(this.elements.container, this.config.classNames.stopped, this.config.autoplay); - - // Add iOS class - utils.toggleClass(this.elements.container, this.config.classNames.isIos, browser.isIos); - - // Add touch class - utils.toggleClass(this.elements.container, this.config.classNames.isTouch, this.touch); - } - // Inject the player wrapper if (this.isVideo) { // Create the wrapper div @@ -59,6 +37,13 @@ const media = { // Wrap the video in a container utils.wrap(this.media, this.elements.wrapper); + + // Faux poster container + this.elements.poster = utils.createElement('span', { + class: this.config.classNames.poster, + }); + + this.elements.wrapper.appendChild(this.elements.poster); } if (this.isEmbed) { @@ -75,8 +60,6 @@ const media = { break; } } else if (this.isHTML5) { - ui.setTitle.call(this); - html5.extend.call(this); } }, diff --git a/src/js/plugins/ads.js b/src/js/plugins/ads.js index b9d9ac1c..4a3deeaa 100644 --- a/src/js/plugins/ads.js +++ b/src/js/plugins/ads.js @@ -6,8 +6,8 @@ /* global google */ -import utils from '../utils'; import i18n from '../i18n'; +import utils from '../utils'; class Ads { /** @@ -52,7 +52,7 @@ class Ads { // Check if the Google IMA3 SDK is loaded or load it ourselves if (!utils.is.object(window.google) || !utils.is.object(window.google.ima)) { utils - .loadScript(this.player.config.urls.googleIMA.api) + .loadScript(this.player.config.urls.googleIMA.sdk) .then(() => { this.ready(); }) @@ -160,6 +160,9 @@ class Ads { // We only overlay ads as we only support video. request.forceNonLinearFullSlot = false; + // Mute based on current state + request.setAdWillPlayMuted(!this.player.muted); + this.loader.requestAds(request); } catch (e) { this.onAdError(e); @@ -226,7 +229,7 @@ class Ads { // Get skippable state // TODO: Skip button - // this.manager.getAdSkippableState(); + // this.player.debug.warn(this.manager.getAdSkippableState()); // Set volume to match player this.manager.setVolume(this.player.volume); diff --git a/src/js/plugins/vimeo.js b/src/js/plugins/vimeo.js index 24003d3f..f1bc123d 100644 --- a/src/js/plugins/vimeo.js +++ b/src/js/plugins/vimeo.js @@ -2,10 +2,10 @@ // Vimeo plugin // ========================================================================== -import utils from './../utils'; import captions from './../captions'; import controls from './../controls'; import ui from './../ui'; +import utils from './../utils'; const vimeo = { setup() { @@ -18,7 +18,7 @@ const vimeo = { // Load the API if not already if (!utils.is.object(window.Vimeo)) { utils - .loadScript(this.config.urls.vimeo.api) + .loadScript(this.config.urls.vimeo.sdk) .then(() => { vimeo.ready.call(this); }) @@ -68,14 +68,14 @@ const vimeo = { // Get from <div> if needed if (utils.is.empty(source)) { - source = player.media.getAttribute(this.config.attributes.embed.id); + source = player.media.getAttribute(player.config.attributes.embed.id); } const id = utils.parseVimeoId(source); // Build an iframe const iframe = utils.createElement('iframe'); - const src = `https://player.vimeo.com/video/${id}?${params}`; + const src = utils.format(player.config.urls.vimeo.iframe, id, params); iframe.setAttribute('src', src); iframe.setAttribute('allowfullscreen', ''); iframe.setAttribute('allowtransparency', ''); @@ -86,6 +86,25 @@ const vimeo = { wrapper.appendChild(iframe); player.media = utils.replaceElement(wrapper, player.media); + // Get poster image + utils.fetch(utils.format(player.config.urls.vimeo.api, id), 'json').then(response => { + if (utils.is.empty(response)) { + return; + } + + // Get the URL for thumbnail + const url = new URL(response[0].thumbnail_large); + + // Get original image + url.pathname = `${url.pathname.split('_')[0]}.jpg`; + + // Set attribute + player.media.setAttribute('poster', url.href); + + // Update + ui.setPoster.call(player); + }); + // Setup instance // https://github.com/vimeo/player.js player.embed = new window.Vimeo.Player(iframe); diff --git a/src/js/plugins/youtube.js b/src/js/plugins/youtube.js index 12bc2b11..4fde9319 100644 --- a/src/js/plugins/youtube.js +++ b/src/js/plugins/youtube.js @@ -2,9 +2,9 @@ // YouTube plugin // ========================================================================== -import utils from './../utils'; import controls from './../controls'; import ui from './../ui'; +import utils from './../utils'; // Standardise YouTube quality unit function mapQualityUnit(input) { @@ -77,7 +77,7 @@ const youtube = { youtube.ready.call(this); } else { // Load the API - utils.loadScript(this.config.urls.youtube.api).catch(error => { + utils.loadScript(this.config.urls.youtube.sdk).catch(error => { this.debug.warn('YouTube API failed to load', error); }); @@ -117,7 +117,7 @@ const youtube = { // Or via Google API const key = this.config.keys.google; if (utils.is.string(key) && !utils.is.empty(key)) { - const url = `https://www.googleapis.com/youtube/v3/videos?id=${videoId}&key=${key}&fields=items(snippet(title))&part=snippet`; + const url = utils.format(this.config.urls.youtube.api, videoId, key); utils .fetch(url) @@ -161,6 +161,9 @@ const youtube = { const container = utils.createElement('div', { id }); player.media = utils.replaceElement(container, player.media); + // Set poster image + player.media.setAttribute('poster', utils.format(player.config.urls.youtube.poster, videoId)); + // Setup instance // https://developers.google.com/youtube/iframe_api_reference player.embed = new window.YT.Player(id, { diff --git a/src/js/plyr.js b/src/js/plyr.js index 6daa403a..e77a7562 100644 --- a/src/js/plyr.js +++ b/src/js/plyr.js @@ -1,26 +1,24 @@ // ========================================================================== // Plyr -// plyr.js v3.2.4 +// plyr.js v3.3.0 // https://github.com/sampotts/plyr // License: The MIT License (MIT) // ========================================================================== -import { providers, types } from './types'; -import defaults from './defaults'; -import support from './support'; -import utils from './utils'; - +import captions from './captions'; import Console from './console'; +import controls from './controls'; +import defaults from './defaults'; import Fullscreen from './fullscreen'; import Listeners from './listeners'; -import Storage from './storage'; -import Ads from './plugins/ads'; - -import captions from './captions'; -import controls from './controls'; import media from './media'; +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'; // Private properties // TODO: Use a WeakMap for private globals @@ -134,17 +132,9 @@ class Plyr { } // Cache original element state for .destroy() - // TODO: Investigate a better solution as I suspect this causes reported double load issues? - setTimeout(() => { - const clone = this.media.cloneNode(true); - - // Prevent the clone autoplaying - if (clone.getAttribute('autoplay')) { - clone.pause(); - } - - this.elements.original = clone; - }, 0); + const clone = this.media.cloneNode(true); + clone.autoplay = false; + this.elements.original = clone; // Set media type based on tag or data attribute // Supported: video, audio, vimeo, youtube @@ -364,6 +354,13 @@ class Plyr { } /** + * Get playing state + */ + get playing() { + return Boolean(this.ready && !this.paused && !this.ended); + } + + /** * Get paused state */ get paused() { @@ -371,10 +368,10 @@ class Plyr { } /** - * Get playing state + * Get stopped state */ - get playing() { - return Boolean(this.ready && !this.paused && !this.ended && (this.isHTML5 ? this.media.readyState > 2 : true)); + get stopped() { + return Boolean(this.paused && this.currentTime === 0); } /** @@ -799,17 +796,18 @@ class Plyr { } /** - * Set the poster image for a HTML5 video + * Set the poster image for a video * @param {input} - the URL for the new poster image */ set poster(input) { - if (!this.isHTML5 || !this.isVideo) { - this.debug.warn('Poster can only be set on HTML5 video'); + if (!this.isVideo) { + this.debug.warn('Poster can only be set for video'); return; } if (utils.is.string(input)) { this.media.setAttribute('poster', input); + ui.setPoster.call(this); } } @@ -817,7 +815,7 @@ class Plyr { * Get the current poster image */ get poster() { - if (!this.isHTML5 || !this.isVideo) { + if (!this.isVideo) { return null; } diff --git a/src/js/plyr.polyfilled.js b/src/js/plyr.polyfilled.js index a4fd7afa..eae4091a 100644 --- a/src/js/plyr.polyfilled.js +++ b/src/js/plyr.polyfilled.js @@ -1,14 +1,12 @@ // ========================================================================== // Plyr Polyfilled Build -// plyr.js v3.2.4 +// plyr.js v3.3.0 // https://github.com/sampotts/plyr // License: The MIT License (MIT) // ========================================================================== import 'babel-polyfill'; - import 'custom-event-polyfill'; - import Plyr from './plyr'; export default Plyr; diff --git a/src/js/ui.js b/src/js/ui.js index ee77a2dd..609d6ab5 100644 --- a/src/js/ui.js +++ b/src/js/ui.js @@ -2,10 +2,14 @@ // Plyr UI // ========================================================================== -import utils from './utils'; import captions from './captions'; import controls from './controls'; import i18n from './i18n'; +import support from './support'; +import utils from './utils'; + +// Sniff out the browser +const browser = utils.getBrowser(); const ui = { addStyleHook() { @@ -78,6 +82,18 @@ const ui = { // Update the UI ui.checkPlaying.call(this); + // Check for picture-in-picture support + utils.toggleClass(this.elements.container, this.config.classNames.pip.supported, support.pip && this.isHTML5 && this.isVideo); + + // Check for airplay support + utils.toggleClass(this.elements.container, this.config.classNames.airplay.supported, support.airplay && this.isHTML5); + + // Add iOS class + utils.toggleClass(this.elements.container, this.config.classNames.isIos, browser.isIos); + + // Add touch class + utils.toggleClass(this.elements.container, this.config.classNames.isTouch, this.touch); + // Ready for API calls this.ready = true; @@ -88,6 +104,9 @@ const ui = { // Set the title ui.setTitle.call(this); + + // Set the poster image + ui.setPoster.call(this); }, // Setup aria attribute for play and iframe title @@ -121,16 +140,29 @@ const ui = { // Default to media type const title = !utils.is.empty(this.config.title) ? this.config.title : 'video'; + const format = i18n.get('frameTitle', this.config); + + iframe.setAttribute('title', format.replace('{title}', title)); + } + }, - iframe.setAttribute('title', i18n.get('frameTitle', this.config)); + // Set the poster image + setPoster() { + if (!utils.is.element(this.elements.poster) || utils.is.empty(this.poster)) { + return; } + + // Set the inline style + const posters = this.poster.split(','); + this.elements.poster.style.backgroundImage = posters.map(p => `url('${p}')`).join(','); }, // Check playing state checkPlaying() { // Class hooks utils.toggleClass(this.elements.container, this.config.classNames.playing, this.playing); - utils.toggleClass(this.elements.container, this.config.classNames.stopped, this.paused); + utils.toggleClass(this.elements.container, this.config.classNames.paused, this.paused); + utils.toggleClass(this.elements.container, this.config.classNames.stopped, this.stopped); // Set ARIA state utils.toggleState(this.elements.buttons.play, this.playing); diff --git a/src/js/utils.js b/src/js/utils.js index fca40f53..2c06e6aa 100644 --- a/src/js/utils.js +++ b/src/js/utils.js @@ -3,7 +3,6 @@ // ========================================================================== import loadjs from 'loadjs'; - import support from './support'; import { providers } from './types'; @@ -269,14 +268,14 @@ const utils = { parent.appendChild(utils.createElement(type, attributes, text)); }, - // Remove an element + // Remove element(s) removeElement(element) { - if (!utils.is.element(element) || !utils.is.element(element.parentNode)) { + if (utils.is.nodeList(element) || utils.is.array(element)) { + Array.from(element).forEach(utils.removeElement); return; } - if (utils.is.nodeList(element) || utils.is.array(element)) { - Array.from(element).forEach(utils.removeElement); + if (!utils.is.element(element) || !utils.is.element(element.parentNode)) { return; } @@ -435,60 +434,6 @@ const utils = { return this.elements.container.querySelector(selector); }, - // Find the UI controls and store references in custom controls - // TODO: Allow settings menus with custom controls - findElements() { - try { - this.elements.controls = utils.getElement.call(this, this.config.selectors.controls.wrapper); - - // Buttons - this.elements.buttons = { - play: utils.getElements.call(this, this.config.selectors.buttons.play), - pause: utils.getElement.call(this, this.config.selectors.buttons.pause), - restart: utils.getElement.call(this, this.config.selectors.buttons.restart), - rewind: utils.getElement.call(this, this.config.selectors.buttons.rewind), - fastForward: utils.getElement.call(this, this.config.selectors.buttons.fastForward), - mute: utils.getElement.call(this, this.config.selectors.buttons.mute), - pip: utils.getElement.call(this, this.config.selectors.buttons.pip), - airplay: utils.getElement.call(this, this.config.selectors.buttons.airplay), - settings: utils.getElement.call(this, this.config.selectors.buttons.settings), - captions: utils.getElement.call(this, this.config.selectors.buttons.captions), - fullscreen: utils.getElement.call(this, this.config.selectors.buttons.fullscreen), - }; - - // Progress - this.elements.progress = utils.getElement.call(this, this.config.selectors.progress); - - // Inputs - this.elements.inputs = { - seek: utils.getElement.call(this, this.config.selectors.inputs.seek), - volume: utils.getElement.call(this, this.config.selectors.inputs.volume), - }; - - // Display - this.elements.display = { - buffer: utils.getElement.call(this, this.config.selectors.display.buffer), - currentTime: utils.getElement.call(this, this.config.selectors.display.currentTime), - duration: utils.getElement.call(this, this.config.selectors.display.duration), - }; - - // Seek tooltip - if (utils.is.element(this.elements.progress)) { - this.elements.display.seekTooltip = this.elements.progress.querySelector(`.${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; - } - }, - // Get the focused element getFocusElement() { let focused = document.activeElement; @@ -632,6 +577,15 @@ const utils = { element.setAttribute('aria-pressed', state); }, + // Format string + format(input, ...args) { + if (utils.is.empty(input)) { + return input; + } + + return input.toString().replace(/{(\d+)}/g, (match, i) => utils.is.string(args[i]) ? args[i] : ''); + }, + // Get percentage getPercentage(current, max) { if (current === 0 || max === 0 || Number.isNaN(current) || Number.isNaN(max)) { diff --git a/src/sass/base.scss b/src/sass/base.scss index bf162c8a..9bb9d98a 100644 --- a/src/sass/base.scss +++ b/src/sass/base.scss @@ -29,6 +29,7 @@ button { font: inherit; line-height: inherit; + width: auto; } // Ignore focus diff --git a/src/sass/components/poster.scss b/src/sass/components/poster.scss new file mode 100644 index 00000000..b2518a78 --- /dev/null +++ b/src/sass/components/poster.scss @@ -0,0 +1,22 @@ +// -------------------------------------------------------------- +// Faux poster overlay +// -------------------------------------------------------------- + +.plyr__poster { + background-color: #000; + background-position: 50% 50%; + background-repeat: no-repeat; + background-size: 100% 100%; + height: 100%; + left: 0; + opacity: 0; + position: absolute; + top: 0; + transition: opacity 0.3s ease; + width: 100%; + z-index: 1; +} + +.plyr--stopped .plyr__poster { + opacity: 1; +} diff --git a/src/sass/plyr.scss b/src/sass/plyr.scss index 14856957..65134331 100644 --- a/src/sass/plyr.scss +++ b/src/sass/plyr.scss @@ -32,6 +32,7 @@ @import 'components/embed'; @import 'components/menus'; @import 'components/progress'; +@import 'components/poster'; @import 'components/sliders'; @import 'components/times'; @import 'components/tooltips'; |