]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/commitdiff
Move to our own player hotkeys
authorChocobozzz <me@florianbigard.com>
Thu, 13 Jan 2022 10:14:28 +0000 (11:14 +0100)
committerChocobozzz <me@florianbigard.com>
Thu, 13 Jan 2022 10:16:35 +0000 (11:16 +0100)
client/package.json
client/src/assets/player/hotkeys/peertube-hotkeys-plugin.ts [new file with mode: 0644]
client/src/assets/player/peertube-player-manager.ts
client/src/assets/player/peertube-videojs-typings.ts
client/yarn.lock

index efefb105414ba802a2d2bfcfeaf2c8adc1b3e138..1f58f9cd06c856f2f8abb5f0814af502ff30a03b 100644 (file)
     "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 (file)
index 0000000..5920450
--- /dev/null
@@ -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 }
index d715adf569097c921bcffb1687371f88e1d39ba1..b9a289aa0fe13e48943e50c776e037f2645980fe 100644 (file)
@@ -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
 
index e4013d8151e292ceb6a2cd3e4b0e43055a9cdbaa..b20ef7a3b70a88f3bf7f159e5f3ee1bfcff21ed2 100644 (file)
@@ -41,8 +41,8 @@ declare module 'video.js' {
     contextmenuUI (options: any): any
 
     bezels (): void
-
     peertubeMobile (): void
+    peerTubeHotkeysPlugin (): void
 
     stats (options?: StatsCardOptions): StatsForNerdsPlugin
 
index 654f7f11f6d2f746474a74be55e7389e376a5d23..750d3b3284ff245aa14fcebf08f7064570b05579 100644 (file)
@@ -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"