From 006871074032e5b24408fb654eb2856585c491e1 Mon Sep 17 00:00:00 2001 From: Sam Potts Date: Mon, 6 Nov 2017 19:38:31 +1100 Subject: Started on documentation and aspect ratio option --- src/js/controls.js | 16 ++--- src/js/defaults.js | 3 + src/js/listeners.js | 7 ++- src/js/media.js | 5 +- src/js/plugins/vimeo.js | 7 +++ src/js/plugins/youtube.js | 4 ++ src/js/plyr.js | 62 ++++++++++++++----- src/js/ui.js | 6 +- src/js/utils.js | 134 ++++++++++++++++++++--------------------- src/less/components/embed.less | 10 ++- 10 files changed, 156 insertions(+), 98 deletions(-) (limited to 'src') diff --git a/src/js/controls.js b/src/js/controls.js index d40165e1..ac7ba2b6 100644 --- a/src/js/controls.js +++ b/src/js/controls.js @@ -6,11 +6,14 @@ import support from './support'; import utils from './utils'; import ui from './ui'; +// Sniff out the browser +const browser = utils.getBrowser(); + const controls = { // Webkit polyfill for lower fill range updateRangeFill(target) { // WebKit only - if (!this.browser.isWebkit) { + if (!browser.isWebkit) { return; } @@ -49,7 +52,7 @@ const controls = { getIconUrl() { return { url: this.config.iconUrl, - absolute: this.config.iconUrl.indexOf('http') === 0 || (this.browser.isIE && !window.svg4everybody), + absolute: this.config.iconUrl.indexOf('http') === 0 || (browser.isIE && !window.svg4everybody), }; }, @@ -1139,14 +1142,11 @@ const controls = { inject() { // Sprite if (this.config.loadSprite) { - const iconUrl = controls.getIconUrl.call(this); + const icon = controls.getIconUrl.call(this); // Only load external sprite using AJAX - if (iconUrl.absolute) { - this.log(`AJAX loading absolute SVG sprite ${this.browser.isIE ? '(due to IE)' : ''}`); - utils.loadSprite(iconUrl.url, 'sprite-plyr'); - } else { - this.log('Sprite will be used as external resource directly'); + if (icon.absolute) { + utils.loadSprite(icon.url, 'sprite-plyr'); } } diff --git a/src/js/defaults.js b/src/js/defaults.js index 837b981b..a3d95bb4 100644 --- a/src/js/defaults.js +++ b/src/js/defaults.js @@ -45,6 +45,9 @@ const defaults = { // Pass a custom duration duration: null, + // Aspect ratio (for embeds) + ratio: '16:9', + // Quality default quality: { default: 'default', diff --git a/src/js/listeners.js b/src/js/listeners.js index 9b84a729..29038a21 100644 --- a/src/js/listeners.js +++ b/src/js/listeners.js @@ -9,6 +9,9 @@ import fullscreen from './fullscreen'; import storage from './storage'; import ui from './ui'; +// Sniff out the browser +const browser = utils.getBrowser(); + const listeners = { // Listen for media events media() { @@ -134,7 +137,7 @@ const listeners = { // Listen for control events controls() { // IE doesn't support input event, so we fallback to change - const inputEvent = this.browser.isIE ? 'change' : 'input'; + const inputEvent = browser.isIE ? 'change' : 'input'; let last = null; // Trigger custom and default handlers @@ -468,7 +471,7 @@ const listeners = { ); // Polyfill for lower fill in for webkit - if (this.browser.isWebkit) { + if (browser.isWebkit) { utils.on(utils.getElements.call(this, 'input[type="range"]'), 'input', event => { controls.updateRangeFill.call(this, event.target); }); diff --git a/src/js/media.js b/src/js/media.js index 9e53f5fc..46e6bec6 100644 --- a/src/js/media.js +++ b/src/js/media.js @@ -8,6 +8,9 @@ import youtube from './plugins/youtube'; import vimeo from './plugins/vimeo'; import ui from './ui'; +// Sniff out the browser +const browser = utils.getBrowser(); + const media = { // Setup media setup() { @@ -45,7 +48,7 @@ const media = { utils.toggleClass(this.elements.container, this.config.classNames.stopped, this.config.autoplay); // Add iOS class - utils.toggleClass(this.elements.container, this.config.classNames.isIos, this.browser.isIos); + utils.toggleClass(this.elements.container, this.config.classNames.isIos, browser.isIos); // Add touch class utils.toggleClass(this.elements.container, this.config.classNames.isTouch, support.touch); diff --git a/src/js/plugins/vimeo.js b/src/js/plugins/vimeo.js index 0b815fa5..0f6aa4db 100644 --- a/src/js/plugins/vimeo.js +++ b/src/js/plugins/vimeo.js @@ -15,6 +15,13 @@ const vimeo = { // Add embed class for responsive utils.toggleClass(this.elements.wrapper, this.config.classNames.embed, true); + // Set aspect ratio + const ratio = this.config.ratio.split(':'); + const padding = 100 / ratio[0] * ratio[1]; + const offset = (100 - padding) / 2; + this.elements.wrapper.style.paddingBottom = `${padding}%`; + this.media.style.transform = `translateY(-${offset}%)`; + // Set ID this.media.setAttribute('id', utils.generateId(this.type)); diff --git a/src/js/plugins/youtube.js b/src/js/plugins/youtube.js index 38f649a5..2c8557dc 100644 --- a/src/js/plugins/youtube.js +++ b/src/js/plugins/youtube.js @@ -17,6 +17,10 @@ const youtube = { // Add embed class for responsive utils.toggleClass(this.elements.wrapper, this.config.classNames.embed, true); + // Set aspect ratio + const ratio = this.config.ratio.split(':'); + this.elements.wrapper.style.paddingBottom = `${100 / ratio[0] * ratio[1]}%`; + // Set ID this.media.setAttribute('id', utils.generateId(this.type)); diff --git a/src/js/plyr.js b/src/js/plyr.js index 5c28887e..08711172 100644 --- a/src/js/plyr.js +++ b/src/js/plyr.js @@ -192,10 +192,7 @@ class Plyr { return; } - // Sniff out the browser - this.browser = utils.getBrowser(); - - // Load saved settings from localStorage + // Setup local storage for user settings storage.setup.call(this); // Check for support again but with type @@ -237,17 +234,27 @@ class Plyr { } } + // --------------------------------------- // API // --------------------------------------- + /** + * If the player is HTML5 + */ get isHTML5() { return types.html5.includes(this.type); } + + /** + * If the player is an embed - e.g. YouTube or Vimeo + */ get isEmbed() { return types.embed.includes(this.type); } - // Play + /** + * Play the media + */ play() { if ('play' in this.media) { this.media.play(); @@ -257,7 +264,9 @@ class Plyr { return this; } - // Pause + /** + * Pause the media + */ pause() { if ('pause' in this.media) { this.media.pause(); @@ -267,7 +276,10 @@ class Plyr { return this; } - // Toggle playback + /** + * Toggle playback based on current status + * @param {boolean} toggle + */ togglePlay(toggle) { // True toggle if nothing passed if ((!utils.is.boolean(toggle) && this.media.paused) || toggle) { @@ -277,31 +289,43 @@ class Plyr { return this.pause(); } - // Stop + /** + * Stop playback + */ stop() { return this.restart().pause(); } - // Restart + /** + * Restart playback + */ restart() { this.currentTime = 0; return this; } - // Rewind + /** + * Rewind + * @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); return this; } - // Fast forward + /** + * Fast forward + * @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); return this; } - // Seek to time - // The input parameter can be an event or a number + /** + * Seek to a time + * @param {number} input - where to seek to in seconds. Defaults to 0 (the start) + */ set currentTime(input) { let targetTime = 0; @@ -327,7 +351,9 @@ class Plyr { return Number(this.media.currentTime); } - // Duration + /** + * Get the duration of the current media + */ get duration() { // Faux duration set via config const fauxDuration = parseInt(this.config.duration, 10); @@ -339,7 +365,10 @@ class Plyr { return !Number.isNaN(fauxDuration) ? fauxDuration : realDuration; } - // Volume + /** + * Set the player volume + * @param {number} value - must be between 0 and 1. Defaults to the value from local storage and config.volume if not set in storage + */ set volume(value) { let volume = value; const max = 1; @@ -377,6 +406,9 @@ class Plyr { } } + /** + * Get the current player volume + */ get volume() { return this.media.volume; } diff --git a/src/js/ui.js b/src/js/ui.js index c0448054..aa579d8d 100644 --- a/src/js/ui.js +++ b/src/js/ui.js @@ -84,15 +84,17 @@ const ui = { // Update the UI ui.checkPlaying.call(this); + // Ready for API calls this.ready = true; // Ready event at end of execution stack utils.dispatchEvent.call(this, this.media, 'ready'); // Autoplay - if (this.config.autoplay) { + // TODO: check we still need this? + /* if (this.isEmbed && this.config.autoplay) { this.play(); - } + } */ }, // Show the duration on metadataloaded diff --git a/src/js/utils.js b/src/js/utils.js index 4296f345..70519fac 100644 --- a/src/js/utils.js +++ b/src/js/utils.js @@ -89,6 +89,73 @@ const utils = { firstScriptTag.parentNode.insertBefore(tag, firstScriptTag); }, + // Load an external SVG sprite + loadSprite(url, id) { + if (typeof url !== 'string') { + return; + } + + const prefix = 'cache-'; + const hasId = typeof id === 'string'; + let isCached = false; + + function updateSprite(data) { + // Inject content + this.innerHTML = data; + + // Inject the SVG to the body + document.body.insertBefore(this, document.body.childNodes[0]); + } + + // Only load once + if (!hasId || !document.querySelectorAll(`#${id}`).length) { + // Create container + const container = document.createElement('div'); + container.setAttribute('hidden', ''); + + if (hasId) { + container.setAttribute('id', id); + } + + // Check in cache + if (support.storage) { + const cached = window.localStorage.getItem(prefix + id); + isCached = cached !== null; + + if (isCached) { + const data = JSON.parse(cached); + updateSprite.call(container, data.content); + } + } + + // ReSharper disable once InconsistentNaming + const xhr = new XMLHttpRequest(); + + // XHR for Chrome/Firefox/Opera/Safari + if ('withCredentials' in xhr) { + xhr.open('GET', url, true); + } else { + return; + } + + // Once loaded, inject to container and body + xhr.onload = () => { + if (support.storage) { + window.localStorage.setItem( + prefix + id, + JSON.stringify({ + content: xhr.responseText, + }) + ); + } + + updateSprite.call(container, xhr.responseText); + }; + + xhr.send(); + } + }, + // Generate a random ID generateId(prefix) { return `${prefix}-${Math.floor(Math.random() * 10000)}`; @@ -564,73 +631,6 @@ const utils = { return fragment.firstChild.innerText; }, - // Load an SVG sprite - loadSprite(url, id) { - if (typeof url !== 'string') { - return; - } - - const prefix = 'cache-'; - const hasId = typeof id === 'string'; - let isCached = false; - - function updateSprite(data) { - // Inject content - this.innerHTML = data; - - // Inject the SVG to the body - document.body.insertBefore(this, document.body.childNodes[0]); - } - - // Only load once - if (!hasId || !document.querySelectorAll(`#${id}`).length) { - // Create container - const container = document.createElement('div'); - container.setAttribute('hidden', ''); - - if (hasId) { - container.setAttribute('id', id); - } - - // Check in cache - if (support.storage) { - const cached = window.localStorage.getItem(prefix + id); - isCached = cached !== null; - - if (isCached) { - const data = JSON.parse(cached); - updateSprite.call(container, data.content); - } - } - - // ReSharper disable once InconsistentNaming - const xhr = new XMLHttpRequest(); - - // XHR for Chrome/Firefox/Opera/Safari - if ('withCredentials' in xhr) { - xhr.open('GET', url, true); - } else { - return; - } - - // Once loaded, inject to container and body - xhr.onload = () => { - if (support.storage) { - window.localStorage.setItem( - prefix + id, - JSON.stringify({ - content: xhr.responseText, - }) - ); - } - - updateSprite.call(container, xhr.responseText); - }; - - xhr.send(); - } - }, - // Get the transition end event transitionEnd: (() => { const element = document.createElement('span'); diff --git a/src/less/components/embed.less b/src/less/components/embed.less index f6b5b307..dcf889b7 100644 --- a/src/less/components/embed.less +++ b/src/less/components/embed.less @@ -4,7 +4,11 @@ // -------------------------------------------------------------- .plyr__video-embed { - padding-bottom: 56.25%; /* 16:9 */ + // Default to 16:9 ratio but this is set by JavaScript based on config + @padding: ((100 / 16) * 9); + @offset: unit((100 - @padding) / 2, ~'%'); + + padding-bottom: unit(@padding, ~'%'); height: 0; iframe { @@ -20,8 +24,8 @@ // Vimeo hack > div { position: relative; - padding-bottom: 200%; - transform: translateY(-35.95%); + padding-bottom: 100%; + transform: translateY(-@offset); } } // To allow mouse events to be captured if full support -- cgit v1.2.3 From f8ecea8fb700059ee6eee7e5dd05d8ed772c0d12 Mon Sep 17 00:00:00 2001 From: Sam Potts Date: Mon, 6 Nov 2017 19:47:14 +1100 Subject: Added Vimeo playback speed --- src/js/plugins/vimeo.js | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/js/plugins/vimeo.js b/src/js/plugins/vimeo.js index 0f6aa4db..76846cf9 100644 --- a/src/js/plugins/vimeo.js +++ b/src/js/plugins/vimeo.js @@ -4,6 +4,7 @@ import utils from './../utils'; import captions from './../captions'; +import controls from './../controls'; import ui from './../ui'; const vimeo = { @@ -51,6 +52,7 @@ const vimeo = { byline: false, portrait: false, title: false, + speed: true, transparent: 0, }; const params = utils.buildUrlParameters(options); @@ -112,12 +114,16 @@ const vimeo = { }); // Playback speed - // Not currently supported in Vimeo + let { playbackRate } = player.media; Object.defineProperty(player.media, 'playbackRate', { get() { - return null; + return playbackRate; + }, + set(input) { + playbackRate = input; + player.embed.setPlaybackRate(input); + utils.dispatchEvent.call(player, player.media, 'ratechange'); }, - set() {}, }); // Volume @@ -155,6 +161,11 @@ const vimeo = { }, }); + // Get available speeds + if (player.config.controls.includes('settings') && player.config.settings.includes('speed')) { + controls.setSpeedMenu.call(player); + } + // Get title player.embed.getVideoTitle().then(title => { player.config.title = title; -- cgit v1.2.3 From 62c1b368cf254fdaba2612a308b6ce0a51ec853c Mon Sep 17 00:00:00 2001 From: Sam Potts Date: Mon, 6 Nov 2017 19:53:27 +1100 Subject: Cleanup --- src/js/plugins/vimeo.js | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) (limited to 'src') diff --git a/src/js/plugins/vimeo.js b/src/js/plugins/vimeo.js index 76846cf9..dce3af03 100644 --- a/src/js/plugins/vimeo.js +++ b/src/js/plugins/vimeo.js @@ -47,8 +47,8 @@ const vimeo = { // Get Vimeo params for the iframe const options = { - loop: this.config.loop.active, - autoplay: this.config.autoplay, + loop: player.config.loop.active, + autoplay: player.config.autoplay, byline: false, portrait: false, title: false, @@ -56,7 +56,7 @@ const vimeo = { transparent: 0, }; const params = utils.buildUrlParameters(options); - const id = utils.parseVimeoId(this.embedId); + const id = utils.parseVimeoId(player.embedId); // Build an iframe const iframe = utils.createElement('iframe'); @@ -66,7 +66,7 @@ const vimeo = { player.media.appendChild(iframe); // Setup instance - // https://github.com/vimeo/this.js + // https://github.com/vimeo/player.js player.embed = new window.Vimeo.Player(iframe); // Create a faux HTML5 API using the Vimeo API @@ -108,7 +108,7 @@ const vimeo = { // Restore pause state if (paused) { - this.pause(); + player.pause(); } }, }); @@ -174,7 +174,7 @@ const vimeo = { // Get current time player.embed.getCurrentTime().then(value => { currentTime = value; - utils.dispatchEvent.call(this, this.media, 'timeupdate'); + utils.dispatchEvent.call(player, player.media, 'timeupdate'); }); // Get duration @@ -220,31 +220,31 @@ const vimeo = { utils.dispatchEvent.call(player, player.media, 'pause'); }); - this.embed.on('timeupdate', data => { - this.media.seeking = false; + player.embed.on('timeupdate', data => { + player.media.seeking = false; currentTime = data.seconds; - utils.dispatchEvent.call(this, this.media, 'timeupdate'); + utils.dispatchEvent.call(player, player.media, 'timeupdate'); }); - this.embed.on('progress', data => { - this.media.buffered = data.percent; - utils.dispatchEvent.call(this, this.media, 'progress'); + player.embed.on('progress', data => { + player.media.buffered = data.percent; + utils.dispatchEvent.call(player, player.media, 'progress'); if (parseInt(data.percent, 10) === 1) { // Trigger event - utils.dispatchEvent.call(this, this.media, 'canplaythrough'); + utils.dispatchEvent.call(player, player.media, 'canplaythrough'); } }); - this.embed.on('seeked', () => { - this.media.seeking = false; - utils.dispatchEvent.call(this, this.media, 'seeked'); - utils.dispatchEvent.call(this, this.media, 'play'); + player.embed.on('seeked', () => { + player.media.seeking = false; + utils.dispatchEvent.call(player, player.media, 'seeked'); + utils.dispatchEvent.call(player, player.media, 'play'); }); - this.embed.on('ended', () => { - this.media.paused = true; - utils.dispatchEvent.call(this, this.media, 'ended'); + player.embed.on('ended', () => { + player.media.paused = true; + utils.dispatchEvent.call(player, player.media, 'ended'); }); // Rebuild UI -- cgit v1.2.3 From 1578525ee38a50b31266bc9ba3ecd62705d4a5d5 Mon Sep 17 00:00:00 2001 From: Sam Potts Date: Mon, 6 Nov 2017 20:24:07 +1100 Subject: Finished aspect ratio setting --- src/js/plugins/vimeo.js | 2 +- src/less/components/embed.less | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/js/plugins/vimeo.js b/src/js/plugins/vimeo.js index dce3af03..6e4f34a2 100644 --- a/src/js/plugins/vimeo.js +++ b/src/js/plugins/vimeo.js @@ -19,7 +19,7 @@ const vimeo = { // Set aspect ratio const ratio = this.config.ratio.split(':'); const padding = 100 / ratio[0] * ratio[1]; - const offset = (100 - padding) / 2; + const offset = (400 - padding) / 8; this.elements.wrapper.style.paddingBottom = `${padding}%`; this.media.style.transform = `translateY(-${offset}%)`; diff --git a/src/less/components/embed.less b/src/less/components/embed.less index dcf889b7..7860f671 100644 --- a/src/less/components/embed.less +++ b/src/less/components/embed.less @@ -6,7 +6,7 @@ .plyr__video-embed { // Default to 16:9 ratio but this is set by JavaScript based on config @padding: ((100 / 16) * 9); - @offset: unit((100 - @padding) / 2, ~'%'); + @offset: unit((400 - @padding) / 8, ~'%'); padding-bottom: unit(@padding, ~'%'); height: 0; @@ -24,7 +24,7 @@ // Vimeo hack > div { position: relative; - padding-bottom: 100%; + padding-bottom: 400%; transform: translateY(-@offset); } } -- cgit v1.2.3 From 3a9beaed59f4eaca6ce2fd3f167f5c171317bb26 Mon Sep 17 00:00:00 2001 From: Sam Potts Date: Mon, 6 Nov 2017 20:27:19 +1100 Subject: Vimeo captions fix --- src/js/plugins/vimeo.js | 2 +- src/less/components/embed.less | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/js/plugins/vimeo.js b/src/js/plugins/vimeo.js index 6e4f34a2..9b67bdb0 100644 --- a/src/js/plugins/vimeo.js +++ b/src/js/plugins/vimeo.js @@ -19,7 +19,7 @@ const vimeo = { // Set aspect ratio const ratio = this.config.ratio.split(':'); const padding = 100 / ratio[0] * ratio[1]; - const offset = (400 - padding) / 8; + const offset = (300 - padding) / 6; this.elements.wrapper.style.paddingBottom = `${padding}%`; this.media.style.transform = `translateY(-${offset}%)`; diff --git a/src/less/components/embed.less b/src/less/components/embed.less index 7860f671..d31e4770 100644 --- a/src/less/components/embed.less +++ b/src/less/components/embed.less @@ -6,7 +6,7 @@ .plyr__video-embed { // Default to 16:9 ratio but this is set by JavaScript based on config @padding: ((100 / 16) * 9); - @offset: unit((400 - @padding) / 8, ~'%'); + @offset: unit((300 - @padding) / 6, ~'%'); padding-bottom: unit(@padding, ~'%'); height: 0; @@ -24,7 +24,7 @@ // Vimeo hack > div { position: relative; - padding-bottom: 400%; + padding-bottom: 300%; transform: translateY(-@offset); } } -- cgit v1.2.3 From 9c4b7e70948af3a1e45b63b3d36f3cae86802919 Mon Sep 17 00:00:00 2001 From: Sam Potts Date: Mon, 6 Nov 2017 20:34:11 +1100 Subject: Keyboard logic --- src/js/listeners.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/js/listeners.js b/src/js/listeners.js index 29038a21..9483c6a9 100644 --- a/src/js/listeners.js +++ b/src/js/listeners.js @@ -325,10 +325,10 @@ const listeners = { }; // Keyboard shortcuts - if (this.config.keyboard.focused) { - utils.on(this.elements.container, 'keydown keyup', handleKey, false); - } else if (this.config.keyboard.global) { + if (this.config.keyboard.global) { utils.on(window, 'keydown keyup', handleKey, false); + } else if (this.config.keyboard.focused) { + utils.on(this.elements.container, 'keydown keyup', handleKey, false); } // Detect tab focus -- cgit v1.2.3 From 2497e25b3ce6849986c3501354636fdae11df6d4 Mon Sep 17 00:00:00 2001 From: Sam Potts Date: Mon, 6 Nov 2017 21:19:00 +1100 Subject: Global listeners to prevent repeat binding --- src/js/listeners.js | 347 +++++++++++++++++++++++++++------------------------- src/js/plyr.js | 4 + 2 files changed, 182 insertions(+), 169 deletions(-) (limited to 'src') diff --git a/src/js/listeners.js b/src/js/listeners.js index 9483c6a9..3c83038b 100644 --- a/src/js/listeners.js +++ b/src/js/listeners.js @@ -13,161 +13,10 @@ import ui from './ui'; const browser = utils.getBrowser(); const listeners = { - // Listen for media events - media() { - // Time change on media - utils.on(this.media, 'timeupdate seeking', event => ui.timeUpdate.call(this, event)); - - // Display duration - utils.on(this.media, 'durationchange loadedmetadata', event => ui.displayDuration.call(this, event)); - - // Handle the media finishing - utils.on(this.media, 'ended', () => { - // Show poster on end - if (this.type === 'video' && this.config.showPosterOnEnd) { - // Restart - this.restart(); - - // Re-load media - this.media.load(); - } - }); - - // Check for buffer progress - utils.on(this.media, 'progress playing', event => ui.updateProgress.call(this, event)); - - // Handle native mute - utils.on(this.media, 'volumechange', event => ui.updateVolume.call(this, event)); - - // Handle native play/pause - utils.on(this.media, 'play pause ended', event => ui.checkPlaying.call(this, event)); - - // Loading - utils.on(this.media, 'waiting canplay seeked', event => ui.checkLoading.call(this, event)); - - // Click video - if (this.supported.ui && this.config.clickToPlay && this.type !== 'audio') { - // Re-fetch the wrapper - const wrapper = utils.getElement.call(this, `.${this.config.classNames.video}`); - - // Bail if there's no wrapper (this should never happen) - if (!wrapper) { - return; - } - - // Set cursor - wrapper.style.cursor = 'pointer'; - - // On click play, pause ore restart - utils.on(wrapper, 'click', () => { - // Touch devices will just show controls (if we're hiding controls) - if (this.config.hideControls && support.touch && !this.media.paused) { - return; - } - - if (this.media.paused) { - this.play(); - } else if (this.media.ended) { - this.restart(); - this.play(); - } else { - this.pause(); - } - }); - } - - // Disable right click - if (this.config.disableContextMenu) { - utils.on( - this.media, - 'contextmenu', - event => { - event.preventDefault(); - }, - false - ); - } - - // Speed change - utils.on(this.media, 'ratechange', () => { - // Update UI - controls.updateSetting.call(this, 'speed'); - - // Save speed to localStorage - storage.set.call(this, { speed: this.speed }); - }); - - // Quality change - utils.on(this.media, 'qualitychange', () => { - // Update UI - controls.updateSetting.call(this, 'quality'); - - // Save speed to localStorage - storage.set.call(this, { quality: this.quality }); - }); - - // Caption language change - utils.on(this.media, 'captionchange', () => { - // Save speed to localStorage - storage.set.call(this, { language: this.language }); - }); - - // Volume change - utils.on(this.media, 'volumechange', () => { - // Save speed to localStorage - storage.set.call(this, { volume: this.volume }); - }); - - // Captions toggle - utils.on(this.media, 'captionsenabled captionsdisabled', () => { - // Update UI - controls.updateSetting.call(this, 'captions'); - - // Save speed to localStorage - storage.set.call(this, { captions: this.captions.enabled }); - }); - - // Proxy events to container - // Bubble up key events for Edge - utils.on(this.media, this.config.events.concat(['keyup', 'keydown']).join(' '), event => { - utils.dispatchEvent.call(this, this.elements.container, event.type, true); - }); - }, - - // Listen for control events - controls() { - // IE doesn't support input event, so we fallback to change - const inputEvent = browser.isIE ? 'change' : 'input'; + // Global listeners + global() { let last = null; - // Trigger custom and default handlers - const proxy = (event, handlerKey, defaultHandler) => { - const customHandler = this.config.listeners[handlerKey]; - - // Execute custom handler - if (utils.is.function(customHandler)) { - customHandler.call(this, event); - } - - // Only call default handler if not prevented in custom handler - if (!event.defaultPrevented && utils.is.function(defaultHandler)) { - defaultHandler.call(this, event); - } - }; - - // Click play/pause helper - const togglePlay = () => { - const play = this.togglePlay(); - - // Determine which buttons - const target = this.elements.buttons[play ? 'pause' : 'play']; - - // Transfer focus - if (utils.is.htmlElement(target)) { - target.focus(); - } - }; - // Get the key code for an event const getKeyCode = event => (event.keyCode ? event.keyCode : event.which); @@ -252,6 +101,7 @@ const listeners = { case 75: // Space and K key if (!held) { + this.warn('togglePlay', event.type); this.togglePlay(); } break; @@ -326,6 +176,7 @@ const listeners = { // Keyboard shortcuts if (this.config.keyboard.global) { + this.warn('bound'); utils.on(window, 'keydown keyup', handleKey, false); } else if (this.config.keyboard.focused) { utils.on(this.elements.container, 'keydown keyup', handleKey, false); @@ -350,6 +201,180 @@ const listeners = { }, 0); }); + // Toggle controls visibility based on mouse movement + if (this.config.hideControls) { + // Toggle controls on mouse events and entering fullscreen + utils.on( + this.elements.container, + 'mouseenter mouseleave mousemove touchstart touchend touchcancel touchmove enterfullscreen', + event => { + this.toggleControls(event); + } + ); + } + + // Handle user exiting fullscreen by escaping etc + if (fullscreen.enabled) { + utils.on(document, fullscreen.eventType, event => { + this.toggleFullscreen(event); + }); + } + }, + + // Listen for media events + media() { + // Time change on media + utils.on(this.media, 'timeupdate seeking', event => ui.timeUpdate.call(this, event)); + + // Display duration + utils.on(this.media, 'durationchange loadedmetadata', event => ui.displayDuration.call(this, event)); + + // Handle the media finishing + utils.on(this.media, 'ended', () => { + // Show poster on end + if (this.type === 'video' && this.config.showPosterOnEnd) { + // Restart + this.restart(); + + // Re-load media + this.media.load(); + } + }); + + // Check for buffer progress + utils.on(this.media, 'progress playing', event => ui.updateProgress.call(this, event)); + + // Handle native mute + utils.on(this.media, 'volumechange', event => ui.updateVolume.call(this, event)); + + // Handle native play/pause + utils.on(this.media, 'play pause ended', event => ui.checkPlaying.call(this, event)); + + // Loading + utils.on(this.media, 'waiting canplay seeked', event => ui.checkLoading.call(this, event)); + + // Click video + if (this.supported.ui && this.config.clickToPlay && this.type !== 'audio') { + // Re-fetch the wrapper + const wrapper = utils.getElement.call(this, `.${this.config.classNames.video}`); + + // Bail if there's no wrapper (this should never happen) + if (!wrapper) { + return; + } + + // Set cursor + wrapper.style.cursor = 'pointer'; + + // On click play, pause ore restart + utils.on(wrapper, 'click', () => { + // Touch devices will just show controls (if we're hiding controls) + if (this.config.hideControls && support.touch && !this.media.paused) { + return; + } + + if (this.media.paused) { + this.play(); + } else if (this.media.ended) { + this.restart(); + this.play(); + } else { + this.pause(); + } + }); + } + + // Disable right click + if (this.config.disableContextMenu) { + utils.on( + this.media, + 'contextmenu', + event => { + event.preventDefault(); + }, + false + ); + } + + // Speed change + utils.on(this.media, 'ratechange', () => { + // Update UI + controls.updateSetting.call(this, 'speed'); + + // Save speed to localStorage + storage.set.call(this, { speed: this.speed }); + }); + + // Quality change + utils.on(this.media, 'qualitychange', () => { + // Update UI + controls.updateSetting.call(this, 'quality'); + + // Save speed to localStorage + storage.set.call(this, { quality: this.quality }); + }); + + // Caption language change + utils.on(this.media, 'captionchange', () => { + // Save speed to localStorage + storage.set.call(this, { language: this.language }); + }); + + // Volume change + utils.on(this.media, 'volumechange', () => { + // Save speed to localStorage + storage.set.call(this, { volume: this.volume }); + }); + + // Captions toggle + utils.on(this.media, 'captionsenabled captionsdisabled', () => { + // Update UI + controls.updateSetting.call(this, 'captions'); + + // Save speed to localStorage + storage.set.call(this, { captions: this.captions.enabled }); + }); + + // Proxy events to container + // Bubble up key events for Edge + utils.on(this.media, this.config.events.concat(['keyup', 'keydown']).join(' '), event => { + utils.dispatchEvent.call(this, this.elements.container, event.type, true); + }); + }, + + // Listen for control events + controls() { + // IE doesn't support input event, so we fallback to change + const inputEvent = browser.isIE ? 'change' : 'input'; + + // Trigger custom and default handlers + const proxy = (event, handlerKey, defaultHandler) => { + const customHandler = this.config.listeners[handlerKey]; + + // Execute custom handler + if (utils.is.function(customHandler)) { + customHandler.call(this, event); + } + + // Only call default handler if not prevented in custom handler + if (!event.defaultPrevented && utils.is.function(defaultHandler)) { + defaultHandler.call(this, event); + } + }; + + // Click play/pause helper + const togglePlay = () => { + const play = this.togglePlay(); + + // Determine which buttons + const target = this.elements.buttons[play ? 'pause' : 'play']; + + // Transfer focus + if (utils.is.htmlElement(target)) { + target.focus(); + } + }; + // Play utils.on(this.elements.buttons.play, 'click', event => proxy(event, 'play', togglePlay)); @@ -484,15 +509,6 @@ const listeners = { // Toggle controls visibility based on mouse movement if (this.config.hideControls) { - // Toggle controls on mouse events and entering fullscreen - utils.on( - this.elements.container, - 'mouseenter mouseleave mousemove touchstart touchend touchcancel touchmove enterfullscreen', - event => { - this.toggleControls(event); - } - ); - // Watch for cursor over controls so they don't hide when trying to interact utils.on(this.elements.controls, 'mouseenter mouseleave', event => { this.elements.controls.hover = event.type === 'mouseenter'; @@ -556,13 +572,6 @@ const listeners = { }), false ); - - // Handle user exiting fullscreen by escaping etc - if (fullscreen.enabled) { - utils.on(document, fullscreen.eventType, event => { - this.toggleFullscreen(event); - }); - } }, }; diff --git a/src/js/plyr.js b/src/js/plyr.js index 08711172..675ddb0c 100644 --- a/src/js/plyr.js +++ b/src/js/plyr.js @@ -13,6 +13,7 @@ import utils from './utils'; import captions from './captions'; import controls from './controls'; import fullscreen from './fullscreen'; +import listeners from './listeners'; import media from './media'; import storage from './storage'; import source from './source'; @@ -214,6 +215,9 @@ class Plyr { // Allow focus to be captured this.elements.container.setAttribute('tabindex', 0); + // Global listeners + listeners.global.call(this); + // Add style hook ui.addStyleHook.call(this); -- cgit v1.2.3 From 84505da84ba97ae1b189f9c695228d324e9dc3b8 Mon Sep 17 00:00:00 2001 From: Sam Potts Date: Mon, 6 Nov 2017 22:00:00 +1100 Subject: Automatic Vimeo aspect ratio --- src/js/listeners.js | 1 - src/js/plugins/vimeo.js | 23 +++++++++++++++++------ src/js/plugins/youtube.js | 9 +++++++-- src/js/plyr.js | 1 - src/js/utils.js | 7 +++++++ 5 files changed, 31 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/src/js/listeners.js b/src/js/listeners.js index 3c83038b..7a455c13 100644 --- a/src/js/listeners.js +++ b/src/js/listeners.js @@ -176,7 +176,6 @@ const listeners = { // Keyboard shortcuts if (this.config.keyboard.global) { - this.warn('bound'); utils.on(window, 'keydown keyup', handleKey, false); } else if (this.config.keyboard.focused) { utils.on(this.elements.container, 'keydown keyup', handleKey, false); diff --git a/src/js/plugins/vimeo.js b/src/js/plugins/vimeo.js index 9b67bdb0..83b6d942 100644 --- a/src/js/plugins/vimeo.js +++ b/src/js/plugins/vimeo.js @@ -16,12 +16,8 @@ const vimeo = { // Add embed class for responsive utils.toggleClass(this.elements.wrapper, this.config.classNames.embed, true); - // Set aspect ratio - const ratio = this.config.ratio.split(':'); - const padding = 100 / ratio[0] * ratio[1]; - const offset = (300 - padding) / 6; - this.elements.wrapper.style.paddingBottom = `${padding}%`; - this.media.style.transform = `translateY(-${offset}%)`; + // Set intial ratio + vimeo.setAspectRatio.call(this); // Set ID this.media.setAttribute('id', utils.generateId(this.type)); @@ -41,6 +37,15 @@ const vimeo = { } }, + // Set aspect ratio + setAspectRatio(input) { + const ratio = utils.is.string(input) ? input.split(':') : this.config.ratio.split(':'); + const padding = 100 / ratio[0] * ratio[1]; + const offset = (300 - padding) / 6; + this.elements.wrapper.style.paddingBottom = `${padding}%`; + this.media.style.transform = `translateY(-${offset}%)`; + }, + // API Ready ready() { const player = this; @@ -161,6 +166,12 @@ const vimeo = { }, }); + // Set aspect ratio based on video size + Promise.all([player.embed.getVideoWidth(), player.embed.getVideoHeight()]).then(dimensions => { + const ratio = utils.getAspectRatio(dimensions[0], dimensions[1]); + vimeo.setAspectRatio.call(this, ratio); + }); + // Get available speeds if (player.config.controls.includes('settings') && player.config.settings.includes('speed')) { controls.setSpeedMenu.call(player); diff --git a/src/js/plugins/youtube.js b/src/js/plugins/youtube.js index 2c8557dc..ca0f2991 100644 --- a/src/js/plugins/youtube.js +++ b/src/js/plugins/youtube.js @@ -18,8 +18,7 @@ const youtube = { utils.toggleClass(this.elements.wrapper, this.config.classNames.embed, true); // Set aspect ratio - const ratio = this.config.ratio.split(':'); - this.elements.wrapper.style.paddingBottom = `${100 / ratio[0] * ratio[1]}%`; + youtube.setAspectRatio.call(this); // Set ID this.media.setAttribute('id', utils.generateId(this.type)); @@ -48,6 +47,12 @@ const youtube = { } }, + // Set aspect ratio + setAspectRatio() { + const ratio = this.config.ratio.split(':'); + this.elements.wrapper.style.paddingBottom = `${100 / ratio[0] * ratio[1]}%`; + }, + // API ready ready(videoId) { const player = this; diff --git a/src/js/plyr.js b/src/js/plyr.js index 675ddb0c..355fe5cb 100644 --- a/src/js/plyr.js +++ b/src/js/plyr.js @@ -79,7 +79,6 @@ class Plyr { }; // Captions - // TODO: captions.enabled should be in config? this.captions = { enabled: null, tracks: null, diff --git a/src/js/utils.js b/src/js/utils.js index 70519fac..1c3d6ed8 100644 --- a/src/js/utils.js +++ b/src/js/utils.js @@ -631,6 +631,13 @@ const utils = { return fragment.firstChild.innerText; }, + // Get aspect ratio for dimensions + getAspectRatio(width, height) { + const getRatio = (w, h) => (h === 0 ? w : getRatio(h, w % h)); + const ratio = getRatio(width, height); + return `${width / ratio}:${height / ratio}`; + }, + // Get the transition end event transitionEnd: (() => { const element = document.createElement('span'); -- cgit v1.2.3