diff --git a/web/public/locales/en/views/live.json b/web/public/locales/en/views/live.json
index fea120601..2af399296 100644
--- a/web/public/locales/en/views/live.json
+++ b/web/public/locales/en/views/live.json
@@ -38,6 +38,14 @@
"label": "Zoom PTZ camera out"
}
},
+ "focus": {
+ "in": {
+ "label": "Focus PTZ camera in"
+ },
+ "out": {
+ "label": "Focus PTZ camera out"
+ }
+ },
"frame": {
"center": {
"label": "Click in the frame to center the PTZ camera"
diff --git a/web/src/types/ptz.ts b/web/src/types/ptz.ts
index 1a626972e..21a300b3d 100644
--- a/web/src/types/ptz.ts
+++ b/web/src/types/ptz.ts
@@ -1,4 +1,11 @@
-type PtzFeature = "pt" | "zoom" | "pt-r" | "zoom-r" | "zoom-a" | "pt-r-fov";
+type PtzFeature =
+ | "pt"
+ | "zoom"
+ | "pt-r"
+ | "zoom-r"
+ | "zoom-a"
+ | "pt-r-fov"
+ | "focus";
export type CameraPtzInfo = {
name: string;
diff --git a/web/src/views/live/LiveCameraView.tsx b/web/src/views/live/LiveCameraView.tsx
index 039265f65..949c642b0 100644
--- a/web/src/views/live/LiveCameraView.tsx
+++ b/web/src/views/live/LiveCameraView.tsx
@@ -92,6 +92,8 @@ import {
LuX,
} from "react-icons/lu";
import {
+ MdCenterFocusStrong,
+ MdCenterFocusWeak,
MdClosedCaption,
MdClosedCaptionDisabled,
MdNoPhotography,
@@ -808,10 +810,10 @@ function PtzControlPanel({
sendPtz("MOVE_DOWN");
break;
case "+":
- sendPtz("ZOOM_IN");
+ sendPtz(modifiers.shift ? "FOCUS_IN" : "ZOOM_IN");
break;
case "-":
- sendPtz("ZOOM_OUT");
+ sendPtz(modifiers.shift ? "FOCUS_OUT" : "ZOOM_OUT");
break;
}
},
@@ -922,6 +924,40 @@ function PtzControlPanel({
>
)}
+ {ptz?.features?.includes("focus") && (
+ <>
+ {
+ e.preventDefault();
+ sendPtz("FOCUS_IN");
+ }}
+ onTouchStart={(e) => {
+ e.preventDefault();
+ sendPtz("FOCUS_IN");
+ }}
+ onMouseUp={onStop}
+ onTouchEnd={onStop}
+ >
+
+
+ {
+ e.preventDefault();
+ sendPtz("FOCUS_OUT");
+ }}
+ onTouchStart={(e) => {
+ e.preventDefault();
+ sendPtz("FOCUS_OUT");
+ }}
+ onMouseUp={onStop}
+ onTouchEnd={onStop}
+ >
+
+
+ >
+ )}
{ptz?.features?.includes("pt-r-fov") && (