aboutsummaryrefslogtreecommitdiffstats
path: root/src/js/listeners.js
diff options
context:
space:
mode:
authorSam Potts <sam@potts.es>2018-06-17 01:26:24 +1000
committerSam Potts <sam@potts.es>2018-06-17 01:26:24 +1000
commitf1b4db4f3665ea824ad75d7b68df1efc9f1a0524 (patch)
treed3fd6d76a15a94f8a5889b3d0033c65b35420c4f /src/js/listeners.js
parent6a6f3914c0943c72a85c290a8175d759e7f70809 (diff)
parentd4abb4b1438cb316aacae480e7b7e9b055a60b24 (diff)
downloadplyr-f1b4db4f3665ea824ad75d7b68df1efc9f1a0524.tar.lz
plyr-f1b4db4f3665ea824ad75d7b68df1efc9f1a0524.tar.xz
plyr-f1b4db4f3665ea824ad75d7b68df1efc9f1a0524.zip
Merge branch 'develop' into a11y-improvements
# Conflicts: # dist/plyr.js # dist/plyr.js.map # dist/plyr.min.js # dist/plyr.min.js.map # dist/plyr.polyfilled.js # dist/plyr.polyfilled.js.map # dist/plyr.polyfilled.min.js # dist/plyr.polyfilled.min.js.map # src/js/controls.js # src/js/fullscreen.js # src/js/plyr.js # src/js/ui.js # src/js/utils.js
Diffstat (limited to 'src/js/listeners.js')
-rw-r--r--src/js/listeners.js296
1 files changed, 133 insertions, 163 deletions
diff --git a/src/js/listeners.js b/src/js/listeners.js
index c391ea4c..9d987508 100644
--- a/src/js/listeners.js
+++ b/src/js/listeners.js
@@ -4,10 +4,10 @@
import controls from './controls';
import ui from './ui';
-import utils from './utils';
-
-// Sniff out the browser
-const browser = utils.getBrowser();
+import browser from './utils/browser';
+import { getElement, getElements, getFocusElement, matches, toggleClass, toggleHidden } from './utils/elements';
+import { on, once, toggleListener, triggerEvent } from './utils/events';
+import is from './utils/is';
class Listeners {
constructor(player) {
@@ -32,7 +32,7 @@ class Listeners {
// If the event is bubbled from the media element
// Firefox doesn't get the keycode for whatever reason
- if (!utils.is.number(code)) {
+ if (!is.number(code)) {
return;
}
@@ -46,37 +46,16 @@ class Listeners {
// 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,
- ];
+ const preventDefault = [32, 37, 38, 39, 40, 48, 49, 50, 51, 52, 53, 54, 56, 57, 67, 70, 73, 75, 76, 77, 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.element(focused) && (
- focused !== this.player.elements.inputs.seek &&
- utils.matches(focused, this.player.config.selectors.editable))
+ const focused = getFocusElement();
+ if (
+ is.element(focused) &&
+ (focused !== this.player.elements.inputs.seek &&
+ matches(focused, this.player.config.selectors.editable))
) {
return;
}
@@ -195,41 +174,37 @@ class Listeners {
this.player.touch = true;
// Add touch class
- utils.toggleClass(this.player.elements.container, this.player.config.classNames.isTouch, true);
-
- // Clean up
- utils.off(document.body, 'touchstart', this.firstTouch);
+ toggleClass(this.player.elements.container, this.player.config.classNames.isTouch, true);
}
// Global window & document listeners
global(toggle = true) {
// Keyboard shortcuts
if (this.player.config.keyboard.global) {
- utils.toggleListener(window, 'keydown keyup', this.handleKey, toggle, false);
+ toggleListener.call(this.player, window, 'keydown keyup', this.handleKey, toggle, false);
}
// Click anywhere closes menu
- utils.toggleListener(document.body, 'click', this.toggleMenu, toggle);
+ toggleListener.call(this.player, document.body, 'click', this.toggleMenu, toggle);
// Detect touch by events
- utils.on(document.body, 'touchstart', this.firstTouch);
+ once.call(this.player, document.body, 'touchstart', this.firstTouch);
}
// Container listeners
container() {
// Keyboard shortcuts
if (!this.player.config.keyboard.global && this.player.config.keyboard.focused) {
- utils.on(this.player.elements.container, 'keydown keyup', this.handleKey, false);
+ on.call(this.player, this.player.elements.container, 'keydown keyup', this.handleKey, false);
}
// Detect tab focus
// Remove class on blur/focusout
- utils.on(this.player.elements.container, 'focusout', event => {
- utils.toggleClass(event.target, this.player.config.classNames.tabFocus, false);
+ on.call(this.player, this.player.elements.container, 'focusout', event => {
+ toggleClass(event.target, this.player.config.classNames.tabFocus, false);
});
-
// Add classname to tabbed elements
- utils.on(this.player.elements.container, 'keydown', event => {
+ on.call(this.player, this.player.elements.container, 'keydown', event => {
if (event.keyCode !== 9) {
return;
}
@@ -237,59 +212,64 @@ class Listeners {
// Delay the adding of classname until the focus has changed
// This event fires before the focusin event
setTimeout(() => {
- utils.toggleClass(utils.getFocusElement(), this.player.config.classNames.tabFocus, true);
+ toggleClass(getFocusElement(), this.player.config.classNames.tabFocus, true);
}, 0);
});
// Toggle controls on mouse events and entering fullscreen
- utils.on(this.player.elements.container, 'mousemove mouseleave touchstart touchmove enterfullscreen exitfullscreen', event => {
- const { controls } = this.player.elements;
+ on.call(
+ this.player,
+ this.player.elements.container,
+ 'mousemove mouseleave touchstart touchmove enterfullscreen exitfullscreen',
+ event => {
+ const { controls } = this.player.elements;
- // Remove button states for fullscreen
- if (event.type === 'enterfullscreen') {
- controls.pressed = false;
- controls.hover = false;
- }
+ // Remove button states for fullscreen
+ if (event.type === 'enterfullscreen') {
+ controls.pressed = false;
+ controls.hover = false;
+ }
- // Show, then hide after a timeout unless another control event occurs
- const show = [
- 'touchstart',
- 'touchmove',
- 'mousemove',
- ].includes(event.type);
+ // Show, then hide after a timeout unless another control event occurs
+ const show = ['touchstart', 'touchmove', 'mousemove'].includes(event.type);
- let delay = 0;
+ let delay = 0;
- if (show) {
- ui.toggleControls.call(this.player, true);
- // Use longer timeout for touch devices
- delay = this.player.touch ? 3000 : 2000;
- }
+ if (show) {
+ ui.toggleControls.call(this.player, true);
+ // Use longer timeout for touch devices
+ delay = this.player.touch ? 3000 : 2000;
+ }
- // Clear timer
- clearTimeout(this.player.timers.controls);
- // Timer to prevent flicker when seeking
- this.player.timers.controls = setTimeout(() => ui.toggleControls.call(this.player, false), delay);
- });
+ // Clear timer
+ clearTimeout(this.player.timers.controls);
+ // Timer to prevent flicker when seeking
+ this.player.timers.controls = setTimeout(() => ui.toggleControls.call(this.player, false), delay);
+ },
+ );
}
// Listen for media events
media() {
// Time change on media
- utils.on(this.player.media, 'timeupdate seeking seeked', event => controls.timeUpdate.call(this.player, event));
+ on.call(this.player, this.player.media, 'timeupdate seeking seeked', event =>
+ controls.timeUpdate.call(this.player, event),
+ );
// Display duration
- utils.on(this.player.media, 'durationchange loadeddata loadedmetadata', event => controls.durationUpdate.call(this.player, event));
+ on.call(this.player, this.player.media, 'durationchange loadeddata loadedmetadata', event =>
+ controls.durationUpdate.call(this.player, event),
+ );
// Check for audio tracks on load
// We can't use `loadedmetadata` as it doesn't seem to have audio tracks at that point
- utils.on(this.player.media, 'loadeddata', () => {
- utils.toggleHidden(this.player.elements.volume, !this.player.hasAudio);
- utils.toggleHidden(this.player.elements.buttons.mute, !this.player.hasAudio);
+ on.call(this.player, this.player.media, 'canplay', () => {
+ toggleHidden(this.player.elements.volume, !this.player.hasAudio);
+ toggleHidden(this.player.elements.buttons.mute, !this.player.hasAudio);
});
// Handle the media finishing
- utils.on(this.player.media, 'ended', () => {
+ on.call(this.player, this.player.media, 'ended', () => {
// Show poster on end
if (this.player.isHTML5 && this.player.isVideo && this.player.config.resetOnEnd) {
// Restart
@@ -298,20 +278,28 @@ class Listeners {
});
// Check for buffer progress
- utils.on(this.player.media, 'progress playing seeking seeked', event => controls.updateProgress.call(this.player, event));
+ on.call(this.player, this.player.media, 'progress playing seeking seeked', event =>
+ controls.updateProgress.call(this.player, event),
+ );
// Handle volume changes
- utils.on(this.player.media, 'volumechange', event => controls.updateVolume.call(this.player, event));
+ on.call(this.player, this.player.media, 'volumechange', event =>
+ controls.updateVolume.call(this.player, event),
+ );
// Handle play/pause
- utils.on(this.player.media, 'playing play pause ended emptied timeupdate', event => ui.checkPlaying.call(this.player, event));
+ on.call(this.player, this.player.media, 'playing play pause ended emptied timeupdate', event =>
+ ui.checkPlaying.call(this.player, event),
+ );
// Loading state
- utils.on(this.player.media, 'waiting canplay seeked playing', event => ui.checkLoading.call(this.player, event));
+ on.call(this.player, this.player.media, 'waiting canplay seeked playing', event =>
+ ui.checkLoading.call(this.player, event),
+ );
// If autoplay, then load advertisement if required
// TODO: Show some sort of loading state while the ad manager loads else there's a delay before ad shows
- utils.on(this.player.media, 'playing', () => {
+ on.call(this.player, this.player.media, 'playing', () => {
if (!this.player.ads) {
return;
}
@@ -326,15 +314,15 @@ class Listeners {
// Click video
if (this.player.supported.ui && this.player.config.clickToPlay && !this.player.isAudio) {
// Re-fetch the wrapper
- const wrapper = utils.getElement.call(this.player, `.${this.player.config.classNames.video}`);
+ const wrapper = getElement.call(this.player, `.${this.player.config.classNames.video}`);
// Bail if there's no wrapper (this should never happen)
- if (!utils.is.element(wrapper)) {
+ if (!is.element(wrapper)) {
return;
}
// On click play, pause ore restart
- utils.on(wrapper, 'click', () => {
+ on.call(this.player, wrapper, 'click', () => {
// Touch devices will just show controls (if we're hiding controls)
if (this.player.config.hideControls && this.player.touch && !this.player.paused) {
return;
@@ -353,7 +341,8 @@ class Listeners {
// Disable right click
if (this.player.supported.ui && this.player.config.disableContextMenu) {
- utils.on(
+ on.call(
+ this.player,
this.player.elements.wrapper,
'contextmenu',
event => {
@@ -364,13 +353,13 @@ class Listeners {
}
// Volume change
- utils.on(this.player.media, 'volumechange', () => {
+ on.call(this.player, this.player.media, 'volumechange', () => {
// Save to storage
this.player.storage.set({ volume: this.player.volume, muted: this.player.muted });
});
// Speed change
- utils.on(this.player.media, 'ratechange', () => {
+ on.call(this.player, this.player.media, 'ratechange', () => {
// Update UI
controls.updateSetting.call(this.player, 'speed');
@@ -379,49 +368,29 @@ class Listeners {
});
// Quality request
- utils.on(this.player.media, 'qualityrequested', event => {
+ on.call(this.player, this.player.media, 'qualityrequested', event => {
// Save to storage
this.player.storage.set({ quality: event.detail.quality });
});
// Quality change
- utils.on(this.player.media, 'qualitychange', event => {
+ on.call(this.player, this.player.media, 'qualitychange', event => {
// Update UI
controls.updateSetting.call(this.player, 'quality', null, event.detail.quality);
});
- // Caption language change
- utils.on(this.player.media, 'languagechange', () => {
- // Update UI
- controls.updateSetting.call(this.player, 'captions');
-
- // Save to storage
- this.player.storage.set({ language: this.player.language });
- });
-
- // Captions toggle
- utils.on(this.player.media, 'captionsenabled captionsdisabled', () => {
- // Update UI
- controls.updateSetting.call(this.player, 'captions');
-
- // Save to storage
- this.player.storage.set({ captions: this.player.captions.active });
- });
-
// Proxy events to container
// Bubble up key events for Edge
- utils.on(this.player.media, this.player.config.events.concat([
- 'keyup',
- 'keydown',
- ]).join(' '), event => {
- let {detail = {}} = event;
+ const proxyEvents = this.player.config.events.concat(['keyup', 'keydown']).join(' ');
+ on.call(this.player, this.player.media, proxyEvents, event => {
+ let { detail = {} } = event;
// Get error details from media
if (event.type === 'error') {
detail = this.player.media.error;
}
- utils.dispatchEvent.call(this.player, this.player.elements.container, event.type, true, detail);
+ triggerEvent.call(this.player, this.player.elements.container, event.type, true, detail);
});
}
@@ -433,7 +402,7 @@ class Listeners {
// Run default and custom handlers
const proxy = (event, defaultHandler, customHandlerKey) => {
const customHandler = this.player.config.listeners[customHandlerKey];
- const hasCustomHandler = utils.is.function(customHandler);
+ const hasCustomHandler = is.function(customHandler);
let returned = true;
// Execute custom handler
@@ -442,33 +411,41 @@ class Listeners {
}
// Only call default handler if not prevented in custom handler
- if (returned && utils.is.function(defaultHandler)) {
+ if (returned && is.function(defaultHandler)) {
defaultHandler.call(this.player, event);
}
};
// Trigger custom and default handlers
- const on = (element, type, defaultHandler, customHandlerKey, passive = true) => {
+ const bind = (element, type, defaultHandler, customHandlerKey, passive = true) => {
const customHandler = this.player.config.listeners[customHandlerKey];
- const hasCustomHandler = utils.is.function(customHandler);
-
- utils.on(element, type, event => proxy(event, defaultHandler, customHandlerKey), passive && !hasCustomHandler);
+ const hasCustomHandler = is.function(customHandler);
+
+ on.call(
+ this.player,
+ element,
+ type,
+ event => proxy(event, defaultHandler, customHandlerKey),
+ passive && !hasCustomHandler,
+ );
};
// Play/pause toggle
- on(this.player.elements.buttons.play, 'click', this.player.togglePlay, 'play');
+ Array.from(this.player.elements.buttons.play).forEach(button => {
+ bind(button, 'click', this.player.togglePlay, 'play');
+ });
// Pause
- on(this.player.elements.buttons.restart, 'click', this.player.restart, 'restart');
+ bind(this.player.elements.buttons.restart, 'click', this.player.restart, 'restart');
// Rewind
- on(this.player.elements.buttons.rewind, 'click', this.player.rewind, 'rewind');
+ bind(this.player.elements.buttons.rewind, 'click', this.player.rewind, 'rewind');
// Rewind
- on(this.player.elements.buttons.fastForward, 'click', this.player.forward, 'fastForward');
+ bind(this.player.elements.buttons.fastForward, 'click', this.player.forward, 'fastForward');
// Mute toggle
- on(
+ bind(
this.player.elements.buttons.mute,
'click',
() => {
@@ -478,10 +455,10 @@ class Listeners {
);
// Captions toggle
- on(this.player.elements.buttons.captions, 'click', this.player.toggleCaptions);
+ bind(this.player.elements.buttons.captions, 'click', () => this.player.toggleCaptions());
// Fullscreen toggle
- on(
+ bind(
this.player.elements.buttons.fullscreen,
'click',
() => {
@@ -491,7 +468,7 @@ class Listeners {
);
// Picture-in-Picture
- on(
+ bind(
this.player.elements.buttons.pip,
'click',
() => {
@@ -501,15 +478,15 @@ class Listeners {
);
// Airplay
- on(this.player.elements.buttons.airplay, 'click', this.player.airplay, 'airplay');
+ bind(this.player.elements.buttons.airplay, 'click', this.player.airplay, 'airplay');
// Settings menu
- on(this.player.elements.buttons.settings, 'click', event => {
+ bind(this.player.elements.buttons.settings, 'click', event => {
controls.toggleMenu.call(this.player, event);
});
// Settings menu
- on(this.player.elements.settings.form, 'click', event => {
+ bind(this.player.elements.settings.form, 'click', event => {
event.stopPropagation();
// Go back to home tab on click
@@ -519,7 +496,7 @@ class Listeners {
};
// Settings menu items - use event delegation as items are added/removed
- if (utils.matches(event.target, this.player.config.selectors.inputs.language)) {
+ if (matches(event.target, this.player.config.selectors.inputs.language)) {
proxy(
event,
() => {
@@ -528,7 +505,7 @@ class Listeners {
},
'language',
);
- } else if (utils.matches(event.target, this.player.config.selectors.inputs.quality)) {
+ } else if (matches(event.target, this.player.config.selectors.inputs.quality)) {
proxy(
event,
() => {
@@ -537,7 +514,7 @@ class Listeners {
},
'quality',
);
- } else if (utils.matches(event.target, this.player.config.selectors.inputs.speed)) {
+ } else if (matches(event.target, this.player.config.selectors.inputs.speed)) {
proxy(
event,
() => {
@@ -553,14 +530,14 @@ class Listeners {
});
// Set range input alternative "value", which matches the tooltip time (#954)
- on(this.player.elements.inputs.seek, 'mousedown mousemove', event => {
+ 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
- on(this.player.elements.inputs.seek, 'mousedown mouseup keydown keyup touchstart touchend', event => {
+ 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;
@@ -573,11 +550,7 @@ class Listeners {
const play = seek.hasAttribute('play-on-seeked');
// Done seeking
- const done = [
- 'mouseup',
- 'touchend',
- 'keyup',
- ].includes(event.type);
+ const done = ['mouseup', 'touchend', 'keyup'].includes(event.type);
// If we're done seeking and it was playing, resume playback
if (play && done) {
@@ -590,7 +563,7 @@ class Listeners {
});
// Seek
- on(
+ bind(
this.player.elements.inputs.seek,
inputEvent,
event => {
@@ -599,7 +572,7 @@ class Listeners {
// If it exists, use seek-value instead of "value" for consistency with tooltip time (#954)
let seekTo = seek.getAttribute('seek-value');
- if (utils.is.empty(seekTo)) {
+ if (is.empty(seekTo)) {
seekTo = seek.value;
}
@@ -612,8 +585,8 @@ class Listeners {
// Current time invert
// Only if one time element is used for both currentTime and duration
- if (this.player.config.toggleInvert && !utils.is.element(this.player.elements.display.duration)) {
- on(this.player.elements.display.currentTime, 'click', () => {
+ if (this.player.config.toggleInvert && !is.element(this.player.elements.display.duration)) {
+ bind(this.player.elements.display.currentTime, 'click', () => {
// Do nothing if we're at the start
if (this.player.currentTime === 0) {
return;
@@ -626,7 +599,7 @@ class Listeners {
}
// Volume
- on(
+ bind(
this.player.elements.inputs.volume,
inputEvent,
event => {
@@ -637,33 +610,32 @@ class Listeners {
// Polyfill for lower fill in <input type="range"> for webkit
if (browser.isWebkit) {
- on(utils.getElements.call(this.player, 'input[type="range"]'), 'input', event => {
- controls.updateRangeFill.call(this.player, event.target);
+ Array.from(getElements.call(this.player, 'input[type="range"]')).forEach(element => {
+ bind(element, 'input', event => controls.updateRangeFill.call(this.player, event.target));
});
}
// Seek tooltip
- on(this.player.elements.progress, 'mouseenter mouseleave mousemove', event => controls.updateSeekTooltip.call(this.player, event));
+ 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)
- on(this.player.elements.controls, 'mouseenter mouseleave', event => {
+ 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)
- on(this.player.elements.controls, 'mousedown mouseup touchstart touchend touchcancel', event => {
- this.player.elements.controls.pressed = [
- 'mousedown',
- 'touchstart',
- ].includes(event.type);
+ 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
- on(this.player.elements.controls, 'focusin focusout', event => {
+ bind(this.player.elements.controls, 'focusin focusout', event => {
const { config, elements, timers } = this.player;
// Skip transition to prevent focus from scrolling the parent element
- utils.toggleClass(elements.controls, config.classNames.noTransition, event.type === 'focusin');
+ toggleClass(elements.controls, config.classNames.noTransition, event.type === 'focusin');
// Toggle
ui.toggleControls.call(this.player, event.type === 'focusin');
@@ -672,7 +644,7 @@ class Listeners {
if (event.type === 'focusin') {
// Restore transition
setTimeout(() => {
- utils.toggleClass(elements.controls, config.classNames.noTransition, false);
+ toggleClass(elements.controls, config.classNames.noTransition, false);
}, 0);
// Delay a little more for keyboard users
@@ -686,7 +658,7 @@ class Listeners {
});
// Mouse wheel for volume
- on(
+ bind(
this.player.elements.inputs.volume,
'wheel',
event => {
@@ -719,7 +691,10 @@ class Listeners {
}
// Don't break page scrolling at max and min
- if ((direction === 1 && this.player.media.volume < 1) || (direction === -1 && this.player.media.volume > 0)) {
+ if (
+ (direction === 1 && this.player.media.volume < 1) ||
+ (direction === -1 && this.player.media.volume > 0)
+ ) {
event.preventDefault();
}
},
@@ -727,11 +702,6 @@ class Listeners {
false,
);
}
-
- // Reset on destroy
- clear() {
- this.global(false);
- }
}
export default Listeners;