From 30e6a40865f552012f0f9abb9b5043f4daa0e20f Mon Sep 17 00:00:00 2001 From: Antoine Cordelois Date: Thu, 3 May 2018 11:42:09 +0200 Subject: Added 480p to SD labels --- src/js/controls.js | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/js/controls.js b/src/js/controls.js index 7ddcefef..4de0a990 100644 --- a/src/js/controls.js +++ b/src/js/controls.js @@ -483,6 +483,7 @@ const controls = { break; case 576: + case 480: label = 'SD'; break; -- cgit v1.2.3 From 9ebc2719d31e39b822eda42c2eb3272330e9fc5d Mon Sep 17 00:00:00 2001 From: Sam Potts Date: Sun, 6 May 2018 00:49:12 +1000 Subject: v3.3.0 --- src/js/captions.js | 4 +- src/js/controls.js | 87 ++++++++++++++++++++++++++++++++++------- src/js/defaults.js | 20 ++++++---- src/js/listeners.js | 21 ++++++---- src/js/media.js | 37 +++++------------- src/js/plugins/ads.js | 9 +++-- src/js/plugins/vimeo.js | 27 +++++++++++-- src/js/plugins/youtube.js | 9 +++-- src/js/plyr.js | 56 +++++++++++++------------- src/js/plyr.polyfilled.js | 4 +- src/js/ui.js | 38 ++++++++++++++++-- src/js/utils.js | 72 ++++++---------------------------- src/sass/base.scss | 1 + src/sass/components/poster.scss | 22 +++++++++++ src/sass/plyr.scss | 1 + 15 files changed, 247 insertions(+), 161 deletions(-) create mode 100644 src/sass/components/poster.scss (limited to 'src') 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 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 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
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 @@ -363,6 +353,13 @@ class Plyr { this.media.pause(); } + /** + * Get playing state + */ + get playing() { + return Boolean(this.ready && !this.paused && !this.ended); + } + /** * Get paused state */ @@ -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'; -- cgit v1.2.3 From 91a4b86860f8db72dba0cec0c8cb18b93e423d85 Mon Sep 17 00:00:00 2001 From: Sam Potts Date: Sun, 6 May 2018 01:03:38 +1000 Subject: Small bug fixes --- src/js/defaults.js | 2 +- src/js/plugins/ads.js | 5 ++++- src/js/plyr.js | 7 +------ src/js/plyr.polyfilled.js | 2 +- src/js/source.js | 10 +++++----- 5 files changed, 12 insertions(+), 14 deletions(-) (limited to 'src') diff --git a/src/js/defaults.js b/src/js/defaults.js index 7857644b..70319352 100644 --- a/src/js/defaults.js +++ b/src/js/defaults.js @@ -56,7 +56,7 @@ const defaults = { // Sprite (for icons) loadSprite: true, iconPrefix: 'plyr', - iconUrl: 'https://cdn.plyr.io/3.3.0/plyr.svg', + iconUrl: 'https://cdn.plyr.io/3.3.2/plyr.svg', // Blank video (used to prevent errors on source change) blankVideo: 'https://cdn.plyr.io/static/blank.mp4', diff --git a/src/js/plugins/ads.js b/src/js/plugins/ads.js index 4a3deeaa..79c00ab3 100644 --- a/src/js/plugins/ads.js +++ b/src/js/plugins/ads.js @@ -18,7 +18,6 @@ class Ads { constructor(player) { this.player = player; this.publisherId = player.config.ads.publisherId; - this.enabled = player.isHTML5 && player.isVideo && player.config.ads.enabled && utils.is.string(this.publisherId) && this.publisherId.length; this.playing = false; this.initialized = false; this.elements = { @@ -44,6 +43,10 @@ class Ads { this.load(); } + get enabled() { + return this.player.isHTML5 && this.player.isVideo && this.player.config.ads.enabled && utils.is.string(this.publisherId) && this.publisherId.length; + } + /** * Load the IMA SDK */ diff --git a/src/js/plyr.js b/src/js/plyr.js index e77a7562..23b1a42b 100644 --- a/src/js/plyr.js +++ b/src/js/plyr.js @@ -1,6 +1,6 @@ // ========================================================================== // Plyr -// plyr.js v3.3.0 +// plyr.js v3.3.2 // https://github.com/sampotts/plyr // License: The MIT License (MIT) // ========================================================================== @@ -333,11 +333,6 @@ class Plyr { return null; } - // If ads are enabled, wait for them first - /* if (this.ads.enabled && !this.ads.initialized) { - return this.ads.managerPromise.then(() => this.ads.play()).catch(() => this.media.play()); - } */ - // Return the promise (for HTML5) return this.media.play(); } diff --git a/src/js/plyr.polyfilled.js b/src/js/plyr.polyfilled.js index eae4091a..eca1fe16 100644 --- a/src/js/plyr.polyfilled.js +++ b/src/js/plyr.polyfilled.js @@ -1,6 +1,6 @@ // ========================================================================== // Plyr Polyfilled Build -// plyr.js v3.3.0 +// plyr.js v3.3.2 // https://github.com/sampotts/plyr // License: The MIT License (MIT) // ========================================================================== diff --git a/src/js/source.js b/src/js/source.js index 4e3f9186..e9a2938e 100644 --- a/src/js/source.js +++ b/src/js/source.js @@ -2,12 +2,12 @@ // Plyr source update // ========================================================================== -import { providers } from './types'; -import utils from './utils'; import html5 from './html5'; import media from './media'; -import ui from './ui'; import support from './support'; +import { providers } from './types'; +import ui from './ui'; +import utils from './utils'; const source = { // Add elements to HTML5 media (source, tracks, etc) @@ -94,8 +94,8 @@ const source = { if (this.config.autoplay) { this.media.setAttribute('autoplay', ''); } - if ('poster' in input) { - this.media.setAttribute('poster', input.poster); + if (!utils.is.empty(input.poster)) { + this.poster = input.poster; } if (this.config.loop.active) { this.media.setAttribute('loop', ''); -- cgit v1.2.3 From 00bbce08fb51fa0751b6f9795cae7a3352c33650 Mon Sep 17 00:00:00 2001 From: Sam Potts Date: Sun, 6 May 2018 01:14:41 +1000 Subject: Reverted menu change --- src/js/listeners.js | 9 --------- 1 file changed, 9 deletions(-) (limited to 'src') diff --git a/src/js/listeners.js b/src/js/listeners.js index f4e9ade3..adb60937 100644 --- a/src/js/listeners.js +++ b/src/js/listeners.js @@ -489,19 +489,12 @@ 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', ); @@ -510,7 +503,6 @@ class Listeners { event, () => { this.player.quality = event.target.value; - showHomeTab(); }, 'quality', ); @@ -519,7 +511,6 @@ class Listeners { event, () => { this.player.speed = parseFloat(event.target.value); - showHomeTab(); }, 'speed', ); -- cgit v1.2.3 From ceb6c9a10058c2ab663bd588e14333ac3564bff6 Mon Sep 17 00:00:00 2001 From: Sam Potts Date: Sun, 6 May 2018 01:16:41 +1000 Subject: v3.3.3 --- src/js/defaults.js | 2 +- src/js/plyr.js | 2 +- src/js/plyr.polyfilled.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/js/defaults.js b/src/js/defaults.js index 70319352..2e17b696 100644 --- a/src/js/defaults.js +++ b/src/js/defaults.js @@ -56,7 +56,7 @@ const defaults = { // Sprite (for icons) loadSprite: true, iconPrefix: 'plyr', - iconUrl: 'https://cdn.plyr.io/3.3.2/plyr.svg', + iconUrl: 'https://cdn.plyr.io/3.3.3/plyr.svg', // Blank video (used to prevent errors on source change) blankVideo: 'https://cdn.plyr.io/static/blank.mp4', diff --git a/src/js/plyr.js b/src/js/plyr.js index 23b1a42b..be32aa6d 100644 --- a/src/js/plyr.js +++ b/src/js/plyr.js @@ -1,6 +1,6 @@ // ========================================================================== // Plyr -// plyr.js v3.3.2 +// plyr.js v3.3.3 // https://github.com/sampotts/plyr // License: The MIT License (MIT) // ========================================================================== diff --git a/src/js/plyr.polyfilled.js b/src/js/plyr.polyfilled.js index eca1fe16..6e6d302e 100644 --- a/src/js/plyr.polyfilled.js +++ b/src/js/plyr.polyfilled.js @@ -1,6 +1,6 @@ // ========================================================================== // Plyr Polyfilled Build -// plyr.js v3.3.2 +// plyr.js v3.3.3 // https://github.com/sampotts/plyr // License: The MIT License (MIT) // ========================================================================== -- cgit v1.2.3 From 165515009266a5dc0b625cef26cf6ba1a80bb12e Mon Sep 17 00:00:00 2001 From: Sam Potts Date: Sun, 6 May 2018 01:32:51 +1000 Subject: v3.3.5 --- src/js/defaults.js | 2 +- src/js/listeners.js | 9 +++++++++ src/js/plugins/ads.js | 2 +- src/js/plyr.js | 5 +++-- src/js/plyr.polyfilled.js | 2 +- src/js/ui.js | 7 ++++++- 6 files changed, 21 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/js/defaults.js b/src/js/defaults.js index 2e17b696..4a2c92b5 100644 --- a/src/js/defaults.js +++ b/src/js/defaults.js @@ -56,7 +56,7 @@ const defaults = { // Sprite (for icons) loadSprite: true, iconPrefix: 'plyr', - iconUrl: 'https://cdn.plyr.io/3.3.3/plyr.svg', + iconUrl: 'https://cdn.plyr.io/3.3.5/plyr.svg', // Blank video (used to prevent errors on source change) blankVideo: 'https://cdn.plyr.io/static/blank.mp4', diff --git a/src/js/listeners.js b/src/js/listeners.js index adb60937..f4e9ade3 100644 --- a/src/js/listeners.js +++ b/src/js/listeners.js @@ -489,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', ); @@ -503,6 +510,7 @@ class Listeners { event, () => { this.player.quality = event.target.value; + showHomeTab(); }, 'quality', ); @@ -511,6 +519,7 @@ class Listeners { event, () => { this.player.speed = parseFloat(event.target.value); + showHomeTab(); }, 'speed', ); diff --git a/src/js/plugins/ads.js b/src/js/plugins/ads.js index 79c00ab3..0246e221 100644 --- a/src/js/plugins/ads.js +++ b/src/js/plugins/ads.js @@ -44,7 +44,7 @@ class Ads { } get enabled() { - return this.player.isHTML5 && this.player.isVideo && this.player.config.ads.enabled && utils.is.string(this.publisherId) && this.publisherId.length; + return this.player.isVideo && this.player.config.ads.enabled && !utils.is.empty(this.publisherId); } /** diff --git a/src/js/plyr.js b/src/js/plyr.js index be32aa6d..acf1ce19 100644 --- a/src/js/plyr.js +++ b/src/js/plyr.js @@ -1,6 +1,6 @@ // ========================================================================== // Plyr -// plyr.js v3.3.3 +// plyr.js v3.3.5 // https://github.com/sampotts/plyr // License: The MIT License (MIT) // ========================================================================== @@ -396,7 +396,8 @@ class Plyr { */ stop() { if (this.isHTML5) { - this.media.load(); + this.pause(); + this.restart(); } else if (utils.is.function(this.media.stop)) { this.media.stop(); } diff --git a/src/js/plyr.polyfilled.js b/src/js/plyr.polyfilled.js index 6e6d302e..ca5e219c 100644 --- a/src/js/plyr.polyfilled.js +++ b/src/js/plyr.polyfilled.js @@ -1,6 +1,6 @@ // ========================================================================== // Plyr Polyfilled Build -// plyr.js v3.3.3 +// plyr.js v3.3.5 // https://github.com/sampotts/plyr // License: The MIT License (MIT) // ========================================================================== diff --git a/src/js/ui.js b/src/js/ui.js index 609d6ab5..2347b5c8 100644 --- a/src/js/ui.js +++ b/src/js/ui.js @@ -158,7 +158,7 @@ const ui = { }, // Check playing state - checkPlaying() { + checkPlaying(event) { // Class hooks utils.toggleClass(this.elements.container, this.config.classNames.playing, this.playing); utils.toggleClass(this.elements.container, this.config.classNames.paused, this.paused); @@ -167,6 +167,11 @@ const ui = { // Set ARIA state utils.toggleState(this.elements.buttons.play, this.playing); + // Only update controls on non timeupdate events + if (utils.is.event(event) && event.type === 'timeupdate') { + return; + } + // Toggle controls this.toggleControls(!this.playing); }, -- cgit v1.2.3 From 1491b017a03fc6bd5aec7b5420fd8a036c9fb729 Mon Sep 17 00:00:00 2001 From: Sam Potts Date: Sun, 6 May 2018 16:18:10 +1000 Subject: Setup multiple players --- src/js/plyr.js | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) (limited to 'src') diff --git a/src/js/plyr.js b/src/js/plyr.js index acf1ce19..18577f5c 100644 --- a/src/js/plyr.js +++ b/src/js/plyr.js @@ -1244,6 +1244,29 @@ class Plyr { static loadSprite(url, id) { return utils.loadSprite(url, id); } + + /** + * Setup multiple instances + * @param {*} selector + * @param {object} options + */ + static setup(selector, options = {}) { + let targets = null; + + if (utils.is.string(selector)) { + targets = Array.from(document.querySelectorAll(selector)); + } else if (utils.is.nodeList(selector)) { + targets = Array.from(selector); + } else if (utils.is.array(selector)) { + targets = selector.filter(i => utils.is.element(i)); + } + + if (utils.is.empty(targets)) { + return null; + } + + return targets.map(t => new Plyr(t, options)); + } } export default Plyr; -- cgit v1.2.3 From 90919411e9e2384494f4796f2c72507685e959bb Mon Sep 17 00:00:00 2001 From: Sam Potts Date: Tue, 8 May 2018 12:57:24 +1000 Subject: Use div for poster, Vimeo fixes, Tooltip fixes --- src/js/defaults.js | 9 +++++---- src/js/media.js | 2 +- src/js/plugins/vimeo.js | 3 ++- src/js/utils.js | 2 +- src/sass/components/embed.scss | 4 ++-- src/sass/components/tooltips.scss | 1 + 6 files changed, 12 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/js/defaults.js b/src/js/defaults.js index 4a2c92b5..9b6b4e73 100644 --- a/src/js/defaults.js +++ b/src/js/defaults.js @@ -157,10 +157,10 @@ const defaults = { // Localisation i18n: { restart: 'Restart', - rewind: 'Rewind {seektime} secs', + rewind: 'Rewind {seektime}s', play: 'Play', pause: 'Pause', - fastForward: 'Forward {seektime} secs', + fastForward: 'Forward {seektime}s', seek: 'Seek', played: 'Played', buffered: 'Buffered', @@ -326,13 +326,14 @@ const defaults = { // Class hooks added to the player in different states classNames: { + type: 'plyr--{0}', + provider: 'plyr--{0}', video: 'plyr__video-wrapper', embed: 'plyr__video-embed', + embedContainer: 'plyr__video-embed__container', poster: 'plyr__poster', ads: 'plyr__ads', control: 'plyr__control', - type: 'plyr--{0}', - provider: 'plyr--{0}', playing: 'plyr--playing', paused: 'plyr--paused', stopped: 'plyr--stopped', diff --git a/src/js/media.js b/src/js/media.js index 99fc5e85..f10bea1f 100644 --- a/src/js/media.js +++ b/src/js/media.js @@ -39,7 +39,7 @@ const media = { utils.wrap(this.media, this.elements.wrapper); // Faux poster container - this.elements.poster = utils.createElement('span', { + this.elements.poster = utils.createElement('div', { class: this.config.classNames.poster, }); diff --git a/src/js/plugins/vimeo.js b/src/js/plugins/vimeo.js index f1bc123d..688d6ddc 100644 --- a/src/js/plugins/vimeo.js +++ b/src/js/plugins/vimeo.js @@ -53,6 +53,7 @@ const vimeo = { const options = { loop: player.config.loop.active, autoplay: player.autoplay, + // muted: player.muted, byline: false, portrait: false, title: false, @@ -82,7 +83,7 @@ const vimeo = { iframe.setAttribute('allow', 'autoplay'); // Inject the package - const wrapper = utils.createElement('div'); + const wrapper = utils.createElement('div', { class: player.config.classNames.embedContainer }); wrapper.appendChild(iframe); player.media = utils.replaceElement(wrapper, player.media); diff --git a/src/js/utils.js b/src/js/utils.js index 2c06e6aa..d46a7601 100644 --- a/src/js/utils.js +++ b/src/js/utils.js @@ -723,7 +723,7 @@ const utils = { } // Vimeo - if (/^https?:\/\/player.vimeo.com\/video\/\d{8,}(?=\b|\/)/.test(url)) { + if (/^https?:\/\/player.vimeo.com\/video\/\d{0,9}(?=\b|\/)/.test(url)) { return providers.vimeo; } diff --git a/src/sass/components/embed.scss b/src/sass/components/embed.scss index d72836de..be807739 100644 --- a/src/sass/components/embed.scss +++ b/src/sass/components/embed.scss @@ -32,8 +32,8 @@ $embed-padding: ((100 / 16) * 9); pointer-events: none; } - // Vimeo hack - > div { + // Only used for Vimeo + > .plyr__video-embed__container { padding-bottom: to-percentage($height); position: relative; transform: translateY(-$offset); diff --git a/src/sass/components/tooltips.scss b/src/sass/components/tooltips.scss index 19a9ce56..537e2444 100644 --- a/src/sass/components/tooltips.scss +++ b/src/sass/components/tooltips.scss @@ -19,6 +19,7 @@ transform: translate(-50%, 10px) scale(0.8); transform-origin: 50% 100%; transition: transform 0.2s 0.1s ease, opacity 0.2s 0.1s ease; + white-space: nowrap; z-index: 2; // The background triangle -- cgit v1.2.3 From bbb11e611e3d07bdbf2b77c6f12b5e5967835f82 Mon Sep 17 00:00:00 2001 From: Sam Potts Date: Tue, 8 May 2018 13:12:39 +1000 Subject: Vimeo options, docs for multiple players --- src/js/plugins/vimeo.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/js/plugins/vimeo.js b/src/js/plugins/vimeo.js index 688d6ddc..0ceb89e5 100644 --- a/src/js/plugins/vimeo.js +++ b/src/js/plugins/vimeo.js @@ -108,7 +108,10 @@ const vimeo = { // Setup instance // https://github.com/vimeo/player.js - player.embed = new window.Vimeo.Player(iframe); + player.embed = new window.Vimeo.Player(iframe, { + autopause: player.config.autopause, + muted: player.muted, + }); player.media.paused = true; player.media.currentTime = 0; -- cgit v1.2.3 From f687b81b70a73835f0190fbfa17a0fbbfcd28b7a Mon Sep 17 00:00:00 2001 From: Sam Potts Date: Tue, 8 May 2018 13:18:30 +1000 Subject: v3.3.6 --- src/js/defaults.js | 2 +- src/js/plyr.js | 2 +- src/js/plyr.polyfilled.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/js/defaults.js b/src/js/defaults.js index 9b6b4e73..a28f56ee 100644 --- a/src/js/defaults.js +++ b/src/js/defaults.js @@ -56,7 +56,7 @@ const defaults = { // Sprite (for icons) loadSprite: true, iconPrefix: 'plyr', - iconUrl: 'https://cdn.plyr.io/3.3.5/plyr.svg', + iconUrl: 'https://cdn.plyr.io/3.3.6/plyr.svg', // Blank video (used to prevent errors on source change) blankVideo: 'https://cdn.plyr.io/static/blank.mp4', diff --git a/src/js/plyr.js b/src/js/plyr.js index 18577f5c..c2a1d6e3 100644 --- a/src/js/plyr.js +++ b/src/js/plyr.js @@ -1,6 +1,6 @@ // ========================================================================== // Plyr -// plyr.js v3.3.5 +// plyr.js v3.3.6 // https://github.com/sampotts/plyr // License: The MIT License (MIT) // ========================================================================== diff --git a/src/js/plyr.polyfilled.js b/src/js/plyr.polyfilled.js index ca5e219c..0cc3c526 100644 --- a/src/js/plyr.polyfilled.js +++ b/src/js/plyr.polyfilled.js @@ -1,6 +1,6 @@ // ========================================================================== // Plyr Polyfilled Build -// plyr.js v3.3.5 +// plyr.js v3.3.6 // https://github.com/sampotts/plyr // License: The MIT License (MIT) // ========================================================================== -- cgit v1.2.3