diff --git a/demo/src/sass/settings/plyr.scss b/demo/src/sass/settings/plyr.scss index 67bde8d3..05190709 100644 --- a/demo/src/sass/settings/plyr.scss +++ b/demo/src/sass/settings/plyr.scss @@ -13,6 +13,6 @@ --plyr-font-weight-regular: 500, --plyr-font-weight-bold: 600, --plyr-font-size-captions-medium: 18px, - --plyr-font-size-captions-large: 21px + --plyr-font-size-captions-large: 21px, ) ); diff --git a/readme.md b/readme.md index bfc07a51..4e2654c7 100644 --- a/readme.md +++ b/readme.md @@ -400,7 +400,7 @@ Note the single quotes encapsulating the JSON and double quotes on the object ke | `toggleInvert` | Boolean | `true` | Allow users to click to toggle the above. | | `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) | +| `fullscreen` | Object | `{ enabled: true, fallback: true, iosNative: false, container: null }` | `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). `container`: A selector for an ancestor of the player element, allows contextual content to remain visual in fullscreen mode. Non-ancestors are ignored. | | `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`: 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. | diff --git a/src/js/captions.js b/src/js/captions.js index 0e6173ca..cf437bde 100644 --- a/src/js/captions.js +++ b/src/js/captions.js @@ -133,8 +133,12 @@ const captions = { }); // Turn off native caption rendering to avoid double captions + // Note: mode='hidden' forces a track to download. To ensure every track + // isn't downloaded at once, only 'showing' tracks should be reassigned // eslint-disable-next-line no-param-reassign - track.mode = 'hidden'; + if (track.mode === 'showing') { + track.mode = 'hidden'; + } // Add event listener for cue changes on.call(this, track, 'cuechange', () => captions.updateCues.call(this)); @@ -211,6 +215,14 @@ const captions = { // Trigger event (not used internally) triggerEvent.call(this, this.media, active ? 'captionsenabled' : 'captionsdisabled'); } + + // Wait for the call stack to clear before setting mode='hidden' + // on the active track - forcing the browser to download it + setTimeout(() => { + if (active && this.captions.toggled) { + this.captions.currentTrackNode.mode = 'hidden'; + } + }); }, // Set captions by track index diff --git a/src/js/config/defaults.js b/src/js/config/defaults.js index 087a5cb7..2814045f 100644 --- a/src/js/config/defaults.js +++ b/src/js/config/defaults.js @@ -115,6 +115,9 @@ const defaults = { enabled: true, // Allow fullscreen? fallback: true, // Fallback using full viewport/window iosNative: false, // Use the native fullscreen in iOS (disables custom controls) + // Selector for the fullscreen container so contextual / non-player content can remain visible in fullscreen mode + // Non-ancestors of the player element will be ignored + // container: null, // defaults to the player element }, // Local storage diff --git a/src/js/fullscreen.js b/src/js/fullscreen.js index 9d433c80..1c836352 100644 --- a/src/js/fullscreen.js +++ b/src/js/fullscreen.js @@ -5,7 +5,7 @@ // ========================================================================== import browser from './utils/browser'; -import { getElements, hasClass, toggleClass } from './utils/elements'; +import { getElements, hasClass, toggleClass, closest } from './utils/elements'; import { on, triggerEvent } from './utils/events'; import is from './utils/is'; import { silencePromise } from './utils/promise'; @@ -25,6 +25,11 @@ class Fullscreen { // Force the use of 'full window/browser' rather than fullscreen this.forceFallback = player.config.fullscreen.fallback === 'force'; + // Get the fullscreen element + // Checks container is an ancestor, defaults to null + this.player.elements.fullscreen = + player.config.fullscreen.container && closest(this.player.elements.container, player.config.fullscreen.container); + // Register event listeners // Handle event (incase user presses escape etc) on.call( @@ -126,7 +131,7 @@ class Fullscreen { get target() { return browser.isIos && this.player.config.fullscreen.iosNative ? this.player.media - : this.player.elements.container; + : this.player.elements.fullscreen || this.player.elements.container; } onChange() { diff --git a/src/js/listeners.js b/src/js/listeners.js index dd2c4834..2cc71537 100644 --- a/src/js/listeners.js +++ b/src/js/listeners.js @@ -333,7 +333,6 @@ class Listeners { const resized = () => { clearTimeout(timers.resized); - timers.resized = setTimeout(setPlayerSize, 50); }; @@ -357,7 +356,7 @@ class Listeners { // Set Vimeo gutter setGutter(ratio, padding, isEnter); - // If not using the native browser fullscreen API, we need to check for resizes of viewport + // 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); @@ -817,6 +816,17 @@ class Listeners { elements.controls.hover = !player.touch && event.type === 'mouseenter'; }); + // Also update controls.hover state for any non-player children of fullscreen element (as above) + if (elements.fullscreen) { + Array.from(elements.fullscreen.children) + .filter(c => !c.contains(elements.container)) + .forEach(child => { + this.bind(child, 'mouseenter mouseleave', event => { + elements.controls.hover = !player.touch && event.type === 'mouseenter'; + }); + }); + } + // Update controls.pressed state (used for ui.toggleControls to avoid hiding when interacting) this.bind(elements.controls, 'mousedown mouseup touchstart touchend touchcancel', event => { elements.controls.pressed = ['mousedown', 'touchstart'].includes(event.type); diff --git a/src/js/plyr.js b/src/js/plyr.js index 14050929..a3bdb285 100644 --- a/src/js/plyr.js +++ b/src/js/plyr.js @@ -80,6 +80,7 @@ class Plyr { // Elements cache this.elements = { container: null, + fullscreen: null, captions: null, buttons: {}, display: {}, @@ -285,6 +286,9 @@ class Plyr { }); } + // Setup fullscreen + this.fullscreen = new Fullscreen(this); + // Setup interface // If embed but not fully supported, build interface now to avoid flash of controls if (this.isHTML5 || (this.isEmbed && !this.supported.ui)) { @@ -297,9 +301,6 @@ class Plyr { // Global listeners this.listeners.global(); - // Setup fullscreen - this.fullscreen = new Fullscreen(this); - // Setup ads if provided if (this.config.ads.enabled) { this.ads = new Ads(this); diff --git a/src/js/utils/elements.js b/src/js/utils/elements.js index b48fa7e1..1d13b701 100644 --- a/src/js/utils/elements.js +++ b/src/js/utils/elements.js @@ -237,6 +237,26 @@ export function matches(element, selector) { return method.call(element, selector); } +// Closest ancestor element matching selector (also tests element itself) +export function closest(element, selector) { + const { prototype } = Element; + + // https://developer.mozilla.org/en-US/docs/Web/API/Element/closest#Polyfill + function closestElement() { + let el = this; + + do { + if (matches.matches(el, selector)) return el; + el = el.parentElement || el.parentNode; + } while (el !== null && el.nodeType === 1); + return null; + } + + const method = prototype.closest || closestElement; + + return method.call(element, selector); +} + // Find all elements export function getElements(selector) { return this.elements.container.querySelectorAll(selector);