2024-03-01 22:58:03 +03:00
|
|
|
import { usePtzCommand } from "@/api/ws";
|
2024-03-01 20:06:01 +03:00
|
|
|
import LivePlayer from "@/components/player/LivePlayer";
|
|
|
|
|
import { Button } from "@/components/ui/button";
|
2024-03-01 23:01:36 +03:00
|
|
|
import {
|
|
|
|
|
DropdownMenu,
|
|
|
|
|
DropdownMenuContent,
|
|
|
|
|
DropdownMenuItem,
|
|
|
|
|
DropdownMenuTrigger,
|
|
|
|
|
} from "@/components/ui/dropdown-menu";
|
2024-03-01 22:58:03 +03:00
|
|
|
import useKeyboardListener from "@/hooks/use-keyboard-listener";
|
2024-03-01 20:06:01 +03:00
|
|
|
import { CameraConfig } from "@/types/frigateConfig";
|
2024-03-01 22:58:03 +03:00
|
|
|
import { CameraPtzInfo } from "@/types/ptz";
|
|
|
|
|
import React, { useCallback, useMemo } from "react";
|
2024-03-01 20:06:01 +03:00
|
|
|
import { isSafari } from "react-device-detect";
|
2024-03-01 22:58:03 +03:00
|
|
|
import { BsThreeDotsVertical } from "react-icons/bs";
|
|
|
|
|
import {
|
|
|
|
|
FaAngleDown,
|
|
|
|
|
FaAngleLeft,
|
|
|
|
|
FaAngleRight,
|
|
|
|
|
FaAngleUp,
|
|
|
|
|
} from "react-icons/fa";
|
2024-03-01 20:06:01 +03:00
|
|
|
import { IoMdArrowBack } from "react-icons/io";
|
2024-03-01 22:58:03 +03:00
|
|
|
import { MdZoomIn, MdZoomOut } from "react-icons/md";
|
2024-03-01 20:06:01 +03:00
|
|
|
import { useNavigate } from "react-router-dom";
|
2024-03-01 22:58:03 +03:00
|
|
|
import useSWR from "swr";
|
2024-03-01 20:06:01 +03:00
|
|
|
|
|
|
|
|
type LiveCameraViewProps = {
|
|
|
|
|
camera: CameraConfig;
|
|
|
|
|
};
|
|
|
|
|
export default function LiveCameraView({ camera }: LiveCameraViewProps) {
|
|
|
|
|
const navigate = useNavigate();
|
|
|
|
|
|
|
|
|
|
const growClassName = useMemo(() => {
|
|
|
|
|
if (camera.detect.width / camera.detect.height > 2) {
|
|
|
|
|
return "absolute left-2 right-2 top-[50%] -translate-y-[50%]";
|
|
|
|
|
} else {
|
|
|
|
|
return "absolute top-2 bottom-2 left-[50%] -translate-x-[50%]";
|
|
|
|
|
}
|
|
|
|
|
}, [camera]);
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="size-full flex flex-col">
|
|
|
|
|
<div className="w-full h-12 flex items-center justify-between">
|
|
|
|
|
<Button className="rounded-lg" onClick={() => navigate(-1)}>
|
|
|
|
|
<IoMdArrowBack className="size-5 mr-[10px]" />
|
|
|
|
|
Back
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="relative size-full">
|
|
|
|
|
<div
|
|
|
|
|
className={growClassName}
|
|
|
|
|
style={{ aspectRatio: camera.detect.width / camera.detect.height }}
|
|
|
|
|
>
|
|
|
|
|
<LivePlayer
|
|
|
|
|
key={camera.name}
|
|
|
|
|
className="size-full"
|
|
|
|
|
windowVisible
|
|
|
|
|
showStillWithoutActivity={false}
|
|
|
|
|
cameraConfig={camera}
|
|
|
|
|
preferredLiveMode={isSafari ? "webrtc" : "mse"}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
2024-03-01 22:58:03 +03:00
|
|
|
{camera.onvif.host != "" && <PtzControlPanel camera={camera.name} />}
|
2024-03-01 20:06:01 +03:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
2024-03-01 22:58:03 +03:00
|
|
|
|
|
|
|
|
function PtzControlPanel({ camera }: { camera: string }) {
|
|
|
|
|
const { data: ptz } = useSWR<CameraPtzInfo>(`${camera}/ptz/info`);
|
|
|
|
|
|
|
|
|
|
const { payload: _, send: sendPtz } = usePtzCommand(camera);
|
|
|
|
|
|
|
|
|
|
const onStop = useCallback(
|
|
|
|
|
(e: React.SyntheticEvent) => {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
sendPtz("STOP");
|
|
|
|
|
},
|
|
|
|
|
[sendPtz],
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
useKeyboardListener(
|
|
|
|
|
["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown", "+", "-"],
|
|
|
|
|
(key, down, repeat) => {
|
|
|
|
|
if (repeat) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!down) {
|
|
|
|
|
sendPtz("STOP");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch (key) {
|
|
|
|
|
case "ArrowLeft":
|
|
|
|
|
sendPtz("MOVE_LEFT");
|
|
|
|
|
break;
|
|
|
|
|
case "ArrowRight":
|
|
|
|
|
sendPtz("MOVE_RIGHT");
|
|
|
|
|
break;
|
|
|
|
|
case "ArrowUp":
|
|
|
|
|
sendPtz("MOVE_UP");
|
|
|
|
|
break;
|
|
|
|
|
case "ArrowDown":
|
|
|
|
|
sendPtz("MOVE_DOWN");
|
|
|
|
|
break;
|
|
|
|
|
case "+":
|
|
|
|
|
sendPtz("ZOOM_IN");
|
|
|
|
|
break;
|
|
|
|
|
case "-":
|
|
|
|
|
sendPtz("ZOOM_OUT");
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="absolute left-[50%] -translate-x-[50%] bottom-[10%] flex items-center gap-1">
|
|
|
|
|
{ptz?.features?.includes("pt") && (
|
|
|
|
|
<>
|
|
|
|
|
<Button
|
|
|
|
|
onMouseDown={(e) => {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
sendPtz("MOVE_LEFT");
|
|
|
|
|
}}
|
|
|
|
|
onTouchStart={(e) => {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
sendPtz("MOVE_LEFT");
|
|
|
|
|
}}
|
|
|
|
|
onMouseUp={onStop}
|
|
|
|
|
onTouchEnd={onStop}
|
|
|
|
|
>
|
|
|
|
|
<FaAngleLeft />
|
|
|
|
|
</Button>
|
|
|
|
|
<Button
|
|
|
|
|
onMouseDown={(e) => {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
sendPtz("MOVE_UP");
|
|
|
|
|
}}
|
|
|
|
|
onTouchStart={(e) => {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
sendPtz("MOVE_UP");
|
|
|
|
|
}}
|
|
|
|
|
onMouseUp={onStop}
|
|
|
|
|
onTouchEnd={onStop}
|
|
|
|
|
>
|
|
|
|
|
<FaAngleUp />
|
|
|
|
|
</Button>
|
|
|
|
|
<Button
|
|
|
|
|
onMouseDown={(e) => {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
sendPtz("MOVE_DOWN");
|
|
|
|
|
}}
|
|
|
|
|
onTouchStart={(e) => {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
sendPtz("MOVE_DOWN");
|
|
|
|
|
}}
|
|
|
|
|
onMouseUp={onStop}
|
|
|
|
|
onTouchEnd={onStop}
|
|
|
|
|
>
|
|
|
|
|
<FaAngleDown />
|
|
|
|
|
</Button>
|
|
|
|
|
<Button
|
|
|
|
|
onMouseDown={(e) => {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
sendPtz("MOVE_RIGHT");
|
|
|
|
|
}}
|
|
|
|
|
onTouchStart={(e) => {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
sendPtz("MOVE_RIGHT");
|
|
|
|
|
}}
|
|
|
|
|
onMouseUp={onStop}
|
|
|
|
|
onTouchEnd={onStop}
|
|
|
|
|
>
|
|
|
|
|
<FaAngleRight />
|
|
|
|
|
</Button>
|
|
|
|
|
</>
|
|
|
|
|
)}
|
|
|
|
|
{ptz?.features?.includes("zoom") && (
|
|
|
|
|
<>
|
|
|
|
|
<Button
|
|
|
|
|
onMouseDown={(e) => {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
sendPtz("ZOOM_IN");
|
|
|
|
|
}}
|
|
|
|
|
onTouchStart={(e) => {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
sendPtz("ZOOM_IN");
|
|
|
|
|
}}
|
|
|
|
|
onMouseUp={onStop}
|
|
|
|
|
onTouchEnd={onStop}
|
|
|
|
|
>
|
|
|
|
|
<MdZoomIn />
|
|
|
|
|
</Button>
|
|
|
|
|
<Button
|
|
|
|
|
onMouseDown={(e) => {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
sendPtz("ZOOM_OUT");
|
|
|
|
|
}}
|
|
|
|
|
onTouchStart={(e) => {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
sendPtz("ZOOM_OUT");
|
|
|
|
|
}}
|
|
|
|
|
onMouseUp={onStop}
|
|
|
|
|
onTouchEnd={onStop}
|
|
|
|
|
>
|
|
|
|
|
<MdZoomOut />
|
|
|
|
|
</Button>
|
|
|
|
|
</>
|
|
|
|
|
)}
|
|
|
|
|
{(ptz?.presets?.length ?? 0) > 0 && (
|
2024-03-01 23:01:36 +03:00
|
|
|
<DropdownMenu>
|
|
|
|
|
<DropdownMenuTrigger asChild>
|
|
|
|
|
<Button>
|
|
|
|
|
<BsThreeDotsVertical />
|
|
|
|
|
</Button>
|
|
|
|
|
</DropdownMenuTrigger>
|
|
|
|
|
<DropdownMenuContent>
|
|
|
|
|
{ptz?.presets.map((preset) => {
|
|
|
|
|
return (
|
|
|
|
|
<DropdownMenuItem onSelect={() => sendPtz(`preset_${preset}`)}>
|
|
|
|
|
{preset}
|
|
|
|
|
</DropdownMenuItem>
|
|
|
|
|
);
|
|
|
|
|
})}
|
|
|
|
|
</DropdownMenuContent>
|
|
|
|
|
</DropdownMenu>
|
2024-03-01 22:58:03 +03:00
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|