aboutsummaryrefslogtreecommitdiffstats
path: root/src/js
diff options
context:
space:
mode:
Diffstat (limited to 'src/js')
-rw-r--r--src/js/captions.js31
-rw-r--r--src/js/console.js28
-rw-r--r--src/js/controls.js49
-rw-r--r--src/js/fullscreen.js4
-rw-r--r--src/js/listeners.js17
-rw-r--r--src/js/media.js4
-rw-r--r--src/js/plugins/vimeo.js2
-rw-r--r--src/js/plyr.js105
-rw-r--r--src/js/source.js7
-rw-r--r--src/js/storage.js99
-rw-r--r--src/js/support.js19
-rw-r--r--src/js/ui.js28
-rw-r--r--src/js/utils.js93
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;