mirror of
https://git.vectorsigma.ru/public/tubearchivist.git
synced 2026-08-09 00:10:04 +00:00
Add video player hotkeys
This commit is contained in:
@@ -1,10 +1,21 @@
|
|||||||
import updateVideoProgressById from '../api/actions/updateVideoProgressById';
|
import updateVideoProgressById from '../api/actions/updateVideoProgressById';
|
||||||
import updateWatchedState from '../api/actions/updateWatchedState';
|
import updateWatchedState from '../api/actions/updateWatchedState';
|
||||||
import { SponsorBlockSegmentType, SponsorBlockType, VideoResponseType } from '../pages/Video';
|
import { SponsorBlockSegmentType, SponsorBlockType, VideoResponseType } from '../pages/Video';
|
||||||
import { Dispatch, Fragment, SetStateAction, SyntheticEvent, useState } from 'react';
|
import {
|
||||||
|
Dispatch,
|
||||||
|
Fragment,
|
||||||
|
SetStateAction,
|
||||||
|
SyntheticEvent,
|
||||||
|
useEffect,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from 'react';
|
||||||
import formatTime from '../functions/formatTime';
|
import formatTime from '../functions/formatTime';
|
||||||
import { useSearchParams } from 'react-router-dom';
|
import { useSearchParams } from 'react-router-dom';
|
||||||
import getApiUrl from '../configuration/getApiUrl';
|
import getApiUrl from '../configuration/getApiUrl';
|
||||||
|
import { useKeyPress } from '../functions/useKeypressHook';
|
||||||
|
|
||||||
|
const VIDEO_PLAYBACK_SPEEDS = [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2, 2.25, 2.5, 2.75, 3];
|
||||||
|
|
||||||
type VideoTag = SyntheticEvent<HTMLVideoElement, Event>;
|
type VideoTag = SyntheticEvent<HTMLVideoElement, Event>;
|
||||||
|
|
||||||
@@ -109,10 +120,23 @@ const VideoPlayer = ({
|
|||||||
onWatchStateChanged,
|
onWatchStateChanged,
|
||||||
onVideoEnd,
|
onVideoEnd,
|
||||||
}: VideoPlayerProps) => {
|
}: VideoPlayerProps) => {
|
||||||
|
const videoRef = useRef<HTMLVideoElement | null>(null);
|
||||||
|
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
const searchParamVideoProgress = searchParams.get('t');
|
const searchParamVideoProgress = searchParams.get('t');
|
||||||
|
|
||||||
const [skippedSegments, setSkippedSegments] = useState<SponsorSegmentsSkippedType>({});
|
const [skippedSegments, setSkippedSegments] = useState<SponsorSegmentsSkippedType>({});
|
||||||
|
const [isMuted, setIsMuted] = useState(false);
|
||||||
|
const [playbackSpeedIndex, setPlaybackSpeedIndex] = useState(3);
|
||||||
|
const [lastSubtitleTack, setLastSubtitleTack] = useState(0);
|
||||||
|
|
||||||
|
// const questionmarkPressed = useKeyPress('?');
|
||||||
|
const mutePressed = useKeyPress('m');
|
||||||
|
const fullscreenPressed = useKeyPress('f');
|
||||||
|
const subtitlesPressed = useKeyPress('c');
|
||||||
|
const increasePlaybackSpeedPressed = useKeyPress('>');
|
||||||
|
const decreasePlaybackSpeedPressed = useKeyPress('<');
|
||||||
|
const resetPlaybackSpeedPressed = useKeyPress('=');
|
||||||
|
|
||||||
const videoId = video.data.youtube_id;
|
const videoId = video.data.youtube_id;
|
||||||
const videoUrl = video.data.media_url;
|
const videoUrl = video.data.media_url;
|
||||||
@@ -153,11 +177,95 @@ const VideoPlayer = ({
|
|||||||
onVideoEnd?.();
|
onVideoEnd?.();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (mutePressed) {
|
||||||
|
setIsMuted(!isMuted);
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [mutePressed]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (increasePlaybackSpeedPressed) {
|
||||||
|
const newSpeed = playbackSpeedIndex + 1;
|
||||||
|
|
||||||
|
if (videoRef.current && VIDEO_PLAYBACK_SPEEDS[newSpeed]) {
|
||||||
|
videoRef.current.playbackRate = VIDEO_PLAYBACK_SPEEDS[newSpeed];
|
||||||
|
|
||||||
|
setPlaybackSpeedIndex(newSpeed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [increasePlaybackSpeedPressed]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (decreasePlaybackSpeedPressed) {
|
||||||
|
const newSpeedIndex = playbackSpeedIndex - 1;
|
||||||
|
|
||||||
|
if (videoRef.current && VIDEO_PLAYBACK_SPEEDS[newSpeedIndex]) {
|
||||||
|
videoRef.current.playbackRate = VIDEO_PLAYBACK_SPEEDS[newSpeedIndex];
|
||||||
|
|
||||||
|
setPlaybackSpeedIndex(newSpeedIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [decreasePlaybackSpeedPressed]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (resetPlaybackSpeedPressed) {
|
||||||
|
const newSpeedIndex = 3;
|
||||||
|
|
||||||
|
if (videoRef.current && VIDEO_PLAYBACK_SPEEDS[newSpeedIndex]) {
|
||||||
|
videoRef.current.playbackRate = VIDEO_PLAYBACK_SPEEDS[newSpeedIndex];
|
||||||
|
|
||||||
|
setPlaybackSpeedIndex(newSpeedIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [resetPlaybackSpeedPressed]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (fullscreenPressed) {
|
||||||
|
if (videoRef.current && videoRef.current.requestFullscreen && !document.fullscreenElement) {
|
||||||
|
videoRef.current.requestFullscreen();
|
||||||
|
} else {
|
||||||
|
document.exitFullscreen().catch(e => {
|
||||||
|
console.error(e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [fullscreenPressed]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (subtitlesPressed) {
|
||||||
|
if (videoRef.current) {
|
||||||
|
const tracks = [...videoRef.current.textTracks];
|
||||||
|
|
||||||
|
if (tracks.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const lastIndex = tracks.findIndex(x => x.mode === 'showing');
|
||||||
|
const active = tracks[lastIndex];
|
||||||
|
|
||||||
|
if (!active && lastSubtitleTack !== 0) {
|
||||||
|
tracks[lastSubtitleTack - 1].mode = 'showing';
|
||||||
|
} else {
|
||||||
|
if (active) {
|
||||||
|
active.mode = 'hidden';
|
||||||
|
|
||||||
|
setLastSubtitleTack(lastIndex + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [subtitlesPressed]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div id="player" className={embed ? '' : 'player-wrapper'}>
|
<div id="player" className={embed ? '' : 'player-wrapper'}>
|
||||||
<div className={embed ? '' : 'video-main'}>
|
<div className={embed ? '' : 'video-main'}>
|
||||||
<video
|
<video
|
||||||
|
ref={videoRef}
|
||||||
key={`${getApiUrl()}${videoUrl}`}
|
key={`${getApiUrl()}${videoUrl}`}
|
||||||
poster={`${getApiUrl()}${videoThumbUrl}`}
|
poster={`${getApiUrl()}${videoThumbUrl}`}
|
||||||
onVolumeChange={(videoTag: VideoTag) => {
|
onVolumeChange={(videoTag: VideoTag) => {
|
||||||
@@ -196,6 +304,7 @@ const VideoPlayer = ({
|
|||||||
width="100%"
|
width="100%"
|
||||||
playsInline
|
playsInline
|
||||||
id="video-item"
|
id="video-item"
|
||||||
|
muted={isMuted}
|
||||||
>
|
>
|
||||||
<source
|
<source
|
||||||
src={`${getApiUrl()}${videoUrl}#t=${videoSrcProgress}`}
|
src={`${getApiUrl()}${videoUrl}#t=${videoSrcProgress}`}
|
||||||
|
|||||||
30
frontend/src/functions/useKeypressHook.tsx
Normal file
30
frontend/src/functions/useKeypressHook.tsx
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
// source: https://thibault.sh/react-hooks/use-key-press
|
||||||
|
export function useKeyPress(targetKey: string) {
|
||||||
|
const [isKeyPressed, setIsKeyPressed] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleKeyDown = (event: KeyboardEvent) => {
|
||||||
|
if (event.key === targetKey) {
|
||||||
|
setIsKeyPressed(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleKeyUp = (event: KeyboardEvent) => {
|
||||||
|
if (event.key === targetKey) {
|
||||||
|
setIsKeyPressed(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('keydown', handleKeyDown);
|
||||||
|
window.addEventListener('keyup', handleKeyUp);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('keydown', handleKeyDown);
|
||||||
|
window.removeEventListener('keyup', handleKeyUp);
|
||||||
|
};
|
||||||
|
}, [targetKey]);
|
||||||
|
|
||||||
|
return isKeyPressed;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user