aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/js/captions.js9
-rw-r--r--src/js/controls.js19
-rw-r--r--src/js/defaults.js3
-rw-r--r--src/js/fullscreen.js241
-rw-r--r--src/js/listeners.js49
-rw-r--r--src/js/media.js2
-rw-r--r--src/js/plyr.js83
-rw-r--r--src/js/source.js3
-rw-r--r--src/js/ui.js10
-rw-r--r--src/js/utils.js68
-rw-r--r--src/sass/components/badges.scss4
-rw-r--r--src/sass/components/menus.scss8
-rw-r--r--src/sass/plyr.scss1
-rw-r--r--src/sass/settings/badges.scss6
14 files changed, 267 insertions, 239 deletions
diff --git a/src/js/captions.js b/src/js/captions.js
index 847ef7ff..6ab8c69e 100644
--- a/src/js/captions.js
+++ b/src/js/captions.js
@@ -39,7 +39,7 @@ const captions = {
// Only Vimeo and HTML5 video supported at this point
if (!this.isVideo || this.isYouTube || (this.isHTML5 && !support.textTracks)) {
// Clear menu and hide
- if (this.config.controls.includes('settings') && this.config.settings.includes('captions')) {
+ if (utils.is.array(this.config.controls) && this.config.controls.includes('settings') && this.config.settings.includes('captions')) {
controls.setCaptionsMenu.call(this);
}
@@ -68,7 +68,7 @@ const captions = {
captions.show.call(this);
// Set available languages in list
- if (this.config.controls.includes('settings') && this.config.settings.includes('captions')) {
+ if (utils.is.array(this.config.controls) && this.config.controls.includes('settings') && this.config.settings.includes('captions')) {
controls.setCaptionsMenu.call(this);
}
},
@@ -78,7 +78,7 @@ const captions = {
// Setup HTML5 track rendering
if (this.isHTML5 && this.isVideo) {
captions.getTracks.call(this).forEach(track => {
- // Remove previous bindings
+ // Show track
utils.on(track, 'cuechange', event => captions.setCue.call(this, event));
// Turn off native caption rendering to avoid double captions
@@ -124,7 +124,8 @@ const captions = {
setCue(input) {
// Get the track from the event if needed
const track = utils.is.event(input) ? input.target : input;
- const active = track.activeCues[0];
+ const { activeCues } = track;
+ const active = activeCues.length && activeCues[0];
const currentTrack = captions.getCurrentTrack.call(this);
// Only display current track
diff --git a/src/js/controls.js b/src/js/controls.js
index ea30acec..5e16b952 100644
--- a/src/js/controls.js
+++ b/src/js/controls.js
@@ -215,7 +215,16 @@ const controls = {
utils.setAttributes(button, attributes);
- this.elements.buttons[type] = button;
+ // We have multiple play buttons
+ if (type === 'play') {
+ if (!utils.is.array(this.elements.buttons[type])) {
+ this.elements.buttons[type] = [];
+ }
+
+ this.elements.buttons[type].push(button);
+ } else {
+ this.elements.buttons[type] = button;
+ }
return button;
},
@@ -893,7 +902,6 @@ const controls = {
// Play/Pause button
if (this.config.controls.includes('play')) {
container.appendChild(controls.createButton.call(this, 'play'));
- // container.appendChild(controls.createButton.call(this, 'pause'));
}
// Fast forward button
@@ -1147,9 +1155,10 @@ const controls = {
// Null by default
let container = null;
+ this.elements.controls = null;
- // HTML passed as the option
- if (utils.is.string(this.config.controls)) {
+ // HTML or Element passed as the option
+ if (utils.is.string(this.config.controls) || utils.is.element(this.config.controls)) {
container = this.config.controls;
} else if (utils.is.function(this.config.controls)) {
// A custom function to build controls
@@ -1193,7 +1202,7 @@ const controls = {
}
// Find the elements if need be
- if (utils.is.element(this.elements.controls)) {
+ if (!utils.is.element(this.elements.controls)) {
utils.findElements.call(this);
}
diff --git a/src/js/defaults.js b/src/js/defaults.js
index 8e50631e..ee87b8f6 100644
--- a/src/js/defaults.js
+++ b/src/js/defaults.js
@@ -56,7 +56,7 @@ const defaults = {
// Sprite (for icons)
loadSprite: true,
iconPrefix: 'plyr',
- iconUrl: 'https://cdn.plyr.io/3.0.0-beta.12/plyr.svg',
+ iconUrl: 'https://cdn.plyr.io/3.0.0-beta.13/plyr.svg',
// Blank video (used to prevent errors on source change)
blankVideo: 'https://cdn.plyr.io/static/blank.mp4',
@@ -120,6 +120,7 @@ const defaults = {
fullscreen: {
enabled: true, // Allow fullscreen?
fallback: true, // Fallback for vintage browsers
+ iosNative: false, // Use the native fullscreen in iOS (disables custom controls)
},
// Local storage
diff --git a/src/js/fullscreen.js b/src/js/fullscreen.js
index 366ea729..6d90bd6e 100644
--- a/src/js/fullscreen.js
+++ b/src/js/fullscreen.js
@@ -1,127 +1,204 @@
// ==========================================================================
-// Plyr fullscreen API
+// Fullscreen wrapper
// ==========================================================================
import utils from './utils';
-// Determine the prefix
-const prefix = (() => {
- let value = false;
+const browser = utils.getBrowser();
- if (utils.is.function(document.cancelFullScreen)) {
- value = '';
+function onChange() {
+ if (!this.enabled) {
+ return;
+ }
+
+ // Update toggle button
+ const button = this.player.elements.buttons.fullscreen;
+ if (utils.is.element(button)) {
+ utils.toggleState(button, this.active);
+ }
+
+ // Trigger an event
+ utils.dispatchEvent(this.target, this.active ? 'enterfullscreen' : 'exitfullscreen', true);
+
+ // Trap focus in container
+ if (!browser.isIos) {
+ utils.trapFocus.call(this.player, this.target, this.active);
+ }
+}
+
+function toggleFallback(toggle = false) {
+ // Store or restore scroll position
+ if (toggle) {
+ this.scrollPosition = {
+ x: window.scrollX || 0,
+ y: window.scrollY || 0,
+ };
} else {
+ window.scrollTo(this.scrollPosition.x, this.scrollPosition.y);
+ }
+
+ // Toggle scroll
+ document.body.style.overflow = toggle ? 'hidden' : '';
+
+ // Toggle class hook
+ utils.toggleClass(this.target, this.player.config.classNames.fullscreen.fallback, toggle);
+
+ // Toggle button and fire events
+ onChange.call(this);
+}
+
+class Fullscreen {
+ constructor(player) {
+ // Keep reference to parent
+ this.player = player;
+
+ // Get prefix
+ this.prefix = Fullscreen.prefix;
+
+ // Scroll position
+ this.scrollPosition = { x: 0, y: 0 };
+
+ // Register event listeners
+ // Handle event (incase user presses escape etc)
+ utils.on(document, this.prefix === 'ms' ? 'MSFullscreenChange' : `${this.prefix}fullscreenchange`, () => {
+ // TODO: Filter for target??
+ onChange.call(this);
+ });
+
+ // Fullscreen toggle on double click
+ utils.on(this.player.elements.container, 'dblclick', () => {
+ this.toggle();
+ });
+
+ // Prevent double click on controls bubbling up
+ utils.on(this.player.elements.controls, 'dblclick', event => event.stopPropagation());
+
+ // Update the UI
+ this.update();
+ }
+
+ // Determine if native supported
+ static get native() {
+ return !!(document.fullscreenEnabled || document.webkitFullscreenEnabled || document.mozFullScreenEnabled || document.msFullscreenEnabled);
+ }
+
+ // Get the prefix for handlers
+ static get prefix() {
+ // No prefix
+ if (utils.is.function(document.cancelFullScreen)) {
+ return false;
+ }
+
// Check for fullscreen support by vendor prefix
- [
+ let value = '';
+ const prefixes = [
'webkit',
- 'o',
'moz',
'ms',
- 'khtml',
- ].some(pre => {
+ ];
+
+ prefixes.some(pre => {
if (utils.is.function(document[`${pre}CancelFullScreen`])) {
value = pre;
return true;
- } else if (utils.is.function(document.msExitFullscreen) && document.msFullscreenEnabled) {
- // Special case for MS (when isn't it?)
+ } else if (utils.is.function(document.msExitFullscreen)) {
value = 'ms';
return true;
}
return false;
});
- }
- return value;
-})();
-
-// Fullscreen API
-const fullscreen = {
- // Get the prefix
- prefix,
+ return value;
+ }
- // Check if we can use it
- enabled: document.fullscreenEnabled || document.webkitFullscreenEnabled || document.mozFullScreenEnabled || document.msFullscreenEnabled,
+ // Determine if fullscreen is enabled
+ get enabled() {
+ const fallback = this.player.config.fullscreen.fallback && !utils.inFrame();
- // Yet again Microsoft awesomeness,
- // Sometimes the prefix is 'ms', sometimes 'MS' to keep you on your toes
- eventType: prefix === 'ms' ? 'MSFullscreenChange' : `${prefix}fullscreenchange`,
+ return (Fullscreen.native || fallback) && this.player.config.fullscreen.enabled && this.player.supported.ui && this.player.isVideo;
+ }
- // Is an element fullscreen
- isFullScreen(element) {
- if (!fullscreen.enabled) {
+ // Get active state
+ get active() {
+ if (!this.enabled) {
return false;
}
- const target = utils.is.nullOrUndefined(element) ? document.body : element;
-
- switch (prefix) {
- case '':
- return document.fullscreenElement === target;
-
- case 'moz':
- return document.mozFullScreenElement === target;
-
- default:
- return document[`${prefix}FullscreenElement`] === target;
+ // Fallback using classname
+ if (!Fullscreen.native) {
+ return utils.hasClass(this.target, this.player.config.classNames.fullscreen.fallback);
}
- },
- // Make an element fullscreen
- requestFullScreen(element) {
- if (!fullscreen.enabled) {
- return false;
- }
+ const element = !this.prefix ? document.fullscreenElement : document[`${this.prefix}FullscreenElement`];
- const target = utils.is.nullOrUndefined(element) ? document.body : element;
+ return element === this.target;
+ }
- return !prefix.length ? target.requestFullScreen() : target[prefix + (prefix === 'ms' ? 'RequestFullscreen' : 'RequestFullScreen')]();
- },
+ // Get target element
+ get target() {
+ return browser.isIos && this.player.config.fullscreen.iosNative ? this.player.media : this.player.elements.container;
+ }
- // Bail from fullscreen
- cancelFullScreen() {
- if (!fullscreen.enabled) {
- return false;
+ // Update UI
+ update() {
+ if (this.enabled) {
+ this.player.debug.log(`${Fullscreen.native ? 'Native' : 'Fallback'} fullscreen enabled`);
+ } else {
+ this.player.debug.log('Fullscreen not supported and fallback disabled');
}
- return !prefix.length ? document.cancelFullScreen() : document[prefix + (prefix === 'ms' ? 'ExitFullscreen' : 'CancelFullScreen')]();
- },
+ // Add styling hook to show button
+ utils.toggleClass(this.player.elements.container, this.player.config.classNames.fullscreen.enabled, this.enabled);
+ }
- // Get the current element
- element() {
- if (!fullscreen.enabled) {
- return null;
+ // Make an element fullscreen
+ enter() {
+ if (!this.enabled) {
+ return;
}
- return !prefix.length ? document.fullscreenElement : document[`${prefix}FullscreenElement`];
- },
+ // iOS native fullscreen doesn't need the request step
+ if (browser.isIos && this.player.config.fullscreen.iosNative) {
+ if (this.player.playing) {
+ this.target.webkitEnterFullscreen();
+ }
+ } else if (!Fullscreen.native) {
+ toggleFallback.call(this, true);
+ } else if (!this.prefix) {
+ this.target.requestFullScreen();
+ } else if (!utils.is.empty(this.prefix)) {
+ this.target[`${this.prefix}${this.prefix === 'ms' ? 'RequestFullscreen' : 'RequestFullScreen'}`]();
+ }
+ }
- // Setup fullscreen
- setup() {
- if (!this.supported.ui || this.isAudio || !this.config.fullscreen.enabled) {
+ // Bail from fullscreen
+ exit() {
+ if (!this.enabled) {
return;
}
- // Check for native support
- const nativeSupport = fullscreen.enabled;
-
- if (nativeSupport || (this.config.fullscreen.fallback && !utils.inFrame())) {
- this.debug.log(`${nativeSupport ? 'Native' : 'Fallback'} fullscreen enabled`);
-
- // Add styling hook to show button
- utils.toggleClass(this.elements.container, this.config.classNames.fullscreen.enabled, true);
- } else {
- this.debug.log('Fullscreen not supported and fallback disabled');
+ // iOS native fullscreen
+ if (browser.isIos && this.player.config.fullscreen.iosNative) {
+ this.target.webkitExitFullscreen();
+ this.player.play();
+ } else if (!Fullscreen.native) {
+ toggleFallback.call(this, false);
+ } else if (!this.prefix) {
+ document.cancelFullScreen();
+ } else if (!utils.is.empty(this.prefix)) {
+ document[`${this.prefix}${this.prefix === 'ms' ? 'ExitFullscreen' : 'CancelFullScreen'}`]();
}
+ }
- // Toggle state
- if (this.elements.buttons && this.elements.buttons.fullscreen) {
- utils.toggleState(this.elements.buttons.fullscreen, false);
+ // Toggle state
+ toggle() {
+ if (!this.active) {
+ this.enter();
+ } else {
+ this.exit();
}
+ }
+}
- // Trap focus in container
- utils.trapFocus.call(this);
- },
-};
-
-export default fullscreen;
+export default Fullscreen;
diff --git a/src/js/listeners.js b/src/js/listeners.js
index b3ccc1c6..214f6e7d 100644
--- a/src/js/listeners.js
+++ b/src/js/listeners.js
@@ -5,7 +5,6 @@
import support from './support';
import utils from './utils';
import controls from './controls';
-import fullscreen from './fullscreen';
import ui from './ui';
// Sniff out the browser
@@ -138,7 +137,7 @@ const listeners = {
case 70:
// F key
- this.toggleFullscreen();
+ this.fullscreen.toggle();
break;
case 67:
@@ -171,8 +170,8 @@ const listeners = {
// 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();
+ if (!this.fullscreen.enabled && this.fullscreen.active && code === 27) {
+ this.fullscreen.toggle();
}
// Store last code for next cycle
@@ -215,18 +214,6 @@ const listeners = {
this.toggleControls(event);
});
}
-
- // Handle user exiting fullscreen by escaping etc
- if (fullscreen.enabled) {
- utils.on(document, fullscreen.eventType, event => {
- this.toggleFullscreen(event);
- });
-
- // Fullscreen toggle on double click
- utils.on(this.elements.container, 'dblclick', event => {
- this.toggleFullscreen(event);
- });
- }
},
// Listen for media events
@@ -266,7 +253,7 @@ const listeners = {
utils.on(this.media, 'playing play pause ended', event => ui.checkPlaying.call(this, event));
// Loading
- utils.on(this.media, 'stalled waiting canplay seeked playing', event => ui.checkLoading.call(this, event));
+ utils.on(this.media, 'waiting canplay seeked playing', event => ui.checkLoading.call(this, event));
// Check if media failed to load
// utils.on(this.media, 'play', event => ui.checkFailed.call(this, event));
@@ -307,7 +294,7 @@ const listeners = {
event => {
event.preventDefault();
},
- false
+ false,
);
}
@@ -394,63 +381,63 @@ const listeners = {
utils.on(this.elements.buttons.play, 'click', event =>
proxy(event, 'play', () => {
this.togglePlay();
- })
+ }),
);
// Pause
utils.on(this.elements.buttons.restart, 'click', event =>
proxy(event, 'restart', () => {
this.restart();
- })
+ }),
);
// Rewind
utils.on(this.elements.buttons.rewind, 'click', event =>
proxy(event, 'rewind', () => {
this.rewind();
- })
+ }),
);
// Rewind
utils.on(this.elements.buttons.forward, 'click', event =>
proxy(event, 'forward', () => {
this.forward();
- })
+ }),
);
// Mute toggle
utils.on(this.elements.buttons.mute, 'click', event =>
proxy(event, 'mute', () => {
this.muted = !this.muted;
- })
+ }),
);
// Captions toggle
utils.on(this.elements.buttons.captions, 'click', event =>
proxy(event, 'captions', () => {
this.toggleCaptions();
- })
+ }),
);
// Fullscreen toggle
utils.on(this.elements.buttons.fullscreen, 'click', event =>
proxy(event, 'fullscreen', () => {
- this.toggleFullscreen();
- })
+ this.fullscreen.toggle();
+ }),
);
// Picture-in-Picture
utils.on(this.elements.buttons.pip, 'click', event =>
proxy(event, 'pip', () => {
this.pip = 'toggle';
- })
+ }),
);
// Airplay
utils.on(this.elements.buttons.airplay, 'click', event =>
proxy(event, 'airplay', () => {
this.airplay();
- })
+ }),
);
// Settings menu
@@ -489,7 +476,7 @@ const listeners = {
utils.on(this.elements.inputs.seek, inputEvent, event =>
proxy(event, 'seek', () => {
this.currentTime = event.target.value / event.target.max * this.duration;
- })
+ }),
);
// Current time invert
@@ -510,7 +497,7 @@ const listeners = {
utils.on(this.elements.inputs.volume, inputEvent, event =>
proxy(event, 'volume', () => {
this.volume = event.target.value;
- })
+ }),
);
// Polyfill for lower fill in <input type="range"> for webkit
@@ -583,7 +570,7 @@ const listeners = {
event.preventDefault();
}
}),
- false
+ false,
);
},
};
diff --git a/src/js/media.js b/src/js/media.js
index 3fbd9774..494c5376 100644
--- a/src/js/media.js
+++ b/src/js/media.js
@@ -86,7 +86,7 @@ const media = {
}
// Remove child sources
- Array.from(this.media.querySelectorAll('source')).forEach(utils.removeElement);
+ utils.removeElement(this.media.querySelectorAll('source'));
// Set blank video src attribute
// This is to prevent a MEDIA_ERR_SRC_NOT_SUPPORTED error
diff --git a/src/js/plyr.js b/src/js/plyr.js
index 1d3e0918..aebf311f 100644
--- a/src/js/plyr.js
+++ b/src/js/plyr.js
@@ -1,6 +1,6 @@
// ==========================================================================
// Plyr
-// plyr.js v3.0.0-beta.12
+// plyr.js v3.0.0-beta.13
// https://github.com/sampotts/plyr
// License: The MIT License (MIT)
// ==========================================================================
@@ -11,12 +11,12 @@ import support from './support';
import utils from './utils';
import Console from './console';
+import Fullscreen from './fullscreen';
import Storage from './storage';
import Ads from './plugins/ads';
import captions from './captions';
import controls from './controls';
-import fullscreen from './fullscreen';
import listeners from './listeners';
import media from './media';
import source from './source';
@@ -26,12 +26,6 @@ import ui from './ui';
// TODO: Use a WeakMap for private globals
// const globals = new WeakMap();
-// Globals
-let scrollPosition = {
- x: 0,
- y: 0,
-};
-
// Plyr instance
class Plyr {
constructor(target, options) {
@@ -232,9 +226,6 @@ class Plyr {
return;
}
- // Setup local storage for user settings
- this.storage = new Storage(this);
-
// Check for support again but with type
this.supported = support.check(this.type, this.provider, this.config.inline);
@@ -244,6 +235,9 @@ class Plyr {
return;
}
+ // Setup local storage for user settings
+ this.storage = new Storage(this);
+
// Store reference
this.media.plyr = this;
@@ -278,6 +272,9 @@ class Plyr {
ui.build.call(this);
}
+ // Setup fullscreen
+ this.fullscreen = new Fullscreen(this);
+
// Setup ads if provided
this.ads = new Ads(this);
}
@@ -851,62 +848,6 @@ class Plyr {
}
/**
- * Toggle fullscreen playback
- * Requires user input event
- * @param {event} event
- */
- toggleFullscreen(event) {
- // Video only
- if (this.isAudio) {
- return;
- }
-
- // Check for native support
- if (fullscreen.enabled) {
- if (utils.is.event(event) && event.type === fullscreen.eventType) {
- // If it's a fullscreen change event, update the state
- this.fullscreen.active = fullscreen.isFullScreen(this.elements.container);
- } else {
- // Else it's a user request to enter or exit
- if (!this.fullscreen.active) {
- fullscreen.requestFullScreen(this.elements.container);
- } else {
- fullscreen.cancelFullScreen();
- }
-
- return;
- }
- } else {
- // Otherwise, it's a simple toggle
- this.fullscreen.active = !this.fullscreen.active;
-
- // Add class hook
- utils.toggleClass(this.elements.container, this.config.classNames.fullscreen.fallback, this.fullscreen.active);
-
- // Make sure we don't lose scroll position
- if (this.fullscreen.active) {
- scrollPosition = {
- x: window.pageXOffset || 0,
- y: window.pageYOffset || 0,
- };
- } else {
- window.scrollTo(scrollPosition.x, scrollPosition.y);
- }
-
- // Bind/unbind escape key
- document.body.style.overflow = this.fullscreen.active ? 'hidden' : '';
- }
-
- // Set button state
- if (utils.is.element(this.elements.buttons.fullscreen)) {
- utils.toggleState(this.elements.buttons.fullscreen, this.fullscreen.active);
- }
-
- // Trigger an event
- utils.dispatchEvent.call(this, this.media, this.fullscreen.active ? 'enterfullscreen' : 'exitfullscreen');
- }
-
- /**
* Toggle picture-in-picture playback on WebKit/MacOS
* TODO: update player with state, support, enabled
* TODO: detect outside changes
@@ -1101,12 +1042,8 @@ class Plyr {
// If it's a soft destroy, make minimal changes
if (soft) {
if (Object.keys(this.elements).length) {
- // Remove buttons
- if (this.elements.buttons && this.elements.buttons.play) {
- Array.from(this.elements.buttons.play).forEach(button => utils.removeElement(button));
- }
-
- // Remove others
+ // Remove elements
+ utils.removeElement(this.elements.buttons.play);
utils.removeElement(this.elements.captions);
utils.removeElement(this.elements.controls);
utils.removeElement(this.elements.wrapper);
diff --git a/src/js/source.js b/src/js/source.js
index 9a6b219c..d252ba6b 100644
--- a/src/js/source.js
+++ b/src/js/source.js
@@ -136,6 +136,9 @@ const source = {
// Setup interface
ui.build.call(this);
}
+
+ // Update the fullscreen support
+ this.fullscreen.update();
},
true,
);
diff --git a/src/js/ui.js b/src/js/ui.js
index 14724fc6..5a52543d 100644
--- a/src/js/ui.js
+++ b/src/js/ui.js
@@ -5,7 +5,6 @@
import utils from './utils';
import captions from './captions';
import controls from './controls';
-import fullscreen from './fullscreen';
import listeners from './listeners';
const ui = {
@@ -33,12 +32,6 @@ const ui = {
if (!this.supported.ui) {
this.debug.warn(`Basic support only for ${this.provider} ${this.type}`);
- // Remove controls
- utils.removeElement.call(this, 'controls');
-
- // Remove large play
- utils.removeElement.call(this, 'buttons.play');
-
// Restore native controls
ui.toggleNativeControls.call(this, true);
@@ -63,9 +56,6 @@ const ui = {
// Remove native controls
ui.toggleNativeControls.call(this);
- // Setup fullscreen
- fullscreen.setup.call(this);
-
// Captions
captions.setup.call(this);
diff --git a/src/js/utils.js b/src/js/utils.js
index 3e9f06ff..c53293f4 100644
--- a/src/js/utils.js
+++ b/src/js/utils.js
@@ -306,12 +306,15 @@ const utils = {
// Remove an element
removeElement(element) {
if (!utils.is.element(element) || !utils.is.element(element.parentNode)) {
- return null;
+ return;
}
- element.parentNode.removeChild(element);
+ if (utils.is.nodeList(element) || utils.is.array(element)) {
+ Array.from(element).forEach(utils.removeElement);
+ return;
+ }
- return element;
+ element.parentNode.removeChild(element);
},
// Remove all child elements
@@ -525,46 +528,51 @@ const utils = {
},
// Trap focus inside container
- trapFocus() {
+ trapFocus(element = null, toggle = false) {
+ if (!utils.is.element(element)) {
+ return;
+ }
+
const focusable = utils.getElements.call(this, 'button:not(:disabled), input:not(:disabled), [tabindex]');
const first = focusable[0];
const last = focusable[focusable.length - 1];
- utils.on(
- this.elements.container,
- 'keydown',
- event => {
- // Bail if not tab key or not fullscreen
- if (event.key !== 'Tab' || event.keyCode !== 9 || !this.fullscreen.active) {
- return;
- }
+ const trap = event => {
+ // Bail if not tab key or not fullscreen
+ if (event.key !== 'Tab' || event.keyCode !== 9) {
+ return;
+ }
- // Get the current focused element
- const focused = utils.getFocusElement();
-
- if (focused === last && !event.shiftKey) {
- // Move focus to first element that can be tabbed if Shift isn't used
- first.focus();
- event.preventDefault();
- } else if (focused === first && event.shiftKey) {
- // Move focus to last element that can be tabbed if Shift is used
- last.focus();
- event.preventDefault();
- }
- },
- false,
- );
+ // Get the current focused element
+ const focused = utils.getFocusElement();
+
+ if (focused === last && !event.shiftKey) {
+ // Move focus to first element that can be tabbed if Shift isn't used
+ first.focus();
+ event.preventDefault();
+ } else if (focused === first && event.shiftKey) {
+ // Move focus to last element that can be tabbed if Shift is used
+ last.focus();
+ event.preventDefault();
+ }
+ };
+
+ if (toggle) {
+ utils.on(this.elements.container, 'keydown', trap, false);
+ } else {
+ utils.off(this.elements.container, 'keydown', trap, false);
+ }
},
// Toggle event listener
toggleListener(elements, event, callback, toggle, passive, capture) {
- // Bail if no elements
- if (utils.is.nullOrUndefined(elements)) {
+ // Bail if no elemetns, event, or callback
+ if (utils.is.empty(elements) || utils.is.empty(event) || !utils.is.function(callback)) {
return;
}
// If a nodelist is passed, call itself on each node
- if (utils.is.nodeList(elements)) {
+ if (utils.is.nodeList(elements) || utils.is.array(elements)) {
// Create listener for each node
Array.from(elements).forEach(element => {
if (element instanceof Node) {
diff --git a/src/sass/components/badges.scss b/src/sass/components/badges.scss
index 7d28ffaf..3a9a28b5 100644
--- a/src/sass/components/badges.scss
+++ b/src/sass/components/badges.scss
@@ -3,9 +3,9 @@
// --------------------------------------------------------------
.plyr__badge {
- background: $plyr-menu-color;
+ background: $plyr-badge-bg;
border-radius: 2px;
- color: $plyr-menu-bg;
+ color: $plyr-badge-color;
font-size: $plyr-font-size-badge;
line-height: 1;
padding: 3px 4px;
diff --git a/src/sass/components/menus.scss b/src/sass/components/menus.scss
index 35fc580d..4ad67ec1 100644
--- a/src/sass/components/menus.scss
+++ b/src/sass/components/menus.scss
@@ -59,6 +59,14 @@
margin: 0;
overflow: hidden;
padding: $plyr-control-padding;
+
+ li {
+ margin-top: 2px;
+
+ &:first-child {
+ margin-top: 0;
+ }
+ }
}
// Options
diff --git a/src/sass/plyr.scss b/src/sass/plyr.scss
index e8615989..14856957 100644
--- a/src/sass/plyr.scss
+++ b/src/sass/plyr.scss
@@ -10,6 +10,7 @@
@import 'settings/cosmetics';
@import 'settings/type';
+@import 'settings/badges';
@import 'settings/captions';
@import 'settings/controls';
@import 'settings/helpers';
diff --git a/src/sass/settings/badges.scss b/src/sass/settings/badges.scss
new file mode 100644
index 00000000..4f98c9a8
--- /dev/null
+++ b/src/sass/settings/badges.scss
@@ -0,0 +1,6 @@
+// ==========================================================================
+// Badges
+// ==========================================================================
+
+$plyr-badge-bg: $plyr-color-fiord !default;
+$plyr-badge-color: #fff !default;