Merge branch 'develop' of github.com:Selz/plyr into develop

# Conflicts:
#	readme.md
This commit is contained in:
Sam Potts 2017-11-07 23:21:35 +11:00
commit 3f41a0cf54
16 changed files with 439 additions and 337 deletions

2
demo/dist/demo.js vendored
View File

@ -1,3 +1,3 @@
document.addEventListener("DOMContentLoaded",function(){function e(e,t,o){e&&e.classList[o?"add":"remove"](t)}function t(t,a){if(t in n&&(a||t!==r)&&(r.length||t!==n.video)){switch(t){case n.video:o.src={type:"video",title:"View From A Blue Moon",sources:[{src:"https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.mp4",type:"video/mp4"}],poster:"https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.jpg",tracks:[{kind:"captions",label:"English",srclang:"en",src:"https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.en.vtt",default:!0},{kind:"captions",label:"French",srclang:"fr",src:"https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.fr.vtt"}]};break;case n.audio:o.src={type:"audio",title:"Kishi Bashi – “It All Began With A Burst”",sources:[{src:"https://cdn.plyr.io/static/demo/Kishi_Bashi_-_It_All_Began_With_a_Burst.mp3",type:"audio/mp3"},{src:"https://cdn.plyr.io/static/demo/Kishi_Bashi_-_It_All_Began_With_a_Burst.ogg",type:"audio/ogg"}]};break;case n.youtube:o.src={type:"video",title:"View From A Blue Moon",sources:[{src:"https://www.youtube.com/watch?v=bTqVqk7FSmY",type:"youtube"}]};break;case n.vimeo:o.src={type:"video",title:"View From A Blue Moon",sources:[{src:"https://vimeo.com/76979871",type:"vimeo"}]}}r=t,Array.from(i).forEach(function(t){return e(t.parentElement,"active",!1)}),e(document.querySelector('[data-source="'+t+'"]'),"active",!0),Array.from(document.querySelectorAll(".plyr__cite")).forEach(function(e){e.setAttribute("hidden","")}),document.querySelector(".plyr__cite--"+t).removeAttribute("hidden")}}window.shr&&window.shr.setup({count:{classname:"button__count"}});document.addEventListener("focusout",function(e){e.target.classList.remove("tab-focus")}),document.addEventListener("keydown",function(e){9===e.keyCode&&window.setTimeout(function(){document.activeElement.classList.add("tab-focus")},0)});var o=new window.Plyr("#player",{debug:!0,title:"View From A Blue Moon",iconUrl:"../dist/plyr.svg",tooltips:{controls:!0},captions:{active:!0},controls:["play-large","play","progress","current-time","mute","volume","captions","settings","fullscreen","pip","airplay"]});window.player=o;var i=document.querySelectorAll("[data-source]"),n={video:"video",audio:"audio",youtube:"youtube",vimeo:"vimeo"},r=window.location.hash.replace("#",""),a=window.history&&window.history.pushState;if(Array.from(i).forEach(function(e){e.addEventListener("click",function(){var o=e.getAttribute("data-source");t(o),a&&window.history.pushState({type:o},"","#"+o)})}),window.addEventListener("popstate",function(e){e.state&&"type"in e.state&&t(e.state.type)}),a){var s=!r.length;s&&(r=n.video),r in n&&window.history.replaceState({type:r},"",s?"":"#"+r),r!==n.video&&t(r,!0)}}),"plyr.io"===window.location.host&&(!function(e,t,o,i,n,r,a){e.GoogleAnalyticsObject=n,e.ga=e.ga||function(){(e.ga.q=e.ga.q||[]).push(arguments)},e.ga.l=1*new Date,r=t.createElement(o),a=t.getElementsByTagName(o)[0],r.async=1,r.src="//www.google-analytics.com/analytics.js",a.parentNode.insertBefore(r,a)}(window,document,"script",0,"ga"),window.ga("create","UA-40881672-11","auto"),window.ga("send","pageview")); document.addEventListener("DOMContentLoaded",function(){function e(e,t,o){e&&e.classList[o?"add":"remove"](t)}function t(t,a){if(t in n&&(a||t!==r)&&(r.length||t!==n.video)){switch(t){case n.video:o.src={type:"video",title:"View From A Blue Moon",sources:[{src:"https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.mp4",type:"video/mp4"}],poster:"https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.jpg",tracks:[{kind:"captions",label:"English",srclang:"en",src:"https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.en.vtt",default:!0},{kind:"captions",label:"French",srclang:"fr",src:"https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.fr.vtt"}]};break;case n.audio:o.src={type:"audio",title:"Kishi Bashi – “It All Began With A Burst”",sources:[{src:"https://cdn.plyr.io/static/demo/Kishi_Bashi_-_It_All_Began_With_a_Burst.mp3",type:"audio/mp3"},{src:"https://cdn.plyr.io/static/demo/Kishi_Bashi_-_It_All_Began_With_a_Burst.ogg",type:"audio/ogg"}]};break;case n.youtube:o.src={type:"video",title:"View From A Blue Moon",sources:[{src:"https://www.youtube.com/watch?v=bTqVqk7FSmY",type:"youtube"}]};break;case n.vimeo:o.src={type:"video",title:"View From A Blue Moon",sources:[{src:"https://vimeo.com/76979871",type:"vimeo"}]}}r=t,Array.from(i).forEach(function(t){return e(t.parentElement,"active",!1)}),e(document.querySelector('[data-source="'+t+'"]'),"active",!0),Array.from(document.querySelectorAll(".plyr__cite")).forEach(function(e){e.setAttribute("hidden","")}),document.querySelector(".plyr__cite--"+t).removeAttribute("hidden")}}window.shr&&window.shr.setup({count:{classname:"button__count"}});document.addEventListener("focusout",function(e){e.target.classList.remove("tab-focus")}),document.addEventListener("keydown",function(e){9===e.keyCode&&window.setTimeout(function(){document.activeElement.classList.add("tab-focus")},0)});var o=new window.Plyr("#player",{debug:!0,title:"View From A Blue Moon",iconUrl:"../dist/plyr.svg",keyboard:{global:!0},tooltips:{controls:!0},captions:{active:!0},controls:["play-large","play","progress","current-time","mute","volume","captions","settings","fullscreen","pip","airplay"]});window.player=o;var i=document.querySelectorAll("[data-source]"),n={video:"video",audio:"audio",youtube:"youtube",vimeo:"vimeo"},r=window.location.hash.replace("#",""),a=window.history&&window.history.pushState;if(Array.from(i).forEach(function(e){e.addEventListener("click",function(){var o=e.getAttribute("data-source");t(o),a&&window.history.pushState({type:o},"","#"+o)})}),window.addEventListener("popstate",function(e){e.state&&"type"in e.state&&t(e.state.type)}),a){var s=!r.length;s&&(r=n.video),r in n&&window.history.replaceState({type:r},"",s?"":"#"+r),r!==n.video&&t(r,!0)}}),"plyr.io"===window.location.host&&(!function(e,t,o,i,n,r,a){e.GoogleAnalyticsObject=n,e.ga=e.ga||function(){(e.ga.q=e.ga.q||[]).push(arguments)},e.ga.l=1*new Date,r=t.createElement(o),a=t.getElementsByTagName(o)[0],r.async=1,r.src="//www.google-analytics.com/analytics.js",a.parentNode.insertBefore(r,a)}(window,document,"script",0,"ga"),window.ga("create","UA-40881672-11","auto"),window.ga("send","pageview"));
//# sourceMappingURL=demo.js.map //# sourceMappingURL=demo.js.map

