diff --git a/web/src/components/player/LivePlayer.tsx b/web/src/components/player/LivePlayer.tsx index 16a66a67c..d42ecb3a0 100644 --- a/web/src/components/player/LivePlayer.tsx +++ b/web/src/components/player/LivePlayer.tsx @@ -6,9 +6,14 @@ import { useEffect, useMemo, useState } from "react"; import MSEPlayer from "./MsePlayer"; import JSMpegPlayer from "./JSMpegPlayer"; import { MdCircle } from "react-icons/md"; +import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip"; import { useCameraActivity } from "@/hooks/use-camera-activity"; import { LivePlayerMode } from "@/types/live"; import useCameraLiveMode from "@/hooks/use-camera-live-mode"; +import { getIconForLabel } from "@/utils/iconUtil"; +import Chip from "../indicators/Chip"; +import { isMobile } from "react-device-detect"; +import { capitalizeFirstLetter } from "@/utils/stringUtil"; type LivePlayerProps = { cameraRef?: (ref: HTMLDivElement | null) => void; @@ -37,9 +42,12 @@ export default function LivePlayer({ pip, onClick, }: LivePlayerProps) { + const [cameraHovered, setCameraHovered] = useState(false); + // camera activity - const { activeMotion, activeTracking } = useCameraActivity(cameraConfig); + const { activeMotion, activeTracking, activeObjects } = + useCameraActivity(cameraConfig); const cameraActive = useMemo( () => @@ -148,11 +156,54 @@ export default function LivePlayer({ : "outline-0 outline-background" } transition-all duration-500 ${className}`} onClick={onClick} + onMouseEnter={() => setCameraHovered(true)} + onMouseLeave={() => setCameraHovered(false)} >
{player} + {activeObjects.length > 0 && ( +
+ +
+ +
+ + {[ + ...new Set([ + ...(activeObjects || []).map(({ label }) => label), + ]), + ] + .map((label) => { + return getIconForLabel(label, "size-3 text-white"); + }) + .sort()} + +
+
+
+ + {[ + ...new Set([ + ...(activeObjects || []).map(({ label }) => label), + ]), + ] + .filter( + (label) => + label !== undefined && !label.includes("-verified"), + ) + .map((label) => capitalizeFirstLetter(label)) + .sort() + .join(", ") + .replaceAll("-verified", "")} + +
+
+ )} +
([]); + const [activeObjects, setActiveObjects] = useState([]); const hasActiveObjects = useMemo( - () => activeObjects.length > 0, + () => activeObjects.filter((obj) => !obj.stationary).length > 0, [activeObjects], ); @@ -30,7 +37,9 @@ export function useCameraActivity( return; } - const eventIndex = activeObjects.indexOf(event.after.id); + const eventIndex = activeObjects.findIndex( + (obj) => obj.id === event.after.id, + ); if (event.type == "end") { if (eventIndex != -1) { @@ -42,14 +51,14 @@ export function useCameraActivity( if (eventIndex == -1) { // add unknown event to list if not stationary if (!event.after.stationary) { - const newActiveObjects = [...activeObjects, event.after.id]; + const newActiveObject: ActiveObjectType = { + id: event.after.id, + label: event.after.label, + stationary: event.after.stationary, + }; + const newActiveObjects = [...activeObjects, newActiveObject]; setActiveObjects(newActiveObjects); } - } else { - // remove known event from list if it has become stationary - if (event.after.stationary) { - activeObjects.splice(eventIndex, 1); - } } } }, [camera, event, activeObjects]); @@ -57,6 +66,7 @@ export function useCameraActivity( return { activeTracking: hasActiveObjects, activeMotion: detectingMotion == "ON", + activeObjects, }; }