Moved to provider + type to make it cleaner in future, fix for multiple players

This commit is contained in:
Sam Potts
2017-11-23 17:35:35 +11:00
parent de6f0f1b77
commit 921cefd212
22 changed files with 199 additions and 215 deletions

View File

@ -32,7 +32,7 @@ const captions = {
}
// Only Vimeo and HTML5 video supported at this point
if (!['video', 'vimeo'].includes(this.type) || (this.type === 'video' && !support.textTracks)) {
if (!this.isVideo || this.isYouTube || (this.isVideo && !support.textTracks)) {
// Clear menu and hide
if (this.config.controls.includes('settings') && this.config.settings.includes('captions')) {
controls.setCaptionsMenu.call(this);
@ -71,7 +71,7 @@ const captions = {
// Set the captions language
setLanguage() {
// Setup HTML5 track rendering
if (this.type === 'video') {
if (this.isVideo) {
captions.getTracks.call(this).forEach(track => {
// Remove previous bindings
utils.on(track, 'cuechange', event => captions.setCue.call(this, event));
@ -91,7 +91,7 @@ const captions = {
captions.setCue.call(this, currentTrack);
}
}
} else if (this.type === 'vimeo' && this.captions.active) {
} else if (this.isVimeo && this.captions.active) {
this.embed.enableTextTrack(this.language);
}
},

2
src/js/controls.js vendored
View File

@ -445,7 +445,7 @@ const controls = {
}
// Toggle the pane and tab
const toggle = !utils.is.empty(this.options.quality) && this.type === 'youtube';
const toggle = !utils.is.empty(this.options.quality) && this.isYouTube;
controls.toggleTab.call(this, type, toggle);
// If we're hiding, nothing more to do

View File

@ -13,7 +13,7 @@ const defaults = {
autoplay: false,
// Only allow one media playing at once (vimeo only)
autopause: false,
autopause: true,
// Default time to skip when rewind/fast forward
seekTime: 10,
@ -267,6 +267,7 @@ const defaults = {
embed: 'plyr__video-embed',
control: 'plyr__control',
type: 'plyr--{0}',
provider: 'plyr--{0}',
stopped: 'plyr--stopped',
playing: 'plyr--playing',
loading: 'plyr--loading',

View File

@ -92,7 +92,7 @@ const fullscreen = {
// Setup fullscreen
setup() {
if (!this.supported.ui || this.type === 'audio' || !this.config.fullscreen.enabled) {
if (!this.supported.ui || this.isAudio || !this.config.fullscreen.enabled) {
return;
}

View File

@ -221,7 +221,7 @@ const listeners = {
// Handle the media finishing
utils.on(this.media, 'ended', () => {
// Show poster on end
if (this.type === 'video' && this.config.showPosterOnEnd) {
if (this.isHTML5 && this.isVideo && this.config.showPosterOnEnd) {
// Restart
this.restart();
@ -243,7 +243,7 @@ const listeners = {
utils.on(this.media, 'stalled waiting canplay seeked playing', event => ui.checkLoading.call(this, event));
// Click video
if (this.supported.ui && this.config.clickToPlay && this.type !== 'audio') {
if (this.supported.ui && this.config.clickToPlay && !this.isAudio) {
// Re-fetch the wrapper
const wrapper = utils.getElement.call(this, `.${this.config.classNames.video}`);

View File

@ -23,6 +23,9 @@ const media = {
// Add type class
utils.toggleClass(this.elements.container, this.config.classNames.type.replace('{0}', this.type), true);
// Add provider class
utils.toggleClass(this.elements.container, this.config.classNames.provider.replace('{0}', this.provider), true);
// Add video class for embeds
// This will require changes if audio embeds are added
if (this.isEmbed) {
@ -31,7 +34,7 @@ const media = {
if (this.supported.ui) {
// Check for picture-in-picture support
utils.toggleClass(this.elements.container, this.config.classNames.pip.supported, support.pip && this.type === 'video');
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);
@ -47,7 +50,7 @@ const media = {
}
// Inject the player wrapper
if (['video', 'youtube', 'vimeo'].includes(this.type)) {
if (this.isVideo || this.isYouTube || this.isVimeo) {
// Create the wrapper div
this.elements.wrapper = utils.createElement('div', {
class: this.config.classNames.video,
@ -58,7 +61,7 @@ const media = {
}
if (this.isEmbed) {
switch (this.type) {
switch (this.provider) {
case 'youtube':
youtube.setup.call(this);
break;

View File

@ -4,13 +4,12 @@
import utils from './../utils';
import captions from './../captions';
import controls from './../controls';
import ui from './../ui';
const vimeo = {
setup() {
// Remove old containers
const containers = utils.getElements.call(this, `[id^="${this.type}-"]`);
const containers = utils.getElements.call(this, `[id^="${this.provider}-"]`);
Array.from(containers).forEach(utils.removeElement);
// Add embed class for responsive
@ -20,7 +19,7 @@ const vimeo = {
vimeo.setAspectRatio.call(this);
// Set ID
this.media.setAttribute('id', utils.generateId(this.type));
this.media.setAttribute('id', utils.generateId(this.provider));
// Load the API if not already
if (!utils.is.object(window.Vimeo)) {

View File

@ -11,7 +11,7 @@ const youtube = {
const videoId = utils.parseYouTubeId(this.embedId);
// Remove old containers
const containers = utils.getElements.call(this, `[id^="${this.type}-"]`);
const containers = utils.getElements.call(this, `[id^="${this.provider}-"]`);
Array.from(containers).forEach(utils.removeElement);
// Add embed class for responsive
@ -21,7 +21,7 @@ const youtube = {
youtube.setAspectRatio.call(this);
// Set ID
this.media.setAttribute('id', utils.generateId(this.type));
this.media.setAttribute('id', utils.generateId(this.provider));
// Setup API
if (utils.is.object(window.YT)) {
@ -31,6 +31,7 @@ const youtube = {
utils.loadScript(this.config.urls.youtube.api);
// Setup callback for the API
// YouTube has it's own system of course...
window.onYouTubeReadyCallbacks = window.onYouTubeReadyCallbacks || [];
// Add to queue

View File

@ -5,8 +5,8 @@
// License: The MIT License (MIT)
// ==========================================================================
import { providers, types } from './types';
import defaults from './defaults';
import types from './types';
import support from './support';
import utils from './utils';
@ -40,11 +40,7 @@ class Plyr {
}
// jQuery, NodeList or Array passed, use first element
if (
(window.jQuery && this.media instanceof jQuery) ||
utils.is.nodeList(this.media) ||
utils.is.array(this.media)
) {
if ((window.jQuery && this.media instanceof jQuery) || utils.is.nodeList(this.media) || utils.is.array(this.media)) {
// eslint-disable-next-line
this.media = this.media[0];
}
@ -149,7 +145,7 @@ class Plyr {
// Embed attributes
const attributes = {
provider: 'data-plyr-provider',
id: 'data-plyr-provider-id',
id: 'data-plyr-embed-id',
};
// Different setup based on type
@ -157,16 +153,18 @@ class Plyr {
// TODO: Handle passing an iframe for true progressive enhancement
// case 'iframe':
case 'div':
this.type = this.media.getAttribute(attributes.provider);
this.type = types.video; // Audio will come later for external providers
this.provider = this.media.getAttribute(attributes.provider);
this.embedId = this.media.getAttribute(attributes.id);
if (utils.is.empty(this.type)) {
this.console.error('Setup failed: embed type missing');
if (utils.is.empty(this.provider) || !Object.keys(providers).includes(this.provider)) {
this.console.error('Setup failed: Invalid provider');
return;
}
// Try and get the embed id
if (utils.is.empty(this.embedId)) {
this.console.error('Setup failed: video id missing');
this.console.error('Setup failed: Embed ID or URL missing');
return;
}
@ -179,19 +177,24 @@ class Plyr {
case 'video':
case 'audio':
this.type = type;
this.provider = providers.html5;
if (this.media.hasAttribute('crossorigin')) {
this.config.crossorigin = true;
}
if (this.media.hasAttribute('autoplay')) {
this.config.autoplay = true;
}
if (this.media.hasAttribute('playsinline')) {
this.config.inline = true;
}
if (this.media.hasAttribute('muted')) {
this.config.muted = true;
}
if (this.media.hasAttribute('loop')) {
this.config.loop.active = true;
}
@ -207,7 +210,7 @@ class Plyr {
storage.setup.call(this);
// Check for support again but with type
this.supported = support.check(this.type, this.config.inline);
this.supported = support.check(this.type, this.provider, this.config.inline);
// If no support for even API, bail
if (!this.supported.api) {
@ -253,17 +256,25 @@ class Plyr {
// ---------------------------------------
/**
* If the player is HTML5
* Types and provider helpers
*/
get isHTML5() {
return types.html5.includes(this.type);
return this.provider === providers.html5;
}
/**
* If the player is an embed - e.g. YouTube or Vimeo
*/
get isEmbed() {
return types.embed.includes(this.type);
return this.isYouTube || this.isVimeo;
}
get isYouTube() {
return this.provider === providers.youtube;
}
get isVimeo() {
return this.provider === providers.vimeo;
}
get isVideo() {
return this.type === types.video;
}
get isAudio() {
return this.type === types.audio;
}
/**
@ -518,11 +529,7 @@ class Plyr {
}
// Get audio tracks
return (
this.media.mozHasAudio ||
Boolean(this.media.webkitAudioDecodedByteCount) ||
Boolean(this.media.audioTracks && this.media.audioTracks.length)
);
return this.media.mozHasAudio || Boolean(this.media.webkitAudioDecodedByteCount) || Boolean(this.media.audioTracks && this.media.audioTracks.length);
}
/**
@ -683,7 +690,7 @@ class Plyr {
* @param {input} - the URL for the new poster image
*/
set poster(input) {
if (!this.isHTML5 || this.type !== 'video') {
if (!this.isHTML5 || !this.isVideo) {
this.console.warn('Poster can only be set on HTML5 video');
return;
}
@ -697,7 +704,7 @@ class Plyr {
* Get the current poster image
*/
get poster() {
if (!this.isHTML5 || this.type !== 'video') {
if (!this.isHTML5 || !this.isVideo) {
return null;
}
@ -731,9 +738,7 @@ class Plyr {
}
// If the method is called without parameter, toggle based on current value
const show = utils.is.boolean(input)
? input
: this.elements.container.className.indexOf(this.config.classNames.captions.active) === -1;
const show = utils.is.boolean(input) ? input : this.elements.container.className.indexOf(this.config.classNames.captions.active) === -1;
// Nothing to change...
if (this.captions.enabled === show) {
@ -828,11 +833,7 @@ class Plyr {
this.fullscreen.active = !this.fullscreen.active;
// Add class hook
utils.toggleClass(
this.elements.container,
this.config.classNames.fullscreen.fallback,
this.fullscreen.active
);
utils.toggleClass(this.elements.container, this.config.classNames.fullscreen.fallback, this.fullscreen.active);
// Make sure we don't lose scroll position
if (this.fullscreen.active) {
@ -920,7 +921,7 @@ class Plyr {
}
// Don't hide if no UI support or it's audio
if (!this.supported.ui || this.type === 'audio') {
if (!this.supported.ui || this.isAudio) {
return this;
}
@ -980,13 +981,13 @@ class Plyr {
// then set the timer to hide the controls
if (!show || this.playing) {
this.timers.controls = window.setTimeout(() => {
console.warn({
/* this.console.warn({
pressed: this.elements.controls.pressed,
hover: this.elements.controls.pressed,
playing: this.playing,
paused: this.paused,
loading: this.loading,
});
}); */
// If the mouse is over the controls (and not entering fullscreen), bail
if ((this.elements.controls.pressed || this.elements.controls.hover) && !isEnterFullscreen) {
@ -1105,8 +1106,18 @@ class Plyr {
};
// Type specific stuff
switch (this.type) {
case 'youtube':
switch (`${this.provider}:${this.type}`) {
case 'html5:video':
case 'html5:audio':
// Restore native video controls
ui.toggleNativeControls.call(this, true);
// Clean up
done();
break;
case 'youtube:video':
// Clear timers
window.clearInterval(this.timers.buffering);
window.clearInterval(this.timers.playing);
@ -1119,7 +1130,7 @@ class Plyr {
break;
case 'vimeo':
case 'vimeo:video':
// Destroy Vimeo API
// then clean up (wait, to prevent postmessage errors)
this.embed.unload().then(done);
@ -1129,16 +1140,6 @@ class Plyr {
break;
case 'video':
case 'audio':
// Restore native video controls
ui.toggleNativeControls.call(this, true);
// Clean up
done();
break;
default:
break;
}

View File

@ -2,7 +2,7 @@
// Plyr source update
// ==========================================================================
import types from './types';
import { providers } from './types';
import utils from './utils';
import media from './media';
import ui from './ui';
@ -48,35 +48,25 @@ const source = {
this.elements.container.removeAttribute('class');
}
// Set the type
if ('type' in input) {
this.type = input.type;
// Get child type for video (it might be an embed)
if (this.type === 'video') {
const firstSource = input.sources[0];
if ('type' in firstSource && types.embed.includes(firstSource.type)) {
this.type = firstSource.type;
}
}
}
// Set the type and provider
this.type = input.type;
this.provider = !utils.is.empty(input.sources[0].provider) ? input.sources[0].provider : providers.html5;
// Check for support
this.supported = support.check(this.type, this.config.inline);
this.supported = support.check(this.type, this.provider, this.config.inline);
// Create new markup
switch (this.type) {
case 'video':
switch (`${this.provider}:${this.type}`) {
case 'html5:video':
this.media = utils.createElement('video');
break;
case 'audio':
case 'html5:audio':
this.media = utils.createElement('audio');
break;
case 'youtube':
case 'vimeo':
case 'youtube:video':
case 'vimeo:video':
this.media = utils.createElement('div');
this.embedId = input.sources[0].src;
break;
@ -117,7 +107,6 @@ const source = {
// Restore class hooks
utils.toggleClass(this.elements.container, this.config.classNames.captions.active, this.supported.ui && this.captions.enabled);
ui.addStyleHook.call(this);
// Set new sources for html5

View File

@ -12,29 +12,29 @@ const support = {
// Check for support
// Basic functionality vs full UI
check(type, inline) {
check(type, provider, inline) {
let api = false;
let ui = false;
const browser = utils.getBrowser();
const playsInline = browser.isIPhone && inline && support.inline;
switch (type) {
case 'video':
switch (`${provider}:${type}`) {
case 'html5:video':
api = support.video;
ui = api && support.rangeInput && (!browser.isIPhone || playsInline);
break;
case 'audio':
case 'html5:audio':
api = support.audio;
ui = api && support.rangeInput;
break;
case 'youtube':
case 'youtube:video':
api = true;
ui = support.rangeInput && (!browser.isIPhone || playsInline);
break;
case 'vimeo':
case 'vimeo:video':
api = true;
ui = support.rangeInput && !browser.isIPhone;
break;
@ -92,12 +92,12 @@ const support = {
try {
// Bail if no checking function
if (!utils.is.function(media.canPlayType)) {
if (!this.isHTML5 || !utils.is.function(media.canPlayType)) {
return false;
}
// Type specific checks
if (this.type === 'video') {
if (this.isVideo) {
switch (type) {
case 'video/webm':
return media.canPlayType('video/webm; codecs="vp8, vorbis"').replace(/no/, '');
@ -111,7 +111,7 @@ const support = {
default:
return false;
}
} else if (this.type === 'audio') {
} else if (this.isAudio) {
switch (type) {
case 'audio/mpeg':
return media.canPlayType('audio/mpeg;').replace(/no/, '');

View File

@ -1,10 +1,16 @@
// ==========================================================================
// Plyr supported types
// Plyr supported types and providers
// ==========================================================================
const types = {
embed: ['youtube', 'vimeo'],
html5: ['video', 'audio'],
export const providers = {
html5: 'html5',
youtube: 'youtube',
vimeo: 'vimeo',
};
export default types;
export const types = {
audio: 'audio',
video: 'video',
};
export default { providers, types };

View File

@ -31,7 +31,7 @@ const ui = {
// Don't setup interface if no support
if (!this.supported.ui) {
this.console.warn(`Basic support only for ${this.type}`);
this.console.warn(`Basic support only for ${this.provider} ${this.type}`);
// Remove controls
utils.removeElement.call(this, 'controls');

View File

@ -73,24 +73,39 @@ const utils = {
// Load an external script
loadScript(url, callback) {
// Check script is not already referenced
if (document.querySelectorAll(`script[src="${url}"]`).length) {
const current = document.querySelector(`script[src="${url}"]`);
// Check script is not already referenced, if so wait for load
if (current !== null) {
current.callbacks = current.callbacks || [];
current.callbacks.push(callback);
return;
}
// Build the element
const element = document.createElement('script');
element.src = url;
// Find first script
const first = document.getElementsByTagName('script')[0];
// Callback queue
element.callbacks = element.callbacks || [];
element.callbacks.push(callback);
// Bind callback
if (utils.is.function(callback)) {
element.addEventListener('load', event => callback.call(null, event), false);
element.addEventListener(
'load',
event => {
element.callbacks.forEach(cb => cb.call(null, event));
element.callbacks = null;
},
false
);
}
// Set the URL after binding callback
element.src = url;
// Inject
const first = document.getElementsByTagName('script')[0];
first.parentNode.insertBefore(element, first);
},