Started on documentation and aspect ratio option

This commit is contained in:
Sam Potts 2017-11-06 19:38:31 +11:00
parent 5fe477340b
commit 0068710740
14 changed files with 159 additions and 108 deletions

2
dist/plyr.css vendored

File diff suppressed because one or more lines are too long

2
dist/plyr.js vendored

File diff suppressed because one or more lines are too long

2
dist/plyr.js.map vendored

File diff suppressed because one or more lines are too long

View File

@ -34,13 +34,6 @@ Oh and yes, it works with Bootstrap.
## Changelog ## Changelog
Check out the [changelog](changelog.md) to see what's new with Plyr. Check out the [changelog](changelog.md) to see what's new with Plyr.
## Features currently being developed
- Playback speed selection
- Quality selection
- Caption language selection
- AirPlay
- Picture in Picture (MacOS Sierra + Safari)
[more info](https://github.com/sampotts/plyr/issues?q=is%3Aissue+is%3Aopen+label%3A%22In+Development%22) [more info](https://github.com/sampotts/plyr/issues?q=is%3Aissue+is%3Aopen+label%3A%22In+Development%22)
## Planned features ## Planned features

16
src/js/controls.js vendored
View File

@ -6,11 +6,14 @@ import support from './support';
import utils from './utils'; import utils from './utils';
import ui from './ui'; import ui from './ui';
// Sniff out the browser
const browser = utils.getBrowser();
const controls = { const controls = {
// Webkit polyfill for lower fill range // Webkit polyfill for lower fill range
updateRangeFill(target) { updateRangeFill(target) {
// WebKit only // WebKit only
if (!this.browser.isWebkit) { if (!browser.isWebkit) {
return; return;
} }
@ -49,7 +52,7 @@ const controls = {
getIconUrl() { getIconUrl() {
return { return {
url: this.config.iconUrl, url: this.config.iconUrl,
absolute: this.config.iconUrl.indexOf('http') === 0 || (this.browser.isIE && !window.svg4everybody), absolute: this.config.iconUrl.indexOf('http') === 0 || (browser.isIE && !window.svg4everybody),
}; };
}, },
@ -1139,14 +1142,11 @@ const controls = {
inject() { inject() {
// Sprite // Sprite
if (this.config.loadSprite) { if (this.config.loadSprite) {
const iconUrl = controls.getIconUrl.call(this); const icon = controls.getIconUrl.call(this);
// Only load external sprite using AJAX // Only load external sprite using AJAX
if (iconUrl.absolute) { if (icon.absolute) {
this.log(`AJAX loading absolute SVG sprite ${this.browser.isIE ? '(due to IE)' : ''}`); utils.loadSprite(icon.url, 'sprite-plyr');
utils.loadSprite(iconUrl.url, 'sprite-plyr');
} else {
this.log('Sprite will be used as external resource directly');
} }
} }

View File

@ -45,6 +45,9 @@ const defaults = {
// Pass a custom duration // Pass a custom duration
duration: null, duration: null,
// Aspect ratio (for embeds)
ratio: '16:9',
// Quality default // Quality default
quality: { quality: {
default: 'default', default: 'default',

View File

@ -9,6 +9,9 @@ import fullscreen from './fullscreen';
import storage from './storage'; import storage from './storage';
import ui from './ui'; import ui from './ui';
// Sniff out the browser
const browser = utils.getBrowser();
const listeners = { const listeners = {
// Listen for media events // Listen for media events
media() { media() {
@ -134,7 +137,7 @@ const listeners = {
// Listen for control events // Listen for control events
controls() { controls() {
// IE doesn't support input event, so we fallback to change // IE doesn't support input event, so we fallback to change
const inputEvent = this.browser.isIE ? 'change' : 'input'; const inputEvent = browser.isIE ? 'change' : 'input';
let last = null; let last = null;
// Trigger custom and default handlers // Trigger custom and default handlers
@ -468,7 +471,7 @@ const listeners = {
); );
// Polyfill for lower fill in <input type="range"> for webkit // Polyfill for lower fill in <input type="range"> for webkit
if (this.browser.isWebkit) { if (browser.isWebkit) {
utils.on(utils.getElements.call(this, 'input[type="range"]'), 'input', event => { utils.on(utils.getElements.call(this, 'input[type="range"]'), 'input', event => {
controls.updateRangeFill.call(this, event.target); controls.updateRangeFill.call(this, event.target);
}); });

View File

@ -8,6 +8,9 @@ import youtube from './plugins/youtube';
import vimeo from './plugins/vimeo'; import vimeo from './plugins/vimeo';
import ui from './ui'; import ui from './ui';
// Sniff out the browser
const browser = utils.getBrowser();
const media = { const media = {
// Setup media // Setup media
setup() { setup() {
@ -45,7 +48,7 @@ const media = {
utils.toggleClass(this.elements.container, this.config.classNames.stopped, this.config.autoplay); utils.toggleClass(this.elements.container, this.config.classNames.stopped, this.config.autoplay);
// Add iOS class // Add iOS class
utils.toggleClass(this.elements.container, this.config.classNames.isIos, this.browser.isIos); utils.toggleClass(this.elements.container, this.config.classNames.isIos, browser.isIos);
// Add touch class // Add touch class
utils.toggleClass(this.elements.container, this.config.classNames.isTouch, support.touch); utils.toggleClass(this.elements.container, this.config.classNames.isTouch, support.touch);

View File

@ -15,6 +15,13 @@ const vimeo = {
// Add embed class for responsive // Add embed class for responsive
utils.toggleClass(this.elements.wrapper, this.config.classNames.embed, true); utils.toggleClass(this.elements.wrapper, this.config.classNames.embed, true);
// Set aspect ratio
const ratio = this.config.ratio.split(':');
const padding = 100 / ratio[0] * ratio[1];
const offset = (100 - padding) / 2;
this.elements.wrapper.style.paddingBottom = `${padding}%`;
this.media.style.transform = `translateY(-${offset}%)`;
// Set ID // Set ID
this.media.setAttribute('id', utils.generateId(this.type)); this.media.setAttribute('id', utils.generateId(this.type));

View File

@ -17,6 +17,10 @@ const youtube = {
// Add embed class for responsive // Add embed class for responsive
utils.toggleClass(this.elements.wrapper, this.config.classNames.embed, true); utils.toggleClass(this.elements.wrapper, this.config.classNames.embed, true);
// Set aspect ratio
const ratio = this.config.ratio.split(':');
this.elements.wrapper.style.paddingBottom = `${100 / ratio[0] * ratio[1]}%`;
// Set ID // Set ID
this.media.setAttribute('id', utils.generateId(this.type)); this.media.setAttribute('id', utils.generateId(this.type));

View File

@ -192,10 +192,7 @@ class Plyr {
return; return;
} }
// Sniff out the browser // Setup local storage for user settings
this.browser = utils.getBrowser();
// Load saved settings from localStorage
storage.setup.call(this); storage.setup.call(this);
// Check for support again but with type // Check for support again but with type
@ -237,17 +234,27 @@ class Plyr {
} }
} }
// ---------------------------------------
// API // API
// --------------------------------------- // ---------------------------------------
/**
* If the player is HTML5
*/
get isHTML5() { get isHTML5() {
return types.html5.includes(this.type); return types.html5.includes(this.type);
} }
/**
* If the player is an embed - e.g. YouTube or Vimeo
*/
get isEmbed() { get isEmbed() {
return types.embed.includes(this.type); return types.embed.includes(this.type);
} }
// Play /**
* Play the media
*/
play() { play() {
if ('play' in this.media) { if ('play' in this.media) {
this.media.play(); this.media.play();
@ -257,7 +264,9 @@ class Plyr {
return this; return this;
} }
// Pause /**
* Pause the media
*/
pause() { pause() {
if ('pause' in this.media) { if ('pause' in this.media) {
this.media.pause(); this.media.pause();
@ -267,7 +276,10 @@ class Plyr {
return this; return this;
} }
// Toggle playback /**
* Toggle playback based on current status
* @param {boolean} toggle
*/
togglePlay(toggle) { togglePlay(toggle) {
// True toggle if nothing passed // True toggle if nothing passed
if ((!utils.is.boolean(toggle) && this.media.paused) || toggle) { if ((!utils.is.boolean(toggle) && this.media.paused) || toggle) {
@ -277,31 +289,43 @@ class Plyr {
return this.pause(); return this.pause();
} }
// Stop /**
* Stop playback
*/
stop() { stop() {
return this.restart().pause(); return this.restart().pause();
} }
// Restart /**
* Restart playback
*/
restart() { restart() {
this.currentTime = 0; this.currentTime = 0;
return this; return this;
} }
// Rewind /**
* Rewind
* @param {number} seekTime - how far to rewind in seconds. Defaults to the config.seekTime
*/
rewind(seekTime) { rewind(seekTime) {
this.currentTime = this.currentTime - (utils.is.number(seekTime) ? seekTime : this.config.seekTime); this.currentTime = this.currentTime - (utils.is.number(seekTime) ? seekTime : this.config.seekTime);
return this; return this;
} }
// Fast forward /**
* Fast forward
* @param {number} seekTime - how far to fast forward in seconds. Defaults to the config.seekTime
*/
forward(seekTime) { forward(seekTime) {
this.currentTime = this.currentTime + (utils.is.number(seekTime) ? seekTime : this.config.seekTime); this.currentTime = this.currentTime + (utils.is.number(seekTime) ? seekTime : this.config.seekTime);
return this; return this;
} }
// Seek to time /**
// The input parameter can be an event or a number * Seek to a time
* @param {number} input - where to seek to in seconds. Defaults to 0 (the start)
*/
set currentTime(input) { set currentTime(input) {
let targetTime = 0; let targetTime = 0;
@ -327,7 +351,9 @@ class Plyr {
return Number(this.media.currentTime); return Number(this.media.currentTime);
} }
// Duration /**
* Get the duration of the current media
*/
get duration() { get duration() {
// Faux duration set via config // Faux duration set via config
const fauxDuration = parseInt(this.config.duration, 10); const fauxDuration = parseInt(this.config.duration, 10);
@ -339,7 +365,10 @@ class Plyr {
return !Number.isNaN(fauxDuration) ? fauxDuration : realDuration; return !Number.isNaN(fauxDuration) ? fauxDuration : realDuration;
} }
// Volume /**
* Set the player volume
* @param {number} value - must be between 0 and 1. Defaults to the value from local storage and config.volume if not set in storage
*/
set volume(value) { set volume(value) {
let volume = value; let volume = value;
const max = 1; const max = 1;
@ -377,6 +406,9 @@ class Plyr {
} }
} }
/**
* Get the current player volume
*/
get volume() { get volume() {
return this.media.volume; return this.media.volume;
} }

View File

@ -84,15 +84,17 @@ const ui = {
// Update the UI // Update the UI
ui.checkPlaying.call(this); ui.checkPlaying.call(this);
// Ready for API calls
this.ready = true; this.ready = true;
// Ready event at end of execution stack // Ready event at end of execution stack
utils.dispatchEvent.call(this, this.media, 'ready'); utils.dispatchEvent.call(this, this.media, 'ready');
// Autoplay // Autoplay
if (this.config.autoplay) { // TODO: check we still need this?
/* if (this.isEmbed && this.config.autoplay) {
this.play(); this.play();
} } */
}, },
// Show the duration on metadataloaded // Show the duration on metadataloaded

View File

@ -89,6 +89,73 @@ const utils = {
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag); firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
}, },
// Load an external SVG sprite
loadSprite(url, id) {
if (typeof url !== 'string') {
return;
}
const prefix = 'cache-';
const hasId = typeof id === 'string';
let isCached = false;
function updateSprite(data) {
// Inject content
this.innerHTML = data;
// Inject the SVG to the body
document.body.insertBefore(this, document.body.childNodes[0]);
}
// Only load once
if (!hasId || !document.querySelectorAll(`#${id}`).length) {
// Create container
const container = document.createElement('div');
container.setAttribute('hidden', '');
if (hasId) {
container.setAttribute('id', id);
}
// Check in cache
if (support.storage) {
const cached = window.localStorage.getItem(prefix + id);
isCached = cached !== null;
if (isCached) {
const data = JSON.parse(cached);
updateSprite.call(container, data.content);
}
}
// ReSharper disable once InconsistentNaming
const xhr = new XMLHttpRequest();
// XHR for Chrome/Firefox/Opera/Safari
if ('withCredentials' in xhr) {
xhr.open('GET', url, true);
} else {
return;
}
// Once loaded, inject to container and body
xhr.onload = () => {
if (support.storage) {
window.localStorage.setItem(
prefix + id,
JSON.stringify({
content: xhr.responseText,
})
);
}
updateSprite.call(container, xhr.responseText);
};
xhr.send();
}
},
// Generate a random ID // Generate a random ID
generateId(prefix) { generateId(prefix) {
return `${prefix}-${Math.floor(Math.random() * 10000)}`; return `${prefix}-${Math.floor(Math.random() * 10000)}`;
@ -564,73 +631,6 @@ const utils = {
return fragment.firstChild.innerText; return fragment.firstChild.innerText;
}, },
// Load an SVG sprite
loadSprite(url, id) {
if (typeof url !== 'string') {
return;
}
const prefix = 'cache-';
const hasId = typeof id === 'string';
let isCached = false;
function updateSprite(data) {
// Inject content
this.innerHTML = data;
// Inject the SVG to the body
document.body.insertBefore(this, document.body.childNodes[0]);
}
// Only load once
if (!hasId || !document.querySelectorAll(`#${id}`).length) {
// Create container
const container = document.createElement('div');
container.setAttribute('hidden', '');
if (hasId) {
container.setAttribute('id', id);
}
// Check in cache
if (support.storage) {
const cached = window.localStorage.getItem(prefix + id);
isCached = cached !== null;
if (isCached) {
const data = JSON.parse(cached);
updateSprite.call(container, data.content);
}
}
// ReSharper disable once InconsistentNaming
const xhr = new XMLHttpRequest();
// XHR for Chrome/Firefox/Opera/Safari
if ('withCredentials' in xhr) {
xhr.open('GET', url, true);
} else {
return;
}
// Once loaded, inject to container and body
xhr.onload = () => {
if (support.storage) {
window.localStorage.setItem(
prefix + id,
JSON.stringify({
content: xhr.responseText,
})
);
}
updateSprite.call(container, xhr.responseText);
};
xhr.send();
}
},
// Get the transition end event // Get the transition end event
transitionEnd: (() => { transitionEnd: (() => {
const element = document.createElement('span'); const element = document.createElement('span');

View File

@ -4,7 +4,11 @@
// -------------------------------------------------------------- // --------------------------------------------------------------
.plyr__video-embed { .plyr__video-embed {
padding-bottom: 56.25%; /* 16:9 */ // Default to 16:9 ratio but this is set by JavaScript based on config
@padding: ((100 / 16) * 9);
@offset: unit((100 - @padding) / 2, ~'%');
padding-bottom: unit(@padding, ~'%');
height: 0; height: 0;
iframe { iframe {
@ -20,8 +24,8 @@
// Vimeo hack // Vimeo hack
> div { > div {
position: relative; position: relative;
padding-bottom: 200%; padding-bottom: 100%;
transform: translateY(-35.95%); transform: translateY(-@offset);
} }
} }
// To allow mouse events to be captured if full support // To allow mouse events to be captured if full support