diff --git a/Grayjay.Desktop.Web/src/components/player/PlayerControlsView/index.tsx b/Grayjay.Desktop.Web/src/components/player/PlayerControlsView/index.tsx index f84cea70..8185d3e1 100644 --- a/Grayjay.Desktop.Web/src/components/player/PlayerControlsView/index.tsx +++ b/Grayjay.Desktop.Web/src/components/player/PlayerControlsView/index.tsx @@ -35,6 +35,7 @@ export interface PlayerControlsProps { handlePlay?: ()=>void; handleTheatre?: () => void; handleToggleVolume?: () => void; + toggleCaptions?:()=>void; onInteraction?: () => void; children: JSX.Element; volume?: number; @@ -269,15 +270,42 @@ const PlayerControlsView: Component = (props) => { const skip = (direction: number) => { let position = props.position.as("milliseconds"); const duration = props.duration.as("milliseconds"); - if (duration > 0) { - position = Math.max(Math.min(duration, position + direction * 5000), 0); - } else { - position = Math.max(position + direction * 5000, 0); - } + position = Math.max(Math.min(duration, position + direction*1000), 0); props.onSetPosition?.(Duration.fromMillis(position)); }; const handleKeyDown = (ev: KeyboardEvent) => { + function prevChapter(){ + let position = props.position.as("seconds"); + position-=1; + position=props.chapters?.find(x=>x.timeStart <= position && x.timeEnd > position)?.timeStart||props.chapters?.find(x=>x.timeEnd < position)?.timeEnd||0; + props.onSetPosition?.(Duration.fromMillis(position*1000)); + } + function nextChapter(){ + let position = props.position.as("seconds"); + const duration = props.duration.as("seconds"); + position=props.chapters?.find(x=>x.timeStart <= position && x.timeEnd > position)?.timeEnd||props.chapters?.find(x=>x.timeStart > position)?.timeStart||duration; + position=Math.min(position,duration); + props.onSetPosition?.(Duration.fromMillis(position*1000)); + } + switch(ev.key){ + case "MediaTrackPrevious": + prevChapter(); + ev.preventDefault(); + break; + case "MediaTrackNext": + nextChapter(); + ev.preventDefault(); + break; + case "MediaPlayPause": + if (props.isPlaying) { + onPause(ev as any); + } else { + onPlay(ev as any); + } + ev.preventDefault(); + break; + } const target = ev.target as any; if (target) { const isInputField = target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.tagName === 'SELECT'; @@ -285,7 +313,21 @@ const PlayerControlsView: Component = (props) => { return; } } - + if(ev.ctrlKey){ + switch (ev.key) { + case "ArrowLeft":{ + prevChapter(); + ev.preventDefault(); + break; + } + case "ArrowRight":{ + nextChapter(); + ev.preventDefault(); + break; + } + } + return; + } switch (ev.key) { case " ": case "k": @@ -307,23 +349,31 @@ const PlayerControlsView: Component = (props) => { props.onInteraction?.(); ev.preventDefault(); break; - case "ArrowUp": - props.onSetVolume?.(Math.min((props.volume ?? 0) + 0.1, 1)); + case "Home": + props.onSetPosition?.(Duration.fromMillis(0)); props.onInteraction?.(); ev.preventDefault(); break; - case "ArrowDown": - props.onSetVolume?.(Math.max((props.volume ?? 0) - 0.1, 0)); + case "End": + props.onSetPosition?.(props.duration); props.onInteraction?.(); ev.preventDefault(); break; - case "f": - onFullscreen(ev as any); + case ",": + skip(-1/60); // framerate might be 30 instead of 60? + ev.preventDefault(); + break; + case ".": + skip(1/60); + ev.preventDefault(); + break; + case "ArrowUp": + props.onSetVolume?.(Math.min((props.volume ?? 0) + 0.1, 1)); props.onInteraction?.(); ev.preventDefault(); break; - case "t": - onTheatre(ev as any); + case "ArrowDown": + props.onSetVolume?.(Math.max((props.volume ?? 0) - 0.1, 0)); props.onInteraction?.(); ev.preventDefault(); break; @@ -332,13 +382,13 @@ const PlayerControlsView: Component = (props) => { props.onInteraction?.(); ev.preventDefault(); break; - case "Home": - props.onSetPosition?.(Duration.fromMillis(0)); + case "f": + onFullscreen(ev as any); props.onInteraction?.(); ev.preventDefault(); break; - case "End": - props.onSetPosition?.(props.duration); + case "t": + onTheatre(ev as any); props.onInteraction?.(); ev.preventDefault(); break; @@ -347,6 +397,10 @@ const PlayerControlsView: Component = (props) => { props.onInteraction?.(); ev.preventDefault(); break; + case "v": + props.toggleCaptions?.(); + ev.preventDefault(); + break; } }; @@ -371,10 +425,10 @@ const PlayerControlsView: Component = (props) => { const startSkipping = (direction: number) => { stopSkipping(); - skip(direction); + skip(direction*5); startSkippingTimeout = setTimeout(() => { skipInterval = setInterval(() => { - skip(direction); + skip(direction*5); }, 100); }, 2000); }; diff --git a/Grayjay.Desktop.Web/src/components/player/VideoPlayerView/index.tsx b/Grayjay.Desktop.Web/src/components/player/VideoPlayerView/index.tsx index 94fab7fb..c1848169 100644 --- a/Grayjay.Desktop.Web/src/components/player/VideoPlayerView/index.tsx +++ b/Grayjay.Desktop.Web/src/components/player/VideoPlayerView/index.tsx @@ -78,6 +78,7 @@ const VideoPlayerView: Component = (props) => { const [isLoading, setIsLoading] = createSignal(true); const [resumePositionVisible, setResumePositionVisible] = createSignal(false); const [endControlsVisible$, setEndControlsVisible] = createSignal(false); + const [showCaptions,setCaptionVisibility]=createSignal(true); let currentUrl: string | undefined; let castingEndedEmitted = false; @@ -1113,6 +1114,7 @@ const VideoPlayerView: Component = (props) => { handleTheatre={props.handleTheatre} handleEscape={handleEscape} handleMinimize={handleMinimize} + toggleCaptions={()=>{setCaptionVisibility(!showCaptions());}} eventMoved={props.eventMoved} buttons={props.buttons} leftButtonContainerStyle={props.leftButtonContainerStyle} @@ -1120,8 +1122,10 @@ const VideoPlayerView: Component = (props) => { {props.children} - -
+ + +
+