Merge pull request #1039 from friday/poster-race-conditions

Fix poster race conditions
This commit is contained in:
Sam Potts
2018-06-17 00:40:28 +10:00
committed by GitHub
6 changed files with 63 additions and 35 deletions

View File

@ -119,8 +119,11 @@ const vimeo = {
iframe.setAttribute('allowtransparency', ''); iframe.setAttribute('allowtransparency', '');
iframe.setAttribute('allow', 'autoplay'); iframe.setAttribute('allow', 'autoplay');
// Get poster, if already set
const { poster } = player;
// Inject the package // Inject the package
const wrapper = createElement('div', { class: player.config.classNames.embedContainer }); const wrapper = createElement('div', { poster, class: player.config.classNames.embedContainer });
wrapper.appendChild(iframe); wrapper.appendChild(iframe);
player.media = replaceElement(wrapper, player.media); player.media = replaceElement(wrapper, player.media);
@ -137,7 +140,7 @@ const vimeo = {
url.pathname = `${url.pathname.split('_')[0]}.jpg`; url.pathname = `${url.pathname.split('_')[0]}.jpg`;
// Set and show poster // Set and show poster
ui.setPoster.call(player, url.href); ui.setPoster.call(player, url.href).catch(() => {});
}); });
// Setup instance // Setup instance

View File

@ -158,10 +158,15 @@ const youtube = {
// Replace the <iframe> with a <div> due to YouTube API issues // Replace the <iframe> with a <div> due to YouTube API issues
const videoId = parseId(source); const videoId = parseId(source);
const id = generateId(player.provider); const id = generateId(player.provider);
const container = createElement('div', { id });
// Get poster, if already set
const { poster } = player;
// Replace media element
const container = createElement('div', { id, poster });
player.media = replaceElement(container, player.media); player.media = replaceElement(container, player.media);
// Set poster image // Id to poster wrapper
const posterSrc = format => `https://img.youtube.com/vi/${videoId}/${format}default.jpg`; 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) // Check thumbnail images in order of quality, but reject fallback thumbnails (120px wide)
@ -174,7 +179,8 @@ const youtube = {
if (!posterSrc.includes('maxres')) { if (!posterSrc.includes('maxres')) {
player.elements.poster.style.backgroundSize = 'cover'; player.elements.poster.style.backgroundSize = 'cover';
} }
}); })
.catch(() => {});
// Setup instance // Setup instance
// https://developers.google.com/youtube/iframe_api_reference // https://developers.google.com/youtube/iframe_api_reference

View File

@ -790,7 +790,7 @@ class Plyr {
return; return;
} }
ui.setPoster.call(this, input); ui.setPoster.call(this, input, false).catch(() => {});
} }
/** /**

View File

@ -8,7 +8,7 @@ import i18n from './i18n';
import support from './support'; import support from './support';
import browser from './utils/browser'; import browser from './utils/browser';
import { getElement, toggleClass, toggleState } from './utils/elements'; import { getElement, toggleClass, toggleState } from './utils/elements';
import { triggerEvent } from './utils/events'; import { ready, triggerEvent } from './utils/events';
import is from './utils/is'; import is from './utils/is';
import loadImage from './utils/loadImage'; import loadImage from './utils/loadImage';
@ -109,8 +109,8 @@ const ui = {
ui.setTitle.call(this); ui.setTitle.call(this);
// Assure the poster image is set, if the property was added before the element was created // 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) { if (this.poster) {
ui.setPoster.call(this, this.poster); ui.setPoster.call(this, this.poster, false).catch(() => {});
} }
// Manually set the duration if user has overridden it. // Manually set the duration if user has overridden it.
@ -163,32 +163,43 @@ const ui = {
}, },
// Set the poster image (async) // Set the poster image (async)
setPoster(poster) { // Used internally for the poster setter, with the passive option forced to false
// Set property regardless of validity setPoster(poster, passive = true) {
this.media.setAttribute('poster', poster); // Don't override if call is passive
if (passive && this.poster) {
// Bail if element is missing return Promise.reject(new Error('Poster already set'));
if (!is.element(this.elements.poster)) {
return Promise.reject();
} }
// Load the image, and set poster if successful // Set property synchronously to respect the call order
const loadPromise = loadImage(poster).then(() => { this.media.setAttribute('poster', poster);
this.elements.poster.style.backgroundImage = `url('${poster}')`;
Object.assign(this.elements.poster.style, { // Wait until ui is ready
backgroundImage: `url('${poster}')`, return ready.call(this)
// Reset backgroundSize as well (since it can be set to "cover" for padded thumbnails for youtube) // Load image
backgroundSize: '', .then(() => loadImage(poster))
.catch(err => {
// Hide poster on error unless it's been set by another call
if (poster === this.poster) {
ui.togglePoster.call(this, false);
}
// Rethrow
throw err;
})
.then(() => {
// Prevent race conditions
if (poster !== this.poster) {
throw new Error('setPoster cancelled by later call to setPoster');
}
})
.then(() => {
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;
}); });
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 // Check playing state

View File

@ -42,9 +42,11 @@ export function setAttributes(element, attributes) {
return; return;
} }
Object.entries(attributes).forEach(([key, value]) => { // Assume null and undefined attributes should be left out,
element.setAttribute(key, value); // Setting them would otherwise convert them to "null" and "undefined"
}); Object.entries(attributes)
.filter(([, value]) => !is.nullOrUndefined(value))
.forEach(([key, value]) => element.setAttribute(key, value));
} }
// Create a DocumentFragment // Create a DocumentFragment

View File

@ -111,3 +111,9 @@ export function unbindListeners() {
this.eventListeners = []; this.eventListeners = [];
} }
} }
// Run method when / if player is ready
export function ready () {
return new Promise(resolve => this.ready ? setTimeout(resolve, 0) : on.call(this, this.elements.container, 'ready', resolve))
.then(() => {});
}