* Fix external events saving

* Only show relevant labels

* Set on recordings view too

* Fix video control width on motion page

* use overlay so player state is maintained between camera switches

* use overlay so player state is maintained between camera switches

* mobile only

* Formatting

* Use higher amount

* Only wrap when needed
This commit is contained in:
Nicolas Mowen 2024-05-10 11:42:56 -06:00 committed by GitHub
parent 82e443a5c3
commit 9680f2a574
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 69 additions and 41 deletions

View File

@ -119,7 +119,11 @@ class ExternalEventProcessor:
( (
self.event_camera[event_id], self.event_camera[event_id],
end_time, end_time,
{"state": ManualEventState.end, "event_id": event_id}, {
"state": ManualEventState.end,
"event_id": event_id,
"end_time": end_time,
},
) )
) )
self.event_camera.pop(event_id) self.event_camera.pop(event_id)

View File

@ -53,6 +53,7 @@ type ReviewFilterGroupProps = {
reviewSummary?: ReviewSummary; reviewSummary?: ReviewSummary;
filter?: ReviewFilter; filter?: ReviewFilter;
motionOnly: boolean; motionOnly: boolean;
filterLabels?: string[];
onUpdateFilter: (filter: ReviewFilter) => void; onUpdateFilter: (filter: ReviewFilter) => void;
setMotionOnly: React.Dispatch<React.SetStateAction<boolean>>; setMotionOnly: React.Dispatch<React.SetStateAction<boolean>>;
}; };
@ -63,12 +64,17 @@ export default function ReviewFilterGroup({
reviewSummary, reviewSummary,
filter, filter,
motionOnly, motionOnly,
filterLabels,
onUpdateFilter, onUpdateFilter,
setMotionOnly, setMotionOnly,
}: ReviewFilterGroupProps) { }: ReviewFilterGroupProps) {
const { data: config } = useSWR<FrigateConfig>("config"); const { data: config } = useSWR<FrigateConfig>("config");
const allLabels = useMemo<string[]>(() => { const allLabels = useMemo<string[]>(() => {
if (filterLabels) {
return filterLabels;
}
if (!config) { if (!config) {
return []; return [];
} }
@ -93,7 +99,7 @@ export default function ReviewFilterGroup({
}); });
return [...labels].sort(); return [...labels].sort();
}, [config, filter]); }, [config, filterLabels, filter]);
const filterValues = useMemo( const filterValues = useMemo(
() => ({ () => ({
@ -197,6 +203,7 @@ export default function ReviewFilterGroup({
filter={filter} filter={filter}
currentSeverity={currentSeverity} currentSeverity={currentSeverity}
reviewSummary={reviewSummary} reviewSummary={reviewSummary}
allLabels={allLabels}
onUpdateFilter={onUpdateFilter} onUpdateFilter={onUpdateFilter}
// not applicable as exports are not used // not applicable as exports are not used
camera="" camera=""

View File

@ -1,4 +1,4 @@
import { useCallback, useMemo, useState } from "react"; import { useCallback, useState } from "react";
import { Drawer, DrawerContent, DrawerTrigger } from "../ui/drawer"; import { Drawer, DrawerContent, DrawerTrigger } from "../ui/drawer";
import { Button } from "../ui/button"; import { Button } from "../ui/button";
import { FaArrowDown, FaCalendarAlt, FaCog, FaFilter } from "react-icons/fa"; import { FaArrowDown, FaCalendarAlt, FaCog, FaFilter } from "react-icons/fa";
@ -10,8 +10,6 @@ import { SelectSeparator } from "../ui/select";
import { ReviewFilter, ReviewSeverity, ReviewSummary } from "@/types/review"; import { ReviewFilter, ReviewSeverity, ReviewSummary } from "@/types/review";
import { getEndOfDayTimestamp } from "@/utils/dateUtil"; import { getEndOfDayTimestamp } from "@/utils/dateUtil";
import { GeneralFilterContent } from "../filter/ReviewFilterGroup"; import { GeneralFilterContent } from "../filter/ReviewFilterGroup";
import useSWR from "swr";
import { FrigateConfig } from "@/types/frigateConfig";
import { toast } from "sonner"; import { toast } from "sonner";
import axios from "axios"; import axios from "axios";
import SaveExportOverlay from "./SaveExportOverlay"; import SaveExportOverlay from "./SaveExportOverlay";
@ -37,6 +35,7 @@ type MobileReviewSettingsDrawerProps = {
range?: TimeRange; range?: TimeRange;
mode: ExportMode; mode: ExportMode;
reviewSummary?: ReviewSummary; reviewSummary?: ReviewSummary;
allLabels: string[];
onUpdateFilter: (filter: ReviewFilter) => void; onUpdateFilter: (filter: ReviewFilter) => void;
setRange: (range: TimeRange | undefined) => void; setRange: (range: TimeRange | undefined) => void;
setMode: (mode: ExportMode) => void; setMode: (mode: ExportMode) => void;
@ -51,11 +50,11 @@ export default function MobileReviewSettingsDrawer({
range, range,
mode, mode,
reviewSummary, reviewSummary,
allLabels,
onUpdateFilter, onUpdateFilter,
setRange, setRange,
setMode, setMode,
}: MobileReviewSettingsDrawerProps) { }: MobileReviewSettingsDrawerProps) {
const { data: config } = useSWR<FrigateConfig>("config");
const [drawerMode, setDrawerMode] = useState<DrawerMode>("none"); const [drawerMode, setDrawerMode] = useState<DrawerMode>("none");
// exports // exports
@ -102,32 +101,6 @@ export default function MobileReviewSettingsDrawer({
// filters // filters
const allLabels = useMemo<string[]>(() => {
if (!config) {
return [];
}
const labels = new Set<string>();
const cameras = filter?.cameras || Object.keys(config.cameras);
cameras.forEach((camera) => {
if (camera == "birdseye") {
return;
}
const cameraConfig = config.cameras[camera];
cameraConfig.objects.track.forEach((label) => {
labels.add(label);
});
if (cameraConfig.audio.enabled_in_config) {
cameraConfig.audio.listen.forEach((label) => {
labels.add(label);
});
}
});
return [...labels].sort();
}, [config, filter]);
const [currentLabels, setCurrentLabels] = useState<string[] | undefined>( const [currentLabels, setCurrentLabels] = useState<string[] | undefined>(
filter?.labels, filter?.labels,
); );

View File

@ -14,6 +14,7 @@ import useSWR from "swr";
import { FrigateConfig } from "@/types/frigateConfig"; import { FrigateConfig } from "@/types/frigateConfig";
import { AxiosResponse } from "axios"; import { AxiosResponse } from "axios";
import { toast } from "sonner"; import { toast } from "sonner";
import { useOverlayState } from "@/hooks/use-overlay-state";
// Android native hls does not seek correctly // Android native hls does not seek correctly
const USE_NATIVE_HLS = !isAndroid; const USE_NATIVE_HLS = !isAndroid;
@ -108,8 +109,8 @@ export default function HlsVideoPlayer({
// controls // controls
const [isPlaying, setIsPlaying] = useState(true); const [isPlaying, setIsPlaying] = useState(true);
const [muted, setMuted] = useState(true); const [muted, setMuted] = useOverlayState("playerMuted", true);
const [volume, setVolume] = useState(1.0); const [volume, setVolume] = useOverlayState("playerVolume", 1.0);
const [mobileCtrlTimeout, setMobileCtrlTimeout] = useState<NodeJS.Timeout>(); const [mobileCtrlTimeout, setMobileCtrlTimeout] = useState<NodeJS.Timeout>();
const [controls, setControls] = useState(isMobile); const [controls, setControls] = useState(isMobile);
const [controlsOpen, setControlsOpen] = useState(false); const [controlsOpen, setControlsOpen] = useState(false);
@ -160,7 +161,7 @@ export default function HlsVideoPlayer({
fullscreen: !isIOS, fullscreen: !isIOS,
}} }}
setControlsOpen={setControlsOpen} setControlsOpen={setControlsOpen}
setMuted={setMuted} setMuted={(muted) => setMuted(muted, true)}
playbackRate={videoRef.current?.playbackRate ?? 1} playbackRate={videoRef.current?.playbackRate ?? 1}
hotKeys={hotKeys} hotKeys={hotKeys}
onPlayPause={(play) => { onPlayPause={(play) => {
@ -226,7 +227,9 @@ export default function HlsVideoPlayer({
controls={false} controls={false}
playsInline playsInline
muted={muted} muted={muted}
onVolumeChange={() => setVolume(videoRef.current?.volume ?? 1.0)} onVolumeChange={() =>
setVolume(videoRef.current?.volume ?? 1.0, true)
}
onPlay={() => { onPlay={() => {
setIsPlaying(true); setIsPlaying(true);
@ -249,7 +252,13 @@ export default function HlsVideoPlayer({
: undefined : undefined
} }
onLoadedData={onPlayerLoaded} onLoadedData={onPlayerLoaded}
onLoadedMetadata={handleLoadedMetadata} onLoadedMetadata={() => {
handleLoadedMetadata();
if (videoRef.current && volume) {
videoRef.current.volume = volume;
}
}}
onEnded={onClipEnded} onEnded={onClipEnded}
onError={(e) => { onError={(e) => {
if ( if (

View File

@ -1,5 +1,5 @@
import { useCallback, useMemo, useState } from "react"; import { useCallback, useMemo, useState } from "react";
import { isSafari } from "react-device-detect"; import { isMobileOnly, isSafari } from "react-device-detect";
import { LuPause, LuPlay } from "react-icons/lu"; import { LuPause, LuPlay } from "react-icons/lu";
import { import {
DropdownMenu, DropdownMenu,
@ -48,6 +48,7 @@ const CONTROLS_DEFAULT: VideoControls = {
fullscreen: false, fullscreen: false,
}; };
const PLAYBACK_RATE_DEFAULT = isSafari ? [0.5, 1, 2] : [0.5, 1, 2, 4, 8, 16]; const PLAYBACK_RATE_DEFAULT = isSafari ? [0.5, 1, 2] : [0.5, 1, 2, 4, 8, 16];
const MIN_ITEMS_WRAP = 6;
type VideoControlsProps = { type VideoControlsProps = {
className?: string; className?: string;
@ -170,8 +171,12 @@ export default function VideoControls({
return ( return (
<div <div
className={cn( className={cn(
"w-[96%] sm:w-auto px-4 py-2 flex flex-wrap sm:flex-nowrap justify-between items-center gap-4 sm:gap-8 text-primary z-50 bg-background/60 rounded-lg", "w-auto px-4 py-2 flex sm:flex-nowrap justify-between items-center gap-4 sm:gap-8 text-primary z-50 bg-background/60 rounded-lg",
className, className,
isMobileOnly &&
Object.values(features).filter((feat) => feat).length >
MIN_ITEMS_WRAP &&
"flex-wrap min-w-[75%]",
)} )}
> >
{video && features.volume && ( {video && features.volume && (

View File

@ -232,6 +232,21 @@ export default function EventView({
100, 100,
); );
// review filter info
const reviewLabels = useMemo(() => {
const uniqueLabels = new Set<string>();
reviewItems?.all?.forEach((rev) => {
rev.data.objects.forEach((obj) =>
uniqueLabels.add(obj.replace("-verified", "")),
);
rev.data.audio.forEach((aud) => uniqueLabels.add(aud));
});
return [...uniqueLabels];
}, [reviewItems]);
if (!config) { if (!config) {
return <ActivityIndicator />; return <ActivityIndicator />;
} }
@ -297,8 +312,9 @@ export default function EventView({
currentSeverity={severityToggle} currentSeverity={severityToggle}
reviewSummary={reviewSummary} reviewSummary={reviewSummary}
filter={filter} filter={filter}
onUpdateFilter={updateFilter}
motionOnly={motionOnly} motionOnly={motionOnly}
filterLabels={reviewLabels}
onUpdateFilter={updateFilter}
setMotionOnly={setMotionOnly} setMotionOnly={setMotionOnly}
/> />
) : ( ) : (

View File

@ -110,6 +110,18 @@ export function RecordingView({
() => chunkedTimeRange[selectedRangeIdx], () => chunkedTimeRange[selectedRangeIdx],
[selectedRangeIdx, chunkedTimeRange], [selectedRangeIdx, chunkedTimeRange],
); );
const reviewLabels = useMemo(() => {
const uniqueLabels = new Set<string>();
reviewItems?.forEach((rev) => {
rev.data.objects.forEach((obj) =>
uniqueLabels.add(obj.replace("-verified", "")),
);
rev.data.audio.forEach((aud) => uniqueLabels.add(aud));
});
return [...uniqueLabels];
}, [reviewItems]);
// export // export
@ -402,8 +414,9 @@ export function RecordingView({
filters={["date", "general"]} filters={["date", "general"]}
reviewSummary={reviewSummary} reviewSummary={reviewSummary}
filter={filter} filter={filter}
onUpdateFilter={updateFilter}
motionOnly={false} motionOnly={false}
filterLabels={reviewLabels}
onUpdateFilter={updateFilter}
setMotionOnly={() => {}} setMotionOnly={() => {}}
/> />
)} )}
@ -445,6 +458,7 @@ export function RecordingView({
latestTime={timeRange.before} latestTime={timeRange.before}
mode={exportMode} mode={exportMode}
range={exportRange} range={exportRange}
allLabels={reviewLabels}
onUpdateFilter={updateFilter} onUpdateFilter={updateFilter}
setRange={setExportRange} setRange={setExportRange}
setMode={setExportMode} setMode={setExportMode}