aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/js/config/defaults.js9
-rw-r--r--src/js/fullscreen.js4
-rw-r--r--src/js/plugins/vimeo.js38
-rw-r--r--src/js/plugins/youtube.js61
-rw-r--r--src/js/plyr.d.ts109
-rw-r--r--src/js/plyr.js6
-rw-r--r--src/js/ui.js2
-rw-r--r--src/js/utils/style.js6
-rw-r--r--src/sass/base.scss1
-rw-r--r--src/sass/components/poster.scss1
10 files changed, 141 insertions, 96 deletions
diff --git a/src/js/config/defaults.js b/src/js/config/defaults.js
index 8f5d081b..80ab190f 100644
--- a/src/js/config/defaults.js
+++ b/src/js/config/defaults.js
@@ -422,20 +422,23 @@ const defaults = {
title: false,
speed: true,
transparent: false,
+ // Custom settings from Plyr
+ customControls: false,
+ referrerPolicy: null, // https://developer.mozilla.org/en-US/docs/Web/API/HTMLIFrameElement/referrerPolicy
// Whether the owner of the video has a Pro or Business account
// (which allows us to properly hide controls without CSS hacks, etc)
premium: false,
- // Custom settings from Plyr
- referrerPolicy: null, // https://developer.mozilla.org/en-US/docs/Web/API/HTMLIFrameElement/referrerPolicy
},
// YouTube plugin
youtube: {
- noCookie: true, // Whether to use an alternative version of YouTube without cookies
rel: 0, // No related vids
showinfo: 0, // Hide info
iv_load_policy: 3, // Hide annotations
modestbranding: 1, // Hide logos as much as possible (they still show one in the corner when paused)
+ // Custom settings from Plyr
+ customControls: false,
+ noCookie: false, // Whether to use an alternative version of YouTube without cookies
},
};
diff --git a/src/js/fullscreen.js b/src/js/fullscreen.js
index d545d144..8bc58c00 100644
--- a/src/js/fullscreen.js
+++ b/src/js/fullscreen.js
@@ -145,8 +145,10 @@ class Fullscreen {
button.pressed = this.active;
}
+ // Always trigger events on the plyr / media element (not a fullscreen container) and let them bubble up
+ const target = this.target === this.player.media ? this.target : this.player.elements.container;
// Trigger an event
- triggerEvent.call(this.player, this.target, this.active ? 'enterfullscreen' : 'exitfullscreen', true);
+ triggerEvent.call(this.player, target, this.active ? 'enterfullscreen' : 'exitfullscreen', true);
}
toggleFallback(toggle = false) {
diff --git a/src/js/plugins/vimeo.js b/src/js/plugins/vimeo.js
index 2e12d160..cd19b097 100644
--- a/src/js/plugins/vimeo.js
+++ b/src/js/plugins/vimeo.js
@@ -112,31 +112,35 @@ 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, id), 'json').then((response) => {
- if (is.empty(response)) {
- return;
- }
+ if (!config.customControls) {
+ fetch(format(player.config.urls.vimeo.api, id), 'json').then((response) => {
+ if (is.empty(response)) {
+ return;
+ }
- // Get the URL for thumbnail
- const url = new URL(response[0].thumbnail_large);
+ // Get the URL for thumbnail
+ const url = new URL(response[0].thumbnail_large);
- // Get original image
- url.pathname = `${url.pathname.split('_')[0]}.jpg`;
+ // Get original image
+ url.pathname = `${url.pathname.split('_')[0]}.jpg`;
- // Set and show poster
- ui.setPoster.call(player, url.href).catch(() => {});
- });
+ // Set and show poster
+ ui.setPoster.call(player, url.href).catch(() => {});
+ });
+ }
// Setup instance
// https://github.com/vimeo/player.js
@@ -407,7 +411,9 @@ const vimeo = {
});
// 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 88601d5e..cc604e38 100644
--- a/src/js/plugins/youtube.js
+++ b/src/js/plugins/youtube.js
@@ -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,29 +122,26 @@ 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;
+ 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
@@ -153,11 +151,16 @@ const youtube = {
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,
@@ -302,7 +305,7 @@ const youtube = {
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 +338,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 +391,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);
diff --git a/src/js/plyr.d.ts b/src/js/plyr.d.ts
index 13523b35..4b332aeb 100644
--- a/src/js/plyr.d.ts
+++ b/src/js/plyr.d.ts
@@ -214,26 +214,17 @@ declare class Plyr {
/**
* Add an event listener for the specified event.
*/
- on(
- event: Plyr.StandardEvent | Plyr.Html5Event | Plyr.YoutubeEvent,
- callback: (this: this, event: Plyr.PlyrEvent) => void,
- ): void;
+ on<K extends keyof Plyr.PlyrEventMap>(event: K, callback: (this: this, event: Plyr.PlyrEventMap[K]) => void): void;
/**
* Add an event listener for the specified event once.
*/
- once(
- event: Plyr.StandardEvent | Plyr.Html5Event | Plyr.YoutubeEvent,
- callback: (this: this, event: Plyr.PlyrEvent) => void,
- ): void;
+ once<K extends keyof Plyr.PlyrEventMap>(event: K, callback: (this: this, event: Plyr.PlyrEventMap[K]) => void): void;
/**
* Remove an event listener for the specified event.
*/
- off(
- event: Plyr.StandardEvent | Plyr.Html5Event | Plyr.YoutubeEvent,
- callback: (this: this, event: Plyr.PlyrEvent) => void,
- ): void;
+ off<K extends keyof Plyr.PlyrEventMap>(event: K, callback: (this: this, event: Plyr.PlyrEventMap[K]) => void): void;
/**
* Check support for a mime type.
@@ -249,37 +240,51 @@ declare class Plyr {
declare namespace Plyr {
type MediaType = 'audio' | 'video';
type Provider = 'html5' | 'youtube' | 'vimeo';
- type StandardEvent =
- | 'progress'
- | 'playing'
- | 'play'
- | 'pause'
- | 'timeupdate'
- | 'volumechange'
- | 'seeking'
- | 'seeked'
- | 'ratechange'
- | 'ended'
- | 'enterfullscreen'
- | 'exitfullscreen'
- | 'captionsenabled'
- | 'captionsdisabled'
- | 'languagechange'
- | 'controlshidden'
- | 'controlsshown'
- | 'ready';
- type Html5Event =
- | 'loadstart'
- | 'loadeddata'
- | 'loadedmetadata'
- | 'canplay'
- | 'canplaythrough'
- | 'stalled'
- | 'waiting'
- | 'emptied'
- | 'cuechange'
- | 'error';
- type YoutubeEvent = 'statechange' | 'qualitychange' | 'qualityrequested';
+ type StandardEventMap = {
+ progress: PlyrEvent;
+ playing: PlyrEvent;
+ play: PlyrEvent;
+ pause: PlyrEvent;
+ timeupdate: PlyrEvent;
+ volumechange: PlyrEvent;
+ seeking: PlyrEvent;
+ seeked: PlyrEvent;
+ ratechange: PlyrEvent;
+ ended: PlyrEvent;
+ enterfullscreen: PlyrEvent;
+ exitfullscreen: PlyrEvent;
+ captionsenabled: PlyrEvent;
+ captionsdisabled: PlyrEvent;
+ languagechange: PlyrEvent;
+ controlshidden: PlyrEvent;
+ controlsshown: PlyrEvent;
+ ready: PlyrEvent;
+ };
+ // For retrocompatibility, we keep StandadEvent
+ type StandadEvent = keyof Plyr.StandardEventMap;
+ type Html5EventMap = {
+ loadstart: PlyrEvent;
+ loadeddata: PlyrEvent;
+ loadedmetadata: PlyrEvent;
+ canplay: PlyrEvent;
+ canplaythrough: PlyrEvent;
+ stalled: PlyrEvent;
+ waiting: PlyrEvent;
+ emptied: PlyrEvent;
+ cuechange: PlyrEvent;
+ error: PlyrEvent;
+ };
+ // For retrocompatibility, we keep Html5Event
+ type Html5Event = keyof Plyr.Html5EventMap;
+ type YoutubeEventMap = {
+ statechange: PlyrStateChangeEvent;
+ qualitychange: PlyrEvent;
+ qualityrequested: PlyrEvent;
+ };
+ // For retrocompatibility, we keep YoutubeEvent
+ type YoutubeEvent = keyof Plyr.YoutubeEventMap;
+
+ type PlyrEventMap = StandardEventMap & Html5EventMap & YoutubeEventMap;
interface FullscreenControl {
/**
@@ -552,7 +557,7 @@ declare namespace Plyr {
interface PreviewThumbnailsOptions {
enabled?: boolean;
- src?: string;
+ src?: string | string[];
}
interface SourceInfo {
@@ -623,6 +628,22 @@ declare namespace Plyr {
readonly detail: { readonly plyr: Plyr };
}
+ enum YoutubeState {
+ UNSTARTED = -1,
+ ENDED = 0,
+ PLAYING = 1,
+ PAUSED = 2,
+ BUFFERING = 3,
+ CUED = 5,
+ }
+
+ interface PlyrStateChangeEvent extends CustomEvent {
+ readonly detail: {
+ readonly plyr: Plyr;
+ readonly code: YoutubeState;
+ };
+ }
+
interface Support {
api: boolean;
ui: boolean;
diff --git a/src/js/plyr.js b/src/js/plyr.js
index 420910a0..f1dcc68a 100644
--- a/src/js/plyr.js
+++ b/src/js/plyr.js
@@ -12,6 +12,7 @@ import { getProviderByUrl, providers, types } from './config/types';
import Console from './console';
import controls from './controls';
import Fullscreen from './fullscreen';
+import html5 from './html5';
import Listeners from './listeners';
import media from './media';
import Ads from './plugins/ads';
@@ -308,7 +309,7 @@ class Plyr {
// Autoplay if required
if (this.isHTML5 && this.config.autoplay) {
- setTimeout(() => silencePromise(this.play()), 10);
+ this.once('canplay', () => silencePromise(this.play()));
}
// Seek time will be recorded (in listeners.js) so we can prevent hiding controls for a few seconds after seek
@@ -1145,6 +1146,9 @@ class Plyr {
// Unbind listeners
unbindListeners.call(this);
+ // Cancel current network requests
+ html5.cancelRequests.call(this);
+
// Replace the container with the original element provided
replaceElement(this.elements.original, this.elements.container);
diff --git a/src/js/ui.js b/src/js/ui.js
index f5868788..f882fc2b 100644
--- a/src/js/ui.js
+++ b/src/js/ui.js
@@ -270,7 +270,7 @@ const ui = {
// Loop through values (as they are the keys when the object is spread 🤔)
Object.values({ ...this.media.style })
// We're only fussed about Plyr specific properties
- .filter((key) => !is.empty(key) && key.startsWith('--plyr'))
+ .filter((key) => !is.empty(key) && is.string(key) && key.startsWith('--plyr'))
.forEach((key) => {
// Set on the container
this.elements.container.style.setProperty(key, this.media.style.getPropertyValue(key));
diff --git a/src/js/utils/style.js b/src/js/utils/style.js
index fcb089b4..f02b0ba5 100644
--- a/src/js/utils/style.js
+++ b/src/js/utils/style.js
@@ -68,7 +68,11 @@ export function setAspectRatio(input) {
const height = (100 / this.media.offsetWidth) * parseInt(window.getComputedStyle(this.media).paddingBottom, 10);
const offset = (height - padding) / (height / 50);
- this.media.style.transform = `translateY(-${offset}%)`;
+ if (this.fullscreen.active) {
+ wrapper.style.paddingBottom = null;
+ } else {
+ this.media.style.transform = `translateY(-${offset}%)`;
+ }
} else if (this.isHTML5) {
wrapper.classList.toggle(this.config.classNames.videoFixedRatio, ratio !== null);
}
diff --git a/src/sass/base.scss b/src/sass/base.scss
index 8ab3e1a8..93f91bd9 100644
--- a/src/sass/base.scss
+++ b/src/sass/base.scss
@@ -12,7 +12,6 @@
font-family: $plyr-font-family;
font-variant-numeric: tabular-nums; // Force monosace-esque number widths
font-weight: $plyr-font-weight-regular;
- height: 100%;
line-height: $plyr-line-height;
max-width: 100%;
min-width: 200px;
diff --git a/src/sass/components/poster.scss b/src/sass/components/poster.scss
index 2e966a32..6b2d7b6b 100644
--- a/src/sass/components/poster.scss
+++ b/src/sass/components/poster.scss
@@ -10,6 +10,7 @@
height: 100%;
left: 0;
opacity: 0;
+ pointer-events: none;
position: absolute;
top: 0;
transition: opacity 0.2s ease;