mirror of
https://github.com/blakeblackshear/frigate.git
synced 2024-11-21 19:07:46 +01:00
Bug fixes (#11332)
* 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:
parent
82e443a5c3
commit
9680f2a574
@ -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)
|
||||||
|
@ -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=""
|
||||||
|
@ -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,
|
||||||
);
|
);
|
||||||
|
@ -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 (
|
||||||
|
@ -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 && (
|
||||||
|
@ -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}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
|
@ -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}
|
||||||
|
Loading…
Reference in New Issue
Block a user