diff --git a/src/shared/components/VideoPlayer/ControlsIndicator.tsx b/src/shared/components/VideoPlayer/ControlsIndicator.tsx index 02555dbc07..050071bcdb 100644 --- a/src/shared/components/VideoPlayer/ControlsIndicator.tsx +++ b/src/shared/components/VideoPlayer/ControlsIndicator.tsx @@ -20,7 +20,7 @@ import { ControlsIndicatorTransitions, ControlsIndicatorWrapper, } from './ControlsIndicator.style' -import { CustomVideojsEvents } from './videoJsPlayer' +import { CustomVideojsEvents } from './utils' import { Text } from '../Text' diff --git a/src/shared/components/VideoPlayer/VideoPlayer.tsx b/src/shared/components/VideoPlayer/VideoPlayer.tsx index 709f2c934b..22fef444e4 100644 --- a/src/shared/components/VideoPlayer/VideoPlayer.tsx +++ b/src/shared/components/VideoPlayer/VideoPlayer.tsx @@ -35,7 +35,8 @@ import { VolumeSlider, VolumeSliderContainer, } from './VideoPlayer.style' -import { CustomVideojsEvents, VOLUME_STEP, VideoJsConfig, useVideoJsPlayer } from './videoJsPlayer' +import { CustomVideojsEvents, VOLUME_STEP, hotkeysHandler } from './utils' +import { VideoJsConfig, useVideoJsPlayer } from './videoJsPlayer' export type VideoPlayerProps = { nextVideo?: VideoFieldsFragment | null @@ -75,6 +76,31 @@ const VideoPlayerComponent: React.ForwardRefRenderFunction(null) const [isLoaded, setIsLoaded] = useState(false) + // handle hotkeys + useEffect(() => { + if (!player || isInBackground) { + return + } + + const handler = (event: KeyboardEvent) => { + if ( + (document.activeElement?.tagName === 'BUTTON' && event.key === ' ') || + document.activeElement?.tagName === 'INPUT' + ) { + return + } + + const playerReservedKeys = ['k', ' ', 'ArrowLeft', 'ArrowRight', 'j', 'l', 'ArrowUp', 'ArrowDown', 'm', 'f'] + if (playerReservedKeys.includes(event.key)) { + event.preventDefault() + hotkeysHandler(event, player) + } + } + document.addEventListener('keydown', handler) + + return () => document.removeEventListener('keydown', handler) + }, [isInBackground, player]) + // handle error useEffect(() => { if (!player) { diff --git a/src/shared/components/VideoPlayer/utils.ts b/src/shared/components/VideoPlayer/utils.ts new file mode 100644 index 0000000000..4c0ee73b53 --- /dev/null +++ b/src/shared/components/VideoPlayer/utils.ts @@ -0,0 +1,89 @@ +import { VideoJsPlayer } from 'video.js' + +export const VOLUME_STEP = 0.1 + +export enum CustomVideojsEvents { + BackwardFiveSec = 'BACKWARD_FIVE_SEC', + BackwardTenSec = 'BACKWARD_TEN_SEC', + ForwardFiveSec = 'FORWARD_FIVE_SEC', + ForwardTenSec = 'FORWARD_TEN_SEC', + Muted = 'MUTED', + Unmuted = 'UNMUTED', + VolumeIncrease = 'VOLUME_INCREASE', + VolumeDecrease = 'VOLUME_DECREASE', + PlayControl = 'PLAY_CONTROL', + PauseControl = 'PAUSE_CONTROL', +} + +export const hotkeysHandler = (event: KeyboardEvent, playerInstance: VideoJsPlayer) => { + if (!playerInstance) { + return + } + const currentTime = playerInstance.currentTime() + const currentVolume = Number(playerInstance.volume().toFixed(2)) + const isMuted = playerInstance.muted() + const isFullscreen = playerInstance.isFullscreen() + const isPaused = playerInstance.paused() + + switch (event.code) { + case 'Space': + case 'KeyK': + if (isPaused) { + playerInstance.play() + playerInstance.trigger(CustomVideojsEvents.PlayControl) + } else { + playerInstance.pause() + playerInstance.trigger(CustomVideojsEvents.PauseControl) + } + return + case 'ArrowLeft': + playerInstance.currentTime(currentTime - 5) + playerInstance.trigger(CustomVideojsEvents.BackwardFiveSec) + return + case 'ArrowRight': + playerInstance.currentTime(currentTime + 5) + playerInstance.trigger(CustomVideojsEvents.ForwardFiveSec) + return + case 'KeyJ': + playerInstance.currentTime(currentTime - 10) + playerInstance.trigger(CustomVideojsEvents.BackwardTenSec) + return + case 'KeyL': + playerInstance.currentTime(currentTime + 10) + playerInstance.trigger(CustomVideojsEvents.ForwardTenSec) + return + case 'ArrowUp': + if (playerInstance.muted()) { + playerInstance.muted(false) + } + if (currentVolume <= 1) { + playerInstance.volume(Math.min(currentVolume + VOLUME_STEP, 1)) + } + playerInstance.trigger(CustomVideojsEvents.VolumeIncrease) + return + case 'ArrowDown': + if (currentVolume >= 0) { + playerInstance.volume(Math.max(currentVolume - VOLUME_STEP, 0)) + } + playerInstance.trigger(CustomVideojsEvents.VolumeDecrease) + return + case 'KeyM': + if (isMuted) { + playerInstance.trigger(CustomVideojsEvents.Unmuted) + playerInstance.muted(false) + } else { + playerInstance.trigger(CustomVideojsEvents.Muted) + playerInstance.muted(true) + } + return + case 'KeyF': + if (isFullscreen) { + playerInstance.exitFullscreen() + } else { + playerInstance.requestFullscreen() + } + return + default: + return + } +} diff --git a/src/shared/components/VideoPlayer/videoJsPlayer.ts b/src/shared/components/VideoPlayer/videoJsPlayer.ts index b47b022a04..840cea09cb 100644 --- a/src/shared/components/VideoPlayer/videoJsPlayer.ts +++ b/src/shared/components/VideoPlayer/videoJsPlayer.ts @@ -2,19 +2,6 @@ import { RefObject, useEffect, useRef, useState } from 'react' import videojs, { VideoJsPlayer, VideoJsPlayerOptions } from 'video.js' import 'video.js/dist/video-js.css' -export enum CustomVideojsEvents { - BackwardFiveSec = 'BACKWARD_FIVE_SEC', - BackwardTenSec = 'BACKWARD_TEN_SEC', - ForwardFiveSec = 'FORWARD_FIVE_SEC', - ForwardTenSec = 'FORWARD_TEN_SEC', - Muted = 'MUTED', - Unmuted = 'UNMUTED', - VolumeIncrease = 'VOLUME_INCREASE', - VolumeDecrease = 'VOLUME_DECREASE', - PlayControl = 'PLAY_CONTROL', - PauseControl = 'PAUSE_CONTROL', -} - export type VideoJsConfig = { src?: string | null width?: number @@ -31,78 +18,6 @@ export type VideoJsConfig = { onTimeUpdated?: (time: number) => void } -export const VOLUME_STEP = 0.1 - -const hotkeysHandler = (event: videojs.KeyboardEvent, playerInstance: VideoJsPlayer) => { - const currentTime = playerInstance.currentTime() - const currentVolume = Number(playerInstance.volume().toFixed(2)) - const isMuted = playerInstance.muted() - const isFullscreen = playerInstance.isFullscreen() - const isPaused = playerInstance.paused() - - switch (event.code) { - case 'Space': - case 'KeyK': - if (isPaused) { - playerInstance.play() - playerInstance.trigger(CustomVideojsEvents.PlayControl) - } else { - playerInstance.pause() - playerInstance.trigger(CustomVideojsEvents.PauseControl) - } - return - case 'ArrowLeft': - playerInstance.currentTime(currentTime - 5) - playerInstance.trigger(CustomVideojsEvents.BackwardFiveSec) - return - case 'ArrowRight': - playerInstance.currentTime(currentTime + 5) - playerInstance.trigger(CustomVideojsEvents.ForwardFiveSec) - return - case 'KeyJ': - playerInstance.currentTime(currentTime - 10) - playerInstance.trigger(CustomVideojsEvents.BackwardTenSec) - return - case 'KeyL': - playerInstance.currentTime(currentTime + 10) - playerInstance.trigger(CustomVideojsEvents.ForwardTenSec) - return - case 'ArrowUp': - if (playerInstance.muted()) { - playerInstance.muted(false) - } - if (currentVolume <= 1) { - playerInstance.volume(Math.min(currentVolume + VOLUME_STEP, 1)) - } - playerInstance.trigger(CustomVideojsEvents.VolumeIncrease) - return - case 'ArrowDown': - if (currentVolume >= 0) { - playerInstance.volume(Math.max(currentVolume - VOLUME_STEP, 0)) - } - playerInstance.trigger(CustomVideojsEvents.VolumeDecrease) - return - case 'KeyM': - if (isMuted) { - playerInstance.trigger(CustomVideojsEvents.Unmuted) - playerInstance.muted(false) - } else { - playerInstance.trigger(CustomVideojsEvents.Muted) - playerInstance.muted(true) - } - return - case 'KeyF': - if (isFullscreen) { - playerInstance.exitFullscreen() - } else { - playerInstance.requestFullscreen() - } - return - default: - return - } -} - type VideoJsPlayerHook = (config: VideoJsConfig) => [VideoJsPlayer | null, RefObject] export const useVideoJsPlayer: VideoJsPlayerHook = ({ fill, @@ -132,9 +47,6 @@ export const useVideoJsPlayer: VideoJsPlayerHook = ({ playsinline: true, loadingSpinner: false, bigPlayButton: false, - userActions: { - hotkeys: (event) => hotkeysHandler(event, playerInstance), - }, controlBar: { // hide all videojs controls besides progress bar children: [], @@ -147,7 +59,6 @@ export const useVideoJsPlayer: VideoJsPlayerHook = ({ const playerInstance = videojs(playerRef.current as Element, videoJsOptions) setPlayer(playerInstance) - playerRef.current.focus() return () => { playerInstance.dispose() diff --git a/src/views/viewer/VideoView/VideoView.tsx b/src/views/viewer/VideoView/VideoView.tsx index 8ed7fc576f..a3025662b3 100644 --- a/src/views/viewer/VideoView/VideoView.tsx +++ b/src/views/viewer/VideoView/VideoView.tsx @@ -1,10 +1,9 @@ import { throttle } from 'lodash' import React, { useCallback, useEffect, useState } from 'react' -import { useMatch, useParams } from 'react-router-dom' +import { useParams } from 'react-router-dom' import { useAddVideoView, useVideo } from '@/api/hooks' import { ChannelLink, InfiniteVideoGrid } from '@/components' -import { absoluteRoutes } from '@/config/routes' import knownLicenses from '@/data/knownLicenses.json' import { useRouterQuery } from '@/hooks' import { AssetType, useAsset, usePersonalDataStore } from '@/providers' @@ -44,7 +43,6 @@ export const VideoView: React.FC = () => { }) const { url: mediaUrl } = useAsset({ entity: video, assetType: AssetType.MEDIA }) - const videoRouteMatch = useMatch({ path: absoluteRoutes.viewer.video(id) }) const [startTimestamp, setStartTimestamp] = useState() useEffect(() => { if (startTimestamp != null) { @@ -66,23 +64,6 @@ export const VideoView: React.FC = () => { const channelId = video?.channel.id const videoId = video?.id - const handleUserKeyPress = useCallback( - (event: Event) => { - if (videoRouteMatch) { - event.preventDefault() - } - }, - [videoRouteMatch] - ) - - useEffect(() => { - window.addEventListener('keydown', handleUserKeyPress) - - return () => { - window.removeEventListener('keydown', handleUserKeyPress) - } - }, [handleUserKeyPress]) - useEffect(() => { if (!videoId || !channelId) { return