Fullscreen API changes, color settings tweaks

This commit is contained in:
Sam Potts 2018-02-11 15:09:34 +11:00
parent d21b58e1c9
commit 73a39769d4
20 changed files with 307 additions and 272 deletions

2
demo/dist/demo.css vendored

File diff suppressed because one or more lines are too long

View File

@ -6,11 +6,11 @@
.button,
.button__count {
align-items: center;
background: #fff;
background: $color-button-background;
border: 0;
border-radius: $border-radius-base;
box-shadow: 0 1px 1px rgba(#000, 0.1);
color: $gray;
color: $color-button-text;
display: inline-flex;
padding: ($spacing-base * 0.75);
position: relative;
@ -71,7 +71,7 @@
&::before {
border: $arrow-size solid transparent;
border-left-width: 0;
border-right-color: #fff;
border-right-color: $color-button-background;
content: '';
height: 0;
position: absolute;

View File

@ -28,6 +28,7 @@ body {
main {
margin: auto;
padding-bottom: 1px; // Collapsing margins
text-align: center;
}

View File

@ -24,5 +24,9 @@ $color-vimeo: #19b7ed;
$color-link: #fff;
$color-background: $color-brand-primary;
// Buttons
$color-button-background: #fff;
$color-button-text: $gray;
// Focus
$tab-focus-default-color: #fff;

2
dist/plyr.css vendored

File diff suppressed because one or more lines are too long

2
dist/plyr.js vendored

File diff suppressed because one or more lines are too long

2
dist/plyr.js.map vendored

File diff suppressed because one or more lines are too long

View File

@ -44,7 +44,7 @@
"stylelint-order": "^0.8.0",
"stylelint-scss": "^2.3.0",
"stylelint-selector-bem-pattern": "^2.0.0",
"uglify-es": "^3.3.9"
"uglify-es": "^3.3.10"
},
"keywords": ["HTML5 Video", "HTML5 Audio", "Media Player", "DASH", "Shaka", "WordPress", "HLS"],
"repository": {

100
readme.md
View File

@ -304,69 +304,77 @@ element.addEventListener('ready', event => {
### Methods
Methods are not chainable. An example use of a method:
Example method use:
```javascript
player.play();
player.play(); // Start playback
player.fullscreen.enter(); // Enter fullscreen
```
| Method | Parameters | Description |
| ------------------------- | ---------------- | ---------------------------------------------------------------------------------------------------------- |
| `play()`¹ | - | Start playback. |
| `pause()` | - | Pause playback. |
| `togglePlay(toggle)` | Boolean | Toggle playback, if no parameters are passed, it will toggle based on current status. |
| `stop()` | - | Stop playback and reset to start. |
| `restart()` | - | Restart playback. |
| `rewind(seekTime)` | Number | Rewind playback by the specified seek time. If no parameter is passed, the default seek time will be used. |
| `forward(seekTime)` | Number | Fast forward by the specified seek time. If no parameter is passed, the default seek time will be used. |
| `increaseVolume(step)` | Number | Increase volume by the specified step. If no parameter is passed, the default step will be used. |
| `decreaseVolume(step)` | Number | Increase volume by the specified step. If no parameter is passed, the default step will be used. |
| `toggleCaptions(toggle)` | Boolean | Toggle captions display. If no parameter is passed, it will toggle based on current status. |
| `toggleFullscreen(event)` | Event | Toggle fullscreen. Fullscreen can only be initiated by a user event. Exit is possible without user input. |
| `airplay()` | - | Trigger the airplay dialog on supported devices. |
| `toggleControls(toggle)` | Boolean | Toggle the controls based on the specified boolean. |
| `on(event, function)` | String, Function | Add an event listener for the specified event. |
| `off(event, function)` | String, Function | Remove an event listener for the specified event. |
| `supports(type)` | String | Check support for a mime type. |
| `destroy()` | - | Destroy the instance and garbage collect any elements. |
| Method | Parameters | Description |
| ------------------------ | ---------------- | ---------------------------------------------------------------------------------------------------------- |
| `play()`¹ | - | Start playback. |
| `pause()` | - | Pause playback. |
| `togglePlay(toggle)` | Boolean | Toggle playback, if no parameters are passed, it will toggle based on current status. |
| `stop()` | - | Stop playback and reset to start. |
| `restart()` | - | Restart playback. |
| `rewind(seekTime)` | Number | Rewind playback by the specified seek time. If no parameter is passed, the default seek time will be used. |
| `forward(seekTime)` | Number | Fast forward by the specified seek time. If no parameter is passed, the default seek time will be used. |
| `increaseVolume(step)` | Number | Increase volume by the specified step. If no parameter is passed, the default step will be used. |
| `decreaseVolume(step)` | Number | Increase volume by the specified step. If no parameter is passed, the default step will be used. |
| `toggleCaptions(toggle)` | Boolean | Toggle captions display. If no parameter is passed, it will toggle based on current status. |
| `fullscreen.enter()` | - | Enter fullscreen. If fullscreen is not supported, a fallback "full window/viewport" is used instead. |
| `fullscreen.exit()` | - | Exit fullscreen. |
| `fullscreen.toggle()` | - | Toggle fullscreen. |
| `airplay()` | - | Trigger the airplay dialog on supported devices. |
| `toggleControls(toggle)` | Boolean | Toggle the controls based on the specified boolean. |
| `on(event, function)` | String, Function | Add an event listener for the specified event. |
| `off(event, function)` | String, Function | Remove an event listener for the specified event. |
| `supports(type)` | String | Check support for a mime type. |
| `destroy()` | - | Destroy the instance and garbage collect any elements. |
1. For HTML5 players, `play()` will return a [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) in _some_ browsers - WebKit and Mozilla [according to MDN](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/play) at time of writing.
### Getters and Setters
An example setter:
Example setters:
```javascript
player.volume = 0.5;
player.volume = 0.5; // Sets volume at 50%
player.currentTime = 10; // Seeks to 10 seconds
```
An example getter:
Example getters:
```javascript
player.volume; // returns 0.5;
player.volume; // 0.5;
player.currentTime; // 10
player.fullscreen.active; // false;
```
| Property | Getter | Setter | Description |
| --------------- | ------ | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `isHTML5` | ✓ | - | Returns a boolean indicating if the current player is HTML5. |
| `isEmbed` | ✓ | - | Returns a boolean indicating if the current player is an embedded player. |
| `paused` | ✓ | - | Returns a boolean indicating if the current player is paused. |
| `playing` | ✓ | - | Returns a boolean indicating if the current player is playing. |
| `ended` | ✓ | - | Returns a boolean indicating if the current player has finished playback. |
| `currentTime` | ✓ | ✓ | Gets or sets the currentTime for the player. The setter accepts a float in seconds. |
| `seeking` | ✓ | - | Returns a boolean indicating if the current player is seeking. |
| `duration` | ✓ | - | Returns the duration for the current media. |
| `volume` | ✓ | ✓ | Gets or sets the volume for the player. The setter accepts a float between 0 and 1. |
| `muted` | ✓ | ✓ | Gets or sets the muted state of the player. The setter accepts a boolean. |
| `hasAudio` | ✓ | - | Returns a boolean indicating if the current media has an audio track. |
| `speed` | ✓ | ✓ | Gets or sets the speed for the player. The setter accepts a value in the options specified in your config. Generally the minimum should be 0.5. |
| `quality`¹ | ✓ | ✓ | Gets or sets the quality for the player. The setter accepts a value from the options specified in your config. |
| `loop` | ✓ | ✓ | Gets or sets the current loop state of the player. The setter accepts a boolean. |
| `source` | ✓ | ✓ | Gets or sets the current source for the player. The setter accepts an object. See [source setter](#source-setter) below for examples. |
| `poster`² | ✓ | ✓ | Gets or sets the current poster image for the player. The setter accepts a string; the URL for the updated poster image. |
| `autoplay` | ✓ | ✓ | Gets or sets the autoplay state of the player. The setter accepts a boolean. |
| `language` | ✓ | ✓ | Gets or sets the preferred captions language for the player. The setter accepts an ISO two-letter language code. Support for the languages is dependent on the captions you include. |
| `pip` | ✓ | ✓ | Gets or sets the picture-in-picture state of the player. The setter accepts a boolean. This currently only supported on Safari 10+ on MacOS Sierra+ and iOS 10+. |
| Property | Getter | Setter | Description |
| -------------------- | ------ | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `isHTML5` | ✓ | - | Returns a boolean indicating if the current player is HTML5. |
| `isEmbed` | ✓ | - | Returns a boolean indicating if the current player is an embedded player. |
| `paused` | ✓ | - | Returns a boolean indicating if the current player is paused. |
| `playing` | ✓ | - | Returns a boolean indicating if the current player is playing. |
| `ended` | ✓ | - | Returns a boolean indicating if the current player has finished playback. |
| `currentTime` | ✓ | ✓ | Gets or sets the currentTime for the player. The setter accepts a float in seconds. |
| `seeking` | ✓ | - | Returns a boolean indicating if the current player is seeking. |
| `duration` | ✓ | - | Returns the duration for the current media. |
| `volume` | ✓ | ✓ | Gets or sets the volume for the player. The setter accepts a float between 0 and 1. |
| `muted` | ✓ | ✓ | Gets or sets the muted state of the player. The setter accepts a boolean. |
| `hasAudio` | ✓ | - | Returns a boolean indicating if the current media has an audio track. |
| `speed` | ✓ | ✓ | Gets or sets the speed for the player. The setter accepts a value in the options specified in your config. Generally the minimum should be 0.5. |
| `quality`¹ | ✓ | ✓ | Gets or sets the quality for the player. The setter accepts a value from the options specified in your config. |
| `loop` | ✓ | ✓ | Gets or sets the current loop state of the player. The setter accepts a boolean. |
| `source` | ✓ | ✓ | Gets or sets the current source for the player. The setter accepts an object. See [source setter](#source-setter) below for examples. |
| `poster`² | ✓ | ✓ | Gets or sets the current poster image for the player. The setter accepts a string; the URL for the updated poster image. |
| `autoplay` | ✓ | ✓ | Gets or sets the autoplay state of the player. The setter accepts a boolean. |
| `language` | ✓ | ✓ | Gets or sets the preferred captions language for the player. The setter accepts an ISO two-letter language code. Support for the languages is dependent on the captions you include. |
| `fullscreen.active` | ✓ | - | Returns a boolean indicating if the current player is in fullscreen mode. |
| `fullscreen.enabled` | ✓ | - | Returns a boolean indicating if the current player has fullscreen enabled. |
| `pip` | ✓ | ✓ | Gets or sets the picture-in-picture state of the player. The setter accepts a boolean. This currently only supported on Safari 10+ on MacOS Sierra+ and iOS 10+. |
1. YouTube only. HTML5 will follow.
2. HTML5 only

View File

@ -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

View File

@ -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;
}
return value;
})();
// Determine if fullscreen is enabled
get enabled() {
const fallback = this.player.config.fullscreen.fallback && !utils.inFrame();
// Fullscreen API
const fullscreen = {
// Get the prefix
prefix,
return (Fullscreen.native || fallback) && this.player.config.fullscreen.enabled && this.player.supported.ui && this.player.isVideo;
}
// Check if we can use it
enabled: document.fullscreenEnabled || document.webkitFullscreenEnabled || document.mozFullScreenEnabled || document.msFullscreenEnabled,
// Yet again Microsoft awesomeness,
// Sometimes the prefix is 'ms', sometimes 'MS' to keep you on your toes
eventType: prefix === 'ms' ? 'MSFullscreenChange' : `${prefix}fullscreenchange`,
// 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);
}
},
const element = !this.prefix ? document.fullscreenElement : document[`${this.prefix}FullscreenElement`];
return element === this.target;
}
// Get target element
get target() {
return browser.isIos && this.player.config.fullscreen.iosNative ? this.player.media : this.player.elements.container;
}
// 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');
}
// Add styling hook to show button
utils.toggleClass(this.player.elements.container, this.player.config.classNames.fullscreen.enabled, this.enabled);
}
// Make an element fullscreen
requestFullScreen(element) {
if (!fullscreen.enabled) {
return false;
}
const target = utils.is.nullOrUndefined(element) ? document.body : element;
return !prefix.length ? target.requestFullScreen() : target[prefix + (prefix === 'ms' ? 'RequestFullscreen' : 'RequestFullScreen')]();
},
// Bail from fullscreen
cancelFullScreen() {
if (!fullscreen.enabled) {
return false;
}
return !prefix.length ? document.cancelFullScreen() : document[prefix + (prefix === 'ms' ? 'ExitFullscreen' : 'CancelFullScreen')]();
},
// Get the current element
element() {
if (!fullscreen.enabled) {
return null;
}
return !prefix.length ? document.fullscreenElement : document[`${prefix}FullscreenElement`];
},
// Setup fullscreen
setup() {
if (!this.supported.ui || this.isAudio || !this.config.fullscreen.enabled) {
enter() {
if (!this.enabled) {
return;
}
// Check for native support
const nativeSupport = fullscreen.enabled;
// 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'}`]();
}
}
if (nativeSupport || (this.config.fullscreen.fallback && !utils.inFrame())) {
this.debug.log(`${nativeSupport ? 'Native' : 'Fallback'} fullscreen enabled`);
// Bail from fullscreen
exit() {
if (!this.enabled) {
return;
}
// Add styling hook to show button
utils.toggleClass(this.elements.container, this.config.classNames.fullscreen.enabled, true);
// 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
toggle() {
if (!this.active) {
this.enter();
} else {
this.debug.log('Fullscreen not supported and fallback disabled');
this.exit();
}
}
}
// Toggle state
if (this.elements.buttons && this.elements.buttons.fullscreen) {
utils.toggleState(this.elements.buttons.fullscreen, false);
}
// Trap focus in container
utils.trapFocus.call(this);
},
};
export default fullscreen;
export default Fullscreen;

View File

@ -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,
);
},
};

