Add relative movement by clicking on camera image for supported ptzs (#10629)

This commit is contained in:
Josh Hawkins 2024-03-23 11:53:33 -05:00 committed by GitHub
parent 63bf986e08
commit 3a9607e59b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 75 additions and 2 deletions

View File

@ -315,6 +315,9 @@ class Dispatcher:
if "preset" in payload.lower():
command = OnvifCommandEnum.preset
param = payload.lower()[payload.index("_") + 1 :]
elif "move_relative" in payload.lower():
command = OnvifCommandEnum.move_relative
param = payload.lower()[payload.index("_") + 1 :]
else:
command = OnvifCommandEnum[payload.lower()]
param = ""

View File

@ -21,6 +21,7 @@ class OnvifCommandEnum(str, Enum):
init = "init"
move_down = "move_down"
move_left = "move_left"
move_relative = "move_relative"
move_right = "move_right"
move_up = "move_up"
preset = "preset"
@ -536,6 +537,9 @@ class OnvifController:
self._stop(camera_name)
elif command == OnvifCommandEnum.preset:
self._move_to_preset(camera_name, param)
elif command == OnvifCommandEnum.move_relative:
_, pan, tilt = param.split("_")
self._move_relative(camera_name, float(pan), float(tilt), 0, 1)
elif (
command == OnvifCommandEnum.zoom_in or command == OnvifCommandEnum.zoom_out
):

View File

@ -45,6 +45,7 @@ import {
FaMicrophoneSlash,
} from "react-icons/fa";
import { GiSpeaker, GiSpeakerOff } from "react-icons/gi";
import { HiViewfinderCircle } from "react-icons/hi2";
import { IoMdArrowBack } from "react-icons/io";
import { LuEar, LuEarOff, LuVideo, LuVideoOff } from "react-icons/lu";
import {
@ -82,6 +83,45 @@ export default function LiveCameraView({ camera }: LiveCameraViewProps) {
);
const { payload: audioState, send: sendAudio } = useAudioState(camera.name);
// click overlay for ptzs
const [clickOverlay, setClickOverlay] = useState(false);
const clickOverlayRef = useRef<HTMLDivElement>(null);
const { send: sendPtz } = usePtzCommand(camera.name);
const handleOverlayClick = useCallback(
(
e: React.MouseEvent<HTMLDivElement> | React.TouchEvent<HTMLDivElement>,
) => {
if (!clickOverlay) {
return;
}
let clientX;
let clientY;
if (isMobile && e.nativeEvent instanceof TouchEvent) {
clientX = e.nativeEvent.touches[0].clientX;
clientY = e.nativeEvent.touches[0].clientY;
} else if (e.nativeEvent instanceof MouseEvent) {
clientX = e.nativeEvent.clientX;
clientY = e.nativeEvent.clientY;
}
if (clickOverlayRef.current && clientX && clientY) {
const rect = clickOverlayRef.current.getBoundingClientRect();
const normalizedX = (clientX - rect.left) / rect.width;
const normalizedY = (clientY - rect.top) / rect.height;
const pan = (normalizedX - 0.5) * 2;
const tilt = (0.5 - normalizedY) * 2;
sendPtz(`move_relative_${pan}_${tilt}`);
}
},
[clickOverlayRef, clickOverlay, sendPtz],
);
// fullscreen state
useEffect(() => {
@ -277,6 +317,8 @@ export default function LiveCameraView({ camera }: LiveCameraViewProps) {
>
<div
className={`flex flex-col justify-center items-center ${growClassName}`}
ref={clickOverlayRef}
onClick={handleOverlayClick}
style={{
aspectRatio: aspectRatio,
}}
@ -293,14 +335,28 @@ export default function LiveCameraView({ camera }: LiveCameraViewProps) {
preferredLiveMode={preferredLiveMode}
/>
</div>
{camera.onvif.host != "" && <PtzControlPanel camera={camera.name} />}
{camera.onvif.host != "" && (
<PtzControlPanel
camera={camera.name}
clickOverlay={clickOverlay}
setClickOverlay={setClickOverlay}
/>
)}
</TransformComponent>
</div>
</TransformWrapper>
);
}
function PtzControlPanel({ camera }: { camera: string }) {
function PtzControlPanel({
camera,
clickOverlay,
setClickOverlay,
}: {
camera: string;
clickOverlay: boolean;
setClickOverlay: React.Dispatch<React.SetStateAction<boolean>>;
}) {
const { data: ptz } = useSWR<CameraPtzInfo>(`${camera}/ptz/info`);
const { send: sendPtz } = usePtzCommand(camera);
@ -442,6 +498,16 @@ function PtzControlPanel({ camera }: { camera: string }) {
</Button>
</>
)}
{ptz?.features?.includes("pt-r-fov") && (
<>
<Button
className={`${clickOverlay ? "text-selected" : "text-primary-foreground"}`}
onClick={() => setClickOverlay(!clickOverlay)}
>
<HiViewfinderCircle />
</Button>
</>
)}
{(ptz?.presets?.length ?? 0) > 0 && (
<DropdownMenu>
<DropdownMenuTrigger asChild>