Add support for full screen mode

This commit is contained in:
Nicolas Mowen 2024-03-02 09:27:05 -07:00
parent f740090c92
commit 38d7628c68

View File

@ -18,7 +18,13 @@ import { TooltipProvider } from "@/components/ui/tooltip";
import useKeyboardListener from "@/hooks/use-keyboard-listener"; import useKeyboardListener from "@/hooks/use-keyboard-listener";
import { CameraConfig } from "@/types/frigateConfig"; import { CameraConfig } from "@/types/frigateConfig";
import { CameraPtzInfo } from "@/types/ptz"; import { CameraPtzInfo } from "@/types/ptz";
import React, { useCallback, useMemo, useState } from "react"; import React, {
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from "react";
import { import {
isDesktop, isDesktop,
isMobile, isMobile,
@ -31,6 +37,7 @@ import {
FaAngleLeft, FaAngleLeft,
FaAngleRight, FaAngleRight,
FaAngleUp, FaAngleUp,
FaExpand,
} from "react-icons/fa"; } from "react-icons/fa";
import { GiSpeaker, GiSpeakerOff } from "react-icons/gi"; import { GiSpeaker, GiSpeakerOff } from "react-icons/gi";
import { IoMdArrowBack } from "react-icons/io"; import { IoMdArrowBack } from "react-icons/io";
@ -52,6 +59,7 @@ type LiveCameraViewProps = {
export default function LiveCameraView({ camera }: LiveCameraViewProps) { export default function LiveCameraView({ camera }: LiveCameraViewProps) {
const navigate = useNavigate(); const navigate = useNavigate();
const { isPortrait } = useMobileOrientation(); const { isPortrait } = useMobileOrientation();
const mainRef = useRef<HTMLDivElement | null>(null);
// camera features // camera features
@ -66,43 +74,97 @@ export default function LiveCameraView({ camera }: LiveCameraViewProps) {
); );
const { payload: audioState, send: sendAudio } = useAudioState(camera.name); const { payload: audioState, send: sendAudio } = useAudioState(camera.name);
// fullscreen state
useEffect(() => {
if (mainRef.current == null) {
return;
}
const listener = () => {
setFullscreen(document.fullscreenElement != null);
};
document.addEventListener("fullscreenchange", listener);
return () => {
document.removeEventListener("fullscreenchange", listener);
};
}, [mainRef]);
// playback state // playback state
const [audio, setAudio] = useState(false); const [audio, setAudio] = useState(false);
const [fullscreen, setFullscreen] = useState(false);
const growClassName = useMemo(() => { const growClassName = useMemo(() => {
const aspect = camera.detect.width / camera.detect.height;
if (fullscreen) {
if (aspect > 16 / 9) {
return "absolute inset-x-0 top-[50%] -translate-y-[50%]";
} else {
return "absolute inset-y-0 left-[50%] -translate-x-[50%]";
}
}
if (isMobile) { if (isMobile) {
if (isPortrait) { if (isPortrait) {
return "absolute left-2 right-2 top-[50%] -translate-y-[50%]"; return "absolute left-2 right-2 top-[50%] -translate-y-[50%]";
} else { } else {
return "absolute top-2 bottom-2 left-[50%] -translate-x-[50%]"; return "absolute top-2 bottom-2 left-[50%] -translate-x-[50%]";
} }
} else if (camera.detect.width / camera.detect.height > 2) { } else if (aspect > 2) {
return "absolute left-2 right-2 top-[50%] -translate-y-[50%]"; return "absolute left-2 right-2 top-[50%] -translate-y-[50%]";
} else { } else {
return "absolute top-2 bottom-2 left-[50%] -translate-x-[50%]"; return "absolute top-2 bottom-2 left-[50%] -translate-x-[50%]";
} }
}, [camera, isPortrait]); }, [camera, fullscreen, isPortrait]);
return ( return (
<div <div
className={`size-full flex flex-col ${isMobile ? "landscape:flex-row" : ""}`} ref={mainRef}
className={
fullscreen
? `fixed inset-0 bg-black z-30`
: `size-full flex flex-col ${isMobile ? "landscape:flex-row" : ""}`
}
> >
<div <div
className={`w-full h-12 flex flex-row items-center justify-between ${isMobile ? "landscape:w-min landscape:h-full landscape:flex-col" : ""}`} className={
fullscreen
? `absolute right-24 top-1 z-40`
: `w-full h-12 flex flex-row items-center justify-between ${isMobile ? "landscape:w-min landscape:h-full landscape:flex-col" : ""}`
}
> >
<Button {!fullscreen ? (
className={`rounded-lg ${isMobile ? "ml-2" : "ml-0"}`} <Button
size={isMobile ? "icon" : "default"} className={`rounded-lg ${isMobile ? "ml-2" : "ml-0"}`}
onClick={() => navigate(-1)} size={isMobile ? "icon" : "default"}
> onClick={() => navigate(-1)}
<IoMdArrowBack className="size-5 lg:mr-[10px]" /> >
{isDesktop && "Back"} <IoMdArrowBack className="size-5 lg:mr-[10px]" />
</Button> {isDesktop && "Back"}
</Button>
) : (
<div />
)}
<TooltipProvider> <TooltipProvider>
<div <div
className={`flex flex-row items-center gap-2 mr-1 *:rounded-lg ${isMobile ? "landscape:flex-col" : ""}`} className={`flex flex-row items-center gap-2 mr-1 *:rounded-lg ${isMobile ? "landscape:flex-col" : ""}`}
> >
<CameraFeatureToggle
className="p-2 md:p-0"
Icon={fullscreen ? FaExpand : FaExpand}
isActive={fullscreen}
title={fullscreen ? "Fullscreen" : "Close"}
onClick={() => {
if (fullscreen) {
document.exitFullscreen();
} else {
mainRef.current?.requestFullscreen();
}
}}
/>
<CameraFeatureToggle <CameraFeatureToggle
className="p-2 md:p-0" className="p-2 md:p-0"
Icon={audio ? GiSpeaker : GiSpeakerOff} Icon={audio ? GiSpeaker : GiSpeakerOff}
@ -143,7 +205,6 @@ export default function LiveCameraView({ camera }: LiveCameraViewProps) {
</div> </div>
</TooltipProvider> </TooltipProvider>
</div> </div>
<div className="relative size-full"> <div className="relative size-full">
<div <div
className={growClassName} className={growClassName}
@ -151,7 +212,7 @@ export default function LiveCameraView({ camera }: LiveCameraViewProps) {
> >
<LivePlayer <LivePlayer
key={camera.name} key={camera.name}
className="size-full" className={`size-full ${fullscreen ? "*:rounded-none" : ""}`}
windowVisible windowVisible
showStillWithoutActivity={false} showStillWithoutActivity={false}
cameraConfig={camera} cameraConfig={camera}