diff --git a/web/src/components/dynamic/CameraFeatureToggle.tsx b/web/src/components/dynamic/CameraFeatureToggle.tsx
new file mode 100644
index 000000000..666bfb33b
--- /dev/null
+++ b/web/src/components/dynamic/CameraFeatureToggle.tsx
@@ -0,0 +1,60 @@
+import { IconType } from "react-icons";
+import {
+ Tooltip,
+ TooltipContent,
+ TooltipTrigger,
+} from "@/components/ui/tooltip";
+import { isDesktop } from "react-device-detect";
+
+const variants = {
+ primary: {
+ active: "font-bold text-primary-foreground bg-primary",
+ inactive: "text-muted-foreground bg-muted",
+ },
+ secondary: {
+ active: "font-bold text-primary",
+ inactive: "text-muted-foreground",
+ },
+};
+
+type CameraFeatureToggleProps = {
+ className?: string;
+ variant?: "primary" | "secondary";
+ isActive: boolean;
+ Icon: IconType;
+ title: string;
+ onClick?: () => void;
+};
+
+export default function CameraFeatureToggle({
+ className = "",
+ variant = "primary",
+ isActive,
+ Icon,
+ title,
+ onClick,
+}: CameraFeatureToggleProps) {
+ const content = (
+
+
+
+ );
+
+ if (isDesktop) {
+ return (
+
+ {content}
+
+ {title}
+
+
+ );
+ }
+
+ return content;
+}
diff --git a/web/src/views/live/LiveCameraView.tsx b/web/src/views/live/LiveCameraView.tsx
index 951e16c76..b83a78935 100644
--- a/web/src/views/live/LiveCameraView.tsx
+++ b/web/src/views/live/LiveCameraView.tsx
@@ -1,4 +1,11 @@
-import { usePtzCommand } from "@/api/ws";
+import {
+ useAudioState,
+ useDetectState,
+ usePtzCommand,
+ useRecordingsState,
+ useSnapshotsState,
+} from "@/api/ws";
+import CameraFeatureToggle from "@/components/dynamic/CameraFeatureToggle";
import LivePlayer from "@/components/player/LivePlayer";
import { Button } from "@/components/ui/button";
import {
@@ -7,6 +14,7 @@ import {
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
+import { TooltipProvider } from "@/components/ui/tooltip";
import useKeyboardListener from "@/hooks/use-keyboard-listener";
import { CameraConfig } from "@/types/frigateConfig";
import { CameraPtzInfo } from "@/types/ptz";
@@ -20,7 +28,15 @@ import {
FaAngleUp,
} from "react-icons/fa";
import { IoMdArrowBack } from "react-icons/io";
-import { MdZoomIn, MdZoomOut } from "react-icons/md";
+import { LuEar, LuEarOff, LuVideo, LuVideoOff } from "react-icons/lu";
+import {
+ MdNoPhotography,
+ MdPersonOff,
+ MdPersonSearch,
+ MdPhotoCamera,
+ MdZoomIn,
+ MdZoomOut,
+} from "react-icons/md";
import { useNavigate } from "react-router-dom";
import useSWR from "swr";
@@ -30,6 +46,19 @@ type LiveCameraViewProps = {
export default function LiveCameraView({ camera }: LiveCameraViewProps) {
const navigate = useNavigate();
+ // camera features
+
+ const { payload: detectState, send: sendDetect } = useDetectState(
+ camera.name,
+ );
+ const { payload: recordState, send: sendRecord } = useRecordingsState(
+ camera.name,
+ );
+ const { payload: snapshotState, send: sendSnapshot } = useSnapshotsState(
+ camera.name,
+ );
+ const { payload: audioState, send: sendAudio } = useAudioState(camera.name);
+
const growClassName = useMemo(() => {
if (camera.detect.width / camera.detect.height > 2) {
return "absolute left-2 right-2 top-[50%] -translate-y-[50%]";
@@ -45,6 +74,36 @@ export default function LiveCameraView({ camera }: LiveCameraViewProps) {
Back
+
+
+ sendDetect(detectState == "ON" ? "OFF" : "ON")}
+ />
+ sendRecord(recordState == "ON" ? "OFF" : "ON")}
+ />
+ sendSnapshot(snapshotState == "ON" ? "OFF" : "ON")}
+ />
+ {camera.audio.enabled_in_config && (
+ sendAudio(audioState == "ON" ? "OFF" : "ON")}
+ />
+ )}
+
+
@@ -70,7 +129,7 @@ export default function LiveCameraView({ camera }: LiveCameraViewProps) {
function PtzControlPanel({ camera }: { camera: string }) {
const { data: ptz } = useSWR(`${camera}/ptz/info`);
- const { payload: _, send: sendPtz } = usePtzCommand(camera);
+ const { send: sendPtz } = usePtzCommand(camera);
const onStop = useCallback(
(e: React.SyntheticEvent) => {