v3.3.0
This commit is contained in:
@ -3,10 +3,10 @@
|
||||
// TODO: Create as class
|
||||
// ==========================================================================
|
||||
|
||||
import support from './support';
|
||||
import utils from './utils';
|
||||
import controls from './controls';
|
||||
import i18n from './i18n';
|
||||
import support from './support';
|
||||
import utils from './utils';
|
||||
|
||||
const captions = {
|
||||
// Setup captions
|
||||
|
87
src/js/controls.js
vendored
87
src/js/controls.js
vendored
@ -2,12 +2,12 @@
|
||||
// Plyr controls
|
||||
// ==========================================================================
|
||||
|
||||
import support from './support';
|
||||
import utils from './utils';
|
||||
import ui from './ui';
|
||||
import i18n from './i18n';
|
||||
import captions from './captions';
|
||||
import html5 from './html5';
|
||||
import i18n from './i18n';
|
||||
import support from './support';
|
||||
import ui from './ui';
|
||||
import utils from './utils';
|
||||
|
||||
// Sniff out the browser
|
||||
const browser = utils.getBrowser();
|
||||
@ -37,17 +37,74 @@ const controls = {
|
||||
|
||||
// Get icon URL
|
||||
getIconUrl() {
|
||||
const url = new URL(this.config.iconUrl, window.location);
|
||||
const cors = url.host !== window.location.host || (browser.isIE && !window.svg4everybody);
|
||||
|
||||
return {
|
||||
url: this.config.iconUrl,
|
||||
absolute: this.config.iconUrl.indexOf('http') === 0 || (browser.isIE && !window.svg4everybody),
|
||||
cors,
|
||||
};
|
||||
},
|
||||
|
||||
// Find the UI controls and store references in custom controls
|
||||
// TODO: Allow settings menus with custom controls
|
||||
findElements() {
|
||||
try {
|
||||
this.elements.controls = utils.getElement.call(this, this.config.selectors.controls.wrapper);
|
||||
|
||||
// Buttons
|
||||
this.elements.buttons = {
|
||||
play: utils.getElements.call(this, this.config.selectors.buttons.play),
|
||||
pause: utils.getElement.call(this, this.config.selectors.buttons.pause),
|
||||
restart: utils.getElement.call(this, this.config.selectors.buttons.restart),
|
||||
rewind: utils.getElement.call(this, this.config.selectors.buttons.rewind),
|
||||
fastForward: utils.getElement.call(this, this.config.selectors.buttons.fastForward),
|
||||
mute: utils.getElement.call(this, this.config.selectors.buttons.mute),
|
||||
pip: utils.getElement.call(this, this.config.selectors.buttons.pip),
|
||||
airplay: utils.getElement.call(this, this.config.selectors.buttons.airplay),
|
||||
settings: utils.getElement.call(this, this.config.selectors.buttons.settings),
|
||||
captions: utils.getElement.call(this, this.config.selectors.buttons.captions),
|
||||
fullscreen: utils.getElement.call(this, this.config.selectors.buttons.fullscreen),
|
||||
};
|
||||
|
||||
// Progress
|
||||
this.elements.progress = utils.getElement.call(this, this.config.selectors.progress);
|
||||
|
||||
// Inputs
|
||||
this.elements.inputs = {
|
||||
seek: utils.getElement.call(this, this.config.selectors.inputs.seek),
|
||||
volume: utils.getElement.call(this, this.config.selectors.inputs.volume),
|
||||
};
|
||||
|
||||
// Display
|
||||
this.elements.display = {
|
||||
buffer: utils.getElement.call(this, this.config.selectors.display.buffer),
|
||||
currentTime: utils.getElement.call(this, this.config.selectors.display.currentTime),
|
||||
duration: utils.getElement.call(this, this.config.selectors.display.duration),
|
||||
};
|
||||
|
||||
// Seek tooltip
|
||||
if (utils.is.element(this.elements.progress)) {
|
||||
this.elements.display.seekTooltip = this.elements.progress.querySelector(`.${this.config.classNames.tooltip}`);
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
// Log it
|
||||
this.debug.warn('It looks like there is a problem with your custom controls HTML', error);
|
||||
|
||||
// Restore native video controls
|
||||
this.toggleNativeControls(true);
|
||||
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
// Create <svg> icon
|
||||
createIcon(type, attributes) {
|
||||
const namespace = 'http://www.w3.org/2000/svg';
|
||||
const iconUrl = controls.getIconUrl.call(this);
|
||||
const iconPath = `${!iconUrl.absolute ? iconUrl.url : ''}#${this.config.iconPrefix}`;
|
||||
const iconPath = `${!iconUrl.cors ? iconUrl.url : ''}#${this.config.iconPrefix}`;
|
||||
|
||||
// Create <svg>
|
||||
const icon = document.createElementNS(namespace, 'svg');
|
||||
@ -840,11 +897,9 @@ const controls = {
|
||||
},
|
||||
|
||||
// Toggle Menu
|
||||
showTab(event) {
|
||||
showTab(target = '') {
|
||||
const { menu } = this.elements.settings;
|
||||
const tab = event.target;
|
||||
const show = tab.getAttribute('aria-expanded') === 'false';
|
||||
const pane = document.getElementById(tab.getAttribute('aria-controls'));
|
||||
const pane = document.getElementById(target);
|
||||
|
||||
// Nothing to show, bail
|
||||
if (!utils.is.element(pane)) {
|
||||
@ -907,8 +962,12 @@ const controls = {
|
||||
current.setAttribute('tabindex', -1);
|
||||
|
||||
// Set attributes on target
|
||||
utils.toggleHidden(pane, !show);
|
||||
tab.setAttribute('aria-expanded', show);
|
||||
utils.toggleHidden(pane, false);
|
||||
|
||||
const tabs = utils.getElements.call(this, `[aria-controls="${target}"]`);
|
||||
Array.from(tabs).forEach(tab => {
|
||||
tab.setAttribute('aria-expanded', true);
|
||||
});
|
||||
pane.removeAttribute('tabindex');
|
||||
|
||||
// Focus the first item
|
||||
@ -1183,7 +1242,7 @@ const controls = {
|
||||
const icon = controls.getIconUrl.call(this);
|
||||
|
||||
// Only load external sprite using AJAX
|
||||
if (icon.absolute) {
|
||||
if (icon.cors) {
|
||||
utils.loadSprite(icon.url, 'sprite-plyr');
|
||||
}
|
||||
}
|
||||
@ -1269,7 +1328,7 @@ const controls = {
|
||||
|
||||
// Find the elements if need be
|
||||
if (!utils.is.element(this.elements.controls)) {
|
||||
utils.findElements.call(this);
|
||||
controls.findElements.call(this);
|
||||
}
|
||||
|
||||
// Edge sometimes doesn't finish the paint so force a redraw
|
||||
|
@ -47,8 +47,8 @@ const defaults = {
|
||||
// Auto hide the controls
|
||||
hideControls: true,
|
||||
|
||||
// Revert to poster on finish (HTML5 - will cause reload)
|
||||
showPosterOnEnd: false,
|
||||
// Reset to start when playback ended
|
||||
resetOnEnd: false,
|
||||
|
||||
// Disable the standard context menu
|
||||
disableContextMenu: true,
|
||||
@ -56,7 +56,7 @@ const defaults = {
|
||||
// Sprite (for icons)
|
||||
loadSprite: true,
|
||||
iconPrefix: 'plyr',
|
||||
iconUrl: 'https://cdn.plyr.io/3.2.4/plyr.svg',
|
||||
iconUrl: 'https://cdn.plyr.io/3.3.0/plyr.svg',
|
||||
|
||||
// Blank video (used to prevent errors on source change)
|
||||
blankVideo: 'https://cdn.plyr.io/static/blank.mp4',
|
||||
@ -192,13 +192,17 @@ const defaults = {
|
||||
// URLs
|
||||
urls: {
|
||||
vimeo: {
|
||||
api: 'https://player.vimeo.com/api/player.js',
|
||||
sdk: 'https://player.vimeo.com/api/player.js',
|
||||
iframe: 'https://player.vimeo.com/video/{0}?{1}',
|
||||
api: 'https://vimeo.com/api/v2/video/{0}.json',
|
||||
},
|
||||
youtube: {
|
||||
api: 'https://www.youtube.com/iframe_api',
|
||||
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: {
|
||||
api: 'https://imasdk.googleapis.com/js/sdkloader/ima3.js',
|
||||
sdk: 'https://imasdk.googleapis.com/js/sdkloader/ima3.js',
|
||||
},
|
||||
},
|
||||
|
||||
@ -324,12 +328,14 @@ const defaults = {
|
||||
classNames: {
|
||||
video: 'plyr__video-wrapper',
|
||||
embed: 'plyr__video-embed',
|
||||
poster: 'plyr__poster',
|
||||
ads: 'plyr__ads',
|
||||
control: 'plyr__control',
|
||||
type: 'plyr--{0}',
|
||||
provider: 'plyr--{0}',
|
||||
stopped: 'plyr--stopped',
|
||||
playing: 'plyr--playing',
|
||||
paused: 'plyr--paused',
|
||||
stopped: 'plyr--stopped',
|
||||
loading: 'plyr--loading',
|
||||
error: 'plyr--has-error',
|
||||
hover: 'plyr--hover',
|
||||
|
@ -2,9 +2,9 @@
|
||||
// Plyr Event Listeners
|
||||
// ==========================================================================
|
||||
|
||||
import utils from './utils';
|
||||
import controls from './controls';
|
||||
import ui from './ui';
|
||||
import utils from './utils';
|
||||
|
||||
// Sniff out the browser
|
||||
const browser = utils.getBrowser();
|
||||
@ -265,12 +265,9 @@ class Listeners {
|
||||
// Handle the media finishing
|
||||
utils.on(this.player.media, 'ended', () => {
|
||||
// Show poster on end
|
||||
if (this.player.isHTML5 && this.player.isVideo && this.player.config.showPosterOnEnd) {
|
||||
if (this.player.isHTML5 && this.player.isVideo && this.player.config.resetOnEnd) {
|
||||
// Restart
|
||||
this.player.restart();
|
||||
|
||||
// Re-load media
|
||||
this.player.media.load();
|
||||
}
|
||||
});
|
||||
|
||||
@ -281,7 +278,7 @@ class Listeners {
|
||||
utils.on(this.player.media, 'volumechange', event => ui.updateVolume.call(this.player, event));
|
||||
|
||||
// Handle play/pause
|
||||
utils.on(this.player.media, 'playing play pause ended emptied', event => ui.checkPlaying.call(this.player, event));
|
||||
utils.on(this.player.media, 'playing play pause ended emptied timeupdate', event => ui.checkPlaying.call(this.player, event));
|
||||
|
||||
// Loading state
|
||||
utils.on(this.player.media, 'waiting canplay seeked playing', event => ui.checkLoading.call(this.player, event));
|
||||
@ -492,12 +489,19 @@ class Listeners {
|
||||
on(this.player.elements.settings.form, 'click', event => {
|
||||
event.stopPropagation();
|
||||
|
||||
// Go back to home tab on click
|
||||
const showHomeTab = () => {
|
||||
const id = `plyr-settings-${this.player.id}-home`;
|
||||
controls.showTab.call(this.player, id);
|
||||
};
|
||||
|
||||
// Settings menu items - use event delegation as items are added/removed
|
||||
if (utils.matches(event.target, this.player.config.selectors.inputs.language)) {
|
||||
proxy(
|
||||
event,
|
||||
() => {
|
||||
this.player.language = event.target.value;
|
||||
showHomeTab();
|
||||
},
|
||||
'language',
|
||||
);
|
||||
@ -506,6 +510,7 @@ class Listeners {
|
||||
event,
|
||||
() => {
|
||||
this.player.quality = event.target.value;
|
||||
showHomeTab();
|
||||
},
|
||||
'quality',
|
||||
);
|
||||
@ -514,11 +519,13 @@ class Listeners {
|
||||
event,
|
||||
() => {
|
||||
this.player.speed = parseFloat(event.target.value);
|
||||
showHomeTab();
|
||||
},
|
||||
'speed',
|
||||
);
|
||||
} else {
|
||||
controls.showTab.call(this.player, event);
|
||||
const tab = event.target;
|
||||
controls.showTab.call(this.player, tab.getAttribute('aria-controls'));
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -2,15 +2,10 @@
|
||||
// Plyr Media
|
||||
// ==========================================================================
|
||||
|
||||
import support from './support';
|
||||
import utils from './utils';
|
||||
import youtube from './plugins/youtube';
|
||||
import vimeo from './plugins/vimeo';
|
||||
import html5 from './html5';
|
||||
import ui from './ui';
|
||||
|
||||
// Sniff out the browser
|
||||
const browser = utils.getBrowser();
|
||||
import vimeo from './plugins/vimeo';
|
||||
import youtube from './plugins/youtube';
|
||||
import utils from './utils';
|
||||
|
||||
const media = {
|
||||
// Setup media
|
||||
@ -33,23 +28,6 @@ const media = {
|
||||
utils.toggleClass(this.elements.container, this.config.classNames.type.replace('{0}', 'video'), true);
|
||||
}
|
||||
|
||||
if (this.supported.ui) {
|
||||
// Check for picture-in-picture support
|
||||
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);
|
||||
|
||||
// If there's no autoplay attribute, assume the video is stopped and add state class
|
||||
utils.toggleClass(this.elements.container, this.config.classNames.stopped, this.config.autoplay);
|
||||
|
||||
// Add iOS class
|
||||
utils.toggleClass(this.elements.container, this.config.classNames.isIos, browser.isIos);
|
||||
|
||||
// Add touch class
|
||||
utils.toggleClass(this.elements.container, this.config.classNames.isTouch, this.touch);
|
||||
}
|
||||
|
||||
// Inject the player wrapper
|
||||
if (this.isVideo) {
|
||||
// Create the wrapper div
|
||||
@ -59,6 +37,13 @@ const media = {
|
||||
|
||||
// Wrap the video in a container
|
||||
utils.wrap(this.media, this.elements.wrapper);
|
||||
|
||||
// Faux poster container
|
||||
this.elements.poster = utils.createElement('span', {
|
||||
class: this.config.classNames.poster,
|
||||
});
|
||||
|
||||
this.elements.wrapper.appendChild(this.elements.poster);
|
||||
}
|
||||
|
||||
if (this.isEmbed) {
|
||||
@ -75,8 +60,6 @@ const media = {
|
||||
break;
|
||||
}
|
||||
} else if (this.isHTML5) {
|
||||
ui.setTitle.call(this);
|
||||
|
||||
html5.extend.call(this);
|
||||
}
|
||||
},
|
||||
|
@ -6,8 +6,8 @@
|
||||
|
||||
/* global google */
|
||||
|
||||
import utils from '../utils';
|
||||
import i18n from '../i18n';
|
||||
import utils from '../utils';
|
||||
|
||||
class Ads {
|
||||
/**
|
||||
@ -52,7 +52,7 @@ class Ads {
|
||||
// Check if the Google IMA3 SDK is loaded or load it ourselves
|
||||
if (!utils.is.object(window.google) || !utils.is.object(window.google.ima)) {
|
||||
utils
|
||||
.loadScript(this.player.config.urls.googleIMA.api)
|
||||
.loadScript(this.player.config.urls.googleIMA.sdk)
|
||||
.then(() => {
|
||||
this.ready();
|
||||
})
|
||||
@ -160,6 +160,9 @@ class Ads {
|
||||
// We only overlay ads as we only support video.
|
||||
request.forceNonLinearFullSlot = false;
|
||||
|
||||
// Mute based on current state
|
||||
request.setAdWillPlayMuted(!this.player.muted);
|
||||
|
||||
this.loader.requestAds(request);
|
||||
} catch (e) {
|
||||
this.onAdError(e);
|
||||
@ -226,7 +229,7 @@ class Ads {
|
||||
|
||||
// Get skippable state
|
||||
// TODO: Skip button
|
||||
// this.manager.getAdSkippableState();
|
||||
// this.player.debug.warn(this.manager.getAdSkippableState());
|
||||
|
||||
// Set volume to match player
|
||||
this.manager.setVolume(this.player.volume);
|
||||
|
@ -2,10 +2,10 @@
|
||||
// Vimeo plugin
|
||||
// ==========================================================================
|
||||
|
||||
import utils from './../utils';
|
||||
import captions from './../captions';
|
||||
import controls from './../controls';
|
||||
import ui from './../ui';
|
||||
import utils from './../utils';
|
||||
|
||||
const vimeo = {
|
||||
setup() {
|
||||
@ -18,7 +18,7 @@ const vimeo = {
|
||||
// Load the API if not already
|
||||
if (!utils.is.object(window.Vimeo)) {
|
||||
utils
|
||||
.loadScript(this.config.urls.vimeo.api)
|
||||
.loadScript(this.config.urls.vimeo.sdk)
|
||||
.then(() => {
|
||||
vimeo.ready.call(this);
|
||||
})
|
||||
@ -68,14 +68,14 @@ const vimeo = {
|
||||
|
||||
// Get from <div> if needed
|
||||
if (utils.is.empty(source)) {
|
||||
source = player.media.getAttribute(this.config.attributes.embed.id);
|
||||
source = player.media.getAttribute(player.config.attributes.embed.id);
|
||||
}
|
||||
|
||||
const id = utils.parseVimeoId(source);
|
||||
|
||||
// Build an iframe
|
||||
const iframe = utils.createElement('iframe');
|
||||
const src = `https://player.vimeo.com/video/${id}?${params}`;
|
||||
const src = utils.format(player.config.urls.vimeo.iframe, id, params);
|
||||
iframe.setAttribute('src', src);
|
||||
iframe.setAttribute('allowfullscreen', '');
|
||||
iframe.setAttribute('allowtransparency', '');
|
||||
@ -86,6 +86,25 @@ const vimeo = {
|
||||
wrapper.appendChild(iframe);
|
||||
player.media = utils.replaceElement(wrapper, player.media);
|
||||
|
||||
// Get poster image
|
||||
utils.fetch(utils.format(player.config.urls.vimeo.api, id), 'json').then(response => {
|
||||
if (utils.is.empty(response)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the URL for thumbnail
|
||||
const url = new URL(response[0].thumbnail_large);
|
||||
|
||||
// Get original image
|
||||
url.pathname = `${url.pathname.split('_')[0]}.jpg`;
|
||||
|
||||
// Set attribute
|
||||
player.media.setAttribute('poster', url.href);
|
||||
|
||||
// Update
|
||||
ui.setPoster.call(player);
|
||||
});
|
||||
|
||||
// Setup instance
|
||||
// https://github.com/vimeo/player.js
|
||||
player.embed = new window.Vimeo.Player(iframe);
|
||||
|
@ -2,9 +2,9 @@
|
||||
// YouTube plugin
|
||||
// ==========================================================================
|
||||
|
||||
import utils from './../utils';
|
||||
import controls from './../controls';
|
||||
import ui from './../ui';
|
||||
import utils from './../utils';
|
||||
|
||||
// Standardise YouTube quality unit
|
||||
function mapQualityUnit(input) {
|
||||
@ -77,7 +77,7 @@ const youtube = {
|
||||
youtube.ready.call(this);
|
||||
} else {
|
||||
// Load the API
|
||||
utils.loadScript(this.config.urls.youtube.api).catch(error => {
|
||||
utils.loadScript(this.config.urls.youtube.sdk).catch(error => {
|
||||
this.debug.warn('YouTube API failed to load', error);
|
||||
});
|
||||
|
||||
@ -117,7 +117,7 @@ const youtube = {
|
||||
// Or via Google API
|
||||
const key = this.config.keys.google;
|
||||
if (utils.is.string(key) && !utils.is.empty(key)) {
|
||||
const url = `https://www.googleapis.com/youtube/v3/videos?id=${videoId}&key=${key}&fields=items(snippet(title))&part=snippet`;
|
||||
const url = utils.format(this.config.urls.youtube.api, videoId, key);
|
||||
|
||||
utils
|
||||
.fetch(url)
|
||||
@ -161,6 +161,9 @@ const youtube = {
|
||||
const container = utils.createElement('div', { id });
|
||||
player.media = utils.replaceElement(container, player.media);
|
||||
|
||||
// Set poster image
|
||||
player.media.setAttribute('poster', utils.format(player.config.urls.youtube.poster, videoId));
|
||||
|
||||
// Setup instance
|
||||
// https://developers.google.com/youtube/iframe_api_reference
|
||||
player.embed = new window.YT.Player(id, {
|
||||
|
@ -1,26 +1,24 @@
|
||||
// ==========================================================================
|
||||
// Plyr
|
||||
// plyr.js v3.2.4
|
||||
// plyr.js v3.3.0
|
||||
// https://github.com/sampotts/plyr
|
||||
// License: The MIT License (MIT)
|
||||
// ==========================================================================
|
||||
|
||||
import { providers, types } from './types';
|
||||
import defaults from './defaults';
|
||||
import support from './support';
|
||||
import utils from './utils';
|
||||
|
||||
import captions from './captions';
|
||||
import Console from './console';
|
||||
import controls from './controls';
|
||||
import defaults from './defaults';
|
||||
import Fullscreen from './fullscreen';
|
||||
import Listeners from './listeners';
|
||||
import Storage from './storage';
|
||||
import Ads from './plugins/ads';
|
||||
|
||||
import captions from './captions';
|
||||
import controls from './controls';
|
||||
import media from './media';
|
||||
import Ads from './plugins/ads';
|
||||
import source from './source';
|
||||
import Storage from './storage';
|
||||
import support from './support';
|
||||
import { providers, types } from './types';
|
||||
import ui from './ui';
|
||||
import utils from './utils';
|
||||
|
||||
// Private properties
|
||||
// TODO: Use a WeakMap for private globals
|
||||
@ -134,17 +132,9 @@ class Plyr {
|
||||
}
|
||||
|
||||
// Cache original element state for .destroy()
|
||||
// TODO: Investigate a better solution as I suspect this causes reported double load issues?
|
||||
setTimeout(() => {
|
||||
const clone = this.media.cloneNode(true);
|
||||
|
||||
// Prevent the clone autoplaying
|
||||
if (clone.getAttribute('autoplay')) {
|
||||
clone.pause();
|
||||
}
|
||||
|
||||
this.elements.original = clone;
|
||||
}, 0);
|
||||
const clone = this.media.cloneNode(true);
|
||||
clone.autoplay = false;
|
||||
this.elements.original = clone;
|
||||
|
||||
// Set media type based on tag or data attribute
|
||||
// Supported: video, audio, vimeo, youtube
|
||||
@ -363,6 +353,13 @@ class Plyr {
|
||||
this.media.pause();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get playing state
|
||||
*/
|
||||
get playing() {
|
||||
return Boolean(this.ready && !this.paused && !this.ended);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get paused state
|
||||
*/
|
||||
@ -371,10 +368,10 @@ class Plyr {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get playing state
|
||||
* Get stopped state
|
||||
*/
|
||||
get playing() {
|
||||
return Boolean(this.ready && !this.paused && !this.ended && (this.isHTML5 ? this.media.readyState > 2 : true));
|
||||
get stopped() {
|
||||
return Boolean(this.paused && this.currentTime === 0);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -799,17 +796,18 @@ class Plyr {
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the poster image for a HTML5 video
|
||||
* Set the poster image for a video
|
||||
* @param {input} - the URL for the new poster image
|
||||
*/
|
||||
set poster(input) {
|
||||
if (!this.isHTML5 || !this.isVideo) {
|
||||
this.debug.warn('Poster can only be set on HTML5 video');
|
||||
if (!this.isVideo) {
|
||||
this.debug.warn('Poster can only be set for video');
|
||||
return;
|
||||
}
|
||||
|
||||
if (utils.is.string(input)) {
|
||||
this.media.setAttribute('poster', input);
|
||||
ui.setPoster.call(this);
|
||||
}
|
||||
}
|
||||
|
||||
@ -817,7 +815,7 @@ class Plyr {
|
||||
* Get the current poster image
|
||||
*/
|
||||
get poster() {
|
||||
if (!this.isHTML5 || !this.isVideo) {
|
||||
if (!this.isVideo) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -1,14 +1,12 @@
|
||||
// ==========================================================================
|
||||
// Plyr Polyfilled Build
|
||||
// plyr.js v3.2.4
|
||||
// plyr.js v3.3.0
|
||||
// https://github.com/sampotts/plyr
|
||||
// License: The MIT License (MIT)
|
||||
// ==========================================================================
|
||||
|
||||
import 'babel-polyfill';
|
||||
|
||||
import 'custom-event-polyfill';
|
||||
|
||||
import Plyr from './plyr';
|
||||
|
||||
export default Plyr;
|
||||
|
38
src/js/ui.js
38
src/js/ui.js
@ -2,10 +2,14 @@
|
||||
// Plyr UI
|
||||
// ==========================================================================
|
||||
|
||||
import utils from './utils';
|
||||
import captions from './captions';
|
||||
import controls from './controls';
|
||||
import i18n from './i18n';
|
||||
import support from './support';
|
||||
import utils from './utils';
|
||||
|
||||
// Sniff out the browser
|
||||
const browser = utils.getBrowser();
|
||||
|
||||
const ui = {
|
||||
addStyleHook() {
|
||||
@ -78,6 +82,18 @@ const ui = {
|
||||
// Update the UI
|
||||
ui.checkPlaying.call(this);
|
||||
|
||||
// Check for picture-in-picture support
|
||||
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);
|
||||
|
||||
// Add iOS class
|
||||
utils.toggleClass(this.elements.container, this.config.classNames.isIos, browser.isIos);
|
||||
|
||||
// Add touch class
|
||||
utils.toggleClass(this.elements.container, this.config.classNames.isTouch, this.touch);
|
||||
|
||||
// Ready for API calls
|
||||
this.ready = true;
|
||||
|
||||
@ -88,6 +104,9 @@ const ui = {
|
||||
|
||||
// Set the title
|
||||
ui.setTitle.call(this);
|
||||
|
||||
// Set the poster image
|
||||
ui.setPoster.call(this);
|
||||
},
|
||||
|
||||
// Setup aria attribute for play and iframe title
|
||||
@ -121,16 +140,29 @@ const ui = {
|
||||
|
||||
// Default to media type
|
||||
const title = !utils.is.empty(this.config.title) ? this.config.title : 'video';
|
||||
const format = i18n.get('frameTitle', this.config);
|
||||
|
||||
iframe.setAttribute('title', i18n.get('frameTitle', this.config));
|
||||
iframe.setAttribute('title', format.replace('{title}', title));
|
||||
}
|
||||
},
|
||||
|
||||
// Set the poster image
|
||||
setPoster() {
|
||||
if (!utils.is.element(this.elements.poster) || utils.is.empty(this.poster)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the inline style
|
||||
const posters = this.poster.split(',');
|
||||
this.elements.poster.style.backgroundImage = posters.map(p => `url('${p}')`).join(',');
|
||||
},
|
||||
|
||||
// Check playing state
|
||||
checkPlaying() {
|
||||
// Class hooks
|
||||
utils.toggleClass(this.elements.container, this.config.classNames.playing, this.playing);
|
||||
utils.toggleClass(this.elements.container, this.config.classNames.stopped, this.paused);
|
||||
utils.toggleClass(this.elements.container, this.config.classNames.paused, this.paused);
|
||||
utils.toggleClass(this.elements.container, this.config.classNames.stopped, this.stopped);
|
||||
|
||||
// Set ARIA state
|
||||
utils.toggleState(this.elements.buttons.play, this.playing);
|
||||
|
@ -3,7 +3,6 @@
|
||||
// ==========================================================================
|
||||
|
||||
import loadjs from 'loadjs';
|
||||
|
||||
import support from './support';
|
||||
import { providers } from './types';
|
||||
|
||||
@ -269,14 +268,14 @@ const utils = {
|
||||
parent.appendChild(utils.createElement(type, attributes, text));
|
||||
},
|
||||
|
||||
// Remove an element
|
||||
// Remove element(s)
|
||||
removeElement(element) {
|
||||
if (!utils.is.element(element) || !utils.is.element(element.parentNode)) {
|
||||
if (utils.is.nodeList(element) || utils.is.array(element)) {
|
||||
Array.from(element).forEach(utils.removeElement);
|
||||
return;
|
||||
}
|
||||
|
||||
if (utils.is.nodeList(element) || utils.is.array(element)) {
|
||||
Array.from(element).forEach(utils.removeElement);
|
||||
if (!utils.is.element(element) || !utils.is.element(element.parentNode)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -435,60 +434,6 @@ const utils = {
|
||||
return this.elements.container.querySelector(selector);
|
||||
},
|
||||
|
||||
// Find the UI controls and store references in custom controls
|
||||
// TODO: Allow settings menus with custom controls
|
||||
findElements() {
|
||||
try {
|
||||
this.elements.controls = utils.getElement.call(this, this.config.selectors.controls.wrapper);
|
||||
|
||||
// Buttons
|
||||
this.elements.buttons = {
|
||||
play: utils.getElements.call(this, this.config.selectors.buttons.play),
|
||||
pause: utils.getElement.call(this, this.config.selectors.buttons.pause),
|
||||
restart: utils.getElement.call(this, this.config.selectors.buttons.restart),
|
||||
rewind: utils.getElement.call(this, this.config.selectors.buttons.rewind),
|
||||
fastForward: utils.getElement.call(this, this.config.selectors.buttons.fastForward),
|
||||
mute: utils.getElement.call(this, this.config.selectors.buttons.mute),
|
||||
pip: utils.getElement.call(this, this.config.selectors.buttons.pip),
|
||||
airplay: utils.getElement.call(this, this.config.selectors.buttons.airplay),
|
||||
settings: utils.getElement.call(this, this.config.selectors.buttons.settings),
|
||||
captions: utils.getElement.call(this, this.config.selectors.buttons.captions),
|
||||
fullscreen: utils.getElement.call(this, this.config.selectors.buttons.fullscreen),
|
||||
};
|
||||
|
||||
// Progress
|
||||
this.elements.progress = utils.getElement.call(this, this.config.selectors.progress);
|
||||
|
||||
// Inputs
|
||||
this.elements.inputs = {
|
||||
seek: utils.getElement.call(this, this.config.selectors.inputs.seek),
|
||||
volume: utils.getElement.call(this, this.config.selectors.inputs.volume),
|
||||
};
|
||||
|
||||
// Display
|
||||
this.elements.display = {
|
||||
buffer: utils.getElement.call(this, this.config.selectors.display.buffer),
|
||||
currentTime: utils.getElement.call(this, this.config.selectors.display.currentTime),
|
||||
duration: utils.getElement.call(this, this.config.selectors.display.duration),
|
||||
};
|
||||
|
||||
// Seek tooltip
|
||||
if (utils.is.element(this.elements.progress)) {
|
||||
this.elements.display.seekTooltip = this.elements.progress.querySelector(`.${this.config.classNames.tooltip}`);
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
// Log it
|
||||
this.debug.warn('It looks like there is a problem with your custom controls HTML', error);
|
||||
|
||||
// Restore native video controls
|
||||
this.toggleNativeControls(true);
|
||||
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
// Get the focused element
|
||||
getFocusElement() {
|
||||
let focused = document.activeElement;
|
||||
@ -632,6 +577,15 @@ const utils = {
|
||||
element.setAttribute('aria-pressed', state);
|
||||
},
|
||||
|
||||
// Format string
|
||||
format(input, ...args) {
|
||||
if (utils.is.empty(input)) {
|
||||
return input;
|
||||
}
|
||||
|
||||
return input.toString().replace(/{(\d+)}/g, (match, i) => utils.is.string(args[i]) ? args[i] : '');
|
||||
},
|
||||
|
||||
// Get percentage
|
||||
getPercentage(current, max) {
|
||||
if (current === 0 || max === 0 || Number.isNaN(current) || Number.isNaN(max)) {
|
||||
|
@ -29,6 +29,7 @@
|
||||
button {
|
||||
font: inherit;
|
||||
line-height: inherit;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
// Ignore focus
|
||||
|
22
src/sass/components/poster.scss
Normal file
22
src/sass/components/poster.scss
Normal file
@ -0,0 +1,22 @@
|
||||
// --------------------------------------------------------------
|
||||
// Faux poster overlay
|
||||
// --------------------------------------------------------------
|
||||
|
||||
.plyr__poster {
|
||||
background-color: #000;
|
||||
background-position: 50% 50%;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 100% 100%;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
width: 100%;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.plyr--stopped .plyr__poster {
|
||||
opacity: 1;
|
||||
}
|
@ -32,6 +32,7 @@
|
||||
@import 'components/embed';
|
||||
@import 'components/menus';
|
||||
@import 'components/progress';
|
||||
@import 'components/poster';
|
||||
@import 'components/sliders';
|
||||
@import 'components/times';
|
||||
@import 'components/tooltips';
|
||||
|
Reference in New Issue
Block a user