aboutsummaryrefslogtreecommitdiffstats
path: root/src/js/plyr.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/js/plyr.js')
-rw-r--r--src/js/plyr.js246
1 files changed, 110 insertions, 136 deletions
diff --git a/src/js/plyr.js b/src/js/plyr.js
index 5885f7c0..93fccbfa 100644
--- a/src/js/plyr.js
+++ b/src/js/plyr.js
@@ -6,9 +6,10 @@
// ==========================================================================
import captions from './captions';
+import defaults from './config/defaults';
+import { getProviderByUrl, providers, types } from './config/types';
import Console from './console';
import controls from './controls';
-import defaults from './defaults';
import Fullscreen from './fullscreen';
import Listeners from './listeners';
import media from './media';
@@ -16,9 +17,14 @@ import Ads from './plugins/ads';
import source from './source';
import Storage from './storage';
import support from './support';
-import { providers, types } from './types';
import ui from './ui';
-import utils from './utils';
+import { closest } from './utils/arrays';
+import { createElement, hasClass, removeElement, replaceElement, toggleClass, wrap } from './utils/elements';
+import { off, on, once, triggerEvent, unbindListeners } from './utils/events';
+import is from './utils/is';
+import loadSprite from './utils/loadSprite';
+import { cloneDeep, extend } from './utils/objects';
+import { parseUrl } from './utils/urls';
// Private properties
// TODO: Use a WeakMap for private globals
@@ -41,18 +47,18 @@ class Plyr {
this.media = target;
// String selector passed
- if (utils.is.string(this.media)) {
+ if (is.string(this.media)) {
this.media = document.querySelectorAll(this.media);
}
// jQuery, NodeList or Array passed, use first element
- if ((window.jQuery && this.media instanceof jQuery) || utils.is.nodeList(this.media) || utils.is.array(this.media)) {
+ if ((window.jQuery && this.media instanceof jQuery) || is.nodeList(this.media) || is.array(this.media)) {
// eslint-disable-next-line
this.media = this.media[0];
}
// Set config
- this.config = utils.extend(
+ this.config = extend(
{},
defaults,
Plyr.defaults,
@@ -108,7 +114,7 @@ class Plyr {
this.debug.log('Support', support);
// We need an element to setup
- if (utils.is.nullOrUndefined(this.media) || !utils.is.element(this.media)) {
+ if (is.nullOrUndefined(this.media) || !is.element(this.media)) {
this.debug.error('Setup failed: no suitable element passed');
return;
}
@@ -144,7 +150,6 @@ class Plyr {
// Embed properties
let iframe = null;
let url = null;
- let params = null;
// Different setup based on type
switch (type) {
@@ -153,10 +158,10 @@ class Plyr {
iframe = this.media.querySelector('iframe');
// <iframe> type
- if (utils.is.element(iframe)) {
+ if (is.element(iframe)) {
// Detect provider
- url = iframe.getAttribute('src');
- this.provider = utils.getProviderByUrl(url);
+ url = parseUrl(iframe.getAttribute('src'));
+ this.provider = getProviderByUrl(url.toString());
// Rework elements
this.elements.container = this.media;
@@ -166,24 +171,20 @@ class Plyr {
this.elements.container.className = '';
// Get attributes from URL and set config
- params = utils.getUrlParams(url);
- if (!utils.is.empty(params)) {
- const truthy = [
- '1',
- 'true',
- ];
-
- if (truthy.includes(params.autoplay)) {
+ if (url.searchParams.length) {
+ const truthy = ['1', 'true'];
+
+ if (truthy.includes(url.searchParams.get('autoplay'))) {
this.config.autoplay = true;
}
- if (truthy.includes(params.loop)) {
+ if (truthy.includes(url.searchParams.get('loop'))) {
this.config.loop.active = true;
}
// TODO: replace fullscreen.iosNative with this playsinline config option
// YouTube requires the playsinline in the URL
if (this.isYouTube) {
- this.config.playsinline = truthy.includes(params.playsinline);
+ this.config.playsinline = truthy.includes(url.searchParams.get('playsinline'));
} else {
this.config.playsinline = true;
}
@@ -197,7 +198,7 @@ class Plyr {
}
// Unsupported or missing provider
- if (utils.is.empty(this.provider) || !Object.keys(providers).includes(this.provider)) {
+ if (is.empty(this.provider) || !Object.keys(providers).includes(this.provider)) {
this.debug.error('Setup failed: Invalid provider');
return;
}
@@ -245,6 +246,8 @@ class Plyr {
return;
}
+ this.eventListeners = [];
+
// Create listeners
this.listeners = new Listeners(this);
@@ -255,9 +258,9 @@ class Plyr {
this.media.plyr = this;
// Wrap media
- if (!utils.is.element(this.elements.container)) {
- this.elements.container = utils.createElement('div');
- utils.wrap(this.media, this.elements.container);
+ if (!is.element(this.elements.container)) {
+ this.elements.container = createElement('div');
+ wrap(this.media, this.elements.container);
}
// Add style hook
@@ -268,7 +271,7 @@ class Plyr {
// Listen for events if debugging
if (this.config.debug) {
- utils.on(this.elements.container, this.config.events.join(' '), event => {
+ on.call(this, this.elements.container, this.config.events.join(' '), event => {
this.debug.log(`event: ${event.type}`);
});
}
@@ -327,7 +330,7 @@ class Plyr {
* Play the media, or play the advertisement (if they are not blocked)
*/
play() {
- if (!utils.is.function(this.media.play)) {
+ if (!is.function(this.media.play)) {
return null;
}
@@ -339,7 +342,7 @@ class Plyr {
* Pause the media
*/
pause() {
- if (!this.playing || !utils.is.function(this.media.pause)) {
+ if (!this.playing || !is.function(this.media.pause)) {
return;
}
@@ -380,7 +383,7 @@ class Plyr {
*/
togglePlay(input) {
// Toggle based on current state if nothing passed
- const toggle = utils.is.boolean(input) ? input : !this.playing;
+ const toggle = is.boolean(input) ? input : !this.playing;
if (toggle) {
this.play();
@@ -396,7 +399,7 @@ class Plyr {
if (this.isHTML5) {
this.pause();
this.restart();
- } else if (utils.is.function(this.media.stop)) {
+ } else if (is.function(this.media.stop)) {
this.media.stop();
}
}
@@ -413,7 +416,7 @@ class Plyr {
* @param {number} seekTime - how far to rewind in seconds. Defaults to the config.seekTime
*/
rewind(seekTime) {
- this.currentTime = this.currentTime - (utils.is.number(seekTime) ? seekTime : this.config.seekTime);
+ this.currentTime = this.currentTime - (is.number(seekTime) ? seekTime : this.config.seekTime);
}
/**
@@ -421,7 +424,7 @@ class Plyr {
* @param {number} seekTime - how far to fast forward in seconds. Defaults to the config.seekTime
*/
forward(seekTime) {
- this.currentTime = this.currentTime + (utils.is.number(seekTime) ? seekTime : this.config.seekTime);
+ this.currentTime = this.currentTime + (is.number(seekTime) ? seekTime : this.config.seekTime);
}
/**
@@ -435,7 +438,7 @@ class Plyr {
}
// Validate input
- const inputIsValid = utils.is.number(input) && input > 0;
+ const inputIsValid = is.number(input) && input > 0;
// Set
this.media.currentTime = inputIsValid ? Math.min(input, this.duration) : 0;
@@ -458,7 +461,7 @@ class Plyr {
const { buffered } = this.media;
// YouTube / Vimeo return a float between 0-1
- if (utils.is.number(buffered)) {
+ if (is.number(buffered)) {
return buffered;
}
@@ -502,17 +505,17 @@ class Plyr {
const max = 1;
const min = 0;
- if (utils.is.string(volume)) {
+ if (is.string(volume)) {
volume = Number(volume);
}
// Load volume from storage if no value specified
- if (!utils.is.number(volume)) {
+ if (!is.number(volume)) {
volume = this.storage.get('volume');
}
// Use config if all else fails
- if (!utils.is.number(volume)) {
+ if (!is.number(volume)) {
({ volume } = this.config);
}
@@ -532,7 +535,7 @@ class Plyr {
this.media.volume = volume;
// If muted, and we're increasing volume manually, reset muted state
- if (!utils.is.empty(value) && this.muted && volume > 0) {
+ if (!is.empty(value) && this.muted && volume > 0) {
this.muted = false;
}
}
@@ -550,7 +553,7 @@ class Plyr {
*/
increaseVolume(step) {
const volume = this.media.muted ? 0 : this.volume;
- this.volume = volume + (utils.is.number(step) ? step : 1);
+ this.volume = volume + (is.number(step) ? step : 1);
}
/**
@@ -559,7 +562,7 @@ class Plyr {
*/
decreaseVolume(step) {
const volume = this.media.muted ? 0 : this.volume;
- this.volume = volume - (utils.is.number(step) ? step : 1);
+ this.volume = volume - (is.number(step) ? step : 1);
}
/**
@@ -570,12 +573,12 @@ class Plyr {
let toggle = mute;
// Load muted state from storage
- if (!utils.is.boolean(toggle)) {
+ if (!is.boolean(toggle)) {
toggle = this.storage.get('muted');
}
// Use config if all else fails
- if (!utils.is.boolean(toggle)) {
+ if (!is.boolean(toggle)) {
toggle = this.config.muted;
}
@@ -621,15 +624,15 @@ class Plyr {
set speed(input) {
let speed = null;
- if (utils.is.number(input)) {
+ if (is.number(input)) {
speed = input;
}
- if (!utils.is.number(speed)) {
+ if (!is.number(speed)) {
speed = this.storage.get('speed');
}
- if (!utils.is.number(speed)) {
+ if (!is.number(speed)) {
speed = this.config.speed.selected;
}
@@ -666,36 +669,31 @@ class Plyr {
* @param {number} input - Quality level
*/
set quality(input) {
- let quality = null;
-
- if (!utils.is.empty(input)) {
- quality = Number(input);
- }
-
- if (!utils.is.number(quality)) {
- quality = this.storage.get('quality');
- }
-
- if (!utils.is.number(quality)) {
- quality = this.config.quality.selected;
- }
-
- if (!utils.is.number(quality)) {
- quality = this.config.quality.default;
- }
+ const config = this.config.quality;
+ const options = this.options.quality;
- if (!this.options.quality.length) {
+ if (!options.length) {
return;
}
- if (!this.options.quality.includes(quality)) {
- const closest = utils.closest(this.options.quality, quality);
- this.debug.warn(`Unsupported quality option: ${quality}, using ${closest} instead`);
- quality = closest;
+ let quality = [
+ !is.empty(input) && Number(input),
+ this.storage.get('quality'),
+ config.selected,
+ config.default,
+ ].find(is.number);
+
+ if (!options.includes(quality)) {
+ const value = closest(options, quality);
+ this.debug.warn(`Unsupported quality option: ${quality}, using ${value} instead`);
+ quality = value;
}
+ // Trigger request event
+ triggerEvent.call(this, this.media, 'qualityrequested', false, { quality });
+
// Update config
- this.config.quality.selected = quality;
+ config.selected = quality;
// Set quality
this.media.quality = quality;
@@ -714,7 +712,7 @@ class Plyr {
* @param {boolean} input - Whether to loop or not
*/
set loop(input) {
- const toggle = utils.is.boolean(input) ? input : this.config.loop.active;
+ const toggle = is.boolean(input) ? input : this.config.loop.active;
this.config.loop.active = toggle;
this.media.loop = toggle;
@@ -794,7 +792,7 @@ class Plyr {
return;
}
- ui.setPoster.call(this, input);
+ ui.setPoster.call(this, input, false).catch(() => {});
}
/**
@@ -813,7 +811,7 @@ class Plyr {
* @param {boolean} input - Whether to autoplay or not
*/
set autoplay(input) {
- const toggle = utils.is.boolean(input) ? input : this.config.autoplay;
+ const toggle = is.boolean(input) ? input : this.config.autoplay;
this.config.autoplay = toggle;
}
@@ -829,41 +827,23 @@ class Plyr {
* @param {boolean} input - Whether to enable captions
*/
toggleCaptions(input) {
- // If there's no full support
- if (!this.supported.ui) {
- return;
+ captions.toggle.call(this, input, false);
}
- // If the method is called without parameter, toggle based on current value
- const active = utils.is.boolean(input) ? input : !this.elements.container.classList.contains(this.config.classNames.captions.active);
-
- // Toggle state
- this.elements.buttons.captions.pressed = active;
-
- // Add class hook
- utils.toggleClass(this.elements.container, this.config.classNames.captions.active, active);
-
- // Update state and trigger event
- if (active !== this.captions.active) {
- this.captions.active = active;
- utils.dispatchEvent.call(this, this.media, this.captions.active ? 'captionsenabled' : 'captionsdisabled');
- }
- }
-
/**
* Set the caption track by index
* @param {number} - Caption index
*/
set currentTrack(input) {
- captions.set.call(this, input);
+ captions.set.call(this, input, false);
}
/**
* Get the current caption track index (-1 if disabled)
*/
get currentTrack() {
- const { active, currentTrack } = this.captions;
- return active ? currentTrack : -1;
+ const { toggled, currentTrack } = this.captions;
+ return toggled ? currentTrack : -1;
}
/**
@@ -872,7 +852,7 @@ class Plyr {
* @param {string} - Two character ISO language code (e.g. EN, FR, PT, etc)
*/
set language(input) {
- captions.setLanguage.call(this, input);
+ captions.setLanguage.call(this, input, false);
}
/**
@@ -899,7 +879,7 @@ class Plyr {
}
// Toggle based on current state if not passed
- const toggle = utils.is.boolean(input) ? input : this.pip === states.inline;
+ const toggle = is.boolean(input) ? input : this.pip === states.inline;
// Toggle based on current state
this.media.webkitSetPresentationMode(toggle ? states.pip : states.inline);
@@ -935,22 +915,22 @@ class Plyr {
// Don't toggle if missing UI support or if it's audio
if (this.supported.ui && !this.isAudio) {
// Get state before change
- const isHidden = utils.hasClass(this.elements.container, this.config.classNames.hideControls);
+ const isHidden = hasClass(this.elements.container, this.config.classNames.hideControls);
// Negate the argument if not undefined since adding the class to hides the controls
const force = typeof toggle === 'undefined' ? undefined : !toggle;
// Apply and get updated state
- const hiding = utils.toggleClass(this.elements.container, this.config.classNames.hideControls, force);
+ const hiding = toggleClass(this.elements.container, this.config.classNames.hideControls, force);
// Close menu
- if (hiding && this.config.controls.includes('settings') && !utils.is.empty(this.config.settings)) {
+ if (hiding && this.config.controls.includes('settings') && !is.empty(this.config.settings)) {
controls.toggleMenu.call(this, false);
}
// Trigger event on change
if (hiding !== isHidden) {
const eventName = hiding ? 'controlshidden' : 'controlsshown';
- utils.dispatchEvent.call(this, this.media, eventName);
+ triggerEvent.call(this, this.media, eventName);
}
return !hiding;
}
@@ -963,16 +943,23 @@ class Plyr {
* @param {function} callback - Callback for when event occurs
*/
on(event, callback) {
- utils.on(this.elements.container, event, callback);
+ on.call(this, this.elements.container, event, callback);
+ }
+ /**
+ * Add event listeners once
+ * @param {string} event - Event type
+ * @param {function} callback - Callback for when event occurs
+ */
+ once(event, callback) {
+ once.call(this, this.elements.container, event, callback);
}
-
/**
* Remove event listeners
* @param {string} event - Event type
* @param {function} callback - Callback for when event occurs
*/
off(event, callback) {
- utils.off(this.elements.container, event, callback);
+ off(this.elements.container, event, callback);
}
/**
@@ -998,10 +985,10 @@ class Plyr {
if (soft) {
if (Object.keys(this.elements).length) {
// Remove elements
- utils.removeElement(this.elements.buttons.play);
- utils.removeElement(this.elements.captions);
- utils.removeElement(this.elements.controls);
- utils.removeElement(this.elements.wrapper);
+ removeElement(this.elements.buttons.play);
+ removeElement(this.elements.captions);
+ removeElement(this.elements.controls);
+ removeElement(this.elements.wrapper);
// Clear for GC
this.elements.buttons.play = null;
@@ -1011,21 +998,21 @@ class Plyr {
}
// Callback
- if (utils.is.function(callback)) {
+ if (is.function(callback)) {
callback();
}
} else {
// Unbind listeners
- this.listeners.clear();
+ unbindListeners.call(this);
// Replace the container with the original element provided
- utils.replaceElement(this.elements.original, this.elements.container);
+ replaceElement(this.elements.original, this.elements.container);
// Event
- utils.dispatchEvent.call(this, this.elements.original, 'destroyed', true);
+ triggerEvent.call(this, this.elements.original, 'destroyed', true);
// Callback
- if (utils.is.function(callback)) {
+ if (is.function(callback)) {
callback.call(this.elements.original);
}
@@ -1043,10 +1030,8 @@ class Plyr {
// Stop playback
this.stop();
- // Type specific stuff
- switch (`${this.provider}:${this.type}`) {
- case 'html5:video':
- case 'html5:audio':
+ // Provider specific stuff
+ if (this.isHTML5) {
// Clear timeout
clearTimeout(this.timers.loading);
@@ -1055,25 +1040,19 @@ class Plyr {
// Clean up
done();
-
- break;
-
- case 'youtube:video':
+ } else if (this.isYouTube) {
// Clear timers
clearInterval(this.timers.buffering);
clearInterval(this.timers.playing);
// Destroy YouTube API
- if (this.embed !== null && utils.is.function(this.embed.destroy)) {
+ if (this.embed !== null && is.function(this.embed.destroy)) {
this.embed.destroy();
}
// Clean up
done();
-
- break;
-
- case 'vimeo:video':
+ } else if (this.isVimeo) {
// Destroy Vimeo API
// then clean up (wait, to prevent postmessage errors)
if (this.embed !== null) {
@@ -1082,11 +1061,6 @@ class Plyr {
// Vimeo does not always return
setTimeout(done, 200);
-
- break;
-
- default:
- break;
}
}
@@ -1114,7 +1088,7 @@ class Plyr {
* @param {string} [id] - Unique ID
*/
static loadSprite(url, id) {
- return utils.loadSprite(url, id);
+ return loadSprite(url, id);
}
/**
@@ -1125,15 +1099,15 @@ class Plyr {
static setup(selector, options = {}) {
let targets = null;
- if (utils.is.string(selector)) {
+ if (is.string(selector)) {
targets = Array.from(document.querySelectorAll(selector));
- } else if (utils.is.nodeList(selector)) {
+ } else if (is.nodeList(selector)) {
targets = Array.from(selector);
- } else if (utils.is.array(selector)) {
- targets = selector.filter(utils.is.element);
+ } else if (is.array(selector)) {
+ targets = selector.filter(is.element);
}
- if (utils.is.empty(targets)) {
+ if (is.empty(targets)) {
return null;
}
@@ -1141,6 +1115,6 @@ class Plyr {
}
}
-Plyr.defaults = utils.cloneDeep(defaults);
+Plyr.defaults = cloneDeep(defaults);
export default Plyr;