remove event listeners in destroy, add once method

This commit is contained in:
cky 2018-06-12 20:55:31 +08:00
parent 0b09b8ee6f
commit 87170ab460
8 changed files with 73 additions and 53 deletions

View File

@ -367,6 +367,7 @@ player.fullscreen.enter(); // Enter fullscreen
| `airplay()` | - | Trigger the airplay dialog on supported devices. | | `airplay()` | - | Trigger the airplay dialog on supported devices. |
| `toggleControls(toggle)` | Boolean | Toggle the controls (video only). Takes optional truthy value to force it on/off. | | `toggleControls(toggle)` | Boolean | Toggle the controls (video only). Takes optional truthy value to force it on/off. |
| `on(event, function)` | String, Function | Add an event listener for the specified event. | | `on(event, function)` | String, Function | Add an event listener for the specified event. |
| `once(event, function)` | String, Function | Add an event listener for the specified event once. |
| `off(event, function)` | String, Function | Remove an event listener for the specified event. | | `off(event, function)` | String, Function | Remove an event listener for the specified event. |
| `supports(type)` | String | Check support for a mime type. | | `supports(type)` | String | Check support for a mime type. |
| `destroy()` | - | Destroy the instance and garbage collect any elements. | | `destroy()` | - | Destroy the instance and garbage collect any elements. |

View File

