Keyboard shortcuts

This commit is contained in:
Sam 2016-08-20 23:39:18 +10:00
parent bea513f5dd
commit 92b9e3400b
8 changed files with 204 additions and 114 deletions

View File

@ -25,6 +25,7 @@ And some other changes and bug fixes:
- Sprite is only loaded once (fixes #259) - Sprite is only loaded once (fixes #259)
- Fixes for Vimeo post message bugs on source change or destroy (fixes #318) - Fixes for Vimeo post message bugs on source change or destroy (fixes #318)
- Save caption state in storage (fixes #311) - Save caption state in storage (fixes #311)
- Added keyboard shortcuts to the current focused player (with option to disable) (fixes #309)
## v1.8.12 ## v1.8.12
- Vimeo keyboard focus fix (Fixes #317) - Vimeo keyboard focus fix (Fixes #317)

2
dist/plyr.css vendored

File diff suppressed because one or more lines are too long

4
dist/plyr.js vendored

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,6 @@
// ========================================================================== // ==========================================================================
// Plyr // Plyr
// plyr.js v1.8.12 // plyr.js v1.9.0
// https://github.com/selz/plyr // https://github.com/selz/plyr
// License: The MIT License (MIT) // License: The MIT License (MIT)
// ========================================================================== // ==========================================================================
@ -48,6 +48,7 @@
hideControls: true, hideControls: true,
showPosterOnEnd: false, showPosterOnEnd: false,
disableContextMenu: true, disableContextMenu: true,
keyboardShorcuts: true,
tooltips: { tooltips: {
controls: false, controls: false,
seek: true seek: true
@ -481,11 +482,11 @@
} }
// Unbind event // Unbind event
function _off(element, events, callback, useCapture) { /*function _off(element, events, callback, useCapture) {
if (element) { if (element) {
_toggleListener(element, events, callback, false, useCapture); _toggleListener(element, events, callback, false, useCapture);
} }
} }*/
// Trigger event // Trigger event
function _triggerEvent(element, eventName, bubbles, properties) { function _triggerEvent(element, eventName, bubbles, properties) {
@ -1394,8 +1395,11 @@
var label = config.i18n.play; var label = config.i18n.play;
// If there's a media title set, use that for the label // If there's a media title set, use that for the label
if (!_is.undefined(config.title) && config.title.length) { if (_is.string(config.title) && config.title.length) {
label += ', ' + config.title; label += ', ' + config.title;
// Set container label
plyr.container.setAttribute('aria-label', config.title);
} }
// If there's a play button, set label // If there's a play button, set label
@ -1614,9 +1618,10 @@
// When embeds are ready // When embeds are ready
function _embedReady() { function _embedReady() {
// Setup the UI if full support // Setup the UI and call ready if full support
if (plyr.supported.full) { if (plyr.supported.full) {
_setupInterface(); _setupInterface();
_ready();
} }
// Set title // Set title
@ -1674,6 +1679,11 @@
// Set title // Set title
config.title = instance.getVideoData().title; config.title = instance.getVideoData().title;
// Set the tabindex
if (plyr.supported.full) {
plyr.media.querySelector('iframe').setAttribute('tabindex', '-1');
}
// Update UI // Update UI
_embedReady(); _embedReady();
@ -1817,8 +1827,6 @@
if(_is.htmlElement(plyr.embed.element) && plyr.supported.full) { if(_is.htmlElement(plyr.embed.element) && plyr.supported.full) {
plyr.embed.element.setAttribute('tabindex', '-1'); plyr.embed.element.setAttribute('tabindex', '-1');
} }
//console.log(plyr.embed);
}); });
plyr.embed.on('play', function() { plyr.embed.on('play', function() {
@ -1950,18 +1958,21 @@
// Toggle playback // Toggle playback
function _togglePlay(toggle) { function _togglePlay(toggle) {
// True toggle
if (!_is.boolean(toggle)) {
toggle = plyr.media.paused;
}
// Play // Play
if (toggle === true) { if (toggle) {
_play(); _play();
} }
// Pause // Pause
else if (toggle === false) { else {
_pause(); _pause();
} }
// True toggle
else { return toggle;
plyr.media[plyr.media.paused ? 'play' : 'pause']();
}
} }
// Rewind // Rewind
@ -2097,12 +2108,14 @@
// Check for native support // Check for native support
var nativeSupport = fullscreen.supportsFullScreen; var nativeSupport = fullscreen.supportsFullScreen;
// If it's a fullscreen change event, it's probably a native close
if (nativeSupport) {
// If it's a fullscreen change event, update the UI
if (event && event.type === fullscreen.fullScreenEventName) { if (event && event.type === fullscreen.fullScreenEventName) {
plyr.isFullscreen = fullscreen.isFullScreen(plyr.container); plyr.isFullscreen = fullscreen.isFullScreen(plyr.container);
} }
// If there's native support, use it // Else it's a user request to enter or exit
else if (nativeSupport) { else {
// Request fullscreen // Request fullscreen
if (!fullscreen.isFullScreen(plyr.container)) { if (!fullscreen.isFullScreen(plyr.container)) {
// Save scroll position // Save scroll position
@ -2117,34 +2130,22 @@
} }
// Check if we're actually full screen (it could fail) // Check if we're actually full screen (it could fail)
plyr.isFullscreen = fullscreen.isFullScreen(plyr.container); // plyr.isFullscreen = fullscreen.isFullScreen(plyr.container);
return;
}
} }
else { else {
// Otherwise, it's a simple toggle // Otherwise, it's a simple toggle
plyr.isFullscreen = !plyr.isFullscreen; plyr.isFullscreen = !plyr.isFullscreen;
// Bind/unbind escape key // Bind/unbind escape key
if (plyr.isFullscreen) { document.body.style.overflow = plyr.isFullscreen ? 'hidden' : '';
_on(document, 'keyup', _handleEscapeFullscreen);
document.body.style.overflow = 'hidden';
}
else {
_off(document, 'keyup', _handleEscapeFullscreen);
document.body.style.overflow = '';
}
} }
// Set class hook // Set class hook
_toggleClass(plyr.container, config.classes.fullscreen.active, plyr.isFullscreen); _toggleClass(plyr.container, config.classes.fullscreen.active, plyr.isFullscreen);
// Trap focus
if (plyr.isFullscreen) {
plyr.container.setAttribute('tabindex', '-1');
}
else {
plyr.container.removeAttribute('tabindex');
}
// Trap focus // Trap focus
_focusTrap(plyr.isFullscreen); _focusTrap(plyr.isFullscreen);
@ -2162,14 +2163,6 @@
} }
} }
// Bail from faux-fullscreen
function _handleEscapeFullscreen(event) {
// If it's a keypress and not escape, bail
if ((event.which || event.charCode || event.keyCode) === 27 && plyr.isFullscreen) {
_toggleFullscreen();
}
}
// Mute // Mute
function _toggleMute(muted) { function _toggleMute(muted) {
// If the method is called without parameter, toggle based on current value // If the method is called without parameter, toggle based on current value
@ -2263,17 +2256,25 @@
} }
// Increase volume // Increase volume
function _increaseVolume() { function _increaseVolume(step) {
var volume = plyr.media.muted ? 0 : (plyr.media.volume * config.volumeMax); var volume = plyr.media.muted ? 0 : (plyr.media.volume * config.volumeMax);
_setVolume(volume + (config.volumeStep / 5)); if (!_is.number(step)) {
step = config.volumeStep;
}
_setVolume(volume + step);
} }
// Decrease volume // Decrease volume
function _decreaseVolume() { function _decreaseVolume(step) {
var volume = plyr.media.muted ? 0 : (plyr.media.volume * config.volumeMax); var volume = plyr.media.muted ? 0 : (plyr.media.volume * config.volumeMax);
_setVolume(volume - (config.volumeStep / 5)); if (!_is.number(step)) {
step = config.volumeStep;
}
_setVolume(volume - step);
} }
// Update volume UI and storage // Update volume UI and storage
@ -2689,6 +2690,9 @@
// Cancel current network requests // Cancel current network requests
_cancelRequests(); _cancelRequests();
// Remove ready class hook
_toggleClass(plyr.container, config.classes.ready, false);
// Setup new source // Setup new source
function setup() { function setup() {
// Remove embed object // Remove embed object
@ -2794,10 +2798,14 @@
// Display duration if available // Display duration if available
_displayDuration(); _displayDuration();
// Call ready
_ready();
} }
// If embed but not fully supported, setupInterface now // If embed but not fully supported, setupInterface and call ready now
else if (_inArray(config.types.embed, plyr.type) && !plyr.supported.full) { else if (_inArray(config.types.embed, plyr.type) && !plyr.supported.full) {
_setupInterface(); _setupInterface();
_ready();
} }
// Set aria title and iframe title // Set aria title and iframe title
@ -2823,16 +2831,8 @@
var inputEvent = (plyr.browser.isIE ? 'change' : 'input'); var inputEvent = (plyr.browser.isIE ? 'change' : 'input');
// Click play/pause helper // Click play/pause helper
function _togglePlay() { function togglePlay() {
var play = plyr.media.paused; var play = _togglePlay();
// Toggle playback
if (play) {
_play();
}
else {
_pause();
}
// Determine which buttons // Determine which buttons
var trigger = plyr.buttons[play ? 'play' : 'pause'], var trigger = plyr.buttons[play ? 'play' : 'pause'],
@ -2861,16 +2861,27 @@
} }
} }
// Detect tab focus // Get the focused element
function checkFocus() { function getFocusElement() {
var focused = document.activeElement; var focused = document.activeElement;
if (!focused || focused === document.body) { if (!focused || focused === document.body) {
focused = null; focused = null;
} }
else if (document.querySelector) { else {
focused = document.querySelector(':focus'); focused = document.querySelector(':focus');
} }
return focused;
}
// Get the key code for an event
function getKeyCode(event) {
return event.keyCode ? event.keyCode : event.which;
}
// Detect tab focus
function checkTabFocus(focused) {
for (var button in plyr.buttons) { for (var button in plyr.buttons) {
var element = plyr.buttons[button]; var element = plyr.buttons[button];
@ -2885,11 +2896,67 @@
} }
} }
// Keyboard shortcuts
if (config.keyboardShorcuts) {
//var held = false;
_on(plyr.container, 'keyup keydown', function(event) {
var code = getKeyCode(event),
down = event.type === 'keydown',
first = true,
timer;
function handleKey() {
switch(code) {
// Space and K key
case 32:
case 75: if (first) { _togglePlay(); } break;
// Arrow up
case 38: _increaseVolume(); break;
// Arrow down
case 40: _decreaseVolume(); break;
// M key
case 77: if (first) { _toggleMute(); } break;
// Arrow forward
case 39: _forward(); break;
// Arrow back
case 37: _rewind(); break;
// F key
case 70: if (first) { _toggleFullscreen(); } break;
// C key
case 67: if (first) { _toggleCaptions(); } break;
}
// Escape is handle natively when in full screen
// So we only need to worry about non native
if (!fullscreen.supportsFullScreen && plyr.isFullscreen && code === 27) {
_toggleFullscreen();
}
// First run completed
first = false;
}
if (down) {
handleKey();
// If a key is held for 200ms, run again
// Handy for volume and skip
timer = setTimeout(handleKey, 200);
}
else {
clearTimeout(timer);
}
});
}
// Focus/tab management
_on(window, 'keyup', function(event) { _on(window, 'keyup', function(event) {
var code = (event.keyCode ? event.keyCode : event.which); var code = getKeyCode(event),
focused = getFocusElement();
if (code === 9) { if (code === 9) {
checkFocus(); checkTabFocus(focused);
} }
}); });
_on(document.body, 'click', function() { _on(document.body, 'click', function() {
@ -2904,10 +2971,10 @@
} }
// Play // Play
_proxyListener(plyr.buttons.play, 'click', config.listeners.play, _togglePlay); _proxyListener(plyr.buttons.play, 'click', config.listeners.play, togglePlay);
// Pause // Pause
_proxyListener(plyr.buttons.pause, 'click', config.listeners.pause, _togglePlay); _proxyListener(plyr.buttons.pause, 'click', config.listeners.pause, togglePlay);
// Restart // Restart
_proxyListener(plyr.buttons.restart, 'click', config.listeners.restart, _seek); _proxyListener(plyr.buttons.restart, 'click', config.listeners.restart, _seek);
@ -2968,25 +3035,26 @@
// Detect "natural" scroll - suppored on OS X Safari only // Detect "natural" scroll - suppored on OS X Safari only
// Other browsers on OS X will be inverted until support improves // Other browsers on OS X will be inverted until support improves
var inverted = event.webkitDirectionInvertedFromDevice; var inverted = event.webkitDirectionInvertedFromDevice,
step = (config.volumeStep / 5);
// Scroll down (or up on natural) to decrease // Scroll down (or up on natural) to decrease
if (event.deltaY < 0 || event.deltaX > 0) { if (event.deltaY < 0 || event.deltaX > 0) {
if (inverted) { if (inverted) {
_decreaseVolume(); _decreaseVolume(step);
} }
else { else {
_increaseVolume(); _increaseVolume(step);
} }
} }
// Scroll up (or down on natural) to increase // Scroll up (or down on natural) to increase
if (event.deltaY > 0 || event.deltaX < 0) { if (event.deltaY > 0 || event.deltaX < 0) {
if (inverted) { if (inverted) {
_increaseVolume(); _increaseVolume(step);
} }
else { else {
_decreaseVolume(); _decreaseVolume(step);
} }
} }
}); });
@ -3231,6 +3299,9 @@
// Wrap media // Wrap media
plyr.container = _wrap(media, document.createElement('div')); plyr.container = _wrap(media, document.createElement('div'));
// Allow focus to be captured
plyr.container.setAttribute('tabindex', 0);
// Add style hook // Add style hook
_toggleStyleHook(); _toggleStyleHook();
@ -3252,16 +3323,21 @@
if (config.autoplay) { if (config.autoplay) {
_play(); _play();
} }
// Call ready
_ready();
} }
// If embed but not fully supported, setupInterface now (to avoid flash of controls) // If embed but not fully supported, setupInterface (to avoid flash of controls) and call ready now
else if (_inArray(config.types.embed, plyr.type) && !plyr.supported.full) { else if (_inArray(config.types.embed, plyr.type) && !plyr.supported.full) {
_setupInterface(); _setupInterface();
_ready();
} }
// Successful setup // Successful setup
plyr.init = true; plyr.init = true;
} }
// Setup the UI
function _setupInterface() { function _setupInterface() {
// Don't setup interface if no support // Don't setup interface if no support
if (!plyr.supported.full) { if (!plyr.supported.full) {
@ -3321,23 +3397,9 @@
// Display duration // Display duration
_displayDuration(); _displayDuration();
// Ready event
_triggerEvent(plyr.container, 'ready', true);
// Class
_toggleClass(plyr.container, config.classes.ready, true);
} }
// Initialize instance var api = {
_init();
// If init failed, return an empty object
if (!plyr.init) {
return {};
}
return {
getOriginal: function() { return original; }, getOriginal: function() { return original; },
getContainer: function() { return plyr.container }, getContainer: function() { return plyr.container },
getEmbed: function() { return plyr.embed; }, getEmbed: function() { return plyr.embed; },
@ -3365,6 +3427,31 @@
destroy: _destroy, destroy: _destroy,
getCurrentTime: function() { return media.currentTime; } getCurrentTime: function() { return media.currentTime; }
}; };
// Everything done
function _ready() {
// Ready event
_triggerEvent(plyr.container, 'ready', true);
// Set class hook on media element
_toggleClass(plyr.media, defaults.classes.setup, true);
// Set container class for ready
_toggleClass(plyr.container, config.classes.ready, true);
// Store a refernce to instance
plyr.media.plyr = api;
}
// Initialize instance
_init();
// If init failed, return null
if (!plyr.init) {
return null;
}
return api;
} }
// Load a sprite // Load a sprite
@ -3544,14 +3631,6 @@
return; return;
} }
// Set plyr to false if setup failed
// Maybe we remove this and add a .get() function to get the instance
// If passed a media element or container?
media.plyr = instance;
// Set class hook
_toggleClass(media, defaults.classes.setup, true);
// Listen for events if debugging // Listen for events if debugging
if (config.debug) { if (config.debug) {
var events = config.events.concat(['setup', 'ready', 'statechange', 'enterfullscreen', 'exitfullscreen', 'captionsenabled', 'captionsdisabled']); var events = config.events.concat(['setup', 'ready', 'statechange', 'enterfullscreen', 'exitfullscreen', 'captionsenabled', 'captionsdisabled']);

View File

@ -40,6 +40,11 @@
} }
} }
// Focus
&:focus {
outline: 0;
}
// Media elements // Media elements
video, video,
audio { audio {

View File

@ -12,7 +12,7 @@
@plyr-color-main: #3498db; @plyr-color-main: #3498db;
// Font // Font
@plyr-font-family: 'San Francisco', -apple-system, BlinkMacSystemFont, '.SFNSText-Regular', Avenir, 'Avenir Next', 'Helvetica Neue', 'Segoe UI', Helvetica, Arial, sans-serif; @plyr-font-family: Avenir, 'Avenir Next', 'Helvetica Neue', 'Segoe UI', Helvetica, Arial, sans-serif;
@plyr-font-size-small: 14px; @plyr-font-size-small: 14px;
@plyr-font-size-base: 16px; @plyr-font-size-base: 16px;

View File

@ -40,6 +40,11 @@
} }
} }
// Focus
&:focus {
outline: 0;
}
// Media elements // Media elements
video, video,
audio { audio {

View File

@ -13,7 +13,7 @@ $plyr-sr-only-important: true !default;
$plyr-color-main: #3498db !default; $plyr-color-main: #3498db !default;
// Font sizes // Font sizes
$plyr-font-family: 'San Francisco', -apple-system, BlinkMacSystemFont, '.SFNSText-Regular', Avenir, 'Avenir Next', 'Helvetica Neue', 'Segoe UI', Helvetica, Arial, sans-serif !default; $plyr-font-family: Avenir, 'Avenir Next', 'Helvetica Neue', 'Segoe UI', Helvetica, Arial, sans-serif !default;
$plyr-font-size-small: 14px !default; $plyr-font-size-small: 14px !default;
$plyr-font-size-base: 16px !default; $plyr-font-size-base: 16px !default;