mirror of
https://github.com/blakeblackshear/frigate.git
synced 2024-12-19 19:06:16 +01:00
Add relative movement by clicking on camera image for supported ptzs (#10629)
This commit is contained in:
parent
63bf986e08
commit
3a9607e59b
@ -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 = ""
|
||||
|
@ -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
|
||||
):
|
||||
|
@ -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>
|
||||
|
Loading…
Reference in New Issue
Block a user