From 0674d6893d1167d3af74f689bf82b1b794706186 Mon Sep 17 00:00:00 2001 From: Guus van Meerveld Date: Fri, 5 Apr 2024 17:35:19 +0200 Subject: [PATCH] watch: added keybinds --- package.json | 1 + src/app/watch/Player/index.tsx | 128 +++++++++++++++++++++++++++------ yarn.lock | 5 ++ 3 files changed, 113 insertions(+), 21 deletions(-) diff --git a/package.json b/package.json index 41b438a..5795fdd 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "next-pwa": "^5.6.0", "react": "^18", "react-dom": "^18", + "react-hotkeys-hook": "^4.5.0", "react-icons": "^5.0.1", "react-player": "^2.15.1", "reactjs-visibility": "^0.1.4", diff --git a/src/app/watch/Player/index.tsx b/src/app/watch/Player/index.tsx index 0bb2496..8ad0527 100644 --- a/src/app/watch/Player/index.tsx +++ b/src/app/watch/Player/index.tsx @@ -1,9 +1,9 @@ "use client"; import screenfull from "screenfull"; -import { useDebounce } from "use-debounce"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; +import { useHotkeys } from "react-hotkeys-hook"; import { FiMaximize as MaximizeIcon, FiMinimize as MinimizeIcon, @@ -37,19 +37,8 @@ export const Player: Component<{ streams: Stream[] }> = ({ streams }) => { const videoPlayerId = "video-player"; - const [progress, setProgress] = useState(0); - const [loaded, setLoaded] = useState(0); - const [duration, setDuration] = useState(0); - const [maximized, setMaximized] = useState(false); - const [volume, setVolume] = useState(40); - const [playbackRate, setPlaybackRate] = useState(1.0); - const [playing, setPlaying] = useState(false); - - const [userSetProgress, setUserSetProgress] = useState(0); - - useEffect(() => { - playerRef.current?.seekTo(userSetProgress); - }, [userSetProgress]); + // TODO: Make framerate based on video, not a set number + const framerate = 60; const volumeIcons = useMemo( () => [ @@ -60,15 +49,111 @@ export const Player: Component<{ streams: Stream[] }> = ({ streams }) => { [] ); - const playbackRateCategories = useMemo( - () => - [0.25, 0.5, 1, 1.25, 1.5, 2].map((speed) => ({ + const [playbackRateMenuItems, playbackRateCategories] = useMemo(() => { + const categories = [0.25, 0.5, 1, 1.25, 1.5, 2]; + + return [ + categories.map((speed) => ({ key: speed, label: speed.toString() })), - [] + categories + ]; + }, []); + + const [progress, setProgress] = useState(0); + const [loaded, setLoaded] = useState(0); + const [duration, setDuration] = useState(0); + const [maximized, setMaximized] = useState(false); + const [volume, setVolume] = useState(40); + const [muted, setMuted] = useState(false); + const [playbackRate, setPlaybackRate] = useState(1.0); + const [playing, setPlaying] = useState(false); + + const [userSetProgress, setUserSetProgress] = useState(0); + + const seekForward = useCallback( + (seconds: number) => { + if (duration >= seconds) { + const newProgress = progress + seconds / duration; + + setUserSetProgress(newProgress); + setProgress(newProgress); + } + }, + [progress, duration] ); + const increaseVolume = useCallback((amount: number) => { + setVolume((state) => { + const newVolume = state + amount; + + if (newVolume >= 0 && newVolume <= 100) return newVolume; + else return state; + }); + }, []); + + const increasePlaybackRate = useCallback( + (amount: number) => { + const indexOfCurrentRate = playbackRateCategories.indexOf(playbackRate); + + if (indexOfCurrentRate < 0) return; + + const newRateIndex = indexOfCurrentRate + amount; + + if (newRateIndex < 0 || newRateIndex > playbackRateCategories.length - 1) + return; + + setPlaybackRate(playbackRateCategories[newRateIndex]); + }, + [playbackRate, playbackRateCategories] + ); + + useHotkeys(["k", "space"], () => setPlaying((state) => !state), { + preventDefault: true + }); + + useHotkeys(["f"], () => setMaximized((state) => !state)); + + useHotkeys(["m"], () => setMuted((state) => !state)); + + useHotkeys(["arrowup"], () => increaseVolume(5), { preventDefault: true }); + useHotkeys(["arrowdown"], () => increaseVolume(-5), { preventDefault: true }); + + useHotkeys(["arrowright"], () => seekForward(5)); + useHotkeys(["arrowleft"], () => seekForward(-5)); + + useHotkeys(["shift+."], () => increasePlaybackRate(1)); + useHotkeys(["shift+,"], () => increasePlaybackRate(-1)); + + useHotkeys(["l"], () => seekForward(10)); + useHotkeys(["j"], () => seekForward(-10)); + + useHotkeys( + ["."], + () => { + if (!playing) seekForward(1 / framerate); + }, + [seekForward, playing, framerate] + ); + useHotkeys( + [","], + () => { + if (!playing) seekForward(-(1 / framerate)); + }, + [seekForward, playing, framerate] + ); + + // Mute if volume is 0 + useEffect(() => { + if (volume === 0) setMuted(true); + else setMuted(false); + }, [volume]); + + useEffect(() => { + playerRef.current?.seekTo(userSetProgress); + }, [userSetProgress]); + const updateMaximized = useCallback(() => { setMaximized(screenfull.isFullscreen); }, [setMaximized]); @@ -146,7 +231,7 @@ export const Player: Component<{ streams: Stream[] }> = ({ streams }) => {