diff options
Diffstat (limited to 'src/js')
-rw-r--r-- | src/js/captions.js | 31 | ||||
-rw-r--r-- | src/js/console.js | 28 | ||||
-rw-r--r-- | src/js/controls.js | 49 | ||||
-rw-r--r-- | src/js/fullscreen.js | 4 | ||||
-rw-r--r-- | src/js/listeners.js | 17 | ||||
-rw-r--r-- | src/js/media.js | 4 | ||||
-rw-r--r-- | src/js/plugins/vimeo.js | 2 | ||||
-rw-r--r-- | src/js/plyr.js | 105 | ||||
-rw-r--r-- | src/js/source.js | 7 | ||||
-rw-r--r-- | src/js/storage.js | 99 | ||||
-rw-r--r-- | src/js/support.js | 19 | ||||
-rw-r--r-- | src/js/ui.js | 28 | ||||
-rw-r--r-- | src/js/utils.js | 93 |
13 files changed, 251 insertions, 235 deletions
diff --git a/src/js/captions.js b/src/js/captions.js index 512099d3..037de128 100644 --- a/src/js/captions.js +++ b/src/js/captions.js @@ -5,7 +5,6 @@ import support from './support'; import utils from './utils'; import controls from './controls'; -import storage from './storage'; const captions = { // Setup captions @@ -16,18 +15,24 @@ const captions = { } // Set default language if not set - if (!utils.is.empty(storage.get.call(this).language)) { - this.captions.language = storage.get.call(this).language; - } else if (utils.is.empty(this.captions.language)) { + const stored = this.storage.get('language'); + + if (!utils.is.empty(stored)) { + this.captions.language = stored; + } + + if (utils.is.empty(this.captions.language)) { this.captions.language = this.config.captions.language.toLowerCase(); } // Set captions enabled state if not set - if (!utils.is.boolean(this.captions.enabled)) { - if (!utils.is.empty(storage.get.call(this).language)) { - this.captions.enabled = storage.get.call(this).captions; + if (!utils.is.boolean(this.captions.active)) { + const active = this.storage.get('captions'); + + if (utils.is.boolean(active)) { + this.captions.active = active; } else { - this.captions.enabled = this.config.captions.active; + this.captions.active = this.config.captions.active; } } @@ -42,7 +47,7 @@ const captions = { } // Inject the container - if (!utils.is.htmlElement(this.elements.captions)) { + if (!utils.is.element(this.elements.captions)) { this.elements.captions = utils.createElement('div', utils.getAttributesFromSelector(this.config.selectors.captions)); utils.insertAfter(this.elements.captions, this.elements.wrapper); @@ -141,7 +146,7 @@ const captions = { return; } - if (utils.is.htmlElement(this.elements.captions)) { + if (utils.is.element(this.elements.captions)) { const content = utils.createElement('span'); // Empty the container @@ -160,19 +165,19 @@ const captions = { // Set new caption text this.elements.captions.appendChild(content); } else { - this.console.warn('No captions element to render to'); + this.debug.warn('No captions element to render to'); } }, // Display captions container and button (for initialization) show() { // If there's no caption toggle, bail - if (!utils.is.htmlElement(this.elements.buttons.captions)) { + if (!utils.is.element(this.elements.buttons.captions)) { return; } // Try to load the value from storage - let active = storage.get.call(this).captions; + let active = this.storage.get('captions'); // Otherwise fall back to the default config if (!utils.is.boolean(active)) { diff --git a/src/js/console.js b/src/js/console.js new file mode 100644 index 00000000..c5389970 --- /dev/null +++ b/src/js/console.js @@ -0,0 +1,28 @@ +// ========================================================================== +// Console wrapper +// ========================================================================== + +const noop = () => {}; + +export default class Console { + constructor(player) { + this.enabled = window.console && player.config.debug; + + if (this.enabled) { + this.log('Debugging enabled'); + } + } + + get log() { + // eslint-disable-next-line no-console + return this.enabled ? Function.prototype.bind.call(console.log, console) : noop; + } + get warn() { + // eslint-disable-next-line no-console + return this.enabled ? Function.prototype.bind.call(console.warn, console) : noop; + } + get error() { + // eslint-disable-next-line no-console + return this.enabled ? Function.prototype.bind.call(console.error, console) : noop; + } +} diff --git a/src/js/controls.js b/src/js/controls.js index 7b1eada6..1bbe10d3 100644 --- a/src/js/controls.js +++ b/src/js/controls.js @@ -22,12 +22,12 @@ const controls = { const range = utils.is.event(target) ? target.target : target; // Needs to be a valid <input type='range'> - if (!utils.is.htmlElement(range) || range.getAttribute('type') !== 'range') { + if (!utils.is.element(range) || range.getAttribute('type') !== 'range') { return; } // Inject the stylesheet if needed - if (!utils.is.htmlElement(this.elements.styleSheet)) { + if (!utils.is.element(this.elements.styleSheet)) { this.elements.styleSheet = utils.createElement('style'); this.elements.container.appendChild(this.elements.styleSheet); } @@ -322,7 +322,7 @@ const controls = { // Create time display createTime(type) { - const container = utils.createElement('span', { + const container = utils.createElement('div', { class: 'plyr__time', }); @@ -368,7 +368,7 @@ const controls = { label.appendChild(faux); label.insertAdjacentHTML('beforeend', title); - if (utils.is.htmlElement(badge)) { + if (utils.is.element(badge)) { label.appendChild(badge); } @@ -381,8 +381,8 @@ const controls = { // Bail if setting not true if ( !this.config.tooltips.seek || - !utils.is.htmlElement(this.elements.inputs.seek) || - !utils.is.htmlElement(this.elements.display.seekTooltip) || + !utils.is.element(this.elements.inputs.seek) || + !utils.is.element(this.elements.display.seekTooltip) || this.duration === 0 ) { return; @@ -542,7 +542,7 @@ const controls = { switch (setting) { case 'captions': - value = this.captions.enabled ? this.captions.language : ''; + value = this.captions.active ? this.captions.language : ''; break; default: @@ -555,13 +555,13 @@ const controls = { // Unsupported value if (!this.options[setting].includes(value)) { - this.console.warn(`Unsupported value of '${value}' for ${setting}`); + this.debug.warn(`Unsupported value of '${value}' for ${setting}`); return; } // Disabled value if (!this.config[setting].options.includes(value)) { - this.console.warn(`Disabled value of '${value}' for ${setting}`); + this.debug.warn(`Disabled value of '${value}' for ${setting}`); return; } @@ -569,7 +569,7 @@ const controls = { } // Get the list if we need to - if (!utils.is.htmlElement(list)) { + if (!utils.is.element(list)) { list = pane && pane.querySelector('ul'); } @@ -582,7 +582,7 @@ const controls = { // Find the radio option const target = list && list.querySelector(`input[value="${value}"]`); - if (utils.is.htmlElement(target)) { + if (utils.is.element(target)) { // Check it target.checked = true; } @@ -638,7 +638,7 @@ const controls = { return this.config.i18n.none; } - if (this.captions.enabled) { + if (this.captions.active) { const currentTrack = captions.getCurrentTrack.call(this); if (utils.is.track(currentTrack)) { @@ -736,10 +736,10 @@ const controls = { toggleMenu(event) { const { form } = this.elements.settings; const button = this.elements.buttons.settings; - const show = utils.is.boolean(event) ? event : utils.is.htmlElement(form) && form.getAttribute('aria-hidden') === 'true'; + const show = utils.is.boolean(event) ? event : utils.is.element(form) && form.getAttribute('aria-hidden') === 'true'; if (utils.is.event(event)) { - const isMenuItem = utils.is.htmlElement(form) && form.contains(event.target); + const isMenuItem = utils.is.element(form) && form.contains(event.target); const isButton = event.target === this.elements.buttons.settings; // If the click was inside the form or if the click @@ -756,11 +756,11 @@ const controls = { } // Set form and button attributes - if (utils.is.htmlElement(button)) { + if (utils.is.element(button)) { button.setAttribute('aria-expanded', show); } - if (utils.is.htmlElement(form)) { + if (utils.is.element(form)) { form.setAttribute('aria-hidden', !show); utils.toggleClass(this.elements.container, this.config.classNames.menu.open, show); @@ -809,7 +809,7 @@ const controls = { const pane = document.getElementById(tab.getAttribute('aria-controls')); // Nothing to show, bail - if (!utils.is.htmlElement(pane)) { + if (!utils.is.element(pane)) { return; } @@ -908,7 +908,7 @@ const controls = { // Progress if (this.config.controls.includes('progress')) { - const progress = utils.createElement('span', utils.getAttributesFromSelector(this.config.selectors.progress)); + const progress = utils.createElement('div', utils.getAttributesFromSelector(this.config.selectors.progress)); // Seek range slider const seek = controls.createRange.call(this, 'seek', { @@ -958,7 +958,7 @@ const controls = { // Volume range control if (this.config.controls.includes('volume')) { - const volume = utils.createElement('span', { + const volume = utils.createElement('div', { class: 'plyr__volume', }); @@ -1186,22 +1186,27 @@ const controls = { } // Inject into the container by default - if (!utils.is.htmlElement(target)) { + if (!utils.is.element(target)) { target = this.elements.container; } // Inject controls HTML - if (utils.is.htmlElement(container)) { + if (utils.is.element(container)) { target.appendChild(container); } else { target.insertAdjacentHTML('beforeend', container); } // Find the elements if need be - if (utils.is.htmlElement(this.elements.controls)) { + if (utils.is.element(this.elements.controls)) { utils.findElements.call(this); } + // Edge sometimes doesn't finish the paint so force a redraw + if (window.navigator.userAgent.includes('Edge')) { + utils.repaint(target); + } + // Setup tooltips if (this.config.tooltips.controls) { const labels = utils.getElements.call( diff --git a/src/js/fullscreen.js b/src/js/fullscreen.js index ac65c2bf..ac7d5fac 100644 --- a/src/js/fullscreen.js +++ b/src/js/fullscreen.js @@ -100,12 +100,12 @@ const fullscreen = { const nativeSupport = fullscreen.enabled; if (nativeSupport || (this.config.fullscreen.fallback && !utils.inFrame())) { - this.console.log(`${nativeSupport ? 'Native' : 'Fallback'} fullscreen enabled`); + this.debug.log(`${nativeSupport ? 'Native' : 'Fallback'} fullscreen enabled`); // Add styling hook to show button utils.toggleClass(this.elements.container, this.config.classNames.fullscreen.enabled, true); } else { - this.console.log('Fullscreen not supported and fallback disabled'); + this.debug.log('Fullscreen not supported and fallback disabled'); } // Toggle state diff --git a/src/js/listeners.js b/src/js/listeners.js index 186dd70d..72f7d42f 100644 --- a/src/js/listeners.js +++ b/src/js/listeners.js @@ -6,7 +6,6 @@ import support from './support'; import utils from './utils'; import controls from './controls'; import fullscreen from './fullscreen'; -import storage from './storage'; import ui from './ui'; // Sniff out the browser @@ -53,7 +52,7 @@ const listeners = { // 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.htmlElement(focused) && utils.matches(focused, this.config.selectors.editable)) { + if (utils.is.element(focused) && utils.matches(focused, this.config.selectors.editable)) { return; } @@ -248,7 +247,7 @@ const listeners = { const wrapper = utils.getElement.call(this, `.${this.config.classNames.video}`); // Bail if there's no wrapper (this should never happen) - if (!utils.is.htmlElement(wrapper)) { + if (!utils.is.element(wrapper)) { return; } @@ -285,7 +284,7 @@ const listeners = { // Volume change utils.on(this.media, 'volumechange', () => { // Save to storage - storage.set.call(this, { volume: this.volume, muted: this.muted }); + this.storage.set({ volume: this.volume, muted: this.muted }); }); // Speed change @@ -294,7 +293,7 @@ const listeners = { controls.updateSetting.call(this, 'speed'); // Save to storage - storage.set.call(this, { speed: this.speed }); + this.storage.set({ speed: this.speed }); }); // Quality change @@ -303,7 +302,7 @@ const listeners = { controls.updateSetting.call(this, 'quality'); // Save to storage - storage.set.call(this, { quality: this.quality }); + this.storage.set({ quality: this.quality }); }); // Caption language change @@ -312,7 +311,7 @@ const listeners = { controls.updateSetting.call(this, 'captions'); // Save to storage - storage.set.call(this, { language: this.language }); + this.storage.set({ language: this.language }); }); // Captions toggle @@ -321,7 +320,7 @@ const listeners = { controls.updateSetting.call(this, 'captions'); // Save to storage - storage.set.call(this, { captions: this.captions.enabled }); + this.storage.set({ captions: this.captions.active }); }); // Proxy events to container @@ -462,7 +461,7 @@ const listeners = { // Current time invert // Only if one time element is used for both currentTime and duration - if (this.config.toggleInvert && !utils.is.htmlElement(this.elements.display.duration)) { + if (this.config.toggleInvert && !utils.is.element(this.elements.display.duration)) { utils.on(this.elements.display.currentTime, 'click', () => { // Do nothing if we're at the start if (this.currentTime === 0) { diff --git a/src/js/media.js b/src/js/media.js index 9eea9a5d..4019c1a7 100644 --- a/src/js/media.js +++ b/src/js/media.js @@ -16,7 +16,7 @@ const media = { setup() { // If there's no media, bail if (!this.media) { - this.console.warn('No media element found!'); + this.debug.warn('No media element found!'); return; } @@ -99,7 +99,7 @@ const media = { this.media.load(); // Debugging - this.console.log('Cancelled network requests'); + this.debug.log('Cancelled network requests'); }, }; diff --git a/src/js/plugins/vimeo.js b/src/js/plugins/vimeo.js index 4ac2ce99..6176583b 100644 --- a/src/js/plugins/vimeo.js +++ b/src/js/plugins/vimeo.js @@ -242,7 +242,7 @@ const vimeo = { }); player.embed.on('loaded', () => { - if (utils.is.htmlElement(player.embed.element) && player.supported.ui) { + if (utils.is.element(player.embed.element) && player.supported.ui) { const frame = player.embed.element; // Fix keyboard focus issues diff --git a/src/js/plyr.js b/src/js/plyr.js index 86ad0ba0..a482f41f 100644 --- a/src/js/plyr.js +++ b/src/js/plyr.js @@ -10,15 +10,21 @@ import defaults from './defaults'; import support from './support'; import utils from './utils'; +import Console from './console'; +import Storage from './storage'; + import captions from './captions'; import controls from './controls'; import fullscreen from './fullscreen'; import listeners from './listeners'; import media from './media'; -import storage from './storage'; import source from './source'; import ui from './ui'; +// Private properties +// TODO: Use a WeakMap for private globals +// const globals = new WeakMap(); + // Globals let scrollPosition = { x: 0, @@ -54,7 +60,7 @@ class Plyr { try { return JSON.parse(this.media.getAttribute('data-plyr-config')); } catch (e) { - return null; + return {}; } })() ); @@ -76,7 +82,7 @@ class Plyr { // Captions this.captions = { - enabled: null, + active: null, currentTrack: null, }; @@ -92,46 +98,35 @@ class Plyr { }; // Debugging - this.console = { - log() {}, - warn() {}, - error() {}, - }; - if (this.config.debug && 'console' in window) { - this.console = { - log: console.log, // eslint-disable-line - warn: console.warn, // eslint-disable-line - error: console.error, // eslint-disable-line - }; - this.console.log('Debugging enabled'); - } + // TODO: move to globals + this.debug = new Console(this); // Log config options and support - this.console.log('Config', this.config); - this.console.log('Support', support); + this.debug.log('Config', this.config); + this.debug.log('Support', support); // We need an element to setup - if (utils.is.nullOrUndefined(this.media) || !utils.is.htmlElement(this.media)) { - this.console.error('Setup failed: no suitable element passed'); + if (utils.is.nullOrUndefined(this.media) || !utils.is.element(this.media)) { + this.debug.error('Setup failed: no suitable element passed'); return; } // Bail if the element is initialized if (this.media.plyr) { - this.console.warn('Target already setup'); + this.debug.warn('Target already setup'); return; } // Bail if not enabled if (!this.config.enabled) { - this.console.error('Setup failed: disabled by config'); + this.debug.error('Setup failed: disabled by config'); return; } // Bail if disabled or no basic support // You may want to disable certain UAs etc if (!support.check().api) { - this.console.error('Setup failed: no support'); + this.debug.error('Setup failed: no support'); return; } @@ -158,13 +153,13 @@ class Plyr { this.embedId = this.media.getAttribute(attributes.id); if (utils.is.empty(this.provider) || !Object.keys(providers).includes(this.provider)) { - this.console.error('Setup failed: Invalid provider'); + this.debug.error('Setup failed: Invalid provider'); return; } // Try and get the embed id if (utils.is.empty(this.embedId)) { - this.console.error('Setup failed: Embed ID or URL missing'); + this.debug.error('Setup failed: Embed ID or URL missing'); return; } @@ -202,19 +197,19 @@ class Plyr { break; default: - this.console.error('Setup failed: unsupported type'); + this.debug.error('Setup failed: unsupported type'); return; } // Setup local storage for user settings - storage.setup.call(this); + this.storage = new Storage(this); // Check for support again but with type this.supported = support.check(this.type, this.provider, this.config.inline); // If no support for even API, bail if (!this.supported.api) { - this.console.error('Setup failed: no support'); + this.debug.error('Setup failed: no support'); return; } @@ -240,7 +235,7 @@ class Plyr { // Listen for events if debugging if (this.config.debug) { utils.on(this.elements.container, this.config.events.join(' '), event => { - this.console.log(`event: ${event.type}`); + this.debug.log(`event: ${event.type}`); }); } @@ -386,7 +381,7 @@ class Plyr { this.media.currentTime = targetTime.toFixed(4); // Logging - this.console.log(`Seeking to ${this.currentTime} seconds`); + this.debug.log(`Seeking to ${this.currentTime} seconds`); } /** @@ -432,7 +427,7 @@ class Plyr { // Load volume from storage if no value specified if (!utils.is.number(volume)) { - ({ volume } = storage.get.call(this)); + volume = this.storage.get('volume'); } // Use config if all else fails @@ -497,7 +492,7 @@ class Plyr { // Load muted state from storage if (!utils.is.boolean(toggle)) { - toggle = storage.get.call(this).muted; + toggle = this.storage.get('muted'); } // Use config if all else fails @@ -541,9 +536,13 @@ class Plyr { if (utils.is.number(input)) { speed = input; - } else if (utils.is.number(storage.get.call(this).speed)) { - ({ speed } = storage.get.call(this)); - } else { + } + + if (!utils.is.number(speed)) { + speed = this.storage.get('speed'); + } + + if (!utils.is.number(speed)) { speed = this.config.speed.selected; } @@ -556,7 +555,7 @@ class Plyr { } if (!this.config.speed.options.includes(speed)) { - this.console.warn(`Unsupported speed (${speed})`); + this.debug.warn(`Unsupported speed (${speed})`); return; } @@ -584,14 +583,18 @@ class Plyr { if (utils.is.string(input)) { quality = input; - } else if (utils.is.number(storage.get.call(this).quality)) { - ({ quality } = storage.get.call(this)); - } else { + } + + if (!utils.is.string(quality)) { + quality = this.storage.get('quality'); + } + + if (!utils.is.string(quality)) { quality = this.config.quality.selected; } if (!this.options.quality.includes(quality)) { - this.console.warn(`Unsupported quality option (${quality})`); + this.debug.warn(`Unsupported quality option (${quality})`); return; } @@ -691,7 +694,7 @@ class Plyr { */ set poster(input) { if (!this.isHTML5 || !this.isVideo) { - this.console.warn('Poster can only be set on HTML5 video'); + this.debug.warn('Poster can only be set on HTML5 video'); return; } @@ -733,7 +736,7 @@ class Plyr { */ toggleCaptions(input) { // If there's no full support, or there's no caption toggle - if (!this.supported.ui || !utils.is.htmlElement(this.elements.buttons.captions)) { + if (!this.supported.ui || !utils.is.element(this.elements.buttons.captions)) { return this; } @@ -741,21 +744,21 @@ class Plyr { const show = utils.is.boolean(input) ? input : this.elements.container.className.indexOf(this.config.classNames.captions.active) === -1; // Nothing to change... - if (this.captions.enabled === show) { + if (this.captions.active === show) { return this; } // Set global - this.captions.enabled = show; + this.captions.active = show; // Toggle state - utils.toggleState(this.elements.buttons.captions, this.captions.enabled); + utils.toggleState(this.elements.buttons.captions, this.captions.active); // Add class hook - utils.toggleClass(this.elements.container, this.config.classNames.captions.active, this.captions.enabled); + utils.toggleClass(this.elements.container, this.config.classNames.captions.active, this.captions.active); // Trigger an event - utils.dispatchEvent.call(this, this.media, this.captions.enabled ? 'captionsenabled' : 'captionsdisabled'); + utils.dispatchEvent.call(this, this.media, this.captions.active ? 'captionsenabled' : 'captionsdisabled'); // Allow chaining return this; @@ -850,7 +853,7 @@ class Plyr { } // Set button state - if (utils.is.htmlElement(this.elements.buttons.fullscreen)) { + if (utils.is.element(this.elements.buttons.fullscreen)) { utils.toggleState(this.elements.buttons.fullscreen, this.fullscreen.active); } @@ -916,7 +919,7 @@ class Plyr { */ toggleControls(toggle) { // We need controls of course... - if (!utils.is.htmlElement(this.elements.controls)) { + if (!utils.is.element(this.elements.controls)) { return this; } @@ -981,7 +984,7 @@ class Plyr { // then set the timer to hide the controls if (!show || this.playing) { this.timers.controls = window.setTimeout(() => { - /* this.console.warn({ + /* this.debug.warn({ pressed: this.elements.controls.pressed, hover: this.elements.controls.pressed, playing: this.playing, @@ -1088,7 +1091,7 @@ class Plyr { // Replace the container with the original element provided const parent = this.elements.container.parentNode; - if (utils.is.htmlElement(parent)) { + if (utils.is.element(parent)) { parent.replaceChild(this.elements.original, this.elements.container); } diff --git a/src/js/source.js b/src/js/source.js index cbea5433..80620bdf 100644 --- a/src/js/source.js +++ b/src/js/source.js @@ -26,7 +26,7 @@ const source = { // Sources are not checked for support so be careful change(input) { if (!utils.is.object(input) || !('sources' in input) || !input.sources.length) { - this.console.warn('Invalid source format'); + this.debug.warn('Invalid source format'); return; } @@ -44,7 +44,7 @@ const source = { this.media = null; // Reset class name - if (utils.is.htmlElement(this.elements.container)) { + if (utils.is.element(this.elements.container)) { this.elements.container.removeAttribute('class'); } @@ -105,8 +105,7 @@ const source = { } } - // Restore class hooks - utils.toggleClass(this.elements.container, this.config.classNames.captions.active, this.supported.ui && this.captions.enabled); + // Restore class hook ui.addStyleHook.call(this); // Set new sources for html5 diff --git a/src/js/storage.js b/src/js/storage.js index ff4222ad..f876f107 100644 --- a/src/js/storage.js +++ b/src/js/storage.js @@ -2,74 +2,65 @@ // Plyr storage // ========================================================================== -import support from './support'; import utils from './utils'; -// Get contents of local storage -function get() { - const store = window.localStorage.getItem(this.config.storage.key); - - if (utils.is.empty(store)) { - return {}; +class Storage { + constructor(player) { + this.enabled = player.config.storage.enabled; + this.key = player.config.storage.key; } - return JSON.parse(store); -} - -// Save a value back to local storage -function set(object) { - // Bail if we don't have localStorage support or it's disabled - if (!support.storage || !this.config.storage.enabled) { - return; + // Check for actual support (see if we can use it) + static get supported() { + if (!('localStorage' in window)) { + return false; + } + + const test = '___test'; + + // Try to use it (it might be disabled, e.g. user is in private mode) + // see: https://github.com/sampotts/plyr/issues/131 + try { + window.localStorage.setItem(test, test); + window.localStorage.removeItem(test); + return true; + } catch (e) { + return false; + } } - // Can only store objectst - if (!utils.is.object(object)) { - return; - } + get(key) { + const store = window.localStorage.getItem(this.key); - // Get current storage - const storage = get.call(this); + if (!Storage.supported || utils.is.empty(store)) { + return null; + } - // Update the working copy of the values - utils.extend(storage, object); + const json = JSON.parse(store); - // Update storage - window.localStorage.setItem(this.config.storage.key, JSON.stringify(storage)); -} + return utils.is.string(key) && key.length ? json[key] : json; + } -// Setup localStorage -function setup() { - let value = null; - let storage = {}; + set(object) { + // Bail if we don't have localStorage support or it's disabled + if (!Storage.supported || !this.enabled) { + return; + } - // Bail if we don't have localStorage support or it's disabled - if (!support.storage || !this.config.storage.enabled) { - return storage; - } + // Can only store objectst + if (!utils.is.object(object)) { + return; + } - // Clean up old volume - // https://github.com/sampotts/plyr/issues/171 - window.localStorage.removeItem('plyr-volume'); + // Get current storage + const storage = this.get(); - // load value from the current key - value = window.localStorage.getItem(this.config.storage.key); + // Update the working copy of the values + utils.extend(storage, object); - if (!value) { - // Key wasn't set (or had been cleared), move along - } else if (/^\d+(\.\d+)?$/.test(value)) { - // If value is a number, it's probably volume from an older - // version of this. See: https://github.com/sampotts/plyr/pull/313 - // Update the key to be JSON - set({ - volume: parseFloat(value), - }); - } else { - // Assume it's JSON from this or a later version of plyr - storage = JSON.parse(value); + // Update storage + window.localStorage.setItem(this.key, JSON.stringify(storage)); } - - return storage; } -export default { setup, set, get }; +export default Storage; diff --git a/src/js/support.js b/src/js/support.js index fc61bbf6..f75517d6 100644 --- a/src/js/support.js +++ b/src/js/support.js @@ -50,25 +50,6 @@ const support = { }; }, - // Local storage - // We can't assume if local storage is present that we can use it - storage: (() => { - if (!('localStorage' in window)) { - return false; - } - - // Try to use it (it might be disabled, e.g. user is in private/porn mode) - // see: https://github.com/sampotts/plyr/issues/131 - const test = '___test'; - try { - window.localStorage.setItem(test, test); - window.localStorage.removeItem(test); - return true; - } catch (e) { - return false; - } - })(), - // Picture-in-picture support // Safari only currently pip: (() => { diff --git a/src/js/ui.js b/src/js/ui.js index 5fb723d6..80ec587e 100644 --- a/src/js/ui.js +++ b/src/js/ui.js @@ -31,7 +31,7 @@ const ui = { // Don't setup interface if no support if (!this.supported.ui) { - this.console.warn(`Basic support only for ${this.provider} ${this.type}`); + this.debug.warn(`Basic support only for ${this.provider} ${this.type}`); // Remove controls utils.removeElement.call(this, 'controls'); @@ -47,7 +47,7 @@ const ui = { } // Inject custom controls if not present - if (!utils.is.htmlElement(this.elements.controls)) { + if (!utils.is.element(this.elements.controls)) { // Inject custom controls controls.inject.call(this); @@ -56,7 +56,7 @@ const ui = { } // If there's no controls, bail - if (!utils.is.htmlElement(this.elements.controls)) { + if (!utils.is.element(this.elements.controls)) { return; } @@ -125,7 +125,7 @@ const ui = { if (this.isEmbed) { const iframe = utils.getElement.call(this, 'iframe'); - if (!utils.is.htmlElement(iframe)) { + if (!utils.is.element(iframe)) { return; } @@ -175,19 +175,19 @@ const ui = { } // Update range - if (utils.is.htmlElement(this.elements.inputs.volume)) { + if (utils.is.element(this.elements.inputs.volume)) { ui.setRange.call(this, this.elements.inputs.volume, this.muted ? 0 : this.volume); } // Update mute state - if (utils.is.htmlElement(this.elements.buttons.mute)) { + if (utils.is.element(this.elements.buttons.mute)) { utils.toggleState(this.elements.buttons.mute, this.muted || this.volume === 0); } }, // Update seek value and lower fill setRange(target, value = 0) { - if (!utils.is.htmlElement(target)) { + if (!utils.is.element(target)) { return; } @@ -201,15 +201,15 @@ const ui = { // Set <progress> value setProgress(target, input) { const value = utils.is.number(input) ? input : 0; - const progress = utils.is.htmlElement(target) ? target : this.elements.display.buffer; + const progress = utils.is.element(target) ? target : this.elements.display.buffer; // Update value and label - if (utils.is.htmlElement(progress)) { + if (utils.is.element(progress)) { progress.value = value; // Update text label inside const label = progress.getElementsByTagName('span')[0]; - if (utils.is.htmlElement(label)) { + if (utils.is.element(label)) { label.childNodes[0].nodeValue = value; } } @@ -267,7 +267,7 @@ const ui = { // Update the displayed time updateTimeDisplay(target = null, time = 0, inverted = false) { // Bail if there's no element to display or the value isn't a number - if (!utils.is.htmlElement(target) || !utils.is.number(time)) { + if (!utils.is.element(target) || !utils.is.number(time)) { return; } @@ -299,7 +299,7 @@ const ui = { // Handle time change event timeUpdate(event) { // Only invert if only one time element is displayed and used for both duration and currentTime - const invert = !utils.is.htmlElement(this.elements.display.duration) && this.config.invertTime; + const invert = !utils.is.element(this.elements.display.duration) && this.config.invertTime; // Duration ui.updateTimeDisplay.call(this, this.elements.display.currentTime, invert ? this.duration - this.currentTime : this.currentTime, invert); @@ -320,12 +320,12 @@ const ui = { } // If there's only one time display, display duration there - if (!utils.is.htmlElement(this.elements.display.duration) && this.config.displayDuration && this.paused) { + if (!utils.is.element(this.elements.display.duration) && this.config.displayDuration && this.paused) { ui.updateTimeDisplay.call(this, this.elements.display.currentTime, this.duration); } // If there's a duration element, update content - if (utils.is.htmlElement(this.elements.display.duration)) { + if (utils.is.element(this.elements.display.duration)) { ui.updateTimeDisplay.call(this, this.elements.display.duration, this.duration); } diff --git a/src/js/utils.js b/src/js/utils.js index b3ce509b..a0a551c4 100644 --- a/src/js/utils.js +++ b/src/js/utils.js @@ -7,6 +7,9 @@ import support from './support'; const utils = { // Check variable types is: { + plyr(input) { + return this.instanceof(input, Plyr); + }, object(input) { return this.getConstructor(input) === Object; }, @@ -25,23 +28,26 @@ const utils = { array(input) { return !this.nullOrUndefined(input) && Array.isArray(input); }, + weakMap(input) { + return this.instanceof(input, WeakMap); + }, nodeList(input) { - return this.instanceof(input, window.NodeList); + return this.instanceof(input, NodeList); }, - htmlElement(input) { - return this.instanceof(input, window.HTMLElement); + element(input) { + return this.instanceof(input, Element); }, textNode(input) { return this.getConstructor(input) === Text; }, event(input) { - return this.instanceof(input, window.Event); + return this.instanceof(input, Event); }, cue(input) { - return this.instanceof(input, window.TextTrackCue) || this.instanceof(input, window.VTTCue); + return this.instanceof(input, TextTrackCue) || this.instanceof(input, VTTCue); }, track(input) { - return this.instanceof(input, window.TextTrack) || (!this.nullOrUndefined(input) && this.string(input.kind)); + return this.instanceof(input, TextTrack) || (!this.nullOrUndefined(input) && this.string(input.kind)); }, nullOrUndefined(input) { return input === null || typeof input === 'undefined'; @@ -249,7 +255,7 @@ const utils = { // Remove an element removeElement(element) { - if (!utils.is.htmlElement(element) || !utils.is.htmlElement(element.parentNode)) { + if (!utils.is.element(element) || !utils.is.element(element.parentNode)) { return null; } @@ -270,6 +276,10 @@ const utils = { // Set attributes setAttributes(element, attributes) { + if (!utils.is.element(element) || utils.is.empty(attributes)) { + return; + } + Object.keys(attributes).forEach(key => { element.setAttribute(key, attributes[key]); }); @@ -334,7 +344,7 @@ const utils = { // Toggle class on an element toggleClass(element, className, toggle) { - if (utils.is.htmlElement(element)) { + if (utils.is.element(element)) { const contains = element.classList.contains(className); element.classList[toggle ? 'add' : 'remove'](className); @@ -347,12 +357,12 @@ const utils = { // Has class name hasClass(element, className) { - return utils.is.htmlElement(element) && element.classList.contains(className); + return utils.is.element(element) && element.classList.contains(className); }, // Toggle hidden attribute on an element toggleHidden(element, toggle) { - if (!utils.is.htmlElement(element)) { + if (!utils.is.element(element)) { return; } @@ -424,14 +434,14 @@ const utils = { }; // Seek tooltip - if (utils.is.htmlElement(this.elements.progress)) { + if (utils.is.element(this.elements.progress)) { this.elements.display.seekTooltip = this.elements.progress.querySelector(`.${this.config.classNames.tooltip}`); } return true; } catch (error) { // Log it - this.console.warn('It looks like there is a problem with your custom controls HTML', error); + this.debug.warn('It looks like there is a problem with your custom controls HTML', error); // Restore native video controls this.toggleNativeControls(true); @@ -560,7 +570,7 @@ const utils = { // http://www.ssbbartgroup.com/blog/how-not-to-misuse-aria-states-properties-and-roles toggleState(element, input) { // Bail if no target - if (!utils.is.htmlElement(element)) { + if (!utils.is.element(element)) { return; } @@ -580,45 +590,31 @@ const utils = { return (current / max * 100).toFixed(2); }, - // Deep extend/merge destination object with N more objects - // http://andrewdupont.net/2009/08/28/deep-extending-objects-in-javascript/ - // Removed call to arguments.callee (used explicit function name instead) - extend(...objects) { - const { length } = objects; - - // Bail if nothing to merge - if (!length) { - return null; + // Deep extend destination object with N more objects + extend(target = {}, ...sources) { + if (!sources.length) { + return target; } - // Return first if specified but nothing to merge - if (length === 1) { - return objects[0]; - } + const source = sources.shift(); - // First object is the destination - let destination = Array.prototype.shift.call(objects); - if (!utils.is.object(destination)) { - destination = {}; + if (!utils.is.object(source)) { + return target; } - // Loop through all objects to merge - objects.forEach(source => { - if (!utils.is.object(source)) { - return; - } - - Object.keys(source).forEach(property => { - if (source[property] && source[property].constructor && source[property].constructor === Object) { - destination[property] = destination[property] || {}; - utils.extend(destination[property], source[property]); - } else { - destination[property] = source[property]; + Object.keys(source).forEach(key => { + if (utils.is.object(source[key])) { + if (!Object.keys(target).includes(key)) { + Object.assign(target, { [key]: {} }); } - }); + + utils.extend(target[key], source[key]); + } else { + Object.assign(target, { [key]: source[key] }); + } }); - return destination; + return utils.extend(target, ...sources); }, // Parse YouTube ID from URL @@ -679,6 +675,15 @@ const utils = { return typeof type === 'string' ? type : false; })(), + + // Force repaint of element + repaint(element) { + window.setTimeout(() => { + element.setAttribute('hidden', ''); + element.offsetHeight; // eslint-disable-line + element.removeAttribute('hidden'); + }, 0); + }, }; export default utils; |