aboutsummaryrefslogtreecommitdiffstats
path: root/src/js/plugins
diff options
context:
space:
mode:
authorSam Potts <sam@potts.es>2020-11-14 13:24:11 +1100
committerGitHub <noreply@github.com>2020-11-14 13:24:11 +1100
commite8d883edba3ee87ff5fbef043ffa50a1a4ae391b (patch)
tree02c0ecff98f37ed530512de0b806090c0cdd6c43 /src/js/plugins
parent5d2c288721bd8d7be77352cf1f8f81c2592aeed5 (diff)
downloadplyr-e8d883edba3ee87ff5fbef043ffa50a1a4ae391b.tar.lz
plyr-e8d883edba3ee87ff5fbef043ffa50a1a4ae391b.tar.xz
plyr-e8d883edba3ee87ff5fbef043ffa50a1a4ae391b.zip
v3.6.3 (#2016)
* force fullscreen events to trigger on plyr element (media element in iOS) and not fullscreen container * Fixing "missing code in detail" for PlyrEvent type When using typescript and listening for youtube statechange event, it is missing the code property definition inside the event (even though it is provided in the code). By making events a map of key-value, we can add easily custom event type for specific event name. Since YouTube "statechange" event differs from the basic PlyrEvent, I added a new Event Type "PlyrStateChangeEvent" having a code property corresponding to a YoutubeState enum defined by the YouTube API documentation. This pattern follows how addEventListener in the lib.dom.d.ts is defined. * Update link to working dash.js demo (was broken) * Fix PreviewThumbnailsOptions type According to the docs, the `src` should also accept an array of strings. * fix issue #1872 * Check if key is a string before attempt --plyr checking * Fix for Slow loading videos not autoplaying * Fix for Slow loading videos not autoplaying * Network requests are not cancelled after the player is destroyed * Fix for apect ratio problem when using Vimeo player on mobile devices (issue #1940) * chore: update packages and linting * Invoke custom listener on triggering fullscreen via double-click * Fix volume when unmuting from volume 0 * adding a nice Svelte plugin that I found * Add missing unit to calc in media query * Assigning player's lastSeekTime on rewind/fast forward to prevent immediate controls hide on mobile * Fix youtube not working when player is inside shadow dom * v3.6.2 * ESLint to use common config * add BitChute to users list * Fix aspect ratio issue * Revert noCookie change * feat: demo radius tweaks * fix: poster image shouldn’t receive click events * chore: package updates * chore: linting * feat: custom controls option for embedded players * Package upgrades * ESLint to use common config * Linting changes * Update README.md * chore: formatting * fix: revert pointer events change for poster * fix: hack for Safari 14 not repainting Vimeo embed on entering fullscreen * fix: demo using custom controls for YouTube * doc: Add STROLLÿN among the list of Plyr users * Fixes #2005 * fix: overflowing volume slider * chore: clean up CSS * fix: hide poster when not using custom controls * Package upgrades * ESLint to use common config * Linting changes * chore: revert customControls default option (to prevent breaking change) * docs: changelog for v3.6.3 Co-authored-by: Som Meaden <som@theprojectsomething.com> Co-authored-by: akuma06 <demon.akuma06@gmail.com> Co-authored-by: Jonathan Arbely <dev@jonathanarbely.de> Co-authored-by: Takeshi <iwatakeshi@users.noreply.github.com> Co-authored-by: Hex <hex@codeigniter.org.cn> Co-authored-by: Syed Husain <syed.husain@appspace.com> Co-authored-by: Danielh112 <Daniel@sbgsportssoftware.com> Co-authored-by: Danil Stoyanov <d.stoyanov@corp.mail.ru> Co-authored-by: Guru Prasad Srinivasa <gurupras@buffalo.edu> Co-authored-by: Stephane Fortin Bouchard <stephane.f.bouchard@gmail.com> Co-authored-by: Zev Averbach <zev@averba.ch> Co-authored-by: Vincent Orback <hello@vincentorback.se> Co-authored-by: trafium <trafium@gmail.com> Co-authored-by: xansen <27698939+xansen@users.noreply.github.com> Co-authored-by: zoomerdev <59863739+zoomerdev@users.noreply.github.com> Co-authored-by: Mikaël Castellani <mikael.castellani@gmail.com> Co-authored-by: dirkjf <d.j.faber@outlook.com>
Diffstat (limited to 'src/js/plugins')
-rw-r--r--src/js/plugins/ads.js20
-rw-r--r--src/js/plugins/preview-thumbnails.js24
-rw-r--r--src/js/plugins/vimeo.js64
-rw-r--r--src/js/plugins/youtube.js71
4 files changed, 96 insertions, 83 deletions
diff --git a/src/js/plugins/ads.js b/src/js/plugins/ads.js
index 1a52ebce..12b5cc31 100644
--- a/src/js/plugins/ads.js
+++ b/src/js/plugins/ads.js
@@ -15,7 +15,7 @@ import { silencePromise } from '../utils/promise';
import { formatTime } from '../utils/time';
import { buildUrlParams } from '../utils/urls';
-const destroy = instance => {
+const destroy = (instance) => {
// Destroy our adsManager
if (instance.manager) {
instance.manager.destroy();
@@ -179,10 +179,10 @@ class Ads {
// Listen and respond to ads loaded and error events
this.loader.addEventListener(
google.ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED,
- event => this.onAdsManagerLoaded(event),
+ (event) => this.onAdsManagerLoaded(event),
false,
);
- this.loader.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, error => this.onAdError(error), false);
+ this.loader.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, (error) => this.onAdError(error), false);
// Request video ads to be pre-loaded
this.requestAds();
@@ -264,11 +264,11 @@ class Ads {
// Add listeners to the required events
// Advertisement error events
- this.manager.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, error => this.onAdError(error));
+ this.manager.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, (error) => this.onAdError(error));
// Advertisement regular events
- Object.keys(google.ima.AdEvent.Type).forEach(type => {
- this.manager.addEventListener(google.ima.AdEvent.Type[type], e => this.onAdEvent(e));
+ Object.keys(google.ima.AdEvent.Type).forEach((type) => {
+ this.manager.addEventListener(google.ima.AdEvent.Type[type], (e) => this.onAdEvent(e));
});
// Resolve our adsManager
@@ -278,7 +278,7 @@ class Ads {
addCuePoints() {
// Add advertisement cue's within the time line if available
if (!is.empty(this.cuePoints)) {
- this.cuePoints.forEach(cuePoint => {
+ this.cuePoints.forEach((cuePoint) => {
if (cuePoint !== 0 && cuePoint !== -1 && cuePoint < this.player.duration) {
const seekElement = this.player.elements.progress;
@@ -310,7 +310,7 @@ class Ads {
const adData = event.getAdData();
// Proxy event
- const dispatchEvent = type => {
+ const dispatchEvent = (type) => {
triggerEvent.call(this.player, this.player.media, `ads${type.replace(/_/g, '').toLowerCase()}`);
};
@@ -565,7 +565,7 @@ class Ads {
}
// Re-set our adsManager promises
- this.managerPromise = new Promise(resolve => {
+ this.managerPromise = new Promise((resolve) => {
this.on('loaded', resolve);
this.player.debug.log(this.manager);
});
@@ -586,7 +586,7 @@ class Ads {
const handlers = this.events[event];
if (is.array(handlers)) {
- handlers.forEach(handler => {
+ handlers.forEach((handler) => {
if (is.function(handler)) {
handler.apply(this, args);
}
diff --git a/src/js/plugins/preview-thumbnails.js b/src/js/plugins/preview-thumbnails.js
index 6ce53f28..16167247 100644
--- a/src/js/plugins/preview-thumbnails.js
+++ b/src/js/plugins/preview-thumbnails.js
@@ -5,15 +5,15 @@ import is from '../utils/is';
import { formatTime } from '../utils/time';
// Arg: vttDataString example: "WEBVTT\n\n1\n00:00:05.000 --> 00:00:10.000\n1080p-00001.jpg"
-const parseVtt = vttDataString => {
+const parseVtt = (vttDataString) => {
const processedList = [];
const frames = vttDataString.split(/\r\n\r\n|\n\n|\r\r/);
- frames.forEach(frame => {
+ frames.forEach((frame) => {
const result = {};
const lines = frame.split(/\r\n|\n|\r/);
- lines.forEach(line => {
+ lines.forEach((line) => {
if (!is.number(result.startTime)) {
// The line with start and end times on it is the first line of interest
const matchTimes = line.match(
@@ -130,7 +130,7 @@ class PreviewThumbnails {
// Download VTT files and parse them
getThumbnails() {
- return new Promise(resolve => {
+ return new Promise((resolve) => {
const { src } = this.player.config.previewThumbnails;
if (is.empty(src)) {
@@ -149,7 +149,7 @@ class PreviewThumbnails {
// Via callback()
if (is.function(src)) {
- src(thumbnails => {
+ src((thumbnails) => {
this.thumbnails = thumbnails;
sortAndResolve();
});
@@ -159,7 +159,7 @@ class PreviewThumbnails {
// If string, convert into single-element list
const urls = is.string(src) ? [src] : src;
// Loop through each src URL. Download and process the VTT file, storing the resulting data in this.thumbnails
- const promises = urls.map(u => this.getThumbnail(u));
+ const promises = urls.map((u) => this.getThumbnail(u));
// Resolve
Promise.all(promises).then(sortAndResolve);
}
@@ -168,8 +168,8 @@ class PreviewThumbnails {
// Process individual VTT file
getThumbnail(url) {
- return new Promise(resolve => {
- fetch(url).then(response => {
+ return new Promise((resolve) => {
+ fetch(url).then((response) => {
const thumbnail = {
frames: parseVtt(response),
height: null,
@@ -360,7 +360,7 @@ class PreviewThumbnails {
// Find the desired thumbnail index
// TODO: Handle a video longer than the thumbs where thumbNum is null
const thumbNum = this.thumbnails[0].frames.findIndex(
- frame => this.seekTime >= frame.startTime && this.seekTime <= frame.endTime,
+ (frame) => this.seekTime >= frame.startTime && this.seekTime <= frame.endTime,
);
const hasThumb = thumbNum >= 0;
let qualityIndex = 0;
@@ -454,7 +454,7 @@ class PreviewThumbnails {
// Remove all preview images that aren't the designated current image
removeOldImages(currentImage) {
// Get a list of all images, convert it from a DOM list to an array
- Array.from(this.currentImageContainer.children).forEach(image => {
+ Array.from(this.currentImageContainer.children).forEach((image) => {
if (image.tagName.toLowerCase() !== 'img') {
return;
}
@@ -481,7 +481,7 @@ class PreviewThumbnails {
// Preload images before and after the current one. Only if the user is still hovering/seeking the same frame
// This will only preload the lowest quality
preloadNearby(thumbNum, forward = true) {
- return new Promise(resolve => {
+ return new Promise((resolve) => {
setTimeout(() => {
const oldThumbFilename = this.thumbnails[0].frames[thumbNum].text;
@@ -496,7 +496,7 @@ class PreviewThumbnails {
let foundOne = false;
- thumbnailsClone.forEach(frame => {
+ thumbnailsClone.forEach((frame) => {
const newThumbFilename = frame.text;
if (newThumbFilename !== oldThumbFilename) {
diff --git a/src/js/plugins/vimeo.js b/src/js/plugins/vimeo.js
index 33c327d7..b050cc53 100644
--- a/src/js/plugins/vimeo.js
+++ b/src/js/plugins/vimeo.js
@@ -58,7 +58,7 @@ const vimeo = {
.then(() => {
vimeo.ready.call(player);
})
- .catch(error => {
+ .catch((error) => {
player.debug.warn('Vimeo SDK (player.js) failed to load', error);
});
} else {
@@ -112,25 +112,29 @@ const vimeo = {
}
// Inject the package
- const { poster } = player;
- if (premium) {
- iframe.setAttribute('data-poster', poster);
+ if (premium || !config.customControls) {
+ iframe.setAttribute('data-poster', player.poster);
player.media = replaceElement(iframe, player.media);
} else {
- const wrapper = createElement('div', { class: player.config.classNames.embedContainer, 'data-poster': poster });
+ const wrapper = createElement('div', {
+ class: player.config.classNames.embedContainer,
+ 'data-poster': player.poster,
+ });
wrapper.appendChild(iframe);
player.media = replaceElement(wrapper, player.media);
}
-
+
// Get poster image
- fetch(format(player.config.urls.vimeo.api, src)).then(response => {
- if (is.empty(response) || !response.thumbnail_url) {
- return;
- }
-
- // Set and show poster
- ui.setPoster.call(player, response.thumbnail_url).catch(() => { });
- });
+ if (!config.customControls) {
+ fetch(format(player.config.urls.vimeo.api, src)).then((response) => {
+ if (is.empty(response) || !response.thumbnail_url) {
+ return;
+ }
+
+ // Set and show poster
+ ui.setPoster.call(player, response.thumbnail_url).catch(() => {});
+ });
+ }
// Setup instance
// https://github.com/vimeo/player.js
@@ -263,11 +267,11 @@ const vimeo = {
let currentSrc;
player.embed
.getVideoUrl()
- .then(value => {
+ .then((value) => {
currentSrc = value;
controls.setDownloadUrl.call(player);
})
- .catch(error => {
+ .catch((error) => {
this.debug.warn(error);
});
@@ -285,49 +289,49 @@ const vimeo = {
});
// Set aspect ratio based on video size
- Promise.all([player.embed.getVideoWidth(), player.embed.getVideoHeight()]).then(dimensions => {
+ Promise.all([player.embed.getVideoWidth(), player.embed.getVideoHeight()]).then((dimensions) => {
const [width, height] = dimensions;
player.embed.ratio = [width, height];
setAspectRatio.call(this);
});
// Set autopause
- player.embed.setAutopause(player.config.autopause).then(state => {
+ player.embed.setAutopause(player.config.autopause).then((state) => {
player.config.autopause = state;
});
// Get title
- player.embed.getVideoTitle().then(title => {
+ player.embed.getVideoTitle().then((title) => {
player.config.title = title;
ui.setTitle.call(this);
});
// Get current time
- player.embed.getCurrentTime().then(value => {
+ player.embed.getCurrentTime().then((value) => {
currentTime = value;
triggerEvent.call(player, player.media, 'timeupdate');
});
// Get duration
- player.embed.getDuration().then(value => {
+ player.embed.getDuration().then((value) => {
player.media.duration = value;
triggerEvent.call(player, player.media, 'durationchange');
});
// Get captions
- player.embed.getTextTracks().then(tracks => {
+ player.embed.getTextTracks().then((tracks) => {
player.media.textTracks = tracks;
captions.setup.call(player);
});
player.embed.on('cuechange', ({ cues = [] }) => {
- const strippedCues = cues.map(cue => stripHTML(cue.text));
+ const strippedCues = cues.map((cue) => stripHTML(cue.text));
captions.updateCues.call(player, strippedCues);
});
player.embed.on('loaded', () => {
// Assure state and events are updated on autoplay
- player.embed.getPaused().then(paused => {
+ player.embed.getPaused().then((paused) => {
assurePlaybackState.call(player, !paused);
if (!paused) {
triggerEvent.call(player, player.media, 'playing');
@@ -360,13 +364,13 @@ const vimeo = {
assurePlaybackState.call(player, false);
});
- player.embed.on('timeupdate', data => {
+ player.embed.on('timeupdate', (data) => {
player.media.seeking = false;
currentTime = data.seconds;
triggerEvent.call(player, player.media, 'timeupdate');
});
- player.embed.on('progress', data => {
+ player.embed.on('progress', (data) => {
player.media.buffered = data.percent;
triggerEvent.call(player, player.media, 'progress');
@@ -377,7 +381,7 @@ const vimeo = {
// Get duration as if we do it before load, it gives an incorrect value
// https://github.com/sampotts/plyr/issues/891
- player.embed.getDuration().then(value => {
+ player.embed.getDuration().then((value) => {
if (value !== player.media.duration) {
player.media.duration = value;
triggerEvent.call(player, player.media, 'durationchange');
@@ -395,13 +399,15 @@ const vimeo = {
triggerEvent.call(player, player.media, 'ended');
});
- player.embed.on('error', detail => {
+ player.embed.on('error', (detail) => {
player.media.error = detail;
triggerEvent.call(player, player.media, 'error');
});
// Rebuild UI
- setTimeout(() => ui.build.call(player), 0);
+ if (config.customControls) {
+ setTimeout(() => ui.build.call(player), 0);
+ }
},
};
diff --git a/src/js/plugins/youtube.js b/src/js/plugins/youtube.js
index 89a75d89..db5781e6 100644
--- a/src/js/plugins/youtube.js
+++ b/src/js/plugins/youtube.js
@@ -70,7 +70,7 @@ const youtube = {
};
// Load the SDK
- loadScript(this.config.urls.youtube.sdk).catch(error => {
+ loadScript(this.config.urls.youtube.sdk).catch((error) => {
this.debug.warn('YouTube API failed to load', error);
});
}
@@ -81,7 +81,7 @@ const youtube = {
const url = format(this.config.urls.youtube.api, videoId);
fetch(url)
- .then(data => {
+ .then((data) => {
if (is.object(data)) {
const { title, height, width } = data;
@@ -104,6 +104,7 @@ const youtube = {
// API ready
ready() {
const player = this;
+ const config = player.config.youtube;
// Ignore already setup (race condition)
const currentId = player.media && player.media.getAttribute('id');
if (!is.empty(currentId) && currentId.startsWith('youtube-')) {
@@ -121,43 +122,46 @@ const youtube = {
// Replace the <iframe> with a <div> due to YouTube API issues
const videoId = parseId(source);
const id = generateId(player.provider);
- // Get poster, if already set
- const { poster } = player;
// Replace media element
- const container = createElement('div', { id, 'data-poster': poster });
+ const container = createElement('div', { id, 'data-poster': config.customControls ? player.poster : undefined });
player.media = replaceElement(container, player.media);
- // Id to poster wrapper
- 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(src => {
- // If the image is padded, use background-size "cover" instead (like youtube does too with their posters)
- if (!src.includes('maxres')) {
- player.elements.poster.style.backgroundSize = 'cover';
- }
- })
- .catch(() => {});
-
- const config = player.config.youtube;
+ // Only load the poster when using custom controls
+ if (config.customControls) {
+ 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((src) => {
+ // If the image is padded, use background-size "cover" instead (like youtube does too with their posters)
+ if (!src.includes('maxres')) {
+ player.elements.poster.style.backgroundSize = 'cover';
+ }
+ })
+ .catch(() => {});
+ }
// Setup instance
// https://developers.google.com/youtube/iframe_api_reference
- player.embed = new window.YT.Player(id, {
+ player.embed = new window.YT.Player(player.media, {
videoId,
host: getHost(config),
playerVars: extend(
{},
{
- autoplay: player.config.autoplay ? 1 : 0, // Autoplay
- hl: player.config.hl, // iframe interface language
- controls: player.supported.ui ? 0 : 1, // Only show controls if not fully supported
- disablekb: 1, // Disable keyboard as we handle it
- playsinline: !player.config.fullscreen.iosNative ? 1 : 0, // Allow iOS inline playback
+ // Autoplay
+ autoplay: player.config.autoplay ? 1 : 0,
+ // iframe interface language
+ hl: player.config.hl,
+ // Only show controls if not fully supported or opted out
+ controls: player.supported.ui && config.customControls ? 0 : 1,
+ // Disable keyboard as we handle it
+ disablekb: 1,
+ // Allow iOS inline playback
+ playsinline: !player.config.fullscreen.iosNative ? 1 : 0,
// Captions are flaky on YouTube
cc_load_policy: player.captions.active ? 1 : 0,
cc_lang_pref: player.config.captions.language,
@@ -278,6 +282,7 @@ const youtube = {
const toggle = is.boolean(input) ? input : muted;
muted = toggle;
instance[toggle ? 'mute' : 'unMute']();
+ instance.setVolume(volume * 100);
triggerEvent.call(player, player.media, 'volumechange');
},
});
@@ -299,10 +304,10 @@ const youtube = {
// Get available speeds
const speeds = instance.getAvailablePlaybackRates();
// Filter based on config
- player.options.speed = speeds.filter(s => player.config.speed.options.includes(s));
+ player.options.speed = speeds.filter((s) => player.config.speed.options.includes(s));
// Set the tabindex to avoid focus entering iframe
- if (player.supported.ui) {
+ if (player.supported.ui && config.customControls) {
player.media.setAttribute('tabindex', -1);
}
@@ -335,7 +340,9 @@ const youtube = {
}, 200);
// Rebuild UI
- setTimeout(() => ui.build.call(player), 50);
+ if (config.customControls) {
+ setTimeout(() => ui.build.call(player), 50);
+ }
},
onStateChange(event) {
// Get the instance
@@ -386,7 +393,7 @@ const youtube = {
case 1:
// Restore paused state (YouTube starts playing on seek if the video hasn't been played yet)
- if (!player.config.autoplay && player.media.paused && !player.embed.hasPlayed) {
+ if (config.customControls && !player.config.autoplay && player.media.paused && !player.embed.hasPlayed) {
player.media.pause();
} else {
assurePlaybackState.call(player, true);