diff options
author | Sam Potts <sam@potts.es> | 2017-12-08 10:05:38 +0000 |
---|---|---|
committer | Sam Potts <sam@potts.es> | 2017-12-08 10:05:38 +0000 |
commit | c8990bd379d97f4eb14cc35aaa90caebfb7db220 (patch) | |
tree | b6305ac793b3f012007feacb87f23b4918dfd48b /src | |
parent | de54929bb7bfb38e5720637846d1e7f5552cdc86 (diff) | |
download | plyr-c8990bd379d97f4eb14cc35aaa90caebfb7db220.tar.lz plyr-c8990bd379d97f4eb14cc35aaa90caebfb7db220.tar.xz plyr-c8990bd379d97f4eb14cc35aaa90caebfb7db220.zip |
IE & Edge fixes, Storage & Console classes
Diffstat (limited to 'src')
-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 | ||||
-rw-r--r-- | src/less/bundle.less | 2 | ||||
-rw-r--r-- | src/less/components/captions.less | 4 | ||||
-rw-r--r-- | src/less/components/control.less (renamed from src/less/components/buttons.less) | 12 | ||||
-rw-r--r-- | src/less/components/controls.less | 2 | ||||
-rw-r--r-- | src/less/components/times.less | 12 | ||||
-rw-r--r-- | src/less/components/volume.less | 5 | ||||
-rw-r--r-- | src/less/lib/mixins.less | 2 | ||||
-rw-r--r-- | src/less/settings.less | 32 |
21 files changed, 294 insertions, 263 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; diff --git a/src/less/bundle.less b/src/less/bundle.less index ad676ec9..2ee66ebc 100644 --- a/src/less/bundle.less +++ b/src/less/bundle.less @@ -13,8 +13,8 @@ @import 'base'; @import 'components/badges'; -@import 'components/buttons'; @import 'components/captions'; +@import 'components/control'; @import 'components/controls'; @import 'components/embed'; @import 'components/menus'; diff --git a/src/less/components/captions.less b/src/less/components/captions.less index ad0fc79f..605d7e49 100644 --- a/src/less/components/captions.less +++ b/src/less/components/captions.less @@ -39,12 +39,12 @@ display: none; } - @media (min-width: @plyr-bp-screen-sm) { + @media @plyr-mq-sm { padding: (@plyr-control-spacing * 2); font-size: @plyr-font-size-captions-base; } - @media (min-width: @plyr-bp-screen-md) { + @media @plyr-mq-md { font-size: @plyr-font-size-captions-medium; } } diff --git a/src/less/components/buttons.less b/src/less/components/control.less index 8c775d73..48b73e0f 100644 --- a/src/less/components/buttons.less +++ b/src/less/components/control.less @@ -1,13 +1,15 @@ +// -------------------------------------------------------------- +// Control buttons +// -------------------------------------------------------------- + .plyr__control { position: relative; - display: inline-block; flex-shrink: 0; overflow: visible; // IE11 - vertical-align: middle; padding: @plyr-control-padding; border: 0; background: transparent; - border-radius: 3px; + border-radius: @plyr-control-radius; cursor: pointer; transition: all 0.3s ease; color: inherit; @@ -66,7 +68,7 @@ svg { position: relative; - left: 2px; + left: 2px; // Offset to make the play button look right width: @plyr-control-icon-size-large; height: @plyr-control-icon-size-large; } @@ -78,7 +80,7 @@ } .plyr--full-ui.plyr--video .plyr__control--overlaid { - display: inline-block; + display: block; } .plyr--playing .plyr__control--overlaid { diff --git a/src/less/components/controls.less b/src/less/components/controls.less index fe257224..fb66e869 100644 --- a/src/less/components/controls.less +++ b/src/less/components/controls.less @@ -30,7 +30,7 @@ margin-left: (@plyr-control-spacing / 2); } - @media (min-width: @plyr-bp-screen-sm) { + @media @plyr-mq-sm { > .plyr__control, .plyr__progress, .plyr__time, diff --git a/src/less/components/times.less b/src/less/components/times.less index 29b06aa0..09a55702 100644 --- a/src/less/components/times.less +++ b/src/less/components/times.less @@ -3,24 +3,20 @@ // -------------------------------------------------------------- .plyr__time { - display: inline-block; - vertical-align: middle; font-size: @plyr-font-size-time; } // Media duration hidden on small screens .plyr__time + .plyr__time { - display: none; - - @media (min-width: @plyr-bp-screen-md) { - display: inline-block; - } - // Add a slash in before &::before { content: '\2044'; margin-right: @plyr-control-spacing; } + + @media @plyr-mq-sm-max { + display: none; + } } .plyr--video .plyr__time { diff --git a/src/less/components/volume.less b/src/less/components/volume.less index 1c9054b7..bb5a49ba 100644 --- a/src/less/components/volume.less +++ b/src/less/components/volume.less @@ -11,12 +11,11 @@ z-index: 2; } - @media (min-width: @plyr-bp-screen-sm) { - display: block; + @media @plyr-mq-sm { max-width: 50px; } - @media (min-width: @plyr-bp-screen-md) { + @media @plyr-mq-md { max-width: 80px; } } diff --git a/src/less/lib/mixins.less b/src/less/lib/mixins.less index e58bb16e..8a3c52ab 100644 --- a/src/less/lib/mixins.less +++ b/src/less/lib/mixins.less @@ -90,7 +90,7 @@ } // Large captions in full screen on larger screens - @media (min-width: @plyr-bp-screen-lg) { + @media @plyr-mq-lg { .plyr__captions { font-size: @plyr-font-size-captions-large; } diff --git a/src/less/settings.less b/src/less/settings.less index 604fa62c..73cd10ca 100644 --- a/src/less/settings.less +++ b/src/less/settings.less @@ -42,10 +42,13 @@ @plyr-control-icon-size-large: 20px; @plyr-control-spacing: 10px; @plyr-control-padding: (@plyr-control-spacing * 0.7); +@plyr-control-radius: 3px; + @plyr-video-controls-bg: #000; @plyr-video-control-color: #fff; @plyr-video-control-color-hover: #fff; @plyr-video-control-bg-hover: @plyr-color-main; + @plyr-audio-controls-bg: #fff; @plyr-audio-control-color: @plyr-color-fiord; @plyr-audio-control-color-hover: #fff; @@ -74,20 +77,35 @@ @plyr-audio-progress-buffered-bg: fade(@plyr-color-heather, 66%); // Range sliders -@plyr-range-track-height: 8px; -@plyr-range-thumb-height: floor(@plyr-range-track-height * 2); -@plyr-range-thumb-width: floor(@plyr-range-track-height * 2); +@plyr-range-track-height: 6px; +@plyr-range-thumb-height: ceil(@plyr-range-track-height * 2.3); +@plyr-range-thumb-width: ceil(@plyr-range-track-height * 2.3); @plyr-range-thumb-bg: #fff; @plyr-range-thumb-border: 2px solid transparent; @plyr-range-thumb-shadow: 0 1px 1px fade(@plyr-video-controls-bg, 15%), 0 0 0 1px fade(@plyr-color-gunmetal, 20%); @plyr-range-thumb-active-border-color: #fff; @plyr-range-thumb-active-bg: @plyr-video-control-bg-hover; -@plyr-range-thumb-active-scale: 1.25; +@plyr-range-thumb-active-scale: 1.5; @plyr-video-range-track-bg: @plyr-video-progress-buffered-bg; @plyr-audio-range-track-bg: @plyr-audio-progress-buffered-bg; @plyr-range-selected-bg: @plyr-color-main; // Breakpoints -@plyr-bp-screen-sm: 480px; -@plyr-bp-screen-md: 768px; -@plyr-bp-screen-lg: 1024px; +@plyr-bp-sm: 480px; +@plyr-bp-md: 768px; +@plyr-bp-lg: 1024px; + +// Max-width media queries +@plyr-bp-xs-max: (@plyr-bp-sm - 1); +@plyr-bp-sm-max: (@plyr-bp-md - 1); +@plyr-bp-md-max: (@plyr-bp-lg - 1); + +// Mobile first +@plyr-mq-sm: ~'only screen and (min-width: @{plyr-bp-sm}) '; +@plyr-mq-md: ~'only screen and (min-width: @{plyr-bp-md}) '; +@plyr-mq-lg: ~'only screen and (min-width: @{plyr-bp-lg}) '; + +// Mobile last +@plyr-mq-xs-max: ~'only screen and (max-width: @{plyr-bp-xs-max}) '; +@plyr-mq-sm-max: ~'only screen and (max-width: @{plyr-bp-sm-max}) '; +@plyr-mq-md-max: ~'only screen and (max-width: @{plyr-bp-md-max}) '; |