From d7b052ff4e5604043cd4405ed400f314d0e9a412 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Thu, 13 Jan 2022 11:14:28 +0100 Subject: Move to our own player hotkeys --- client/package.json | 1 - .../player/hotkeys/peertube-hotkeys-plugin.ts | 196 +++++++++++++++++++++ .../src/assets/player/peertube-player-manager.ts | 83 +-------- .../src/assets/player/peertube-videojs-typings.ts | 2 +- client/yarn.lock | 5 - 5 files changed, 199 insertions(+), 88 deletions(-) create mode 100644 client/src/assets/player/hotkeys/peertube-hotkeys-plugin.ts (limited to 'client') diff --git a/client/package.json b/client/package.json index efefb1054..1f58f9cd0 100644 --- a/client/package.json +++ b/client/package.json @@ -131,7 +131,6 @@ "typescript": "~4.4.4", "video.js": "^7", "videojs-dock": "^3.0.0", - "videojs-hotkeys": "^0.2.27", "videostream": "~3.2.1", "wdio-chromedriver-service": "^7.2.0", "wdio-geckodriver-service": "^2.0.3", diff --git a/client/src/assets/player/hotkeys/peertube-hotkeys-plugin.ts b/client/src/assets/player/hotkeys/peertube-hotkeys-plugin.ts new file mode 100644 index 000000000..5920450bd --- /dev/null +++ b/client/src/assets/player/hotkeys/peertube-hotkeys-plugin.ts @@ -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 } diff --git a/client/src/assets/player/peertube-player-manager.ts b/client/src/assets/player/peertube-player-manager.ts index d715adf56..b9a289aa0 100644 --- a/client/src/assets/player/peertube-player-manager.ts +++ b/client/src/assets/player/peertube-player-manager.ts @@ -1,4 +1,3 @@ -import 'videojs-hotkeys/videojs.hotkeys' import 'videojs-dock' import '@peertube/videojs-contextmenu' import './upnext/end-card' @@ -23,6 +22,7 @@ import './videojs-components/theater-button' import './playlist/playlist-plugin' import './mobile/peertube-mobile-plugin' import './mobile/peertube-mobile-buttons' +import './hotkeys/peertube-hotkeys-plugin' import videojs from 'video.js' import { HlsJsEngineSettings } from '@peertube/p2p-media-loader-hlsjs' import { PluginsManager } from '@root-helpers/plugins-manager' @@ -192,6 +192,7 @@ export class PeertubePlayerManager { }) if (isMobile()) player.peertubeMobile() + if (options.common.enableHotkeys === true) player.peerTubeHotkeysPlugin() player.bezels() @@ -286,10 +287,6 @@ export class PeertubePlayerManager { plugins.playlist = commonOptions.playlist } - if (commonOptions.enableHotkeys === true) { - PeertubePlayerManager.addHotkeysOptions(plugins) - } - if (isHLS) { const { hlsjs } = PeertubePlayerManager.addP2PMediaLoaderOptions(plugins, options, p2pMediaLoaderModule) @@ -638,82 +635,6 @@ export class PeertubePlayerManager { 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) { if (autoplay !== true) return autoplay diff --git a/client/src/assets/player/peertube-videojs-typings.ts b/client/src/assets/player/peertube-videojs-typings.ts index e4013d815..b20ef7a3b 100644 --- a/client/src/assets/player/peertube-videojs-typings.ts +++ b/client/src/assets/player/peertube-videojs-typings.ts @@ -41,8 +41,8 @@ declare module 'video.js' { contextmenuUI (options: any): any bezels (): void - peertubeMobile (): void + peerTubeHotkeysPlugin (): void stats (options?: StatsCardOptions): StatsForNerdsPlugin diff --git a/client/yarn.lock b/client/yarn.lock index 654f7f11f..750d3b328 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -11794,11 +11794,6 @@ videojs-font@3.2.0: resolved "https://registry.yarnpkg.com/videojs-font/-/videojs-font-3.2.0.tgz#212c9d3f4e4ec3fa7345167d64316add35e92232" 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: version "0.15.3" resolved "https://registry.yarnpkg.com/videojs-vtt.js/-/videojs-vtt.js-0.15.3.tgz#84260393b79487fcf195d9372f812d7fab83a993" -- cgit v1.2.3