diff options
author | Sam Potts <sam@potts.es> | 2018-06-18 21:41:25 +1000 |
---|---|---|
committer | Sam Potts <sam@potts.es> | 2018-06-18 21:41:25 +1000 |
commit | cc3c0b54484e6f5a7b4dba8a36a44f345e462f26 (patch) | |
tree | 5fe2838546d9f981b21572dee88ee7a1c3195477 /src/js/listeners.js | |
parent | 4811e3333f1417bc9e14f6cc38afc789e9051c4c (diff) | |
parent | 3c9c1b4cdcd0eb9076c3f0bafbabb057ee140c42 (diff) | |
download | plyr-cc3c0b54484e6f5a7b4dba8a36a44f345e462f26.tar.lz plyr-cc3c0b54484e6f5a7b4dba8a36a44f345e462f26.tar.xz plyr-cc3c0b54484e6f5a7b4dba8a36a44f345e462f26.zip |
Merge branch 'develop'
Diffstat (limited to 'src/js/listeners.js')
-rw-r--r-- | src/js/listeners.js | 296 |
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; |