mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-04-05 22:57:40 +03:00
Add ui.rotate support to RecordingView / HlsVideoPlayer
- HlsVideoPlayer: add rotate prop; when true, wraps <video> in a
ResizeObserver-tracked container that swaps width/height and applies
rotate(90deg) transform, mirroring the MsePlayer grid-rotation logic
- DynamicVideoPlayer: thread rotate prop through to HlsVideoPlayer
- RecordingView: invert getCameraAspect ratio (1/ratio) for cameras
with ui.rotate so the outer container gets portrait proportions;
pass rotate={camera.ui?.rotate} to DynamicVideoPlayer
https://claude.ai/code/session_01CDLHQPGpf8w44jpsG8g8nM
This commit is contained in:
parent
25a869eb43
commit
4a35ce1f70
@ -59,6 +59,7 @@ type HlsVideoPlayerProps = {
|
||||
camera?: string;
|
||||
currentTimeOverride?: number;
|
||||
transformedOverlay?: ReactNode;
|
||||
rotate?: boolean;
|
||||
};
|
||||
|
||||
export default function HlsVideoPlayer({
|
||||
@ -84,6 +85,7 @@ export default function HlsVideoPlayer({
|
||||
camera,
|
||||
currentTimeOverride,
|
||||
transformedOverlay,
|
||||
rotate,
|
||||
}: HlsVideoPlayerProps) {
|
||||
const { t } = useTranslation("components/player");
|
||||
const { data: config } = useSWR<FrigateConfig>("config");
|
||||
@ -99,6 +101,33 @@ export default function HlsVideoPlayer({
|
||||
const [loadedMetadata, setLoadedMetadata] = useState(false);
|
||||
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(
|
||||
(width: number, height: number) => {
|
||||
if (setFullResolution) {
|
||||
@ -388,9 +417,42 @@ export default function HlsVideoPlayer({
|
||||
/>
|
||||
</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
|
||||
ref={videoRef}
|
||||
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"
|
||||
autoPlay
|
||||
controls={!frigateControls}
|
||||
@ -508,6 +570,8 @@ export default function HlsVideoPlayer({
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</TransformComponent>
|
||||
</TransformWrapper>
|
||||
|
||||
@ -48,6 +48,7 @@ type DynamicVideoPlayerProps = {
|
||||
toggleFullscreen: () => void;
|
||||
containerRef?: React.MutableRefObject<HTMLDivElement | null>;
|
||||
transformedOverlay?: ReactNode;
|
||||
rotate?: boolean;
|
||||
};
|
||||
export default function DynamicVideoPlayer({
|
||||
className,
|
||||
@ -67,6 +68,7 @@ export default function DynamicVideoPlayer({
|
||||
toggleFullscreen,
|
||||
containerRef,
|
||||
transformedOverlay,
|
||||
rotate,
|
||||
}: DynamicVideoPlayerProps) {
|
||||
const { t } = useTranslation(["components/player"]);
|
||||
const apiHost = useApiHost();
|
||||
@ -322,6 +324,7 @@ export default function DynamicVideoPlayer({
|
||||
camera={contextCamera || camera}
|
||||
currentTimeOverride={currentTime}
|
||||
transformedOverlay={transformedOverlay}
|
||||
rotate={rotate}
|
||||
/>
|
||||
)}
|
||||
<PreviewPlayer
|
||||
|
||||
@ -379,17 +379,21 @@ export function RecordingView({
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let ratio: number;
|
||||
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];
|
||||
|
||||
if (!camera) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return camera.detect.width / camera.detect.height;
|
||||
return camera?.ui?.rotate ? 1 / ratio : ratio;
|
||||
},
|
||||
[config, fullResolution, mainCamera],
|
||||
);
|
||||
@ -811,6 +815,7 @@ export function RecordingView({
|
||||
<DynamicVideoPlayer
|
||||
className={grow}
|
||||
camera={mainCamera}
|
||||
rotate={config?.cameras[mainCamera]?.ui?.rotate}
|
||||
timeRange={currentTimeRange}
|
||||
cameraPreviews={allPreviews ?? []}
|
||||
startTimestamp={playbackStart}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user