diff options
Diffstat (limited to 'src/js')
-rw-r--r-- | src/js/defaults.js | 1 | ||||
-rw-r--r-- | src/js/fullscreen.js | 238 | ||||
-rw-r--r-- | src/js/listeners.js | 49 | ||||
-rw-r--r-- | src/js/plyr.js | 75 | ||||
-rw-r--r-- | src/js/source.js | 3 | ||||
-rw-r--r-- | src/js/ui.js | 4 | ||||
-rw-r--r-- | src/js/utils.js | 57 |
7 files changed, 217 insertions, 210 deletions
diff --git a/src/js/defaults.js b/src/js/defaults.js index 8e50631e..a66c48ef 100644 --- a/src/js/defaults.js +++ b/src/js/defaults.js @@ -120,6 +120,7 @@ const defaults = { fullscreen: { enabled: true, // Allow fullscreen? fallback: true, // Fallback for vintage browsers + iosNative: false, // Use the native fullscreen in iOS (disables custom controls) }, // Local storage diff --git a/src/js/fullscreen.js b/src/js/fullscreen.js index 366ea729..0c031276 100644 --- a/src/js/fullscreen.js +++ b/src/js/fullscreen.js @@ -1,127 +1,201 @@ // ========================================================================== -// Plyr fullscreen API +// Fullscreen wrapper // ========================================================================== import utils from './utils'; -// Determine the prefix -const prefix = (() => { - let value = false; +const browser = utils.getBrowser(); - if (utils.is.function(document.cancelFullScreen)) { - value = ''; +function onChange() { + if (!this.enabled) { + return; + } + + // Update toggle button + const button = this.player.elements.buttons.fullscreen; + if (utils.is.element(button)) { + utils.toggleState(button, this.active); + } + + // Trigger an event + utils.dispatchEvent(this.target, this.active ? 'enterfullscreen' : 'exitfullscreen', true); + + // Trap focus in container + if (!browser.isIos) { + utils.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 + utils.toggleClass(this.target, this.player.config.classNames.fullscreen.fallback, toggle); + + // Toggle button and fire events + onChange.call(this); +} + +class Fullscreen { + constructor(player) { + // Keep reference to parent + this.player = player; + + // Get prefix + this.prefix = Fullscreen.prefix; + + // Scroll position + this.scrollPosition = { x: 0, y: 0 }; + + // Register event listeners + // Handle event (incase user presses escape etc) + utils.on(document, this.prefix === 'ms' ? 'MSFullscreenChange' : `${this.prefix}fullscreenchange`, () => { + // TODO: Filter for target?? + onChange.call(this); + }); + + // Fullscreen toggle on double click + utils.on(this.player.elements.container, 'dblclick', () => { + this.toggle(); + }); + + // Update the UI + this.update(); + } + + // Determine if native supported + static get native() { + return !!(document.fullscreenEnabled || document.webkitFullscreenEnabled || document.mozFullScreenEnabled || document.msFullscreenEnabled); + } + + // Get the prefix for handlers + static get prefix() { + // No prefix + if (utils.is.function(document.cancelFullScreen)) { + return false; + } + // Check for fullscreen support by vendor prefix - [ + let value = ''; + const prefixes = [ 'webkit', - 'o', 'moz', 'ms', - 'khtml', - ].some(pre => { + ]; + + prefixes.some(pre => { if (utils.is.function(document[`${pre}CancelFullScreen`])) { value = pre; return true; - } else if (utils.is.function(document.msExitFullscreen) && document.msFullscreenEnabled) { - // Special case for MS (when isn't it?) + } else if (utils.is.function(document.msExitFullscreen)) { value = 'ms'; return true; } return false; }); - } - - return value; -})(); -// Fullscreen API -const fullscreen = { - // Get the prefix - prefix, + return value; + } - // Check if we can use it - enabled: document.fullscreenEnabled || document.webkitFullscreenEnabled || document.mozFullScreenEnabled || document.msFullscreenEnabled, + // Determine if fullscreen is enabled + get enabled() { + const fallback = this.player.config.fullscreen.fallback && !utils.inFrame(); - // Yet again Microsoft awesomeness, - // Sometimes the prefix is 'ms', sometimes 'MS' to keep you on your toes - eventType: prefix === 'ms' ? 'MSFullscreenChange' : `${prefix}fullscreenchange`, + return (Fullscreen.native || fallback) && this.player.config.fullscreen.enabled && this.player.supported.ui && this.player.isVideo; + } - // Is an element fullscreen - isFullScreen(element) { - if (!fullscreen.enabled) { + // Get active state + get active() { + if (!this.enabled) { return false; } - const target = utils.is.nullOrUndefined(element) ? document.body : element; - - switch (prefix) { - case '': - return document.fullscreenElement === target; - - case 'moz': - return document.mozFullScreenElement === target; - - default: - return document[`${prefix}FullscreenElement`] === target; + // Fallback using classname + if (!Fullscreen.native) { + return utils.hasClass(this.target, this.player.config.classNames.fullscreen.fallback); } - }, - // Make an element fullscreen - requestFullScreen(element) { - if (!fullscreen.enabled) { - return false; - } + const element = !this.prefix ? document.fullscreenElement : document[`${this.prefix}FullscreenElement`]; - const target = utils.is.nullOrUndefined(element) ? document.body : element; + return element === this.target; + } - return !prefix.length ? target.requestFullScreen() : target[prefix + (prefix === 'ms' ? 'RequestFullscreen' : 'RequestFullScreen')](); - }, + // Get target element + get target() { + return browser.isIos && this.player.config.fullscreen.iosNative ? this.player.media : this.player.elements.container; + } - // Bail from fullscreen - cancelFullScreen() { - if (!fullscreen.enabled) { - return false; + // Update UI + update() { + if (this.enabled) { + this.player.debug.log(`${Fullscreen.native ? 'Native' : 'Fallback'} fullscreen enabled`); + } else { + this.player.debug.log('Fullscreen not supported and fallback disabled'); } - return !prefix.length ? document.cancelFullScreen() : document[prefix + (prefix === 'ms' ? 'ExitFullscreen' : 'CancelFullScreen')](); - }, + // Add styling hook to show button + utils.toggleClass(this.player.elements.container, this.player.config.classNames.fullscreen.enabled, this.enabled); + } - // Get the current element - element() { - if (!fullscreen.enabled) { - return null; + // Make an element fullscreen + enter() { + if (!this.enabled) { + return; } - return !prefix.length ? document.fullscreenElement : document[`${prefix}FullscreenElement`]; - }, + // iOS native fullscreen doesn't need the request step + if (browser.isIos && this.player.config.fullscreen.iosNative) { + if (this.player.playing) { + this.target.webkitEnterFullscreen(); + } + } else if (!Fullscreen.native) { + toggleFallback.call(this, true); + } else if (!this.prefix) { + this.target.requestFullScreen(); + } else if (!utils.is.empty(this.prefix)) { + this.target[`${this.prefix}${this.prefix === 'ms' ? 'RequestFullscreen' : 'RequestFullScreen'}`](); + } + } - // Setup fullscreen - setup() { - if (!this.supported.ui || this.isAudio || !this.config.fullscreen.enabled) { + // Bail from fullscreen + exit() { + if (!this.enabled) { return; } - // Check for native support - const nativeSupport = fullscreen.enabled; - - if (nativeSupport || (this.config.fullscreen.fallback && !utils.inFrame())) { - this.debug.log(`${nativeSupport ? 'Native' : 'Fallback'} fullscreen enabled`); - - // Add styling hook to show button - utils.toggleClass(this.elements.container, this.config.classNames.fullscreen.enabled, true); - } else { - this.debug.log('Fullscreen not supported and fallback disabled'); + // iOS native fullscreen + if (browser.isIos && this.player.config.fullscreen.iosNative) { + this.target.webkitExitFullscreen(); + this.player.play(); + } else if (!Fullscreen.native) { + toggleFallback.call(this, false); + } else if (!this.prefix) { + document.cancelFullScreen(); + } else if (!utils.is.empty(this.prefix)) { + document[`${this.prefix}${this.prefix === 'ms' ? 'ExitFullscreen' : 'CancelFullScreen'}`](); } + } - // Toggle state - if (this.elements.buttons && this.elements.buttons.fullscreen) { - utils.toggleState(this.elements.buttons.fullscreen, false); + // Toggle state + toggle() { + if (!this.active) { + this.enter(); + } else { + this.exit(); } + } +} - // Trap focus in container - utils.trapFocus.call(this); - }, -}; - -export default fullscreen; +export default Fullscreen; diff --git a/src/js/listeners.js b/src/js/listeners.js index b3ccc1c6..214f6e7d 100644 --- a/src/js/listeners.js +++ b/src/js/listeners.js @@ -5,7 +5,6 @@ import support from './support'; import utils from './utils'; import controls from './controls'; -import fullscreen from './fullscreen'; import ui from './ui'; // Sniff out the browser @@ -138,7 +137,7 @@ const listeners = { case 70: // F key - this.toggleFullscreen(); + this.fullscreen.toggle(); break; case 67: @@ -171,8 +170,8 @@ const listeners = { // Escape is handle natively when in full screen // So we only need to worry about non native - if (!fullscreen.enabled && this.fullscreen.active && code === 27) { - this.toggleFullscreen(); + if (!this.fullscreen.enabled && this.fullscreen.active && code === 27) { + this.fullscreen.toggle(); } // Store last code for next cycle @@ -215,18 +214,6 @@ const listeners = { this.toggleControls(event); }); } - - // Handle user exiting fullscreen by escaping etc - if (fullscreen.enabled) { - utils.on(document, fullscreen.eventType, event => { - this.toggleFullscreen(event); - }); - - // Fullscreen toggle on double click - utils.on(this.elements.container, 'dblclick', event => { - this.toggleFullscreen(event); - }); - } }, // Listen for media events @@ -266,7 +253,7 @@ const listeners = { utils.on(this.media, 'playing play pause ended', event => ui.checkPlaying.call(this, event)); // Loading - utils.on(this.media, 'stalled waiting canplay seeked playing', event => ui.checkLoading.call(this, event)); + utils.on(this.media, 'waiting canplay seeked playing', event => ui.checkLoading.call(this, event)); // Check if media failed to load // utils.on(this.media, 'play', event => ui.checkFailed.call(this, event)); @@ -307,7 +294,7 @@ const listeners = { event => { event.preventDefault(); }, - false + false, ); } @@ -394,63 +381,63 @@ const listeners = { utils.on(this.elements.buttons.play, 'click', event => proxy(event, 'play', () => { this.togglePlay(); - }) + }), ); // Pause utils.on(this.elements.buttons.restart, 'click', event => proxy(event, 'restart', () => { this.restart(); - }) + }), ); // Rewind utils.on(this.elements.buttons.rewind, 'click', event => proxy(event, 'rewind', () => { this.rewind(); - }) + }), ); // Rewind utils.on(this.elements.buttons.forward, 'click', event => proxy(event, 'forward', () => { this.forward(); - }) + }), ); // Mute toggle utils.on(this.elements.buttons.mute, 'click', event => proxy(event, 'mute', () => { this.muted = !this.muted; - }) + }), ); // Captions toggle utils.on(this.elements.buttons.captions, 'click', event => proxy(event, 'captions', () => { this.toggleCaptions(); - }) + }), ); // Fullscreen toggle utils.on(this.elements.buttons.fullscreen, 'click', event => proxy(event, 'fullscreen', () => { - this.toggleFullscreen(); - }) + this.fullscreen.toggle(); + }), ); // Picture-in-Picture utils.on(this.elements.buttons.pip, 'click', event => proxy(event, 'pip', () => { this.pip = 'toggle'; - }) + }), ); // Airplay utils.on(this.elements.buttons.airplay, 'click', event => proxy(event, 'airplay', () => { this.airplay(); - }) + }), ); // Settings menu @@ -489,7 +476,7 @@ const listeners = { utils.on(this.elements.inputs.seek, inputEvent, event => proxy(event, 'seek', () => { this.currentTime = event.target.value / event.target.max * this.duration; - }) + }), ); // Current time invert @@ -510,7 +497,7 @@ const listeners = { utils.on(this.elements.inputs.volume, inputEvent, event => proxy(event, 'volume', () => { this.volume = event.target.value; - }) + }), ); // Polyfill for lower fill in <input type="range"> for webkit @@ -583,7 +570,7 @@ const listeners = { event.preventDefault(); } }), - false + false, ); }, }; diff --git a/src/js/plyr.js b/src/js/plyr.js index 1d3e0918..148f462a 100644 --- a/src/js/plyr.js +++ b/src/js/plyr.js @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Plyr // plyr.js v3.0.0-beta.12 // https://github.com/sampotts/plyr @@ -11,12 +11,12 @@ import support from './support'; import utils from './utils'; import Console from './console'; +import Fullscreen from './fullscreen'; import Storage from './storage'; import Ads from './plugins/ads'; import captions from './captions'; import controls from './controls'; -import fullscreen from './fullscreen'; import listeners from './listeners'; import media from './media'; import source from './source'; @@ -26,12 +26,6 @@ import ui from './ui'; // TODO: Use a WeakMap for private globals // const globals = new WeakMap(); -// Globals -let scrollPosition = { - x: 0, - y: 0, -}; - // Plyr instance class Plyr { constructor(target, options) { @@ -232,9 +226,6 @@ class Plyr { return; } - // Setup local storage for user settings - this.storage = new Storage(this); - // Check for support again but with type this.supported = support.check(this.type, this.provider, this.config.inline); @@ -244,6 +235,9 @@ class Plyr { return; } + // Setup local storage for user settings + this.storage = new Storage(this); + // Store reference this.media.plyr = this; @@ -278,6 +272,9 @@ class Plyr { ui.build.call(this); } + // Setup fullscreen + this.fullscreen = new Fullscreen(this); + // Setup ads if provided this.ads = new Ads(this); } @@ -851,62 +848,6 @@ class Plyr { } /** - * Toggle fullscreen playback - * Requires user input event - * @param {event} event - */ - toggleFullscreen(event) { - // Video only - if (this.isAudio) { - return; - } - - // Check for native support - if (fullscreen.enabled) { - if (utils.is.event(event) && event.type === fullscreen.eventType) { - // If it's a fullscreen change event, update the state - this.fullscreen.active = fullscreen.isFullScreen(this.elements.container); - } else { - // Else it's a user request to enter or exit - if (!this.fullscreen.active) { - fullscreen.requestFullScreen(this.elements.container); - } else { - fullscreen.cancelFullScreen(); - } - - return; - } - } else { - // Otherwise, it's a simple toggle - this.fullscreen.active = !this.fullscreen.active; - - // Add class hook - utils.toggleClass(this.elements.container, this.config.classNames.fullscreen.fallback, this.fullscreen.active); - - // Make sure we don't lose scroll position - if (this.fullscreen.active) { - scrollPosition = { - x: window.pageXOffset || 0, - y: window.pageYOffset || 0, - }; - } else { - window.scrollTo(scrollPosition.x, scrollPosition.y); - } - - // Bind/unbind escape key - document.body.style.overflow = this.fullscreen.active ? 'hidden' : ''; - } - - // Set button state - if (utils.is.element(this.elements.buttons.fullscreen)) { - utils.toggleState(this.elements.buttons.fullscreen, this.fullscreen.active); - } - - // Trigger an event - utils.dispatchEvent.call(this, this.media, this.fullscreen.active ? 'enterfullscreen' : 'exitfullscreen'); - } - - /** * Toggle picture-in-picture playback on WebKit/MacOS * TODO: update player with state, support, enabled * TODO: detect outside changes diff --git a/src/js/source.js b/src/js/source.js index 9a6b219c..d252ba6b 100644 --- a/src/js/source.js +++ b/src/js/source.js @@ -136,6 +136,9 @@ const source = { // Setup interface ui.build.call(this); } + + // Update the fullscreen support + this.fullscreen.update(); }, true, ); diff --git a/src/js/ui.js b/src/js/ui.js index 14724fc6..e6c77a00 100644 --- a/src/js/ui.js +++ b/src/js/ui.js @@ -5,7 +5,6 @@ import utils from './utils'; import captions from './captions'; import controls from './controls'; -import fullscreen from './fullscreen'; import listeners from './listeners'; const ui = { @@ -63,9 +62,6 @@ const ui = { // Remove native controls ui.toggleNativeControls.call(this); - // Setup fullscreen - fullscreen.setup.call(this); - // Captions captions.setup.call(this); diff --git a/src/js/utils.js b/src/js/utils.js index 3e9f06ff..38e3d402 100644 --- a/src/js/utils.js +++ b/src/js/utils.js @@ -525,41 +525,46 @@ const utils = { }, // Trap focus inside container - trapFocus() { + trapFocus(element = null, toggle = false) { + if (!utils.is.element(element)) { + return; + } + const focusable = utils.getElements.call(this, 'button:not(:disabled), input:not(:disabled), [tabindex]'); const first = focusable[0]; const last = focusable[focusable.length - 1]; - utils.on( - this.elements.container, - 'keydown', - event => { - // Bail if not tab key or not fullscreen - if (event.key !== 'Tab' || event.keyCode !== 9 || !this.fullscreen.active) { - return; - } + const trap = event => { + // Bail if not tab key or not fullscreen + if (event.key !== 'Tab' || event.keyCode !== 9) { + return; + } - // Get the current focused element - const focused = utils.getFocusElement(); - - 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(); - } - }, - false, - ); + // Get the current focused element + const focused = utils.getFocusElement(); + + 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(); + } + }; + + if (toggle) { + utils.on(this.elements.container, 'keydown', trap, false); + } else { + utils.off(this.elements.container, 'keydown', trap, false); + } }, // Toggle event listener toggleListener(elements, event, callback, toggle, passive, capture) { - // Bail if no elements - if (utils.is.nullOrUndefined(elements)) { + // Bail if no elemetns, event, or callback + if (utils.is.empty(elements) || utils.is.empty(event) || !utils.is.function(callback)) { return; } |