@ -80,7 +80,7 @@ const captions = {
// Watch changes to textTracks and update captions menu // Watch changes to textTracks and update captions menu
if (this.isHTML5) { if (this.isHTML5) {
const trackEvents = this.config.captions.update ? 'addtrack removetrack' : 'removetrack'; 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) // 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'; track.mode = 'hidden';
// Add event listener for cue changes // 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
View File

@ -1063,12 +1063,10 @@ const controls = {
container.style.width = ''; container.style.width = '';
container.style.height = ''; container.style.height = '';
// Only listen once
utils.off(container, utils.transitionEndEvent, restore);
}; };
// Listen for the transition finishing and restore auto height/width // 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 // Set dimensions to target
container.style.width = `${size.width}px`; container.style.width = `${size.width}px`;

View File

@ -62,13 +62,13 @@ class Fullscreen {
// Register event listeners // Register event listeners
// Handle event (incase user presses escape etc) // 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?? // TODO: Filter for target??
onChange.call(this); onChange.call(this);
}); });
// Fullscreen toggle on double click // 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 // Ignore double click in controls
if (utils.is.element(this.player.elements.controls) && this.player.elements.controls.contains(event.target)) { if (utils.is.element(this.player.elements.controls) && this.player.elements.controls.contains(event.target)) {
return; return;

View File

@ -63,9 +63,8 @@ const html5 = {
// Restore time // Restore time
const onLoadedMetaData = () => { const onLoadedMetaData = () => {
player.currentTime = currentTime; player.currentTime = currentTime;
player.off('loadedmetadata', onLoadedMetaData);
}; };
player.on('loadedmetadata', onLoadedMetaData); player.once('loadedmetadata', onLoadedMetaData);
// Load new source // Load new source
player.media.load(); player.media.load();

View File

@ -197,39 +197,36 @@ class Listeners {
// Add touch class // Add touch class
utils.toggleClass(this.player.elements.container, this.player.config.classNames.isTouch, true); 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 window & document listeners
global(toggle = true) { global(toggle = true) {
// Keyboard shortcuts // Keyboard shortcuts
if (this.player.config.keyboard.global) { 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 // 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 // Detect touch by events
utils.on(document.body, 'touchstart', this.firstTouch); utils.once(document.body, 'touchstart', this.firstTouch);
} }
// Container listeners // Container listeners
container() { container() {
// Keyboard shortcuts // Keyboard shortcuts
if (!this.player.config.keyboard.global && this.player.config.keyboard.focused) { 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 // Detect tab focus
// Remove class on blur/focusout // 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); utils.toggleClass(event.target, this.player.config.classNames.tabFocus, false);
}); });
// Add classname to tabbed elements // 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) { if (event.keyCode !== 9) {
return; return;
} }
@ -242,7 +239,7 @@ class Listeners {
}); });
// Toggle controls on mouse events and entering fullscreen // 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; const { controls } = this.player.elements;
// Remove button states for fullscreen // Remove button states for fullscreen
@ -276,20 +273,20 @@ class Listeners {
// Listen for media events // Listen for media events
media() { media() {
// Time change on 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 // 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 // Check for audio tracks on load
// We can't use `loadedmetadata` as it doesn't seem to have audio tracks at that point // 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.volume, !this.player.hasAudio);
utils.toggleHidden(this.player.elements.buttons.mute, !this.player.hasAudio); utils.toggleHidden(this.player.elements.buttons.mute, !this.player.hasAudio);
}); });
// Handle the media finishing // Handle the media finishing
utils.on(this.player.media, 'ended', () => { utils.on.call(this.player, this.player.media, 'ended', () => {
// Show poster on end // Show poster on end
if (this.player.isHTML5 && this.player.isVideo && this.player.config.resetOnEnd) { if (this.player.isHTML5 && this.player.isVideo && this.player.config.resetOnEnd) {
// Restart // Restart
@ -298,20 +295,20 @@ class Listeners {
}); });
// Check for buffer progress // 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 // 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 // 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 // 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 // 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 // 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) { if (!this.player.ads) {
return; return;
} }
@ -334,7 +331,7 @@ class Listeners {
} }
// On click play, pause ore restart // 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) // Touch devices will just show controls (if we're hiding controls)
if (this.player.config.hideControls && this.player.touch && !this.player.paused) { if (this.player.config.hideControls && this.player.touch && !this.player.paused) {
return; return;
@ -353,7 +350,7 @@ class Listeners {
// Disable right click // Disable right click
if (this.player.supported.ui && this.player.config.disableContextMenu) { if (this.player.supported.ui && this.player.config.disableContextMenu) {
utils.on( utils.on.call(this.player,
this.player.elements.wrapper, this.player.elements.wrapper,
'contextmenu', 'contextmenu',
event => { event => {
@ -364,13 +361,13 @@ class Listeners {
} }
// Volume change // Volume change
utils.on(this.player.media, 'volumechange', () => { utils.on.call(this.player, this.player.media, 'volumechange', () => {
// Save to storage // Save to storage
this.player.storage.set({ volume: this.player.volume, muted: this.player.muted }); this.player.storage.set({ volume: this.player.volume, muted: this.player.muted });
}); });
// Speed change // Speed change
utils.on(this.player.media, 'ratechange', () => { utils.on.call(this.player, this.player.media, 'ratechange', () => {
// Update UI // Update UI
controls.updateSetting.call(this.player, 'speed'); controls.updateSetting.call(this.player, 'speed');
@ -379,19 +376,19 @@ class Listeners {
}); });
// Quality request // Quality request
utils.on(this.player.media, 'qualityrequested', event => { utils.on.call(this.player, this.player.media, 'qualityrequested', event => {
// Save to storage // Save to storage
this.player.storage.set({ quality: event.detail.quality }); this.player.storage.set({ quality: event.detail.quality });
}); });
// Quality change // Quality change
utils.on(this.player.media, 'qualitychange', event => { utils.on.call(this.player, this.player.media, 'qualitychange', event => {
// Update UI // Update UI
controls.updateSetting.call(this.player, 'quality', null, event.detail.quality); controls.updateSetting.call(this.player, 'quality', null, event.detail.quality);
}); });
// Caption language change // Caption language change
utils.on(this.player.media, 'languagechange', () => { utils.on.call(this.player, this.player.media, 'languagechange', () => {
// Update UI // Update UI
controls.updateSetting.call(this.player, 'captions'); controls.updateSetting.call(this.player, 'captions');
@ -400,7 +397,7 @@ class Listeners {
}); });
// Captions toggle // Captions toggle
utils.on(this.player.media, 'captionsenabled captionsdisabled', () => { utils.on.call(this.player, this.player.media, 'captionsenabled captionsdisabled', () => {
// Update UI // Update UI
controls.updateSetting.call(this.player, 'captions'); controls.updateSetting.call(this.player, 'captions');
@ -410,7 +407,7 @@ class Listeners {
// Proxy events to container // Proxy events to container
// Bubble up key events for Edge // 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', 'keyup',
'keydown', 'keydown',
]).join(' '), event => { ]).join(' '), event => {
@ -452,7 +449,7 @@ class Listeners {
const customHandler = this.player.config.listeners[customHandlerKey]; const customHandler = this.player.config.listeners[customHandlerKey];
const hasCustomHandler = utils.is.function(customHandler); 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 // Play/pause toggle
@ -727,11 +724,6 @@ class Listeners {
false, false,
); );
} }
// Reset on destroy
clear() {
this.global(false);
}
} }
export default Listeners; export default Listeners;

View File

@ -245,6 +245,8 @@ class Plyr {
return; return;
} }
this.eventListeners = [];
// Create listeners // Create listeners
this.listeners = new Listeners(this); this.listeners = new Listeners(this);
@ -271,7 +273,7 @@ class Plyr {
// Listen for events if debugging // Listen for events if debugging
if (this.config.debug) { 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}`); this.debug.log(`event: ${event.type}`);
}); });
} }
@ -961,9 +963,16 @@ class Plyr {
* @param {function} callback - Callback for when event occurs * @param {function} callback - Callback for when event occurs
*/ */
on(event, callback) { 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 * Remove event listeners
* @param {string} event - Event type * @param {string} event - Event type
@ -1014,8 +1023,7 @@ class Plyr {
} }
} else { } else {
// Unbind listeners // Unbind listeners
this.listeners.clear(); utils.cleanupEventListeners.call(this);
// Replace the container with the original element provided // Replace the container with the original element provided
utils.replaceElement(this.elements.original, this.elements.container); utils.replaceElement(this.elements.original, this.elements.container);

View File

@ -494,14 +494,14 @@ const utils = {
}; };
if (toggle) { if (toggle) {
utils.on(this.elements.container, 'keydown', trap, false); utils.on.call(this, this.elements.container, 'keydown', trap, false);
} else { } else {
utils.off(this.elements.container, 'keydown', trap, false); utils.off(this.elements.container, 'keydown', trap, false);
} }
}, },
// Toggle event listener // 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 // Bail if no elemetns, event, or callback
if (utils.is.empty(elements) || utils.is.empty(event) || !utils.is.function(callback)) { if (utils.is.empty(elements) || utils.is.empty(event) || !utils.is.function(callback)) {
return; return;
@ -512,7 +512,7 @@ const utils = {
// Create listener for each node // Create listener for each node
Array.from(elements).forEach(element => { Array.from(elements).forEach(element => {
if (element instanceof Node) { 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 // If a single node is passed, bind the event listener
events.forEach(type => { 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); 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 // Bind event handler
on(element, events = '', callback, passive = true, capture = false) { 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 // Unbind event handler