Aspect ratio improvements (fixes #1042, fixes #1366)

This commit is contained in:
Sam Potts 2019-04-12 12:19:48 +10:00
parent 9ca7b861a9
commit b247093495
8 changed files with 109 additions and 33 deletions

View File

@ -293,7 +293,7 @@ Note the single quotes encapsulating the JSON and double quotes on the object ke
| `listeners` | Object | `null` | Allows binding of event listeners to the controls before the default handlers. See the `defaults.js` for available listeners. If your handler prevents default on the event (`event.preventDefault()`), the default handler will not fire. |
| `captions` | Object | `{ active: false, language: 'auto', update: false }` | `active`: Toggles if captions should be active by default. `language`: Sets the default language to load (if available). 'auto' uses the browser language. `update`: Listen to changes to tracks and update menu. This is needed for some streaming libraries, but can result in unselectable language options). |
| `fullscreen` | Object | `{ enabled: true, fallback: true, iosNative: false }` | `enabled`: Toggles whether fullscreen should be enabled. `fallback`: Allow fallback to a full-window solution (`true`/`false`/`'force'`). `iosNative`: whether to use native iOS fullscreen when entering fullscreen (no custom controls) |
| `ratio` | String | `16:9` | The aspect ratio you want to use for embedded players. |
| `ratio` | String | `null` | Force an aspect ratio for all videos. The format is `'w:h'` - e.g. `'16:9'` or `'4:3'`. If this is not specified then the default for HTML5 and Vimeo is to use the native resolution of the video. As dimensions are not available from YouTube via SDK, 16:9 is forced as a sensible default. |
| `storage` | Object | `{ enabled: true, key: 'plyr' }` | `enabled`: Allow use of local storage to store user settings. `key`: The key name to use. |
| `speed` | Object | `{ selected: 1, options: [0.5, 0.75, 1, 1.25, 1.5, 1.75, 2] }` | `selected`: The default speed for playback. `options`: Options to display in the menu. Most browsers will refuse to play slower than 0.5. |
| `quality` | Object | `{ default: 576, options: [4320, 2880, 2160, 1440, 1080, 720, 576, 480, 360, 240] }` | `default` is the default quality level (if it exists in your sources). `options` are the options to display. This is used to filter the available sources. |
@ -405,6 +405,7 @@ player.fullscreen.active; // false;
| `fullscreen.active` | ✓ | - | Returns a boolean indicating if the current player is in fullscreen mode. |
| `fullscreen.enabled` | ✓ | - | Returns a boolean indicating if the current player has fullscreen enabled. |
| `pip`² | ✓ | ✓ | Gets or sets the picture-in-picture state of the player. The setter accepts a boolean. This currently only supported on Safari 10+ (on MacOS Sierra+ and iOS 10+) and Chrome 70+. |
| `ratio` | ✓ | ✓ | Gets or sets the video aspect ratio. The setter accepts a string in the same format as the `ratio` option. |
1. YouTube only. HTML5 will follow.
2. HTML5 only

View File

