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
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)
## Planned features

16
src/js/controls.js vendored
View File

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

View File

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

View File

@ -9,6 +9,9 @@ import fullscreen from './fullscreen';
import storage from './storage';
import ui from './ui';
// Sniff out the browser
const browser = utils.getBrowser();
const listeners = {
// Listen for media events
media() {
@ -134,7 +137,7 @@ const listeners = {
// Listen for control events
controls() {
// 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;
// Trigger custom and default handlers
@ -468,7 +471,7 @@ const listeners = {
);
// 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 => {
controls.updateRangeFill.call(this, event.target);
});

View File

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

View File

@ -15,6 +15,13 @@ const vimeo = {
// Add embed class for responsive
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
this.media.setAttribute('id', utils.generateId(this.type));

View File

@ -17,6 +17,10 @@ const youtube = {
// Add embed class for responsive
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
this.media.setAttribute('id', utils.generateId(this.type));

View File

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

View File

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

View File

@ -89,6 +89,73 @@ const utils = {
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
generateId(prefix) {
return `${prefix}-${Math.floor(Math.random() * 10000)}`;
@ -564,73 +631,6 @@ const utils = {
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
transitionEnd: (() => {
const element = document.createElement('span');

View File

@ -4,7 +4,11 @@
// --------------------------------------------------------------
.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;
iframe {
@ -20,8 +24,8 @@
// Vimeo hack
> div {
position: relative;
padding-bottom: 200%;
transform: translateY(-35.95%);
padding-bottom: 100%;
transform: translateY(-@offset);
}
}
// To allow mouse events to be captured if full support