mirror of
https://github.com/blakeblackshear/frigate.git
synced 2024-11-21 19:07:46 +01:00
Live view improvements (#10781)
* Show frigate features in bottom sheet on mobile * Use flex wrap on mobile so the ptz icons are not cutoff * Support opening pip from live view * Remove unused
This commit is contained in:
parent
a886b6a3e5
commit
4d8d3cd22e
@ -22,6 +22,7 @@ type LivePlayerProps = {
|
|||||||
playAudio?: boolean;
|
playAudio?: boolean;
|
||||||
micEnabled?: boolean; // only webrtc supports mic
|
micEnabled?: boolean; // only webrtc supports mic
|
||||||
iOSCompatFullScreen?: boolean;
|
iOSCompatFullScreen?: boolean;
|
||||||
|
pip?: boolean;
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -35,6 +36,7 @@ export default function LivePlayer({
|
|||||||
playAudio = false,
|
playAudio = false,
|
||||||
micEnabled = false,
|
micEnabled = false,
|
||||||
iOSCompatFullScreen = false,
|
iOSCompatFullScreen = false,
|
||||||
|
pip,
|
||||||
onClick,
|
onClick,
|
||||||
}: LivePlayerProps) {
|
}: LivePlayerProps) {
|
||||||
// camera activity
|
// camera activity
|
||||||
@ -105,6 +107,7 @@ export default function LivePlayer({
|
|||||||
microphoneEnabled={micEnabled}
|
microphoneEnabled={micEnabled}
|
||||||
iOSCompatFullScreen={iOSCompatFullScreen}
|
iOSCompatFullScreen={iOSCompatFullScreen}
|
||||||
onPlaying={() => setLiveReady(true)}
|
onPlaying={() => setLiveReady(true)}
|
||||||
|
pip={pip}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else if (liveMode == "mse") {
|
} else if (liveMode == "mse") {
|
||||||
@ -116,6 +119,7 @@ export default function LivePlayer({
|
|||||||
playbackEnabled={cameraActive}
|
playbackEnabled={cameraActive}
|
||||||
audioEnabled={playAudio}
|
audioEnabled={playAudio}
|
||||||
onPlaying={() => setLiveReady(true)}
|
onPlaying={() => setLiveReady(true)}
|
||||||
|
pip={pip}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
@ -6,6 +6,7 @@ type MSEPlayerProps = {
|
|||||||
className?: string;
|
className?: string;
|
||||||
playbackEnabled?: boolean;
|
playbackEnabled?: boolean;
|
||||||
audioEnabled?: boolean;
|
audioEnabled?: boolean;
|
||||||
|
pip?: boolean;
|
||||||
onPlaying?: () => void;
|
onPlaying?: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -14,6 +15,7 @@ function MSEPlayer({
|
|||||||
className,
|
className,
|
||||||
playbackEnabled = true,
|
playbackEnabled = true,
|
||||||
audioEnabled = false,
|
audioEnabled = false,
|
||||||
|
pip = false,
|
||||||
onPlaying,
|
onPlaying,
|
||||||
}: MSEPlayerProps) {
|
}: MSEPlayerProps) {
|
||||||
let connectTS: number = 0;
|
let connectTS: number = 0;
|
||||||
@ -268,6 +270,16 @@ function MSEPlayer({
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [playbackEnabled, onDisconnect, onConnect]);
|
}, [playbackEnabled, onDisconnect, onConnect]);
|
||||||
|
|
||||||
|
// control pip
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!videoRef.current || !pip) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
videoRef.current.requestPictureInPicture();
|
||||||
|
}, [pip, videoRef]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<video
|
<video
|
||||||
ref={videoRef}
|
ref={videoRef}
|
||||||
|
@ -8,6 +8,7 @@ type WebRtcPlayerProps = {
|
|||||||
audioEnabled?: boolean;
|
audioEnabled?: boolean;
|
||||||
microphoneEnabled?: boolean;
|
microphoneEnabled?: boolean;
|
||||||
iOSCompatFullScreen?: boolean; // ios doesn't support fullscreen divs so we must support the video element
|
iOSCompatFullScreen?: boolean; // ios doesn't support fullscreen divs so we must support the video element
|
||||||
|
pip?: boolean;
|
||||||
onPlaying?: () => void;
|
onPlaying?: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -18,6 +19,7 @@ export default function WebRtcPlayer({
|
|||||||
audioEnabled = false,
|
audioEnabled = false,
|
||||||
microphoneEnabled = false,
|
microphoneEnabled = false,
|
||||||
iOSCompatFullScreen = false,
|
iOSCompatFullScreen = false,
|
||||||
|
pip = false,
|
||||||
onPlaying,
|
onPlaying,
|
||||||
}: WebRtcPlayerProps) {
|
}: WebRtcPlayerProps) {
|
||||||
// metadata
|
// metadata
|
||||||
@ -173,8 +175,19 @@ export default function WebRtcPlayer({
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
// ios compat
|
// ios compat
|
||||||
|
|
||||||
const [iOSCompatControls, setiOSCompatControls] = useState(false);
|
const [iOSCompatControls, setiOSCompatControls] = useState(false);
|
||||||
|
|
||||||
|
// control pip
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!videoRef.current || !pip) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
videoRef.current.requestPictureInPicture();
|
||||||
|
}, [pip, videoRef]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<video
|
<video
|
||||||
ref={videoRef}
|
ref={videoRef}
|
||||||
|
@ -8,12 +8,15 @@ import {
|
|||||||
import CameraFeatureToggle from "@/components/dynamic/CameraFeatureToggle";
|
import CameraFeatureToggle from "@/components/dynamic/CameraFeatureToggle";
|
||||||
import LivePlayer from "@/components/player/LivePlayer";
|
import LivePlayer from "@/components/player/LivePlayer";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Drawer, DrawerContent, DrawerTrigger } from "@/components/ui/drawer";
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "@/components/ui/dropdown-menu";
|
} from "@/components/ui/dropdown-menu";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import { Switch } from "@/components/ui/switch";
|
||||||
import { TooltipProvider } from "@/components/ui/tooltip";
|
import { TooltipProvider } from "@/components/ui/tooltip";
|
||||||
import { useResizeObserver } from "@/hooks/resize-observer";
|
import { useResizeObserver } from "@/hooks/resize-observer";
|
||||||
import useKeyboardListener from "@/hooks/use-keyboard-listener";
|
import useKeyboardListener from "@/hooks/use-keyboard-listener";
|
||||||
@ -39,6 +42,7 @@ import {
|
|||||||
FaAngleLeft,
|
FaAngleLeft,
|
||||||
FaAngleRight,
|
FaAngleRight,
|
||||||
FaAngleUp,
|
FaAngleUp,
|
||||||
|
FaCog,
|
||||||
FaCompress,
|
FaCompress,
|
||||||
FaExpand,
|
FaExpand,
|
||||||
FaMicrophone,
|
FaMicrophone,
|
||||||
@ -47,7 +51,13 @@ import {
|
|||||||
import { GiSpeaker, GiSpeakerOff } from "react-icons/gi";
|
import { GiSpeaker, GiSpeakerOff } from "react-icons/gi";
|
||||||
import { HiViewfinderCircle } from "react-icons/hi2";
|
import { HiViewfinderCircle } from "react-icons/hi2";
|
||||||
import { IoMdArrowBack } from "react-icons/io";
|
import { IoMdArrowBack } from "react-icons/io";
|
||||||
import { LuEar, LuEarOff, LuVideo, LuVideoOff } from "react-icons/lu";
|
import {
|
||||||
|
LuEar,
|
||||||
|
LuEarOff,
|
||||||
|
LuPictureInPicture,
|
||||||
|
LuVideo,
|
||||||
|
LuVideoOff,
|
||||||
|
} from "react-icons/lu";
|
||||||
import {
|
import {
|
||||||
MdNoPhotography,
|
MdNoPhotography,
|
||||||
MdPersonOff,
|
MdPersonOff,
|
||||||
@ -70,19 +80,6 @@ export default function LiveCameraView({ camera }: LiveCameraViewProps) {
|
|||||||
const [{ width: windowWidth, height: windowHeight }] =
|
const [{ width: windowWidth, height: windowHeight }] =
|
||||||
useResizeObserver(window);
|
useResizeObserver(window);
|
||||||
|
|
||||||
// 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);
|
|
||||||
|
|
||||||
// click overlay for ptzs
|
// click overlay for ptzs
|
||||||
|
|
||||||
const [clickOverlay, setClickOverlay] = useState(false);
|
const [clickOverlay, setClickOverlay] = useState(false);
|
||||||
@ -122,20 +119,25 @@ export default function LiveCameraView({ camera }: LiveCameraViewProps) {
|
|||||||
[clickOverlayRef, clickOverlay, sendPtz],
|
[clickOverlayRef, clickOverlay, sendPtz],
|
||||||
);
|
);
|
||||||
|
|
||||||
// fullscreen state
|
// fullscreen / pip state
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (mainRef.current == null) {
|
if (mainRef.current == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const listener = () => {
|
const fsListener = () => {
|
||||||
setFullscreen(document.fullscreenElement != null);
|
setFullscreen(document.fullscreenElement != null);
|
||||||
};
|
};
|
||||||
document.addEventListener("fullscreenchange", listener);
|
const pipListener = () => {
|
||||||
|
setPip(document.pictureInPictureElement != null);
|
||||||
|
};
|
||||||
|
document.addEventListener("fullscreenchange", fsListener);
|
||||||
|
document.addEventListener("focusin", pipListener);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
document.removeEventListener("fullscreenchange", listener);
|
document.removeEventListener("fullscreenchange", fsListener);
|
||||||
|
document.removeEventListener("focusin", pipListener);
|
||||||
};
|
};
|
||||||
}, [mainRef]);
|
}, [mainRef]);
|
||||||
|
|
||||||
@ -144,6 +146,7 @@ export default function LiveCameraView({ camera }: LiveCameraViewProps) {
|
|||||||
const [audio, setAudio] = useState(false);
|
const [audio, setAudio] = useState(false);
|
||||||
const [mic, setMic] = useState(false);
|
const [mic, setMic] = useState(false);
|
||||||
const [fullscreen, setFullscreen] = useState(false);
|
const [fullscreen, setFullscreen] = useState(false);
|
||||||
|
const [pip, setPip] = useState(false);
|
||||||
|
|
||||||
const growClassName = useMemo(() => {
|
const growClassName = useMemo(() => {
|
||||||
const aspect = camera.detect.width / camera.detect.height;
|
const aspect = camera.detect.width / camera.detect.height;
|
||||||
@ -246,6 +249,23 @@ export default function LiveCameraView({ camera }: LiveCameraViewProps) {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{!isIOS && (
|
||||||
|
<CameraFeatureToggle
|
||||||
|
className="p-2 md:p-0"
|
||||||
|
variant={fullscreen ? "overlay" : "primary"}
|
||||||
|
Icon={LuPictureInPicture}
|
||||||
|
isActive={pip}
|
||||||
|
title={pip ? "Close" : "Picture in Picture"}
|
||||||
|
onClick={() => {
|
||||||
|
if (!pip) {
|
||||||
|
setPip(true);
|
||||||
|
} else {
|
||||||
|
document.exitPictureInPicture();
|
||||||
|
setPip(false);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{window.isSecureContext && (
|
{window.isSecureContext && (
|
||||||
<CameraFeatureToggle
|
<CameraFeatureToggle
|
||||||
className="p-2 md:p-0"
|
className="p-2 md:p-0"
|
||||||
@ -264,42 +284,11 @@ export default function LiveCameraView({ camera }: LiveCameraViewProps) {
|
|||||||
title={`${audio ? "Disable" : "Enable"} Camera Audio`}
|
title={`${audio ? "Disable" : "Enable"} Camera Audio`}
|
||||||
onClick={() => setAudio(!audio)}
|
onClick={() => setAudio(!audio)}
|
||||||
/>
|
/>
|
||||||
<CameraFeatureToggle
|
<FrigateCameraFeatures
|
||||||
className="p-2 md:p-0"
|
camera={camera.name}
|
||||||
variant={fullscreen ? "overlay" : "primary"}
|
audioDetectEnabled={camera.audio.enabled_in_config}
|
||||||
Icon={detectState == "ON" ? MdPersonSearch : MdPersonOff}
|
fullscreen={fullscreen}
|
||||||
isActive={detectState == "ON"}
|
|
||||||
title={`${detectState == "ON" ? "Disable" : "Enable"} Detect`}
|
|
||||||
onClick={() => sendDetect(detectState == "ON" ? "OFF" : "ON")}
|
|
||||||
/>
|
/>
|
||||||
<CameraFeatureToggle
|
|
||||||
className="p-2 md:p-0"
|
|
||||||
variant={fullscreen ? "overlay" : "primary"}
|
|
||||||
Icon={recordState == "ON" ? LuVideo : LuVideoOff}
|
|
||||||
isActive={recordState == "ON"}
|
|
||||||
title={`${recordState == "ON" ? "Disable" : "Enable"} Recording`}
|
|
||||||
onClick={() => sendRecord(recordState == "ON" ? "OFF" : "ON")}
|
|
||||||
/>
|
|
||||||
<CameraFeatureToggle
|
|
||||||
className="p-2 md:p-0"
|
|
||||||
variant={fullscreen ? "overlay" : "primary"}
|
|
||||||
Icon={snapshotState == "ON" ? MdPhotoCamera : MdNoPhotography}
|
|
||||||
isActive={snapshotState == "ON"}
|
|
||||||
title={`${snapshotState == "ON" ? "Disable" : "Enable"} Snapshots`}
|
|
||||||
onClick={() =>
|
|
||||||
sendSnapshot(snapshotState == "ON" ? "OFF" : "ON")
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
{camera.audio.enabled_in_config && (
|
|
||||||
<CameraFeatureToggle
|
|
||||||
className="p-2 md:p-0"
|
|
||||||
variant={fullscreen ? "overlay" : "primary"}
|
|
||||||
Icon={audioState == "ON" ? LuEar : LuEarOff}
|
|
||||||
isActive={audioState == "ON"}
|
|
||||||
title={`${audioState == "ON" ? "Disable" : "Enable"} Audio Detect`}
|
|
||||||
onClick={() => sendAudio(audioState == "ON" ? "OFF" : "ON")}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
</div>
|
</div>
|
||||||
@ -333,6 +322,7 @@ export default function LiveCameraView({ camera }: LiveCameraViewProps) {
|
|||||||
micEnabled={mic}
|
micEnabled={mic}
|
||||||
iOSCompatFullScreen={isIOS}
|
iOSCompatFullScreen={isIOS}
|
||||||
preferredLiveMode={preferredLiveMode}
|
preferredLiveMode={preferredLiveMode}
|
||||||
|
pip={pip}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{camera.onvif.host != "" && (
|
{camera.onvif.host != "" && (
|
||||||
@ -405,7 +395,7 @@ function PtzControlPanel({
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="absolute left-[50%] -translate-x-[50%] bottom-[10%] flex items-center gap-1">
|
<div className="absolute inset-x-2 md:left-[50%] md:-translate-x-[50%] bottom-[10%] flex flex-wrap md:flex-nowrap justify-center items-center gap-1">
|
||||||
{ptz?.features?.includes("pt") && (
|
{ptz?.features?.includes("pt") && (
|
||||||
<>
|
<>
|
||||||
<Button
|
<Button
|
||||||
@ -532,3 +522,145 @@ function PtzControlPanel({
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type FrigateCameraFeaturesProps = {
|
||||||
|
camera: string;
|
||||||
|
audioDetectEnabled: boolean;
|
||||||
|
fullscreen: boolean;
|
||||||
|
};
|
||||||
|
function FrigateCameraFeatures({
|
||||||
|
camera,
|
||||||
|
audioDetectEnabled,
|
||||||
|
fullscreen,
|
||||||
|
}: FrigateCameraFeaturesProps) {
|
||||||
|
const { payload: detectState, send: sendDetect } = useDetectState(camera);
|
||||||
|
const { payload: recordState, send: sendRecord } = useRecordingsState(camera);
|
||||||
|
const { payload: snapshotState, send: sendSnapshot } =
|
||||||
|
useSnapshotsState(camera);
|
||||||
|
const { payload: audioState, send: sendAudio } = useAudioState(camera);
|
||||||
|
|
||||||
|
// desktop shows icons part of row
|
||||||
|
if (isDesktop) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<CameraFeatureToggle
|
||||||
|
className="p-2 md:p-0"
|
||||||
|
variant={fullscreen ? "overlay" : "primary"}
|
||||||
|
Icon={detectState == "ON" ? MdPersonSearch : MdPersonOff}
|
||||||
|
isActive={detectState == "ON"}
|
||||||
|
title={`${detectState == "ON" ? "Disable" : "Enable"} Detect`}
|
||||||
|
onClick={() => sendDetect(detectState == "ON" ? "OFF" : "ON")}
|
||||||
|
/>
|
||||||
|
<CameraFeatureToggle
|
||||||
|
className="p-2 md:p-0"
|
||||||
|
variant={fullscreen ? "overlay" : "primary"}
|
||||||
|
Icon={recordState == "ON" ? LuVideo : LuVideoOff}
|
||||||
|
isActive={recordState == "ON"}
|
||||||
|
title={`${recordState == "ON" ? "Disable" : "Enable"} Recording`}
|
||||||
|
onClick={() => sendRecord(recordState == "ON" ? "OFF" : "ON")}
|
||||||
|
/>
|
||||||
|
<CameraFeatureToggle
|
||||||
|
className="p-2 md:p-0"
|
||||||
|
variant={fullscreen ? "overlay" : "primary"}
|
||||||
|
Icon={snapshotState == "ON" ? MdPhotoCamera : MdNoPhotography}
|
||||||
|
isActive={snapshotState == "ON"}
|
||||||
|
title={`${snapshotState == "ON" ? "Disable" : "Enable"} Snapshots`}
|
||||||
|
onClick={() => sendSnapshot(snapshotState == "ON" ? "OFF" : "ON")}
|
||||||
|
/>
|
||||||
|
{audioDetectEnabled && (
|
||||||
|
<CameraFeatureToggle
|
||||||
|
className="p-2 md:p-0"
|
||||||
|
variant={fullscreen ? "overlay" : "primary"}
|
||||||
|
Icon={audioState == "ON" ? LuEar : LuEarOff}
|
||||||
|
isActive={audioState == "ON"}
|
||||||
|
title={`${audioState == "ON" ? "Disable" : "Enable"} Audio Detect`}
|
||||||
|
onClick={() => sendAudio(audioState == "ON" ? "OFF" : "ON")}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// mobile doesn't show settings in fullscreen view
|
||||||
|
if (fullscreen) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Drawer>
|
||||||
|
<DrawerTrigger>
|
||||||
|
<CameraFeatureToggle
|
||||||
|
className="p-2"
|
||||||
|
variant="primary"
|
||||||
|
Icon={FaCog}
|
||||||
|
isActive={false}
|
||||||
|
title={`${camera} Settings`}
|
||||||
|
/>
|
||||||
|
</DrawerTrigger>
|
||||||
|
<DrawerContent className="px-2 py-4 flex flex-col gap-3 rounded-2xl">
|
||||||
|
<div className="flex justify-between items-center gap-1">
|
||||||
|
<Label
|
||||||
|
className="w-full mx-2 text-secondary-foreground capitalize cursor-pointer"
|
||||||
|
htmlFor={"camera-detect"}
|
||||||
|
>
|
||||||
|
Object Detection
|
||||||
|
</Label>
|
||||||
|
<Switch
|
||||||
|
id={"camera-detect"}
|
||||||
|
checked={detectState == "ON"}
|
||||||
|
onCheckedChange={() =>
|
||||||
|
sendDetect(detectState == "ON" ? "OFF" : "ON")
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between items-center gap-1">
|
||||||
|
<Label
|
||||||
|
className="w-full mx-2 text-secondary-foreground capitalize cursor-pointer"
|
||||||
|
htmlFor={"camera-record"}
|
||||||
|
>
|
||||||
|
Recording
|
||||||
|
</Label>
|
||||||
|
<Switch
|
||||||
|
id={"camera-record"}
|
||||||
|
checked={recordState == "ON"}
|
||||||
|
onCheckedChange={() =>
|
||||||
|
sendRecord(recordState == "ON" ? "OFF" : "ON")
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between items-center gap-1">
|
||||||
|
<Label
|
||||||
|
className="w-full mx-2 text-secondary-foreground capitalize cursor-pointer"
|
||||||
|
htmlFor={"camera-snapshot"}
|
||||||
|
>
|
||||||
|
Snapshots
|
||||||
|
</Label>
|
||||||
|
<Switch
|
||||||
|
id={"camera-snapshot"}
|
||||||
|
checked={snapshotState == "ON"}
|
||||||
|
onCheckedChange={() =>
|
||||||
|
sendSnapshot(snapshotState == "ON" ? "OFF" : "ON")
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{audioDetectEnabled && (
|
||||||
|
<div className="flex justify-between items-center gap-1">
|
||||||
|
<Label
|
||||||
|
className="w-full mx-2 text-secondary-foreground capitalize cursor-pointer"
|
||||||
|
htmlFor={"camera-audio-detect"}
|
||||||
|
>
|
||||||
|
Audio Detection
|
||||||
|
</Label>
|
||||||
|
<Switch
|
||||||
|
id={"camera-audio-detect"}
|
||||||
|
checked={audioState == "ON"}
|
||||||
|
onCheckedChange={() =>
|
||||||
|
sendAudio(audioState == "ON" ? "OFF" : "ON")
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</DrawerContent>
|
||||||
|
</Drawer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user