mirror of
https://github.com/videojs/video.js.git
synced 2024-12-27 02:43:45 +02:00
feat: add hotkeys support ("m", "f", "k", and Space) (#5571)
Extend keyboard support for the SeekBar, and pass unhandled keydown events from components back to the player. Switch from raw keycodes to the keycode module. Using `userActions.hotkeys`, which can either be a function to match the hotkeys plugin, or an object with properties like `fullscreenKey`, see the documentation for more info. This is currently off by default, we will consider turning it on by default in the future, see #5765. Fixes #4048, fixes #3022.
This commit is contained in:
parent
9786d8a29f
commit
61053bf674
@ -35,6 +35,11 @@
|
||||
* [sources](#sources)
|
||||
* [techCanOverridePoster](#techcanoverrideposter)
|
||||
* [techOrder](#techorder)
|
||||
* [userActions](#useractions)
|
||||
* [userActions.hotkeys](#useractions.hotkeys)
|
||||
* [userActions.hotkeys.fullscreenKey](#useractions.hotkeys.fullscreenkey)
|
||||
* [userActions.hotkeys.muteKey](#useractions.hotkeys.mutekey)
|
||||
* [userActions.hotkeys.playPauseKey](#useractions.hotkeys.playpausekey)
|
||||
* [vtt.js](#vttjs)
|
||||
* [Component Options](#component-options)
|
||||
* [children](#children-1)
|
||||
@ -365,7 +370,82 @@ This can be useful when multiple techs are used and each has to set their own po
|
||||
|
||||
> Type: `Array`, Default: `['html5']`
|
||||
|
||||
Defines the order in which Video.js techs are preferred. By default, this means that the `Html5` tech is preferred. Other regisetered techs will be added after this tech in the order in which they are registered.
|
||||
Defines the order in which Video.js techs are preferred. By default, this means that the `Html5` tech is preferred. Other registered techs will be added after this tech in the order in which they are registered.
|
||||
|
||||
### `userActions`
|
||||
|
||||
> Type: `Object`
|
||||
|
||||
### `userActions.hotkeys`
|
||||
|
||||
> Type: `boolean|function|object`
|
||||
|
||||
Controls how player-wide hotkeys operate. If set to `false`, or `undefined`, hotkeys are disabled. If set to `true` or an object (to allow definitions of `fullscreenKey` etc. below), hotkeys are enabled as described below. To override the default hotkey handling, set `userActions.hotkeys` to a function which accepts a `keydown` event:
|
||||
|
||||
```js
|
||||
var player = videojs('my-player', {
|
||||
userActions: {
|
||||
hotkeys: function(event) {
|
||||
// `this` is the player in this context
|
||||
|
||||
// `x` key = pause
|
||||
if (event.which === 88) {
|
||||
this.pause();
|
||||
}
|
||||
// `y` key = play
|
||||
if (event.which === 89) {
|
||||
this.play();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
Default hotkey handling is:
|
||||
|
||||
| Key | Action | Enabled by |
|
||||
| :-: | ------ | ---------- |
|
||||
| `f` | toggle fullscreen | only enabled if a Fullscreen button is present in the Control Bar
|
||||
| `m` | toggle mute | always enabled, even if no Control Bar is present
|
||||
| `k` | toggle play/pause | always enabled, even if no Control Bar is present
|
||||
| `Space` | toggle play/pause | always enabled, even if no Control Bar is present
|
||||
|
||||
Note that the `Space` key activates controls such as buttons and menus if that control has keyboard focus. The other hotkeys work regardless of which
|
||||
control in the player has focus.
|
||||
|
||||
### `userActions.hotkeys.fullscreenKey`
|
||||
|
||||
> Type: `function`
|
||||
|
||||
Override the fullscreen key definition. If this is set, the function receives the `keydown` event; if the function returns `true`, then the fullscreen toggle action is performed.
|
||||
|
||||
```js
|
||||
var player = videojs('my-player', {
|
||||
userActions: {
|
||||
hotkeys: {
|
||||
muteKey: function(event) {
|
||||
// disable mute key
|
||||
},
|
||||
fullscreenKey: function(event) {
|
||||
// override fullscreen to trigger when pressing the v key
|
||||
return (event.which === 86);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### `userActions.hotkeys.muteKey`
|
||||
|
||||
> Type: `function`
|
||||
|
||||
Override the mute key definition. If this is set, the function receives the `keydown` event; if the function returns `true`, then the mute toggle action is performed.
|
||||
|
||||
### `userActions.hotkeys.playPauseKey`
|
||||
|
||||
> Type: `function`
|
||||
|
||||
Override the play/pause key definition. If this is set, the function receives the `keydown` event; if the function returns `true`, then the play/pause toggle action is performed.
|
||||
|
||||
### `vtt.js`
|
||||
|
||||
|
5145
package-lock.json
generated
5145
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -81,6 +81,7 @@
|
||||
"@babel/runtime": "^7.2.0",
|
||||
"@videojs/http-streaming": "1.8.0",
|
||||
"global": "4.3.2",
|
||||
"keycode": "^2.2.0",
|
||||
"safe-json-parse": "4.0.0",
|
||||
"tsml": "1.0.1",
|
||||
"videojs-font": "3.1.0",
|
||||
|
@ -90,7 +90,8 @@ const externals = {
|
||||
'mux.js/lib/mp4',
|
||||
'mux.js/lib/tools/ts-inspector.js',
|
||||
'mux.js/lib/mp4/probe',
|
||||
'aes-decrypter'
|
||||
'aes-decrypter',
|
||||
'keycode'
|
||||
]),
|
||||
test: Object.keys(globals.test).concat([
|
||||
])
|
||||
|
158
sandbox/hotkeys.html.example
Normal file
158
sandbox/hotkeys.html.example
Normal file
@ -0,0 +1,158 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Video.js Hotkeys - Sandbox</title>
|
||||
<link href="../dist/video-js.css" rel="stylesheet" type="text/css">
|
||||
<script src="../dist/video.js"></script>
|
||||
<style>
|
||||
.hotkeys-function { background: #FF6961; }
|
||||
.hotkeys-override { background: #77DD77; }
|
||||
.hotkeys-normal { background: #AEC6CF; }
|
||||
|
||||
.video-js {
|
||||
height: 150px;
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
display: grid;
|
||||
margin: 0 auto;
|
||||
grid-gap: 10px;
|
||||
grid-template-columns: 300px 300px 300px;
|
||||
}
|
||||
.panel > p:first-child {
|
||||
border-bottom: black 1px solid;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Video.js Hotkeys</h1>
|
||||
<p>All the various ways to configure hotkeys.</p>
|
||||
<div class="wrapper">
|
||||
<div class="panel hotkeys-normal">
|
||||
<p>Default (no) hotkeys</p>
|
||||
<video-js
|
||||
id="vid0"
|
||||
controls
|
||||
preload="auto"
|
||||
poster="//vjs.zencdn.net/v/oceans.png">
|
||||
<source src="//vjs.zencdn.net/v/oceans.mp4" type="video/mp4">
|
||||
<source src="//vjs.zencdn.net/v/oceans.webm" type="video/webm">
|
||||
<source src="//vjs.zencdn.net/v/oceans.ogv" type="video/ogg">
|
||||
<track kind="captions" src="../docs/examples/shared/example-captions.vtt" srclang="en" label="English">
|
||||
<p class="vjs-no-js">To view this video please enable JavaScript, and consider upgrading to a web browser that <a href="http://videojs.com/html5-video-support/" target="_blank">supports HTML5 video</a></p>
|
||||
</video-js>
|
||||
</div>
|
||||
<div class="panel hotkeys-normal">
|
||||
<p>Disable hotkeys</p>
|
||||
<video-js
|
||||
id="vid1"
|
||||
controls
|
||||
preload="auto"
|
||||
poster="//vjs.zencdn.net/v/oceans.png">
|
||||
<source src="//vjs.zencdn.net/v/oceans.mp4" type="video/mp4">
|
||||
<source src="//vjs.zencdn.net/v/oceans.webm" type="video/webm">
|
||||
<source src="//vjs.zencdn.net/v/oceans.ogv" type="video/ogg">
|
||||
<track kind="captions" src="../docs/examples/shared/example-captions.vtt" srclang="en" label="English">
|
||||
<p class="vjs-no-js">To view this video please enable JavaScript, and consider upgrading to a web browser that <a href="http://videojs.com/html5-video-support/" target="_blank">supports HTML5 video</a></p>
|
||||
</video-js>
|
||||
</div>
|
||||
<div class="panel hotkeys-normal">
|
||||
<p>Enable default hotkeys (k = play/pause, m = mute/unmute, f = fullscreen)</p>
|
||||
<video-js
|
||||
id="vid12"
|
||||
controls
|
||||
preload="auto"
|
||||
poster="//vjs.zencdn.net/v/oceans.png">
|
||||
<source src="//vjs.zencdn.net/v/oceans.mp4" type="video/mp4">
|
||||
<source src="//vjs.zencdn.net/v/oceans.webm" type="video/webm">
|
||||
<source src="//vjs.zencdn.net/v/oceans.ogv" type="video/ogg">
|
||||
<track kind="captions" src="../docs/examples/shared/example-captions.vtt" srclang="en" label="English">
|
||||
<p class="vjs-no-js">To view this video please enable JavaScript, and consider upgrading to a web browser that <a href="http://videojs.com/html5-video-support/" target="_blank">supports HTML5 video</a></p>
|
||||
</video-js>
|
||||
</div>
|
||||
<div class="panel hotkeys-function">
|
||||
<p>Custom hotkey function (x = pause, y = play)</p>
|
||||
<video-js
|
||||
id="vid2"
|
||||
controls
|
||||
preload="auto"
|
||||
poster="//vjs.zencdn.net/v/oceans.png">
|
||||
<source src="//vjs.zencdn.net/v/oceans.mp4" type="video/mp4">
|
||||
<source src="//vjs.zencdn.net/v/oceans.webm" type="video/webm">
|
||||
<source src="//vjs.zencdn.net/v/oceans.ogv" type="video/ogg">
|
||||
<track kind="captions" src="../docs/examples/shared/example-captions.vtt" srclang="en" label="English">
|
||||
<p class="vjs-no-js">To view this video please enable JavaScript, and consider upgrading to a web browser that <a href="http://videojs.com/html5-video-support/" target="_blank">supports HTML5 video</a></p>
|
||||
</video-js>
|
||||
</div>
|
||||
<div class="panel hotkeys-override">
|
||||
<p>Customize specific hotkeys (z = play/pause, v = fullscreen)</p>
|
||||
<video-js
|
||||
id="vid3"
|
||||
controls
|
||||
preload="auto"
|
||||
poster="//vjs.zencdn.net/v/oceans.png">
|
||||
<source src="//vjs.zencdn.net/v/oceans.mp4" type="video/mp4">
|
||||
<source src="//vjs.zencdn.net/v/oceans.webm" type="video/webm">
|
||||
<source src="//vjs.zencdn.net/v/oceans.ogv" type="video/ogg">
|
||||
<track kind="captions" src="../docs/examples/shared/example-captions.vtt" srclang="en" label="English">
|
||||
<p class="vjs-no-js">To view this video please enable JavaScript, and consider upgrading to a web browser that <a href="http://videojs.com/html5-video-support/" target="_blank">supports HTML5 video</a></p>
|
||||
</video-js>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
var player0 = videojs('vid0', {});
|
||||
|
||||
// Note that we support both explicitly disbling and explicitly enabling Hotkeys
|
||||
// because one day the default may change from disabled to enabled.
|
||||
var player1 = videojs('vid1', {
|
||||
userActions: {
|
||||
hotkeys: false
|
||||
}
|
||||
});
|
||||
|
||||
var player12 = videojs('vid12', {
|
||||
userActions: {
|
||||
hotkeys: true
|
||||
}
|
||||
});
|
||||
|
||||
var player2 = videojs('vid2', {
|
||||
userActions: {
|
||||
hotkeys: function(event) {
|
||||
// `this` is the player in this context
|
||||
|
||||
// `x` key = pause
|
||||
if (event.which === 88) {
|
||||
this.pause();
|
||||
}
|
||||
// `y` key = play
|
||||
if (event.which === 89) {
|
||||
this.play();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var player3 = videojs('vid3', {
|
||||
userActions: {
|
||||
hotkeys: {
|
||||
playPauseKey: function(event) {
|
||||
// override play/pause to trigger when pressing the z key
|
||||
return (event.which === 90);
|
||||
},
|
||||
muteKey: function(event) {
|
||||
// disable mute key
|
||||
},
|
||||
fullscreenKey: function(event) {
|
||||
// override fullscreen to trigger when pressing the v key
|
||||
return (event.which === 86);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -47,6 +47,8 @@ class BigPlayButton extends Button {
|
||||
// exit early if clicked via the mouse
|
||||
if (this.mouseused_ && event.clientX && event.clientY) {
|
||||
silencePromise(playPromise);
|
||||
// call handleFocus manually to get hotkeys working
|
||||
this.player_.handleFocus({});
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -5,6 +5,7 @@ import ClickableComponent from './clickable-component.js';
|
||||
import Component from './component';
|
||||
import log from './utils/log.js';
|
||||
import {assign} from './utils/obj';
|
||||
import keycode from 'keycode';
|
||||
|
||||
/**
|
||||
* Base class for all buttons.
|
||||
@ -104,14 +105,12 @@ class Button extends ClickableComponent {
|
||||
* @listens keydown
|
||||
*/
|
||||
handleKeyPress(event) {
|
||||
// Ignore Space or Enter key operation, which is handled by the browser for a button.
|
||||
if (!(keycode.isEventKey(event, 'Space') || keycode.isEventKey(event, 'Enter'))) {
|
||||
|
||||
// Ignore Space (32) or Enter (13) key operation, which is handled by the browser for a button.
|
||||
if (event.which === 32 || event.which === 13) {
|
||||
return;
|
||||
// Pass keypress handling up for unsupported keys
|
||||
super.handleKeyPress(event);
|
||||
}
|
||||
|
||||
// Pass keypress handling up for unsupported keys
|
||||
super.handleKeyPress(event);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -8,6 +8,7 @@ import * as Fn from './utils/fn.js';
|
||||
import log from './utils/log.js';
|
||||
import document from 'global/document';
|
||||
import {assign} from './utils/obj';
|
||||
import keycode from 'keycode';
|
||||
|
||||
/**
|
||||
* Clickable Component which is clickable or keyboard actionable,
|
||||
@ -224,12 +225,11 @@ class ClickableComponent extends Component {
|
||||
* @listens keydown
|
||||
*/
|
||||
handleKeyPress(event) {
|
||||
|
||||
// Support Space (32) or Enter (13) key operation to fire a click event
|
||||
if (event.which === 32 || event.which === 13) {
|
||||
// Support Space or Enter key operation to fire a click event
|
||||
if (keycode.isEventKey(event, 'Space') || keycode.isEventKey(event, 'Enter')) {
|
||||
event.preventDefault();
|
||||
this.trigger('click');
|
||||
} else if (super.handleKeyPress) {
|
||||
} else {
|
||||
|
||||
// Pass keypress handling up for unsupported keys
|
||||
super.handleKeyPress(event);
|
||||
|
@ -36,6 +36,21 @@ class CloseButton extends Button {
|
||||
return `vjs-close-button ${super.buildCSSClass()}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* This gets called when a `CloseButton` has focus and `keydown` is triggered via a key
|
||||
* press.
|
||||
*
|
||||
* @param {EventTarget~Event} event
|
||||
* The event that caused this function to get called.
|
||||
*
|
||||
* @listens keydown
|
||||
*/
|
||||
handleKeyPress(event) {
|
||||
// Override the default `Button` behavior, and don't pass the keypress event
|
||||
// up to the player because this button is part of a `ModalDialog`, which
|
||||
// doesn't pass keypresses to the player either.
|
||||
}
|
||||
|
||||
/**
|
||||
* This gets called when a `CloseButton` gets clicked. See
|
||||
* {@link ClickableComponent#handleClick} for more information on when this will be
|
||||
|
@ -1077,6 +1077,19 @@ class Component {
|
||||
this.el_.blur();
|
||||
}
|
||||
|
||||
/**
|
||||
* When this Component receives a keydown event which it does not process,
|
||||
* it passes the event to the Player for handling.
|
||||
*
|
||||
* @param {EventTarget~Event} event
|
||||
* The `keydown` event that caused this function to be called.
|
||||
*/
|
||||
handleKeyPress(event) {
|
||||
if (this.player_) {
|
||||
this.player_.handleKeyPress(event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit a 'tap' events when touch event support gets detected. This gets used to
|
||||
* support toggling the controls through a tap on the video. They get enabled
|
||||
|
@ -8,6 +8,7 @@ import * as Dom from '../../utils/dom.js';
|
||||
import * as Fn from '../../utils/fn.js';
|
||||
import formatTime from '../../utils/format-time.js';
|
||||
import {silencePromise} from '../../utils/promise';
|
||||
import keycode from 'keycode';
|
||||
|
||||
import './load-progress-bar.js';
|
||||
import './play-progress-bar.js';
|
||||
@ -16,6 +17,9 @@ import './mouse-time-display.js';
|
||||
// The number of seconds the `step*` functions move the timeline.
|
||||
const STEP_SECONDS = 5;
|
||||
|
||||
// The multiplier of STEP_SECONDS that PgUp/PgDown move the timeline.
|
||||
const PAGE_KEY_MULTIPLIER = 12;
|
||||
|
||||
// The interval at which the bar should update as it progresses.
|
||||
const UPDATE_REFRESH_INTERVAL = 30;
|
||||
|
||||
@ -362,8 +366,15 @@ class SeekBar extends Slider {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when this SeekBar has focus and a key gets pressed down. By
|
||||
* default it will call `this.handleAction` when the key is space or enter.
|
||||
* Called when this SeekBar has focus and a key gets pressed down.
|
||||
* Supports the following keys:
|
||||
*
|
||||
* Space or Enter key fire a click event
|
||||
* Home key moves to start of the timeline
|
||||
* End key moves to end of the timeline
|
||||
* Digit "0" through "9" keys move to 0%, 10% ... 80%, 90% of the timeline
|
||||
* PageDown key moves back a larger step than ArrowDown
|
||||
* PageUp key moves forward a large step
|
||||
*
|
||||
* @param {EventTarget~Event} event
|
||||
* The `keydown` event that caused this function to be called.
|
||||
@ -371,13 +382,27 @@ class SeekBar extends Slider {
|
||||
* @listens keydown
|
||||
*/
|
||||
handleKeyPress(event) {
|
||||
|
||||
// Support Space (32) or Enter (13) key operation to fire a click event
|
||||
if (event.which === 32 || event.which === 13) {
|
||||
if (keycode.isEventKey(event, 'Space') || keycode.isEventKey(event, 'Enter')) {
|
||||
event.preventDefault();
|
||||
this.handleAction(event);
|
||||
} else if (super.handleKeyPress) {
|
||||
} else if (keycode.isEventKey(event, 'Home')) {
|
||||
event.preventDefault();
|
||||
this.player_.currentTime(0);
|
||||
} else if (keycode.isEventKey(event, 'End')) {
|
||||
event.preventDefault();
|
||||
this.player_.currentTime(this.player_.duration());
|
||||
} else if (/^[0-9]$/.test(keycode(event))) {
|
||||
event.preventDefault();
|
||||
const gotoFraction = (keycode.codes[keycode(event)] - keycode.codes['0']) * 10.0 / 100.0;
|
||||
|
||||
this.player_.currentTime(this.player_.duration() * gotoFraction);
|
||||
} else if (keycode.isEventKey(event, 'PgDn')) {
|
||||
event.preventDefault();
|
||||
this.player_.currentTime(this.player_.currentTime() - (STEP_SECONDS * PAGE_KEY_MULTIPLIER));
|
||||
} else if (keycode.isEventKey(event, 'PgUp')) {
|
||||
event.preventDefault();
|
||||
this.player_.currentTime(this.player_.currentTime() + (STEP_SECONDS * PAGE_KEY_MULTIPLIER));
|
||||
} else {
|
||||
// Pass keypress handling up for unsupported keys
|
||||
super.handleKeyPress(event);
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ import * as Events from '../utils/events.js';
|
||||
import toTitleCase from '../utils/to-title-case.js';
|
||||
import { IS_IOS } from '../utils/browser.js';
|
||||
import document from 'global/document';
|
||||
import keycode from 'keycode';
|
||||
|
||||
/**
|
||||
* A `MenuButton` class for any popup {@link Menu}.
|
||||
@ -282,24 +283,28 @@ class MenuButton extends Component {
|
||||
* @listens keydown
|
||||
*/
|
||||
handleKeyPress(event) {
|
||||
|
||||
// Escape (27) key or Tab (9) key unpress the 'button'
|
||||
if (event.which === 27 || event.which === 9) {
|
||||
// Escape or Tab unpress the 'button'
|
||||
if (keycode.isEventKey(event, 'Esc') || keycode.isEventKey(event, 'Tab')) {
|
||||
if (this.buttonPressed_) {
|
||||
this.unpressButton();
|
||||
}
|
||||
// Don't preventDefault for Tab key - we still want to lose focus
|
||||
if (event.which !== 9) {
|
||||
if (!keycode.isEventKey(event, 'Tab')) {
|
||||
event.preventDefault();
|
||||
// Set focus back to the menu button's button
|
||||
this.menuButton_.el_.focus();
|
||||
this.menuButton_.focus();
|
||||
}
|
||||
// Enter (13) or Up (38) key or Down (40) key press the 'button'
|
||||
} else if (event.which === 13 || event.which === 38 || event.which === 40) {
|
||||
// Up Arrow or Down Arrow also 'press' the button to open the menu
|
||||
} else if (keycode.isEventKey(event, 'Up') || keycode.isEventKey(event, 'Down')) {
|
||||
if (!this.buttonPressed_) {
|
||||
this.pressButton();
|
||||
event.preventDefault();
|
||||
this.pressButton();
|
||||
}
|
||||
} else {
|
||||
// NOTE: This is a special case where we don't pass unhandled
|
||||
// keypress events up to the Component handler, because it is
|
||||
// just entending the keypress handling of the actual `Button`
|
||||
// in the `MenuButton` which already passes unused keys up.
|
||||
}
|
||||
}
|
||||
|
||||
@ -313,18 +318,22 @@ class MenuButton extends Component {
|
||||
* @listens keydown
|
||||
*/
|
||||
handleSubmenuKeyPress(event) {
|
||||
|
||||
// Escape (27) key or Tab (9) key unpress the 'button'
|
||||
if (event.which === 27 || event.which === 9) {
|
||||
// Escape or Tab unpress the 'button'
|
||||
if (keycode.isEventKey(event, 'Esc') || keycode.isEventKey(event, 'Tab')) {
|
||||
if (this.buttonPressed_) {
|
||||
this.unpressButton();
|
||||
}
|
||||
// Don't preventDefault for Tab key - we still want to lose focus
|
||||
if (event.which !== 9) {
|
||||
if (!keycode.isEventKey(event, 'Tab')) {
|
||||
event.preventDefault();
|
||||
// Set focus back to the menu button's button
|
||||
this.menuButton_.el_.focus();
|
||||
this.menuButton_.focus();
|
||||
}
|
||||
} else {
|
||||
// NOTE: This is a special case where we don't pass unhandled
|
||||
// keypress events up to the Component handler, because it is
|
||||
// just entending the keypress handling of the `MenuItem`
|
||||
// in the `Menu` which already passes unused keys up.
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,6 +4,8 @@
|
||||
import ClickableComponent from '../clickable-component.js';
|
||||
import Component from '../component.js';
|
||||
import {assign} from '../utils/obj';
|
||||
import {MenuKeys} from './menu-keys.js';
|
||||
import keycode from 'keycode';
|
||||
|
||||
/**
|
||||
* The component for a menu item. `<li>`
|
||||
@ -68,6 +70,22 @@ class MenuItem extends ClickableComponent {
|
||||
}, props), attrs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ignore keys which are used by the menu, but pass any other ones up. See
|
||||
* {@link ClickableComponent#handleKeyPress} for instances where this is called.
|
||||
*
|
||||
* @param {EventTarget~Event} event
|
||||
* The `keydown` event that caused this function to be called.
|
||||
*
|
||||
* @listens keydown
|
||||
*/
|
||||
handleKeyPress(event) {
|
||||
if (!MenuKeys.some((key) => keycode.isEventKey(event, key))) {
|
||||
// Pass keypress handling up for unused keys
|
||||
super.handleKeyPress(event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Any click on a `MenuItem` puts it into the selected state.
|
||||
* See {@link ClickableComponent#handleClick} for instances where this is called.
|
||||
|
19
src/js/menu/menu-keys.js
Normal file
19
src/js/menu/menu-keys.js
Normal file
@ -0,0 +1,19 @@
|
||||
/**
|
||||
* @file menu-keys.js
|
||||
*/
|
||||
|
||||
/**
|
||||
* All keys used for operation of a menu (`MenuButton`, `Menu`, and `MenuItem`)
|
||||
* Note that 'Enter' and 'Space' are not included here (otherwise they would
|
||||
* prevent the `MenuButton` and `MenuItem` from being keyboard-clickable)
|
||||
* @typedef MenuKeys
|
||||
* @array
|
||||
*/
|
||||
export const MenuKeys = [
|
||||
'Tab',
|
||||
'Esc',
|
||||
'Up',
|
||||
'Down',
|
||||
'Right',
|
||||
'Left'
|
||||
];
|
@ -6,6 +6,7 @@ import document from 'global/document';
|
||||
import * as Dom from '../utils/dom.js';
|
||||
import * as Fn from '../utils/fn.js';
|
||||
import * as Events from '../utils/events.js';
|
||||
import keycode from 'keycode';
|
||||
|
||||
/**
|
||||
* The Menu component is used to build popup menus, including subtitle and
|
||||
@ -132,14 +133,20 @@ class Menu extends Component {
|
||||
*/
|
||||
handleKeyPress(event) {
|
||||
// Left and Down Arrows
|
||||
if (event.which === 37 || event.which === 40) {
|
||||
if (keycode.isEventKey(event, 'Left') || keycode.isEventKey(event, 'Down')) {
|
||||
event.preventDefault();
|
||||
this.stepForward();
|
||||
|
||||
// Up and Right Arrows
|
||||
} else if (event.which === 38 || event.which === 39) {
|
||||
} else if (keycode.isEventKey(event, 'Right') || keycode.isEventKey(event, 'Up')) {
|
||||
event.preventDefault();
|
||||
this.stepBack();
|
||||
} else {
|
||||
// NOTE: This is a special case where we don't pass unhandled
|
||||
// keypress events up to the Component handler, because this
|
||||
// is just adding a keypress handler on top of the MenuItem's
|
||||
// existing keypress handler, which already handles passing keypress
|
||||
// events up.
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,9 +6,9 @@ import * as Fn from './utils/fn';
|
||||
import Component from './component';
|
||||
import window from 'global/window';
|
||||
import document from 'global/document';
|
||||
import keycode from 'keycode';
|
||||
|
||||
const MODAL_CLASS_NAME = 'vjs-modal-dialog';
|
||||
const ESC = 27;
|
||||
|
||||
/**
|
||||
* The `ModalDialog` displays over the video and its controls, which blocks
|
||||
@ -119,13 +119,13 @@ class ModalDialog extends Component {
|
||||
* Handles `keydown` events on the document, looking for ESC, which closes
|
||||
* the modal.
|
||||
*
|
||||
* @param {EventTarget~Event} e
|
||||
* @param {EventTarget~Event} event
|
||||
* The keypress that triggered this event.
|
||||
*
|
||||
* @listens keydown
|
||||
*/
|
||||
handleKeyPress(e) {
|
||||
if (e.which === ESC && this.closeable()) {
|
||||
handleKeyPress(event) {
|
||||
if (keycode.isEventKey(event, 'Escape') && this.closeable()) {
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
@ -466,7 +466,7 @@ class ModalDialog extends Component {
|
||||
*/
|
||||
handleKeyDown(event) {
|
||||
// exit early if it isn't a tab key
|
||||
if (event.which !== 9) {
|
||||
if (!keycode.isEventKey(event, 'Tab')) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
110
src/js/player.js
110
src/js/player.js
@ -34,6 +34,7 @@ import * as middleware from './tech/middleware.js';
|
||||
import {ALL as TRACK_TYPES} from './tracks/track-types';
|
||||
import filterSource from './utils/filter-source';
|
||||
import {getMimetype, findMimetype} from './utils/mimetypes';
|
||||
import keycode from 'keycode';
|
||||
|
||||
// The following imports are used only to ensure that the corresponding modules
|
||||
// are always included in the video.js package. Importing the modules will
|
||||
@ -519,6 +520,8 @@ class Player extends Component {
|
||||
this.reportUserActivity();
|
||||
|
||||
this.one('play', this.listenForUserActivity_);
|
||||
this.on('focus', this.handleFocus);
|
||||
this.on('blur', this.handleBlur);
|
||||
this.on('stageclick', this.handleStageClick_);
|
||||
|
||||
this.breakpoints(this.options_.breakpoints);
|
||||
@ -2675,7 +2678,7 @@ class Player extends Component {
|
||||
* Event to check for key press
|
||||
*/
|
||||
fullWindowOnEscKey(event) {
|
||||
if (event.keyCode === 27) {
|
||||
if (keycode.isEventKey(event, 'Esc')) {
|
||||
if (this.isFullscreen() === true) {
|
||||
this.exitFullscreen();
|
||||
} else {
|
||||
@ -2708,6 +2711,111 @@ class Player extends Component {
|
||||
this.trigger('exitFullWindow');
|
||||
}
|
||||
|
||||
/**
|
||||
* This gets called when a `Player` gains focus via a `focus` event.
|
||||
* Turns on listening for `keydown` events. When they happen it
|
||||
* calls `this.handleKeyPress`.
|
||||
*
|
||||
* @param {EventTarget~Event} event
|
||||
* The `focus` event that caused this function to be called.
|
||||
*
|
||||
* @listens focus
|
||||
*/
|
||||
handleFocus(event) {
|
||||
// call off first to make sure we don't keep adding keydown handlers
|
||||
Events.off(document, 'keydown', Fn.bind(this, this.handleKeyPress));
|
||||
Events.on(document, 'keydown', Fn.bind(this, this.handleKeyPress));
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a `Player` loses focus. Turns off the listener for
|
||||
* `keydown` events. Which Stops `this.handleKeyPress` from getting called.
|
||||
*
|
||||
* @param {EventTarget~Event} event
|
||||
* The `blur` event that caused this function to be called.
|
||||
*
|
||||
* @listens blur
|
||||
*/
|
||||
handleBlur(event) {
|
||||
Events.off(document, 'keydown', Fn.bind(this, this.handleKeyPress));
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when this Player has focus and a key gets pressed down, or when
|
||||
* any Component of this player receives a key press that it doesn't handle.
|
||||
* This allows player-wide hotkeys (either as defined below, or optionally
|
||||
* by an external function).
|
||||
*
|
||||
* @param {EventTarget~Event} event
|
||||
* The `keydown` event that caused this function to be called.
|
||||
*
|
||||
* @listens keydown
|
||||
*/
|
||||
handleKeyPress(event) {
|
||||
|
||||
if (this.options_.userActions && this.options_.userActions.hotkeys && (this.options_.userActions.hotkeys !== false)) {
|
||||
|
||||
if (typeof this.options_.userActions.hotkeys === 'function') {
|
||||
|
||||
this.options_.userActions.hotkeys.call(this, event);
|
||||
|
||||
} else {
|
||||
|
||||
this.handleHotkeys(event);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when this Player receives a hotkey keydown event.
|
||||
* Supported player-wide hotkeys are:
|
||||
*
|
||||
* f - toggle fullscreen
|
||||
* m - toggle mute
|
||||
* k or Space - toggle play/pause
|
||||
*
|
||||
* @param {EventTarget~Event} event
|
||||
* The `keydown` event that caused this function to be called.
|
||||
*/
|
||||
handleHotkeys(event) {
|
||||
const hotkeys = this.options_.userActions ? this.options_.userActions.hotkeys : {};
|
||||
|
||||
// set fullscreenKey, muteKey, playPauseKey from `hotkeys`, use defaults if not set
|
||||
const {
|
||||
fullscreenKey = keydownEvent => keycode.isEventKey(keydownEvent, 'f'),
|
||||
muteKey = keydownEvent => keycode.isEventKey(keydownEvent, 'm'),
|
||||
playPauseKey = keydownEvent => (keycode.isEventKey(keydownEvent, 'k') || keycode.isEventKey(keydownEvent, 'Space'))
|
||||
} = hotkeys;
|
||||
|
||||
if (fullscreenKey.call(this, event)) {
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
const FSToggle = Component.getComponent('FullscreenToggle');
|
||||
|
||||
if (document[FullscreenApi.fullscreenEnabled] !== false) {
|
||||
FSToggle.prototype.handleClick.call(this);
|
||||
}
|
||||
|
||||
} else if (muteKey.call(this, event)) {
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
const MuteToggle = Component.getComponent('MuteToggle');
|
||||
|
||||
MuteToggle.prototype.handleClick.call(this);
|
||||
|
||||
} else if (playPauseKey.call(this, event)) {
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
const PlayToggle = Component.getComponent('PlayToggle');
|
||||
|
||||
PlayToggle.prototype.handleClick.call(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the player can play a given mimetype
|
||||
*
|
||||
|
@ -117,6 +117,9 @@ class PosterImage extends ClickableComponent {
|
||||
} else {
|
||||
this.player_.pause();
|
||||
}
|
||||
|
||||
// call handleFocus manually to get hotkeys working
|
||||
this.player_.handleFocus({});
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import Component from '../component.js';
|
||||
import * as Dom from '../utils/dom.js';
|
||||
import {assign} from '../utils/obj';
|
||||
import {IS_CHROME} from '../utils/browser.js';
|
||||
import keycode from 'keycode';
|
||||
|
||||
/**
|
||||
* The base functionality for a slider. Can be vertical or horizontal.
|
||||
@ -316,14 +317,18 @@ class Slider extends Component {
|
||||
*/
|
||||
handleKeyPress(event) {
|
||||
// Left and Down Arrows
|
||||
if (event.which === 37 || event.which === 40) {
|
||||
if (keycode.isEventKey(event, 'Left') || keycode.isEventKey(event, 'Down')) {
|
||||
event.preventDefault();
|
||||
this.stepBack();
|
||||
|
||||
// Up and Right Arrows
|
||||
} else if (event.which === 38 || event.which === 39) {
|
||||
} else if (keycode.isEventKey(event, 'Right') || keycode.isEventKey(event, 'Up')) {
|
||||
event.preventDefault();
|
||||
this.stepForward();
|
||||
} else {
|
||||
|
||||
// Pass keypress handling up for unsupported keys
|
||||
super.handleKeyPress(event);
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user