mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-04-05 22:57:40 +03:00
Merge pull request #91 from ibs0d/claude/add-camera-rotation-support-Lg2l9
Add ui.rotate support to RecordingView / HlsVideoPlayer
This commit is contained in:
commit
e0ee08ac15
@ -59,6 +59,7 @@ type HlsVideoPlayerProps = {
|
|||||||
camera?: string;
|
camera?: string;
|
||||||
currentTimeOverride?: number;
|
currentTimeOverride?: number;
|
||||||
transformedOverlay?: ReactNode;
|
transformedOverlay?: ReactNode;
|
||||||
|
rotate?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function HlsVideoPlayer({
|
export default function HlsVideoPlayer({
|
||||||
@ -84,6 +85,7 @@ export default function HlsVideoPlayer({
|
|||||||
camera,
|
camera,
|
||||||
currentTimeOverride,
|
currentTimeOverride,
|
||||||
transformedOverlay,
|
transformedOverlay,
|
||||||
|
rotate,
|
||||||
}: HlsVideoPlayerProps) {
|
}: HlsVideoPlayerProps) {
|
||||||
const { t } = useTranslation("components/player");
|
const { t } = useTranslation("components/player");
|
||||||
const { data: config } = useSWR<FrigateConfig>("config");
|
const { data: config } = useSWR<FrigateConfig>("config");
|
||||||
@ -99,6 +101,33 @@ export default function HlsVideoPlayer({
|
|||||||
const [loadedMetadata, setLoadedMetadata] = useState(false);
|
const [loadedMetadata, setLoadedMetadata] = useState(false);
|
||||||
const [bufferTimeout, setBufferTimeout] = useState<NodeJS.Timeout>();
|
const [bufferTimeout, setBufferTimeout] = useState<NodeJS.Timeout>();
|
||||||
|
|
||||||
|
// rotation support
|
||||||
|
const rotateContainerRef = useRef<HTMLDivElement>(null);
|
||||||
|
const [rotateContainerSize, setRotateContainerSize] = useState({
|
||||||
|
width: 0,
|
||||||
|
height: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!rotate) return;
|
||||||
|
|
||||||
|
const container = rotateContainerRef.current;
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
|
const updateSize = () => {
|
||||||
|
setRotateContainerSize({
|
||||||
|
width: container.clientWidth,
|
||||||
|
height: container.clientHeight,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
updateSize();
|
||||||
|
const resizeObserver = new ResizeObserver(updateSize);
|
||||||
|
resizeObserver.observe(container);
|
||||||
|
|
||||||
|
return () => resizeObserver.disconnect();
|
||||||
|
}, [rotate]);
|
||||||
|
|
||||||
const applyVideoDimensions = useCallback(
|
const applyVideoDimensions = useCallback(
|
||||||
(width: number, height: number) => {
|
(width: number, height: number) => {
|
||||||
if (setFullResolution) {
|
if (setFullResolution) {
|
||||||
@ -388,9 +417,42 @@ export default function HlsVideoPlayer({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
<div
|
||||||
|
ref={rotateContainerRef}
|
||||||
|
className="size-full"
|
||||||
|
style={
|
||||||
|
rotate
|
||||||
|
? { position: "relative" as const, overflow: "hidden" as const }
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={
|
||||||
|
rotate
|
||||||
|
? {
|
||||||
|
position: "absolute" as const,
|
||||||
|
top: "50%",
|
||||||
|
left: "50%",
|
||||||
|
width: rotateContainerSize.height || "100%",
|
||||||
|
height: rotateContainerSize.width || "100%",
|
||||||
|
transform: "translate(-50%, -50%)",
|
||||||
|
}
|
||||||
|
: { width: "100%", height: "100%" }
|
||||||
|
}
|
||||||
|
>
|
||||||
<video
|
<video
|
||||||
ref={videoRef}
|
ref={videoRef}
|
||||||
className={`size-full rounded-lg bg-black md:rounded-2xl ${loadedMetadata ? "" : "invisible"} cursor-pointer`}
|
className={`size-full rounded-lg bg-black md:rounded-2xl ${loadedMetadata ? "" : "invisible"} cursor-pointer`}
|
||||||
|
style={
|
||||||
|
rotate
|
||||||
|
? {
|
||||||
|
transform: "rotate(90deg)",
|
||||||
|
transformOrigin: "center center",
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
preload="auto"
|
preload="auto"
|
||||||
autoPlay
|
autoPlay
|
||||||
controls={!frigateControls}
|
controls={!frigateControls}
|
||||||
@ -508,6 +570,8 @@ export default function HlsVideoPlayer({
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</TransformComponent>
|
</TransformComponent>
|
||||||
</TransformWrapper>
|
</TransformWrapper>
|
||||||
|
|||||||
@ -48,6 +48,7 @@ type DynamicVideoPlayerProps = {
|
|||||||
toggleFullscreen: () => void;
|
toggleFullscreen: () => void;
|
||||||
containerRef?: React.MutableRefObject<HTMLDivElement | null>;
|
containerRef?: React.MutableRefObject<HTMLDivElement | null>;
|
||||||
transformedOverlay?: ReactNode;
|
transformedOverlay?: ReactNode;
|
||||||
|
rotate?: boolean;
|
||||||
};
|
};
|
||||||
export default function DynamicVideoPlayer({
|
export default function DynamicVideoPlayer({
|
||||||
className,
|
className,
|
||||||
@ -67,6 +68,7 @@ export default function DynamicVideoPlayer({
|
|||||||
toggleFullscreen,
|
toggleFullscreen,
|
||||||
containerRef,
|
containerRef,
|
||||||
transformedOverlay,
|
transformedOverlay,
|
||||||
|
rotate,
|
||||||
}: DynamicVideoPlayerProps) {
|
}: DynamicVideoPlayerProps) {
|
||||||
const { t } = useTranslation(["components/player"]);
|
const { t } = useTranslation(["components/player"]);
|
||||||
const apiHost = useApiHost();
|
const apiHost = useApiHost();
|
||||||
@ -322,6 +324,7 @@ export default function DynamicVideoPlayer({
|
|||||||
camera={contextCamera || camera}
|
camera={contextCamera || camera}
|
||||||
currentTimeOverride={currentTime}
|
currentTimeOverride={currentTime}
|
||||||
transformedOverlay={transformedOverlay}
|
transformedOverlay={transformedOverlay}
|
||||||
|
rotate={rotate}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<PreviewPlayer
|
<PreviewPlayer
|
||||||
|
|||||||
@ -379,17 +379,21 @@ export function RecordingView({
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let ratio: number;
|
||||||
if (cam == mainCamera && fullResolution.width && fullResolution.height) {
|
if (cam == mainCamera && fullResolution.width && fullResolution.height) {
|
||||||
return fullResolution.width / fullResolution.height;
|
ratio = fullResolution.width / fullResolution.height;
|
||||||
|
} else {
|
||||||
|
const camera = config.cameras[cam];
|
||||||
|
|
||||||
|
if (!camera) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
ratio = camera.detect.width / camera.detect.height;
|
||||||
}
|
}
|
||||||
|
|
||||||
const camera = config.cameras[cam];
|
const camera = config.cameras[cam];
|
||||||
|
return camera?.ui?.rotate ? 1 / ratio : ratio;
|
||||||
if (!camera) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return camera.detect.width / camera.detect.height;
|
|
||||||
},
|
},
|
||||||
[config, fullResolution, mainCamera],
|
[config, fullResolution, mainCamera],
|
||||||
);
|
);
|
||||||
@ -811,6 +815,7 @@ export function RecordingView({
|
|||||||
<DynamicVideoPlayer
|
<DynamicVideoPlayer
|
||||||
className={grow}
|
className={grow}
|
||||||
camera={mainCamera}
|
camera={mainCamera}
|
||||||
|
rotate={config?.cameras[mainCamera]?.ui?.rotate}
|
||||||
timeRange={currentTimeRange}
|
timeRange={currentTimeRange}
|
||||||
cameraPreviews={allPreviews ?? []}
|
cameraPreviews={allPreviews ?? []}
|
||||||
startTimestamp={playbackStart}
|
startTimestamp={playbackStart}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user