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

View File

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

View File

@ -24,5 +24,9 @@ $color-vimeo: #19b7ed;
$color-link: #fff; $color-link: #fff;
$color-background: $color-brand-primary; $color-background: $color-brand-primary;
// Buttons
$color-button-background: #fff;
$color-button-text: $gray;
// Focus // Focus
$tab-focus-default-color: #fff; $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-order": "^0.8.0",
"stylelint-scss": "^2.3.0", "stylelint-scss": "^2.3.0",
"stylelint-selector-bem-pattern": "^2.0.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"], "keywords": ["HTML5 Video", "HTML5 Audio", "Media Player", "DASH", "Shaka", "WordPress", "HLS"],
"repository": { "repository": {

100
readme.md
View File

@ -304,69 +304,77 @@ element.addEventListener('ready', event => {
### Methods ### Methods
Methods are not chainable. An example use of a method: Example method use:
```javascript ```javascript
player.play(); player.play(); // Start playback
player.fullscreen.enter(); // Enter fullscreen
``` ```
| Method | Parameters | Description | | Method | Parameters | Description |
| ------------------------- | ---------------- | ---------------------------------------------------------------------------------------------------------- | | ------------------------ | ---------------- | ---------------------------------------------------------------------------------------------------------- |
| `play()`¹ | - | Start playback. | | `play()`¹ | - | Start playback. |
| `pause()` | - | Pause playback. | | `pause()` | - | Pause playback. |
| `togglePlay(toggle)` | Boolean | Toggle playback, if no parameters are passed, it will toggle based on current status. | | `togglePlay(toggle)` | Boolean | Toggle playback, if no parameters are passed, it will toggle based on current status. |
| `stop()` | - | Stop playback and reset to start. | | `stop()` | - | Stop playback and reset to start. |
| `restart()` | - | Restart playback. | | `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. | | `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. | | `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. | | `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. | | `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. | | `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. | | `fullscreen.enter()` | - | Enter fullscreen. If fullscreen is not supported, a fallback "full window/viewport" is used instead. |
| `airplay()` | - | Trigger the airplay dialog on supported devices. | | `fullscreen.exit()` | - | Exit fullscreen. |
| `toggleControls(toggle)` | Boolean | Toggle the controls based on the specified boolean. | | `fullscreen.toggle()` | - | Toggle fullscreen. |
| `on(event, function)` | String, Function | Add an event listener for the specified event. | | `airplay()` | - | Trigger the airplay dialog on supported devices. |
| `off(event, function)` | String, Function | Remove an event listener for the specified event. | | `toggleControls(toggle)` | Boolean | Toggle the controls based on the specified boolean. |
| `supports(type)` | String | Check support for a mime type. | | `on(event, function)` | String, Function | Add an event listener for the specified event. |
| `destroy()` | - | Destroy the instance and garbage collect any elements. | | `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. 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 ### Getters and Setters
An example setter: Example setters:
```javascript ```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 ```javascript
player.volume; // returns 0.5; player.volume; // 0.5;
player.currentTime; // 10
player.fullscreen.active; // false;
``` ```
| Property | Getter | Setter | Description | | Property | Getter | Setter | Description |
| --------------- | ------ | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | -------------------- | ------ | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `isHTML5` | ✓ | - | Returns a boolean indicating if the current player is HTML5. | | `isHTML5` | ✓ | - | Returns a boolean indicating if the current player is HTML5. |
| `isEmbed` | ✓ | - | Returns a boolean indicating if the current player is an embedded player. | | `isEmbed` | ✓ | - | Returns a boolean indicating if the current player is an embedded player. |
| `paused` | ✓ | - | Returns a boolean indicating if the current player is paused. | | `paused` | ✓ | - | Returns a boolean indicating if the current player is paused. |
| `playing` | ✓ | - | Returns a boolean indicating if the current player is playing. | | `playing` | ✓ | - | Returns a boolean indicating if the current player is playing. |
| `ended` | ✓ | - | Returns a boolean indicating if the current player has finished playback. | | `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. | | `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. | | `seeking` | ✓ | - | Returns a boolean indicating if the current player is seeking. |
| `duration` | ✓ | - | Returns the duration for the current media. | | `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. | | `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. | | `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. | | `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. | | `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. | | `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. | | `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. | | `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. | | `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. | | `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. | | `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+. | | `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. 1. YouTube only. HTML5 will follow.
2. HTML5 only 2. HTML5 only

View File

@ -120,6 +120,7 @@ const defaults = {
fullscreen: { fullscreen: {
enabled: true, // Allow fullscreen? enabled: true, // Allow fullscreen?
fallback: true, // Fallback for vintage browsers fallback: true, // Fallback for vintage browsers
iosNative: false, // Use the native fullscreen in iOS (disables custom controls)
}, },
// Local storage // Local storage

View File

@ -1,127 +1,201 @@
// ========================================================================== // ==========================================================================
// Plyr fullscreen API // Fullscreen wrapper
// ========================================================================== // ==========================================================================
import utils from './utils'; import utils from './utils';
// Determine the prefix const browser = utils.getBrowser();
const prefix = (() => {
let value = false;
if (utils.is.function(document.cancelFullScreen)) { function onChange() {
value = ''; 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 { } 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 // Check for fullscreen support by vendor prefix
[ let value = '';
const prefixes = [
'webkit', 'webkit',
'o',
'moz', 'moz',
'ms', 'ms',
'khtml', ];
].some(pre => {
prefixes.some(pre => {
if (utils.is.function(document[`${pre}CancelFullScreen`])) { if (utils.is.function(document[`${pre}CancelFullScreen`])) {
value = pre; value = pre;
return true; return true;
} else if (utils.is.function(document.msExitFullscreen) && document.msFullscreenEnabled) { } else if (utils.is.function(document.msExitFullscreen)) {
// Special case for MS (when isn't it?)
value = 'ms'; value = 'ms';
return true; return true;
} }
return false; return false;
}); });
return value;
} }
return value; // Determine if fullscreen is enabled
})(); get enabled() {
const fallback = this.player.config.fullscreen.fallback && !utils.inFrame();
// Fullscreen API return (Fullscreen.native || fallback) && this.player.config.fullscreen.enabled && this.player.supported.ui && this.player.isVideo;
const fullscreen = { }
// Get the prefix
prefix,
// Check if we can use it // Get active state
enabled: document.fullscreenEnabled || document.webkitFullscreenEnabled || document.mozFullScreenEnabled || document.msFullscreenEnabled, get active() {
if (!this.enabled) {
// 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) {
return false; return false;
} }
const target = utils.is.nullOrUndefined(element) ? document.body : element; // Fallback using classname
if (!Fullscreen.native) {
switch (prefix) { return utils.hasClass(this.target, this.player.config.classNames.fullscreen.fallback);
case '':
return document.fullscreenElement === target;
case 'moz':
return document.mozFullScreenElement === target;
default:
return document[`${prefix}FullscreenElement`] === target;
} }
},
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 // Make an element fullscreen
requestFullScreen(element) { enter() {
if (!fullscreen.enabled) { if (!this.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) {
return; return;
} }
// Check for native support // iOS native fullscreen doesn't need the request step
const nativeSupport = fullscreen.enabled; 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())) { // Bail from fullscreen
this.debug.log(`${nativeSupport ? 'Native' : 'Fallback'} fullscreen enabled`); exit() {
if (!this.enabled) {
return;
}
// Add styling hook to show button // iOS native fullscreen
utils.toggleClass(this.elements.container, this.config.classNames.fullscreen.enabled, true); 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 { } else {
this.debug.log('Fullscreen not supported and fallback disabled'); this.exit();
} }
}
}
// Toggle state export default Fullscreen;
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;

View File

@ -5,7 +5,6 @@
import support from './support'; import support from './support';
import utils from './utils'; import utils from './utils';
import controls from './controls'; import controls from './controls';
import fullscreen from './fullscreen';
import ui from './ui'; import ui from './ui';
// Sniff out the browser // Sniff out the browser
@ -138,7 +137,7 @@ const listeners = {
case 70: case 70:
// F key // F key
this.toggleFullscreen(); this.fullscreen.toggle();
break; break;
case 67: case 67:
@ -171,8 +170,8 @@ const listeners = {
// Escape is handle natively when in full screen // Escape is handle natively when in full screen
// So we only need to worry about non native // So we only need to worry about non native
if (!fullscreen.enabled && this.fullscreen.active && code === 27) { if (!this.fullscreen.enabled && this.fullscreen.active && code === 27) {
this.toggleFullscreen(); this.fullscreen.toggle();
} }
// Store last code for next cycle // Store last code for next cycle
@ -215,18 +214,6 @@ const listeners = {
this.toggleControls(event); 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 // Listen for media events
@ -266,7 +253,7 @@ const listeners = {
utils.on(this.media, 'playing play pause ended', event => ui.checkPlaying.call(this, event)); utils.on(this.media, 'playing play pause ended', event => ui.checkPlaying.call(this, event));
// Loading // 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 // Check if media failed to load
// utils.on(this.media, 'play', event => ui.checkFailed.call(this, event)); // utils.on(this.media, 'play', event => ui.checkFailed.call(this, event));
@ -307,7 +294,7 @@ const listeners = {
event => { event => {
event.preventDefault(); event.preventDefault();
}, },
false false,
); );
} }
@ -394,63 +381,63 @@ const listeners = {
utils.on(this.elements.buttons.play, 'click', event => utils.on(this.elements.buttons.play, 'click', event =>
proxy(event, 'play', () => { proxy(event, 'play', () => {
this.togglePlay(); this.togglePlay();
}) }),
); );
// Pause // Pause
utils.on(this.elements.buttons.restart, 'click', event => utils.on(this.elements.buttons.restart, 'click', event =>
proxy(event, 'restart', () => { proxy(event, 'restart', () => {
this.restart(); this.restart();
}) }),
); );
// Rewind // Rewind
utils.on(this.elements.buttons.rewind, 'click', event => utils.on(this.elements.buttons.rewind, 'click', event =>
proxy(event, 'rewind', () => { proxy(event, 'rewind', () => {
this.rewind(); this.rewind();
}) }),
); );
// Rewind // Rewind
utils.on(this.elements.buttons.forward, 'click', event => utils.on(this.elements.buttons.forward, 'click', event =>
proxy(event, 'forward', () => { proxy(event, 'forward', () => {
this.forward(); this.forward();
}) }),
); );
// Mute toggle // Mute toggle
utils.on(this.elements.buttons.mute, 'click', event => utils.on(this.elements.buttons.mute, 'click', event =>
proxy(event, 'mute', () => { proxy(event, 'mute', () => {
this.muted = !this.muted; this.muted = !this.muted;
}) }),
); );
// Captions toggle // Captions toggle
utils.on(this.elements.buttons.captions, 'click', event => utils.on(this.elements.buttons.captions, 'click', event =>
proxy(event, 'captions', () => { proxy(event, 'captions', () => {
this.toggleCaptions(); this.toggleCaptions();
}) }),
); );
// Fullscreen toggle // Fullscreen toggle
utils.on(this.elements.buttons.fullscreen, 'click', event => utils.on(this.elements.buttons.fullscreen, 'click', event =>
proxy(event, 'fullscreen', () => { proxy(event, 'fullscreen', () => {
this.toggleFullscreen(); this.fullscreen.toggle();
}) }),
); );
// Picture-in-Picture // Picture-in-Picture
utils.on(this.elements.buttons.pip, 'click', event => utils.on(this.elements.buttons.pip, 'click', event =>
proxy(event, 'pip', () => { proxy(event, 'pip', () => {
this.pip = 'toggle'; this.pip = 'toggle';
}) }),
); );
// Airplay // Airplay
utils.on(this.elements.buttons.airplay, 'click', event => utils.on(this.elements.buttons.airplay, 'click', event =>
proxy(event, 'airplay', () => { proxy(event, 'airplay', () => {
this.airplay(); this.airplay();
}) }),
); );
// Settings menu // Settings menu
@ -489,7 +476,7 @@ const listeners = {
utils.on(this.elements.inputs.seek, inputEvent, event => utils.on(this.elements.inputs.seek, inputEvent, event =>
proxy(event, 'seek', () => { proxy(event, 'seek', () => {
this.currentTime = event.target.value / event.target.max * this.duration; this.currentTime = event.target.value / event.target.max * this.duration;
}) }),
); );
// Current time invert // Current time invert
@ -510,7 +497,7 @@ const listeners = {
utils.on(this.elements.inputs.volume, inputEvent, event => utils.on(this.elements.inputs.volume, inputEvent, event =>
proxy(event, 'volume', () => { proxy(event, 'volume', () => {
this.volume = event.target.value; this.volume = event.target.value;
}) }),
); );
// Polyfill for lower fill in <input type="range"> for webkit // Polyfill for lower fill in <input type="range"> for webkit
@ -583,7 +570,7 @@ const listeners = {
event.preventDefault(); event.preventDefault();
} }
}), }),
false false,
); );
}, },
}; };

