From cdacae669786a76d821e828573f996b97ec4df9e Mon Sep 17 00:00:00 2001 From: Sam Potts Date: Fri, 12 Apr 2019 19:00:17 +1000 Subject: [PATCH 01/20] Set download URL via setter --- src/js/controls.js | 6 +++--- src/js/listeners.js | 2 +- src/js/plugins/vimeo.js | 6 +++--- src/js/plyr.js | 13 +++++++++++++ 4 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/js/controls.js b/src/js/controls.js index 73903e16..78d3144f 100644 --- a/src/js/controls.js +++ b/src/js/controls.js @@ -1244,8 +1244,8 @@ const controls = { controls.focusFirstMenuItem.call(this, target, tabFocus); }, - // Set the download link - setDownloadLink() { + // Set the download URL + setDownloadUrl() { const button = this.elements.buttons.download; // Bail if no button @@ -1253,7 +1253,7 @@ const controls = { return; } - // Set download link + // Set attribute button.setAttribute('href', this.download); }, diff --git a/src/js/listeners.js b/src/js/listeners.js index 5a593b10..b463498f 100644 --- a/src/js/listeners.js +++ b/src/js/listeners.js @@ -486,7 +486,7 @@ class Listeners { // Update download link when ready and if quality changes on.call(player, player.media, 'ready qualitychange', () => { - controls.setDownloadLink.call(player); + controls.setDownloadUrl.call(player); }); // Proxy events to container diff --git a/src/js/plugins/vimeo.js b/src/js/plugins/vimeo.js index 9d6c1665..e1e873fa 100644 --- a/src/js/plugins/vimeo.js +++ b/src/js/plugins/vimeo.js @@ -48,14 +48,14 @@ const vimeo = { // Set intial ratio setAspectRatio.call(this); - // Load the API if not already + // Load the SDK if not already if (!is.object(window.Vimeo)) { loadScript(this.config.urls.vimeo.sdk) .then(() => { vimeo.ready.call(this); }) .catch(error => { - this.debug.warn('Vimeo API failed to load', error); + this.debug.warn('Vimeo SDK (player.js) failed to load', error); }); } else { vimeo.ready.call(this); @@ -259,7 +259,7 @@ const vimeo = { .getVideoUrl() .then(value => { currentSrc = value; - controls.setDownloadLink.call(player); + controls.setDownloadUrl.call(player); }) .catch(error => { this.debug.warn(error); diff --git a/src/js/plyr.js b/src/js/plyr.js index 1dd3ecb9..0a804437 100644 --- a/src/js/plyr.js +++ b/src/js/plyr.js @@ -823,6 +823,19 @@ class Plyr { return is.url(download) ? download : this.source; } + /** + * Set the download URL + */ + set download(input) { + if (!is.url(input)) { + return; + } + + this.config.urls.download = input; + + controls.setDownloadUrl.call(this); + } + /** * Set the poster image for a video * @param {String} input - the URL for the new poster image From e9367ee85e443b28b003e3ff79ac0e2445879aa4 Mon Sep 17 00:00:00 2001 From: Sam Potts Date: Fri, 12 Apr 2019 20:18:17 +1000 Subject: [PATCH 02/20] Fix setting initial speed (fixes #1408) --- src/js/plyr.js | 4 +++- src/js/ui.js | 27 ++++++++++++++++----------- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/src/js/plyr.js b/src/js/plyr.js index 3b169321..d67fae83 100644 --- a/src/js/plyr.js +++ b/src/js/plyr.js @@ -678,7 +678,9 @@ class Plyr { this.config.speed.selected = speed; // Set media speed - this.media.playbackRate = speed; + setTimeout(() => { + this.media.playbackRate = speed; + }, 0); } /** diff --git a/src/js/ui.js b/src/js/ui.js index 8e50bb83..50de7df1 100644 --- a/src/js/ui.js +++ b/src/js/ui.js @@ -67,15 +67,15 @@ const ui = { // Reset mute state this.muted = null; - // Reset speed - this.speed = null; - // Reset loop state this.loop = null; // Reset quality setting this.quality = null; + // Reset speed + this.speed = null; + // Reset volume display controls.updateVolume.call(this); @@ -233,13 +233,16 @@ const ui = { clearTimeout(this.timers.loading); // Timer to prevent flicker when seeking - this.timers.loading = setTimeout(() => { - // Update progress bar loading class state - toggleClass(this.elements.container, this.config.classNames.loading, this.loading); + this.timers.loading = setTimeout( + () => { + // Update progress bar loading class state + toggleClass(this.elements.container, this.config.classNames.loading, this.loading); - // Update controls visibility - ui.toggleControls.call(this); - }, this.loading ? 250 : 0); + // Update controls visibility + ui.toggleControls.call(this); + }, + this.loading ? 250 : 0, + ); }, // Toggle controls based on state and `force` argument @@ -248,10 +251,12 @@ const ui = { if (controls && this.config.hideControls) { // Don't hide controls if a touch-device user recently seeked. (Must be limited to touch devices, or it occasionally prevents desktop controls from hiding.) - const recentTouchSeek = (this.touch && this.lastSeekTime + 2000 > Date.now()); + const recentTouchSeek = this.touch && this.lastSeekTime + 2000 > Date.now(); // Show controls if force, loading, paused, button interaction, or recent seek, otherwise hide - this.toggleControls(Boolean(force || this.loading || this.paused || controls.pressed || controls.hover || recentTouchSeek)); + this.toggleControls( + Boolean(force || this.loading || this.paused || controls.pressed || controls.hover || recentTouchSeek), + ); } }, }; From b675ba1f350207071526453d0e03153225dc4c63 Mon Sep 17 00:00:00 2001 From: Sam Potts Date: Sat, 13 Apr 2019 12:55:48 +1000 Subject: [PATCH 03/20] Fix issue with setGutter call --- src/js/listeners.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/listeners.js b/src/js/listeners.js index b463498f..eb621207 100644 --- a/src/js/listeners.js +++ b/src/js/listeners.js @@ -356,7 +356,7 @@ class Listeners { const { padding, ratio } = setPlayerSize(isEnter); // Set Vimeo gutter - setGutter(ratio, padding, isEnter); + setGutter.call(player, ratio, padding, isEnter); // If not using native fullscreen, we need to check for resizes of viewport if (!usingNative) { From 243db9eda32d0ceb705b0cb99426995db071b113 Mon Sep 17 00:00:00 2001 From: Sam Potts Date: Sat, 13 Apr 2019 12:56:15 +1000 Subject: [PATCH 04/20] Fix issue with empty controls and preview thumbs --- src/js/plugins/previewThumbnails.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/js/plugins/previewThumbnails.js b/src/js/plugins/previewThumbnails.js index 813bc47e..3e4b17a3 100644 --- a/src/js/plugins/previewThumbnails.js +++ b/src/js/plugins/previewThumbnails.js @@ -149,9 +149,11 @@ class PreviewThumbnails { // If the URLs don't start with '/', then we need to set their relative path to be the location of the VTT file // If the URLs do start with '/', then they obviously don't need a prefix, so it will remain blank // If the thumbnail URLs start with with none of '/', 'http://' or 'https://', then we need to set their relative path to be the location of the VTT file - if (!thumbnail.frames[0].text.startsWith('/') && + if ( + !thumbnail.frames[0].text.startsWith('/') && !thumbnail.frames[0].text.startsWith('http://') && - !thumbnail.frames[0].text.startsWith('https://')) { + !thumbnail.frames[0].text.startsWith('https://') + ) { thumbnail.urlPrefix = url.substring(0, url.lastIndexOf('/') + 1); } @@ -297,7 +299,9 @@ class PreviewThumbnails { this.elements.thumb.container.appendChild(timeContainer); // Inject the whole thumb - this.player.elements.progress.appendChild(this.elements.thumb.container); + if (is.element(this.player.elements.progress)) { + this.player.elements.progress.appendChild(this.elements.thumb.container); + } // Create HTML element: plyr__preview-scrubbing-container this.elements.scrubbing.container = createElement('div', { From 2cf44c236d586ca07f03744a3df8ffeff425b8f7 Mon Sep 17 00:00:00 2001 From: Emiel Beinema Date: Mon, 15 Apr 2019 12:16:48 +0200 Subject: [PATCH 05/20] Use querySelector on container for showMenuPanel --- src/js/controls.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/controls.js b/src/js/controls.js index 78d3144f..104f5c95 100644 --- a/src/js/controls.js +++ b/src/js/controls.js @@ -1191,7 +1191,7 @@ const controls = { // Show a panel in the menu showMenuPanel(type = '', tabFocus = false) { - const target = document.getElementById(`plyr-settings-${this.id}-${type}`); + const target = this.elements.container.querySelector(`#plyr-settings-${this.id}-${type}`); // Nothing to show, bail if (!is.element(target)) { From a91652287b10237552b6b1ac87c8286aff4f583d Mon Sep 17 00:00:00 2001 From: Emiel Beinema Date: Mon, 15 Apr 2019 12:17:10 +0200 Subject: [PATCH 06/20] Don't close menu on click in menu in webcomponent --- src/js/controls.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/js/controls.js b/src/js/controls.js index 104f5c95..275ffb3f 100644 --- a/src/js/controls.js +++ b/src/js/controls.js @@ -1138,7 +1138,9 @@ const controls = { } else if (is.keyboardEvent(input) && input.which === 27) { show = false; } else if (is.event(input)) { - const isMenuItem = popup.contains(input.target); + // If Plyr is in a shadowDOM, the event target is set to the component, instead of the + // element in the shadowDOM. The path, however, is complete. + const isMenuItem = popup.contains(input.path[0]); // If the click was inside the menu or if the click // wasn't the button or menu item and we're trying to From b2fff4c33f45c8e61bbe1f6549e69ef46ef8b2fa Mon Sep 17 00:00:00 2001 From: Sam Potts Date: Mon, 15 Apr 2019 22:08:09 +1000 Subject: [PATCH 07/20] Increase speed limits --- src/js/plyr.js | 52 +++++++++++++++++++++++++++++++---------- src/js/utils/numbers.js | 17 ++++++++++++++ 2 files changed, 57 insertions(+), 12 deletions(-) create mode 100644 src/js/utils/numbers.js diff --git a/src/js/plyr.js b/src/js/plyr.js index d67fae83..be1cba4b 100644 --- a/src/js/plyr.js +++ b/src/js/plyr.js @@ -25,6 +25,7 @@ import { createElement, hasClass, removeElement, replaceElement, toggleClass, wr import { off, on, once, triggerEvent, unbindListeners } from './utils/events'; import is from './utils/is'; import loadSprite from './utils/loadSprite'; +import { clamp } from './utils/numbers'; import { cloneDeep, extend } from './utils/objects'; import { getAspectRatio, reduceAspectRatio, setAspectRatio, validateRatio } from './utils/style'; import { parseUrl } from './utils/urls'; @@ -661,18 +662,9 @@ class Plyr { speed = this.config.speed.selected; } - // Set min/max - if (speed < 0.1) { - speed = 0.1; - } - if (speed > 2.0) { - speed = 2.0; - } - - if (!this.config.speed.options.includes(speed)) { - this.debug.warn(`Unsupported speed (${speed})`); - return; - } + // Clamp to min/max + const { minimumSpeed: min, maximumSpeed: max } = this; + speed = clamp(speed, min, max); // Update config this.config.speed.selected = speed; @@ -690,6 +682,42 @@ class Plyr { return Number(this.media.playbackRate); } + /** + * Get the minimum allowed speed + */ + get minimumSpeed() { + if (this.isYouTube) { + // https://developers.google.com/youtube/iframe_api_reference#setPlaybackRate + return Math.min(...this.options.speed); + } + + if (this.isVimeo) { + // https://github.com/vimeo/player.js/#setplaybackrateplaybackrate-number-promisenumber-rangeerrorerror + return 0.5; + } + + // https://stackoverflow.com/a/32320020/1191319 + return 0.0625; + } + + /** + * Get the maximum allowed speed + */ + get maximumSpeed() { + if (this.isYouTube) { + // https://developers.google.com/youtube/iframe_api_reference#setPlaybackRate + return Math.max(...this.options.speed); + } + + if (this.isVimeo) { + // https://github.com/vimeo/player.js/#setplaybackrateplaybackrate-number-promisenumber-rangeerrorerror + return 2; + } + + // https://stackoverflow.com/a/32320020/1191319 + return 16; + } + /** * Set playback quality * Currently HTML5 & YouTube only diff --git a/src/js/utils/numbers.js b/src/js/utils/numbers.js new file mode 100644 index 00000000..f6eb65c8 --- /dev/null +++ b/src/js/utils/numbers.js @@ -0,0 +1,17 @@ +/** + * Returns a number whose value is limited to the given range. + * + * Example: limit the output of this computation to between 0 and 255 + * (x * 255).clamp(0, 255) + * + * @param {Number} input + * @param {Number} min The lower boundary of the output range + * @param {Number} max The upper boundary of the output range + * @returns A number in the range [min, max] + * @type Number + */ +export function clamp(input = 0, min = 0, max = 255) { + return Math.min(Math.max(input, min), max); +} + +export default { clamp }; From b694e7d3abc3f40acf9891761f1a767923174c5e Mon Sep 17 00:00:00 2001 From: Sam Potts Date: Mon, 22 Apr 2019 12:28:26 +1000 Subject: [PATCH 08/20] Use polyfill.io v3 --- demo/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo/index.html b/demo/index.html index c1942e74..04458522 100644 --- a/demo/index.html +++ b/demo/index.html @@ -265,7 +265,7 @@ From e147f3a754765a8d21899751e8e8cf283929abfd Mon Sep 17 00:00:00 2001 From: Sam Potts Date: Thu, 25 Apr 2019 12:01:00 +1000 Subject: [PATCH 09/20] Formatting --- src/js/captions.js | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/src/js/captions.js b/src/js/captions.js index ae4642aa..b326d85e 100644 --- a/src/js/captions.js +++ b/src/js/captions.js @@ -124,19 +124,21 @@ const captions = { // Handle tracks (add event listener and "pseudo"-default) if (this.isHTML5 && this.isVideo) { - tracks.filter(track => !meta.get(track)).forEach(track => { - this.debug.log('Track added', track); - // Attempt to store if the original dom element was "default" - meta.set(track, { - default: track.mode === 'showing', + tracks + .filter(track => !meta.get(track)) + .forEach(track => { + this.debug.log('Track added', track); + // Attempt to store if the original dom element was "default" + meta.set(track, { + default: track.mode === 'showing', + }); + + // Turn off native caption rendering to avoid double captions + track.mode = 'hidden'; + + // Add event listener for cue changes + on.call(this, track, 'cuechange', () => captions.updateCues.call(this)); }); - - // Turn off native caption rendering to avoid double captions - track.mode = 'hidden'; - - // Add event listener for cue changes - on.call(this, track, 'cuechange', () => captions.updateCues.call(this)); - }); } // Update language first time it matches, or if the previous matching track was removed @@ -300,10 +302,12 @@ const captions = { const sortIsDefault = track => Number((this.captions.meta.get(track) || {}).default); const sorted = Array.from(tracks).sort((a, b) => sortIsDefault(b) - sortIsDefault(a)); let track; + languages.every(language => { track = sorted.find(track => track.language === language); return !track; // Break iteration if there is a match }); + // If no match is found but is required, get first return track || (force ? sorted[0] : undefined); }, @@ -360,6 +364,7 @@ const captions = { // Get cues from track if (!cues) { const track = captions.getCurrentTrack.call(this); + cues = Array.from((track || {}).activeCues || []) .map(cue => cue.getCueAsHTML()) .map(getHTML); From c3fd822857d1b573ce938c470af85603117b07af Mon Sep 17 00:00:00 2001 From: Sam Potts Date: Thu, 25 Apr 2019 12:01:15 +1000 Subject: [PATCH 10/20] Notes on autoplay support --- readme.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/readme.md b/readme.md index e217bf09..ec2d77eb 100644 --- a/readme.md +++ b/readme.md @@ -275,7 +275,7 @@ Note the single quotes encapsulating the JSON and double quotes on the object ke | `iconUrl` | String | `null` | Specify a URL or path to the SVG sprite. See the [SVG section](#svg) for more info. | | `iconPrefix` | String | `plyr` | Specify the id prefix for the icons used in the default controls (e.g. "plyr-play" would be "plyr"). This is to prevent clashes if you're using your own SVG sprite but with the default controls. Most people can ignore this option. | | `blankVideo` | String | `https://cdn.plyr.io/static/blank.mp4` | Specify a URL or path to a blank video file used to properly cancel network requests. | -| `autoplay` | Boolean | `false` | Autoplay the media on load. This is generally advised against on UX grounds. It is also disabled by default in some browsers. If the `autoplay` attribute is present on a `