Fix frigate+ submit and recordings layouts for portrait cameras (#10486)

* Fix plus submission dialog

* Different layout for portrait recordings

* Fix now preview found pulsing

* Fix bug with uneven milliseconds

* Improve consistency of video scaling
This commit is contained in:
Nicolas Mowen 2024-03-15 17:28:57 -06:00 committed by GitHub
parent 64763293a2
commit c14f3c3902
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 70 additions and 21 deletions

View File

@ -59,6 +59,7 @@ export default function HlsVideoPlayer({
const hlsRef = useRef<Hls>(); const hlsRef = useRef<Hls>();
const [useHlsCompat, setUseHlsCompat] = useState(false); const [useHlsCompat, setUseHlsCompat] = useState(false);
const [loadedMetadata, setLoadedMetadata] = useState(false);
useEffect(() => { useEffect(() => {
if (!videoRef.current) { if (!videoRef.current) {
@ -153,7 +154,7 @@ export default function HlsVideoPlayer({
return ( return (
<div <div
className={`relative ${className ?? ""}`} className={`relative`}
onMouseOver={ onMouseOver={
isDesktop isDesktop
? () => { ? () => {
@ -174,7 +175,7 @@ export default function HlsVideoPlayer({
<TransformComponent> <TransformComponent>
<video <video
ref={videoRef} ref={videoRef}
className="size-full rounded-2xl" className={`${className ?? ""} bg-black rounded-2xl ${loadedMetadata ? "" : "invisible"}`}
preload="auto" preload="auto"
autoPlay autoPlay
controls={false} controls={false}
@ -204,6 +205,7 @@ export default function HlsVideoPlayer({
: undefined : undefined
} }
onLoadedData={onPlayerLoaded} onLoadedData={onPlayerLoaded}
onLoadedMetadata={() => setLoadedMetadata(true)}
onEnded={onClipEnded} onEnded={onClipEnded}
onError={(e) => { onError={(e) => {
if ( if (

View File

@ -211,7 +211,7 @@ function PreviewVideoPlayer({
</video> </video>
{!loaded && <Skeleton className="absolute inset-0" />} {!loaded && <Skeleton className="absolute inset-0" />}
{cameraPreviews && !currentPreview && ( {cameraPreviews && !currentPreview && (
<div className="absolute inset-x-0 top-1/2 -y-translate-1/2 bg-black text-white rounded-2xl align-center text-center"> <div className="absolute inset-0 bg-black text-white rounded-2xl flex justify-center items-center">
No Preview Found No Preview Found
</div> </div>
)} )}

View File

@ -38,16 +38,23 @@ export default function DynamicVideoPlayer({
const { data: config } = useSWR<FrigateConfig>("config"); const { data: config } = useSWR<FrigateConfig>("config");
// playback behavior // playback behavior
const wideVideo = useMemo(() => {
const grow = useMemo(() => {
if (!config) { if (!config) {
return false; return "aspect-video";
} }
return ( const aspectRatio =
config.cameras[camera].detect.width / config.cameras[camera].detect.width /
config.cameras[camera].detect.height > config.cameras[camera].detect.height;
1.7
); if (aspectRatio > 2) {
return "";
} else if (aspectRatio < 16 / 9) {
return "aspect-tall";
} else {
return "aspect-video";
}
}, [camera, config]); }, [camera, config]);
// controlling playback // controlling playback
@ -163,7 +170,7 @@ export default function DynamicVideoPlayer({
className={`w-full relative ${isScrubbing || isLoading ? "hidden" : "visible"}`} className={`w-full relative ${isScrubbing || isLoading ? "hidden" : "visible"}`}
> >
<HlsVideoPlayer <HlsVideoPlayer
className={`${wideVideo ? "" : "aspect-video"}`} className={`${grow}`}
videoRef={playerRef} videoRef={playerRef}
currentSource={source} currentSource={source}
onTimeUpdate={onTimeUpdate} onTimeUpdate={onTimeUpdate}
@ -180,7 +187,7 @@ export default function DynamicVideoPlayer({
</HlsVideoPlayer> </HlsVideoPlayer>
</div> </div>
<PreviewPlayer <PreviewPlayer
className={`${isScrubbing || isLoading ? "visible" : "hidden"} ${className ?? ""}`} className={`${isScrubbing || isLoading ? "visible" : "hidden"} ${grow}`}
camera={camera} camera={camera}
timeRange={timeRange} timeRange={timeRange}
cameraPreviews={cameraPreviews} cameraPreviews={cameraPreviews}

View File

@ -118,6 +118,7 @@ export default function Events() {
endDate.setHours(0, 0, 0, 0); endDate.setHours(0, 0, 0, 0);
} else { } else {
endDate = new Date(); endDate = new Date();
endDate.setMilliseconds(0);
} }
return { return {

View File

@ -26,6 +26,8 @@ import { FaList, FaVideo } from "react-icons/fa";
import useSWR from "swr"; import useSWR from "swr";
export default function SubmitPlus() { export default function SubmitPlus() {
const { data: config } = useSWR<FrigateConfig>("config");
// filters // filters
const [selectedCameras, setSelectedCameras] = useState<string[]>(); const [selectedCameras, setSelectedCameras] = useState<string[]>();
@ -45,6 +47,24 @@ export default function SubmitPlus() {
]); ]);
const [upload, setUpload] = useState<Event>(); const [upload, setUpload] = useState<Event>();
const grow = useMemo(() => {
if (!config || !upload) {
return "";
}
const camera = config.cameras[upload.camera];
if (!camera) {
return "";
}
if (camera.detect.width / camera.detect.height < 16 / 9) {
return "aspect-video object-contain";
}
return "";
}, [config, upload]);
const onSubmitToPlus = useCallback( const onSubmitToPlus = useCallback(
async (falsePositive: boolean) => { async (falsePositive: boolean) => {
if (!upload) { if (!upload) {
@ -102,7 +122,7 @@ export default function SubmitPlus() {
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
<img <img
className="flex-grow-0" className={`w-full ${grow} bg-black`}
src={`${baseUrl}api/events/${upload?.id}/snapshot.jpg`} src={`${baseUrl}api/events/${upload?.id}/snapshot.jpg`}
alt={`${upload?.label}`} alt={`${upload?.label}`}
/> />

View File

@ -170,21 +170,34 @@ export function DesktopRecordingView({
: null, : null,
); );
const grow = useMemo(() => { const mainCameraAspect = useMemo(() => {
if (!config) { if (!config) {
return "aspect-video"; return "normal";
} }
const aspectRatio = const aspectRatio =
config.cameras[mainCamera].detect.width / config.cameras[mainCamera].detect.width /
config.cameras[mainCamera].detect.height; config.cameras[mainCamera].detect.height;
if (aspectRatio > 2) { if (aspectRatio > 2) {
return "aspect-wide"; return "wide";
} else if (aspectRatio < 16 / 9) {
return "tall";
} else { } else {
return "aspect-video"; return "normal";
} }
}, [config, mainCamera]); }, [config, mainCamera]);
const grow = useMemo(() => {
if (mainCameraAspect == "wide") {
return "w-full aspect-wide";
} else if (mainCameraAspect == "tall") {
return "h-full aspect-tall";
} else {
return "w-full aspect-video";
}
}, [mainCameraAspect]);
return ( return (
<div ref={contentRef} className="relative size-full"> <div ref={contentRef} className="relative size-full">
<Button <Button
@ -197,13 +210,15 @@ export function DesktopRecordingView({
<div className="flex h-full justify-center overflow-hidden"> <div className="flex h-full justify-center overflow-hidden">
<div className="flex flex-1 flex-wrap"> <div className="flex flex-1 flex-wrap">
<div className="w-full flex flex-col h-full px-2 justify-center items-center"> <div
className={`size-full flex px-2 items-center ${mainCameraAspect == "tall" ? "flex-row justify-evenly" : "flex-col justify-center"}`}
>
<div <div
key={mainCamera} key={mainCamera}
className="w-[82%] flex justify-center items mb-5" className={`flex justify-center items mb-5 ${mainCameraAspect == "tall" ? "h-[96%]" : "w-[82%]"}`}
> >
<DynamicVideoPlayer <DynamicVideoPlayer
className={`w-full ${grow}`} className={grow}
camera={mainCamera} camera={mainCamera}
timeRange={currentTimeRange} timeRange={currentTimeRange}
cameraPreviews={allPreviews ?? []} cameraPreviews={allPreviews ?? []}
@ -222,13 +237,17 @@ export function DesktopRecordingView({
isScrubbing={scrubbing} isScrubbing={scrubbing}
/> />
</div> </div>
<div className="w-full flex justify-center gap-2 overflow-x-auto"> <div
className={`flex justify-center gap-2 ${mainCameraAspect == "tall" ? "h-full flex-col overflow-y-auto items-center" : "w-full overflow-x-auto"}`}
>
{allCameras.map((cam) => { {allCameras.map((cam) => {
if (cam !== mainCamera) { if (cam !== mainCamera) {
return ( return (
<div key={cam}> <div key={cam}>
<PreviewPlayer <PreviewPlayer
className="size-full" className={
mainCameraAspect == "tall" ? "" : "size-full"
}
camera={cam} camera={cam}
timeRange={currentTimeRange} timeRange={currentTimeRange}
cameraPreviews={allPreviews ?? []} cameraPreviews={allPreviews ?? []}