File diff suppressed because one or more lines are too long

View File

@ -43,6 +43,9 @@ document.addEventListener('DOMContentLoaded', () => {
debug: true, debug: true,
title: 'View From A Blue Moon', title: 'View From A Blue Moon',
iconUrl: '../dist/plyr.svg', iconUrl: '../dist/plyr.svg',
keyboard: {
global: true,
},
tooltips: { tooltips: {
controls: true, controls: true,
}, },

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

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

@ -19,9 +19,18 @@ const defaults = {
volume: 1, volume: 1,
muted: false, muted: false,
// Pass a custom duration
duration: null,
// Display the media duration // Display the media duration
displayDuration: true, displayDuration: true,
// Aspect ratio (for embeds)
ratio: '16:9',
// Looping
loop: false,
// Click video to play // Click video to play
clickToPlay: true, clickToPlay: true,
@ -42,22 +51,12 @@ const defaults = {
// Blank video (used to prevent errors on source change) // Blank video (used to prevent errors on source change)
blankVideo: 'https://cdn.plyr.io/static/blank.mp4', blankVideo: 'https://cdn.plyr.io/static/blank.mp4',
// Pass a custom duration
duration: null,
// Quality default // Quality default
quality: { quality: {
default: 'default', default: 'default',
options: ['hd2160', 'hd1440', 'hd1080', 'hd720', 'large', 'medium', 'small', 'tiny', 'default'], options: ['hd2160', 'hd1440', 'hd1080', 'hd720', 'large', 'medium', 'small', 'tiny', 'default'],
}, },
// Set loops
loop: {
active: false,
start: null,
end: null,
},
// Speed default and options to display // Speed default and options to display
speed: { speed: {
default: 1, default: 1,

View File

@ -9,7 +9,217 @@ 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 = {
// Global listeners
global() {
let last = null;
// Get the key code for an event
const getKeyCode = event => (event.keyCode ? event.keyCode : event.which);
// Handle key press
const handleKey = event => {
const code = getKeyCode(event);
const pressed = event.type === 'keydown';
const held = pressed && code === last;
// If the event is bubbled from the media element
// Firefox doesn't get the keycode for whatever reason
if (!utils.is.number(code)) {
return;
}
// Seek by the number keys
const seekByKey = () => {
// Divide the max duration into 10th's and times by the number value
this.currentTime = this.duration / 10 * (code - 48);
};
// Handle the key on keydown
// Reset on keyup
if (pressed) {
// Which keycodes should we prevent default
const preventDefault = [
48,
49,
50,
51,
52,
53,
54,
56,
57,
32,
75,
38,
40,
77,
39,
37,
70,
67,
73,
76,
79,
];
// Check focused element
// and if the focused element is not editable (e.g. text input)
// and any that accept key input http://webaim.org/techniques/keyboard/
const focused = utils.getFocusElement();
if (utils.is.htmlElement(focused) && utils.matches(focused, this.config.selectors.editable)) {
return;
}
// If the code is found prevent default (e.g. prevent scrolling for arrows)
if (preventDefault.includes(code)) {
event.preventDefault();
event.stopPropagation();
}
switch (code) {
case 48:
case 49:
case 50:
case 51:
case 52:
case 53:
case 54:
case 55:
case 56:
case 57:
// 0-9
if (!held) {
seekByKey();
}
break;
case 32:
case 75:
// Space and K key
if (!held) {
this.warn('togglePlay', event.type);
this.togglePlay();
}
break;
case 38:
// Arrow up
this.increaseVolume(0.1);
break;
case 40:
// Arrow down
this.decreaseVolume(0.1);
break;
case 77:
// M key
if (!held) {
this.muted = 'toggle';
}
break;
case 39:
// Arrow forward
this.forward();
break;
case 37:
// Arrow back
this.rewind();
break;
case 70:
// F key
this.toggleFullscreen();
break;
case 67:
// C key
if (!held) {
this.toggleCaptions();
}
break;
case 73:
this.setLoop('start');
break;
case 76:
this.setLoop();
break;
case 79:
this.setLoop('end');
break;
default:
break;
}
// Escape is handle natively when in full screen
// So we only need to worry about non native
if (!fullscreen.enabled && this.fullscreen.active && code === 27) {
this.toggleFullscreen();
}
// Store last code for next cycle
last = code;
} else {
last = null;
}
};
// Keyboard shortcuts
if (this.config.keyboard.global) {
utils.on(window, 'keydown keyup', handleKey, false);
} else if (this.config.keyboard.focused) {
utils.on(this.elements.container, 'keydown keyup', handleKey, false);
}
// Detect tab focus
// Remove class on blur/focusout
utils.on(this.elements.container, 'focusout', event => {
utils.toggleClass(event.target, this.config.classNames.tabFocus, false);
});
// Add classname to tabbed elements
utils.on(this.elements.container, 'keydown', event => {
if (event.keyCode !== 9) {
return;
}
// Delay the adding of classname until the focus has changed
// This event fires before the focusin event
window.setTimeout(() => {
utils.toggleClass(utils.getFocusElement(), this.config.classNames.tabFocus, true);
}, 0);
});
// Toggle controls visibility based on mouse movement
if (this.config.hideControls) {
// Toggle controls on mouse events and entering fullscreen
utils.on(
this.elements.container,
'mouseenter mouseleave mousemove touchstart touchend touchcancel touchmove enterfullscreen',
event => {
this.toggleControls(event);
}
);
}
// Handle user exiting fullscreen by escaping etc
if (fullscreen.enabled) {
utils.on(document, fullscreen.eventType, event => {
this.toggleFullscreen(event);
});
}
},
// Listen for media events // Listen for media events
media() { media() {
// Time change on media // Time change on media
@ -134,8 +344,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;
// Trigger custom and default handlers // Trigger custom and default handlers
const proxy = (event, handlerKey, defaultHandler) => { const proxy = (event, handlerKey, defaultHandler) => {
@ -165,188 +374,6 @@ const listeners = {
} }
}; };
// Get the key code for an event
const getKeyCode = event => (event.keyCode ? event.keyCode : event.which);
// Handle key press
const handleKey = event => {
const code = getKeyCode(event);
const pressed = event.type === 'keydown';
const held = pressed && code === last;
// If the event is bubbled from the media element
// Firefox doesn't get the keycode for whatever reason
if (!utils.is.number(code)) {
return;
}
// Seek by the number keys
const seekByKey = () => {
// Divide the max duration into 10th's and times by the number value
this.currentTime = this.duration / 10 * (code - 48);
};
// Handle the key on keydown
// Reset on keyup
if (pressed) {
// Which keycodes should we prevent default
const preventDefault = [
48,
49,
50,
51,
52,
53,
54,
56,
57,
32,
75,
38,
40,
77,
39,
37,
70,
67,
73,
76,
79,
];
// Check focused element
// and if the focused element is not editable (e.g. text input)
// and any that accept key input http://webaim.org/techniques/keyboard/
const focused = utils.getFocusElement();
if (utils.is.htmlElement(focused) && utils.matches(focused, this.config.selectors.editable)) {
return;
}
// If the code is found prevent default (e.g. prevent scrolling for arrows)
if (preventDefault.includes(code)) {
event.preventDefault();
event.stopPropagation();
}
switch (code) {
case 48:
case 49:
case 50:
case 51:
case 52:
case 53:
case 54:
case 55:
case 56:
case 57:
// 0-9
if (!held) {
seekByKey();
}
break;
case 32:
case 75:
// Space and K key
if (!held) {
this.togglePlay();
}
break;
case 38:
// Arrow up
this.increaseVolume(0.1);
break;
case 40:
// Arrow down
this.decreaseVolume(0.1);
break;
case 77:
// M key
if (!held) {
this.muted = 'toggle';
}
break;
case 39:
// Arrow forward
this.forward();
break;
case 37:
// Arrow back
this.rewind();
break;
case 70:
// F key
this.toggleFullscreen();
break;
case 67:
// C key
if (!held) {
this.toggleCaptions();
}
break;
case 73:
this.setLoop('start');
break;
case 76:
this.setLoop();
break;
case 79:
this.setLoop('end');
break;
default:
break;
}
// Escape is handle natively when in full screen
// So we only need to worry about non native
if (!fullscreen.enabled && this.fullscreen.active && code === 27) {
this.toggleFullscreen();
}
// Store last code for next cycle
last = code;
} else {
last = null;
}
};
// Keyboard shortcuts
if (this.config.keyboard.focused) {
utils.on(this.elements.container, 'keydown keyup', handleKey, false);
} else if (this.config.keyboard.global) {
utils.on(window, 'keydown keyup', handleKey, false);
}
// Detect tab focus
// Remove class on blur/focusout
utils.on(this.elements.container, 'focusout', event => {
utils.toggleClass(event.target, this.config.classNames.tabFocus, false);
});
// Add classname to tabbed elements
utils.on(this.elements.container, 'keydown', event => {
if (event.keyCode !== 9) {
return;
}
// Delay the adding of classname until the focus has changed
// This event fires before the focusin event
window.setTimeout(() => {
utils.toggleClass(utils.getFocusElement(), this.config.classNames.tabFocus, true);
}, 0);
});
// Play // Play
utils.on(this.elements.buttons.play, 'click', event => proxy(event, 'play', togglePlay)); utils.on(this.elements.buttons.play, 'click', event => proxy(event, 'play', togglePlay));
@ -468,7 +495,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);
}); });
@ -481,15 +508,6 @@ const listeners = {
// Toggle controls visibility based on mouse movement // Toggle controls visibility based on mouse movement
if (this.config.hideControls) { if (this.config.hideControls) {
// Toggle controls on mouse events and entering fullscreen
utils.on(
this.elements.container,
'mouseenter mouseleave mousemove touchstart touchend touchcancel touchmove enterfullscreen',
event => {
this.toggleControls(event);
}
);
// Watch for cursor over controls so they don't hide when trying to interact // Watch for cursor over controls so they don't hide when trying to interact
utils.on(this.elements.controls, 'mouseenter mouseleave', event => { utils.on(this.elements.controls, 'mouseenter mouseleave', event => {
this.elements.controls.hover = event.type === 'mouseenter'; this.elements.controls.hover = event.type === 'mouseenter';
@ -553,13 +571,6 @@ const listeners = {
}), }),
false false
); );
// Handle user exiting fullscreen by escaping etc
if (fullscreen.enabled) {
utils.on(document, fullscreen.eventType, event => {
this.toggleFullscreen(event);
});
}
}, },
}; };

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

@ -4,6 +4,7 @@
import utils from './../utils'; import utils from './../utils';
import captions from './../captions'; import captions from './../captions';
import controls from './../controls';
import ui from './../ui'; import ui from './../ui';
const vimeo = { const vimeo = {
@ -15,6 +16,9 @@ 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 intial ratio
vimeo.setAspectRatio.call(this);
// Set ID // Set ID
this.media.setAttribute('id', utils.generateId(this.type)); this.media.setAttribute('id', utils.generateId(this.type));
@ -33,21 +37,31 @@ const vimeo = {
} }
}, },
// Set aspect ratio
setAspectRatio(input) {
const ratio = utils.is.string(input) ? input.split(':') : this.config.ratio.split(':');
const padding = 100 / ratio[0] * ratio[1];
const offset = (300 - padding) / 6;
this.elements.wrapper.style.paddingBottom = `${padding}%`;
this.media.style.transform = `translateY(-${offset}%)`;
},
// API Ready // API Ready
ready() { ready() {
const player = this; const player = this;
// Get Vimeo params for the iframe // Get Vimeo params for the iframe
const options = { const options = {
loop: this.config.loop.active, loop: player.config.loop.active,
autoplay: this.config.autoplay, autoplay: player.config.autoplay,
byline: false, byline: false,
portrait: false, portrait: false,
title: false, title: false,
speed: true,
transparent: 0, transparent: 0,
}; };
const params = utils.buildUrlParameters(options); const params = utils.buildUrlParameters(options);
const id = utils.parseVimeoId(this.embedId); const id = utils.parseVimeoId(player.embedId);
// Build an iframe // Build an iframe
const iframe = utils.createElement('iframe'); const iframe = utils.createElement('iframe');
@ -57,7 +71,7 @@ const vimeo = {
player.media.appendChild(iframe); player.media.appendChild(iframe);
// Setup instance // Setup instance
// https://github.com/vimeo/this.js // https://github.com/vimeo/player.js
player.embed = new window.Vimeo.Player(iframe); player.embed = new window.Vimeo.Player(iframe);
// Create a faux HTML5 API using the Vimeo API // Create a faux HTML5 API using the Vimeo API
@ -99,18 +113,22 @@ const vimeo = {
// Restore pause state // Restore pause state
if (paused) { if (paused) {
this.pause(); player.pause();
} }
}, },
}); });
// Playback speed // Playback speed
// Not currently supported in Vimeo let { playbackRate } = player.media;
Object.defineProperty(player.media, 'playbackRate', { Object.defineProperty(player.media, 'playbackRate', {
get() { get() {
return null; return playbackRate;
},
set(input) {
playbackRate = input;
player.embed.setPlaybackRate(input);
utils.dispatchEvent.call(player, player.media, 'ratechange');
}, },
set() {},
}); });
// Volume // Volume
@ -148,6 +166,17 @@ const vimeo = {
}, },
}); });
// Set aspect ratio based on video size
Promise.all([player.embed.getVideoWidth(), player.embed.getVideoHeight()]).then(dimensions => {
const ratio = utils.getAspectRatio(dimensions[0], dimensions[1]);
vimeo.setAspectRatio.call(this, ratio);
});
// Get available speeds
if (player.config.controls.includes('settings') && player.config.settings.includes('speed')) {
controls.setSpeedMenu.call(player);
}
// Get title // Get title
player.embed.getVideoTitle().then(title => { player.embed.getVideoTitle().then(title => {
player.config.title = title; player.config.title = title;
@ -156,7 +185,7 @@ const vimeo = {
// Get current time // Get current time
player.embed.getCurrentTime().then(value => { player.embed.getCurrentTime().then(value => {
currentTime = value; currentTime = value;
utils.dispatchEvent.call(this, this.media, 'timeupdate'); utils.dispatchEvent.call(player, player.media, 'timeupdate');
}); });
// Get duration // Get duration
@ -202,31 +231,31 @@ const vimeo = {
utils.dispatchEvent.call(player, player.media, 'pause'); utils.dispatchEvent.call(player, player.media, 'pause');
}); });
this.embed.on('timeupdate', data => { player.embed.on('timeupdate', data => {
this.media.seeking = false; player.media.seeking = false;
currentTime = data.seconds; currentTime = data.seconds;
utils.dispatchEvent.call(this, this.media, 'timeupdate'); utils.dispatchEvent.call(player, player.media, 'timeupdate');
}); });
this.embed.on('progress', data => { player.embed.on('progress', data => {
this.media.buffered = data.percent; player.media.buffered = data.percent;
utils.dispatchEvent.call(this, this.media, 'progress'); utils.dispatchEvent.call(player, player.media, 'progress');
if (parseInt(data.percent, 10) === 1) { if (parseInt(data.percent, 10) === 1) {
// Trigger event // Trigger event
utils.dispatchEvent.call(this, this.media, 'canplaythrough'); utils.dispatchEvent.call(player, player.media, 'canplaythrough');
} }
}); });
this.embed.on('seeked', () => { player.embed.on('seeked', () => {
this.media.seeking = false; player.media.seeking = false;
utils.dispatchEvent.call(this, this.media, 'seeked'); utils.dispatchEvent.call(player, player.media, 'seeked');
utils.dispatchEvent.call(this, this.media, 'play'); utils.dispatchEvent.call(player, player.media, 'play');
}); });
this.embed.on('ended', () => { player.embed.on('ended', () => {
this.media.paused = true; player.media.paused = true;
utils.dispatchEvent.call(this, this.media, 'ended'); utils.dispatchEvent.call(player, player.media, 'ended');
}); });
// Rebuild UI // Rebuild UI

View File

@ -17,6 +17,9 @@ 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
youtube.setAspectRatio.call(this);
// Set ID // Set ID
this.media.setAttribute('id', utils.generateId(this.type)); this.media.setAttribute('id', utils.generateId(this.type));
@ -44,6 +47,12 @@ const youtube = {
} }
}, },
// Set aspect ratio
setAspectRatio() {
const ratio = this.config.ratio.split(':');
this.elements.wrapper.style.paddingBottom = `${100 / ratio[0] * ratio[1]}%`;
},
// API ready // API ready
ready(videoId) { ready(videoId) {
const player = this; const player = this;
@ -66,9 +75,9 @@ const youtube = {
origin: window && window.location.hostname, origin: window && window.location.hostname,
widget_referrer: window && window.location.href, widget_referrer: window && window.location.href,
// Captions is flaky on YouTube // Captions are flaky on YouTube
// cc_load_policy: (this.captions.active ? 1 : 0), cc_load_policy: (this.captions.active ? 1 : 0),
// cc_lang_pref: 'en', cc_lang_pref: this.config.captions.language,
}, },
events: { events: {
onError(event) { onError(event) {

View File

@ -13,6 +13,7 @@ import utils from './utils';
import captions from './captions'; import captions from './captions';
import controls from './controls'; import controls from './controls';
import fullscreen from './fullscreen'; import fullscreen from './fullscreen';
import listeners from './listeners';
import media from './media'; import media from './media';
import storage from './storage'; import storage from './storage';
import source from './source'; import source from './source';
@ -78,7 +79,6 @@ class Plyr {
}; };
// Captions // Captions
// TODO: captions.enabled should be in config?
this.captions = { this.captions = {
enabled: null, enabled: null,
tracks: null, tracks: null,
@ -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
@ -217,6 +214,9 @@ class Plyr {
// Allow focus to be captured // Allow focus to be captured
this.elements.container.setAttribute('tabindex', 0); this.elements.container.setAttribute('tabindex', 0);
// Global listeners
listeners.global.call(this);
// Add style hook // Add style hook
ui.addStyleHook.call(this); ui.addStyleHook.call(this);
@ -237,17 +237,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 +267,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 +279,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 +292,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 +354,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 +368,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 +409,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,71 +631,11 @@ const utils = {
return fragment.firstChild.innerText; return fragment.firstChild.innerText;
}, },
// Load an SVG sprite // Get aspect ratio for dimensions
loadSprite(url, id) { getAspectRatio(width, height) {
if (typeof url !== 'string') { const getRatio = (w, h) => (h === 0 ? w : getRatio(h, w % h));
return; const ratio = getRatio(width, height);
} return `${width / ratio}:${height / ratio}`;
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

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((300 - @padding) / 6, ~'%');
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: 300%;
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