Explicitly set video tag dimensions to fit inside dialog (#14184)

This commit is contained in:
Josh Hawkins 2024-10-07 08:19:22 -05:00 committed by GitHub
parent 74047453ef
commit ddcec82b61
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -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>
); );
} }