mirror of
https://github.com/blakeblackshear/frigate.git
synced 2024-11-21 19:07:46 +01:00
stay in fullscreen when navigating to a camera (#11666)
This commit is contained in:
parent
8c325801ef
commit
a3d116e70e
@ -39,7 +39,7 @@ type HlsVideoPlayerProps = {
|
||||
onPlaying?: () => void;
|
||||
setFullResolution?: React.Dispatch<React.SetStateAction<VideoResolutionType>>;
|
||||
onUploadFrame?: (playTime: number) => Promise<AxiosResponse> | undefined;
|
||||
setFullscreen?: (full: boolean) => void;
|
||||
toggleFullscreen?: () => void;
|
||||
};
|
||||
export default function HlsVideoPlayer({
|
||||
videoRef,
|
||||
@ -53,7 +53,7 @@ export default function HlsVideoPlayer({
|
||||
onPlaying,
|
||||
setFullResolution,
|
||||
onUploadFrame,
|
||||
setFullscreen,
|
||||
toggleFullscreen,
|
||||
}: HlsVideoPlayerProps) {
|
||||
const { data: config } = useSWR<FrigateConfig>("config");
|
||||
|
||||
@ -224,7 +224,7 @@ export default function HlsVideoPlayer({
|
||||
}
|
||||
}}
|
||||
fullscreen={fullscreen}
|
||||
setFullscreen={setFullscreen}
|
||||
toggleFullscreen={toggleFullscreen}
|
||||
/>
|
||||
<TransformComponent
|
||||
wrapperStyle={{
|
||||
|
@ -68,7 +68,7 @@ type VideoControlsProps = {
|
||||
onSeek: (diff: number) => void;
|
||||
onSetPlaybackRate: (rate: number) => void;
|
||||
onUploadFrame?: () => void;
|
||||
setFullscreen?: (full: boolean) => void;
|
||||
toggleFullscreen?: () => void;
|
||||
};
|
||||
export default function VideoControls({
|
||||
className,
|
||||
@ -88,7 +88,7 @@ export default function VideoControls({
|
||||
onSeek,
|
||||
onSetPlaybackRate,
|
||||
onUploadFrame,
|
||||
setFullscreen,
|
||||
toggleFullscreen,
|
||||
}: VideoControlsProps) {
|
||||
// layout
|
||||
|
||||
@ -160,8 +160,8 @@ export default function VideoControls({
|
||||
}
|
||||
break;
|
||||
case "f":
|
||||
if (setFullscreen && down && !repeat) {
|
||||
setFullscreen(!fullscreen);
|
||||
if (toggleFullscreen && down && !repeat) {
|
||||
toggleFullscreen();
|
||||
}
|
||||
break;
|
||||
case "m":
|
||||
@ -178,7 +178,7 @@ export default function VideoControls({
|
||||
},
|
||||
// only update when preview only changes
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[video, isPlaying, fullscreen, setFullscreen, onSeek],
|
||||
[video, isPlaying, fullscreen, toggleFullscreen, onSeek],
|
||||
);
|
||||
useKeyboardListener(
|
||||
hotKeys
|
||||
@ -287,11 +287,8 @@ export default function VideoControls({
|
||||
onUploadFrame={onUploadFrame}
|
||||
/>
|
||||
)}
|
||||
{features.fullscreen && setFullscreen && (
|
||||
<div
|
||||
className="cursor-pointer"
|
||||
onClick={() => setFullscreen(!fullscreen)}
|
||||
>
|
||||
{features.fullscreen && toggleFullscreen && (
|
||||
<div className="cursor-pointer" onClick={toggleFullscreen}>
|
||||
{fullscreen ? <FaCompress /> : <FaExpand />}
|
||||
</div>
|
||||
)}
|
||||
|
@ -29,7 +29,7 @@ type DynamicVideoPlayerProps = {
|
||||
onTimestampUpdate?: (timestamp: number) => void;
|
||||
onClipEnded?: () => void;
|
||||
setFullResolution: React.Dispatch<React.SetStateAction<VideoResolutionType>>;
|
||||
setFullscreen: (full: boolean) => void;
|
||||
toggleFullscreen: () => void;
|
||||
};
|
||||
export default function DynamicVideoPlayer({
|
||||
className,
|
||||
@ -44,7 +44,7 @@ export default function DynamicVideoPlayer({
|
||||
onTimestampUpdate,
|
||||
onClipEnded,
|
||||
setFullResolution,
|
||||
setFullscreen,
|
||||
toggleFullscreen,
|
||||
}: DynamicVideoPlayerProps) {
|
||||
const apiHost = useApiHost();
|
||||
const { data: config } = useSWR<FrigateConfig>("config");
|
||||
@ -207,7 +207,7 @@ export default function DynamicVideoPlayer({
|
||||
}}
|
||||
setFullResolution={setFullResolution}
|
||||
onUploadFrame={onUploadFrameToPlus}
|
||||
setFullscreen={setFullscreen}
|
||||
toggleFullscreen={toggleFullscreen}
|
||||
/>
|
||||
<PreviewPlayer
|
||||
className={cn(
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { useFullscreen } from "@/hooks/use-fullscreen";
|
||||
import {
|
||||
useHashState,
|
||||
usePersistedOverlayState,
|
||||
@ -6,7 +7,7 @@ import { FrigateConfig } from "@/types/frigateConfig";
|
||||
import LiveBirdseyeView from "@/views/live/LiveBirdseyeView";
|
||||
import LiveCameraView from "@/views/live/LiveCameraView";
|
||||
import LiveDashboardView from "@/views/live/LiveDashboardView";
|
||||
import { useEffect, useMemo } from "react";
|
||||
import { useEffect, useMemo, useRef } from "react";
|
||||
import useSWR from "swr";
|
||||
|
||||
function Live() {
|
||||
@ -20,6 +21,12 @@ function Live() {
|
||||
"default" as string,
|
||||
);
|
||||
|
||||
// fullscreen
|
||||
|
||||
const mainRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
const { fullscreen, toggleFullscreen } = useFullscreen(mainRef);
|
||||
|
||||
// document title
|
||||
|
||||
useEffect(() => {
|
||||
@ -78,21 +85,31 @@ function Live() {
|
||||
[cameras, selectedCameraName],
|
||||
);
|
||||
|
||||
if (selectedCameraName == "birdseye") {
|
||||
return <LiveBirdseyeView />;
|
||||
}
|
||||
|
||||
if (selectedCamera) {
|
||||
return <LiveCameraView config={config} camera={selectedCamera} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="size-full" ref={mainRef}>
|
||||
{selectedCameraName === "birdseye" ? (
|
||||
<LiveBirdseyeView
|
||||
fullscreen={fullscreen}
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -45,6 +45,7 @@ import { VideoResolutionType } from "@/types/live";
|
||||
import { ASPECT_VERTICAL_LAYOUT, ASPECT_WIDE_LAYOUT } from "@/types/record";
|
||||
import { useResizeObserver } from "@/hooks/resize-observer";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useFullscreen } from "@/hooks/use-fullscreen";
|
||||
|
||||
const SEGMENT_DURATION = 30;
|
||||
|
||||
@ -227,32 +228,7 @@ export function RecordingView({
|
||||
|
||||
// fullscreen
|
||||
|
||||
const [fullscreen, setFullscreen] = useState(false);
|
||||
|
||||
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]);
|
||||
const { fullscreen, toggleFullscreen } = useFullscreen(mainLayoutRef);
|
||||
|
||||
// layout
|
||||
|
||||
@ -535,7 +511,7 @@ export function RecordingView({
|
||||
}}
|
||||
isScrubbing={scrubbing || exportMode == "timeline"}
|
||||
setFullResolution={setFullResolution}
|
||||
setFullscreen={onToggleFullscreen}
|
||||
toggleFullscreen={toggleFullscreen}
|
||||
/>
|
||||
</div>
|
||||
{isDesktop && (
|
||||
|
@ -40,8 +40,6 @@ import {
|
||||
TooltipTrigger,
|
||||
TooltipContent,
|
||||
} from "@/components/ui/tooltip";
|
||||
import { useFullscreen } from "@/hooks/use-fullscreen";
|
||||
import { toast } from "sonner";
|
||||
import { Toaster } from "@/components/ui/sonner";
|
||||
|
||||
type DraggableGridLayoutProps = {
|
||||
@ -55,6 +53,8 @@ type DraggableGridLayoutProps = {
|
||||
visibleCameras: string[];
|
||||
isEditMode: boolean;
|
||||
setIsEditMode: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
fullscreen: boolean;
|
||||
toggleFullscreen: () => void;
|
||||
};
|
||||
export default function DraggableGridLayout({
|
||||
cameras,
|
||||
@ -67,6 +67,8 @@ export default function DraggableGridLayout({
|
||||
visibleCameras,
|
||||
isEditMode,
|
||||
setIsEditMode,
|
||||
fullscreen,
|
||||
toggleFullscreen,
|
||||
}: DraggableGridLayoutProps) {
|
||||
const { data: config } = useSWR<FrigateConfig>("config");
|
||||
const birdseyeConfig = useMemo(() => config?.birdseye, [config]);
|
||||
@ -289,20 +291,6 @@ export default function DraggableGridLayout({
|
||||
}
|
||||
}, [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 aspectRatio = 16 / 9;
|
||||
// subtract container margin, 1 camera takes up at least 4 rows
|
||||
@ -463,6 +451,7 @@ export default function DraggableGridLayout({
|
||||
</Tooltip>
|
||||
{!isEditMode && (
|
||||
<>
|
||||
{!fullscreen && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div
|
||||
@ -478,6 +467,7 @@ export default function DraggableGridLayout({
|
||||
{isEditMode ? "Exit Editing" : "Edit Camera Group"}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div
|
||||
|
@ -4,7 +4,6 @@ import BirdseyeLivePlayer from "@/components/player/BirdseyeLivePlayer";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { TooltipProvider } from "@/components/ui/tooltip";
|
||||
import { useResizeObserver } from "@/hooks/resize-observer";
|
||||
import { useFullscreen } from "@/hooks/use-fullscreen";
|
||||
import { FrigateConfig } from "@/types/frigateConfig";
|
||||
import { useMemo, useRef } from "react";
|
||||
import {
|
||||
@ -19,7 +18,15 @@ import { useNavigate } from "react-router-dom";
|
||||
import { TransformWrapper, TransformComponent } from "react-zoom-pan-pinch";
|
||||
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 navigate = useNavigate();
|
||||
const { isPortrait } = useMobileOrientation();
|
||||
@ -28,10 +35,6 @@ export default function LiveBirdseyeView() {
|
||||
const [{ width: windowWidth, height: windowHeight }] =
|
||||
useResizeObserver(window);
|
||||
|
||||
// fullscreen state
|
||||
|
||||
const { fullscreen, toggleFullscreen } = useFullscreen(mainRef);
|
||||
|
||||
// playback state
|
||||
|
||||
const cameraAspectRatio = useMemo(() => {
|
||||
|
@ -72,15 +72,18 @@ import {
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { TransformWrapper, TransformComponent } from "react-zoom-pan-pinch";
|
||||
import useSWR from "swr";
|
||||
import { useFullscreen } from "@/hooks/use-fullscreen";
|
||||
|
||||
type LiveCameraViewProps = {
|
||||
config?: FrigateConfig;
|
||||
camera: CameraConfig;
|
||||
fullscreen: boolean;
|
||||
toggleFullscreen: () => void;
|
||||
};
|
||||
export default function LiveCameraView({
|
||||
config,
|
||||
camera,
|
||||
fullscreen,
|
||||
toggleFullscreen,
|
||||
}: LiveCameraViewProps) {
|
||||
const navigate = useNavigate();
|
||||
const { isPortrait } = useMobileOrientation();
|
||||
@ -175,9 +178,7 @@ export default function LiveCameraView({
|
||||
[clickOverlayRef, clickOverlay, sendPtz],
|
||||
);
|
||||
|
||||
// fullscreen / pip state
|
||||
|
||||
const { fullscreen, toggleFullscreen } = useFullscreen(mainRef);
|
||||
// pip state
|
||||
|
||||
useEffect(() => {
|
||||
setPip(document.pictureInPictureElement != null);
|
||||
@ -314,6 +315,18 @@ export default function LiveCameraView({
|
||||
<div
|
||||
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 && (
|
||||
<CameraFeatureToggle
|
||||
className="p-2 md:p-0"
|
||||
|
@ -30,12 +30,16 @@ type LiveDashboardViewProps = {
|
||||
cameraGroup?: string;
|
||||
includeBirdseye: boolean;
|
||||
onSelectCamera: (camera: string) => void;
|
||||
fullscreen: boolean;
|
||||
toggleFullscreen: () => void;
|
||||
};
|
||||
export default function LiveDashboardView({
|
||||
cameras,
|
||||
cameraGroup,
|
||||
includeBirdseye,
|
||||
onSelectCamera,
|
||||
fullscreen,
|
||||
toggleFullscreen,
|
||||
}: LiveDashboardViewProps) {
|
||||
const { data: config } = useSWR<FrigateConfig>("config");
|
||||
|
||||
@ -214,7 +218,7 @@ export default function LiveDashboardView({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{events && events.length > 0 && (
|
||||
{!fullscreen && events && events.length > 0 && (
|
||||
<ScrollArea>
|
||||
<TooltipProvider>
|
||||
<div className="flex items-center gap-2 px-1">
|
||||
@ -281,6 +285,8 @@ export default function LiveDashboardView({
|
||||
visibleCameras={visibleCameras}
|
||||
isEditMode={isEditMode}
|
||||
setIsEditMode={setIsEditMode}
|
||||
fullscreen={fullscreen}
|
||||
toggleFullscreen={toggleFullscreen}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
Loading…
Reference in New Issue
Block a user