View File

@ -1,4 +1,4 @@
// ========================================================================== // ==========================================================================
// Plyr // Plyr
// plyr.js v3.0.0-beta.12 // plyr.js v3.0.0-beta.12
// https://github.com/sampotts/plyr // https://github.com/sampotts/plyr
@ -11,12 +11,12 @@ import support from './support';
import utils from './utils'; import utils from './utils';
import Console from './console'; import Console from './console';
import Fullscreen from './fullscreen';
import Storage from './storage'; import Storage from './storage';
import Ads from './plugins/ads'; import Ads from './plugins/ads';
import captions from './captions'; import captions from './captions';
import controls from './controls'; import controls from './controls';
import fullscreen from './fullscreen';
import listeners from './listeners'; import listeners from './listeners';
import media from './media'; import media from './media';
import source from './source'; import source from './source';
@ -26,12 +26,6 @@ import ui from './ui';
// TODO: Use a WeakMap for private globals // TODO: Use a WeakMap for private globals
// const globals = new WeakMap(); // const globals = new WeakMap();
// Globals
let scrollPosition = {
x: 0,
y: 0,
};
// Plyr instance // Plyr instance
class Plyr { class Plyr {
constructor(target, options) { constructor(target, options) {
@ -232,9 +226,6 @@ class Plyr {
return; return;
} }
// Setup local storage for user settings
this.storage = new Storage(this);
// Check for support again but with type // Check for support again but with type
this.supported = support.check(this.type, this.provider, this.config.inline); this.supported = support.check(this.type, this.provider, this.config.inline);
@ -244,6 +235,9 @@ class Plyr {
return; return;
} }
// Setup local storage for user settings
this.storage = new Storage(this);
// Store reference // Store reference
this.media.plyr = this; this.media.plyr = this;
@ -278,6 +272,9 @@ class Plyr {
ui.build.call(this); ui.build.call(this);
} }
// Setup fullscreen
this.fullscreen = new Fullscreen(this);
// Setup ads if provided // Setup ads if provided
this.ads = new Ads(this); this.ads = new Ads(this);
} }
@ -850,62 +847,6 @@ class Plyr {
return this.captions.language; 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 * Toggle picture-in-picture playback on WebKit/MacOS
* TODO: update player with state, support, enabled * TODO: update player with state, support, enabled

View File

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

View File

@ -5,7 +5,6 @@
import utils from './utils'; import utils from './utils';
import captions from './captions'; import captions from './captions';
import controls from './controls'; import controls from './controls';
import fullscreen from './fullscreen';
import listeners from './listeners'; import listeners from './listeners';
const ui = { const ui = {
@ -63,9 +62,6 @@ const ui = {
// Remove native controls // Remove native controls
ui.toggleNativeControls.call(this); ui.toggleNativeControls.call(this);
// Setup fullscreen
fullscreen.setup.call(this);
// Captions // Captions
captions.setup.call(this); captions.setup.call(this);

View File

@ -525,41 +525,46 @@ const utils = {
}, },
// Trap focus inside container // 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 focusable = utils.getElements.call(this, 'button:not(:disabled), input:not(:disabled), [tabindex]');
const first = focusable[0]; const first = focusable[0];
const last = focusable[focusable.length - 1]; const last = focusable[focusable.length - 1];
utils.on( const trap = event => {
this.elements.container, // Bail if not tab key or not fullscreen
'keydown', if (event.key !== 'Tab' || event.keyCode !== 9) {
event => { return;
// Bail if not tab key or not fullscreen }
if (event.key !== 'Tab' || event.keyCode !== 9 || !this.fullscreen.active) {
return;
}
// Get the current focused element // Get the current focused element
const focused = utils.getFocusElement(); const focused = utils.getFocusElement();
if (focused === last && !event.shiftKey) { if (focused === last && !event.shiftKey) {
// Move focus to first element that can be tabbed if Shift isn't used // Move focus to first element that can be tabbed if Shift isn't used
first.focus(); first.focus();
event.preventDefault(); event.preventDefault();
} else if (focused === first && event.shiftKey) { } else if (focused === first && event.shiftKey) {
// Move focus to last element that can be tabbed if Shift is used // Move focus to last element that can be tabbed if Shift is used
last.focus(); last.focus();
event.preventDefault(); event.preventDefault();
} }
}, };
false,
); if (toggle) {
utils.on(this.elements.container, 'keydown', trap, false);
} else {
utils.off(this.elements.container, 'keydown', trap, false);
}
}, },
// Toggle event listener // Toggle event listener
toggleListener(elements, event, callback, toggle, passive, capture) { toggleListener(elements, event, callback, toggle, passive, capture) {
// Bail if no elements // Bail if no elemetns, event, or callback
if (utils.is.nullOrUndefined(elements)) { if (utils.is.empty(elements) || utils.is.empty(event) || !utils.is.function(callback)) {
return; return;
} }

View File

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

View File

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

View File

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

View File

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