stay in fullscreen when navigating to a camera (#11666)

This commit is contained in:
Josh Hawkins 2024-05-31 07:58:33 -05:00 committed by GitHub
parent 8c325801ef
commit a3d116e70e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 102 additions and 100 deletions

View File

@ -39,7 +39,7 @@ type HlsVideoPlayerProps = {
onPlaying?: () => void; onPlaying?: () => void;
setFullResolution?: React.Dispatch<React.SetStateAction<VideoResolutionType>>; setFullResolution?: React.Dispatch<React.SetStateAction<VideoResolutionType>>;
onUploadFrame?: (playTime: number) => Promise<AxiosResponse> | undefined; onUploadFrame?: (playTime: number) => Promise<AxiosResponse> | undefined;
setFullscreen?: (full: boolean) => void; toggleFullscreen?: () => void;
}; };
export default function HlsVideoPlayer({ export default function HlsVideoPlayer({
videoRef, videoRef,
@ -53,7 +53,7 @@ export default function HlsVideoPlayer({
onPlaying, onPlaying,
setFullResolution, setFullResolution,
onUploadFrame, onUploadFrame,
setFullscreen, toggleFullscreen,
}: HlsVideoPlayerProps) { }: HlsVideoPlayerProps) {
const { data: config } = useSWR<FrigateConfig>("config"); const { data: config } = useSWR<FrigateConfig>("config");
@ -224,7 +224,7 @@ export default function HlsVideoPlayer({
} }
}} }}
fullscreen={fullscreen} fullscreen={fullscreen}
setFullscreen={setFullscreen} toggleFullscreen={toggleFullscreen}
/> />
<TransformComponent <TransformComponent
wrapperStyle={{ wrapperStyle={{

View File

@ -68,7 +68,7 @@ type VideoControlsProps = {
onSeek: (diff: number) => void; onSeek: (diff: number) => void;
onSetPlaybackRate: (rate: number) => void; onSetPlaybackRate: (rate: number) => void;
onUploadFrame?: () => void; onUploadFrame?: () => void;
setFullscreen?: (full: boolean) => void; toggleFullscreen?: () => void;
}; };
export default function VideoControls({ export default function VideoControls({
className, className,
@ -88,7 +88,7 @@ export default function VideoControls({
onSeek, onSeek,
onSetPlaybackRate, onSetPlaybackRate,
onUploadFrame, onUploadFrame,
setFullscreen, toggleFullscreen,
}: VideoControlsProps) { }: VideoControlsProps) {
// layout // layout
@ -160,8 +160,8 @@ export default function VideoControls({
} }
break; break;
case "f": case "f":
if (setFullscreen && down && !repeat) { if (toggleFullscreen && down && !repeat) {
setFullscreen(!fullscreen); toggleFullscreen();
} }
break; break;
case "m": case "m":
@ -178,7 +178,7 @@ export default function VideoControls({
}, },
// only update when preview only changes // only update when preview only changes
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
[video, isPlaying, fullscreen, setFullscreen, onSeek], [video, isPlaying, fullscreen, toggleFullscreen, onSeek],
); );
useKeyboardListener( useKeyboardListener(
hotKeys hotKeys
@ -287,11 +287,8 @@ export default function VideoControls({
onUploadFrame={onUploadFrame} onUploadFrame={onUploadFrame}
/> />
)} )}
{features.fullscreen && setFullscreen && ( {features.fullscreen && toggleFullscreen && (
<div <div className="cursor-pointer" onClick={toggleFullscreen}>
className="cursor-pointer"
onClick={() => setFullscreen(!fullscreen)}
>
{fullscreen ? <FaCompress /> : <FaExpand />} {fullscreen ? <FaCompress /> : <FaExpand />}
</div> </div>
)} )}

View File

@ -29,7 +29,7 @@ type DynamicVideoPlayerProps = {
onTimestampUpdate?: (timestamp: number) => void; onTimestampUpdate?: (timestamp: number) => void;
onClipEnded?: () => void; onClipEnded?: () => void;
setFullResolution: React.Dispatch<React.SetStateAction<VideoResolutionType>>; setFullResolution: React.Dispatch<React.SetStateAction<VideoResolutionType>>;
setFullscreen: (full: boolean) => void; toggleFullscreen: () => void;
}; };
export default function DynamicVideoPlayer({ export default function DynamicVideoPlayer({
className, className,
@ -44,7 +44,7 @@ export default function DynamicVideoPlayer({
onTimestampUpdate, onTimestampUpdate,
onClipEnded, onClipEnded,
setFullResolution, setFullResolution,
setFullscreen, toggleFullscreen,
}: DynamicVideoPlayerProps) { }: DynamicVideoPlayerProps) {
const apiHost = useApiHost(); const apiHost = useApiHost();
const { data: config } = useSWR<FrigateConfig>("config"); const { data: config } = useSWR<FrigateConfig>("config");
@ -207,7 +207,7 @@ export default function DynamicVideoPlayer({
}} }}
setFullResolution={setFullResolution} setFullResolution={setFullResolution}
onUploadFrame={onUploadFrameToPlus} onUploadFrame={onUploadFrameToPlus}
setFullscreen={setFullscreen} toggleFullscreen={toggleFullscreen}
/> />
<PreviewPlayer <PreviewPlayer
className={cn( className={cn(

View File

@ -1,3 +1,4 @@
import { useFullscreen } from "@/hooks/use-fullscreen";
import { import {
useHashState, useHashState,
usePersistedOverlayState, usePersistedOverlayState,
@ -6,7 +7,7 @@ import { FrigateConfig } from "@/types/frigateConfig";
import LiveBirdseyeView from "@/views/live/LiveBirdseyeView"; import LiveBirdseyeView from "@/views/live/LiveBirdseyeView";
import LiveCameraView from "@/views/live/LiveCameraView"; import LiveCameraView from "@/views/live/LiveCameraView";
import LiveDashboardView from "@/views/live/LiveDashboardView"; import LiveDashboardView from "@/views/live/LiveDashboardView";
import { useEffect, useMemo } from "react"; import { useEffect, useMemo, useRef } from "react";
import useSWR from "swr"; import useSWR from "swr";
function Live() { function Live() {
@ -20,6 +21,12 @@ function Live() {
"default" as string, "default" as string,
); );
// fullscreen
const mainRef = useRef<HTMLDivElement | null>(null);
const { fullscreen, toggleFullscreen } = useFullscreen(mainRef);
// document title // document title
useEffect(() => { useEffect(() => {
@ -78,21 +85,31 @@ function Live() {
[cameras, selectedCameraName], [cameras, selectedCameraName],
); );
if (selectedCameraName == "birdseye") {
return <LiveBirdseyeView />;
}
if (selectedCamera) {
return <LiveCameraView config={config} camera={selectedCamera} />;
}
return ( return (
<LiveDashboardView <div className="size-full" ref={mainRef}>
cameras={cameras} {selectedCameraName === "birdseye" ? (
cameraGroup={cameraGroup} <LiveBirdseyeView
includeBirdseye={includesBirdseye} fullscreen={fullscreen}
onSelectCamera={setSelectedCameraName} toggleFullscreen={toggleFullscreen}
/> />
) : selectedCamera ? (
<LiveCameraView
config={config}
camera={selectedCamera}
fullscreen={fullscreen}
toggleFullscreen={toggleFullscreen}
/>
) : (
<LiveDashboardView
cameras={cameras}
cameraGroup={cameraGroup}
includeBirdseye={includesBirdseye}
onSelectCamera={setSelectedCameraName}
fullscreen={fullscreen}
toggleFullscreen={toggleFullscreen}
/>
)}
</div>
); );
} }

View File

@ -45,6 +45,7 @@ import { VideoResolutionType } from "@/types/live";
import { ASPECT_VERTICAL_LAYOUT, ASPECT_WIDE_LAYOUT } from "@/types/record"; import { ASPECT_VERTICAL_LAYOUT, ASPECT_WIDE_LAYOUT } from "@/types/record";
import { useResizeObserver } from "@/hooks/resize-observer"; import { useResizeObserver } from "@/hooks/resize-observer";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { useFullscreen } from "@/hooks/use-fullscreen";
const SEGMENT_DURATION = 30; const SEGMENT_DURATION = 30;
@ -227,32 +228,7 @@ export function RecordingView({
// fullscreen // fullscreen
const [fullscreen, setFullscreen] = useState(false); const { fullscreen, toggleFullscreen } = useFullscreen(mainLayoutRef);
const onToggleFullscreen = useCallback(
(full: boolean) => {
if (full) {
mainLayoutRef.current?.requestFullscreen();
} else {
document.exitFullscreen();
}
},
[mainLayoutRef],
);
useEffect(() => {
if (mainLayoutRef.current == null) {
return;
}
const fsListener = () => {
setFullscreen(document.fullscreenElement != null);
};
document.addEventListener("fullscreenchange", fsListener);
return () => {
document.removeEventListener("fullscreenchange", fsListener);
};
}, [mainLayoutRef]);
// layout // layout
@ -535,7 +511,7 @@ export function RecordingView({
}} }}
isScrubbing={scrubbing || exportMode == "timeline"} isScrubbing={scrubbing || exportMode == "timeline"}
setFullResolution={setFullResolution} setFullResolution={setFullResolution}
setFullscreen={onToggleFullscreen} toggleFullscreen={toggleFullscreen}
/> />
</div> </div>
{isDesktop && ( {isDesktop && (

View File

@ -40,8 +40,6 @@ import {
TooltipTrigger, TooltipTrigger,
TooltipContent, TooltipContent,
} from "@/components/ui/tooltip"; } from "@/components/ui/tooltip";
import { useFullscreen } from "@/hooks/use-fullscreen";
import { toast } from "sonner";
import { Toaster } from "@/components/ui/sonner"; import { Toaster } from "@/components/ui/sonner";
type DraggableGridLayoutProps = { type DraggableGridLayoutProps = {
@ -55,6 +53,8 @@ type DraggableGridLayoutProps = {
visibleCameras: string[]; visibleCameras: string[];
isEditMode: boolean; isEditMode: boolean;
setIsEditMode: React.Dispatch<React.SetStateAction<boolean>>; setIsEditMode: React.Dispatch<React.SetStateAction<boolean>>;
fullscreen: boolean;
toggleFullscreen: () => void;
}; };
export default function DraggableGridLayout({ export default function DraggableGridLayout({
cameras, cameras,
@ -67,6 +67,8 @@ export default function DraggableGridLayout({
visibleCameras, visibleCameras,
isEditMode, isEditMode,
setIsEditMode, setIsEditMode,
fullscreen,
toggleFullscreen,
}: DraggableGridLayoutProps) { }: DraggableGridLayoutProps) {
const { data: config } = useSWR<FrigateConfig>("config"); const { data: config } = useSWR<FrigateConfig>("config");
const birdseyeConfig = useMemo(() => config?.birdseye, [config]); const birdseyeConfig = useMemo(() => config?.birdseye, [config]);
@ -289,20 +291,6 @@ export default function DraggableGridLayout({
} }
}, [containerRef, containerHeight]); }, [containerRef, containerHeight]);
// fullscreen state
const { fullscreen, toggleFullscreen, error, clearError } =
useFullscreen(gridContainerRef);
useEffect(() => {
if (error !== null) {
toast.error(`Error attempting fullscreen mode: ${error}`, {
position: "top-center",
});
clearError();
}
}, [error, clearError]);
const cellHeight = useMemo(() => { const cellHeight = useMemo(() => {
const aspectRatio = 16 / 9; const aspectRatio = 16 / 9;
// subtract container margin, 1 camera takes up at least 4 rows // subtract container margin, 1 camera takes up at least 4 rows
@ -463,21 +451,23 @@ export default function DraggableGridLayout({
</Tooltip> </Tooltip>
{!isEditMode && ( {!isEditMode && (
<> <>
<Tooltip> {!fullscreen && (
<TooltipTrigger asChild> <Tooltip>
<div <TooltipTrigger asChild>
className="cursor-pointer rounded-lg bg-secondary text-secondary-foreground opacity-60 transition-all duration-300 hover:bg-muted hover:opacity-100" <div
onClick={() => className="cursor-pointer rounded-lg bg-secondary text-secondary-foreground opacity-60 transition-all duration-300 hover:bg-muted hover:opacity-100"
setEditGroup((prevEditGroup) => !prevEditGroup) onClick={() =>
} setEditGroup((prevEditGroup) => !prevEditGroup)
> }
<LuPencil className="size-5 md:m-[6px]" /> >
</div> <LuPencil className="size-5 md:m-[6px]" />
</TooltipTrigger> </div>
<TooltipContent> </TooltipTrigger>
{isEditMode ? "Exit Editing" : "Edit Camera Group"} <TooltipContent>
</TooltipContent> {isEditMode ? "Exit Editing" : "Edit Camera Group"}
</Tooltip> </TooltipContent>
</Tooltip>
)}
<Tooltip> <Tooltip>
<TooltipTrigger asChild> <TooltipTrigger asChild>
<div <div

View File

@ -4,7 +4,6 @@ import BirdseyeLivePlayer from "@/components/player/BirdseyeLivePlayer";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { TooltipProvider } from "@/components/ui/tooltip"; import { TooltipProvider } from "@/components/ui/tooltip";
import { useResizeObserver } from "@/hooks/resize-observer"; import { useResizeObserver } from "@/hooks/resize-observer";
import { useFullscreen } from "@/hooks/use-fullscreen";
import { FrigateConfig } from "@/types/frigateConfig"; import { FrigateConfig } from "@/types/frigateConfig";
import { useMemo, useRef } from "react"; import { useMemo, useRef } from "react";
import { import {
@ -19,7 +18,15 @@ import { useNavigate } from "react-router-dom";
import { TransformWrapper, TransformComponent } from "react-zoom-pan-pinch"; import { TransformWrapper, TransformComponent } from "react-zoom-pan-pinch";
import useSWR from "swr"; import useSWR from "swr";
export default function LiveBirdseyeView() { type LiveBirdseyeViewProps = {
fullscreen: boolean;
toggleFullscreen: () => void;
};
export default function LiveBirdseyeView({
fullscreen,
toggleFullscreen,
}: LiveBirdseyeViewProps) {
const { data: config } = useSWR<FrigateConfig>("config"); const { data: config } = useSWR<FrigateConfig>("config");
const navigate = useNavigate(); const navigate = useNavigate();
const { isPortrait } = useMobileOrientation(); const { isPortrait } = useMobileOrientation();
@ -28,10 +35,6 @@ export default function LiveBirdseyeView() {
const [{ width: windowWidth, height: windowHeight }] = const [{ width: windowWidth, height: windowHeight }] =
useResizeObserver(window); useResizeObserver(window);
// fullscreen state
const { fullscreen, toggleFullscreen } = useFullscreen(mainRef);
// playback state // playback state
const cameraAspectRatio = useMemo(() => { const cameraAspectRatio = useMemo(() => {

View File

@ -72,15 +72,18 @@ import {
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { TransformWrapper, TransformComponent } from "react-zoom-pan-pinch"; import { TransformWrapper, TransformComponent } from "react-zoom-pan-pinch";
import useSWR from "swr"; import useSWR from "swr";
import { useFullscreen } from "@/hooks/use-fullscreen";
type LiveCameraViewProps = { type LiveCameraViewProps = {
config?: FrigateConfig; config?: FrigateConfig;
camera: CameraConfig; camera: CameraConfig;
fullscreen: boolean;
toggleFullscreen: () => void;
}; };
export default function LiveCameraView({ export default function LiveCameraView({
config, config,
camera, camera,
fullscreen,
toggleFullscreen,
}: LiveCameraViewProps) { }: LiveCameraViewProps) {
const navigate = useNavigate(); const navigate = useNavigate();
const { isPortrait } = useMobileOrientation(); const { isPortrait } = useMobileOrientation();
@ -175,9 +178,7 @@ export default function LiveCameraView({
[clickOverlayRef, clickOverlay, sendPtz], [clickOverlayRef, clickOverlay, sendPtz],
); );
// fullscreen / pip state // pip state
const { fullscreen, toggleFullscreen } = useFullscreen(mainRef);
useEffect(() => { useEffect(() => {
setPip(document.pictureInPictureElement != null); setPip(document.pictureInPictureElement != null);
@ -314,6 +315,18 @@ export default function LiveCameraView({
<div <div
className={`flex flex-row items-center gap-2 *:rounded-lg ${isMobile ? "landscape:flex-col" : ""}`} className={`flex flex-row items-center gap-2 *:rounded-lg ${isMobile ? "landscape:flex-col" : ""}`}
> >
{fullscreen && (
<Button
className="bg-gray-500 bg-gradient-to-br from-gray-400 to-gray-500 text-primary"
size="sm"
onClick={() => navigate(-1)}
>
<IoMdArrowRoundBack className="size-5 text-secondary-foreground" />
{isDesktop && (
<div className="text-secondary-foreground">Back</div>
)}
</Button>
)}
{!isIOS && ( {!isIOS && (
<CameraFeatureToggle <CameraFeatureToggle
className="p-2 md:p-0" className="p-2 md:p-0"

View File

@ -30,12 +30,16 @@ type LiveDashboardViewProps = {
cameraGroup?: string; cameraGroup?: string;
includeBirdseye: boolean; includeBirdseye: boolean;
onSelectCamera: (camera: string) => void; onSelectCamera: (camera: string) => void;
fullscreen: boolean;
toggleFullscreen: () => void;
}; };
export default function LiveDashboardView({ export default function LiveDashboardView({
cameras, cameras,
cameraGroup, cameraGroup,
includeBirdseye, includeBirdseye,
onSelectCamera, onSelectCamera,
fullscreen,
toggleFullscreen,
}: LiveDashboardViewProps) { }: LiveDashboardViewProps) {
const { data: config } = useSWR<FrigateConfig>("config"); const { data: config } = useSWR<FrigateConfig>("config");
@ -214,7 +218,7 @@ export default function LiveDashboardView({
</div> </div>
)} )}
{events && events.length > 0 && ( {!fullscreen && events && events.length > 0 && (
<ScrollArea> <ScrollArea>
<TooltipProvider> <TooltipProvider>
<div className="flex items-center gap-2 px-1"> <div className="flex items-center gap-2 px-1">
@ -281,6 +285,8 @@ export default function LiveDashboardView({
visibleCameras={visibleCameras} visibleCameras={visibleCameras}
isEditMode={isEditMode} isEditMode={isEditMode}
setIsEditMode={setIsEditMode} setIsEditMode={setIsEditMode}
fullscreen={fullscreen}
toggleFullscreen={toggleFullscreen}
/> />
)} )}
</div> </div>