diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/js/config/defaults.js | 2 | ||||
-rw-r--r-- | src/js/listeners.js | 86 | ||||
-rw-r--r-- | src/js/media.js | 1 | ||||
-rw-r--r-- | src/js/plugins/vimeo.js | 9 | ||||
-rw-r--r-- | src/js/plugins/youtube.js | 4 | ||||
-rw-r--r-- | src/js/plyr.js | 8 | ||||
-rw-r--r-- | src/js/plyr.polyfilled.js | 2 | ||||
-rw-r--r-- | src/js/utils/browser.js | 6 | ||||
-rw-r--r-- | src/js/utils/style.js | 80 | ||||
-rw-r--r-- | src/sass/components/poster.scss | 5 | ||||
-rw-r--r-- | src/sass/lib/mixins.scss | 11 | ||||
-rw-r--r-- | src/sass/types/video.scss | 26 |
12 files changed, 149 insertions, 91 deletions
diff --git a/src/js/config/defaults.js b/src/js/config/defaults.js index 7a73c318..77117588 100644 --- a/src/js/config/defaults.js +++ b/src/js/config/defaults.js @@ -61,7 +61,7 @@ const defaults = { // Sprite (for icons) loadSprite: true, iconPrefix: 'plyr', - iconUrl: 'https://cdn.plyr.io/3.6.4/plyr.svg', + iconUrl: 'https://cdn.plyr.io/3.6.7/plyr.svg', // Blank video (used to prevent errors on source change) blankVideo: 'https://cdn.plyr.io/static/blank.mp4', diff --git a/src/js/listeners.js b/src/js/listeners.js index 3d1f8ef0..c490070c 100644 --- a/src/js/listeners.js +++ b/src/js/listeners.js @@ -10,7 +10,7 @@ import { getElement, getElements, matches, toggleClass } from './utils/elements' import { off, on, once, toggleListener, triggerEvent } from './utils/events'; import is from './utils/is'; import { silencePromise } from './utils/promise'; -import { getAspectRatio, setAspectRatio } from './utils/style'; +import { getAspectRatio, getViewportSize, supportsCSS } from './utils/style'; class Listeners { constructor(player) { @@ -149,16 +149,16 @@ class Listeners { break; /* case 73: - this.setLoop('start'); - break; + this.setLoop('start'); + break; - case 76: - this.setLoop(); - break; + case 76: + this.setLoop(); + break; - case 79: - this.setLoop('end'); - break; */ + case 79: + this.setLoop('end'); + break; */ default: break; @@ -305,39 +305,49 @@ class Listeners { ); // Set a gutter for Vimeo - const setGutter = (ratio, padding, toggle) => { + const setGutter = () => { if (!player.isVimeo || player.config.vimeo.premium) { return; } - const target = player.elements.wrapper.firstChild; - const [, y] = ratio; - const [videoX, videoY] = getAspectRatio.call(player); - - target.style.maxWidth = toggle ? `${(y / videoY) * videoX}px` : null; - target.style.margin = toggle ? '0 auto' : null; - }; + const target = elements.wrapper; + const { active } = player.fullscreen; + const [videoWidth, videoHeight] = getAspectRatio.call(player); + const useNativeAspectRatio = supportsCSS(`aspect-ratio: ${videoWidth} / ${videoHeight}`); - // Resize on fullscreen change - const setPlayerSize = (measure) => { - // If we don't need to measure the viewport - if (!measure) { - return setAspectRatio.call(player); + // If not active, remove styles + if (!active) { + if (useNativeAspectRatio) { + target.style.width = null; + target.style.height = null; + } else { + target.style.maxWidth = null; + target.style.margin = null; + } + return; } - const rect = elements.container.getBoundingClientRect(); - const { width, height } = rect; + // Determine which dimension will overflow and constrain view + const [viewportWidth, viewportHeight] = getViewportSize(); + const overflow = viewportWidth / viewportHeight > videoWidth / videoHeight; - return setAspectRatio.call(player, `${width}:${height}`); + if (useNativeAspectRatio) { + target.style.width = overflow ? 'auto' : '100%'; + target.style.height = overflow ? '100%' : 'auto'; + } else { + target.style.maxWidth = overflow ? `${(viewportHeight / videoHeight) * videoWidth}px` : null; + target.style.margin = overflow ? '0 auto' : null; + } }; + // Handle resizing const resized = () => { clearTimeout(timers.resized); - timers.resized = setTimeout(setPlayerSize, 50); + timers.resized = setTimeout(setGutter, 50); }; on.call(player, elements.container, 'enterfullscreen exitfullscreen', (event) => { - const { target, usingNative } = player.fullscreen; + const { target } = player.fullscreen; // Ignore events not from target if (target !== elements.container) { @@ -349,26 +359,12 @@ class Listeners { return; } - const isEnter = event.type === 'enterfullscreen'; - // Set the player size when entering fullscreen to viewport size - const { padding, ratio } = setPlayerSize(isEnter); - // Set Vimeo gutter - setGutter(ratio, padding, isEnter); - - // Horrible hack for Safari 14 not repainting properly on entering fullscreen - if (isEnter) { - setTimeout(() => repaint(elements.container), 100); - } + setGutter(); - // If not using native browser fullscreen API, we need to check for resizes of viewport - if (!usingNative) { - if (isEnter) { - on.call(player, window, 'resize', resized); - } else { - off.call(player, window, 'resize', resized); - } - } + // Watch for resizes + const method = event.type === 'enterfullscreen' ? on : off; + method.call(player, window, 'resize', resized); }); }; diff --git a/src/js/media.js b/src/js/media.js index ddac5ebf..4584fea3 100644 --- a/src/js/media.js +++ b/src/js/media.js @@ -41,7 +41,6 @@ const media = { // Poster image container this.elements.poster = createElement('div', { class: this.config.classNames.poster, - hidden: '', }); this.elements.wrapper.appendChild(this.elements.poster); diff --git a/src/js/plugins/vimeo.js b/src/js/plugins/vimeo.js index 5fe41aba..10246c66 100644 --- a/src/js/plugins/vimeo.js +++ b/src/js/plugins/vimeo.js @@ -11,7 +11,7 @@ import fetch from '../utils/fetch'; import is from '../utils/is'; import loadScript from '../utils/load-script'; import { format, stripHTML } from '../utils/strings'; -import { setAspectRatio } from '../utils/style'; +import { roundAspectRatio, setAspectRatio } from '../utils/style'; import { buildUrlParams } from '../utils/urls'; // Parse Vimeo ID from URL @@ -104,7 +104,10 @@ const vimeo = { const src = format(player.config.urls.vimeo.iframe, id, params); iframe.setAttribute('src', src); iframe.setAttribute('allowfullscreen', ''); - iframe.setAttribute('allow', ['autoplay', 'fullscreen', 'picture-in-picture'].join('; ')); + iframe.setAttribute( + 'allow', + ['autoplay', 'fullscreen', 'picture-in-picture', 'encrypted-media', 'accelerometer', 'gyroscope'].join('; '), + ); // Set the referrer policy if required if (!is.empty(referrerPolicy)) { @@ -291,7 +294,7 @@ const vimeo = { // Set aspect ratio based on video size Promise.all([player.embed.getVideoWidth(), player.embed.getVideoHeight()]).then((dimensions) => { const [width, height] = dimensions; - player.embed.ratio = [width, height]; + player.embed.ratio = roundAspectRatio(width, height); setAspectRatio.call(this); }); diff --git a/src/js/plugins/youtube.js b/src/js/plugins/youtube.js index db5781e6..19d2f1a5 100644 --- a/src/js/plugins/youtube.js +++ b/src/js/plugins/youtube.js @@ -11,7 +11,7 @@ import loadImage from '../utils/load-image'; import loadScript from '../utils/load-script'; import { extend } from '../utils/objects'; import { format, generateId } from '../utils/strings'; -import { setAspectRatio } from '../utils/style'; +import { roundAspectRatio, setAspectRatio } from '../utils/style'; // Parse YouTube ID from URL function parseId(url) { @@ -90,7 +90,7 @@ const youtube = { ui.setTitle.call(this); // Set aspect ratio - this.embed.ratio = [width, height]; + this.embed.ratio = roundAspectRatio(width, height); } setAspectRatio.call(this); diff --git a/src/js/plyr.js b/src/js/plyr.js index b40f5c5a..a7876c2a 100644 --- a/src/js/plyr.js +++ b/src/js/plyr.js @@ -1,6 +1,6 @@ // ========================================================================== // Plyr -// plyr.js v3.6.4 +// plyr.js v3.6.7 // https://github.com/sampotts/plyr // License: The MIT License (MIT) // ========================================================================== @@ -29,7 +29,7 @@ import loadSprite from './utils/load-sprite'; import { clamp } from './utils/numbers'; import { cloneDeep, extend } from './utils/objects'; import { silencePromise } from './utils/promise'; -import { getAspectRatio, reduceAspectRatio, setAspectRatio, validateRatio } from './utils/style'; +import { getAspectRatio, reduceAspectRatio, setAspectRatio, validateAspectRatio } from './utils/style'; import { parseUrl } from './utils/urls'; // Private properties @@ -916,12 +916,12 @@ class Plyr { return; } - if (!is.string(input) || !validateRatio(input)) { + if (!is.string(input) || !validateAspectRatio(input)) { this.debug.error(`Invalid aspect ratio specified (${input})`); return; } - this.config.ratio = input; + this.config.ratio = reduceAspectRatio(input); setAspectRatio.call(this); } diff --git a/src/js/plyr.polyfilled.js b/src/js/plyr.polyfilled.js index f8f613e7..d40d5d97 100644 --- a/src/js/plyr.polyfilled.js +++ b/src/js/plyr.polyfilled.js @@ -1,6 +1,6 @@ // ========================================================================== // Plyr Polyfilled Build -// plyr.js v3.6.4 +// plyr.js v3.6.7 // https://github.com/sampotts/plyr // License: The MIT License (MIT) // ========================================================================== diff --git a/src/js/utils/browser.js b/src/js/utils/browser.js index 7b8fa5e2..6b5e768a 100644 --- a/src/js/utils/browser.js +++ b/src/js/utils/browser.js @@ -4,11 +4,13 @@ // ========================================================================== const browser = { - isIE: /* @cc_on!@ */ false || !!document.documentMode, + isIE: Boolean(window.document.documentMode), isEdge: window.navigator.userAgent.includes('Edge'), isWebkit: 'WebkitAppearance' in document.documentElement.style && !/Edge/.test(navigator.userAgent), isIPhone: /(iPhone|iPod)/gi.test(navigator.platform), - isIos: /(iPad|iPhone|iPod)/gi.test(navigator.platform), + isIos: + (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1) || + /(iPad|iPhone|iPod)/gi.test(navigator.platform), }; export default browser; diff --git a/src/js/utils/style.js b/src/js/utils/style.js index f02b0ba5..e05e058f 100644 --- a/src/js/utils/style.js +++ b/src/js/utils/style.js @@ -2,9 +2,39 @@ // Style utils // ========================================================================== +import { closest } from './arrays'; import is from './is'; -export function validateRatio(input) { +// Check support for a CSS declaration +export function supportsCSS(declaration) { + if (!window || !window.CSS) { + return false; + } + + return window.CSS.supports(declaration); +} + +// Standard/common aspect ratios +const standardRatios = [ + [1, 1], + [4, 3], + [3, 4], + [5, 4], + [4, 5], + [3, 2], + [2, 3], + [16, 10], + [10, 16], + [16, 9], + [9, 16], + [21, 9], + [9, 21], + [32, 9], + [9, 32], +].reduce((out, [x, y]) => ({ ...out, [x / y]: [x, y] }), {}); + +// Validate an aspect ratio +export function validateAspectRatio(input) { if (!is.array(input) && (!is.string(input) || !input.includes(':'))) { return false; } @@ -14,6 +44,7 @@ export function validateRatio(input) { return ratio.map(Number).every(is.number); } +// Reduce an aspect ratio to it's lowest form export function reduceAspectRatio(ratio) { if (!is.array(ratio) || !ratio.every(is.number)) { return null; @@ -26,8 +57,9 @@ export function reduceAspectRatio(ratio) { return [width / divider, height / divider]; } +// Calculate an aspect ratio export function getAspectRatio(input) { - const parse = (ratio) => (validateRatio(ratio) ? ratio.split(':').map(Number) : null); + const parse = (ratio) => (validateAspectRatio(ratio) ? ratio.split(':').map(Number) : null); // Try provided ratio let ratio = parse(input); @@ -44,10 +76,10 @@ export function getAspectRatio(input) { // Get from HTML5 video if (ratio === null && this.isHTML5) { const { videoWidth, videoHeight } = this.media; - ratio = reduceAspectRatio([videoWidth, videoHeight]); + ratio = [videoWidth, videoHeight]; } - return ratio; + return reduceAspectRatio(ratio); } // Set aspect ratio for responsive container @@ -58,10 +90,20 @@ export function setAspectRatio(input) { const { wrapper } = this.elements; const ratio = getAspectRatio.call(this, input); - const [w, h] = is.array(ratio) ? ratio : [0, 0]; - const padding = (100 / w) * h; - wrapper.style.paddingBottom = `${padding}%`; + if (!is.array(ratio)) { + return {}; + } + + const [x, y] = reduceAspectRatio(ratio); + const useNative = supportsCSS(`aspect-ratio: ${x}/${y}`); + const padding = (100 / x) * y; + + if (useNative) { + wrapper.style.aspectRatio = `${x}/${y}`; + } else { + wrapper.style.paddingBottom = `${padding}%`; + } // For Vimeo we have an extra <div> to hide the standard controls and UI if (this.isVimeo && !this.config.vimeo.premium && this.supported.ui) { @@ -74,10 +116,30 @@ export function setAspectRatio(input) { this.media.style.transform = `translateY(-${offset}%)`; } } else if (this.isHTML5) { - wrapper.classList.toggle(this.config.classNames.videoFixedRatio, ratio !== null); + wrapper.classList.add(this.config.classNames.videoFixedRatio); } return { padding, ratio }; } -export default { setAspectRatio }; +// Round an aspect ratio to closest standard ratio +export function roundAspectRatio(x, y, tolerance = 0.05) { + const ratio = x / y; + const closestRatio = closest(Object.keys(standardRatios), ratio); + + // Check match is within tolerance + if (Math.abs(closestRatio - ratio) <= tolerance) { + return standardRatios[closestRatio]; + } + + // No match + return [x, y]; +} + +// Get the size of the viewport +// https://stackoverflow.com/questions/1248081/how-to-get-the-browser-viewport-dimensions +export function getViewportSize() { + const width = Math.max(document.documentElement.clientWidth || 0, window.innerWidth || 0); + const height = Math.max(document.documentElement.clientHeight || 0, window.innerHeight || 0); + return [width, height]; +} diff --git a/src/sass/components/poster.scss b/src/sass/components/poster.scss index 3a158c1b..788d819c 100644 --- a/src/sass/components/poster.scss +++ b/src/sass/components/poster.scss @@ -20,3 +20,8 @@ .plyr--stopped.plyr__poster-enabled .plyr__poster { opacity: 1; } + +// Allow interaction with YouTube controls while paused +.plyr--youtube.plyr--paused.plyr__poster-enabled:not(.plyr--stopped) .plyr__poster { + display: none; +} diff --git a/src/sass/lib/mixins.scss b/src/sass/lib/mixins.scss index cbb8cc78..d1bc4b3e 100644 --- a/src/sass/lib/mixins.scss +++ b/src/sass/lib/mixins.scss @@ -59,17 +59,6 @@ height: 100%; } - .plyr__video-wrapper { - height: 100%; - position: static; - } - - // Vimeo requires some different styling - &.plyr--vimeo .plyr__video-wrapper { - height: 0; - position: relative; - } - // Display correct icon .plyr__control .icon--exit-fullscreen { display: block; diff --git a/src/sass/types/video.scss b/src/sass/types/video.scss index 9a10d5ea..e0de7acc 100644 --- a/src/sass/types/video.scss +++ b/src/sass/types/video.scss @@ -14,7 +14,6 @@ .plyr__video-wrapper { background: var(--plyr-video-background, $plyr-video-background); - height: 100%; margin: auto; overflow: hidden; position: relative; @@ -26,29 +25,32 @@ $embed-padding: ((100 / 16) * 9); .plyr__video-embed, .plyr__video-wrapper--fixed-ratio { - height: 0; - padding-bottom: to-percentage($embed-padding); + @supports not (aspect-ratio: 16 / 9) { + height: 0; + padding-bottom: to-percentage($embed-padding); + position: relative; + } + + aspect-ratio: 16 / 9; } .plyr__video-embed iframe, .plyr__video-wrapper--fixed-ratio video { border: 0; + height: 100%; left: 0; position: absolute; top: 0; + width: 100%; } -// If the full custom UI is supported -.plyr--full-ui .plyr__video-embed { +// For Vimeo, if the full custom UI is supported +.plyr--full-ui .plyr__video-embed > .plyr__video-embed__container { $height: 240; $offset: to-percentage(($height - $embed-padding) / ($height / 50)); - - // Only used for Vimeo - > .plyr__video-embed__container { - padding-bottom: to-percentage($height); - position: relative; - transform: translateY(-$offset); - } + padding-bottom: to-percentage($height); + position: relative; + transform: translateY(-$offset); } // Controls container |