diff --git a/js/src/components/video-audio-toggle.tsx b/js/src/components/video-audio-toggle.tsx index 774e3ce..712f38b 100644 --- a/js/src/components/video-audio-toggle.tsx +++ b/js/src/components/video-audio-toggle.tsx @@ -6,27 +6,28 @@ export function VideoAudioToggle() { const setRecordingMode = useMediaSettingsStore((s) => s.setRecordingMode); return ( - - - setRecordingMode(recordingMode === "video" ? "audio" : "video") - } - title={ - recordingMode === "video" ? "Switch to audio-only" : "Switch to video" - } - className="cursor-pointer rounded bg-white/10 px-1.5 py-0.5 font-mono text-xs transition-colors hover:text-white/80" - > - {recordingMode === "video" ? ( - <> - + + setRecordingMode(recordingMode === "video" ? "audio" : "video") + } + title={ + recordingMode === "video" ? "Switch to audio-only (V)" : "Switch to video (V)" + } + className="cursor-pointer transition-colors hover:text-white/80" + > + + V + {" "} + {recordingMode === "video" ? ( + <> + ); } diff --git a/js/src/features/particles/stream-view.tsx b/js/src/features/particles/stream-view.tsx index c96fbe1..b7d2615 100644 --- a/js/src/features/particles/stream-view.tsx +++ b/js/src/features/particles/stream-view.tsx @@ -11,6 +11,7 @@ import { TextParticleView } from "@/features/particles/text-particle-view"; import { FallbackParticleView } from "@/features/particles/fallback-particle-view"; import { Avatar, AvatarFallback, AvatarGroup } from "@/components/ui/avatar"; import { VideoAudioToggle } from "@/components/video-audio-toggle"; +import { useMediaSettingsStore } from "@/stores/media-settings-store"; import { KeybindingsOverlay, type KeybindingGroup } from "@/components/keybindings-overlay"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import { useNetwork, useNetworks } from "@/hooks/use-networks"; @@ -132,6 +133,7 @@ const STREAM_VIEW_KEYBINDINGS: KeybindingGroup[] = [ { keys: ["Hold", "`"], description: "Reply" }, { keys: ["S"], description: "Screen record" }, { keys: ["T"], description: "Text compose" }, + { keys: ["V"], description: "Toggle video / audio" }, { keys: ["H"], description: "Join huddle" }, ], }, @@ -174,6 +176,8 @@ export function StreamView({ path, streamParticle }: StreamViewProps) { usePrefetchAdjacentMedia(children, currentIndex); const authedUser = useAuthStore((s) => s.user); + const recordingMode = useMediaSettingsStore((s) => s.recordingMode); + const setRecordingMode = useMediaSettingsStore((s) => s.setRecordingMode); const network = useNetwork(networkId); const presenceBySegment = usePresencePositions( streamParticle.playback_markers, @@ -284,6 +288,10 @@ export function StreamView({ path, streamParticle }: StreamViewProps) { navigate(`/${networkId}`); break; } + case "v": + e.preventDefault(); + setRecordingMode(recordingMode === "video" ? "audio" : "video"); + break; case "?": e.preventDefault(); setShowKeybindings((v) => !v); @@ -316,7 +324,7 @@ export function StreamView({ path, streamParticle }: StreamViewProps) { window.removeEventListener("keyup", handleKeyUp); }; }, - [composeActive, next, prev, pause, resume, navigate, networkId, streamParticle.id, setShowKeybindings, handleToggleReaction], + [composeActive, next, prev, pause, resume, navigate, networkId, streamParticle.id, setShowKeybindings, handleToggleReaction, recordingMode, setRecordingMode], ); // Click-to-navigate: left 30% = prev, right 70% = next diff --git a/js/src/hooks/use-stream-keyboard-nav.ts b/js/src/hooks/use-stream-keyboard-nav.ts index bced63b..ad2c09d 100644 --- a/js/src/hooks/use-stream-keyboard-nav.ts +++ b/js/src/hooks/use-stream-keyboard-nav.ts @@ -1,4 +1,5 @@ import { useEffect, useState } from "react"; +import { useMediaSettingsStore } from "@/stores/media-settings-store"; interface UseStreamKeyboardNavOptions { streams: Array<{ id: string }>; @@ -12,6 +13,8 @@ export function useStreamKeyboardNav({ onNavigate, }: UseStreamKeyboardNavOptions) { const [selectedIndex, setSelectedIndex] = useState(null); + const recordingMode = useMediaSettingsStore((s) => s.recordingMode); + const setRecordingMode = useMediaSettingsStore((s) => s.setRecordingMode); // Initialize selection when streams first load; clear if streams become empty. // Do NOT reset on every Firestore update — that would scroll the list to the top. @@ -61,6 +64,13 @@ export function useStreamKeyboardNav({ return; } + // V: toggle video / audio + if (e.key === "v" || e.key === "V") { + e.preventDefault(); + setRecordingMode(recordingMode === "video" ? "audio" : "video"); + return; + } + // Arrow keys: move selection let delta: number | null = null; @@ -81,7 +91,7 @@ export function useStreamKeyboardNav({ // components (ToggleGroup, etc.) consume them for their own navigation. window.addEventListener("keydown", handleKeyDown, true); return () => window.removeEventListener("keydown", handleKeyDown, true); - }, [enabled, streams, onNavigate]); + }, [enabled, streams, onNavigate, recordingMode, setRecordingMode]); return { selectedIndex }; }