Move to our own player hotkeys
This commit is contained in:
parent
fc3412fd4e
commit
d7b052ff4e
|
@ -131,7 +131,6 @@
|
||||||
"typescript": "~4.4.4",
|
"typescript": "~4.4.4",
|
||||||
"video.js": "^7",
|
"video.js": "^7",
|
||||||
"videojs-dock": "^3.0.0",
|
"videojs-dock": "^3.0.0",
|
||||||
"videojs-hotkeys": "^0.2.27",
|
|
||||||
"videostream": "~3.2.1",
|
"videostream": "~3.2.1",
|
||||||
"wdio-chromedriver-service": "^7.2.0",
|
"wdio-chromedriver-service": "^7.2.0",
|
||||||
"wdio-geckodriver-service": "^2.0.3",
|
"wdio-geckodriver-service": "^2.0.3",
|
||||||
|
|
|
@ -0,0 +1,196 @@
|
||||||
|
import videojs from 'video.js'
|
||||||
|
|
||||||
|
type KeyHandler = { accept: (event: KeyboardEvent) => boolean, cb: (e: KeyboardEvent) => void }
|
||||||
|
|
||||||
|
const Plugin = videojs.getPlugin('plugin')
|
||||||
|
|
||||||
|
class PeerTubeHotkeysPlugin extends Plugin {
|
||||||
|
private static readonly VOLUME_STEP = 0.1
|
||||||
|
private static readonly SEEK_STEP = 5
|
||||||
|
|
||||||
|
private readonly handleKeyFunction: (event: KeyboardEvent) => void
|
||||||
|
|
||||||
|
private readonly handlers: KeyHandler[]
|
||||||
|
|
||||||
|
constructor (player: videojs.Player, options: videojs.PlayerOptions) {
|
||||||
|
super(player, options)
|
||||||
|
|
||||||
|
this.handlers = this.buildHandlers()
|
||||||
|
|
||||||
|
this.handleKeyFunction = (event: KeyboardEvent) => this.onKeyDown(event)
|
||||||
|
document.addEventListener('keydown', this.handleKeyFunction)
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose () {
|
||||||
|
document.removeEventListener('keydown', this.handleKeyFunction)
|
||||||
|
}
|
||||||
|
|
||||||
|
private onKeyDown (event: KeyboardEvent) {
|
||||||
|
if (!this.isValidKeyTarget(event.target as HTMLElement)) return
|
||||||
|
|
||||||
|
for (const handler of this.handlers) {
|
||||||
|
if (handler.accept(event)) {
|
||||||
|
handler.cb(event)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private buildHandlers () {
|
||||||
|
const handlers: KeyHandler[] = [
|
||||||
|
// Play
|
||||||
|
{
|
||||||
|
accept: e => (e.key === ' ' || e.key === 'MediaPlayPause'),
|
||||||
|
cb: e => {
|
||||||
|
e.preventDefault()
|
||||||
|
e.stopPropagation()
|
||||||
|
|
||||||
|
if (this.player.paused()) this.player.play()
|
||||||
|
else this.player.pause()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Increase volume
|
||||||
|
{
|
||||||
|
accept: e => this.isNaked(e, 'ArrowUp'),
|
||||||
|
cb: e => {
|
||||||
|
e.preventDefault()
|
||||||
|
this.player.volume(this.player.volume() + PeerTubeHotkeysPlugin.VOLUME_STEP)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Decrease volume
|
||||||
|
{
|
||||||
|
accept: e => this.isNaked(e, 'ArrowDown'),
|
||||||
|
cb: e => {
|
||||||
|
e.preventDefault()
|
||||||
|
this.player.volume(this.player.volume() - PeerTubeHotkeysPlugin.VOLUME_STEP)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Rewind
|
||||||
|
{
|
||||||
|
accept: e => this.isNaked(e, 'ArrowLeft') || this.isNaked(e, 'MediaRewind'),
|
||||||
|
cb: e => {
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
const target = Math.max(0, this.player.currentTime() - PeerTubeHotkeysPlugin.SEEK_STEP)
|
||||||
|
this.player.currentTime(target)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Forward
|
||||||
|
{
|
||||||
|
accept: e => this.isNaked(e, 'ArrowRight') || this.isNaked(e, 'MediaForward'),
|
||||||
|
cb: e => {
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
const target = Math.min(this.player.duration(), this.player.currentTime() + PeerTubeHotkeysPlugin.SEEK_STEP)
|
||||||
|
this.player.currentTime(target)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Fullscreen
|
||||||
|
{
|
||||||
|
// f key or Ctrl + Enter
|
||||||
|
accept: e => this.isNaked(e, 'f') || (!e.altKey && e.ctrlKey && e.key === 'Enter'),
|
||||||
|
cb: e => {
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
if (this.player.isFullscreen()) this.player.exitFullscreen()
|
||||||
|
else this.player.requestFullscreen()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Mute
|
||||||
|
{
|
||||||
|
accept: e => this.isNaked(e, 'm'),
|
||||||
|
cb: e => {
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
this.player.muted(!this.player.muted())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Increase playback rate
|
||||||
|
{
|
||||||
|
accept: e => e.key === '>',
|
||||||
|
cb: () => {
|
||||||
|
const target = Math.min(this.player.playbackRate() + 0.1, 5)
|
||||||
|
|
||||||
|
this.player.playbackRate(parseFloat(target.toFixed(2)))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Decrease playback rate
|
||||||
|
{
|
||||||
|
accept: e => e.key === '<',
|
||||||
|
cb: () => {
|
||||||
|
const target = Math.max(this.player.playbackRate() - 0.1, 0.10)
|
||||||
|
|
||||||
|
this.player.playbackRate(parseFloat(target.toFixed(2)))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Previous frame
|
||||||
|
{
|
||||||
|
accept: e => e.key === ',',
|
||||||
|
cb: () => {
|
||||||
|
this.player.pause()
|
||||||
|
|
||||||
|
// Calculate movement distance (assuming 30 fps)
|
||||||
|
const dist = 1 / 30
|
||||||
|
this.player.currentTime(this.player.currentTime() - dist)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Next frame
|
||||||
|
{
|
||||||
|
accept: e => e.key === '.',
|
||||||
|
cb: () => {
|
||||||
|
this.player.pause()
|
||||||
|
|
||||||
|
// Calculate movement distance (assuming 30 fps)
|
||||||
|
const dist = 1 / 30
|
||||||
|
this.player.currentTime(this.player.currentTime() + dist)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
// 0-9 key handlers
|
||||||
|
for (let i = 0; i < 10; i++) {
|
||||||
|
handlers.push({
|
||||||
|
accept: e => e.key === i + '',
|
||||||
|
cb: e => {
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
this.player.currentTime(this.player.duration() * i * 0.1)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return handlers
|
||||||
|
}
|
||||||
|
|
||||||
|
private isValidKeyTarget (eventEl: HTMLElement) {
|
||||||
|
const playerEl = this.player.el()
|
||||||
|
const activeEl = document.activeElement
|
||||||
|
const currentElTagName = eventEl.tagName.toLowerCase()
|
||||||
|
|
||||||
|
return (
|
||||||
|
activeEl === playerEl ||
|
||||||
|
activeEl === playerEl.querySelector('.vjs-tech') ||
|
||||||
|
activeEl === playerEl.querySelector('.vjs-control-bar') ||
|
||||||
|
eventEl.id === 'content' ||
|
||||||
|
currentElTagName === 'body' ||
|
||||||
|
currentElTagName === 'video'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private isNaked (event: KeyboardEvent, key: string) {
|
||||||
|
return (!event.ctrlKey && !event.altKey && !event.metaKey && !event.shiftKey && event.key === key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
videojs.registerPlugin('peerTubeHotkeysPlugin', PeerTubeHotkeysPlugin)
|
||||||
|
export { PeerTubeHotkeysPlugin }
|
|
@ -1,4 +1,3 @@
|
||||||
import 'videojs-hotkeys/videojs.hotkeys'
|
|
||||||
import 'videojs-dock'
|
import 'videojs-dock'
|
||||||
import '@peertube/videojs-contextmenu'
|
import '@peertube/videojs-contextmenu'
|
||||||
import './upnext/end-card'
|
import './upnext/end-card'
|
||||||
|
@ -23,6 +22,7 @@ import './videojs-components/theater-button'
|
||||||
import './playlist/playlist-plugin'
|
import './playlist/playlist-plugin'
|
||||||
import './mobile/peertube-mobile-plugin'
|
import './mobile/peertube-mobile-plugin'
|
||||||
import './mobile/peertube-mobile-buttons'
|
import './mobile/peertube-mobile-buttons'
|
||||||
|
import './hotkeys/peertube-hotkeys-plugin'
|
||||||
import videojs from 'video.js'
|
import videojs from 'video.js'
|
||||||
import { HlsJsEngineSettings } from '@peertube/p2p-media-loader-hlsjs'
|
import { HlsJsEngineSettings } from '@peertube/p2p-media-loader-hlsjs'
|
||||||
import { PluginsManager } from '@root-helpers/plugins-manager'
|
import { PluginsManager } from '@root-helpers/plugins-manager'
|
||||||
|
@ -192,6 +192,7 @@ export class PeertubePlayerManager {
|
||||||
})
|
})
|
||||||
|
|
||||||
if (isMobile()) player.peertubeMobile()
|
if (isMobile()) player.peertubeMobile()
|
||||||
|
if (options.common.enableHotkeys === true) player.peerTubeHotkeysPlugin()
|
||||||
|
|
||||||
player.bezels()
|
player.bezels()
|
||||||
|
|
||||||
|
@ -286,10 +287,6 @@ export class PeertubePlayerManager {
|
||||||
plugins.playlist = commonOptions.playlist
|
plugins.playlist = commonOptions.playlist
|
||||||
}
|
}
|
||||||
|
|
||||||
if (commonOptions.enableHotkeys === true) {
|
|
||||||
PeertubePlayerManager.addHotkeysOptions(plugins)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isHLS) {
|
if (isHLS) {
|
||||||
const { hlsjs } = PeertubePlayerManager.addP2PMediaLoaderOptions(plugins, options, p2pMediaLoaderModule)
|
const { hlsjs } = PeertubePlayerManager.addP2PMediaLoaderOptions(plugins, options, p2pMediaLoaderModule)
|
||||||
|
|
||||||
|
@ -638,82 +635,6 @@ export class PeertubePlayerManager {
|
||||||
player.contextmenuUI({ content })
|
player.contextmenuUI({ content })
|
||||||
}
|
}
|
||||||
|
|
||||||
private static addHotkeysOptions (plugins: VideoJSPluginOptions) {
|
|
||||||
const isNaked = (event: KeyboardEvent, key: string) =>
|
|
||||||
(!event.ctrlKey && !event.altKey && !event.metaKey && !event.shiftKey && event.key === key)
|
|
||||||
|
|
||||||
Object.assign(plugins, {
|
|
||||||
hotkeys: {
|
|
||||||
skipInitialFocus: true,
|
|
||||||
enableInactiveFocus: false,
|
|
||||||
captureDocumentHotkeys: true,
|
|
||||||
documentHotkeysFocusElementFilter: (e: HTMLElement) => {
|
|
||||||
const tagName = e.tagName.toLowerCase()
|
|
||||||
return e.id === 'content' || tagName === 'body' || tagName === 'video'
|
|
||||||
},
|
|
||||||
|
|
||||||
enableVolumeScroll: false,
|
|
||||||
enableModifiersForNumbers: false,
|
|
||||||
|
|
||||||
rewindKey: function (event: KeyboardEvent) {
|
|
||||||
return isNaked(event, 'ArrowLeft')
|
|
||||||
},
|
|
||||||
|
|
||||||
forwardKey: function (event: KeyboardEvent) {
|
|
||||||
return isNaked(event, 'ArrowRight')
|
|
||||||
},
|
|
||||||
|
|
||||||
fullscreenKey: function (event: KeyboardEvent) {
|
|
||||||
// fullscreen with the f key or Ctrl+Enter
|
|
||||||
return isNaked(event, 'f') || (!event.altKey && event.ctrlKey && event.key === 'Enter')
|
|
||||||
},
|
|
||||||
|
|
||||||
customKeys: {
|
|
||||||
increasePlaybackRateKey: {
|
|
||||||
key: function (event: KeyboardEvent) {
|
|
||||||
return isNaked(event, '>')
|
|
||||||
},
|
|
||||||
handler: function (player: videojs.Player) {
|
|
||||||
const newValue = Math.min(player.playbackRate() + 0.1, 5)
|
|
||||||
player.playbackRate(parseFloat(newValue.toFixed(2)))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
decreasePlaybackRateKey: {
|
|
||||||
key: function (event: KeyboardEvent) {
|
|
||||||
return isNaked(event, '<')
|
|
||||||
},
|
|
||||||
handler: function (player: videojs.Player) {
|
|
||||||
const newValue = Math.max(player.playbackRate() - 0.1, 0.10)
|
|
||||||
player.playbackRate(parseFloat(newValue.toFixed(2)))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
previousFrame: {
|
|
||||||
key: function (event: KeyboardEvent) {
|
|
||||||
return event.key === ','
|
|
||||||
},
|
|
||||||
handler: function (player: videojs.Player) {
|
|
||||||
player.pause()
|
|
||||||
// Calculate movement distance (assuming 30 fps)
|
|
||||||
const dist = 1 / 30
|
|
||||||
player.currentTime(player.currentTime() - dist)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
nextFrame: {
|
|
||||||
key: function (event: KeyboardEvent) {
|
|
||||||
return event.key === '.'
|
|
||||||
},
|
|
||||||
handler: function (player: videojs.Player) {
|
|
||||||
player.pause()
|
|
||||||
// Calculate movement distance (assuming 30 fps)
|
|
||||||
const dist = 1 / 30
|
|
||||||
player.currentTime(player.currentTime() + dist)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
private static getAutoPlayValue (autoplay: any) {
|
private static getAutoPlayValue (autoplay: any) {
|
||||||
if (autoplay !== true) return autoplay
|
if (autoplay !== true) return autoplay
|
||||||
|
|
||||||
|
|
|
@ -41,8 +41,8 @@ declare module 'video.js' {
|
||||||
contextmenuUI (options: any): any
|
contextmenuUI (options: any): any
|
||||||
|
|
||||||
bezels (): void
|
bezels (): void
|
||||||
|
|
||||||
peertubeMobile (): void
|
peertubeMobile (): void
|
||||||
|
peerTubeHotkeysPlugin (): void
|
||||||
|
|
||||||
stats (options?: StatsCardOptions): StatsForNerdsPlugin
|
stats (options?: StatsCardOptions): StatsForNerdsPlugin
|
||||||
|
|
||||||
|
|
|
@ -11794,11 +11794,6 @@ videojs-font@3.2.0:
|
||||||
resolved "https://registry.yarnpkg.com/videojs-font/-/videojs-font-3.2.0.tgz#212c9d3f4e4ec3fa7345167d64316add35e92232"
|
resolved "https://registry.yarnpkg.com/videojs-font/-/videojs-font-3.2.0.tgz#212c9d3f4e4ec3fa7345167d64316add35e92232"
|
||||||
integrity sha512-g8vHMKK2/JGorSfqAZQUmYYNnXmfec4MLhwtEFS+mMs2IDY398GLysy6BH6K+aS1KMNu/xWZ8Sue/X/mdQPliA==
|
integrity sha512-g8vHMKK2/JGorSfqAZQUmYYNnXmfec4MLhwtEFS+mMs2IDY398GLysy6BH6K+aS1KMNu/xWZ8Sue/X/mdQPliA==
|
||||||
|
|
||||||
videojs-hotkeys@^0.2.27:
|
|
||||||
version "0.2.27"
|
|
||||||
resolved "https://registry.yarnpkg.com/videojs-hotkeys/-/videojs-hotkeys-0.2.27.tgz#0df97952b9dff0e6cc1cf8a439fed7eac9c73f01"
|
|
||||||
integrity sha512-pwtm1QocRmzJy1PWQsmFVHyeldYHHpLdeATK3FsFHVMmNpz6CROkAn8TFy2UILr8Ghgq134K8jEKNue8HWpudQ==
|
|
||||||
|
|
||||||
videojs-vtt.js@^0.15.3:
|
videojs-vtt.js@^0.15.3:
|
||||||
version "0.15.3"
|
version "0.15.3"
|
||||||
resolved "https://registry.yarnpkg.com/videojs-vtt.js/-/videojs-vtt.js-0.15.3.tgz#84260393b79487fcf195d9372f812d7fab83a993"
|
resolved "https://registry.yarnpkg.com/videojs-vtt.js/-/videojs-vtt.js-0.15.3.tgz#84260393b79487fcf195d9372f812d7fab83a993"
|
||||||
|
|
Loading…
Reference in New Issue