From 99ae4eb3c5aa335926ea76e868d236112404dc22 Mon Sep 17 00:00:00 2001 From: Jesper Date: Tue, 10 Mar 2020 09:30:42 +0100 Subject: Compare fullscreenElement with shadowroot host if player is in shadow DOM --- src/js/fullscreen.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/js/fullscreen.js') diff --git a/src/js/fullscreen.js b/src/js/fullscreen.js index c74b3406..6dc069b2 100644 --- a/src/js/fullscreen.js +++ b/src/js/fullscreen.js @@ -118,7 +118,7 @@ class Fullscreen { const element = !this.prefix ? document.fullscreenElement : document[`${this.prefix}${this.property}Element`]; - return element === this.target; + return (element && element.shadowRoot) ? element === this.target.getRootNode().host : element === this.target; } // Get target element -- cgit v1.2.3 From 71928443f317e624ab94ff18e207447f06f745ad Mon Sep 17 00:00:00 2001 From: ydylla Date: Mon, 23 Mar 2020 22:50:19 +0100 Subject: silence all internal play promises --- src/js/fullscreen.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src/js/fullscreen.js') diff --git a/src/js/fullscreen.js b/src/js/fullscreen.js index c74b3406..5da89e9c 100644 --- a/src/js/fullscreen.js +++ b/src/js/fullscreen.js @@ -8,6 +8,7 @@ import browser from './utils/browser'; import { getElements, hasClass, toggleClass } from './utils/elements'; import { on, triggerEvent } from './utils/events'; import is from './utils/is'; +import { silencePromise } from './utils/promise'; class Fullscreen { constructor(player) { @@ -268,7 +269,7 @@ class Fullscreen { // iOS native fullscreen if (browser.isIos && this.player.config.fullscreen.iosNative) { this.target.webkitExitFullscreen(); - this.player.play(); + silencePromise(this.player.play()); } else if (!Fullscreen.native || this.forceFallback) { this.toggleFallback(false); } else if (!this.prefix) { -- cgit v1.2.3 From 49ed2cac4eff3ff3eae7a2c72e5280a302906f7d Mon Sep 17 00:00:00 2001 From: Som Meaden Date: Sat, 4 Apr 2020 13:43:51 +1000 Subject: This is a PR to allow for contextual content to be included in fullscreen (or fallback) mode. This means arbitrary elements (extensions to the basic player UI) can be overlaid and remain visible when the player switches to fullscreen. Example use-cases include: - display of video title or other metadata (see the included demo) - alternative access to menu items, such as a searchable captions list (in cases where many hundreds of languages are available) - custom share dialogs - integrated playlists with 'playing next' overlays This approach / PR is just an example of how this feature could work and aims to keep Plyr complexity to a minimum (while enabling some fairly interesting integrations). It utilises a single config option, and does away with the need for injecting bespoke APIs or elements into the player context on a per-project basis. Or trying to mess with what is a pretty slick, but tightly coupled system. For the user: A new `fullscreen.container` attribute is used to provide a container selector. The container must be an ancestor of the player, otherwise it's ignored. When toggling fullscreen mode, this container is now used in place of the player. Hovering over any children of the container is the same as hovering over the controls. The exception is where the player and the child share a common ancestor (that's not the fullscreen container) ... sounds complex but it's not. You can also gain pretty fine control this way with pointer events. Under the hood: it adds a `utils/elements/closest` helper method to find the right ancestor. If found this is returned as the fullscreen target in place of the player container. Fullscreen is instantiated slightly earlier in the setup so this container is available for the `listeners.controls` call. In here we add some more 'mouseenter/mouseleave' listeners to any direct descendants of the container, that aren't also ancestors of the player. And that's it. No extra classes, nothing else. There are some style changes to the demo (top margin on the player) but these would be project specific. Thanks for reading. --- src/js/fullscreen.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'src/js/fullscreen.js') diff --git a/src/js/fullscreen.js b/src/js/fullscreen.js index 4d3c89ac..0db4aa3f 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() { -- cgit v1.2.3 From 502d5977d79148957828cbf313b7ef4c9f31973f Mon Sep 17 00:00:00 2001 From: Sam Potts Date: Sat, 11 Apr 2020 16:23:14 +1000 Subject: Converted to 2 space indentation --- src/js/fullscreen.js | 477 +++++++++++++++++++++++++-------------------------- 1 file changed, 237 insertions(+), 240 deletions(-) (limited to 'src/js/fullscreen.js') diff --git a/src/js/fullscreen.js b/src/js/fullscreen.js index 4d3c89ac..9d433c80 100644 --- a/src/js/fullscreen.js +++ b/src/js/fullscreen.js @@ -11,283 +11,280 @@ import is from './utils/is'; import { silencePromise } from './utils/promise'; class Fullscreen { - constructor(player) { - // Keep reference to parent - this.player = player; - - // Get prefix - this.prefix = Fullscreen.prefix; - this.property = Fullscreen.property; - - // Scroll position - this.scrollPosition = { x: 0, y: 0 }; - - // Force the use of 'full window/browser' rather than fullscreen - this.forceFallback = player.config.fullscreen.fallback === 'force'; - - // Register event listeners - // Handle event (incase user presses escape etc) - on.call( - this.player, - document, - this.prefix === 'ms' ? 'MSFullscreenChange' : `${this.prefix}fullscreenchange`, - () => { - // TODO: Filter for target?? - this.onChange(); - }, - ); - - // Fullscreen toggle on double click - on.call(this.player, this.player.elements.container, 'dblclick', event => { - // Ignore double click in controls - if (is.element(this.player.elements.controls) && this.player.elements.controls.contains(event.target)) { - return; - } - - this.toggle(); - }); - - // Tap focus when in fullscreen - on.call(this, this.player.elements.container, 'keydown', event => this.trapFocus(event)); - - // Update the UI - this.update(); + constructor(player) { + // Keep reference to parent + this.player = player; + + // Get prefix + this.prefix = Fullscreen.prefix; + this.property = Fullscreen.property; + + // Scroll position + this.scrollPosition = { x: 0, y: 0 }; + + // Force the use of 'full window/browser' rather than fullscreen + this.forceFallback = player.config.fullscreen.fallback === 'force'; + + // Register event listeners + // Handle event (incase user presses escape etc) + on.call( + this.player, + document, + this.prefix === 'ms' ? 'MSFullscreenChange' : `${this.prefix}fullscreenchange`, + () => { + // TODO: Filter for target?? + this.onChange(); + }, + ); + + // Fullscreen toggle on double click + on.call(this.player, this.player.elements.container, 'dblclick', event => { + // Ignore double click in controls + if (is.element(this.player.elements.controls) && this.player.elements.controls.contains(event.target)) { + return; + } + + this.toggle(); + }); + + // Tap focus when in fullscreen + on.call(this, this.player.elements.container, 'keydown', event => this.trapFocus(event)); + + // Update the UI + this.update(); + } + + // Determine if native supported + static get native() { + return !!( + document.fullscreenEnabled || + document.webkitFullscreenEnabled || + document.mozFullScreenEnabled || + document.msFullscreenEnabled + ); + } + + // If we're actually using native + get usingNative() { + return Fullscreen.native && !this.forceFallback; + } + + // Get the prefix for handlers + static get prefix() { + // No prefix + if (is.function(document.exitFullscreen)) { + return ''; } - // Determine if native supported - static get native() { - return !!( - document.fullscreenEnabled || - document.webkitFullscreenEnabled || - document.mozFullScreenEnabled || - document.msFullscreenEnabled - ); + // Check for fullscreen support by vendor prefix + let value = ''; + const prefixes = ['webkit', 'moz', 'ms']; + + prefixes.some(pre => { + if (is.function(document[`${pre}ExitFullscreen`]) || is.function(document[`${pre}CancelFullScreen`])) { + value = pre; + return true; + } + + return false; + }); + + return value; + } + + static get property() { + return this.prefix === 'moz' ? 'FullScreen' : 'Fullscreen'; + } + + // Determine if fullscreen is enabled + get enabled() { + return ( + (Fullscreen.native || this.player.config.fullscreen.fallback) && + this.player.config.fullscreen.enabled && + this.player.supported.ui && + this.player.isVideo + ); + } + + // Get active state + get active() { + if (!this.enabled) { + return false; } - // If we're actually using native - get usingNative() { - return Fullscreen.native && !this.forceFallback; + // Fallback using classname + if (!Fullscreen.native || this.forceFallback) { + return hasClass(this.target, this.player.config.classNames.fullscreen.fallback); } - // Get the prefix for handlers - static get prefix() { - // No prefix - if (is.function(document.exitFullscreen)) { - return ''; - } - - // Check for fullscreen support by vendor prefix - let value = ''; - const prefixes = ['webkit', 'moz', 'ms']; + const element = !this.prefix ? document.fullscreenElement : document[`${this.prefix}${this.property}Element`]; - prefixes.some(pre => { - if (is.function(document[`${pre}ExitFullscreen`]) || is.function(document[`${pre}CancelFullScreen`])) { - value = pre; - return true; - } + return element && element.shadowRoot ? element === this.target.getRootNode().host : element === this.target; + } - return false; - }); + // Get target element + get target() { + return browser.isIos && this.player.config.fullscreen.iosNative + ? this.player.media + : this.player.elements.container; + } - return value; + onChange() { + if (!this.enabled) { + return; } - static get property() { - return this.prefix === 'moz' ? 'FullScreen' : 'Fullscreen'; + // Update toggle button + const button = this.player.elements.buttons.fullscreen; + if (is.element(button)) { + button.pressed = this.active; } - // Determine if fullscreen is enabled - get enabled() { - return ( - (Fullscreen.native || this.player.config.fullscreen.fallback) && - this.player.config.fullscreen.enabled && - this.player.supported.ui && - this.player.isVideo - ); + // 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); } - // Get active state - get active() { - if (!this.enabled) { - return false; - } + // Toggle scroll + document.body.style.overflow = toggle ? 'hidden' : ''; - // Fallback using classname - if (!Fullscreen.native || this.forceFallback) { - return hasClass(this.target, this.player.config.classNames.fullscreen.fallback); - } + // Toggle class hook + toggleClass(this.target, this.player.config.classNames.fullscreen.fallback, toggle); - const element = !this.prefix ? document.fullscreenElement : document[`${this.prefix}${this.property}Element`]; + // Force full viewport on iPhone X+ + if (browser.isIos) { + let viewport = document.head.querySelector('meta[name="viewport"]'); + const property = 'viewport-fit=cover'; - return element && element.shadowRoot ? element === this.target.getRootNode().host : element === this.target; - } + // Inject the viewport meta if required + if (!viewport) { + viewport = document.createElement('meta'); + viewport.setAttribute('name', 'viewport'); + } - // Get target element - get target() { - return browser.isIos && this.player.config.fullscreen.iosNative - ? this.player.media - : this.player.elements.container; - } + // Check if the property already exists + const hasProperty = is.string(viewport.content) && viewport.content.includes(property); - onChange() { - if (!this.enabled) { - return; - } + if (toggle) { + this.cleanupViewport = !hasProperty; - // Update toggle button - const button = this.player.elements.buttons.fullscreen; - if (is.element(button)) { - button.pressed = this.active; + if (!hasProperty) { + viewport.content += `,${property}`; } - - // Trigger an event - triggerEvent.call(this.player, this.target, this.active ? 'enterfullscreen' : 'exitfullscreen', true); + } else if (this.cleanupViewport) { + viewport.content = viewport.content + .split(',') + .filter(part => part.trim() !== property) + .join(','); + } } - 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(); + } - // 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; } - // 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(); - } + // 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) { - let mode; - - if (this.forceFallback) { - mode = 'Fallback (forced)'; - } else if (Fullscreen.native) { - mode = 'Native'; - } else { - mode = 'Fallback'; - } - - this.player.debug.log(`${mode} fullscreen enabled`); - } else { - this.player.debug.log('Fullscreen not supported and fallback disabled'); - } - - // Add styling hook to show button - toggleClass(this.player.elements.container, this.player.config.classNames.fullscreen.enabled, this.enabled); + } + + // Update UI + update() { + if (this.enabled) { + let mode; + + if (this.forceFallback) { + mode = 'Fallback (forced)'; + } else if (Fullscreen.native) { + mode = 'Native'; + } else { + mode = 'Fallback'; + } + + this.player.debug.log(`${mode} fullscreen enabled`); + } else { + this.player.debug.log('Fullscreen not supported and fallback disabled'); } - // Make an element fullscreen - enter() { - if (!this.enabled) { - return; - } + // Add styling hook to show button + toggleClass(this.player.elements.container, this.player.config.classNames.fullscreen.enabled, this.enabled); + } - // iOS native fullscreen doesn't need the request step - if (browser.isIos && this.player.config.fullscreen.iosNative) { - this.target.webkitEnterFullscreen(); - } else if (!Fullscreen.native || this.forceFallback) { - this.toggleFallback(true); - } else if (!this.prefix) { - this.target.requestFullscreen({ navigationUI: 'hide' }); - } else if (!is.empty(this.prefix)) { - this.target[`${this.prefix}Request${this.property}`](); - } + // Make an element fullscreen + enter() { + if (!this.enabled) { + return; } - // Bail from fullscreen - exit() { - if (!this.enabled) { - return; - } + // iOS native fullscreen doesn't need the request step + if (browser.isIos && this.player.config.fullscreen.iosNative) { + this.target.webkitEnterFullscreen(); + } else if (!Fullscreen.native || this.forceFallback) { + this.toggleFallback(true); + } else if (!this.prefix) { + this.target.requestFullscreen({ navigationUI: 'hide' }); + } else if (!is.empty(this.prefix)) { + this.target[`${this.prefix}Request${this.property}`](); + } + } - // iOS native fullscreen - if (browser.isIos && this.player.config.fullscreen.iosNative) { - this.target.webkitExitFullscreen(); - silencePromise(this.player.play()); - } else if (!Fullscreen.native || this.forceFallback) { - this.toggleFallback(false); - } else if (!this.prefix) { - (document.cancelFullScreen || document.exitFullscreen).call(document); - } else if (!is.empty(this.prefix)) { - const action = this.prefix === 'moz' ? 'Cancel' : 'Exit'; - document[`${this.prefix}${action}${this.property}`](); - } + // Bail from fullscreen + exit() { + if (!this.enabled) { + return; } - // Toggle state - toggle() { - if (!this.active) { - this.enter(); - } else { - this.exit(); - } + // iOS native fullscreen + if (browser.isIos && this.player.config.fullscreen.iosNative) { + this.target.webkitExitFullscreen(); + silencePromise(this.player.play()); + } else if (!Fullscreen.native || this.forceFallback) { + this.toggleFallback(false); + } else if (!this.prefix) { + (document.cancelFullScreen || document.exitFullscreen).call(document); + } else if (!is.empty(this.prefix)) { + const action = this.prefix === 'moz' ? 'Cancel' : 'Exit'; + document[`${this.prefix}${action}${this.property}`](); + } + } + + // Toggle state + toggle() { + if (!this.active) { + this.enter(); + } else { + this.exit(); } + } } export default Fullscreen; -- cgit v1.2.3 From ba91f23c50a6c7efa7d39dfb7105c16c20671434 Mon Sep 17 00:00:00 2001 From: Sam Potts Date: Fri, 24 Apr 2020 00:39:26 +1000 Subject: Fix linting issues --- src/js/fullscreen.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/js/fullscreen.js') diff --git a/src/js/fullscreen.js b/src/js/fullscreen.js index 1c836352..5029e7de 100644 --- a/src/js/fullscreen.js +++ b/src/js/fullscreen.js @@ -5,7 +5,7 @@ // ========================================================================== import browser from './utils/browser'; -import { getElements, hasClass, toggleClass, closest } from './utils/elements'; +import { closest,getElements, hasClass, toggleClass } from './utils/elements'; import { on, triggerEvent } from './utils/events'; import is from './utils/is'; import { silencePromise } from './utils/promise'; -- cgit v1.2.3 From 9dee5acec68d15d32002e0ccf8e7f539bfe8376c Mon Sep 17 00:00:00 2001 From: Som Meaden Date: Tue, 5 May 2020 16:35:36 +1000 Subject: force fullscreen events to trigger on plyr element (media element in iOS) and not fullscreen container --- src/js/fullscreen.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'src/js/fullscreen.js') diff --git a/src/js/fullscreen.js b/src/js/fullscreen.js index 5029e7de..c44b7f52 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) { -- cgit v1.2.3