aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/js/controls.js231
-rw-r--r--src/js/listeners.js145
-rw-r--r--src/sass/components/menus.scss2
-rw-r--r--src/sass/utils/hidden.scss4
4 files changed, 188 insertions, 194 deletions
diff --git a/src/js/controls.js b/src/js/controls.js
index b3435236..710ee93b 100644
--- a/src/js/controls.js
+++ b/src/js/controls.js
@@ -370,18 +370,22 @@ const controls = {
type: 'button',
role: 'menuitemradio',
class: `${this.config.classNames.control} ${attributes.class ? attributes.class : ''}`.trim(),
- value,
'aria-checked': checked,
- })
+ value,
+ }),
);
+ const flex = createElement('span');
+
// We have to set as HTML incase of special characters
- item.innerHTML = title;
+ flex.innerHTML = title;
if (is.element(badge)) {
- item.appendChild(badge);
+ flex.appendChild(badge);
}
+ item.appendChild(flex);
+
Object.defineProperty(item, 'checked', {
enumerable: true,
get() {
@@ -399,6 +403,34 @@ const controls = {
},
});
+ this.listeners.bind(
+ item,
+ 'click',
+ () => {
+ item.checked = true;
+
+ switch (type) {
+ case 'language':
+ this.currentTrack = Number(value);
+ break;
+
+ case 'quality':
+ this.quality = value;
+ break;
+
+ case 'speed':
+ this.speed = parseFloat(value);
+ break;
+
+ default:
+ break;
+ }
+
+ controls.showMenuPanel.call(this, 'home');
+ },
+ type,
+ );
+
list.appendChild(item);
},
@@ -657,11 +689,88 @@ const controls = {
toggleHidden(this.elements.settings.buttons[setting], !toggle);
},
+ // Update the selected setting
+ updateSetting(setting, container, input) {
+ const pane = this.elements.settings.panels[setting];
+ let value = null;
+ let list = container;
+
+ if (setting === 'captions') {
+ value = this.currentTrack;
+ } else {
+ value = !is.empty(input) ? input : this[setting];
+
+ // Get default
+ if (is.empty(value)) {
+ value = this.config[setting].default;
+ }
+
+ // Unsupported value
+ if (!is.empty(this.options[setting]) && !this.options[setting].includes(value)) {
+ this.debug.warn(`Unsupported value of '${value}' for ${setting}`);
+ return;
+ }
+
+ // Disabled value
+ if (!this.config[setting].options.includes(value)) {
+ this.debug.warn(`Disabled value of '${value}' for ${setting}`);
+ return;
+ }
+ }
+
+ // Get the list if we need to
+ if (!is.element(list)) {
+ list = pane && pane.querySelector('[role="menu"]');
+ }
+
+ // If there's no list it means it's not been rendered...
+ if (!is.element(list)) {
+ return;
+ }
+
+ // Update the label
+ const label = this.elements.settings.buttons[setting].querySelector(`.${this.config.classNames.menu.value}`);
+ label.innerHTML = controls.getLabel.call(this, setting, value);
+
+ // Find the radio option and check it
+ const target = list && list.querySelector(`[value="${value}"]`);
+
+ if (is.element(target)) {
+ target.checked = true;
+ }
+ },
+
+ // Translate a value into a nice label
+ getLabel(setting, value) {
+ switch (setting) {
+ case 'speed':
+ return value === 1 ? i18n.get('normal', this.config) : `${value}×`;
+
+ case 'quality':
+ if (is.number(value)) {
+ const label = i18n.get(`qualityLabel.${value}`, this.config);
+
+ if (!label.length) {
+ return `${value}p`;
+ }
+
+ return label;
+ }
+
+ return toTitleCase(value);
+
+ case 'captions':
+ return captions.getLabel.call(this);
+
+ default:
+ return null;
+ }
+ },
+
// Set the quality menu
setQualityMenu(options) {
// Menu required
if (!is.element(this.elements.settings.panels.quality)) {
- console.warn('Not an element');
return;
}
@@ -674,10 +783,12 @@ const controls = {
}
// Toggle the pane and tab
- console.warn(this.options.quality);
const toggle = !is.empty(this.options.quality) && this.options.quality.length > 1;
controls.toggleMenuButton.call(this, type, toggle);
+ // Empty the menu
+ emptyElement(list);
+
// Check if we need to toggle the parent
controls.checkMenu.call(this);
@@ -686,9 +797,6 @@ const controls = {
return;
}
- // Empty the menu
- emptyElement(list);
-
// Get the badge HTML for HD, 4K etc
const getBadge = quality => {
const label = i18n.get(`qualityBadge.${quality}`, this.config);
@@ -719,84 +827,6 @@ const controls = {
controls.updateSetting.call(this, type, list);
},
- // Translate a value into a nice label
- getLabel(setting, value) {
- switch (setting) {
- case 'speed':
- return value === 1 ? i18n.get('normal', this.config) : `${value}×`;
-
- case 'quality':
- if (is.number(value)) {
- const label = i18n.get(`qualityLabel.${value}`, this.config);
-
- if (!label.length) {
- return `${value}p`;
- }
-
- return label;
- }
-
- return toTitleCase(value);
-
- case 'captions':
- return captions.getLabel.call(this);
-
- default:
- return null;
- }
- },
-
- // Update the selected setting
- updateSetting(setting, container, input) {
- const pane = this.elements.settings.panels[setting];
- let value = null;
- let list = container;
-
- if (setting === 'captions') {
- value = this.currentTrack;
- } else {
- value = !is.empty(input) ? input : this[setting];
-
- // Get default
- if (is.empty(value)) {
- value = this.config[setting].default;
- }
-
- // Unsupported value
- if (!is.empty(this.options[setting]) && !this.options[setting].includes(value)) {
- this.debug.warn(`Unsupported value of '${value}' for ${setting}`);
- return;
- }
-
- // Disabled value
- if (!this.config[setting].options.includes(value)) {
- this.debug.warn(`Disabled value of '${value}' for ${setting}`);
- return;
- }
- }
-
- // Get the list if we need to
- if (!is.element(list)) {
- list = pane && pane.querySelector('[role="menu"]');
- }
-
- // If there's no list it means it's not been rendered...
- if (!is.element(list)) {
- return;
- }
-
- // Update the label
- const label = this.elements.settings.buttons[setting].querySelector(`.${this.config.classNames.menu.value}`);
- label.innerHTML = controls.getLabel.call(this, setting, value);
-
- // Find the radio option and check it
- const target = list && list.querySelector(`[value="${value}"]`);
-
- if (is.element(target)) {
- target.checked = true;
- }
- },
-
// Set the looping options
/* setLoopMenu() {
// Menu required
@@ -846,13 +876,19 @@ const controls = {
// Set a list of available captions languages
setCaptionsMenu() {
+ // Menu required
+ if (!is.element(this.elements.settings.panels.captions)) {
+ return;
+ }
+
// TODO: Captions or language? Currently it's mixed
const type = 'captions';
const list = this.elements.settings.panels.captions.querySelector('[role="menu"]');
const tracks = captions.getTracks.call(this);
+ const toggle = Boolean(tracks.length);
// Toggle the pane and tab
- controls.toggleMenuButton.call(this, type, tracks.length);
+ controls.toggleMenuButton.call(this, type, toggle);
// Empty the menu
emptyElement(list);
@@ -861,7 +897,7 @@ const controls = {
controls.checkMenu.call(this);
// If there's no captions, bail
- if (!tracks.length) {
+ if (!toggle) {
return;
}
@@ -892,17 +928,13 @@ const controls = {
// Set a list of available captions languages
setSpeedMenu(options) {
- // Do nothing if not selected
- if (!this.config.controls.includes('settings') || !this.config.settings.includes('speed')) {
- return;
- }
-
// Menu required
if (!is.element(this.elements.settings.panels.speed)) {
return;
}
const type = 'speed';
+ const list = this.elements.settings.panels.speed.querySelector('[role="menu"]');
// Set the speed options
if (is.array(options)) {
@@ -918,6 +950,9 @@ const controls = {
const toggle = !is.empty(this.options.speed) && this.options.speed.length > 1;
controls.toggleMenuButton.call(this, type, toggle);
+ // Empty the menu
+ emptyElement(list);
+
// Check if we need to toggle the parent
controls.checkMenu.call(this);
@@ -926,12 +961,6 @@ const controls = {
return;
}
- // Get the list to populate
- const list = this.elements.settings.panels.speed.querySelector('[role="menu"]');
-
- // Empty the menu
- emptyElement(list);
-
// Create items
this.options.speed.forEach(speed => {
controls.createMenuItem.call(this, {
@@ -1069,7 +1098,6 @@ const controls = {
// Set attributes on current tab
toggleHidden(current, true);
- // current.setAttribute('tabindex', -1);
// Set attributes on target
toggleHidden(target, false);
@@ -1238,6 +1266,7 @@ const controls = {
class: `${this.config.classNames.control} ${this.config.classNames.control}--forward`,
role: 'menuitem',
'aria-haspopup': true,
+ hidden: '',
}),
);
diff --git a/src/js/listeners.js b/src/js/listeners.js
index cc9d3889..d6786111 100644
--- a/src/js/listeners.js
+++ b/src/js/listeners.js
@@ -243,7 +243,8 @@ class Listeners {
// Clear timer
clearTimeout(this.player.timers.controls);
- // Timer to prevent flicker when seeking
+
+ // Set new timer to prevent flicker when seeking
this.player.timers.controls = setTimeout(() => ui.toggleControls.call(this.player, false), delay);
},
);
@@ -394,58 +395,58 @@ class Listeners {
});
}
- // Listen for control events
- controls() {
- // IE doesn't support input event, so we fallback to change
- const inputEvent = browser.isIE ? 'change' : 'input';
+ // Run default and custom handlers
+ proxy(event, defaultHandler, customHandlerKey) {
+ const customHandler = this.player.config.listeners[customHandlerKey];
+ const hasCustomHandler = is.function(customHandler);
+ let returned = true;
- // Run default and custom handlers
- const proxy = (event, defaultHandler, customHandlerKey) => {
- const customHandler = this.player.config.listeners[customHandlerKey];
- const hasCustomHandler = is.function(customHandler);
- let returned = true;
+ // Execute custom handler
+ if (hasCustomHandler) {
+ returned = customHandler.call(this.player, event);
+ }
- // Execute custom handler
- if (hasCustomHandler) {
- returned = customHandler.call(this.player, event);
- }
+ // Only call default handler if not prevented in custom handler
+ if (returned && is.function(defaultHandler)) {
+ defaultHandler.call(this.player, event);
+ }
+ }
- // Only call default handler if not prevented in custom handler
- if (returned && is.function(defaultHandler)) {
- defaultHandler.call(this.player, event);
- }
- };
+ // Trigger custom and default handlers
+ bind(element, type, defaultHandler, customHandlerKey, passive = true) {
+ const customHandler = this.player.config.listeners[customHandlerKey];
+ const hasCustomHandler = is.function(customHandler);
- // Trigger custom and default handlers
- const bind = (element, type, defaultHandler, customHandlerKey, passive = true) => {
- const customHandler = this.player.config.listeners[customHandlerKey];
- const hasCustomHandler = is.function(customHandler);
+ on.call(
+ this.player,
+ element,
+ type,
+ event => this.proxy(event, defaultHandler, customHandlerKey),
+ passive && !hasCustomHandler,
+ );
+ }
- on.call(
- this.player,
- element,
- type,
- event => proxy(event, defaultHandler, customHandlerKey),
- passive && !hasCustomHandler,
- );
- };
+ // Listen for control events
+ controls() {
+ // IE doesn't support input event, so we fallback to change
+ const inputEvent = browser.isIE ? 'change' : 'input';
// Play/pause toggle
Array.from(this.player.elements.buttons.play).forEach(button => {
- bind(button, 'click', this.player.togglePlay, 'play');
+ this.bind(button, 'click', this.player.togglePlay, 'play');
});
// Pause
- bind(this.player.elements.buttons.restart, 'click', this.player.restart, 'restart');
+ this.bind(this.player.elements.buttons.restart, 'click', this.player.restart, 'restart');
// Rewind
- bind(this.player.elements.buttons.rewind, 'click', this.player.rewind, 'rewind');
+ this.bind(this.player.elements.buttons.rewind, 'click', this.player.rewind, 'rewind');
// Rewind
- bind(this.player.elements.buttons.fastForward, 'click', this.player.forward, 'fastForward');
+ this.bind(this.player.elements.buttons.fastForward, 'click', this.player.forward, 'fastForward');
// Mute toggle
- bind(
+ this.bind(
this.player.elements.buttons.mute,
'click',
() => {
@@ -455,10 +456,10 @@ class Listeners {
);
// Captions toggle
- bind(this.player.elements.buttons.captions, 'click', () => this.player.toggleCaptions());
+ this.bind(this.player.elements.buttons.captions, 'click', () => this.player.toggleCaptions());
// Fullscreen toggle
- bind(
+ this.bind(
this.player.elements.buttons.fullscreen,
'click',
() => {
@@ -468,7 +469,7 @@ class Listeners {
);
// Picture-in-Picture
- bind(
+ this.bind(
this.player.elements.buttons.pip,
'click',
() => {
@@ -478,62 +479,22 @@ class Listeners {
);
// Airplay
- bind(this.player.elements.buttons.airplay, 'click', this.player.airplay, 'airplay');
+ this.bind(this.player.elements.buttons.airplay, 'click', this.player.airplay, 'airplay');
// Settings menu
- bind(this.player.elements.buttons.settings, 'click', event => {
+ this.bind(this.player.elements.buttons.settings, 'click', event => {
controls.toggleMenu.call(this.player, event);
});
- // Settings menu
- bind(this.player.elements.settings.popup, 'click', event => {
- event.stopPropagation();
-
- // Go back to home tab on click
- const showHomeTab = () => {
- controls.showMenuPanel.call(this.player, 'home');
- };
-
- // Settings menu items - use event delegation as items are added/removed
- if (matches(event.target, this.player.config.selectors.inputs.language)) {
- proxy(
- event,
- () => {
- this.player.currentTrack = Number(event.target.value);
- showHomeTab();
- },
- 'language',
- );
- } else if (matches(event.target, this.player.config.selectors.inputs.quality)) {
- proxy(
- event,
- () => {
- this.player.quality = event.target.value;
- showHomeTab();
- },
- 'quality',
- );
- } else if (matches(event.target, this.player.config.selectors.inputs.speed)) {
- proxy(
- event,
- () => {
- this.player.speed = parseFloat(event.target.value);
- showHomeTab();
- },
- 'speed',
- );
- }
- });
-
// Set range input alternative "value", which matches the tooltip time (#954)
- bind(this.player.elements.inputs.seek, 'mousedown mousemove', event => {
+ this.bind(this.player.elements.inputs.seek, 'mousedown mousemove', event => {
const clientRect = this.player.elements.progress.getBoundingClientRect();
const percent = 100 / clientRect.width * (event.pageX - clientRect.left);
event.currentTarget.setAttribute('seek-value', percent);
});
// Pause while seeking
- bind(this.player.elements.inputs.seek, 'mousedown mouseup keydown keyup touchstart touchend', event => {
+ this.bind(this.player.elements.inputs.seek, 'mousedown mouseup keydown keyup touchstart touchend', event => {
const seek = event.currentTarget;
const code = event.keyCode ? event.keyCode : event.which;
@@ -559,7 +520,7 @@ class Listeners {
});
// Seek
- bind(
+ this.bind(
this.player.elements.inputs.seek,
inputEvent,
event => {
@@ -582,7 +543,7 @@ class Listeners {
// Current time invert
// Only if one time element is used for both currentTime and duration
if (this.player.config.toggleInvert && !is.element(this.player.elements.display.duration)) {
- bind(this.player.elements.display.currentTime, 'click', () => {
+ this.bind(this.player.elements.display.currentTime, 'click', () => {
// Do nothing if we're at the start
if (this.player.currentTime === 0) {
return;
@@ -595,7 +556,7 @@ class Listeners {
}
// Volume
- bind(
+ this.bind(
this.player.elements.inputs.volume,
inputEvent,
event => {
@@ -607,27 +568,27 @@ class Listeners {
// Polyfill for lower fill in <input type="range"> for webkit
if (browser.isWebkit) {
Array.from(getElements.call(this.player, 'input[type="range"]')).forEach(element => {
- bind(element, 'input', event => controls.updateRangeFill.call(this.player, event.target));
+ this.bind(element, 'input', event => controls.updateRangeFill.call(this.player, event.target));
});
}
// Seek tooltip
- bind(this.player.elements.progress, 'mouseenter mouseleave mousemove', event =>
+ this.bind(this.player.elements.progress, 'mouseenter mouseleave mousemove', event =>
controls.updateSeekTooltip.call(this.player, event),
);
// Update controls.hover state (used for ui.toggleControls to avoid hiding when interacting)
- bind(this.player.elements.controls, 'mouseenter mouseleave', event => {
+ this.bind(this.player.elements.controls, 'mouseenter mouseleave', event => {
this.player.elements.controls.hover = !this.player.touch && event.type === 'mouseenter';
});
// Update controls.pressed state (used for ui.toggleControls to avoid hiding when interacting)
- bind(this.player.elements.controls, 'mousedown mouseup touchstart touchend touchcancel', event => {
+ this.bind(this.player.elements.controls, 'mousedown mouseup touchstart touchend touchcancel', event => {
this.player.elements.controls.pressed = ['mousedown', 'touchstart'].includes(event.type);
});
// Focus in/out on controls
- bind(this.player.elements.controls, 'focusin focusout', event => {
+ this.bind(this.player.elements.controls, 'focusin focusout', event => {
const { config, elements, timers } = this.player;
// Skip transition to prevent focus from scrolling the parent element
@@ -654,7 +615,7 @@ class Listeners {
});
// Mouse wheel for volume
- bind(
+ this.bind(
this.player.elements.inputs.volume,
'wheel',
event => {
diff --git a/src/sass/components/menus.scss b/src/sass/components/menus.scss
index 35bfdeaa..be354e46 100644
--- a/src/sass/components/menus.scss
+++ b/src/sass/components/menus.scss
@@ -191,7 +191,7 @@
align-items: center;
display: flex;
margin-left: auto;
- margin-right: -$plyr-control-padding;
+ margin-right: -($plyr-control-padding - 2);
overflow: hidden;
padding-left: ceil($plyr-control-padding * 3.5);
pointer-events: none;
diff --git a/src/sass/utils/hidden.scss b/src/sass/utils/hidden.scss
index e4fa0aec..a42c3be8 100644
--- a/src/sass/utils/hidden.scss
+++ b/src/sass/utils/hidden.scss
@@ -22,3 +22,7 @@
width: 1px;
}
}
+
+.plyr [hidden] {
+ display: none !important;
+}