aboutsummaryrefslogtreecommitdiffstats
path: root/src/js
diff options
context:
space:
mode:
authorSam Potts <sam@potts.es>2018-05-19 11:27:52 +1000
committerGitHub <noreply@github.com>2018-05-19 11:27:52 +1000
commit55bbf64f2b5d5c488ca1a1fab83b53bcaae09191 (patch)
tree4ba3b1ebf7e12a7d887bb68873ae55eba5aedefa /src/js
parent3bba65f2c22fe11bca7d89f8451fa1b0b5e8030e (diff)
parentc845558d960412ad5e942334fd9f60ed173e0a5a (diff)
downloadplyr-55bbf64f2b5d5c488ca1a1fab83b53bcaae09191.tar.lz
plyr-55bbf64f2b5d5c488ca1a1fab83b53bcaae09191.tar.xz
plyr-55bbf64f2b5d5c488ca1a1fab83b53bcaae09191.zip
Merge pull request #963 from friday/verify-poster
Make sure poster element isn't shown if the image isn't loaded
Diffstat (limited to 'src/js')
-rw-r--r--src/js/defaults.js2
-rw-r--r--src/js/plugins/vimeo.js7
-rw-r--r--src/js/plugins/youtube.js14
-rw-r--r--src/js/plyr.js5
-rw-r--r--src/js/ui.js44
-rw-r--r--src/js/utils.js15
6 files changed, 67 insertions, 20 deletions
diff --git a/src/js/defaults.js b/src/js/defaults.js
index da089efc..f66a7c2f 100644
--- a/src/js/defaults.js
+++ b/src/js/defaults.js
@@ -199,7 +199,6 @@ const defaults = {
youtube: {
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: {
sdk: 'https://imasdk.googleapis.com/js/sdkloader/ima3.js',
@@ -332,6 +331,7 @@ const defaults = {
embed: 'plyr__video-embed',
embedContainer: 'plyr__video-embed__container',
poster: 'plyr__poster',
+ posterEnabled: 'plyr__poster-enabled',
ads: 'plyr__ads',
control: 'plyr__control',
playing: 'plyr--playing',
diff --git a/src/js/plugins/vimeo.js b/src/js/plugins/vimeo.js
index 0ceb89e5..96b36781 100644
--- a/src/js/plugins/vimeo.js
+++ b/src/js/plugins/vimeo.js
@@ -99,11 +99,8 @@ const vimeo = {
// Get original image
url.pathname = `${url.pathname.split('_')[0]}.jpg`;
- // Set attribute
- player.media.setAttribute('poster', url.href);
-
- // Update
- ui.setPoster.call(player);
+ // Set and show poster
+ ui.setPoster.call(player, url.href);
});
// Setup instance
diff --git a/src/js/plugins/youtube.js b/src/js/plugins/youtube.js
index 4fde9319..10283998 100644
--- a/src/js/plugins/youtube.js
+++ b/src/js/plugins/youtube.js
@@ -162,7 +162,19 @@ const youtube = {
player.media = utils.replaceElement(container, player.media);
// Set poster image
- player.media.setAttribute('poster', utils.format(player.config.urls.youtube.poster, videoId));
+ const posterSrc = format => `https://img.youtube.com/vi/${videoId}/${format}default.jpg`;
+
+ // Check thumbnail images in order of quality, but reject fallback thumbnails (120px wide)
+ utils.loadImage(posterSrc('maxres'), 121) // Higest quality and unpadded
+ .catch(() => utils.loadImage(posterSrc('sd'), 121)) // 480p padded 4:3
+ .catch(() => utils.loadImage(posterSrc('hq'))) // 360p padded 4:3. Always exists
+ .then(image => ui.setPoster.call(player, image.src))
+ .then(posterSrc => {
+ // If the image is padded, use background-size "cover" instead (like youtube does too with their posters)
+ if (!posterSrc.includes('maxres')) {
+ player.elements.poster.style.backgroundSize = 'cover';
+ }
+ });
// Setup instance
// https://developers.google.com/youtube/iframe_api_reference
diff --git a/src/js/plyr.js b/src/js/plyr.js
index ffd2a1e3..4c569fec 100644
--- a/src/js/plyr.js
+++ b/src/js/plyr.js
@@ -802,10 +802,7 @@ class Plyr {
return;
}
- if (utils.is.string(input)) {
- this.media.setAttribute('poster', input);
- ui.setPoster.call(this);
- }
+ ui.setPoster.call(this, input);
}
/**
diff --git a/src/js/ui.js b/src/js/ui.js
index 039144a5..3a8f2d05 100644
--- a/src/js/ui.js
+++ b/src/js/ui.js
@@ -105,8 +105,10 @@ const ui = {
// Set the title
ui.setTitle.call(this);
- // Set the poster image
- ui.setPoster.call(this);
+ // Assure the poster image is set, if the property was added before the element was created
+ if (this.poster && this.elements.poster && !this.elements.poster.style.backgroundImage) {
+ ui.setPoster.call(this, this.poster);
+ }
},
// Setup aria attribute for play and iframe title
@@ -146,15 +148,39 @@ const ui = {
}
},
- // Set the poster image
- setPoster() {
- if (!utils.is.element(this.elements.poster) || utils.is.empty(this.poster)) {
- return;
+ // Toggle poster
+ togglePoster(enable) {
+ utils.toggleClass(this.elements.container, this.config.classNames.posterEnabled, enable);
+ },
+
+ // Set the poster image (async)
+ setPoster(poster) {
+ // Set property regardless of validity
+ this.media.setAttribute('poster', poster);
+
+ // Bail if element is missing
+ if (!utils.is.element(this.elements.poster)) {
+ return Promise.reject();
}
- // Set the inline style
- const posters = this.poster.split(',');
- this.elements.poster.style.backgroundImage = posters.map(p => `url('${p}')`).join(',');
+ // Load the image, and set poster if successful
+ const loadPromise = utils.loadImage(poster)
+ .then(() => {
+ this.elements.poster.style.backgroundImage = `url('${poster}')`;
+ Object.assign(this.elements.poster.style, {
+ backgroundImage: `url('${poster}')`,
+ // Reset backgroundSize as well (since it can be set to "cover" for padded thumbnails for youtube)
+ backgroundSize: '',
+ });
+ ui.togglePoster.call(this, true);
+ return poster;
+ });
+
+ // Hide the element if the poster can't be loaded (otherwise it will just be a black element covering the video)
+ loadPromise.catch(() => ui.togglePoster.call(this, false));
+
+ // Return the promise so the caller can use it as well
+ return loadPromise;
},
// Check playing state
diff --git a/src/js/utils.js b/src/js/utils.js
index 13e26655..0c5a28d7 100644
--- a/src/js/utils.js
+++ b/src/js/utils.js
@@ -120,6 +120,21 @@ const utils = {
});
},
+ // Load image avoiding xhr/fetch CORS issues
+ // Server status can't be obtained this way unfortunately, so this uses "naturalWidth" to determine if the image has loaded.
+ // By default it checks if it is at least 1px, but you can add a second argument to change this.
+ loadImage(src, minWidth = 1) {
+ return new Promise((resolve, reject) => {
+ const image = new Image();
+ const handler = () => {
+ delete image.onload;
+ delete image.onerror;
+ (image.naturalWidth >= minWidth ? resolve : reject)(image);
+ };
+ Object.assign(image, {onload: handler, onerror: handler, src});
+ });
+ },
+
// Load an external script
loadScript(url) {
return new Promise((resolve, reject) => {