watch: added keybinds
continuous-integration/drone/push Build is passing Details

nextui
Guus van Meerveld 8 months ago
parent 8632980212
commit 0674d6893d

@ -21,6 +21,7 @@
"next-pwa": "^5.6.0", "next-pwa": "^5.6.0",
"react": "^18", "react": "^18",
"react-dom": "^18", "react-dom": "^18",
"react-hotkeys-hook": "^4.5.0",
"react-icons": "^5.0.1", "react-icons": "^5.0.1",
"react-player": "^2.15.1", "react-player": "^2.15.1",
"reactjs-visibility": "^0.1.4", "reactjs-visibility": "^0.1.4",

@ -1,9 +1,9 @@
"use client"; "use client";
import screenfull from "screenfull"; import screenfull from "screenfull";
import { useDebounce } from "use-debounce";
import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useHotkeys } from "react-hotkeys-hook";
import { import {
FiMaximize as MaximizeIcon, FiMaximize as MaximizeIcon,
FiMinimize as MinimizeIcon, FiMinimize as MinimizeIcon,
@ -37,19 +37,8 @@ export const Player: Component<{ streams: Stream[] }> = ({ streams }) => {
const videoPlayerId = "video-player"; const videoPlayerId = "video-player";
const [progress, setProgress] = useState(0); // TODO: Make framerate based on video, not a set number
const [loaded, setLoaded] = useState(0); const framerate = 60;
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]);
const volumeIcons = useMemo( const volumeIcons = useMemo(
() => [ () => [
@ -60,15 +49,111 @@ export const Player: Component<{ streams: Stream[] }> = ({ streams }) => {
[] []
); );
const playbackRateCategories = useMemo( const [playbackRateMenuItems, playbackRateCategories] = useMemo(() => {
() => const categories = [0.25, 0.5, 1, 1.25, 1.5, 2];
[0.25, 0.5, 1, 1.25, 1.5, 2].map((speed) => ({
return [
categories.map((speed) => ({
key: speed, key: speed,
label: speed.toString() 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(() => { const updateMaximized = useCallback(() => {
setMaximized(screenfull.isFullscreen); setMaximized(screenfull.isFullscreen);
}, [setMaximized]); }, [setMaximized]);
@ -146,7 +231,7 @@ export const Player: Component<{ streams: Stream[] }> = ({ streams }) => {
<Dropdown> <Dropdown>
<DropdownTrigger> <DropdownTrigger>
<Button variant="light" isIconOnly className="text-xl"> <Button variant="light" isIconOnly className="text-xl">
{volume === 0 ? ( {muted ? (
<MutedIcon /> <MutedIcon />
) : ( ) : (
volumeIcons[ volumeIcons[
@ -158,6 +243,7 @@ export const Player: Component<{ streams: Stream[] }> = ({ streams }) => {
<DropdownMenu aria-label="Volume menu"> <DropdownMenu aria-label="Volume menu">
<DropdownItem> <DropdownItem>
<Slider <Slider
aria-label="Volume slider"
className="h-48" className="h-48"
value={volume} value={volume}
onChange={(value) => { onChange={(value) => {
@ -190,7 +276,7 @@ export const Player: Component<{ streams: Stream[] }> = ({ streams }) => {
onAction={(key) => { onAction={(key) => {
setPlaybackRate(parseFloat(key as string)); setPlaybackRate(parseFloat(key as string));
}} }}
items={playbackRateCategories} items={playbackRateMenuItems}
> >
{(item) => ( {(item) => (
<DropdownItem key={item.key}> <DropdownItem key={item.key}>
@ -217,7 +303,7 @@ export const Player: Component<{ streams: Stream[] }> = ({ streams }) => {
<ReactPlayer <ReactPlayer
playing={playing} playing={playing}
volume={volume / 100} volume={volume / 100}
muted={volume === 0} muted={muted}
playbackRate={playbackRate} playbackRate={playbackRate}
ref={playerRef} ref={playerRef}
className="absolute top-0 left-0" className="absolute top-0 left-0"

@ -5914,6 +5914,11 @@ react-fast-compare@^3.0.1:
resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.2.tgz#929a97a532304ce9fee4bcae44234f1ce2c21d49" resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.2.tgz#929a97a532304ce9fee4bcae44234f1ce2c21d49"
integrity sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ== integrity sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==
react-hotkeys-hook@^4.5.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/react-hotkeys-hook/-/react-hotkeys-hook-4.5.0.tgz#807b389b15256daf6a813a1ec09e6698064fe97f"
integrity sha512-Samb85GSgAWFQNvVt3PS90LPPGSf9mkH/r4au81ZP1yOIFayLC3QAvqTgGtJ8YEDMXtPmaVBs6NgipHO6h4Mug==
react-icons@^5.0.1: react-icons@^5.0.1:
version "5.0.1" version "5.0.1"
resolved "https://registry.yarnpkg.com/react-icons/-/react-icons-5.0.1.tgz#1694e11bfa2a2888cab47dcc30154ce90485feee" resolved "https://registry.yarnpkg.com/react-icons/-/react-icons-5.0.1.tgz#1694e11bfa2a2888cab47dcc30154ce90485feee"

Loading…
Cancel
Save