aboutsummaryrefslogtreecommitdiffstats
path: root/src/js
diff options
context:
space:
mode:
Diffstat (limited to 'src/js')
-rw-r--r--src/js/defaults.js1
-rw-r--r--src/js/fullscreen.js238
-rw-r--r--src/js/listeners.js49
-rw-r--r--src/js/plyr.js75
-rw-r--r--src/js/source.js3
-rw-r--r--src/js/ui.js4
-rw-r--r--src/js/utils.js57
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;
}