mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-06-23 15:30:34 -04:00
192 lines
4.8 KiB
TypeScript
192 lines
4.8 KiB
TypeScript
/*
|
|
* Kyoo - A portable and vast media library solution.
|
|
* Copyright (c) Kyoo.
|
|
*
|
|
* See AUTHORS.md and LICENSE file in the project root for full license information.
|
|
*
|
|
* Kyoo is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* any later version.
|
|
*
|
|
* Kyoo is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
import type { Subtitle } from "@kyoo/models";
|
|
import { atom, useSetAtom } from "jotai";
|
|
import { useEffect } from "react";
|
|
import { Platform } from "react-native";
|
|
import { useRouter } from "solito/router";
|
|
import {
|
|
durationAtom,
|
|
fullscreenAtom,
|
|
mutedAtom,
|
|
playAtom,
|
|
progressAtom,
|
|
subtitleAtom,
|
|
volumeAtom,
|
|
} from "./state";
|
|
|
|
type Action =
|
|
| { type: "play" }
|
|
| { type: "mute" }
|
|
| { type: "fullscreen" }
|
|
| { type: "seek"; value: number }
|
|
| { type: "seekTo"; value: number }
|
|
| { type: "seekPercent"; value: number }
|
|
| { type: "volume"; value: number }
|
|
| { type: "subtitle"; subtitles: Subtitle[]; fonts: string[] };
|
|
|
|
export const reducerAtom = atom(null, (get, set, action: Action) => {
|
|
const duration = get(durationAtom);
|
|
switch (action.type) {
|
|
case "play":
|
|
set(playAtom, !get(playAtom));
|
|
break;
|
|
case "mute":
|
|
set(mutedAtom, !get(mutedAtom));
|
|
break;
|
|
case "fullscreen":
|
|
set(fullscreenAtom, !get(fullscreenAtom));
|
|
break;
|
|
case "seek":
|
|
if (duration)
|
|
set(progressAtom, Math.max(0, Math.min(get(progressAtom) + action.value, duration)));
|
|
break;
|
|
case "seekTo":
|
|
set(progressAtom, action.value);
|
|
break;
|
|
case "seekPercent":
|
|
if (duration) set(progressAtom, (duration * action.value) / 100);
|
|
break;
|
|
case "volume":
|
|
set(volumeAtom, Math.max(0, Math.min(get(volumeAtom) + action.value, 100)));
|
|
break;
|
|
case "subtitle": {
|
|
const subtitle = get(subtitleAtom);
|
|
const index = subtitle ? action.subtitles.findIndex((x) => x.index === subtitle.index) : -1;
|
|
set(
|
|
subtitleAtom,
|
|
index === -1 ? null : action.subtitles[(index + 1) % action.subtitles.length],
|
|
);
|
|
break;
|
|
}
|
|
}
|
|
});
|
|
|
|
export const useVideoKeyboard = (
|
|
subtitles?: Subtitle[],
|
|
fonts?: string[],
|
|
previousEpisode?: string,
|
|
nextEpisode?: string,
|
|
) => {
|
|
const reducer = useSetAtom(reducerAtom);
|
|
const router = useRouter();
|
|
|
|
useEffect(() => {
|
|
if (Platform.OS !== "web") return;
|
|
const handler = (event: KeyboardEvent) => {
|
|
if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) return;
|
|
|
|
switch (event.key) {
|
|
case " ":
|
|
case "k":
|
|
case "MediaPlay":
|
|
case "MediaPause":
|
|
case "MediaPlayPause":
|
|
reducer({ type: "play" });
|
|
break;
|
|
|
|
case "m":
|
|
reducer({ type: "mute" });
|
|
break;
|
|
|
|
case "ArrowLeft":
|
|
reducer({ type: "seek", value: -5 });
|
|
break;
|
|
case "ArrowRight":
|
|
reducer({ type: "seek", value: +5 });
|
|
break;
|
|
|
|
case "j":
|
|
reducer({ type: "seek", value: -10 });
|
|
break;
|
|
case "l":
|
|
reducer({ type: "seek", value: +10 });
|
|
break;
|
|
|
|
case "ArrowUp":
|
|
reducer({ type: "volume", value: +5 });
|
|
break;
|
|
case "ArrowDown":
|
|
reducer({ type: "volume", value: -5 });
|
|
break;
|
|
|
|
case "f":
|
|
reducer({ type: "fullscreen" });
|
|
break;
|
|
|
|
case "v":
|
|
case "c":
|
|
if (!subtitles || !fonts) return;
|
|
reducer({ type: "subtitle", subtitles, fonts });
|
|
break;
|
|
|
|
case "n":
|
|
case "N":
|
|
if (nextEpisode) router.push(nextEpisode);
|
|
break;
|
|
|
|
case "p":
|
|
case "P":
|
|
if (previousEpisode) router.push(previousEpisode);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
switch (event.code) {
|
|
case "Digit0":
|
|
reducer({ type: "seekPercent", value: 0 });
|
|
break;
|
|
case "Digit1":
|
|
reducer({ type: "seekPercent", value: 10 });
|
|
break;
|
|
case "Digit2":
|
|
reducer({ type: "seekPercent", value: 20 });
|
|
break;
|
|
case "Digit3":
|
|
reducer({ type: "seekPercent", value: 30 });
|
|
break;
|
|
case "Digit4":
|
|
reducer({ type: "seekPercent", value: 40 });
|
|
break;
|
|
case "Digit5":
|
|
reducer({ type: "seekPercent", value: 50 });
|
|
break;
|
|
case "Digit6":
|
|
reducer({ type: "seekPercent", value: 60 });
|
|
break;
|
|
case "Digit7":
|
|
reducer({ type: "seekPercent", value: 70 });
|
|
break;
|
|
case "Digit8":
|
|
reducer({ type: "seekPercent", value: 80 });
|
|
break;
|
|
case "Digit9":
|
|
reducer({ type: "seekPercent", value: 90 });
|
|
break;
|
|
}
|
|
};
|
|
|
|
document.addEventListener("keyup", handler);
|
|
return () => document.removeEventListener("keyup", handler);
|
|
}, [subtitles, fonts, nextEpisode, previousEpisode, router, reducer]);
|
|
};
|