#63302 Плагин управления клавиатурой

This commit is contained in:
Ксения Петрова
2025-03-06 15:46:48 +00:00
committed by Юрий Шикин
parent f23eb300d6
commit 7332fa2928
11 changed files with 214 additions and 68 deletions
+1
View File
@@ -2,3 +2,4 @@
.idea
distribution
node_modules
tsup.config.bundled_*
+10
View File
@@ -0,0 +1,10 @@
{
"semi": false,
"trailingComma": "none",
"singleQuote": true,
"printWidth": 80,
"arrowParens": "always",
"max-len": ["error", 140, 2],
"tabWidth": 2,
"useTabs": false
}
+2 -1
View File
@@ -3,6 +3,7 @@
"VokaAdvertisementPlugin": false,
"VokaStatisticsPlugin": false,
"VokaQualityPlugin": false,
"VokaCaptionsPlugin": false
"VokaCaptionsPlugin": false,
"hotkeys": true
}
}
+16
View File
@@ -18,6 +18,7 @@
"keycode": "^2.2.1",
"postcss": "^8.5.1",
"postcss-preset-env": "^10.1.3",
"prettier": "^3.5.3",
"ts-bus": "^2.3.1",
"ts-node": "^10.9.2",
"tsup": "^8.3.6",
@@ -4537,6 +4538,21 @@
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
"license": "MIT"
},
"node_modules/prettier": {
"version": "3.5.3",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz",
"integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==",
"license": "MIT",
"bin": {
"prettier": "bin/prettier.cjs"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/process": {
"version": "0.11.10",
"resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
+1
View File
@@ -25,6 +25,7 @@
"keycode": "^2.2.1",
"postcss": "^8.5.1",
"postcss-preset-env": "^10.1.3",
"prettier": "^3.5.3",
"ts-bus": "^2.3.1",
"ts-node": "^10.9.2",
"tsup": "^8.3.6",
+1 -1
View File
@@ -57,7 +57,7 @@ class BigPlayButton extends Button {
handleKeyDown(event: Event) {
this.mouseused_ = false
super.handleKeyDown(event)
}
+3 -3
View File
@@ -1,10 +1,10 @@
import videojs from 'video.js'
const Component = videojs.getComponent('component')
import ClickableComponent from './ClickableComponent'
const log = videojs.log
const Dom = videojs.dom
import ClickableComponent from './ClickableComponent'
import { assign } from '../utilities/obj'
import keycode from 'keycode'
const Dom = videojs.dom
/**
* Base class for all buttons.
@@ -115,7 +115,7 @@ class Button extends ClickableComponent {
super.disable()
this.el().setAttribute('disabled', 'disabled')
}
/**
* This gets called when a `Button` has focus and `keydown` is triggered via a key
* press.
+15 -16
View File
@@ -3,6 +3,7 @@ import ClickableComponent from './ClickableComponent'
import ClickableComponentOptions = videojs.ClickableComponentOptions
import { VideoJsPlayer } from '@types/video.js'
import VokaEvent from "@/constants/VokaEvent"
import './BigPlayButton'
/**
@@ -23,17 +24,6 @@ class CentralPanel extends ClickableComponent {
// this.bigPlayButton = this.getChild('BigPlayButton') as videojs.Component
this.isClickDisabled = false
if (this.bigPlayButton) {
this.bigPlayButton.el().addEventListener('animationend', () => {
;(this.bigPlayButton as videojs.Component).removeClass(
'animation-press-play'
)
;(this.bigPlayButton as videojs.Component).removeClass(
'animation-press-pause'
)
})
}
}
enableClick() {
@@ -52,8 +42,8 @@ class CentralPanel extends ClickableComponent {
*/
createEl() {
return videojs.dom.createEl('div', {
className: 'voka-central-panel'
});
className: 'voka-central-panel',
})
}
handleClick(event: Event) {
@@ -62,12 +52,21 @@ class CentralPanel extends ClickableComponent {
if (this.isClickDisabled)
return
if (this.player_.paused()) {
this.player_.play()
if (this.player().paused()) {
if (this.player().ended()) {
this.player().trigger(VokaEvent.Replay)
}
this.player().play()
} else {
this.player_.pause()
this.player().pause()
}
}
handleKeyDown(event: Event) {
// Pass keypress handling up for unsupported keys
super.handleKeyDown(event)
}
}
videojs.registerComponent('CentralPanel', CentralPanel)
+95 -47
View File
@@ -1,11 +1,11 @@
import videojs from 'video.js'
const Component = videojs.getComponent('Component')
// import ClickableComponentOptions = videojs.ClickableComponentOptions
import { VideoJsPlayer, VideoJsPlayerOptions } from '@types/video.js'
import keycode from 'keycode'
const Component = videojs.getComponent('component')
const Dom = videojs.dom
const browser = videojs.browser
const log = videojs.log
import { assign } from '../utilities/obj'
import keycode from 'keycode'
export const enum TooltipPosition {
top = 'top',
@@ -38,16 +38,32 @@ class ClickableComponent extends Component {
* The `Player` that this class should be attached to.
*
* @param {Object} [options]
* The key/value store of player options.
* The key/value store of component options.
*
* @param {function} [options.clickHandler]
* The function to call when the button is clicked / activated
*
* @param {string} [options.controlText]
* The text to set on the button
*
* @param {string} [options.className]
* A class or space separated list of classes to add the component
*
*/
constructor(player: any, options: any) {
constructor(player: VideoJsPlayer, options: VideoJsPlayerOptions) {
super(player, options)
this.options_ = options
if (this.options_.controlText) {
this.controlText(this.options_.controlText)
}
this.handleMouseOver_ = (e) => this.handleMouseOver(e)
this.handleMouseOut_ = (e) => this.handleMouseOut(e)
this.handleClick_ = (e) => this.handleClick(e)
this.handleKeyDown_ = (e) => this.handleKeyDown(e)
this.emitTapEvents()
this.enable()
}
@@ -66,8 +82,8 @@ class ClickableComponent extends Component {
* @return {Element}
* The element that gets created.
*/
createEl(tag: string, props?: any, attributes?: any) {
props = assign(
createEl(tag: string = 'div', props?: any = {}, attributes?: any = {}) {
props = Object.assign(
{
className: this.buildCSSClass(),
tabIndex: -1
@@ -77,7 +93,7 @@ class ClickableComponent extends Component {
if (tag === 'button') {
log.error(
`Creating a ClickableComponent with an HTML element of ${tag} is not supported use a Button instead.`
`Creating a ClickableComponent with an HTML element of ${tag} is not supported; use a Button instead.`
)
}
@@ -91,38 +107,76 @@ class ClickableComponent extends Component {
this.tabIndex_ = props.tabIndex
const el = Dom.createEl(tag, props, attributes, '')
const el = Dom.createEl(tag, props, attributes)
el.appendChild(
Dom.createEl(
'span',
{
className: 'voka-icon-placeholder'
},
{
'aria-hidden': true
},
''
if (!this.player_.options_.experimentalSvgIcons) {
el.appendChild(
Dom.createEl(
'span',
{
className: 'voka-icon-placeholder'
},
{
'aria-hidden': true
}
)
)
)
}
this.controlText(this.controlText_, el)
this.createControlTextEl(el)
return el
}
dispose() {
// remove controlTextEl_ on dispose
// this.controlTextEl_ = null
this.controlTextEl_ = null
super.dispose()
}
/**
* Create a control text element on this `ClickableComponent`
*
* @param {Element} [el]
* Parent element for the control text.
*
* @return {Element}
* The control text element that gets created.
*/
createControlTextEl(el) {
this.controlTextEl_ = Dom.createEl(
'span',
{
className: 'voka-control-text'
},
{
// let the screen reader user know that the text of the element may change
'aria-live': 'polite'
}
)
if (el) {
el.appendChild(this.controlTextEl_)
}
this.controlText(this.controlText_, el)
return this.controlTextEl_
}
/**
* Get or set the localize text to use for the controls on the `ClickableComponent`.
*
* @param {string} [text]
* Control text for element.
*
* @param {Element} [el=this.el()]
* Element to set the title on.
*
* @return {string}
* - The control text when getting
*/
// @ts-ignore
controlText(
text?: string,
el: Element = this.el(),
@@ -164,12 +218,12 @@ class ClickableComponent extends Component {
if (!this.enabled_) {
this.enabled_ = true
this.removeClass('vjs-disabled')
this.el().setAttribute('aria-disabled', 'false')
this.el_.setAttribute('aria-disabled', 'false')
if (typeof this.tabIndex_ !== 'undefined') {
this.el().setAttribute('tabIndex', this.tabIndex_)
this.el_.setAttribute('tabIndex', this.tabIndex_)
}
this.on(['tap', 'click'], this.handleClick)
this.on('keydown', this.handleKeyDown)
this.on(['tap', 'click'], this.handleClick_)
this.on('keydown', this.handleKeyDown_)
}
}
@@ -179,19 +233,16 @@ class ClickableComponent extends Component {
disable() {
this.enabled_ = false
this.addClass('vjs-disabled')
this.el().setAttribute('aria-disabled', 'true')
this.el_.setAttribute('aria-disabled', 'true')
if (typeof this.tabIndex_ !== 'undefined') {
this.el().removeAttribute('tabIndex')
this.el_.removeAttribute('tabIndex')
}
this.off('mouseover', this.handleMouseOver)
this.off('mouseout', this.handleMouseOut)
this.off(['tap', 'click'], this.handleClick)
this.off('keydown', this.handleKeyDown)
this.off('mouseover', this.handleMouseOver_)
this.off('mouseout', this.handleMouseOut_)
this.off(['tap', 'click'], this.handleClick_)
this.off('keydown', this.handleKeyDown_)
}
handleMouseOver(e: Event) {}
handleMouseOut(e: Event) {}
/**
* Handles language change in ClickableComponent for the player in components
*
@@ -205,7 +256,7 @@ class ClickableComponent extends Component {
* Event handler that is called when a `ClickableComponent` receives a
* `click` or `tap` event.
*
* @param {EventTarget~Event} event
* @param {Event} event
* The `tap` or `click` event that caused this function to be called.
*
* @listens tap
@@ -213,8 +264,9 @@ class ClickableComponent extends Component {
* @abstract
*/
handleClick(event: Event) {
event.stopPropagation()
if (this.options_.clickHandler) {
this.options_.clickHandler()
this.options_.clickHandler.call(this, arguments)
}
}
@@ -224,25 +276,21 @@ class ClickableComponent extends Component {
*
* By default, if the key is Space or Enter, it will trigger a `click` event.
*
* @param {EventTarget~Event} event
* @param {KeyboardEvent} event
* The `keydown` event that caused this function to be called.
*
* @listens keydown
*/
handleKeyDown(event: Event) {
handleKeyDown(event: KeyboardEvent) {
// Support Space or Enter key operation to fire a click event. Also,
// prevent the event from propagating through the DOM and triggering
// Player hotkeys.
if (
keycode.isEventKey(event, 'Space') ||
keycode.isEventKey(event, 'Enter')
) {
if (['space', 'enter'].includes(keycode(event))) {
event.preventDefault()
event.stopPropagation()
this.trigger('click')
} else {
// Pass keypress handling up for unsupported keys
// @ts-ignore @todo убрать ignore после PR https://github.com/DefinitelyTyped/DefinitelyTyped/pull/60138
super.handleKeyDown(event)
}
}
+2
View File
@@ -4,6 +4,7 @@ import { EventBus } from 'ts-bus'
import { IVokaPlayer } from '@/public/IVokaPlayer'
import { AutoplayChecker } from '@/internal/utils/AutoplayChecker'
import * as languages from '@/languages.json'
import '../../plugins/hotkeys'
import '../../components/Skin'
@@ -85,6 +86,7 @@ export class VokaPlayerImpl implements IVokaPlayer {
//plugins.vokaStatisticsPlugin = {}
//plugins.vokaAdvertisementPlugin = {}
//plugins.vokaCaptionsPlugin = {}
plugins.hotkeys = {}
const childrenComponents = [
// Non-visual component, not in UI layer list.
+68
View File
@@ -0,0 +1,68 @@
import videojs from "video.js";
import { VideoJsPlayer } from "@types/video.js"
import keycode from 'keycode'
import VokaEvent from "@/constants/VokaEvent"
const Plugin = videojs.getPlugin('plugin');
export class Hotkeys extends Plugin {
player!: VideoJsPlayer
volumeStep: number
constructor(player: VideoJsPlayer) {
super(player)
this.volumeStep = 0.1
this.player.el().addEventListener('keydown', (e) => this.handleKeyDown(e));
}
handleKeyDown(event: Event) {
event.preventDefault()
event.stopPropagation()
switch (keycode(event)) {
case 'up':
this.changeVolume(this.volumeStep)
break
case 'down':
this.changeVolume(-this.volumeStep)
break
case 'm':
this.mute()
break
case 'space':
case 'enter':
this.togglePlay()
break
default:
break
}
}
togglePlay() {
if (this.player.paused()) {
if (this.player.ended()) {
this.player.trigger(VokaEvent.Replay)
}
this.player.play()
} else {
this.player.pause()
}
}
changeVolume(diff: number) {
const value = this.player.volume() + diff
this.player.volume(value)
}
mute() {
this.player.muted(!this.player.muted())
}
}
videojs.registerPlugin('hotkeys', Hotkeys);