Add ability to add legacy birdseye to camera groups (#10404)

* initial try

* add birdseye

* remove vite

* cleanup

* memoize

* remove console

* ensure birdseye is actually enabled in config

* birdseye first in select list and fix jsmpeg player size
This commit is contained in:
Josh Hawkins 2024-03-12 14:53:01 -05:00 committed by GitHub
parent dce2e9b366
commit 9e10b914c9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 61 additions and 20 deletions

View File

@ -146,6 +146,8 @@ function NewGroupDialog({ open, setOpen, currentGroups }: NewGroupDialogProps) {
const { data: config, mutate: updateConfig } = const { data: config, mutate: updateConfig } =
useSWR<FrigateConfig>("config"); useSWR<FrigateConfig>("config");
const birdseyeConfig = useMemo(() => config?.birdseye, [config]);
// add fields // add fields
const [editState, setEditState] = useState<"none" | "add" | "edit">("none"); const [editState, setEditState] = useState<"none" | "add" | "edit">("none");
@ -298,7 +300,10 @@ function NewGroupDialog({ open, setOpen, currentGroups }: NewGroupDialogProps) {
</div> </div>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent> <DropdownMenuContent>
{Object.keys(config?.cameras ?? {}).map((camera) => ( {[
...(birdseyeConfig?.enabled ? ["birdseye"] : []),
...Object.keys(config?.cameras ?? {}),
].map((camera) => (
<FilterCheckBox <FilterCheckBox
key={camera} key={camera}
isChecked={cameras.includes(camera)} isChecked={cameras.includes(camera)}

View File

@ -13,21 +13,18 @@ export default function BirdseyeLivePlayer({
birdseyeConfig, birdseyeConfig,
liveMode, liveMode,
}: LivePlayerProps) { }: LivePlayerProps) {
let player;
if (liveMode == "webrtc") { if (liveMode == "webrtc") {
return ( player = (
<div className="max-w-5xl"> <WebRtcPlayer className={`rounded-2xl size-full`} camera="birdseye" />
<WebRtcPlayer camera="birdseye" />
</div>
); );
} else if (liveMode == "mse") { } else if (liveMode == "mse") {
if ("MediaSource" in window || "ManagedMediaSource" in window) { if ("MediaSource" in window || "ManagedMediaSource" in window) {
return ( player = (
<div className="max-w-5xl"> <MSEPlayer className={`rounded-2xl size-full`} camera="birdseye" />
<MSEPlayer camera="birdseye" />
</div>
); );
} else { } else {
return ( player = (
<div className="w-5xl text-center text-sm"> <div className="w-5xl text-center text-sm">
MSE is only supported on iOS 17.1+. You'll need to update if available MSE is only supported on iOS 17.1+. You'll need to update if available
or use jsmpeg / webRTC streams. See the docs for more info. or use jsmpeg / webRTC streams. See the docs for more info.
@ -35,16 +32,23 @@ export default function BirdseyeLivePlayer({
); );
} }
} else if (liveMode == "jsmpeg") { } else if (liveMode == "jsmpeg") {
return ( player = (
<div className={`max-w-[${birdseyeConfig.width}px]`}>
<JSMpegPlayer <JSMpegPlayer
className="size-full flex justify-center rounded-2xl overflow-hidden"
camera="birdseye" camera="birdseye"
width={birdseyeConfig.width} width={birdseyeConfig.width}
height={birdseyeConfig.height} height={birdseyeConfig.height}
/> />
</div>
); );
} else { } else {
<ActivityIndicator />; player = <ActivityIndicator />;
} }
return (
<div className={`relative flex justify-center w-full cursor-pointer`}>
<div className="absolute top-0 inset-x-0 rounded-2xl z-10 w-full h-[30%] bg-gradient-to-b from-black/20 to-transparent pointer-events-none"></div>
<div className="absolute bottom-0 inset-x-0 rounded-2xl z-10 w-full h-[10%] bg-gradient-to-t from-black/20 to-transparent pointer-events-none"></div>
<div className="size-full">{player}</div>
</div>
);
} }

View File

@ -11,6 +11,14 @@ function Live() {
const [selectedCameraName, setSelectedCameraName] = useOverlayState("camera"); const [selectedCameraName, setSelectedCameraName] = useOverlayState("camera");
const [cameraGroup] = useOverlayState("cameraGroup"); const [cameraGroup] = useOverlayState("cameraGroup");
const includesBirdseye = useMemo(() => {
if (config && cameraGroup) {
return config.camera_groups[cameraGroup].cameras.includes("birdseye");
} else {
return false;
}
}, [config, cameraGroup]);
const cameras = useMemo(() => { const cameras = useMemo(() => {
if (!config) { if (!config) {
return []; return [];
@ -40,6 +48,7 @@ function Live() {
return ( return (
<LiveDashboardView <LiveDashboardView
cameras={cameras} cameras={cameras}
includeBirdseye={includesBirdseye}
onSelectCamera={setSelectedCameraName} onSelectCamera={setSelectedCameraName}
/> />
); );

View File

@ -22,6 +22,7 @@ import {
SelectTrigger, SelectTrigger,
SelectValue, SelectValue,
} from "@/components/ui/select"; } from "@/components/ui/select";
import BirdseyeLivePlayer from "@/components/player/BirdseyeLivePlayer";
// Color data // Color data
const colors = [ const colors = [
@ -184,6 +185,7 @@ function UIPlayground() {
}; };
const [isEventsReviewTimeline, setIsEventsReviewTimeline] = useState(true); const [isEventsReviewTimeline, setIsEventsReviewTimeline] = useState(true);
const birdseyeConfig = config?.birdseye;
return ( return (
<> <>
@ -243,6 +245,14 @@ function UIPlayground() {
<div className="w-[40px] my-4"> <div className="w-[40px] my-4">
<CameraActivityIndicator /> <CameraActivityIndicator />
</div> </div>
<div className="">
{birdseyeConfig && (
<BirdseyeLivePlayer
birdseyeConfig={birdseyeConfig}
liveMode={birdseyeConfig.restream ? "mse" : "jsmpeg"}
/>
)}
</div>
<p> <p>
<Button onClick={handleZoomOut} disabled={zoomLevel === 0}> <Button onClick={handleZoomOut} disabled={zoomLevel === 0}>
Zoom Out Zoom Out

View File

@ -2,12 +2,13 @@ import { useFrigateReviews } from "@/api/ws";
import Logo from "@/components/Logo"; import Logo from "@/components/Logo";
import { CameraGroupSelector } from "@/components/filter/CameraGroupSelector"; import { CameraGroupSelector } from "@/components/filter/CameraGroupSelector";
import { AnimatedEventThumbnail } from "@/components/image/AnimatedEventThumbnail"; import { AnimatedEventThumbnail } from "@/components/image/AnimatedEventThumbnail";
import BirdseyeLivePlayer from "@/components/player/BirdseyeLivePlayer";
import LivePlayer from "@/components/player/LivePlayer"; import LivePlayer from "@/components/player/LivePlayer";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area"; import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
import { TooltipProvider } from "@/components/ui/tooltip"; import { TooltipProvider } from "@/components/ui/tooltip";
import { usePersistence } from "@/hooks/use-persistence"; import { usePersistence } from "@/hooks/use-persistence";
import { CameraConfig } from "@/types/frigateConfig"; import { CameraConfig, FrigateConfig } from "@/types/frigateConfig";
import { ReviewSegment } from "@/types/review"; import { ReviewSegment } from "@/types/review";
import { useCallback, useEffect, useMemo, useState } from "react"; import { useCallback, useEffect, useMemo, useState } from "react";
import { isDesktop, isMobile, isSafari } from "react-device-detect"; import { isDesktop, isMobile, isSafari } from "react-device-detect";
@ -16,12 +17,16 @@ import useSWR from "swr";
type LiveDashboardViewProps = { type LiveDashboardViewProps = {
cameras: CameraConfig[]; cameras: CameraConfig[];
includeBirdseye: boolean;
onSelectCamera: (camera: string) => void; onSelectCamera: (camera: string) => void;
}; };
export default function LiveDashboardView({ export default function LiveDashboardView({
cameras, cameras,
includeBirdseye,
onSelectCamera, onSelectCamera,
}: LiveDashboardViewProps) { }: LiveDashboardViewProps) {
const { data: config } = useSWR<FrigateConfig>("config");
// layout // layout
const [layout, setLayout] = usePersistence<"grid" | "list">( const [layout, setLayout] = usePersistence<"grid" | "list">(
@ -74,6 +79,8 @@ export default function LiveDashboardView({
}; };
}, [visibilityListener]); }, [visibilityListener]);
const birdseyeConfig = useMemo(() => config?.birdseye, [config]);
return ( return (
<div className="size-full overflow-y-auto px-2"> <div className="size-full overflow-y-auto px-2">
{isMobile && ( {isMobile && (
@ -123,6 +130,12 @@ export default function LiveDashboardView({
<div <div
className={`my-4 grid ${layout == "grid" ? "grid-cols-2 xl:grid-cols-3 3xl:grid-cols-4" : ""} gap-2 md:gap-4 *:rounded-2xl *:bg-black`} className={`my-4 grid ${layout == "grid" ? "grid-cols-2 xl:grid-cols-3 3xl:grid-cols-4" : ""} gap-2 md:gap-4 *:rounded-2xl *:bg-black`}
> >
{includeBirdseye && birdseyeConfig?.enabled && (
<BirdseyeLivePlayer
birdseyeConfig={birdseyeConfig}
liveMode={birdseyeConfig.restream ? "mse" : "jsmpeg"}
/>
)}
{cameras.map((camera) => { {cameras.map((camera) => {
let grow; let grow;
const aspectRatio = camera.detect.width / camera.detect.height; const aspectRatio = camera.detect.width / camera.detect.height;