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.js2116
1 files changed, 1058 insertions, 1058 deletions
diff --git a/src/js/plyr.js b/src/js/plyr.js
index 00b95a5f..c755e2be 100644
--- a/src/js/plyr.js
+++ b/src/js/plyr.js
@@ -37,748 +37,748 @@ import { parseUrl } from './utils/urls';
// Plyr instance
class Plyr {
- constructor(target, options) {
- this.timers = {};
-
- // State
- this.ready = false;
- this.loading = false;
- this.failed = false;
-
- // Touch device
- this.touch = support.touch;
-
- // Set the media element
- this.media = target;
-
- // String selector passed
- 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) || is.nodeList(this.media) || is.array(this.media)) {
- // eslint-disable-next-line
- this.media = this.media[0];
- }
-
- // Set config
- this.config = extend(
- {},
- defaults,
- Plyr.defaults,
- options || {},
- (() => {
- try {
- return JSON.parse(this.media.getAttribute('data-plyr-config'));
- } catch (e) {
- return {};
- }
- })(),
- );
-
- // Elements cache
- this.elements = {
- container: null,
- captions: null,
- buttons: {},
- display: {},
- progress: {},
- inputs: {},
- settings: {
- popup: null,
- menu: null,
- panels: {},
- buttons: {},
- },
- };
-
- // Captions
- this.captions = {
- active: null,
- currentTrack: -1,
- meta: new WeakMap(),
- };
-
- // Fullscreen
- this.fullscreen = {
- active: false,
- };
-
- // Options
- this.options = {
- speed: [],
- quality: [],
- };
-
- // Debugging
- // TODO: move to globals
- this.debug = new Console(this.config.debug);
-
- // Log config options and support
- this.debug.log('Config', this.config);
- this.debug.log('Support', support);
-
- // We need an element to setup
- if (is.nullOrUndefined(this.media) || !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.debug.warn('Target already setup');
- return;
- }
-
- // Bail if not enabled
- if (!this.config.enabled) {
- this.debug.error('Setup failed: disabled by config');
- return;
+ constructor(target, options) {
+ this.timers = {};
+
+ // State
+ this.ready = false;
+ this.loading = false;
+ this.failed = false;
+
+ // Touch device
+ this.touch = support.touch;
+
+ // Set the media element
+ this.media = target;
+
+ // String selector passed
+ 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) || is.nodeList(this.media) || is.array(this.media)) {
+ // eslint-disable-next-line
+ this.media = this.media[0];
+ }
+
+ // Set config
+ this.config = extend(
+ {},
+ defaults,
+ Plyr.defaults,
+ options || {},
+ (() => {
+ try {
+ return JSON.parse(this.media.getAttribute('data-plyr-config'));
+ } catch (e) {
+ return {};
}
+ })(),
+ );
+
+ // Elements cache
+ this.elements = {
+ container: null,
+ captions: null,
+ buttons: {},
+ display: {},
+ progress: {},
+ inputs: {},
+ settings: {
+ popup: null,
+ menu: null,
+ panels: {},
+ buttons: {},
+ },
+ };
+
+ // Captions
+ this.captions = {
+ active: null,
+ currentTrack: -1,
+ meta: new WeakMap(),
+ };
+
+ // Fullscreen
+ this.fullscreen = {
+ active: false,
+ };
+
+ // Options
+ this.options = {
+ speed: [],
+ quality: [],
+ };
+
+ // Debugging
+ // TODO: move to globals
+ this.debug = new Console(this.config.debug);
+
+ // Log config options and support
+ this.debug.log('Config', this.config);
+ this.debug.log('Support', support);
+
+ // We need an element to setup
+ if (is.nullOrUndefined(this.media) || !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.debug.warn('Target already setup');
+ return;
+ }
+
+ // Bail if not enabled
+ if (!this.config.enabled) {
+ 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.debug.error('Setup failed: no support');
+ return;
+ }
+
+ // Cache original element state for .destroy()
+ const clone = this.media.cloneNode(true);
+ clone.autoplay = false;
+ this.elements.original = clone;
+
+ // Set media type based on tag or data attribute
+ // Supported: video, audio, vimeo, youtube
+ const type = this.media.tagName.toLowerCase();
+ // Embed properties
+ let iframe = null;
+ let url = null;
+
+ // Different setup based on type
+ switch (type) {
+ case 'div':
+ // Find the frame
+ iframe = this.media.querySelector('iframe');
+
+ // <iframe> type
+ if (is.element(iframe)) {
+ // Detect provider
+ url = parseUrl(iframe.getAttribute('src'));
+ this.provider = getProviderByUrl(url.toString());
+
+ // Rework elements
+ this.elements.container = this.media;
+ this.media = iframe;
+
+ // Reset classname
+ this.elements.container.className = '';
+
+ // Get attributes from URL and set config
+ if (url.search.length) {
+ const truthy = ['1', 'true'];
+
+ if (truthy.includes(url.searchParams.get('autoplay'))) {
+ this.config.autoplay = true;
+ }
+ if (truthy.includes(url.searchParams.get('loop'))) {
+ this.config.loop.active = true;
+ }
- // Bail if disabled or no basic support
- // You may want to disable certain UAs etc
- if (!support.check().api) {
- this.debug.error('Setup failed: no support');
- return;
- }
-
- // Cache original element state for .destroy()
- const clone = this.media.cloneNode(true);
- clone.autoplay = false;
- this.elements.original = clone;
-
- // Set media type based on tag or data attribute
- // Supported: video, audio, vimeo, youtube
- const type = this.media.tagName.toLowerCase();
- // Embed properties
- let iframe = null;
- let url = null;
-
- // Different setup based on type
- switch (type) {
- case 'div':
- // Find the frame
- iframe = this.media.querySelector('iframe');
-
- // <iframe> type
- if (is.element(iframe)) {
- // Detect provider
- url = parseUrl(iframe.getAttribute('src'));
- this.provider = getProviderByUrl(url.toString());
-
- // Rework elements
- this.elements.container = this.media;
- this.media = iframe;
-
- // Reset classname
- this.elements.container.className = '';
-
- // Get attributes from URL and set config
- if (url.search.length) {
- const truthy = ['1', 'true'];
-
- if (truthy.includes(url.searchParams.get('autoplay'))) {
- this.config.autoplay = true;
- }
- 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(url.searchParams.get('playsinline'));
- this.config.youtube.hl = url.searchParams.get('hl'); // TODO: Should this be setting language?
- } else {
- this.config.playsinline = true;
- }
- }
- } else {
- // <div> with attributes
- this.provider = this.media.getAttribute(this.config.attributes.embed.provider);
-
- // Remove attribute
- this.media.removeAttribute(this.config.attributes.embed.provider);
- }
-
- // Unsupported or missing provider
- if (is.empty(this.provider) || !Object.keys(providers).includes(this.provider)) {
- this.debug.error('Setup failed: Invalid provider');
- return;
- }
-
- // Audio will come later for external providers
- this.type = types.video;
-
- break;
-
- case 'video':
- case 'audio':
- this.type = type;
- this.provider = providers.html5;
-
- // Get config from attributes
- if (this.media.hasAttribute('crossorigin')) {
- this.config.crossorigin = true;
- }
- if (this.media.hasAttribute('autoplay')) {
- this.config.autoplay = true;
- }
- if (this.media.hasAttribute('playsinline') || this.media.hasAttribute('webkit-playsinline')) {
- this.config.playsinline = true;
- }
- if (this.media.hasAttribute('muted')) {
- this.config.muted = true;
- }
- if (this.media.hasAttribute('loop')) {
- this.config.loop.active = true;
- }
-
- break;
+ // TODO: replace fullscreen.iosNative with this playsinline config option
+ // YouTube requires the playsinline in the URL
+ if (this.isYouTube) {
+ this.config.playsinline = truthy.includes(url.searchParams.get('playsinline'));
+ this.config.youtube.hl = url.searchParams.get('hl'); // TODO: Should this be setting language?
+ } else {
+ this.config.playsinline = true;
+ }
+ }
+ } else {
+ // <div> with attributes
+ this.provider = this.media.getAttribute(this.config.attributes.embed.provider);
- default:
- this.debug.error('Setup failed: unsupported type');
- return;
+ // Remove attribute
+ this.media.removeAttribute(this.config.attributes.embed.provider);
}
- // Check for support again but with type
- this.supported = support.check(this.type, this.provider, this.config.playsinline);
-
- // If no support for even API, bail
- if (!this.supported.api) {
- this.debug.error('Setup failed: no support');
- return;
+ // Unsupported or missing provider
+ if (is.empty(this.provider) || !Object.keys(providers).includes(this.provider)) {
+ this.debug.error('Setup failed: Invalid provider');
+ return;
}
- this.eventListeners = [];
+ // Audio will come later for external providers
+ this.type = types.video;
- // Create listeners
- this.listeners = new Listeners(this);
+ break;
- // Setup local storage for user settings
- this.storage = new Storage(this);
+ case 'video':
+ case 'audio':
+ this.type = type;
+ this.provider = providers.html5;
- // Store reference
- this.media.plyr = this;
-
- // Wrap media
- if (!is.element(this.elements.container)) {
- this.elements.container = createElement('div', { tabindex: 0 });
- wrap(this.media, this.elements.container);
+ // Get config from attributes
+ if (this.media.hasAttribute('crossorigin')) {
+ this.config.crossorigin = true;
}
-
- // Add style hook
- ui.addStyleHook.call(this);
-
- // Setup media
- media.setup.call(this);
-
- // Listen for events if debugging
- if (this.config.debug) {
- on.call(this, this.elements.container, this.config.events.join(' '), event => {
- this.debug.log(`event: ${event.type}`);
- });
+ if (this.media.hasAttribute('autoplay')) {
+ this.config.autoplay = true;
}
-
- // Setup interface
- // If embed but not fully supported, build interface now to avoid flash of controls
- if (this.isHTML5 || (this.isEmbed && !this.supported.ui)) {
- ui.build.call(this);
+ if (this.media.hasAttribute('playsinline') || this.media.hasAttribute('webkit-playsinline')) {
+ this.config.playsinline = true;
}
-
- // Container listeners
- this.listeners.container();
-
- // Global listeners
- this.listeners.global();
-
- // Setup fullscreen
- this.fullscreen = new Fullscreen(this);
-
- // Setup ads if provided
- if (this.config.ads.enabled) {
- this.ads = new Ads(this);
- }
-
- // Autoplay if required
- if (this.isHTML5 && this.config.autoplay) {
- setTimeout(() => silencePromise(this.play()), 10);
+ if (this.media.hasAttribute('muted')) {
+ this.config.muted = true;
}
-
- // Seek time will be recorded (in listeners.js) so we can prevent hiding controls for a few seconds after seek
- this.lastSeekTime = 0;
-
- // Setup preview thumbnails if enabled
- if (this.config.previewThumbnails.enabled) {
- this.previewThumbnails = new PreviewThumbnails(this);
+ if (this.media.hasAttribute('loop')) {
+ this.config.loop.active = true;
}
- }
-
- // ---------------------------------------
- // API
- // ---------------------------------------
-
- /**
- * Types and provider helpers
- */
- get isHTML5() {
- return this.provider === providers.html5;
- }
- get isEmbed() {
- return this.isYouTube || this.isVimeo;
- }
-
- get isYouTube() {
- return this.provider === providers.youtube;
- }
+ break;
- get isVimeo() {
- return this.provider === providers.vimeo;
+ default:
+ this.debug.error('Setup failed: unsupported type');
+ return;
}
- get isVideo() {
- return this.type === types.video;
- }
+ // Check for support again but with type
+ this.supported = support.check(this.type, this.provider, this.config.playsinline);
- get isAudio() {
- return this.type === types.audio;
+ // If no support for even API, bail
+ if (!this.supported.api) {
+ this.debug.error('Setup failed: no support');
+ return;
}
- /**
- * Play the media, or play the advertisement (if they are not blocked)
- */
- play() {
- if (!is.function(this.media.play)) {
- return null;
- }
+ this.eventListeners = [];
- // Intecept play with ads
- if (this.ads && this.ads.enabled) {
- this.ads.managerPromise.then(() => this.ads.play()).catch(() => silencePromise(this.media.play()));
- }
+ // Create listeners
+ this.listeners = new Listeners(this);
- // Return the promise (for HTML5)
- return this.media.play();
- }
+ // Setup local storage for user settings
+ this.storage = new Storage(this);
- /**
- * Pause the media
- */
- pause() {
- if (!this.playing || !is.function(this.media.pause)) {
- return null;
- }
+ // Store reference
+ this.media.plyr = this;
- return this.media.pause();
+ // Wrap media
+ if (!is.element(this.elements.container)) {
+ this.elements.container = createElement('div', { tabindex: 0 });
+ wrap(this.media, this.elements.container);
}
- /**
- * Get playing state
- */
- get playing() {
- return Boolean(this.ready && !this.paused && !this.ended);
- }
+ // Add style hook
+ ui.addStyleHook.call(this);
- /**
- * Get paused state
- */
- get paused() {
- return Boolean(this.media.paused);
- }
+ // Setup media
+ media.setup.call(this);
- /**
- * Get stopped state
- */
- get stopped() {
- return Boolean(this.paused && this.currentTime === 0);
+ // Listen for events if debugging
+ if (this.config.debug) {
+ on.call(this, this.elements.container, this.config.events.join(' '), event => {
+ this.debug.log(`event: ${event.type}`);
+ });
}
- /**
- * Get ended state
- */
- get ended() {
- return Boolean(this.media.ended);
+ // Setup interface
+ // If embed but not fully supported, build interface now to avoid flash of controls
+ if (this.isHTML5 || (this.isEmbed && !this.supported.ui)) {
+ ui.build.call(this);
}
- /**
- * Toggle playback based on current status
- * @param {Boolean} input
- */
- togglePlay(input) {
- // Toggle based on current state if nothing passed
- const toggle = is.boolean(input) ? input : !this.playing;
+ // Container listeners
+ this.listeners.container();
- if (toggle) {
- return this.play();
- }
+ // Global listeners
+ this.listeners.global();
- return this.pause();
- }
+ // Setup fullscreen
+ this.fullscreen = new Fullscreen(this);
- /**
- * Stop playback
- */
- stop() {
- if (this.isHTML5) {
- this.pause();
- this.restart();
- } else if (is.function(this.media.stop)) {
- this.media.stop();
- }
+ // Setup ads if provided
+ if (this.config.ads.enabled) {
+ this.ads = new Ads(this);
}
- /**
- * Restart playback
- */
- restart() {
- this.currentTime = 0;
+ // Autoplay if required
+ if (this.isHTML5 && this.config.autoplay) {
+ setTimeout(() => silencePromise(this.play()), 10);
}
- /**
- * Rewind
- * @param {Number} seekTime - how far to rewind in seconds. Defaults to the config.seekTime
- */
- rewind(seekTime) {
- this.currentTime -= is.number(seekTime) ? seekTime : this.config.seekTime;
- }
+ // Seek time will be recorded (in listeners.js) so we can prevent hiding controls for a few seconds after seek
+ this.lastSeekTime = 0;
- /**
- * Fast forward
- * @param {Number} seekTime - how far to fast forward in seconds. Defaults to the config.seekTime
- */
- forward(seekTime) {
- this.currentTime += is.number(seekTime) ? seekTime : this.config.seekTime;
+ // Setup preview thumbnails if enabled
+ if (this.config.previewThumbnails.enabled) {
+ this.previewThumbnails = new PreviewThumbnails(this);
}
+ }
- /**
- * Seek to a time
- * @param {Number} input - where to seek to in seconds. Defaults to 0 (the start)
- */
- set currentTime(input) {
- // Bail if media duration isn't available yet
- if (!this.duration) {
- return;
- }
-
- // Validate input
- const inputIsValid = is.number(input) && input > 0;
+ // ---------------------------------------
+ // API
+ // ---------------------------------------
- // Set
- this.media.currentTime = inputIsValid ? Math.min(input, this.duration) : 0;
+ /**
+ * Types and provider helpers
+ */
+ get isHTML5() {
+ return this.provider === providers.html5;
+ }
- // Logging
- this.debug.log(`Seeking to ${this.currentTime} seconds`);
- }
+ get isEmbed() {
+ return this.isYouTube || this.isVimeo;
+ }
- /**
- * Get current time
- */
- get currentTime() {
- return Number(this.media.currentTime);
- }
+ get isYouTube() {
+ return this.provider === providers.youtube;
+ }
- /**
- * Get buffered
- */
- get buffered() {
- const { buffered } = this.media;
+ get isVimeo() {
+ return this.provider === providers.vimeo;
+ }
- // YouTube / Vimeo return a float between 0-1
- if (is.number(buffered)) {
- return buffered;
- }
+ get isVideo() {
+ return this.type === types.video;
+ }
- // HTML5
- // TODO: Handle buffered chunks of the media
- // (i.e. seek to another section buffers only that section)
- if (buffered && buffered.length && this.duration > 0) {
- return buffered.end(0) / this.duration;
- }
+ get isAudio() {
+ return this.type === types.audio;
+ }
- return 0;
+ /**
+ * Play the media, or play the advertisement (if they are not blocked)
+ */
+ play() {
+ if (!is.function(this.media.play)) {
+ return null;
}
- /**
- * Get seeking status
- */
- get seeking() {
- return Boolean(this.media.seeking);
- }
+ // Intecept play with ads
+ if (this.ads && this.ads.enabled) {
+ this.ads.managerPromise.then(() => this.ads.play()).catch(() => silencePromise(this.media.play()));
+ }
+
+ // Return the promise (for HTML5)
+ return this.media.play();
+ }
+
+ /**
+ * Pause the media
+ */
+ pause() {
+ if (!this.playing || !is.function(this.media.pause)) {
+ return null;
+ }
+
+ return this.media.pause();
+ }
+
+ /**
+ * Get playing state
+ */
+ get playing() {
+ return Boolean(this.ready && !this.paused && !this.ended);
+ }
+
+ /**
+ * Get paused state
+ */
+ get paused() {
+ return Boolean(this.media.paused);
+ }
+
+ /**
+ * Get stopped state
+ */
+ get stopped() {
+ return Boolean(this.paused && this.currentTime === 0);
+ }
+
+ /**
+ * Get ended state
+ */
+ get ended() {
+ return Boolean(this.media.ended);
+ }
+
+ /**
+ * Toggle playback based on current status
+ * @param {Boolean} input
+ */
+ togglePlay(input) {
+ // Toggle based on current state if nothing passed
+ const toggle = is.boolean(input) ? input : !this.playing;
+
+ if (toggle) {
+ return this.play();
+ }
+
+ return this.pause();
+ }
+
+ /**
+ * Stop playback
+ */
+ stop() {
+ if (this.isHTML5) {
+ this.pause();
+ this.restart();
+ } else if (is.function(this.media.stop)) {
+ this.media.stop();
+ }
+ }
+
+ /**
+ * Restart playback
+ */
+ restart() {
+ this.currentTime = 0;
+ }
+
+ /**
+ * Rewind
+ * @param {Number} seekTime - how far to rewind in seconds. Defaults to the config.seekTime
+ */
+ rewind(seekTime) {
+ this.currentTime -= is.number(seekTime) ? seekTime : this.config.seekTime;
+ }
+
+ /**
+ * Fast forward
+ * @param {Number} seekTime - how far to fast forward in seconds. Defaults to the config.seekTime
+ */
+ forward(seekTime) {
+ this.currentTime += is.number(seekTime) ? seekTime : this.config.seekTime;
+ }
+
+ /**
+ * Seek to a time
+ * @param {Number} input - where to seek to in seconds. Defaults to 0 (the start)
+ */
+ set currentTime(input) {
+ // Bail if media duration isn't available yet
+ if (!this.duration) {
+ return;
+ }
+
+ // Validate input
+ const inputIsValid = is.number(input) && input > 0;
+
+ // Set
+ this.media.currentTime = inputIsValid ? Math.min(input, this.duration) : 0;
+
+ // Logging
+ this.debug.log(`Seeking to ${this.currentTime} seconds`);
+ }
+
+ /**
+ * Get current time
+ */
+ get currentTime() {
+ return Number(this.media.currentTime);
+ }
+
+ /**
+ * Get buffered
+ */
+ get buffered() {
+ const { buffered } = this.media;
+
+ // YouTube / Vimeo return a float between 0-1
+ if (is.number(buffered)) {
+ return buffered;
+ }
+
+ // HTML5
+ // TODO: Handle buffered chunks of the media
+ // (i.e. seek to another section buffers only that section)
+ if (buffered && buffered.length && this.duration > 0) {
+ return buffered.end(0) / this.duration;
+ }
+
+ return 0;
+ }
+
+ /**
+ * Get seeking status
+ */
+ get seeking() {
+ return Boolean(this.media.seeking);
+ }
+
+ /**
+ * Get the duration of the current media
+ */
+ get duration() {
+ // Faux duration set via config
+ const fauxDuration = parseFloat(this.config.duration);
+ // Media duration can be NaN or Infinity before the media has loaded
+ const realDuration = (this.media || {}).duration;
+ const duration = !is.number(realDuration) || realDuration === Infinity ? 0 : realDuration;
+
+ // If config duration is funky, use regular duration
+ return fauxDuration || duration;
+ }
+
+ /**
+ * Set the player volume
+ * @param {Number} value - must be between 0 and 1. Defaults to the value from local storage and config.volume if not set in storage
+ */
+ set volume(value) {
+ let volume = value;
+ const max = 1;
+ const min = 0;
+
+ if (is.string(volume)) {
+ volume = Number(volume);
+ }
+
+ // Load volume from storage if no value specified
+ if (!is.number(volume)) {
+ volume = this.storage.get('volume');
+ }
+
+ // Use config if all else fails
+ if (!is.number(volume)) {
+ ({ volume } = this.config);
+ }
+
+ // Maximum is volumeMax
+ if (volume > max) {
+ volume = max;
+ }
+ // Minimum is volumeMin
+ if (volume < min) {
+ volume = min;
+ }
+
+ // Update config
+ this.config.volume = volume;
+
+ // Set the player volume
+ this.media.volume = volume;
- /**
- * Get the duration of the current media
- */
- get duration() {
- // Faux duration set via config
- const fauxDuration = parseFloat(this.config.duration);
- // Media duration can be NaN or Infinity before the media has loaded
- const realDuration = (this.media || {}).duration;
- const duration = !is.number(realDuration) || realDuration === Infinity ? 0 : realDuration;
-
- // If config duration is funky, use regular duration
- return fauxDuration || duration;
+ // If muted, and we're increasing volume manually, reset muted state
+ if (!is.empty(value) && this.muted && volume > 0) {
+ this.muted = false;
}
+ }
- /**
- * Set the player volume
- * @param {Number} value - must be between 0 and 1. Defaults to the value from local storage and config.volume if not set in storage
- */
- set volume(value) {
- let volume = value;
- const max = 1;
- const min = 0;
-
- if (is.string(volume)) {
- volume = Number(volume);
- }
+ /**
+ * Get the current player volume
+ */
+ get volume() {
+ return Number(this.media.volume);
+ }
- // Load volume from storage if no value specified
- if (!is.number(volume)) {
- volume = this.storage.get('volume');
- }
+ /**
+ * Increase volume
+ * @param {Boolean} step - How much to decrease by (between 0 and 1)
+ */
+ increaseVolume(step) {
+ const volume = this.media.muted ? 0 : this.volume;
+ this.volume = volume + (is.number(step) ? step : 0);
+ }
- // Use config if all else fails
- if (!is.number(volume)) {
- ({ volume } = this.config);
- }
+ /**
+ * Decrease volume
+ * @param {Boolean} step - How much to decrease by (between 0 and 1)
+ */
+ decreaseVolume(step) {
+ this.increaseVolume(-step);
+ }
- // Maximum is volumeMax
- if (volume > max) {
- volume = max;
- }
- // Minimum is volumeMin
- if (volume < min) {
- volume = min;
- }
+ /**
+ * Set muted state
+ * @param {Boolean} mute
+ */
+ set muted(mute) {
+ let toggle = mute;
- // Update config
- this.config.volume = volume;
+ // Load muted state from storage
+ if (!is.boolean(toggle)) {
+ toggle = this.storage.get('muted');
+ }
+
+ // Use config if all else fails
+ if (!is.boolean(toggle)) {
+ toggle = this.config.muted;
+ }
- // Set the player volume
- this.media.volume = volume;
+ // Update config
+ this.config.muted = toggle;
- // If muted, and we're increasing volume manually, reset muted state
- if (!is.empty(value) && this.muted && volume > 0) {
- this.muted = false;
- }
- }
+ // Set mute on the player
+ this.media.muted = toggle;
+ }
- /**
- * Get the current player volume
- */
- get volume() {
- return Number(this.media.volume);
- }
+ /**
+ * Get current muted state
+ */
+ get muted() {
+ return Boolean(this.media.muted);
+ }
- /**
- * Increase volume
- * @param {Boolean} step - How much to decrease by (between 0 and 1)
- */
- increaseVolume(step) {
- const volume = this.media.muted ? 0 : this.volume;
- this.volume = volume + (is.number(step) ? step : 0);
+ /**
+ * Check if the media has audio
+ */
+ get hasAudio() {
+ // Assume yes for all non HTML5 (as we can't tell...)
+ if (!this.isHTML5) {
+ return true;
}
- /**
- * Decrease volume
- * @param {Boolean} step - How much to decrease by (between 0 and 1)
- */
- decreaseVolume(step) {
- this.increaseVolume(-step);
+ if (this.isAudio) {
+ return true;
}
- /**
- * Set muted state
- * @param {Boolean} mute
- */
- set muted(mute) {
- let toggle = mute;
-
- // Load muted state from storage
- if (!is.boolean(toggle)) {
- toggle = this.storage.get('muted');
- }
-
- // Use config if all else fails
- if (!is.boolean(toggle)) {
- toggle = this.config.muted;
- }
+ // Get audio tracks
+ return (
+ Boolean(this.media.mozHasAudio) ||
+ Boolean(this.media.webkitAudioDecodedByteCount) ||
+ Boolean(this.media.audioTracks && this.media.audioTracks.length)
+ );
+ }
- // Update config
- this.config.muted = toggle;
+ /**
+ * Set playback speed
+ * @param {Number} speed - the speed of playback (0.5-2.0)
+ */
+ set speed(input) {
+ let speed = null;
- // Set mute on the player
- this.media.muted = toggle;
+ if (is.number(input)) {
+ speed = input;
}
- /**
- * Get current muted state
- */
- get muted() {
- return Boolean(this.media.muted);
+ if (!is.number(speed)) {
+ speed = this.storage.get('speed');
}
- /**
- * Check if the media has audio
- */
- get hasAudio() {
- // Assume yes for all non HTML5 (as we can't tell...)
- if (!this.isHTML5) {
- return true;
- }
-
- if (this.isAudio) {
- return true;
- }
-
- // Get audio tracks
- return (
- Boolean(this.media.mozHasAudio) ||
- Boolean(this.media.webkitAudioDecodedByteCount) ||
- Boolean(this.media.audioTracks && this.media.audioTracks.length)
- );
+ if (!is.number(speed)) {
+ speed = this.config.speed.selected;
}
- /**
- * Set playback speed
- * @param {Number} speed - the speed of playback (0.5-2.0)
- */
- set speed(input) {
- let speed = null;
+ // Clamp to min/max
+ const { minimumSpeed: min, maximumSpeed: max } = this;
+ speed = clamp(speed, min, max);
- if (is.number(input)) {
- speed = input;
- }
-
- if (!is.number(speed)) {
- speed = this.storage.get('speed');
- }
+ // Update config
+ this.config.speed.selected = speed;
- if (!is.number(speed)) {
- speed = this.config.speed.selected;
- }
+ // Set media speed
+ setTimeout(() => {
+ this.media.playbackRate = speed;
+ }, 0);
+ }
+
+ /**
+ * Get current playback speed
+ */
+ get speed() {
+ return Number(this.media.playbackRate);
+ }
- // Clamp to min/max
- const { minimumSpeed: min, maximumSpeed: max } = this;
- speed = clamp(speed, min, max);
-
- // Update config
- this.config.speed.selected = speed;
-
- // Set media speed
- setTimeout(() => {
- this.media.playbackRate = speed;
- }, 0);
+ /**
+ * Get the minimum allowed speed
+ */
+ get minimumSpeed() {
+ if (this.isYouTube) {
+ // https://developers.google.com/youtube/iframe_api_reference#setPlaybackRate
+ return Math.min(...this.options.speed);
}
- /**
- * Get current playback speed
- */
- get speed() {
- return Number(this.media.playbackRate);
+ if (this.isVimeo) {
+ // https://github.com/vimeo/player.js/#setplaybackrateplaybackrate-number-promisenumber-rangeerrorerror
+ return 0.5;
}
- /**
- * Get the minimum allowed speed
- */
- get minimumSpeed() {
- if (this.isYouTube) {
- // https://developers.google.com/youtube/iframe_api_reference#setPlaybackRate
- return Math.min(...this.options.speed);
- }
+ // https://stackoverflow.com/a/32320020/1191319
+ return 0.0625;
+ }
- if (this.isVimeo) {
- // https://github.com/vimeo/player.js/#setplaybackrateplaybackrate-number-promisenumber-rangeerrorerror
- return 0.5;
- }
+ /**
+ * Get the maximum allowed speed
+ */
+ get maximumSpeed() {
+ if (this.isYouTube) {
+ // https://developers.google.com/youtube/iframe_api_reference#setPlaybackRate
+ return Math.max(...this.options.speed);
+ }
- // https://stackoverflow.com/a/32320020/1191319
- return 0.0625;
+ if (this.isVimeo) {
+ // https://github.com/vimeo/player.js/#setplaybackrateplaybackrate-number-promisenumber-rangeerrorerror
+ return 2;
}
- /**
- * Get the maximum allowed speed
- */
- get maximumSpeed() {
- if (this.isYouTube) {
- // https://developers.google.com/youtube/iframe_api_reference#setPlaybackRate
- return Math.max(...this.options.speed);
- }
+ // https://stackoverflow.com/a/32320020/1191319
+ return 16;
+ }
- if (this.isVimeo) {
- // https://github.com/vimeo/player.js/#setplaybackrateplaybackrate-number-promisenumber-rangeerrorerror
- return 2;
- }
+ /**
+ * Set playback quality
+ * Currently HTML5 & YouTube only
+ * @param {Number} input - Quality level
+ */
+ set quality(input) {
+ const config = this.config.quality;
+ const options = this.options.quality;
- // https://stackoverflow.com/a/32320020/1191319
- return 16;
+ if (!options.length) {
+ return;
}
- /**
- * Set playback quality
- * Currently HTML5 & YouTube only
- * @param {Number} input - Quality level
- */
- set quality(input) {
- const config = this.config.quality;
- const options = this.options.quality;
-
- if (!options.length) {
- return;
- }
-
- let quality = [
- !is.empty(input) && Number(input),
- this.storage.get('quality'),
- config.selected,
- config.default,
- ].find(is.number);
+ let quality = [
+ !is.empty(input) && Number(input),
+ this.storage.get('quality'),
+ config.selected,
+ config.default,
+ ].find(is.number);
- let updateStorage = true;
+ let updateStorage = true;
- if (!options.includes(quality)) {
- const value = closest(options, quality);
- this.debug.warn(`Unsupported quality option: ${quality}, using ${value} instead`);
- quality = value;
+ if (!options.includes(quality)) {
+ const value = closest(options, quality);
+ this.debug.warn(`Unsupported quality option: ${quality}, using ${value} instead`);
+ quality = value;
- // Don't update storage if quality is not supported
- updateStorage = false;
- }
+ // Don't update storage if quality is not supported
+ updateStorage = false;
+ }
- // Update config
- config.selected = quality;
+ // Update config
+ config.selected = quality;
- // Set quality
- this.media.quality = quality;
+ // Set quality
+ this.media.quality = quality;
- // Save to storage
- if (updateStorage) {
- this.storage.set({ quality });
- }
+ // Save to storage
+ if (updateStorage) {
+ this.storage.set({ quality });
}
+ }
- /**
- * Get current quality level
- */
- get quality() {
- return this.media.quality;
- }
+ /**
+ * Get current quality level
+ */
+ get quality() {
+ return this.media.quality;
+ }
- /**
- * Toggle loop
- * TODO: Finish fancy new logic. Set the indicator on load as user may pass loop as config
- * @param {Boolean} input - Whether to loop or not
- */
- set loop(input) {
- const toggle = is.boolean(input) ? input : this.config.loop.active;
- this.config.loop.active = toggle;
- this.media.loop = toggle;
+ /**
+ * Toggle loop
+ * TODO: Finish fancy new logic. Set the indicator on load as user may pass loop as config
+ * @param {Boolean} input - Whether to loop or not
+ */
+ set loop(input) {
+ const toggle = is.boolean(input) ? input : this.config.loop.active;
+ this.config.loop.active = toggle;
+ this.media.loop = toggle;
- // Set default to be a true toggle
- /* const type = ['start', 'end', 'all', 'none', 'toggle'].includes(input) ? input : 'toggle';
+ // Set default to be a true toggle
+ /* const type = ['start', 'end', 'all', 'none', 'toggle'].includes(input) ? input : 'toggle';
switch (type) {
case 'start':
@@ -819,433 +819,433 @@ class Plyr {
this.config.loop.end = null;
break;
} */
- }
-
- /**
- * Get current loop state
- */
- get loop() {
- return Boolean(this.media.loop);
- }
-
- /**
- * Set new media source
- * @param {Object} input - The new source object (see docs)
- */
- set source(input) {
- source.change.call(this, input);
- }
-
- /**
- * Get current source
- */
- get source() {
- return this.media.currentSrc;
- }
-
- /**
- * Get a download URL (either source or custom)
- */
- get download() {
- const { download } = this.config.urls;
-
- return is.url(download) ? download : this.source;
- }
-
- /**
- * Set the download URL
- */
- set download(input) {
- if (!is.url(input)) {
- return;
- }
-
- this.config.urls.download = input;
-
- controls.setDownloadUrl.call(this);
- }
-
- /**
- * Set the poster image for a video
- * @param {String} input - the URL for the new poster image
- */
- set poster(input) {
- if (!this.isVideo) {
- this.debug.warn('Poster can only be set for video');
- return;
- }
-
- ui.setPoster.call(this, input, false).catch(() => {});
- }
-
- /**
- * Get the current poster image
- */
- get poster() {
- if (!this.isVideo) {
- return null;
- }
-
- return this.media.getAttribute('poster');
- }
-
- /**
- * Get the current aspect ratio in use
- */
- get ratio() {
- if (!this.isVideo) {
- return null;
- }
-
- const ratio = reduceAspectRatio(getAspectRatio.call(this));
-
- return is.array(ratio) ? ratio.join(':') : ratio;
- }
-
- /**
- * Set video aspect ratio
- */
- set ratio(input) {
- if (!this.isVideo) {
- this.debug.warn('Aspect ratio can only be set for video');
- return;
- }
-
- if (!is.string(input) || !validateRatio(input)) {
- this.debug.error(`Invalid aspect ratio specified (${input})`);
- return;
- }
-
- this.config.ratio = input;
-
- setAspectRatio.call(this);
- }
-
- /**
- * Set the autoplay state
- * @param {Boolean} input - Whether to autoplay or not
- */
- set autoplay(input) {
- const toggle = is.boolean(input) ? input : this.config.autoplay;
- this.config.autoplay = toggle;
- }
-
- /**
- * Get the current autoplay state
- */
- get autoplay() {
- return Boolean(this.config.autoplay);
- }
-
- /**
- * Toggle captions
- * @param {Boolean} input - Whether to enable captions
- */
- toggleCaptions(input) {
- captions.toggle.call(this, input, false);
- }
-
- /**
- * Set the caption track by index
- * @param {Number} - Caption index
- */
- set currentTrack(input) {
- captions.set.call(this, input, false);
- }
-
- /**
- * Get the current caption track index (-1 if disabled)
- */
- get currentTrack() {
- const { toggled, currentTrack } = this.captions;
- return toggled ? currentTrack : -1;
- }
-
- /**
- * Set the wanted language for captions
- * Since tracks can be added later it won't update the actual caption track until there is a matching track
- * @param {String} - Two character ISO language code (e.g. EN, FR, PT, etc)
- */
- set language(input) {
- captions.setLanguage.call(this, input, false);
- }
-
- /**
- * Get the current track's language
- */
- get language() {
- return (captions.getCurrentTrack.call(this) || {}).language;
- }
-
- /**
- * Toggle picture-in-picture playback on WebKit/MacOS
- * TODO: update player with state, support, enabled
- * TODO: detect outside changes
- */
- set pip(input) {
- // Bail if no support
- if (!support.pip) {
- return;
- }
-
- // Toggle based on current state if not passed
- const toggle = is.boolean(input) ? input : !this.pip;
-
- // Toggle based on current state
- // Safari
- if (is.function(this.media.webkitSetPresentationMode)) {
- this.media.webkitSetPresentationMode(toggle ? pip.active : pip.inactive);
- }
-
- // Chrome
- if (is.function(this.media.requestPictureInPicture)) {
- if (!this.pip && toggle) {
- this.media.requestPictureInPicture();
- } else if (this.pip && !toggle) {
- document.exitPictureInPicture();
- }
- }
- }
-
- /**
- * Get the current picture-in-picture state
- */
- get pip() {
- if (!support.pip) {
- return null;
- }
-
- // Safari
- if (!is.empty(this.media.webkitPresentationMode)) {
- return this.media.webkitPresentationMode === pip.active;
- }
-
- // Chrome
- return this.media === document.pictureInPictureElement;
- }
+ }
- /**
- * Trigger the airplay dialog
- * TODO: update player with state, support, enabled
- */
- airplay() {
- // Show dialog if supported
- if (support.airplay) {
- this.media.webkitShowPlaybackTargetPicker();
+ /**
+ * Get current loop state
+ */
+ get loop() {
+ return Boolean(this.media.loop);
+ }
+
+ /**
+ * Set new media source
+ * @param {Object} input - The new source object (see docs)
+ */
+ set source(input) {
+ source.change.call(this, input);
+ }
+
+ /**
+ * Get current source
+ */
+ get source() {
+ return this.media.currentSrc;
+ }
+
+ /**
+ * Get a download URL (either source or custom)
+ */
+ get download() {
+ const { download } = this.config.urls;
+
+ return is.url(download) ? download : this.source;
+ }
+
+ /**
+ * Set the download URL
+ */
+ set download(input) {
+ if (!is.url(input)) {
+ return;
+ }
+
+ this.config.urls.download = input;
+
+ controls.setDownloadUrl.call(this);
+ }
+
+ /**
+ * Set the poster image for a video
+ * @param {String} input - the URL for the new poster image
+ */
+ set poster(input) {
+ if (!this.isVideo) {
+ this.debug.warn('Poster can only be set for video');
+ return;
+ }
+
+ ui.setPoster.call(this, input, false).catch(() => {});
+ }
+
+ /**
+ * Get the current poster image
+ */
+ get poster() {
+ if (!this.isVideo) {
+ return null;
+ }
+
+ return this.media.getAttribute('poster');
+ }
+
+ /**
+ * Get the current aspect ratio in use
+ */
+ get ratio() {
+ if (!this.isVideo) {
+ return null;
+ }
+
+ const ratio = reduceAspectRatio(getAspectRatio.call(this));
+
+ return is.array(ratio) ? ratio.join(':') : ratio;
+ }
+
+ /**
+ * Set video aspect ratio
+ */
+ set ratio(input) {
+ if (!this.isVideo) {
+ this.debug.warn('Aspect ratio can only be set for video');
+ return;
+ }
+
+ if (!is.string(input) || !validateRatio(input)) {
+ this.debug.error(`Invalid aspect ratio specified (${input})`);
+ return;
+ }
+
+ this.config.ratio = input;
+
+ setAspectRatio.call(this);
+ }
+
+ /**
+ * Set the autoplay state
+ * @param {Boolean} input - Whether to autoplay or not
+ */
+ set autoplay(input) {
+ const toggle = is.boolean(input) ? input : this.config.autoplay;
+ this.config.autoplay = toggle;
+ }
+
+ /**
+ * Get the current autoplay state
+ */
+ get autoplay() {
+ return Boolean(this.config.autoplay);
+ }
+
+ /**
+ * Toggle captions
+ * @param {Boolean} input - Whether to enable captions
+ */
+ toggleCaptions(input) {
+ captions.toggle.call(this, input, false);
+ }
+
+ /**
+ * Set the caption track by index
+ * @param {Number} - Caption index
+ */
+ set currentTrack(input) {
+ captions.set.call(this, input, false);
+ }
+
+ /**
+ * Get the current caption track index (-1 if disabled)
+ */
+ get currentTrack() {
+ const { toggled, currentTrack } = this.captions;
+ return toggled ? currentTrack : -1;
+ }
+
+ /**
+ * Set the wanted language for captions
+ * Since tracks can be added later it won't update the actual caption track until there is a matching track
+ * @param {String} - Two character ISO language code (e.g. EN, FR, PT, etc)
+ */
+ set language(input) {
+ captions.setLanguage.call(this, input, false);
+ }
+
+ /**
+ * Get the current track's language
+ */
+ get language() {
+ return (captions.getCurrentTrack.call(this) || {}).language;
+ }
+
+ /**
+ * Toggle picture-in-picture playback on WebKit/MacOS
+ * TODO: update player with state, support, enabled
+ * TODO: detect outside changes
+ */
+ set pip(input) {
+ // Bail if no support
+ if (!support.pip) {
+ return;
+ }
+
+ // Toggle based on current state if not passed
+ const toggle = is.boolean(input) ? input : !this.pip;
+
+ // Toggle based on current state
+ // Safari
+ if (is.function(this.media.webkitSetPresentationMode)) {
+ this.media.webkitSetPresentationMode(toggle ? pip.active : pip.inactive);
+ }
+
+ // Chrome
+ if (is.function(this.media.requestPictureInPicture)) {
+ if (!this.pip && toggle) {
+ this.media.requestPictureInPicture();
+ } else if (this.pip && !toggle) {
+ document.exitPictureInPicture();
+ }
+ }
+ }
+
+ /**
+ * Get the current picture-in-picture state
+ */
+ get pip() {
+ if (!support.pip) {
+ return null;
+ }
+
+ // Safari
+ if (!is.empty(this.media.webkitPresentationMode)) {
+ return this.media.webkitPresentationMode === pip.active;
+ }
+
+ // Chrome
+ return this.media === document.pictureInPictureElement;
+ }
+
+ /**
+ * Trigger the airplay dialog
+ * TODO: update player with state, support, enabled
+ */
+ airplay() {
+ // Show dialog if supported
+ if (support.airplay) {
+ this.media.webkitShowPlaybackTargetPicker();
+ }
+ }
+
+ /**
+ * Toggle the player controls
+ * @param {Boolean} [toggle] - Whether to show the controls
+ */
+ toggleControls(toggle) {
+ // Don't toggle if missing UI support or if it's audio
+ if (this.supported.ui && !this.isAudio) {
+ // Get state before change
+ 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 = toggleClass(this.elements.container, this.config.classNames.hideControls, force);
+
+ // Close menu
+ 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';
+ triggerEvent.call(this, this.media, eventName);
+ }
+
+ return !hiding;
+ }
+
+ return false;
+ }
+
+ /**
+ * Add event listeners
+ * @param {String} event - Event type
+ * @param {Function} callback - Callback for when event occurs
+ */
+ on(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) {
+ off(this.elements.container, event, callback);
+ }
+
+ /**
+ * Destroy an instance
+ * Event listeners are removed when elements are removed
+ * http://stackoverflow.com/questions/12528049/if-a-dom-element-is-removed-are-its-listeners-also-removed-from-memory
+ * @param {Function} callback - Callback for when destroy is complete
+ * @param {Boolean} soft - Whether it's a soft destroy (for source changes etc)
+ */
+ destroy(callback, soft = false) {
+ if (!this.ready) {
+ return;
+ }
+
+ const done = () => {
+ // Reset overflow (incase destroyed while in fullscreen)
+ document.body.style.overflow = '';
+
+ // GC for embed
+ this.embed = null;
+
+ // If it's a soft destroy, make minimal changes
+ if (soft) {
+ if (Object.keys(this.elements).length) {
+ // Remove elements
+ 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;
+ this.elements.captions = null;
+ this.elements.controls = null;
+ this.elements.wrapper = null;
}
- }
-
- /**
- * Toggle the player controls
- * @param {Boolean} [toggle] - Whether to show the controls
- */
- toggleControls(toggle) {
- // Don't toggle if missing UI support or if it's audio
- if (this.supported.ui && !this.isAudio) {
- // Get state before change
- 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 = toggleClass(this.elements.container, this.config.classNames.hideControls, force);
-
- // Close menu
- 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';
- triggerEvent.call(this, this.media, eventName);
- }
-
- return !hiding;
+ // Callback
+ if (is.function(callback)) {
+ callback();
}
+ } else {
+ // Unbind listeners
+ unbindListeners.call(this);
- return false;
- }
-
- /**
- * Add event listeners
- * @param {String} event - Event type
- * @param {Function} callback - Callback for when event occurs
- */
- on(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);
- }
+ // Replace the container with the original element provided
+ replaceElement(this.elements.original, this.elements.container);
- /**
- * Remove event listeners
- * @param {String} event - Event type
- * @param {Function} callback - Callback for when event occurs
- */
- off(event, callback) {
- off(this.elements.container, event, callback);
- }
+ // Event
+ triggerEvent.call(this, this.elements.original, 'destroyed', true);
- /**
- * Destroy an instance
- * Event listeners are removed when elements are removed
- * http://stackoverflow.com/questions/12528049/if-a-dom-element-is-removed-are-its-listeners-also-removed-from-memory
- * @param {Function} callback - Callback for when destroy is complete
- * @param {Boolean} soft - Whether it's a soft destroy (for source changes etc)
- */
- destroy(callback, soft = false) {
- if (!this.ready) {
- return;
+ // Callback
+ if (is.function(callback)) {
+ callback.call(this.elements.original);
}
- const done = () => {
- // Reset overflow (incase destroyed while in fullscreen)
- document.body.style.overflow = '';
-
- // GC for embed
- this.embed = null;
-
- // If it's a soft destroy, make minimal changes
- if (soft) {
- if (Object.keys(this.elements).length) {
- // Remove elements
- 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;
- this.elements.captions = null;
- this.elements.controls = null;
- this.elements.wrapper = null;
- }
-
- // Callback
- if (is.function(callback)) {
- callback();
- }
- } else {
- // Unbind listeners
- unbindListeners.call(this);
-
- // Replace the container with the original element provided
- replaceElement(this.elements.original, this.elements.container);
-
- // Event
- triggerEvent.call(this, this.elements.original, 'destroyed', true);
-
- // Callback
- if (is.function(callback)) {
- callback.call(this.elements.original);
- }
-
- // Reset state
- this.ready = false;
-
- // Clear for garbage collection
- setTimeout(() => {
- this.elements = null;
- this.media = null;
- }, 200);
- }
- };
-
- // Stop playback
- this.stop();
-
- // Clear timeouts
- clearTimeout(this.timers.loading);
- clearTimeout(this.timers.controls);
- clearTimeout(this.timers.resized);
-
- // Provider specific stuff
- if (this.isHTML5) {
- // Restore native video controls
- ui.toggleNativeControls.call(this, true);
-
- // Clean up
- done();
- } else if (this.isYouTube) {
- // Clear timers
- clearInterval(this.timers.buffering);
- clearInterval(this.timers.playing);
-
- // Destroy YouTube API
- if (this.embed !== null && is.function(this.embed.destroy)) {
- this.embed.destroy();
- }
-
- // Clean up
- done();
- } else if (this.isVimeo) {
- // Destroy Vimeo API
- // then clean up (wait, to prevent postmessage errors)
- if (this.embed !== null) {
- this.embed.unload().then(done);
- }
-
- // Vimeo does not always return
- setTimeout(done, 200);
- }
- }
-
- /**
- * Check for support for a mime type (HTML5 only)
- * @param {String} type - Mime type
- */
- supports(type) {
- return support.mime.call(this, type);
- }
-
- /**
- * Check for support
- * @param {String} type - Player type (audio/video)
- * @param {String} provider - Provider (html5/youtube/vimeo)
- * @param {Boolean} inline - Where player has `playsinline` sttribute
- */
- static supported(type, provider, inline) {
- return support.check(type, provider, inline);
- }
-
- /**
- * Load an SVG sprite into the page
- * @param {String} url - URL for the SVG sprite
- * @param {String} [id] - Unique ID
- */
- static loadSprite(url, id) {
- return loadSprite(url, id);
- }
-
- /**
- * Setup multiple instances
- * @param {*} selector
- * @param {Object} options
- */
- static setup(selector, options = {}) {
- let targets = null;
-
- if (is.string(selector)) {
- targets = Array.from(document.querySelectorAll(selector));
- } else if (is.nodeList(selector)) {
- targets = Array.from(selector);
- } else if (is.array(selector)) {
- targets = selector.filter(is.element);
- }
-
- if (is.empty(targets)) {
- return null;
- }
+ // Reset state
+ this.ready = false;
- return targets.map(t => new Plyr(t, options));
- }
+ // Clear for garbage collection
+ setTimeout(() => {
+ this.elements = null;
+ this.media = null;
+ }, 200);
+ }
+ };
+
+ // Stop playback
+ this.stop();
+
+ // Clear timeouts
+ clearTimeout(this.timers.loading);
+ clearTimeout(this.timers.controls);
+ clearTimeout(this.timers.resized);
+
+ // Provider specific stuff
+ if (this.isHTML5) {
+ // Restore native video controls
+ ui.toggleNativeControls.call(this, true);
+
+ // Clean up
+ done();
+ } else if (this.isYouTube) {
+ // Clear timers
+ clearInterval(this.timers.buffering);
+ clearInterval(this.timers.playing);
+
+ // Destroy YouTube API
+ if (this.embed !== null && is.function(this.embed.destroy)) {
+ this.embed.destroy();
+ }
+
+ // Clean up
+ done();
+ } else if (this.isVimeo) {
+ // Destroy Vimeo API
+ // then clean up (wait, to prevent postmessage errors)
+ if (this.embed !== null) {
+ this.embed.unload().then(done);
+ }
+
+ // Vimeo does not always return
+ setTimeout(done, 200);
+ }
+ }
+
+ /**
+ * Check for support for a mime type (HTML5 only)
+ * @param {String} type - Mime type
+ */
+ supports(type) {
+ return support.mime.call(this, type);
+ }
+
+ /**
+ * Check for support
+ * @param {String} type - Player type (audio/video)
+ * @param {String} provider - Provider (html5/youtube/vimeo)
+ * @param {Boolean} inline - Where player has `playsinline` sttribute
+ */
+ static supported(type, provider, inline) {
+ return support.check(type, provider, inline);
+ }
+
+ /**
+ * Load an SVG sprite into the page
+ * @param {String} url - URL for the SVG sprite
+ * @param {String} [id] - Unique ID
+ */
+ static loadSprite(url, id) {
+ return loadSprite(url, id);
+ }
+
+ /**
+ * Setup multiple instances
+ * @param {*} selector
+ * @param {Object} options
+ */
+ static setup(selector, options = {}) {
+ let targets = null;
+
+ if (is.string(selector)) {
+ targets = Array.from(document.querySelectorAll(selector));
+ } else if (is.nodeList(selector)) {
+ targets = Array.from(selector);
+ } else if (is.array(selector)) {
+ targets = selector.filter(is.element);
+ }
+
+ if (is.empty(targets)) {
+ return null;
+ }
+
+ return targets.map(t => new Plyr(t, options));
+ }
}
Plyr.defaults = cloneDeep(defaults);