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 --- .../player/hotkeys/peertube-hotkeys-plugin.ts | 196 +++++++++++++++++++++ 1 file changed, 196 insertions(+) create mode 100644 client/src/assets/player/hotkeys/peertube-hotkeys-plugin.ts (limited to 'client/src/assets/player/hotkeys') 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 } -- cgit v1.2.3