mirror of
				https://github.com/videojs/video.js.git
				synced 2025-10-31 00:08:01 +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:
		
				
					committed by
					
						 Gary Katsevman
						Gary Katsevman
					
				
			
			
				
	
			
			
			
						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); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user