aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--readme.md22
-rw-r--r--src/js/controls.js6
-rw-r--r--src/js/fullscreen.js173
-rw-r--r--src/js/utils/elements.js35
4 files changed, 115 insertions, 121 deletions
diff --git a/readme.md b/readme.md
index 6e1f2460..204ce4d5 100644
--- a/readme.md
+++ b/readme.md
@@ -276,7 +276,7 @@ Note the single quotes encapsulating the JSON and double quotes on the object ke
| `enabled` | Boolean | `true` | Completely disable Plyr. This would allow you to do a User Agent check or similar to programmatically enable or disable Plyr for a certain UA. Example below. |
| `debug` | Boolean | `false` | Display debugging information in the console |
| `controls` | Array, Function or Element | `['play-large', 'play', 'progress', 'current-time', 'mute', 'volume', 'captions', 'settings', 'pip', 'airplay', 'fullscreen']` | If a function is passed, it is assumed your method will return either an element or HTML string for the controls. Three arguments will be passed to your function; `id` (the unique id for the player), `seektime` (the seektime step in seconds), and `title` (the media title). See [controls.md](controls.md) for more info on how the html needs to be structured. |
-| `settings` | Array | `['captions', 'quality', 'speed', 'loop']` | If the default controls are used, you can specify which settings to show in the menu |
+| `settings` | Array | `['captions', 'quality', 'speed', 'loop']` | If the default controls are used, you can specify which settings to show in the menu |
| `i18n` | Object | See [defaults.js](/src/js/config/defaults.js) | Used for internationalization (i18n) of the text within the UI. |
| `loadSprite` | Boolean | `true` | Load the SVG sprite specified as the `iconUrl` option (if a URL). If `false`, it is assumed you are handling sprite loading yourself. |
| `iconUrl` | String | `null` | Specify a URL or path to the SVG sprite. See the [SVG section](#svg) for more info. |
@@ -302,7 +302,7 @@ Note the single quotes encapsulating the JSON and double quotes on the object ke
| `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 | `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. |
+| `speed` | Object | `{ selected: 1, options: [0.5, 0.75, 1, 1.25, 1.5, 1.75, 2] }` | `selected`: The default speed for playback. `options`: The speed options to display in the UI. YouTube and Vimeo will ignore any options outside of the 0.5-2 range, so options outside of this range will be hidden automatically. |
| `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. |
| `loop` | Object | `{ active: false }` | `active`: Whether to loop the current video. If the `loop` attribute is present on a `<video>` or `<audio>` element, this will be automatically set to true This is an object to support future functionality. |
| `ads` | Object | `{ enabled: false, publisherId: '' }` | `enabled`: Whether to enable advertisements. `publisherId`: Your unique [vi.ai](https://vi.ai/publisher-video-monetization/?aid=plyrio) publisher ID. |
@@ -678,15 +678,15 @@ If a User Agent is disabled but supports `<video>` and `<audio>` natively, it wi
Some awesome folks have made plugins for CMSs and Components for JavaScript frameworks:
-| Type | Maintainer | Link |
-| --------- | -------------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
-| WordPress | Brandon Lavigne ([@drrobotnik](https://github.com/drrobotnik)) | [https://wordpress.org/plugins/plyr/](https://wordpress.org/plugins/plyr/) |
-| Angular | Simon Bobrov ([@smnbbrv](https://github.com/smnbbrv)) | [https://github.com/smnbbrv/ngx-plyr](https://github.com/smnbbrv/ngx-plyr) |
-| React | Chintan Prajapati ([@chintan9](https://github.com/chintan9)) | [https://github.com/chintan9/plyr-react](https://github.com/chintan9/plyr-react) |
-| Vue | Gabe Dunn ([@redxtech](https://github.com/redxtech)) | [https://github.com/redxtech/vue-plyr](https://github.com/redxtech/vue-plyr) |
-| Neos | Jon Uhlmann ([@jonnitto](https://github.com/jonnitto)) | [https://packagist.org/packages/jonnitto/plyr](https://packagist.org/packages/jonnitto/plyr) |
-| Kirby | Dominik Pschenitschni ([@dpschen](https://github.com/dpschen)) | [https://github.com/dpschen/kirby-plyrtag](https://github.com/dpschen/kirby-plyrtag) |
-| REDAXO | FriendsOfRedaxo / skerbis ([@skerbis](https://friendsofredaxo.github.io)) | [https://github.com/FriendsOfREDAXO/plyr](https://github.com/FriendsOfREDAXO/plyr) |
+| Type | Maintainer | Link |
+| --------- | ------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
+| WordPress | Brandon Lavigne ([@drrobotnik](https://github.com/drrobotnik)) | [https://wordpress.org/plugins/plyr/](https://wordpress.org/plugins/plyr/) |
+| Angular | Simon Bobrov ([@smnbbrv](https://github.com/smnbbrv)) | [https://github.com/smnbbrv/ngx-plyr](https://github.com/smnbbrv/ngx-plyr) |
+| React | Chintan Prajapati ([@chintan9](https://github.com/chintan9)) | [https://github.com/chintan9/plyr-react](https://github.com/chintan9/plyr-react) |
+| Vue | Gabe Dunn ([@redxtech](https://github.com/redxtech)) | [https://github.com/redxtech/vue-plyr](https://github.com/redxtech/vue-plyr) |
+| Neos | Jon Uhlmann ([@jonnitto](https://github.com/jonnitto)) | [https://packagist.org/packages/jonnitto/plyr](https://packagist.org/packages/jonnitto/plyr) |
+| Kirby | Dominik Pschenitschni ([@dpschen](https://github.com/dpschen)) | [https://github.com/dpschen/kirby-plyrtag](https://github.com/dpschen/kirby-plyrtag) |
+| REDAXO | FriendsOfRedaxo / skerbis ([@skerbis](https://friendsofredaxo.github.io)) | [https://github.com/FriendsOfREDAXO/plyr](https://github.com/FriendsOfREDAXO/plyr) |
# Issues
diff --git a/src/js/controls.js b/src/js/controls.js
index f93cb8a3..1cce51f6 100644
--- a/src/js/controls.js
+++ b/src/js/controls.js
@@ -1578,9 +1578,13 @@ const controls = {
element: 'a',
href: this.download,
target: '_blank',
- download: '',
});
+ // Set download attribute for HTML5 only
+ if (this.isHTML5) {
+ attributes.download = '';
+ }
+
const { download } = this.config.urls;
if (!is.url(download) && this.isEmbed) {
diff --git a/src/js/fullscreen.js b/src/js/fullscreen.js
index 7ae3ff17..c74b3406 100644
--- a/src/js/fullscreen.js
+++ b/src/js/fullscreen.js
@@ -5,79 +5,10 @@
// ==========================================================================
import browser from './utils/browser';
-import { hasClass, toggleClass, trapFocus } from './utils/elements';
+import { getElements, hasClass, toggleClass } from './utils/elements';
import { on, triggerEvent } from './utils/events';
import is from './utils/is';
-function onChange() {
- if (!this.enabled) {
- return;
- }
-
- // Update toggle button
- const button = this.player.elements.buttons.fullscreen;
- if (is.element(button)) {
- button.pressed = this.active;
- }
-
- // Trigger an event
- triggerEvent.call(this.player, this.target, this.active ? 'enterfullscreen' : 'exitfullscreen', true);
-
- // Trap focus in container
- if (!browser.isIos) {
- trapFocus.call(this.player, this.target, this.active);
- }
-}
-
-function toggleFallback(toggle = false) {
- // Store or restore scroll position
- if (toggle) {
- this.scrollPosition = {
- x: window.scrollX || 0,
- y: window.scrollY || 0,
- };
- } else {
- window.scrollTo(this.scrollPosition.x, this.scrollPosition.y);
- }
-
- // Toggle scroll
- document.body.style.overflow = toggle ? 'hidden' : '';
-
- // Toggle class hook
- toggleClass(this.target, this.player.config.classNames.fullscreen.fallback, toggle);
-
- // Force full viewport on iPhone X+
- if (browser.isIos) {
- let viewport = document.head.querySelector('meta[name="viewport"]');
- const property = 'viewport-fit=cover';
-
- // Inject the viewport meta if required
- if (!viewport) {
- viewport = document.createElement('meta');
- viewport.setAttribute('name', 'viewport');
- }
-
- // Check if the property already exists
- const hasProperty = is.string(viewport.content) && viewport.content.includes(property);
-
- if (toggle) {
- this.cleanupViewport = !hasProperty;
-
- if (!hasProperty) {
- viewport.content += `,${property}`;
- }
- } else if (this.cleanupViewport) {
- viewport.content = viewport.content
- .split(',')
- .filter(part => part.trim() !== property)
- .join(',');
- }
- }
-
- // Toggle button and fire events
- onChange.call(this);
-}
-
class Fullscreen {
constructor(player) {
// Keep reference to parent
@@ -101,7 +32,7 @@ class Fullscreen {
this.prefix === 'ms' ? 'MSFullscreenChange' : `${this.prefix}fullscreenchange`,
() => {
// TODO: Filter for target??
- onChange.call(this);
+ this.onChange();
},
);
@@ -115,6 +46,9 @@ class Fullscreen {
this.toggle();
});
+ // Tap focus when in fullscreen
+ on.call(this, this.player.elements.container, 'keydown', event => this.trapFocus(event));
+
// Update the UI
this.update();
}
@@ -194,6 +128,97 @@ class Fullscreen {
: this.player.elements.container;
}
+ onChange() {
+ if (!this.enabled) {
+ return;
+ }
+
+ // Update toggle button
+ const button = this.player.elements.buttons.fullscreen;
+ if (is.element(button)) {
+ button.pressed = this.active;
+ }
+
+ // Trigger an event
+ triggerEvent.call(this.player, this.target, this.active ? 'enterfullscreen' : 'exitfullscreen', true);
+ }
+
+ toggleFallback(toggle = false) {
+ // Store or restore scroll position
+ if (toggle) {
+ this.scrollPosition = {
+ x: window.scrollX || 0,
+ y: window.scrollY || 0,
+ };
+ } else {
+ window.scrollTo(this.scrollPosition.x, this.scrollPosition.y);
+ }
+
+ // Toggle scroll
+ document.body.style.overflow = toggle ? 'hidden' : '';
+
+ // Toggle class hook
+ toggleClass(this.target, this.player.config.classNames.fullscreen.fallback, toggle);
+
+ // Force full viewport on iPhone X+
+ if (browser.isIos) {
+ let viewport = document.head.querySelector('meta[name="viewport"]');
+ const property = 'viewport-fit=cover';
+
+ // Inject the viewport meta if required
+ if (!viewport) {
+ viewport = document.createElement('meta');
+ viewport.setAttribute('name', 'viewport');
+ }
+
+ // Check if the property already exists
+ const hasProperty = is.string(viewport.content) && viewport.content.includes(property);
+
+ if (toggle) {
+ this.cleanupViewport = !hasProperty;
+
+ if (!hasProperty) {
+ viewport.content += `,${property}`;
+ }
+ } else if (this.cleanupViewport) {
+ viewport.content = viewport.content
+ .split(',')
+ .filter(part => part.trim() !== property)
+ .join(',');
+ }
+ }
+
+ // Toggle button and fire events
+ this.onChange();
+ }
+
+ // Trap focus inside container
+ trapFocus(event) {
+ // Bail if iOS, not active, not the tab key
+ if (browser.isIos || !this.active || event.key !== 'Tab' || event.keyCode !== 9) {
+ return;
+ }
+
+ // Get the current focused element
+ const focused = document.activeElement;
+ const focusable = getElements.call(
+ this.player,
+ 'a[href], button:not(:disabled), input:not(:disabled), [tabindex]',
+ );
+ const [first] = focusable;
+ const last = focusable[focusable.length - 1];
+
+ if (focused === last && !event.shiftKey) {
+ // Move focus to first element that can be tabbed if Shift isn't used
+ first.focus();
+ event.preventDefault();
+ } else if (focused === first && event.shiftKey) {
+ // Move focus to last element that can be tabbed if Shift is used
+ last.focus();
+ event.preventDefault();
+ }
+ }
+
// Update UI
update() {
if (this.enabled) {
@@ -226,9 +251,9 @@ class Fullscreen {
if (browser.isIos && this.player.config.fullscreen.iosNative) {
this.target.webkitEnterFullscreen();
} else if (!Fullscreen.native || this.forceFallback) {
- toggleFallback.call(this, true);
+ this.toggleFallback(true);
} else if (!this.prefix) {
- this.target.requestFullscreen({ navigationUI: "hide" });
+ this.target.requestFullscreen({ navigationUI: 'hide' });
} else if (!is.empty(this.prefix)) {
this.target[`${this.prefix}Request${this.property}`]();
}
@@ -245,7 +270,7 @@ class Fullscreen {
this.target.webkitExitFullscreen();
this.player.play();
} else if (!Fullscreen.native || this.forceFallback) {
- toggleFallback.call(this, false);
+ this.toggleFallback(false);
} else if (!this.prefix) {
(document.cancelFullScreen || document.exitFullscreen).call(document);
} else if (!is.empty(this.prefix)) {
diff --git a/src/js/utils/elements.js b/src/js/utils/elements.js
index 921d533a..b88aad0c 100644
--- a/src/js/utils/elements.js
+++ b/src/js/utils/elements.js
@@ -2,7 +2,6 @@
// Element utils
// ==========================================================================
-import { toggleListener } from './events';
import is from './is';
import { extend } from './objects';
@@ -248,40 +247,6 @@ export function getElement(selector) {
return this.elements.container.querySelector(selector);
}
-// Trap focus inside container
-export function trapFocus(element = null, toggle = false) {
- if (!is.element(element)) {
- return;
- }
-
- const focusable = getElements.call(this, 'button:not(:disabled), input:not(:disabled), [tabindex]');
- const first = focusable[0];
- const last = focusable[focusable.length - 1];
- const player = this;
-
- const trap = event => {
- // Bail if not tab key or not fullscreen
- if (event.key !== 'Tab' || event.keyCode !== 9 || !player.fullscreen.active) {
- return;
- }
-
- // Get the current focused element
- const focused = document.activeElement;
-
- if (focused === last && !event.shiftKey) {
- // Move focus to first element that can be tabbed if Shift isn't used
- first.focus();
- event.preventDefault();
- } else if (focused === first && event.shiftKey) {
- // Move focus to last element that can be tabbed if Shift is used
- last.focus();
- event.preventDefault();
- }
- };
-
- toggleListener.call(this, this.elements.container, 'keydown', trap, toggle, false);
-}
-
// Set focus and tab focus class
export function setFocus(element = null, tabFocus = false) {
if (!is.element(element)) {