feat(video): add "volume adjusted" bar on top for keyboard events

This commit is contained in:
Jip Fr 2023-04-20 20:51:05 +02:00
parent 0c2df2cd3c
commit 2424cdfc9e
8 changed files with 58 additions and 5 deletions

View File

@ -31,6 +31,7 @@ import { PictureInPictureAction } from "@/video/components/actions/PictureInPict
import { CaptionRendererAction } from "./actions/CaptionRendererAction"; import { CaptionRendererAction } from "./actions/CaptionRendererAction";
import { SettingsAction } from "./actions/SettingsAction"; import { SettingsAction } from "./actions/SettingsAction";
import { DividerAction } from "./actions/DividerAction"; import { DividerAction } from "./actions/DividerAction";
import { VolumeAdjustedAction } from "./actions/VolumeAdjustedAction";
type Props = VideoPlayerBaseProps; type Props = VideoPlayerBaseProps;
@ -91,6 +92,7 @@ export function VideoPlayer(props: Props) {
<> <>
<KeyboardShortcutsAction /> <KeyboardShortcutsAction />
<PageTitleAction /> <PageTitleAction />
<VolumeAdjustedAction />
<VideoPlayerError onGoBack={props.onGoBack}> <VideoPlayerError onGoBack={props.onGoBack}>
<BackdropAction onBackdropChange={onBackdropChange}> <BackdropAction onBackdropChange={onBackdropChange}>
<CenterPosition> <CenterPosition>

View File

@ -65,12 +65,12 @@ export function KeyboardShortcutsAction() {
// Decrease volume // Decrease volume
case "arrowdown": case "arrowdown":
controls.setVolume(Math.max(mediaPlaying.volume - 0.1, 0)); controls.setVolume(Math.max(mediaPlaying.volume - 0.1, 0), true);
break; break;
// Increase volume // Increase volume
case "arrowup": case "arrowup":
controls.setVolume(Math.min(mediaPlaying.volume + 0.1, 1)); controls.setVolume(Math.min(mediaPlaying.volume + 0.1, 1), true);
break; break;
// Do a barrel Roll! // Do a barrel Roll!

View File

@ -0,0 +1,33 @@
import { Icon, Icons } from "@/components/Icon";
import { useVideoPlayerDescriptor } from "@/video/state/hooks";
import { useControls } from "@/video/state/logic/controls";
import { useInterface } from "@/video/state/logic/interface";
import { useMediaPlaying } from "@/video/state/logic/mediaplaying";
export function VolumeAdjustedAction() {
const descriptor = useVideoPlayerDescriptor();
const videoInterface = useInterface(descriptor);
const mediaPlaying = useMediaPlaying(descriptor);
return (
<div
className={[
videoInterface.volumeChangedWithKeybind
? "mt-10 scale-100 opacity-100"
: "mt-5 scale-75 opacity-0",
"absolute left-1/2 z-[100] flex -translate-x-1/2 items-center space-x-4 rounded-full bg-bink-300 bg-opacity-50 py-2 px-5 transition-all duration-100",
].join(" ")}
>
<Icon
icon={mediaPlaying.volume > 0 ? Icons.VOLUME : Icons.VOLUME_X}
className="text-xl text-white"
/>
<div className="h-2 w-44 overflow-hidden rounded-full bg-denim-100">
<div
className="h-full rounded-r-full bg-bink-500 transition-[width] duration-100"
style={{ width: `${mediaPlaying.volume * 100}%` }}
/>
</div>
</div>
);
}

View File

@ -32,6 +32,7 @@ function initPlayer(): VideoPlayerState {
isFocused: false, isFocused: false,
leftControlHovering: false, leftControlHovering: false,
popoutBounds: null, popoutBounds: null,
volumeChangedWithKeybind: false,
}, },
mediaPlaying: { mediaPlaying: {

View File

@ -5,6 +5,8 @@ import { VideoPlayerMeta } from "@/video/state/types";
import { getPlayerState } from "../cache"; import { getPlayerState } from "../cache";
import { VideoPlayerStateController } from "../providers/providerTypes"; import { VideoPlayerStateController } from "../providers/providerTypes";
let volumeChangedWithKeybindDebounce: NodeJS.Timeout | null = null;
export type ControlMethods = { export type ControlMethods = {
openPopout(id: string): void; openPopout(id: string): void;
closePopout(): void; closePopout(): void;
@ -48,8 +50,20 @@ export function useControls(
enterFullscreen() { enterFullscreen() {
state.stateProvider?.enterFullscreen(); state.stateProvider?.enterFullscreen();
}, },
setVolume(volume) { setVolume(volume, isKeyboardEvent = false) {
state.stateProvider?.setVolume(volume); if (isKeyboardEvent) {
if (volumeChangedWithKeybindDebounce)
clearTimeout(volumeChangedWithKeybindDebounce);
state.interface.volumeChangedWithKeybind = true;
updateInterface(descriptor, state);
volumeChangedWithKeybindDebounce = setTimeout(() => {
state.interface.volumeChangedWithKeybind = false;
updateInterface(descriptor, state);
}, 3e3);
}
state.stateProvider?.setVolume(volume, isKeyboardEvent);
}, },
startAirplay() { startAirplay() {
state.stateProvider?.startAirplay(); state.stateProvider?.startAirplay();

View File

@ -9,6 +9,7 @@ export type VideoInterfaceEvent = {
isFocused: boolean; isFocused: boolean;
isFullscreen: boolean; isFullscreen: boolean;
popoutBounds: null | DOMRect; popoutBounds: null | DOMRect;
volumeChangedWithKeybind: boolean;
}; };
function getInterfaceFromState(state: VideoPlayerState): VideoInterfaceEvent { function getInterfaceFromState(state: VideoPlayerState): VideoInterfaceEvent {
@ -18,6 +19,7 @@ function getInterfaceFromState(state: VideoPlayerState): VideoInterfaceEvent {
isFocused: state.interface.isFocused, isFocused: state.interface.isFocused,
isFullscreen: state.interface.isFullscreen, isFullscreen: state.interface.isFullscreen,
popoutBounds: state.interface.popoutBounds, popoutBounds: state.interface.popoutBounds,
volumeChangedWithKeybind: state.interface.volumeChangedWithKeybind,
}; };
} }

View File

@ -16,7 +16,7 @@ export type VideoPlayerStateController = {
setSeeking(active: boolean): void; setSeeking(active: boolean): void;
exitFullscreen(): void; exitFullscreen(): void;
enterFullscreen(): void; enterFullscreen(): void;
setVolume(volume: number): void; setVolume(volume: number, isKeyboardEvent?: boolean): void;
startAirplay(): void; startAirplay(): void;
setCaption(id: string, url: string): void; setCaption(id: string, url: string): void;
clearCaption(): void; clearCaption(): void;

View File

@ -28,6 +28,7 @@ export type VideoPlayerState = {
isFullscreen: boolean; isFullscreen: boolean;
popout: string | null; // id of current popout (eg source select, episode select) popout: string | null; // id of current popout (eg source select, episode select)
isFocused: boolean; // is the video player the users focus? (shortcuts only works when its focused) isFocused: boolean; // is the video player the users focus? (shortcuts only works when its focused)
volumeChangedWithKeybind: boolean; // has the volume recently been adjusted with the up/down arrows recently?
leftControlHovering: boolean; // is the cursor hovered over the left side of player controls leftControlHovering: boolean; // is the cursor hovered over the left side of player controls
popoutBounds: null | DOMRect; // bounding box of current popout popoutBounds: null | DOMRect; // bounding box of current popout
}; };