remove event listeners in destroy, add once method
This commit is contained in:
		@ -80,7 +80,7 @@ const captions = {
 | 
			
		||||
        // Watch changes to textTracks and update captions menu
 | 
			
		||||
        if (this.isHTML5) {
 | 
			
		||||
            const trackEvents = this.config.captions.update ? 'addtrack removetrack' : 'removetrack';
 | 
			
		||||
            utils.on(this.media.textTracks, trackEvents, captions.update.bind(this));
 | 
			
		||||
            utils.on.call(this, this.media.textTracks, trackEvents, captions.update.bind(this));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Update available languages in list next tick (the event must not be triggered before the listeners)
 | 
			
		||||
@ -107,7 +107,7 @@ const captions = {
 | 
			
		||||
                    track.mode = 'hidden';
 | 
			
		||||
 | 
			
		||||
                    // Add event listener for cue changes
 | 
			
		||||
                    utils.on(track, 'cuechange', () => captions.updateCues.call(this));
 | 
			
		||||
                    utils.on.call(this, track, 'cuechange', () => captions.updateCues.call(this));
 | 
			
		||||
                });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										4
									
								
								src/js/controls.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								src/js/controls.js
									
									
									
									
										vendored
									
									
								
							@ -1063,12 +1063,10 @@ const controls = {
 | 
			
		||||
                container.style.width = '';
 | 
			
		||||
                container.style.height = '';
 | 
			
		||||
 | 
			
		||||
                // Only listen once
 | 
			
		||||
                utils.off(container, utils.transitionEndEvent, restore);
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            // Listen for the transition finishing and restore auto height/width
 | 
			
		||||
            utils.on(container, utils.transitionEndEvent, restore);
 | 
			
		||||
            utils.once(container, utils.transitionEndEvent, restore);
 | 
			
		||||
 | 
			
		||||
            // Set dimensions to target
 | 
			
		||||
            container.style.width = `${size.width}px`;
 | 
			
		||||
 | 
			
		||||
@ -62,13 +62,13 @@ class Fullscreen {
 | 
			
		||||
 | 
			
		||||
        // Register event listeners
 | 
			
		||||
        // Handle event (incase user presses escape etc)
 | 
			
		||||
        utils.on(document, this.prefix === 'ms' ? 'MSFullscreenChange' : `${this.prefix}fullscreenchange`, () => {
 | 
			
		||||
        utils.on.call(this.player, 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', event => {
 | 
			
		||||
        utils.on.call(this.player, this.player.elements.container, 'dblclick', event => {
 | 
			
		||||
            // Ignore double click in controls
 | 
			
		||||
            if (utils.is.element(this.player.elements.controls) && this.player.elements.controls.contains(event.target)) {
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
@ -63,9 +63,8 @@ const html5 = {
 | 
			
		||||
                // Restore time
 | 
			
		||||
                const onLoadedMetaData = () => {
 | 
			
		||||
                    player.currentTime = currentTime;
 | 
			
		||||
                    player.off('loadedmetadata', onLoadedMetaData);
 | 
			
		||||
                };
 | 
			
		||||
                player.on('loadedmetadata', onLoadedMetaData);
 | 
			
		||||
                player.once('loadedmetadata', onLoadedMetaData);
 | 
			
		||||
 | 
			
		||||
                // Load new source
 | 
			
		||||
                player.media.load();
 | 
			
		||||
 | 
			
		||||
@ -197,39 +197,36 @@ class Listeners {
 | 
			
		||||
        // Add touch class
 | 
			
		||||
        utils.toggleClass(this.player.elements.container, this.player.config.classNames.isTouch, true);
 | 
			
		||||
 | 
			
		||||
        // Clean up
 | 
			
		||||
        utils.off(document.body, 'touchstart', this.firstTouch);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Global window & document listeners
 | 
			
		||||
    global(toggle = true) {
 | 
			
		||||
        // Keyboard shortcuts
 | 
			
		||||
        if (this.player.config.keyboard.global) {
 | 
			
		||||
            utils.toggleListener(window, 'keydown keyup', this.handleKey, toggle, false);
 | 
			
		||||
            utils.toggleListener.call(this.player, window, 'keydown keyup', this.handleKey, toggle, false);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Click anywhere closes menu
 | 
			
		||||
        utils.toggleListener(document.body, 'click', this.toggleMenu, toggle);
 | 
			
		||||
        utils.toggleListener.call(this.player, document.body, 'click', this.toggleMenu, toggle);
 | 
			
		||||
 | 
			
		||||
        // Detect touch by events
 | 
			
		||||
        utils.on(document.body, 'touchstart', this.firstTouch);
 | 
			
		||||
        utils.once(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);
 | 
			
		||||
            utils.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.on.call(this.player, this.player.elements.container, 'focusout', event => {
 | 
			
		||||
            utils.toggleClass(event.target, this.player.config.classNames.tabFocus, false);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // Add classname to tabbed elements
 | 
			
		||||
        utils.on(this.player.elements.container, 'keydown', event => {
 | 
			
		||||
        utils.on.call(this.player, this.player.elements.container, 'keydown', event => {
 | 
			
		||||
            if (event.keyCode !== 9) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
@ -242,7 +239,7 @@ class Listeners {
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // Toggle controls on mouse events and entering fullscreen
 | 
			
		||||
        utils.on(this.player.elements.container, 'mousemove mouseleave touchstart touchmove enterfullscreen exitfullscreen', event => {
 | 
			
		||||
        utils.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
 | 
			
		||||
@ -276,20 +273,20 @@ class Listeners {
 | 
			
		||||
    // Listen for media events
 | 
			
		||||
    media() {
 | 
			
		||||
        // Time change on media
 | 
			
		||||
        utils.on(this.player.media, 'timeupdate seeking seeked', event => controls.timeUpdate.call(this.player, event));
 | 
			
		||||
        utils.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));
 | 
			
		||||
        utils.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.on.call(this.player, this.player.media, 'loadeddata', () => {
 | 
			
		||||
            utils.toggleHidden(this.player.elements.volume, !this.player.hasAudio);
 | 
			
		||||
            utils.toggleHidden(this.player.elements.buttons.mute, !this.player.hasAudio);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // Handle the media finishing
 | 
			
		||||
        utils.on(this.player.media, 'ended', () => {
 | 
			
		||||
        utils.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 +295,20 @@ class Listeners {
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // Check for buffer progress
 | 
			
		||||
        utils.on(this.player.media, 'progress playing seeking seeked', event => controls.updateProgress.call(this.player, event));
 | 
			
		||||
        utils.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));
 | 
			
		||||
        utils.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));
 | 
			
		||||
        utils.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));
 | 
			
		||||
        utils.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', () => {
 | 
			
		||||
        utils.on.call(this.player, this.player.media, 'playing', () => {
 | 
			
		||||
            if (!this.player.ads) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
@ -334,7 +331,7 @@ class Listeners {
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // On click play, pause ore restart
 | 
			
		||||
            utils.on(wrapper, 'click', () => {
 | 
			
		||||
            utils.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 +350,7 @@ class Listeners {
 | 
			
		||||
 | 
			
		||||
        // Disable right click
 | 
			
		||||
        if (this.player.supported.ui && this.player.config.disableContextMenu) {
 | 
			
		||||
            utils.on(
 | 
			
		||||
            utils.on.call(this.player,
 | 
			
		||||
                this.player.elements.wrapper,
 | 
			
		||||
                'contextmenu',
 | 
			
		||||
                event => {
 | 
			
		||||
@ -364,13 +361,13 @@ class Listeners {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Volume change
 | 
			
		||||
        utils.on(this.player.media, 'volumechange', () => {
 | 
			
		||||
        utils.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', () => {
 | 
			
		||||
        utils.on.call(this.player, this.player.media, 'ratechange', () => {
 | 
			
		||||
            // Update UI
 | 
			
		||||
            controls.updateSetting.call(this.player, 'speed');
 | 
			
		||||
 | 
			
		||||
@ -379,19 +376,19 @@ class Listeners {
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // Quality request
 | 
			
		||||
        utils.on(this.player.media, 'qualityrequested', event => {
 | 
			
		||||
        utils.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 => {
 | 
			
		||||
        utils.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', () => {
 | 
			
		||||
        utils.on.call(this.player, this.player.media, 'languagechange', () => {
 | 
			
		||||
            // Update UI
 | 
			
		||||
            controls.updateSetting.call(this.player, 'captions');
 | 
			
		||||
 | 
			
		||||
@ -400,7 +397,7 @@ class Listeners {
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // Captions toggle
 | 
			
		||||
        utils.on(this.player.media, 'captionsenabled captionsdisabled', () => {
 | 
			
		||||
        utils.on.call(this.player, this.player.media, 'captionsenabled captionsdisabled', () => {
 | 
			
		||||
            // Update UI
 | 
			
		||||
            controls.updateSetting.call(this.player, 'captions');
 | 
			
		||||
 | 
			
		||||
@ -410,7 +407,7 @@ class Listeners {
 | 
			
		||||
 | 
			
		||||
        // Proxy events to container
 | 
			
		||||
        // Bubble up key events for Edge
 | 
			
		||||
        utils.on(this.player.media, this.player.config.events.concat([
 | 
			
		||||
        utils.on.call(this.player, this.player.media, this.player.config.events.concat([
 | 
			
		||||
            'keyup',
 | 
			
		||||
            'keydown',
 | 
			
		||||
        ]).join(' '), event => {
 | 
			
		||||
@ -452,7 +449,7 @@ class Listeners {
 | 
			
		||||
            const customHandler = this.player.config.listeners[customHandlerKey];
 | 
			
		||||
            const hasCustomHandler = utils.is.function(customHandler);
 | 
			
		||||
 | 
			
		||||
            utils.on(element, type, event => proxy(event, defaultHandler, customHandlerKey), passive && !hasCustomHandler);
 | 
			
		||||
            utils.on.call(this.player, element, type, event => proxy(event, defaultHandler, customHandlerKey), passive && !hasCustomHandler);
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        // Play/pause toggle
 | 
			
		||||
@ -727,11 +724,6 @@ class Listeners {
 | 
			
		||||
            false,
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Reset on destroy
 | 
			
		||||
    clear() {
 | 
			
		||||
        this.global(false);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default Listeners;
 | 
			
		||||
 | 
			
		||||
@ -245,6 +245,8 @@ class Plyr {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.eventListeners = [];
 | 
			
		||||
 | 
			
		||||
        // Create listeners
 | 
			
		||||
        this.listeners = new Listeners(this);
 | 
			
		||||
 | 
			
		||||
@ -271,7 +273,7 @@ class Plyr {
 | 
			
		||||
 | 
			
		||||
        // Listen for events if debugging
 | 
			
		||||
        if (this.config.debug) {
 | 
			
		||||
            utils.on(this.elements.container, this.config.events.join(' '), event => {
 | 
			
		||||
            utils.on.call(this, this.elements.container, this.config.events.join(' '), event => {
 | 
			
		||||
                this.debug.log(`event: ${event.type}`);
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
@ -961,9 +963,16 @@ class Plyr {
 | 
			
		||||
     * @param {function} callback - Callback for when event occurs
 | 
			
		||||
     */
 | 
			
		||||
    on(event, callback) {
 | 
			
		||||
        utils.on(this.elements.container, event, callback);
 | 
			
		||||
        utils.on.call(this, this.elements.container, event, callback);
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * Add event listeners once
 | 
			
		||||
     * @param {string} event - Event type
 | 
			
		||||
     * @param {function} callback - Callback for when event occurs
 | 
			
		||||
     */
 | 
			
		||||
    once(event, callback) {
 | 
			
		||||
        utils.once(this.elements.container, event, callback);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Remove event listeners
 | 
			
		||||
     * @param {string} event - Event type
 | 
			
		||||
@ -1014,8 +1023,7 @@ class Plyr {
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                // Unbind listeners
 | 
			
		||||
                this.listeners.clear();
 | 
			
		||||
 | 
			
		||||
                utils.cleanupEventListeners.call(this);
 | 
			
		||||
                // Replace the container with the original element provided
 | 
			
		||||
                utils.replaceElement(this.elements.original, this.elements.container);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -494,14 +494,14 @@ const utils = {
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        if (toggle) {
 | 
			
		||||
            utils.on(this.elements.container, 'keydown', trap, false);
 | 
			
		||||
            utils.on.call(this, this.elements.container, 'keydown', trap, false);
 | 
			
		||||
        } else {
 | 
			
		||||
            utils.off(this.elements.container, 'keydown', trap, false);
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    // Toggle event listener
 | 
			
		||||
    toggleListener(elements, event, callback, toggle = false, passive = true, capture = false) {
 | 
			
		||||
    toggleListener(elements, event, callback, toggle = false, passive = true, capture = false, once = false) {
 | 
			
		||||
        // Bail if no elemetns, event, or callback
 | 
			
		||||
        if (utils.is.empty(elements) || utils.is.empty(event) || !utils.is.function(callback)) {
 | 
			
		||||
            return;
 | 
			
		||||
@ -512,7 +512,7 @@ const utils = {
 | 
			
		||||
            // Create listener for each node
 | 
			
		||||
            Array.from(elements).forEach(element => {
 | 
			
		||||
                if (element instanceof Node) {
 | 
			
		||||
                    utils.toggleListener.call(null, element, event, callback, toggle, passive, capture);
 | 
			
		||||
                    utils.toggleListener.call(this, element, event, callback, toggle, passive, capture, once);
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
@ -538,13 +538,35 @@ const utils = {
 | 
			
		||||
 | 
			
		||||
        // If a single node is passed, bind the event listener
 | 
			
		||||
        events.forEach(type => {
 | 
			
		||||
            if (this && this.eventListeners && toggle && !once) {
 | 
			
		||||
                // cache event listener
 | 
			
		||||
                this.eventListeners.push({ elements, type, callback, options });
 | 
			
		||||
            }
 | 
			
		||||
            elements[toggle ? 'addEventListener' : 'removeEventListener'](type, callback, options);
 | 
			
		||||
        });
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    // remove all cached event listeners
 | 
			
		||||
    cleanupEventListeners() {
 | 
			
		||||
        if (this && this.eventListeners) {
 | 
			
		||||
            this.eventListeners.forEach(item => {
 | 
			
		||||
                const { elements, type, callback, options } = item;
 | 
			
		||||
                elements.removeEventListener(type, callback, options);
 | 
			
		||||
            });
 | 
			
		||||
            this.eventListeners = [];
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    // Bind event handler
 | 
			
		||||
    on(element, events = '', callback, passive = true, capture = false) {
 | 
			
		||||
        utils.toggleListener(element, events, callback, true, passive, capture);
 | 
			
		||||
        utils.toggleListener.call(this, element, events, callback, true, passive, capture);
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    // Bind event handler once
 | 
			
		||||
    once(element, events = '', callback, passive = true, capture = false) {
 | 
			
		||||
        function onceCallback(...args) {
 | 
			
		||||
            utils.off(element, events, onceCallback, passive, capture);
 | 
			
		||||
            callback.apply(this, args);
 | 
			
		||||
        }
 | 
			
		||||
        utils.toggleListener(element, events, onceCallback, true, passive, capture, true);
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    // Unbind event handler
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user