diff options
author | Sam Potts <sam@potts.es> | 2018-06-13 00:41:30 +1000 |
---|---|---|
committer | Sam Potts <sam@potts.es> | 2018-06-13 00:41:30 +1000 |
commit | aae1092bacd464a074ee26feab6f371903e83d90 (patch) | |
tree | f64843714e6511c843a5597c4fed8ebea9474bfd /src/js | |
parent | 392dfd024c505f5ae1bbb2f0d3e0793c251a1f35 (diff) | |
parent | 7158e507adb11e187380f3148750435f2bd611ae (diff) | |
download | plyr-aae1092bacd464a074ee26feab6f371903e83d90.tar.lz plyr-aae1092bacd464a074ee26feab6f371903e83d90.tar.xz plyr-aae1092bacd464a074ee26feab6f371903e83d90.zip |
Merge branch 'develop' of github.com:sampotts/plyr into develop
# Conflicts:
# src/js/captions.js
# src/js/controls.js
# src/js/fullscreen.js
# src/js/html5.js
# src/js/listeners.js
# src/js/plugins/youtube.js
# src/js/plyr.js
# src/js/utils.js
Diffstat (limited to 'src/js')
-rw-r--r-- | src/js/captions.js | 10 | ||||
-rw-r--r-- | src/js/controls.js | 12 | ||||
-rw-r--r-- | src/js/fullscreen.js | 8 | ||||
-rw-r--r-- | src/js/html5.js | 85 | ||||
-rw-r--r-- | src/js/listeners.js | 64 | ||||
-rw-r--r-- | src/js/plugins/ads.js | 4 | ||||
-rw-r--r-- | src/js/plugins/vimeo.js | 34 | ||||
-rw-r--r-- | src/js/plugins/youtube.js | 113 | ||||
-rw-r--r-- | src/js/plyr.js | 66 | ||||
-rw-r--r-- | src/js/ui.js | 4 | ||||
-rw-r--r-- | src/js/utils/events.js | 35 |
11 files changed, 190 insertions, 245 deletions
diff --git a/src/js/captions.js b/src/js/captions.js index 0506d1e6..6682d6f0 100644 --- a/src/js/captions.js +++ b/src/js/captions.js @@ -8,7 +8,7 @@ import i18n from './i18n'; import support from './support'; import browser from './utils/browser'; import { createElement, emptyElement, getAttributesFromSelector, insertAfter, removeElement, toggleClass } from './utils/elements'; -import { on, trigger } from './utils/events'; +import { on, triggerEvent } from './utils/events'; import fetch from './utils/fetch'; import is from './utils/is'; import { getHTML } from './utils/strings'; @@ -82,7 +82,7 @@ const captions = { // Watch changes to textTracks and update captions menu if (this.isHTML5) { const trackEvents = this.config.captions.update ? 'addtrack removetrack' : 'removetrack'; - on(this.media.textTracks, trackEvents, captions.update.bind(this)); + 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 - on(track, 'cuechange', () => captions.updateCues.call(this)); + on.call(this, track, 'cuechange', () => captions.updateCues.call(this)); }); } @@ -166,7 +166,7 @@ const captions = { } // Trigger event - trigger.call(this, this.media, 'languagechange'); + triggerEvent.call(this, this.media, 'languagechange'); } if (this.isHTML5 && this.isVideo) { @@ -280,7 +280,7 @@ const captions = { this.elements.captions.appendChild(caption); // Trigger event - trigger.call(this, this.media, 'cuechange'); + triggerEvent.call(this, this.media, 'cuechange'); } }, }; diff --git a/src/js/controls.js b/src/js/controls.js index cfab26bc..6d15e486 100644 --- a/src/js/controls.js +++ b/src/js/controls.js @@ -7,9 +7,10 @@ import html5 from './html5'; import i18n from './i18n'; import support from './support'; import { repaint, transitionEndEvent } from './utils/animation'; +import { dedupe } from './utils/arrays'; import browser from './utils/browser'; import { createElement, emptyElement, getAttributesFromSelector, getElement, getElements, hasClass, removeElement, setAttributes, toggleClass, toggleHidden, toggleState } from './utils/elements'; -import { off, on } from './utils/events'; +import { once } from './utils/events'; import is from './utils/is'; import loadSprite from './utils/loadSprite'; import { extend } from './utils/objects'; @@ -634,7 +635,6 @@ const controls = { }, // Set the quality menu - // TODO: Vimeo support setQualityMenu(options) { // Menu required if (!is.element(this.elements.settings.panes.quality)) { @@ -644,9 +644,9 @@ const controls = { const type = 'quality'; const list = this.elements.settings.panes.quality.querySelector('ul'); - // Set options if passed and filter based on config + // Set options if passed and filter based on uniqueness and config if (is.array(options)) { - this.options.quality = options.filter(quality => this.config.quality.options.includes(quality)); + this.options.quality = dedupe(options).filter(quality => this.config.quality.options.includes(quality)); } // Toggle the pane and tab @@ -1065,12 +1065,10 @@ const controls = { container.style.width = ''; container.style.height = ''; - // Only listen once - off(container, transitionEndEvent, restore); }; // Listen for the transition finishing and restore auto height/width - on(container, transitionEndEvent, restore); + once(container, transitionEndEvent, restore); // Set dimensions to target container.style.width = `${size.width}px`; diff --git a/src/js/fullscreen.js b/src/js/fullscreen.js index 180853c5..998dc613 100644 --- a/src/js/fullscreen.js +++ b/src/js/fullscreen.js @@ -5,7 +5,7 @@ import browser from './utils/browser'; import { hasClass, toggleClass, toggleState, trapFocus } from './utils/elements'; -import { on, trigger } from './utils/events'; +import { on, triggerEvent } from './utils/events'; import is from './utils/is'; function onChange() { @@ -20,7 +20,7 @@ function onChange() { } // Trigger an event - trigger.call(this.player, this.target, this.active ? 'enterfullscreen' : 'exitfullscreen', true); + triggerEvent.call(this.player, this.target, this.active ? 'enterfullscreen' : 'exitfullscreen', true); // Trap focus in container if (!browser.isIos) { @@ -63,13 +63,13 @@ class Fullscreen { // Register event listeners // Handle event (incase user presses escape etc) - on(document, this.prefix === 'ms' ? 'MSFullscreenChange' : `${this.prefix}fullscreenchange`, () => { + on.call(this.player, document, this.prefix === 'ms' ? 'MSFullscreenChange' : `${this.prefix}fullscreenchange`, () => { // TODO: Filter for target?? onChange.call(this); }); // Fullscreen toggle on double click - on(this.player.elements.container, 'dblclick', event => { + on.call(this.player, this.player.elements.container, 'dblclick', event => { // Ignore double click in controls if (is.element(this.player.elements.controls) && this.player.elements.controls.contains(event.target)) { return; diff --git a/src/js/html5.js b/src/js/html5.js index d13e6aa6..6aa96f4c 100644 --- a/src/js/html5.js +++ b/src/js/html5.js @@ -3,43 +3,28 @@ // ========================================================================== import support from './support'; -import { dedupe } from './utils/arrays'; import { removeElement } from './utils/elements'; -import { trigger } from './utils/events'; -import is from './utils/is'; +import { triggerEvent } from './utils/events'; const html5 = { getSources() { if (!this.isHTML5) { - return null; + return []; } - return this.media.querySelectorAll('source'); + const sources = Array.from(this.media.querySelectorAll('source')); + + // Filter out unsupported sources + return sources.filter(source => support.mime.call(this, source.getAttribute('type'))); }, // Get quality levels getQualityOptions() { - if (!this.isHTML5) { - return null; - } - - // Get sources - const sources = html5.getSources.call(this); - - if (is.empty(sources)) { - return null; - } - - // Get <source> with size attribute - const sizes = Array.from(sources).filter(source => !is.empty(source.getAttribute('size'))); - - // If none, bail - if (is.empty(sizes)) { - return null; - } - - // Reduce to unique list - return dedupe(sizes.map(source => Number(source.getAttribute('size')))); + // Get sizes from <source> elements + return html5.getSources + .call(this) + .map(source => Number(source.getAttribute('size'))) + .filter(Boolean); }, extend() { @@ -54,60 +39,34 @@ const html5 = { get() { // Get sources const sources = html5.getSources.call(player); + const [source] = sources.filter(source => source.getAttribute('src') === player.source); - if (is.empty(sources)) { - return null; - } - - const matches = Array.from(sources).filter(source => source.getAttribute('src') === player.source); - - if (is.empty(matches)) { - return null; - } - - return Number(matches[0].getAttribute('size')); + // Return size, if match is found + return source && Number(source.getAttribute('size')); }, set(input) { // Get sources const sources = html5.getSources.call(player); - if (is.empty(sources)) { - return; - } + // Get first match for requested size + const source = sources.find(source => Number(source.getAttribute('size')) === input); - // Get matches for requested size - const matches = Array.from(sources).filter(source => Number(source.getAttribute('size')) === input); - - // No matches for requested size - if (is.empty(matches)) { + // No matching source found + if (!source) { return; } - // Get supported sources - const supported = matches.filter(source => support.mime.call(player, source.getAttribute('type'))); - - // No supported sources - if (is.empty(supported)) { - return; - } - - // Trigger change event - trigger.call(player, player.media, 'qualityrequested', false, { - quality: input, - }); - // Get current state const { currentTime, playing } = player; // Set new source - player.media.src = supported[0].getAttribute('src'); + player.media.src = source.getAttribute('src'); // 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(); @@ -118,7 +77,7 @@ const html5 = { } // Trigger change event - trigger.call(player, player.media, 'qualitychange', false, { + triggerEvent.call(player, player.media, 'qualitychange', false, { quality: input, }); }, @@ -133,7 +92,7 @@ const html5 = { } // Remove child sources - removeElement(html5.getSources()); + removeElement(html5.getSources.call(this)); // Set blank video src attribute // This is to prevent a MEDIA_ERR_SRC_NOT_SUPPORTED error diff --git a/src/js/listeners.js b/src/js/listeners.js index b3cc3779..65d24277 100644 --- a/src/js/listeners.js +++ b/src/js/listeners.js @@ -6,7 +6,7 @@ import controls from './controls'; import ui from './ui'; import browser from './utils/browser'; import { getElement, getElements, getFocusElement, matches, toggleClass, toggleHidden } from './utils/elements'; -import { off, on, toggleListener, trigger } from './utils/events'; +import { on, once, toggleListener, triggerEvent } from './utils/events'; import is from './utils/is'; class Listeners { @@ -197,39 +197,36 @@ class Listeners { // Add touch class toggleClass(this.player.elements.container, this.player.config.classNames.isTouch, true); - // Clean up - off(document.body, 'touchstart', this.firstTouch); } // Global window & document listeners global(toggle = true) { // Keyboard shortcuts if (this.player.config.keyboard.global) { - toggleListener(window, 'keydown keyup', this.handleKey, toggle, false); + toggleListener.call(this.player, window, 'keydown keyup', this.handleKey, toggle, false); } // Click anywhere closes menu - toggleListener(document.body, 'click', this.toggleMenu, toggle); + toggleListener.call(this.player, document.body, 'click', this.toggleMenu, toggle); // Detect touch by events - on(document.body, 'touchstart', this.firstTouch); + once(document.body, 'touchstart', this.firstTouch); } // Container listeners container() { // Keyboard shortcuts if (!this.player.config.keyboard.global && this.player.config.keyboard.focused) { - 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 - on(this.player.elements.container, 'focusout', event => { + on.call(this.player, this.player.elements.container, 'focusout', event => { toggleClass(event.target, this.player.config.classNames.tabFocus, false); }); - // Add classname to tabbed elements - on(this.player.elements.container, 'keydown', event => { + 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 - on(this.player.elements.container, 'mousemove mouseleave touchstart touchmove enterfullscreen exitfullscreen', event => { + 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 - 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 - 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 - on(this.player.media, 'loadeddata canplay', () => { + 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 - 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 +295,20 @@ class Listeners { }); // Check for buffer progress - 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 - 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 - 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 - 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 - on(this.player.media, 'playing', () => { + 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 - 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 +350,7 @@ class Listeners { // Disable right click if (this.player.supported.ui && this.player.config.disableContextMenu) { - on( + on.call(this.player, this.player.elements.wrapper, 'contextmenu', event => { @@ -364,13 +361,13 @@ class Listeners { } // Volume change - 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 - on(this.player.media, 'ratechange', () => { + on.call(this.player, this.player.media, 'ratechange', () => { // Update UI controls.updateSetting.call(this.player, 'speed'); @@ -379,19 +376,19 @@ class Listeners { }); // Quality request - 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 - 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 - on(this.player.media, 'languagechange', () => { + on.call(this.player, this.player.media, 'languagechange', () => { // Update UI controls.updateSetting.call(this.player, 'captions'); @@ -400,7 +397,7 @@ class Listeners { }); // Captions toggle - on(this.player.media, 'captionsenabled captionsdisabled', () => { + 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 - on(this.player.media, this.player.config.events.concat([ + on.call(this.player, this.player.media, this.player.config.events.concat([ 'keyup', 'keydown', ]).join(' '), event => { @@ -421,7 +418,7 @@ class Listeners { detail = this.player.media.error; } - trigger.call(this.player, this.player.elements.container, event.type, true, detail); + triggerEvent.call(this.player, this.player.elements.container, event.type, true, detail); }); } @@ -452,7 +449,7 @@ class Listeners { const customHandler = this.player.config.listeners[customHandlerKey]; const hasCustomHandler = is.function(customHandler); - on(element, type, event => proxy(event, defaultHandler, customHandlerKey), passive && !hasCustomHandler); + 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; diff --git a/src/js/plugins/ads.js b/src/js/plugins/ads.js index 07eee58f..19df7666 100644 --- a/src/js/plugins/ads.js +++ b/src/js/plugins/ads.js @@ -8,7 +8,7 @@ import i18n from '../i18n'; import { createElement } from './../utils/elements'; -import { trigger } from './../utils/events'; +import { triggerEvent } from './../utils/events'; import is from './../utils/is'; import loadScript from './../utils/loadScript'; import { formatTime } from './../utils/time'; @@ -270,7 +270,7 @@ class Ads { // Proxy event const dispatchEvent = type => { const event = `ads${type.replace(/_/g, '').toLowerCase()}`; - trigger.call(this.player, this.player.media, event); + triggerEvent.call(this.player, this.player.media, event); }; switch (event.type) { diff --git a/src/js/plugins/vimeo.js b/src/js/plugins/vimeo.js index 76a85424..c8c09b05 100644 --- a/src/js/plugins/vimeo.js +++ b/src/js/plugins/vimeo.js @@ -6,7 +6,7 @@ import captions from './../captions'; import controls from './../controls'; import ui from './../ui'; import { createElement, replaceElement, toggleClass } from './../utils/elements'; -import { trigger } from './../utils/events'; +import { triggerEvent } from './../utils/events'; import fetch from './../utils/fetch'; import is from './../utils/is'; import loadScript from './../utils/loadScript'; @@ -41,7 +41,7 @@ function assurePlaybackState(play) { } if (this.media.paused === play) { this.media.paused = !play; - trigger.call(this, this.media, play ? 'play' : 'pause'); + triggerEvent.call(this, this.media, play ? 'play' : 'pause'); } } @@ -186,7 +186,7 @@ const vimeo = { // Set seeking state and trigger event media.seeking = true; - trigger.call(player, media, 'seeking'); + triggerEvent.call(player, media, 'seeking'); // If paused, mute until seek is complete Promise.resolve(restorePause && embed.setVolume(0)) @@ -213,7 +213,7 @@ const vimeo = { .setPlaybackRate(input) .then(() => { speed = input; - trigger.call(player, player.media, 'ratechange'); + triggerEvent.call(player, player.media, 'ratechange'); }) .catch(error => { // Hide menu item (and menu if empty) @@ -233,7 +233,7 @@ const vimeo = { set(input) { player.embed.setVolume(input).then(() => { volume = input; - trigger.call(player, player.media, 'volumechange'); + triggerEvent.call(player, player.media, 'volumechange'); }); }, }); @@ -249,7 +249,7 @@ const vimeo = { player.embed.setVolume(toggle ? 0 : player.config.volume).then(() => { muted = toggle; - trigger.call(player, player.media, 'volumechange'); + triggerEvent.call(player, player.media, 'volumechange'); }); }, }); @@ -316,13 +316,13 @@ const vimeo = { // Get current time player.embed.getCurrentTime().then(value => { currentTime = value; - trigger.call(player, player.media, 'timeupdate'); + triggerEvent.call(player, player.media, 'timeupdate'); }); // Get duration player.embed.getDuration().then(value => { player.media.duration = value; - trigger.call(player, player.media, 'durationchange'); + triggerEvent.call(player, player.media, 'durationchange'); }); // Get captions @@ -341,7 +341,7 @@ const vimeo = { player.embed.getPaused().then(paused => { assurePlaybackState.call(player, !paused); if (!paused) { - trigger.call(player, player.media, 'playing'); + triggerEvent.call(player, player.media, 'playing'); } }); @@ -356,7 +356,7 @@ const vimeo = { player.embed.on('play', () => { assurePlaybackState.call(player, true); - trigger.call(player, player.media, 'playing'); + triggerEvent.call(player, player.media, 'playing'); }); player.embed.on('pause', () => { @@ -366,16 +366,16 @@ const vimeo = { player.embed.on('timeupdate', data => { player.media.seeking = false; currentTime = data.seconds; - trigger.call(player, player.media, 'timeupdate'); + triggerEvent.call(player, player.media, 'timeupdate'); }); player.embed.on('progress', data => { player.media.buffered = data.percent; - trigger.call(player, player.media, 'progress'); + triggerEvent.call(player, player.media, 'progress'); // Check all loaded if (parseInt(data.percent, 10) === 1) { - trigger.call(player, player.media, 'canplaythrough'); + triggerEvent.call(player, player.media, 'canplaythrough'); } // Get duration as if we do it before load, it gives an incorrect value @@ -383,24 +383,24 @@ const vimeo = { player.embed.getDuration().then(value => { if (value !== player.media.duration) { player.media.duration = value; - trigger.call(player, player.media, 'durationchange'); + triggerEvent.call(player, player.media, 'durationchange'); } }); }); player.embed.on('seeked', () => { player.media.seeking = false; - trigger.call(player, player.media, 'seeked'); + triggerEvent.call(player, player.media, 'seeked'); }); player.embed.on('ended', () => { player.media.paused = true; - trigger.call(player, player.media, 'ended'); + triggerEvent.call(player, player.media, 'ended'); }); player.embed.on('error', detail => { player.media.error = detail; - trigger.call(player, player.media, 'error'); + triggerEvent.call(player, player.media, 'error'); }); // Rebuild UI diff --git a/src/js/plugins/youtube.js b/src/js/plugins/youtube.js index e486aa43..8a16726e 100644 --- a/src/js/plugins/youtube.js +++ b/src/js/plugins/youtube.js @@ -6,7 +6,7 @@ import controls from './../controls'; import ui from './../ui'; import { dedupe } from './../utils/arrays'; import { createElement, replaceElement, toggleClass } from './../utils/elements'; -import { trigger } from './../utils/events'; +import { triggerEvent } from './../utils/events'; import fetch from './../utils/fetch'; import is from './../utils/is'; import loadImage from './../utils/loadImage'; @@ -25,52 +25,25 @@ function parseId(url) { // Standardise YouTube quality unit function mapQualityUnit(input) { - switch (input) { - case 'hd2160': - return 2160; - - case 2160: - return 'hd2160'; - - case 'hd1440': - return 1440; - - case 1440: - return 'hd1440'; - - case 'hd1080': - return 1080; - - case 1080: - return 'hd1080'; - - case 'hd720': - return 720; - - case 720: - return 'hd720'; - - case 'large': - return 480; - - case 480: - return 'large'; - - case 'medium': - return 360; - - case 360: - return 'medium'; - - case 'small': - return 240; - - case 240: - return 'small'; - - default: - return 'default'; + const qualities = { + hd2160: 2160, + hd1440: 1440, + hd1080: 1080, + hd720: 720, + large: 480, + medium: 360, + small: 240, + tiny: 144, + }; + + const entry = Object.entries(qualities).find(entry => entry.includes(input)); + + if (entry) { + // Get the match corresponding to the input + return entry.find(value => value !== input); } + + return 'default'; } function mapQualityUnits(levels) { @@ -88,7 +61,7 @@ function assurePlaybackState(play) { } if (this.media.paused === play) { this.media.paused = !play; - trigger.call(this, this.media, play ? 'play' : 'pause'); + triggerEvent.call(this, this.media, play ? 'play' : 'pause'); } } @@ -266,10 +239,10 @@ const youtube = { player.media.error = detail; - trigger.call(player, player.media, 'error'); + triggerEvent.call(player, player.media, 'error'); }, onPlaybackQualityChange() { - trigger.call(player, player.media, 'qualitychange', false, { + triggerEvent.call(player, player.media, 'qualitychange', false, { quality: player.media.quality, }); }, @@ -280,7 +253,7 @@ const youtube = { // Get current speed player.media.playbackRate = instance.getPlaybackRate(); - trigger.call(player, player.media, 'ratechange'); + triggerEvent.call(player, player.media, 'ratechange'); }, onReady(event) { // Get the instance @@ -321,7 +294,7 @@ const youtube = { // Set seeking state and trigger event player.media.seeking = true; - trigger.call(player, player.media, 'seeking'); + triggerEvent.call(player, player.media, 'seeking'); // Seek after events sent instance.seekTo(time); @@ -344,15 +317,7 @@ const youtube = { return mapQualityUnit(instance.getPlaybackQuality()); }, set(input) { - const quality = input; - - // Set via API - instance.setPlaybackQuality(mapQualityUnit(quality)); - - // Trigger request event - trigger.call(player, player.media, 'qualityrequested', false, { - quality, - }); + instance.setPlaybackQuality(mapQualityUnit(input)); }, }); @@ -365,7 +330,7 @@ const youtube = { set(input) { volume = input; instance.setVolume(volume * 100); - trigger.call(player, player.media, 'volumechange'); + triggerEvent.call(player, player.media, 'volumechange'); }, }); @@ -379,7 +344,7 @@ const youtube = { const toggle = is.boolean(input) ? input : muted; muted = toggle; instance[toggle ? 'mute' : 'unMute'](); - trigger.call(player, player.media, 'volumechange'); + triggerEvent.call(player, player.media, 'volumechange'); }, }); @@ -405,8 +370,8 @@ const youtube = { player.media.setAttribute('tabindex', -1); } - trigger.call(player, player.media, 'timeupdate'); - trigger.call(player, player.media, 'durationchange'); + triggerEvent.call(player, player.media, 'timeupdate'); + triggerEvent.call(player, player.media, 'durationchange'); // Reset timer clearInterval(player.timers.buffering); @@ -418,7 +383,7 @@ const youtube = { // Trigger progress only when we actually buffer something if (player.media.lastBuffered === null || player.media.lastBuffered < player.media.buffered) { - trigger.call(player, player.media, 'progress'); + triggerEvent.call(player, player.media, 'progress'); } // Set last buffer point @@ -429,7 +394,7 @@ const youtube = { clearInterval(player.timers.buffering); // Trigger event - trigger.call(player, player.media, 'canplaythrough'); + triggerEvent.call(player, player.media, 'canplaythrough'); } }, 200); @@ -451,7 +416,7 @@ const youtube = { if (seeked) { // Unset seeking and fire seeked event player.media.seeking = false; - trigger.call(player, player.media, 'seeked'); + triggerEvent.call(player, player.media, 'seeked'); } // Handle events @@ -464,11 +429,11 @@ const youtube = { switch (event.data) { case -1: // Update scrubber - trigger.call(player, player.media, 'timeupdate'); + triggerEvent.call(player, player.media, 'timeupdate'); // Get loaded % from YouTube player.media.buffered = instance.getVideoLoadedFraction(); - trigger.call(player, player.media, 'progress'); + triggerEvent.call(player, player.media, 'progress'); break; @@ -481,7 +446,7 @@ const youtube = { instance.stopVideo(); instance.playVideo(); } else { - trigger.call(player, player.media, 'ended'); + triggerEvent.call(player, player.media, 'ended'); } break; @@ -493,11 +458,11 @@ const youtube = { } else { assurePlaybackState.call(player, true); - trigger.call(player, player.media, 'playing'); + triggerEvent.call(player, player.media, 'playing'); // Poll to get playback progress player.timers.playing = setInterval(() => { - trigger.call(player, player.media, 'timeupdate'); + triggerEvent.call(player, player.media, 'timeupdate'); }, 50); // Check duration again due to YouTube bug @@ -505,7 +470,7 @@ const youtube = { // https://code.google.com/p/gdata-issues/issues/detail?id=8690 if (player.media.duration !== instance.getDuration()) { player.media.duration = instance.getDuration(); - trigger.call(player, player.media, 'durationchange'); + triggerEvent.call(player, player.media, 'durationchange'); } // Get quality @@ -527,7 +492,7 @@ const youtube = { break; } - trigger.call(player, player.elements.container, 'statechange', false, { + triggerEvent.call(player, player.elements.container, 'statechange', false, { code: event.data, }); }, diff --git a/src/js/plyr.js b/src/js/plyr.js index 1031efb2..71619851 100644 --- a/src/js/plyr.js +++ b/src/js/plyr.js @@ -20,9 +20,9 @@ import support from './support'; import ui from './ui'; import { closest } from './utils/arrays'; import { createElement, hasClass, removeElement, replaceElement, toggleClass, toggleState, wrap } from './utils/elements'; -import { off, on, trigger } from './utils/events'; +import { off, on, once, triggerEvent, unbindListeners } from './utils/events'; import is from './utils/is'; -import loadSprite from './utils/loadScript'; +import loadSprite from './utils/loadSprite'; import { cloneDeep, extend } from './utils/objects'; import { parseUrl } from './utils/urls'; @@ -171,7 +171,7 @@ class Plyr { this.elements.container.className = ''; // Get attributes from URL and set config - if (!url.searchParams) { + if (url.searchParams.length) { const truthy = [ '1', 'true', @@ -249,6 +249,8 @@ class Plyr { return; } + this.eventListeners = []; + // Create listeners this.listeners = new Listeners(this); @@ -275,7 +277,7 @@ class Plyr { // Listen for events if debugging if (this.config.debug) { - on(this.elements.container, this.config.events.join(' '), event => { + on.call(this, this.elements.container, this.config.events.join(' '), event => { this.debug.log(`event: ${event.type}`); }); } @@ -673,36 +675,31 @@ class Plyr { * @param {number} input - Quality level */ set quality(input) { - let quality = null; - - if (!is.empty(input)) { - quality = Number(input); - } - - if (!is.number(quality)) { - quality = this.storage.get('quality'); - } - - if (!is.number(quality)) { - quality = this.config.quality.selected; - } - - if (!is.number(quality)) { - quality = this.config.quality.default; - } + const config = this.config.quality; + const options = this.options.quality; - if (!this.options.quality.length) { + if (!options.length) { return; } - if (!this.options.quality.includes(quality)) { - const value = closest(this.options.quality, quality); + let quality = ([ + !is.empty(input) && Number(input), + this.storage.get('quality'), + config.selected, + config.default, + ]).find(is.number); + + if (!options.includes(quality)) { + const value = closest(options, quality); this.debug.warn(`Unsupported quality option: ${quality}, using ${value} instead`); quality = value; } + // Trigger request event + triggerEvent.call(this, this.media, 'qualityrequested', false, { quality }); + // Update config - this.config.quality.selected = quality; + config.selected = quality; // Set quality this.media.quality = quality; @@ -853,7 +850,7 @@ class Plyr { // Update state and trigger event if (active !== this.captions.active) { this.captions.active = active; - trigger.call(this, this.media, this.captions.active ? 'captionsenabled' : 'captionsdisabled'); + triggerEvent.call(this, this.media, this.captions.active ? 'captionsenabled' : 'captionsdisabled'); } } @@ -957,7 +954,7 @@ class Plyr { // Trigger event on change if (hiding !== isHidden) { const eventName = hiding ? 'controlshidden' : 'controlsshown'; - trigger.call(this, this.media, eventName); + triggerEvent.call(this, this.media, eventName); } return !hiding; } @@ -970,9 +967,16 @@ class Plyr { * @param {function} callback - Callback for when event occurs */ on(event, callback) { - on(this.elements.container, event, callback); + 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) { + once(this.elements.container, event, callback); } - /** * Remove event listeners * @param {string} event - Event type @@ -1023,13 +1027,13 @@ class Plyr { } } else { // Unbind listeners - this.listeners.clear(); + unbindListeners.call(this); // Replace the container with the original element provided replaceElement(this.elements.original, this.elements.container); // Event - trigger.call(this, this.elements.original, 'destroyed', true); + triggerEvent.call(this, this.elements.original, 'destroyed', true); // Callback if (is.function(callback)) { diff --git a/src/js/ui.js b/src/js/ui.js index e3faf42f..d3d86124 100644 --- a/src/js/ui.js +++ b/src/js/ui.js @@ -8,7 +8,7 @@ import i18n from './i18n'; import support from './support'; import browser from './utils/browser'; import { getElement, toggleClass, toggleState } from './utils/elements'; -import { trigger } from './utils/events'; +import { triggerEvent } from './utils/events'; import is from './utils/is'; import loadImage from './utils/loadImage'; @@ -102,7 +102,7 @@ const ui = { // Ready event at end of execution stack setTimeout(() => { - trigger.call(this, this.media, 'ready'); + triggerEvent.call(this, this.media, 'ready'); }, 0); // Set the title diff --git a/src/js/utils/events.js b/src/js/utils/events.js index cb92a93c..a8e05f54 100644 --- a/src/js/utils/events.js +++ b/src/js/utils/events.js @@ -27,7 +27,7 @@ const supportsPassiveListeners = (() => { })(); // Toggle event listener -export function toggleListener(elements, event, callback, toggle = false, passive = true, capture = false) { +export function toggleListener(elements, event, callback, toggle = false, passive = true, capture = false, once = false) { // Bail if no elemetns, event, or callback if (is.empty(elements) || is.empty(event) || !is.function(callback)) { return; @@ -64,22 +64,37 @@ export function toggleListener(elements, event, callback, toggle = false, passiv // 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); }); } // Bind event handler export function on(element, events = '', callback, passive = true, capture = false) { - toggleListener(element, events, callback, true, passive, capture); + toggleListener.call(this, element, events, callback, true, passive, capture); } // Unbind event handler export function off(element, events = '', callback, passive = true, capture = false) { - toggleListener(element, events, callback, false, passive, capture); + toggleListener.call(this, element, events, callback, false, passive, capture); +} + +// Bind once-only event handler +export function once(element, events = '', callback, passive = true, capture = false) { + function onceCallback(...args) { + off(element, events, onceCallback, passive, capture); + callback.apply(this, args); + } + + toggleListener(element, events, onceCallback, true, passive, capture, true); } // Trigger event -export function trigger(element, type = '', bubbles = false, detail = {}) { +export function triggerEvent(element, type = '', bubbles = false, detail = {}) { // Bail if no element if (!is.element(element) || is.empty(type)) { return; @@ -96,3 +111,15 @@ export function trigger(element, type = '', bubbles = false, detail = {}) { // Dispatch the event element.dispatchEvent(event); } + +// Unbind all cached event listeners +export function unbindListeners() { + if (this && this.eventListeners) { + this.eventListeners.forEach(item => { + const { elements, type, callback, options } = item; + elements.removeEventListener(type, callback, options); + }); + + this.eventListeners = []; + } +} |