From 0c2df2cd3cbb866e92a8fdb500ef10818b932c59 Mon Sep 17 00:00:00 2001 From: Jip Fr Date: Thu, 20 Apr 2023 19:50:57 +0200 Subject: [PATCH 1/4] fix(player): fix dismissal of UI after only 1 mousemove event --- src/video/components/actions/BackdropAction.tsx | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/video/components/actions/BackdropAction.tsx b/src/video/components/actions/BackdropAction.tsx index d7e75605..e0a1f9eb 100644 --- a/src/video/components/actions/BackdropAction.tsx +++ b/src/video/components/actions/BackdropAction.tsx @@ -24,18 +24,16 @@ export function BackdropAction(props: BackdropActionProps) { const handleMouseMove = useCallback(() => { if (!moved) { setTimeout(() => { + // If NOT a touch, set moved to true const isTouch = Date.now() - lastTouchEnd.current < 200; - if (!isTouch) { - setMoved(true); - } + if (!isTouch) setMoved(true); }, 20); - return; } // remove after all if (timeout.current) clearTimeout(timeout.current); timeout.current = setTimeout(() => { - if (moved) setMoved(false); + setMoved(false); timeout.current = null; }, 3000); }, [setMoved, moved]); From 2424cdfc9e8eeb3288b6f7c47f402e7fe8447649 Mon Sep 17 00:00:00 2001 From: Jip Fr Date: Thu, 20 Apr 2023 20:51:05 +0200 Subject: [PATCH 2/4] feat(video): add "volume adjusted" bar on top for keyboard events --- src/video/components/VideoPlayer.tsx | 2 ++ .../actions/KeyboardShortcutsAction.tsx | 4 +-- .../actions/VolumeAdjustedAction.tsx | 33 +++++++++++++++++++ src/video/state/init.ts | 1 + src/video/state/logic/controls.ts | 18 ++++++++-- src/video/state/logic/interface.ts | 2 ++ src/video/state/providers/providerTypes.ts | 2 +- src/video/state/types.ts | 1 + 8 files changed, 58 insertions(+), 5 deletions(-) create mode 100644 src/video/components/actions/VolumeAdjustedAction.tsx diff --git a/src/video/components/VideoPlayer.tsx b/src/video/components/VideoPlayer.tsx index 22d96502..8e5888a1 100644 --- a/src/video/components/VideoPlayer.tsx +++ b/src/video/components/VideoPlayer.tsx @@ -31,6 +31,7 @@ import { PictureInPictureAction } from "@/video/components/actions/PictureInPict import { CaptionRendererAction } from "./actions/CaptionRendererAction"; import { SettingsAction } from "./actions/SettingsAction"; import { DividerAction } from "./actions/DividerAction"; +import { VolumeAdjustedAction } from "./actions/VolumeAdjustedAction"; type Props = VideoPlayerBaseProps; @@ -91,6 +92,7 @@ export function VideoPlayer(props: Props) { <> + diff --git a/src/video/components/actions/KeyboardShortcutsAction.tsx b/src/video/components/actions/KeyboardShortcutsAction.tsx index 24e8b813..ba5ffc32 100644 --- a/src/video/components/actions/KeyboardShortcutsAction.tsx +++ b/src/video/components/actions/KeyboardShortcutsAction.tsx @@ -65,12 +65,12 @@ export function KeyboardShortcutsAction() { // Decrease volume case "arrowdown": - controls.setVolume(Math.max(mediaPlaying.volume - 0.1, 0)); + controls.setVolume(Math.max(mediaPlaying.volume - 0.1, 0), true); break; // Increase volume case "arrowup": - controls.setVolume(Math.min(mediaPlaying.volume + 0.1, 1)); + controls.setVolume(Math.min(mediaPlaying.volume + 0.1, 1), true); break; // Do a barrel Roll! diff --git a/src/video/components/actions/VolumeAdjustedAction.tsx b/src/video/components/actions/VolumeAdjustedAction.tsx new file mode 100644 index 00000000..29a58da1 --- /dev/null +++ b/src/video/components/actions/VolumeAdjustedAction.tsx @@ -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 ( +
+ 0 ? Icons.VOLUME : Icons.VOLUME_X} + className="text-xl text-white" + /> +
+
+
+
+ ); +} diff --git a/src/video/state/init.ts b/src/video/state/init.ts index bd4037fe..3c55a642 100644 --- a/src/video/state/init.ts +++ b/src/video/state/init.ts @@ -32,6 +32,7 @@ function initPlayer(): VideoPlayerState { isFocused: false, leftControlHovering: false, popoutBounds: null, + volumeChangedWithKeybind: false, }, mediaPlaying: { diff --git a/src/video/state/logic/controls.ts b/src/video/state/logic/controls.ts index e6d33369..fc8f99e5 100644 --- a/src/video/state/logic/controls.ts +++ b/src/video/state/logic/controls.ts @@ -5,6 +5,8 @@ import { VideoPlayerMeta } from "@/video/state/types"; import { getPlayerState } from "../cache"; import { VideoPlayerStateController } from "../providers/providerTypes"; +let volumeChangedWithKeybindDebounce: NodeJS.Timeout | null = null; + export type ControlMethods = { openPopout(id: string): void; closePopout(): void; @@ -48,8 +50,20 @@ export function useControls( enterFullscreen() { state.stateProvider?.enterFullscreen(); }, - setVolume(volume) { - state.stateProvider?.setVolume(volume); + setVolume(volume, isKeyboardEvent = false) { + 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() { state.stateProvider?.startAirplay(); diff --git a/src/video/state/logic/interface.ts b/src/video/state/logic/interface.ts index 2f22823f..43185a3a 100644 --- a/src/video/state/logic/interface.ts +++ b/src/video/state/logic/interface.ts @@ -9,6 +9,7 @@ export type VideoInterfaceEvent = { isFocused: boolean; isFullscreen: boolean; popoutBounds: null | DOMRect; + volumeChangedWithKeybind: boolean; }; function getInterfaceFromState(state: VideoPlayerState): VideoInterfaceEvent { @@ -18,6 +19,7 @@ function getInterfaceFromState(state: VideoPlayerState): VideoInterfaceEvent { isFocused: state.interface.isFocused, isFullscreen: state.interface.isFullscreen, popoutBounds: state.interface.popoutBounds, + volumeChangedWithKeybind: state.interface.volumeChangedWithKeybind, }; } diff --git a/src/video/state/providers/providerTypes.ts b/src/video/state/providers/providerTypes.ts index ad09e812..acc73dc5 100644 --- a/src/video/state/providers/providerTypes.ts +++ b/src/video/state/providers/providerTypes.ts @@ -16,7 +16,7 @@ export type VideoPlayerStateController = { setSeeking(active: boolean): void; exitFullscreen(): void; enterFullscreen(): void; - setVolume(volume: number): void; + setVolume(volume: number, isKeyboardEvent?: boolean): void; startAirplay(): void; setCaption(id: string, url: string): void; clearCaption(): void; diff --git a/src/video/state/types.ts b/src/video/state/types.ts index 1ba9ef7a..be6016c4 100644 --- a/src/video/state/types.ts +++ b/src/video/state/types.ts @@ -28,6 +28,7 @@ export type VideoPlayerState = { isFullscreen: boolean; 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) + 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 popoutBounds: null | DOMRect; // bounding box of current popout }; From a0a51c898a618af4ac380733bfd0e2dc2325d6f0 Mon Sep 17 00:00:00 2001 From: Jip Fr Date: Thu, 20 Apr 2023 20:53:35 +0200 Subject: [PATCH 3/4] chore: remove unused import --- src/video/components/actions/VolumeAdjustedAction.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/video/components/actions/VolumeAdjustedAction.tsx b/src/video/components/actions/VolumeAdjustedAction.tsx index 29a58da1..a5c547c5 100644 --- a/src/video/components/actions/VolumeAdjustedAction.tsx +++ b/src/video/components/actions/VolumeAdjustedAction.tsx @@ -1,6 +1,5 @@ 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"; From 7007f030e19572db271c83cdd0a086e0d08094cc Mon Sep 17 00:00:00 2001 From: Jip Fr Date: Thu, 20 Apr 2023 21:07:44 +0200 Subject: [PATCH 4/4] feat(player): use state-specific debouncer, not global --- src/video/state/init.ts | 1 + src/video/state/logic/controls.ts | 8 +++----- src/video/state/types.ts | 1 + 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/video/state/init.ts b/src/video/state/init.ts index 3c55a642..570d27d3 100644 --- a/src/video/state/init.ts +++ b/src/video/state/init.ts @@ -33,6 +33,7 @@ function initPlayer(): VideoPlayerState { leftControlHovering: false, popoutBounds: null, volumeChangedWithKeybind: false, + volumeChangedWithKeybindDebounce: null, }, mediaPlaying: { diff --git a/src/video/state/logic/controls.ts b/src/video/state/logic/controls.ts index fc8f99e5..76cc8e2e 100644 --- a/src/video/state/logic/controls.ts +++ b/src/video/state/logic/controls.ts @@ -5,8 +5,6 @@ import { VideoPlayerMeta } from "@/video/state/types"; import { getPlayerState } from "../cache"; import { VideoPlayerStateController } from "../providers/providerTypes"; -let volumeChangedWithKeybindDebounce: NodeJS.Timeout | null = null; - export type ControlMethods = { openPopout(id: string): void; closePopout(): void; @@ -52,13 +50,13 @@ export function useControls( }, setVolume(volume, isKeyboardEvent = false) { if (isKeyboardEvent) { - if (volumeChangedWithKeybindDebounce) - clearTimeout(volumeChangedWithKeybindDebounce); + if (state.interface.volumeChangedWithKeybindDebounce) + clearTimeout(state.interface.volumeChangedWithKeybindDebounce); state.interface.volumeChangedWithKeybind = true; updateInterface(descriptor, state); - volumeChangedWithKeybindDebounce = setTimeout(() => { + state.interface.volumeChangedWithKeybindDebounce = setTimeout(() => { state.interface.volumeChangedWithKeybind = false; updateInterface(descriptor, state); }, 3e3); diff --git a/src/video/state/types.ts b/src/video/state/types.ts index be6016c4..fc059979 100644 --- a/src/video/state/types.ts +++ b/src/video/state/types.ts @@ -29,6 +29,7 @@ export type VideoPlayerState = { 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) volumeChangedWithKeybind: boolean; // has the volume recently been adjusted with the up/down arrows recently? + volumeChangedWithKeybindDebounce: NodeJS.Timeout | null; // debounce for the duration of the "volume changed thingamajig" leftControlHovering: boolean; // is the cursor hovered over the left side of player controls popoutBounds: null | DOMRect; // bounding box of current popout };