@ -42,8 +42,9 @@ const defaults = {
// Clicking the currentTime inverts it's value to show time left rather than elapsed
toggleInvert: true,
// Aspect ratio (for embeds)
ratio: '16:9',
// Force an aspect ratio
// The format must be `'w:h'` (e.g. `'16:9'`)
ratio: null,
// Click video container to play/pause
clickToPlay: true,
@ -330,6 +331,7 @@ const defaults = {
provider: 'plyr--{0}',
video: 'plyr__video-wrapper',
embed: 'plyr__video-embed',
videoFixedRatio: 'plyr__video-wrapper--fixed-ratio',
embedContainer: 'plyr__video-embed__container',
poster: 'plyr__poster',
posterEnabled: 'plyr__poster-enabled',

View File

@ -6,6 +6,7 @@ import support from './support';
import { removeElement } from './utils/elements';
import { triggerEvent } from './utils/events';
import is from './utils/is';
import { setAspectRatio } from './utils/style';
const html5 = {
getSources() {
@ -43,6 +44,9 @@ const html5 = {
const player = this;
// Set aspect ratio if set
setAspectRatio.call(player);
// Quality
Object.defineProperty(player.media, 'quality', {
get() {

View File

@ -9,7 +9,7 @@ import browser from './utils/browser';
import { getElement, getElements, matches, toggleClass, toggleHidden } from './utils/elements';
import { off, on, once, toggleListener, triggerEvent } from './utils/events';
import is from './utils/is';
import { setAspectRatio } from './utils/style';
import { getAspectRatio, setAspectRatio } from './utils/style';
class Listeners {
constructor(player) {
@ -317,10 +317,10 @@ class Listeners {
}
const target = player.elements.wrapper.firstChild;
const [, height] = ratio.split(':').map(Number);
const [videoWidth, videoHeight] = player.embed.ratio.split(':').map(Number);
const [, y] = ratio;
const [videoX, videoY] = getAspectRatio.call(this);
target.style.maxWidth = toggle ? `${(height / videoHeight) * videoWidth}px` : null;
target.style.maxWidth = toggle ? `${(y / videoY) * videoX}px` : null;
target.style.margin = toggle ? '0 auto' : null;
};

View File

@ -282,7 +282,7 @@ const vimeo = {
Promise.all([player.embed.getVideoWidth(), player.embed.getVideoHeight()]).then(dimensions => {
const [width, height] = dimensions;
player.embed.ratio = `${width}:${height}`;
setAspectRatio.call(this, player.embed.ratio);
setAspectRatio.call(this);
});
// Set autopause

View File

@ -26,6 +26,7 @@ import { off, on, once, triggerEvent, unbindListeners } from './utils/events';
import is from './utils/is';
import loadSprite from './utils/loadSprite';
import { cloneDeep, extend } from './utils/objects';
import { getAspectRatio, reduceAspectRatio, setAspectRatio, validateRatio } from './utils/style';
import { parseUrl } from './utils/urls';
// Private properties
@ -846,6 +847,34 @@ class Plyr {
return this.media.getAttribute('poster');
}
/**
* Get the current aspect ratio in use
*/
get ratio() {
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

View File

@ -4,26 +4,63 @@
import is from './is';
/* function reduceAspectRatio(width, height) {
const getRatio = (w, h) => (h === 0 ? w : getRatio(h, w % h));
const ratio = getRatio(width, height);
return `${width / ratio}:${height / ratio}`;
} */
export function validateRatio(input) {
if (!is.array(input) && (!is.string(input) || !input.includes(':'))) {
return false;
}
const ratio = is.array(input) ? input : input.split(':');
return ratio.map(Number).every(is.number);
}
export function reduceAspectRatio(ratio) {
if (!is.array(ratio) || !ratio.every(is.number)) {
return null;
}
const [width, height] = ratio;
const getDivider = (w, h) => (h === 0 ? w : getDivider(h, w % h));
const divider = getDivider(width, height);
return [width / divider, height / divider];
}
export function getAspectRatio(input) {
const parse = ratio => {
if (!validateRatio(ratio)) {
return null;
}
return ratio.split(':').map(Number);
};
// Provided ratio
let ratio = parse(input);
// Get from config
if (ratio === null) {
ratio = parse(this.config.ratio);
}
// Get from embed
if (ratio === null && !is.empty(this.embed) && is.string(this.embed.ratio)) {
ratio = parse(this.embed.ratio);
}
return ratio;
}
// Set aspect ratio for responsive container
export function setAspectRatio(input) {
let ratio = input;
if (!is.string(ratio) && !is.nullOrUndefined(this.embed)) {
({ ratio } = this.embed);
if (!this.isVideo) {
return {};
}
if (!is.string(ratio)) {
({ ratio } = this.config);
}
const ratio = getAspectRatio.call(this, input);
const [x, y] = ratio.split(':').map(Number);
const padding = (100 / x) * y;
const [w, h] = is.array(ratio) ? ratio : [0, 0];
const padding = (100 / w) * h;
this.elements.wrapper.style.paddingBottom = `${padding}%`;
@ -32,6 +69,8 @@ export function setAspectRatio(input) {
const height = 240;
const offset = (height - padding) / (height / 50);
this.media.style.transform = `translateY(-${offset}%)`;
} else if (this.isHTML5) {
this.elements.wrapper.classList.toggle(this.config.classNames.videoFixedRatio, ratio !== null);
}
return { padding, ratio };

View File

@ -6,20 +6,21 @@
// Default to 16:9 ratio but this is set by JavaScript based on config
$embed-padding: ((100 / 16) * 9);
.plyr__video-embed {
.plyr__video-embed,
.plyr__video-wrapper--fixed-ratio {
height: 0;
padding-bottom: to-percentage($embed-padding);
position: relative;
}
iframe {
border: 0;
height: 100%;
left: 0;
position: absolute;
top: 0;
user-select: none;
width: 100%;
}
.plyr__video-embed iframe,
.plyr__video-wrapper--fixed-ratio video {
border: 0;
height: 100%;
left: 0;
position: absolute;
top: 0;
user-select: none;
width: 100%;
}
// If the full custom UI is supported