Started on documentation and aspect ratio option
This commit is contained in:
parent
5fe477340b
commit
0068710740
2
dist/plyr.css
vendored
2
dist/plyr.css
vendored
File diff suppressed because one or more lines are too long
2
dist/plyr.js
vendored
2
dist/plyr.js
vendored
File diff suppressed because one or more lines are too long
2
dist/plyr.js.map
vendored
2
dist/plyr.js.map
vendored
File diff suppressed because one or more lines are too long
@ -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
16
src/js/controls.js
vendored
@ -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');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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',
|
||||||
|
@ -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);
|
||||||
});
|
});
|
||||||
|
@ -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);
|
||||||
|
@ -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));
|
||||||
|
|
||||||
|
@ -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));
|
||||||
|
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
134
src/js/utils.js
134
src/js/utils.js
@ -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');
|
||||||
|
@ -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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user