View File

@ -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);
}
@ -850,62 +847,6 @@ class Plyr {
return this.captions.language;
}
/**
* 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

View File

@ -136,6 +136,9 @@ const source = {
// Setup interface
ui.build.call(this);
}
// Update the fullscreen support
this.fullscreen.update();
},
true,
);

View File

@ -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);

View File

@ -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();
// 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,
);
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;
}

View File

@ -3,9 +3,9 @@
// --------------------------------------------------------------
.plyr__badge {
background: $plyr-menu-color;
background: $plyr-badge-bg;
border-radius: 2px;
color: $plyr-menu-bg;
color: $plyr-badge-color;
font-size: $plyr-font-size-badge;
line-height: 1;
padding: 3px 4px;

View File

@ -59,6 +59,14 @@
margin: 0;
overflow: hidden;
padding: $plyr-control-padding;
li {
margin-top: 2px;
&:first-child {
margin-top: 0;
}
}
}
// Options

View File

@ -10,6 +10,7 @@
@import 'settings/cosmetics';
@import 'settings/type';
@import 'settings/badges';
@import 'settings/captions';
@import 'settings/controls';
@import 'settings/helpers';

View File

@ -0,0 +1,6 @@
// ==========================================================================
// Badges
// ==========================================================================
$plyr-badge-bg: $plyr-color-fiord !default;
$plyr-badge-color: #fff !default;