mirror of
https://github.com/blakeblackshear/frigate.git
synced 2025-02-05 00:15:51 +01:00
Explicitly set video tag dimensions to fit inside dialog (#14184)
This commit is contained in:
parent
74047453ef
commit
ddcec82b61
@ -25,7 +25,6 @@ import HlsVideoPlayer from "@/components/player/HlsVideoPlayer";
|
|||||||
import { baseUrl } from "@/api/baseUrl";
|
import { baseUrl } from "@/api/baseUrl";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import ActivityIndicator from "@/components/indicators/activity-indicator";
|
import ActivityIndicator from "@/components/indicators/activity-indicator";
|
||||||
import { ASPECT_VERTICAL_LAYOUT, ASPECT_WIDE_LAYOUT } from "@/types/record";
|
|
||||||
import {
|
import {
|
||||||
FaCheckCircle,
|
FaCheckCircle,
|
||||||
FaChevronDown,
|
FaChevronDown,
|
||||||
@ -63,6 +62,8 @@ import { TransformComponent, TransformWrapper } from "react-zoom-pan-pinch";
|
|||||||
import { Card, CardContent } from "@/components/ui/card";
|
import { Card, CardContent } from "@/components/ui/card";
|
||||||
import useImageLoaded from "@/hooks/use-image-loaded";
|
import useImageLoaded from "@/hooks/use-image-loaded";
|
||||||
import ImageLoadingIndicator from "@/components/indicators/ImageLoadingIndicator";
|
import ImageLoadingIndicator from "@/components/indicators/ImageLoadingIndicator";
|
||||||
|
import { useResizeObserver } from "@/hooks/resize-observer";
|
||||||
|
import { VideoResolutionType } from "@/types/live";
|
||||||
|
|
||||||
const SEARCH_TABS = [
|
const SEARCH_TABS = [
|
||||||
"details",
|
"details",
|
||||||
@ -225,7 +226,7 @@ export default function SearchDetailDialog({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{page == "video" && <VideoTab search={search} config={config} />}
|
{page == "video" && <VideoTab search={search} />}
|
||||||
{page == "object lifecycle" && (
|
{page == "object lifecycle" && (
|
||||||
<ObjectLifecycle
|
<ObjectLifecycle
|
||||||
className="w-full overflow-x-hidden"
|
className="w-full overflow-x-hidden"
|
||||||
@ -595,9 +596,8 @@ function ObjectSnapshotTab({
|
|||||||
|
|
||||||
type VideoTabProps = {
|
type VideoTabProps = {
|
||||||
search: SearchResult;
|
search: SearchResult;
|
||||||
config?: FrigateConfig;
|
|
||||||
};
|
};
|
||||||
function VideoTab({ search, config }: VideoTabProps) {
|
function VideoTab({ search }: VideoTabProps) {
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
const videoRef = useRef<HTMLVideoElement | null>(null);
|
const videoRef = useRef<HTMLVideoElement | null>(null);
|
||||||
|
|
||||||
@ -608,61 +608,48 @@ function VideoTab({ search, config }: VideoTabProps) {
|
|||||||
`review/event/${search.id}`,
|
`review/event/${search.id}`,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const mainCameraAspect = useMemo(() => {
|
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||||
const camera = config?.cameras?.[search.camera];
|
|
||||||
|
|
||||||
if (!camera) {
|
const [{ width: containerWidth, height: containerHeight }] =
|
||||||
return "normal";
|
useResizeObserver(containerRef);
|
||||||
}
|
const [videoResolution, setVideoResolution] = useState<VideoResolutionType>({
|
||||||
|
width: 0,
|
||||||
|
height: 0,
|
||||||
|
});
|
||||||
|
|
||||||
const aspectRatio = camera.detect.width / camera.detect.height;
|
const videoAspectRatio = useMemo(() => {
|
||||||
|
return videoResolution.width / videoResolution.height || 16 / 9;
|
||||||
|
}, [videoResolution]);
|
||||||
|
|
||||||
if (!aspectRatio) {
|
const containerAspectRatio = useMemo(() => {
|
||||||
return "normal";
|
return containerWidth / containerHeight || 16 / 9;
|
||||||
} else if (aspectRatio > ASPECT_WIDE_LAYOUT) {
|
}, [containerWidth, containerHeight]);
|
||||||
return "wide";
|
|
||||||
} else if (aspectRatio < ASPECT_VERTICAL_LAYOUT) {
|
const videoDimensions = useMemo(() => {
|
||||||
return "tall";
|
if (!containerWidth || !containerHeight)
|
||||||
|
return { width: "100%", height: "100%" };
|
||||||
|
|
||||||
|
if (containerAspectRatio > videoAspectRatio) {
|
||||||
|
const height = containerHeight;
|
||||||
|
const width = height * videoAspectRatio;
|
||||||
|
return { width: `${width}px`, height: `${height}px` };
|
||||||
} else {
|
} else {
|
||||||
return "normal";
|
const width = containerWidth;
|
||||||
|
const height = width / videoAspectRatio;
|
||||||
|
return { width: `${width}px`, height: `${height}px` };
|
||||||
}
|
}
|
||||||
}, [config, search]);
|
}, [containerWidth, containerHeight, videoAspectRatio, containerAspectRatio]);
|
||||||
|
|
||||||
const containerClassName = useMemo(() => {
|
|
||||||
if (mainCameraAspect == "wide") {
|
|
||||||
return "flex justify-center items-center";
|
|
||||||
} else if (mainCameraAspect == "tall") {
|
|
||||||
if (isDesktop) {
|
|
||||||
return "size-full flex flex-col justify-center items-center";
|
|
||||||
} else {
|
|
||||||
return "size-full";
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}, [mainCameraAspect]);
|
|
||||||
|
|
||||||
const videoClassName = useMemo(() => {
|
|
||||||
if (mainCameraAspect == "wide") {
|
|
||||||
return "w-full aspect-wide";
|
|
||||||
} else if (mainCameraAspect == "tall") {
|
|
||||||
if (isDesktop) {
|
|
||||||
return "w-[50%] aspect-tall flex justify-center";
|
|
||||||
} else {
|
|
||||||
return "size-full";
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return "w-full aspect-video";
|
|
||||||
}
|
|
||||||
}, [mainCameraAspect]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative flex flex-col">
|
<div ref={containerRef} className="relative flex h-full w-full flex-col">
|
||||||
<div className={`aspect-video ${containerClassName}`}>
|
<div className="relative flex flex-grow items-center justify-center">
|
||||||
{(isLoading || !reviewItem) && (
|
{(isLoading || !reviewItem) && (
|
||||||
<ActivityIndicator className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2" />
|
<ActivityIndicator className="absolute left-1/2 top-1/2 z-10 -translate-x-1/2 -translate-y-1/2" />
|
||||||
)}
|
)}
|
||||||
<div className={videoClassName}>
|
<div
|
||||||
|
className="relative flex items-center justify-center"
|
||||||
|
style={videoDimensions}
|
||||||
|
>
|
||||||
<HlsVideoPlayer
|
<HlsVideoPlayer
|
||||||
videoRef={videoRef}
|
videoRef={videoRef}
|
||||||
currentSource={`${baseUrl}vod/${search.camera}/start/${search.start_time}/end/${endTime}/index.m3u8`}
|
currentSource={`${baseUrl}vod/${search.camera}/start/${search.start_time}/end/${endTime}/index.m3u8`}
|
||||||
@ -672,36 +659,37 @@ function VideoTab({ search, config }: VideoTabProps) {
|
|||||||
fullscreen={false}
|
fullscreen={false}
|
||||||
supportsFullscreen={false}
|
supportsFullscreen={false}
|
||||||
onPlaying={() => setIsLoading(false)}
|
onPlaying={() => setIsLoading(false)}
|
||||||
|
setFullResolution={setVideoResolution}
|
||||||
/>
|
/>
|
||||||
|
{!isLoading && reviewItem && (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"absolute top-2 z-10 flex items-center",
|
||||||
|
isIOS ? "right-8" : "right-2",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger>
|
||||||
|
<Chip
|
||||||
|
className="cursor-pointer rounded-md bg-gray-500 bg-gradient-to-br from-gray-400 to-gray-500"
|
||||||
|
onClick={() => {
|
||||||
|
if (reviewItem?.id) {
|
||||||
|
const params = new URLSearchParams({
|
||||||
|
id: reviewItem.id,
|
||||||
|
}).toString();
|
||||||
|
navigate(`/review?${params}`);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FaHistory className="size-4 text-white" />
|
||||||
|
</Chip>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent side="left">View in History</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{!isLoading && reviewItem && (
|
|
||||||
<div
|
|
||||||
className={cn(
|
|
||||||
"absolute top-2 flex items-center",
|
|
||||||
isIOS ? "right-8" : "right-2",
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger>
|
|
||||||
<Chip
|
|
||||||
className="cursor-pointer rounded-md bg-gray-500 bg-gradient-to-br from-gray-400 to-gray-500"
|
|
||||||
onClick={() => {
|
|
||||||
if (reviewItem?.id) {
|
|
||||||
const params = new URLSearchParams({
|
|
||||||
id: reviewItem.id,
|
|
||||||
}).toString();
|
|
||||||
navigate(`/review?${params}`);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<FaHistory className="size-4 text-white" />
|
|
||||||
</Chip>
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent side="left">View in History</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user