aboutsummaryrefslogtreecommitdiffstats
path: root/src/js
diff options
context:
space:
mode:
Diffstat (limited to 'src/js')
-rw-r--r--src/js/captions.js4
-rw-r--r--src/js/config/defaults.js3
-rw-r--r--src/js/controls.js50
-rw-r--r--src/js/fullscreen.js3
-rw-r--r--src/js/html5.js10
-rw-r--r--src/js/listeners.js33
-rw-r--r--src/js/plugins/ads.js5
-rw-r--r--src/js/plugins/previewThumbnails.js11
-rw-r--r--src/js/plugins/youtube.js35
-rw-r--r--src/js/plyr.js12
-rw-r--r--src/js/ui.js15
-rw-r--r--src/js/utils/animation.js16
-rw-r--r--src/js/utils/elements.js11
-rw-r--r--src/js/utils/i18n.js4
-rw-r--r--src/js/utils/loadSprite.js1
-rw-r--r--src/js/utils/style.js11
16 files changed, 120 insertions, 104 deletions
diff --git a/src/js/captions.js b/src/js/captions.js
index a8bb4d77..f7c24534 100644
--- a/src/js/captions.js
+++ b/src/js/captions.js
@@ -133,7 +133,7 @@ const captions = {
});
// Turn off native caption rendering to avoid double captions
- track.mode = 'hidden';
+ Object.assign(track, { mode: 'hidden' });
// Add event listener for cue changes
on.call(this, track, 'cuechange', () => captions.updateCues.call(this));
@@ -302,7 +302,7 @@ const captions = {
let track;
languages.every(language => {
- track = sorted.find(track => track.language === language);
+ track = sorted.find(t => t.language === language);
return !track; // Break iteration if there is a match
});
diff --git a/src/js/config/defaults.js b/src/js/config/defaults.js
index 808fa0ae..4bb0e8d5 100644
--- a/src/js/config/defaults.js
+++ b/src/js/config/defaults.js
@@ -128,6 +128,7 @@ const defaults = {
// 'fast-forward',
'progress',
'current-time',
+ // 'duration',
'mute',
'volume',
'captions',
@@ -195,7 +196,7 @@ const defaults = {
},
youtube: {
sdk: 'https://www.youtube.com/iframe_api',
- api: 'https://noembed.com/embed?url=https://www.youtube.com/watch?v={0}', // 'https://www.googleapis.com/youtube/v3/videos?id={0}&key={1}&fields=items(snippet(title),fileDetails)&part=snippet',
+ api: 'https://noembed.com/embed?url=https://www.youtube.com/watch?v={0}',
},
googleIMA: {
sdk: 'https://imasdk.googleapis.com/js/sdkloader/ima3.js',
diff --git a/src/js/controls.js b/src/js/controls.js
index d3e20a57..43a92140 100644
--- a/src/js/controls.js
+++ b/src/js/controls.js
@@ -11,7 +11,20 @@ import support from './support';
import { repaint, transitionEndEvent } from './utils/animation';
import { dedupe } from './utils/arrays';
import browser from './utils/browser';
-import { createElement, emptyElement, getAttributesFromSelector, getElement, getElements, hasClass, matches, removeElement, setAttributes, setFocus, toggleClass, toggleHidden } from './utils/elements';
+import {
+ createElement,
+ emptyElement,
+ getAttributesFromSelector,
+ getElement,
+ getElements,
+ hasClass,
+ matches,
+ removeElement,
+ setAttributes,
+ setFocus,
+ toggleClass,
+ toggleHidden,
+} from './utils/elements';
import { off, on } from './utils/events';
import i18n from './utils/i18n';
import is from './utils/is';
@@ -480,15 +493,15 @@ const controls = {
get() {
return menuItem.getAttribute('aria-checked') === 'true';
},
- set(checked) {
+ set(check) {
// Ensure exclusivity
- if (checked) {
+ if (check) {
Array.from(menuItem.parentNode.children)
.filter(node => matches(node, '[role="menuitemradio"]'))
.forEach(node => node.setAttribute('aria-checked', 'false'));
}
- menuItem.setAttribute('aria-checked', checked ? 'true' : 'false');
+ menuItem.setAttribute('aria-checked', check ? 'true' : 'false');
},
});
@@ -596,17 +609,17 @@ const controls = {
let value = 0;
const setProgress = (target, input) => {
- const value = is.number(input) ? input : 0;
+ const val = is.number(input) ? input : 0;
const progress = is.element(target) ? target : this.elements.display.buffer;
// Update value and label
if (is.element(progress)) {
- progress.value = value;
+ progress.value = val;
// Update text label inside
const label = progress.getElementsByTagName('span')[0];
if (is.element(label)) {
- label.childNodes[0].nodeValue = value;
+ label.childNodes[0].nodeValue = val;
}
}
};
@@ -688,14 +701,8 @@ const controls = {
return;
}
- // Calculate percentage
- let percent = 0;
- const clientRect = this.elements.progress.getBoundingClientRect();
const visible = `${this.config.classNames.tooltip}--visible`;
-
- const toggle = toggle => {
- toggleClass(this.elements.display.seekTooltip, visible, toggle);
- };
+ const toggle = show => toggleClass(this.elements.display.seekTooltip, visible, show);
// Hide on touch
if (this.touch) {
@@ -704,6 +711,9 @@ const controls = {
}
// Determine percentage, if already visible
+ let percent = 0;
+ const clientRect = this.elements.progress.getBoundingClientRect();
+
if (is.event(event)) {
percent = (100 / clientRect.width) * (event.pageX - clientRect.left);
} else if (hasClass(this.elements.display.seekTooltip, visible)) {
@@ -1100,7 +1110,7 @@ const controls = {
let target = pane;
if (!is.element(target)) {
- target = Object.values(this.elements.settings.panels).find(pane => !pane.hidden);
+ target = Object.values(this.elements.settings.panels).find(p => !p.hidden);
}
const firstItem = target.querySelector('[role^="menuitem"]');
@@ -1398,7 +1408,7 @@ const controls = {
// Settings button / menu
if (control === 'settings' && !is.empty(this.config.settings)) {
- const control = createElement(
+ const wrapper = createElement(
'div',
extend({}, defaultAttributes, {
class: `${defaultAttributes.class} plyr__menu`.trim(),
@@ -1406,7 +1416,7 @@ const controls = {
}),
);
- control.appendChild(
+ wrapper.appendChild(
createButton.call(this, 'settings', {
'aria-haspopup': true,
'aria-controls': `plyr-settings-${data.id}`,
@@ -1546,11 +1556,11 @@ const controls = {
});
popup.appendChild(inner);
- control.appendChild(popup);
- container.appendChild(control);
+ wrapper.appendChild(popup);
+ container.appendChild(wrapper);
this.elements.settings.popup = popup;
- this.elements.settings.menu = control;
+ this.elements.settings.menu = wrapper;
}
// Picture in picture button
diff --git a/src/js/fullscreen.js b/src/js/fullscreen.js
index c86bf877..d4d15b23 100644
--- a/src/js/fullscreen.js
+++ b/src/js/fullscreen.js
@@ -73,9 +73,6 @@ function toggleFallback(toggle = false) {
.filter(part => part.trim() !== property)
.join(',');
}
-
- // Force a repaint as sometimes Safari doesn't want to fill the screen
- setTimeout(() => repaint(this.target), 100);
}
// Toggle button and fire events
diff --git a/src/js/html5.js b/src/js/html5.js
index 0d9a64ea..b03e9c26 100644
--- a/src/js/html5.js
+++ b/src/js/html5.js
@@ -44,15 +44,17 @@ const html5 = {
const player = this;
- // Set aspect ratio if set
- setAspectRatio.call(player);
+ // Set aspect ratio if fixed
+ if (!is.empty(this.config.ratio)) {
+ setAspectRatio.call(player);
+ }
// Quality
Object.defineProperty(player.media, 'quality', {
get() {
// Get sources
const sources = html5.getSources.call(player);
- const source = sources.find(source => source.getAttribute('src') === player.source);
+ const source = sources.find(s => s.getAttribute('src') === player.source);
// Return size, if match is found
return source && Number(source.getAttribute('size'));
@@ -61,7 +63,7 @@ const html5 = {
// Get sources
const sources = html5.getSources.call(player);
// Get first match for requested size
- const source = sources.find(source => Number(source.getAttribute('size')) === input);
+ const source = sources.find(s => Number(s.getAttribute('size')) === input);
// No matching source found
if (!source) {
diff --git a/src/js/listeners.js b/src/js/listeners.js
index ca527f1e..c5076ff3 100644
--- a/src/js/listeners.js
+++ b/src/js/listeners.js
@@ -147,7 +147,7 @@ class Listeners {
player.loop = !player.loop;
break;
- /* case 73:
+ /* case 73:
this.setLoop('start');
break;
@@ -275,12 +275,12 @@ class Listeners {
elements.container,
'mousemove mouseleave touchstart touchmove enterfullscreen exitfullscreen',
event => {
- const { controls } = elements;
+ const { controls: controlsElement } = elements;
// Remove button states for fullscreen
- if (controls && event.type === 'enterfullscreen') {
- controls.pressed = false;
- controls.hover = false;
+ if (controlsElement && event.type === 'enterfullscreen') {
+ controlsElement.pressed = false;
+ controlsElement.hover = false;
}
// Show, then hide after a timeout unless another control event occurs
@@ -301,14 +301,6 @@ class Listeners {
},
);
- // Force edge to repaint on exit fullscreen
- // TODO: Fix weird bug where Edge doesn't re-draw when exiting fullscreen
- /* if (browser.isEdge) {
- on.call(player, elements.container, 'exitfullscreen', () => {
- setTimeout(() => repaint(elements.container), 100);
- });
- } */
-
// Set a gutter for Vimeo
const setGutter = (ratio, padding, toggle) => {
if (!player.isVimeo) {
@@ -337,15 +329,20 @@ class Listeners {
};
const resized = () => {
- window.clearTimeout(timers.resized);
- timers.resized = window.setTimeout(setPlayerSize, 50);
+ clearTimeout(timers.resized);
+ timers.resized = setTimeout(setPlayerSize, 50);
};
on.call(player, elements.container, 'enterfullscreen exitfullscreen', event => {
const { target, usingNative } = player.fullscreen;
- // Ignore for iOS native
- if (!player.isEmbed || target !== elements.container) {
+ // Ignore events not from target
+ if (target !== elements.container) {
+ return;
+ }
+
+ // If it's not an embed and no ratio specified
+ if (!player.isEmbed && is.empty(player.config.ratio)) {
return;
}
@@ -801,7 +798,7 @@ class Listeners {
// Show controls when they receive focus (e.g., when using keyboard tab key)
this.bind(elements.controls, 'focusin', () => {
- const { config, elements, timers } = player;
+ const { config, timers } = player;
// Skip transition to prevent focus from scrolling the parent element
toggleClass(elements.controls, config.classNames.noTransition, true);
diff --git a/src/js/plugins/ads.js b/src/js/plugins/ads.js
index 2acfaed9..e6fab967 100644
--- a/src/js/plugins/ads.js
+++ b/src/js/plugins/ads.js
@@ -267,7 +267,7 @@ class Ads {
// Advertisement regular events
Object.keys(google.ima.AdEvent.Type).forEach(type => {
- this.manager.addEventListener(google.ima.AdEvent.Type[type], event => this.onAdEvent(event));
+ this.manager.addEventListener(google.ima.AdEvent.Type[type], e => this.onAdEvent(e));
});
// Resolve our adsManager
@@ -310,8 +310,7 @@ class Ads {
// Proxy event
const dispatchEvent = type => {
- const event = `ads${type.replace(/_/g, '').toLowerCase()}`;
- triggerEvent.call(this.player, this.player.media, event);
+ triggerEvent.call(this.player, this.player.media, `ads${type.replace(/_/g, '').toLowerCase()}`);
};
// Bubble the event
diff --git a/src/js/plugins/previewThumbnails.js b/src/js/plugins/previewThumbnails.js
index f03abe69..67367b95 100644
--- a/src/js/plugins/previewThumbnails.js
+++ b/src/js/plugins/previewThumbnails.js
@@ -2,6 +2,7 @@ import { createElement } from '../utils/elements';
import { once } from '../utils/events';
import fetch from '../utils/fetch';
import is from '../utils/is';
+import { extend } from '../utils/objects';
import { formatTime } from '../utils/time';
// Arg: vttDataString example: "WEBVTT\n\n1\n00:00:05.000 --> 00:00:10.000\n1080p-00001.jpg"
@@ -100,6 +101,10 @@ class PreviewThumbnails {
}
this.getThumbnails().then(() => {
+ if (!this.enabled) {
+ return;
+ }
+
// Render DOM elements
this.render();
@@ -425,7 +430,9 @@ class PreviewThumbnails {
if (image.dataset.index !== currentImage.dataset.index && !image.dataset.deleting) {
// Wait 200ms, as the new image can take some time to show on certain browsers (even though it was downloaded before showing). This will prevent flicker, and show some generosity towards slower clients
// First set attribute 'deleting' to prevent multi-handling of this on repeat firing of this function
+ // eslint-disable-next-line no-param-reassign
image.dataset.deleting = true;
+
// This has to be set before the timeout - to prevent issues switching between hover and scrub
const { currentImageContainer } = this;
@@ -632,9 +639,13 @@ class PreviewThumbnails {
// Find difference between height and preview container height
const multiplier = this.thumbContainerHeight / frame.h;
+ // eslint-disable-next-line no-param-reassign
previewImage.style.height = `${Math.floor(previewImage.naturalHeight * multiplier)}px`;
+ // eslint-disable-next-line no-param-reassign
previewImage.style.width = `${Math.floor(previewImage.naturalWidth * multiplier)}px`;
+ // eslint-disable-next-line no-param-reassign
previewImage.style.left = `-${frame.x * multiplier}px`;
+ // eslint-disable-next-line no-param-reassign
previewImage.style.top = `-${frame.y * multiplier}px`;
}
}
diff --git a/src/js/plugins/youtube.js b/src/js/plugins/youtube.js
index a5b1dafd..34c5de7e 100644
--- a/src/js/plugins/youtube.js
+++ b/src/js/plugins/youtube.js
@@ -56,26 +56,23 @@ const youtube = {
if (is.object(window.YT) && is.function(window.YT.Player)) {
youtube.ready.call(this);
} else {
- // Load the API
- loadScript(this.config.urls.youtube.sdk).catch(error => {
- this.debug.warn('YouTube API failed to load', error);
- });
-
- // Setup callback for the API
- // YouTube has it's own system of course...
- window.onYouTubeReadyCallbacks = window.onYouTubeReadyCallbacks || [];
-
- // Add to queue
- window.onYouTubeReadyCallbacks.push(() => {
- youtube.ready.call(this);
- });
+ // Reference current global callback
+ const callback = window.onYouTubeIframeAPIReady;
// Set callback to process queue
window.onYouTubeIframeAPIReady = () => {
- window.onYouTubeReadyCallbacks.forEach(callback => {
+ // Call global callback if set
+ if (is.function(callback)) {
callback();
- });
+ }
+
+ youtube.ready.call(this);
};
+
+ // Load the SDK
+ loadScript(this.config.urls.youtube.sdk).catch(error => {
+ this.debug.warn('YouTube API failed to load', error);
+ });
}
},
@@ -108,7 +105,7 @@ const youtube = {
ready() {
const player = this;
// Ignore already setup (race condition)
- const currentId = player.media.getAttribute('id');
+ const currentId = player.media && player.media.getAttribute('id');
if (!is.empty(currentId) && currentId.startsWith('youtube-')) {
return;
}
@@ -131,16 +128,16 @@ const youtube = {
player.media = replaceElement(container, player.media);
// Id to poster wrapper
- const posterSrc = format => `https://i.ytimg.com/vi/${videoId}/${format}default.jpg`;
+ const posterSrc = s => `https://i.ytimg.com/vi/${videoId}/${s}default.jpg`;
// Check thumbnail images in order of quality, but reject fallback thumbnails (120px wide)
loadImage(posterSrc('maxres'), 121) // Higest quality and unpadded
.catch(() => loadImage(posterSrc('sd'), 121)) // 480p padded 4:3
.catch(() => loadImage(posterSrc('hq'))) // 360p padded 4:3. Always exists
.then(image => ui.setPoster.call(player, image.src))
- .then(posterSrc => {
+ .then(src => {
// If the image is padded, use background-size "cover" instead (like youtube does too with their posters)
- if (!posterSrc.includes('maxres')) {
+ if (!src.includes('maxres')) {
player.elements.poster.style.backgroundSize = 'cover';
}
})
diff --git a/src/js/plyr.js b/src/js/plyr.js
index 5ec5bf2e..84fa87fa 100644
--- a/src/js/plyr.js
+++ b/src/js/plyr.js
@@ -323,27 +323,27 @@ class Plyr {
* Types and provider helpers
*/
get isHTML5() {
- return Boolean(this.provider === providers.html5);
+ return this.provider === providers.html5;
}
get isEmbed() {
- return Boolean(this.isYouTube || this.isVimeo);
+ return this.isYouTube || this.isVimeo;
}
get isYouTube() {
- return Boolean(this.provider === providers.youtube);
+ return this.provider === providers.youtube;
}
get isVimeo() {
- return Boolean(this.provider === providers.vimeo);
+ return this.provider === providers.vimeo;
}
get isVideo() {
- return Boolean(this.type === types.video);
+ return this.type === types.video;
}
get isAudio() {
- return Boolean(this.type === types.audio);
+ return this.type === types.audio;
}
/**
diff --git a/src/js/ui.js b/src/js/ui.js
index 50de7df1..df52eb64 100644
--- a/src/js/ui.js
+++ b/src/js/ui.js
@@ -213,7 +213,7 @@ const ui = {
// Set state
Array.from(this.elements.buttons.play || []).forEach(target => {
- target.pressed = this.playing;
+ Object.assign(target, { pressed: this.playing });
});
// Only update controls on non timeupdate events
@@ -247,15 +247,22 @@ const ui = {
// Toggle controls based on state and `force` argument
toggleControls(force) {
- const { controls } = this.elements;
+ const { controls: controlsElement } = this.elements;
- if (controls && this.config.hideControls) {
+ if (controlsElement && this.config.hideControls) {
// Don't hide controls if a touch-device user recently seeked. (Must be limited to touch devices, or it occasionally prevents desktop controls from hiding.)
const recentTouchSeek = this.touch && this.lastSeekTime + 2000 > Date.now();
// Show controls if force, loading, paused, button interaction, or recent seek, otherwise hide
this.toggleControls(
- Boolean(force || this.loading || this.paused || controls.pressed || controls.hover || recentTouchSeek),
+ Boolean(
+ force ||
+ this.loading ||
+ this.paused ||
+ controlsElement.pressed ||
+ controlsElement.hover ||
+ recentTouchSeek,
+ ),
);
}
},
diff --git a/src/js/utils/animation.js b/src/js/utils/animation.js
index 6b950b61..3f721b5a 100644
--- a/src/js/utils/animation.js
+++ b/src/js/utils/animation.js
@@ -2,7 +2,6 @@
// Animation utils
// ==========================================================================
-import { toggleHidden } from './elements';
import is from './is';
export const transitionEndEvent = (() => {
@@ -21,14 +20,19 @@ export const transitionEndEvent = (() => {
})();
// Force repaint of element
-export function repaint(element) {
+export function repaint(element, delay) {
setTimeout(() => {
try {
- toggleHidden(element, true);
- element.offsetHeight; // eslint-disable-line
- toggleHidden(element, false);
+ // eslint-disable-next-line no-param-reassign
+ element.hidden = true;
+
+ // eslint-disable-next-line no-unused-expressions
+ element.offsetHeight;
+
+ // eslint-disable-next-line no-param-reassign
+ element.hidden = false;
} catch (e) {
// Do nothing
}
- }, 0);
+ }, delay);
}
diff --git a/src/js/utils/elements.js b/src/js/utils/elements.js
index 94744771..4f10938e 100644
--- a/src/js/utils/elements.js
+++ b/src/js/utils/elements.js
@@ -192,11 +192,8 @@ export function toggleHidden(element, hidden) {
hide = !element.hidden;
}
- if (hide) {
- element.setAttribute('hidden', '');
- } else {
- element.removeAttribute('hidden');
- }
+ // eslint-disable-next-line no-param-reassign
+ element.hidden = hide;
}
// Mirror Element.classList.toggle, with IE compatibility for "force" argument
@@ -231,14 +228,14 @@ export function matches(element, selector) {
return Array.from(document.querySelectorAll(selector)).includes(this);
}
- const matches =
+ const method =
prototype.matches ||
prototype.webkitMatchesSelector ||
prototype.mozMatchesSelector ||
prototype.msMatchesSelector ||
match;
- return matches.call(element, selector);
+ return method.call(element, selector);
}
// Find all elements
diff --git a/src/js/utils/i18n.js b/src/js/utils/i18n.js
index 758ed695..5eee5829 100644
--- a/src/js/utils/i18n.js
+++ b/src/js/utils/i18n.js
@@ -36,8 +36,8 @@ const i18n = {
'{title}': config.title,
};
- Object.entries(replace).forEach(([key, value]) => {
- string = replaceAll(string, key, value);
+ Object.entries(replace).forEach(([k, v]) => {
+ string = replaceAll(string, k, v);
});
return string;
diff --git a/src/js/utils/loadSprite.js b/src/js/utils/loadSprite.js
index 092f9986..fe4add00 100644
--- a/src/js/utils/loadSprite.js
+++ b/src/js/utils/loadSprite.js
@@ -18,6 +18,7 @@ export default function loadSprite(url, id) {
const exists = () => document.getElementById(id) !== null;
const update = (container, data) => {
+ // eslint-disable-next-line no-param-reassign
container.innerHTML = data;
// Check again incase of race condition
diff --git a/src/js/utils/style.js b/src/js/utils/style.js
index 6f3069c9..941db8f2 100644
--- a/src/js/utils/style.js
+++ b/src/js/utils/style.js
@@ -27,15 +27,8 @@ export function reduceAspectRatio(ratio) {
}
export function getAspectRatio(input) {
- const parse = ratio => {
- if (!validateRatio(ratio)) {
- return null;
- }
-
- return ratio.split(':').map(Number);
- };
-
- // Provided ratio
+ const parse = ratio => (validateRatio(ratio) ? ratio.split(':').map(Number) : null);
+ // Try provided ratio
let ratio = parse(input);
// Get from config