mirror of
https://github.com/blakeblackshear/frigate.git
synced 2024-11-21 19:07:46 +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():
|
if "preset" in payload.lower():
|
||||||
command = OnvifCommandEnum.preset
|
command = OnvifCommandEnum.preset
|
||||||
param = payload.lower()[payload.index("_") + 1 :]
|
param = payload.lower()[payload.index("_") + 1 :]
|
||||||
|
elif "move_relative" in payload.lower():
|
||||||
|
command = OnvifCommandEnum.move_relative
|
||||||
|
param = payload.lower()[payload.index("_") + 1 :]
|
||||||
else:
|
else:
|
||||||
command = OnvifCommandEnum[payload.lower()]
|
command = OnvifCommandEnum[payload.lower()]
|
||||||
param = ""
|
param = ""
|
||||||
|
@ -21,6 +21,7 @@ class OnvifCommandEnum(str, Enum):
|
|||||||
init = "init"
|
init = "init"
|
||||||
move_down = "move_down"
|
move_down = "move_down"
|
||||||
move_left = "move_left"
|
move_left = "move_left"
|
||||||
|
move_relative = "move_relative"
|
||||||
move_right = "move_right"
|
move_right = "move_right"
|
||||||
move_up = "move_up"
|
move_up = "move_up"
|
||||||
preset = "preset"
|
preset = "preset"
|
||||||
@ -536,6 +537,9 @@ class OnvifController:
|
|||||||
self._stop(camera_name)
|
self._stop(camera_name)
|
||||||
elif command == OnvifCommandEnum.preset:
|
elif command == OnvifCommandEnum.preset:
|
||||||
self._move_to_preset(camera_name, param)
|
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 (
|
elif (
|
||||||
command == OnvifCommandEnum.zoom_in or command == OnvifCommandEnum.zoom_out
|
command == OnvifCommandEnum.zoom_in or command == OnvifCommandEnum.zoom_out
|
||||||
):
|
):
|
||||||
|
@ -45,6 +45,7 @@ import {
|
|||||||
FaMicrophoneSlash,
|
FaMicrophoneSlash,
|
||||||
} from "react-icons/fa";
|
} from "react-icons/fa";
|
||||||
import { GiSpeaker, GiSpeakerOff } from "react-icons/gi";
|
import { GiSpeaker, GiSpeakerOff } from "react-icons/gi";
|
||||||
|
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, LuVideo, LuVideoOff } from "react-icons/lu";
|
||||||
import {
|
import {
|
||||||
@ -82,6 +83,45 @@ export default function LiveCameraView({ camera }: LiveCameraViewProps) {
|
|||||||
);
|
);
|
||||||
const { payload: audioState, send: sendAudio } = useAudioState(camera.name);
|
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
|
// fullscreen state
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -277,6 +317,8 @@ export default function LiveCameraView({ camera }: LiveCameraViewProps) {
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={`flex flex-col justify-center items-center ${growClassName}`}
|
className={`flex flex-col justify-center items-center ${growClassName}`}
|
||||||
|
ref={clickOverlayRef}
|
||||||
|
onClick={handleOverlayClick}
|
||||||
style={{
|
style={{
|
||||||
aspectRatio: aspectRatio,
|
aspectRatio: aspectRatio,
|
||||||
}}
|
}}
|
||||||
@ -293,14 +335,28 @@ export default function LiveCameraView({ camera }: LiveCameraViewProps) {
|
|||||||
preferredLiveMode={preferredLiveMode}
|
preferredLiveMode={preferredLiveMode}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{camera.onvif.host != "" && <PtzControlPanel camera={camera.name} />}
|
{camera.onvif.host != "" && (
|
||||||
|
<PtzControlPanel
|
||||||
|
camera={camera.name}
|
||||||
|
clickOverlay={clickOverlay}
|
||||||
|
setClickOverlay={setClickOverlay}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</TransformComponent>
|
</TransformComponent>
|
||||||
</div>
|
</div>
|
||||||
</TransformWrapper>
|
</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 { data: ptz } = useSWR<CameraPtzInfo>(`${camera}/ptz/info`);
|
||||||
|
|
||||||
const { send: sendPtz } = usePtzCommand(camera);
|
const { send: sendPtz } = usePtzCommand(camera);
|
||||||
@ -442,6 +498,16 @@ function PtzControlPanel({ camera }: { camera: string }) {
|
|||||||
</Button>
|
</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 && (
|
{(ptz?.presets?.length ?? 0) > 0 && (
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
|
Loading…
Reference in New Issue
Block a user