new display options selector

This commit is contained in:
Scott Lamb 2021-03-16 14:58:47 -07:00
parent 1cb6a19a65
commit 4d6a8c98d9
4 changed files with 136 additions and 7 deletions

View File

@ -0,0 +1,98 @@
// This file is part of Moonfire NVR, a security camera network video recorder.
// Copyright (C) 2021 The Moonfire NVR Authors; see AUTHORS and LICENSE.txt.
// SPDX-License-Identifier: GPL-v3.0-or-later WITH GPL-3.0-linking-exception
import Card from "@material-ui/core/Card";
import Checkbox from "@material-ui/core/Checkbox";
import InputLabel from "@material-ui/core/InputLabel";
import FormControl from "@material-ui/core/FormControl";
import MenuItem from "@material-ui/core/MenuItem";
import Select from "@material-ui/core/Select";
import React from "react";
import { useTheme } from "@material-ui/core/styles";
import FormControlLabel from "@material-ui/core/FormControlLabel";
interface Props {
split90k?: number;
setSplit90k: (split90k?: number) => void;
trimStartAndEnd: boolean;
setTrimStartAndEnd: (trimStartAndEnd: boolean) => void;
timestampTrack: boolean;
setTimestampTrack: (timestampTrack: boolean) => void;
}
/**
* Returns a card for setting options relating to how videos are displayed.
*/
const DisplaySelector = (props: Props) => {
const theme = useTheme();
return (
<Card
sx={{
padding: theme.spacing(1),
display: "flex",
flexDirection: "column",
}}
>
{/*<TextField
select
label="Max video duration"
value={split90k}
onChange={(e) => setSplit90k(e.target.value)}
variant="outlined"
>
<MenuItem value={60 * 60 * 90000}>1 hour</MenuItem>
<MenuItem value={4 * 60 * 60 * 90000}>4 hours</MenuItem>
<MenuItem value={24 * 60 * 60 * 90000}>24 hours</MenuItem>
<MenuItem value={undefined}>infinite</MenuItem>
</TextField>*/}
<FormControl fullWidth variant="outlined">
<InputLabel id="split90k-label" shrink>
Max video duration
</InputLabel>
<Select
labelId="split90k-label"
id="split90k"
value={props.split90k}
onChange={(e) => props.setSplit90k(e.target.value)}
displayEmpty
>
<MenuItem value={60 * 60 * 90000}>1 hour</MenuItem>
<MenuItem value={4 * 60 * 60 * 90000}>4 hours</MenuItem>
<MenuItem value={24 * 60 * 60 * 90000}>24 hours</MenuItem>
<MenuItem value={undefined}>infinite</MenuItem>
</Select>
</FormControl>
<FormControlLabel
title="Trim each segment of video so that it is fully
contained within the select time range. When this is not selected,
all segments will overlap with the selected time range but may start
and/or end outside it."
control={
<Checkbox
checked={props.trimStartAndEnd}
onChange={(_, checked: boolean) =>
props.setTrimStartAndEnd(checked)
}
name="trim-start-and-end"
/>
}
label="Trim start and end"
/>
<FormControlLabel
title="Include a text track in each .mp4 with the
timestamp at which the video was recorded."
control={
<Checkbox
checked={props.timestampTrack}
onChange={(_, checked: boolean) => props.setTimestampTrack(checked)}
name="timestamp-track"
/>
}
label="Timestamp track"
/>
</Card>
);
};
export default DisplaySelector;

View File

@ -15,6 +15,8 @@ import Alert from "@material-ui/core/Alert";
interface Props {
stream: Stream;
range90k: [number, number] | null;
split90k?: number;
trimStartAndEnd: boolean;
setActiveRecording: (recording: [Stream, api.Recording] | null) => void;
formatTime: (time90k: number) => string;
}
@ -85,6 +87,8 @@ const Row = ({
const VideoList = ({
stream,
range90k,
split90k,
trimStartAndEnd,
setActiveRecording,
formatTime,
}: Props) => {
@ -102,6 +106,7 @@ const VideoList = ({
stream: stream.streamType,
startTime90k: range90k[0],
endTime90k: range90k[1],
split90k,
};
let response = await api.recordings(req, { signal });
if (response.status === "success") {
@ -122,7 +127,7 @@ const VideoList = ({
clearTimeout(timerId);
};
}
}, [range90k, snackbars, stream]);
}, [range90k, split90k, snackbars, stream]);
if (state === null) {
return null;
@ -154,13 +159,19 @@ const VideoList = ({
const vse = resp.videoSampleEntries[r.videoSampleEntryId];
const durationSec = (r.endTime90k - r.startTime90k) / 90000;
const rate = (r.sampleFileBytes / durationSec) * 0.000008;
const start = trimStartAndEnd
? Math.max(r.startTime90k, state.range90k[0])
: r.startTime90k;
const end = trimStartAndEnd
? Math.min(r.endTime90k, state.range90k[1])
: r.endTime90k;
return (
<Row
key={r.startId}
className="recording"
onClick={() => setActiveRecording([stream, r])}
start={formatTime(Math.max(r.startTime90k, state.range90k[0]))}
end={formatTime(Math.min(r.endTime90k, state.range90k[1]))}
start={formatTime(start)}
end={formatTime(end)}
resolution={`${vse.width}x${vse.height}`}
fps={frameRateFmt.format(r.videoSamples / durationSec)}
storage={`${sizeFmt.format(r.sampleFileBytes / 1048576)} MiB`}

View File

@ -15,6 +15,7 @@ import TableContainer from "@material-ui/core/TableContainer";
import Paper from "@material-ui/core/Paper";
import StreamMultiSelector from "./StreamMultiSelector";
import TimerangeSelector from "./TimerangeSelector";
import DisplaySelector from "./DisplaySelector";
const useStyles = makeStyles((theme: Theme) => ({
root: {
@ -23,9 +24,11 @@ const useStyles = makeStyles((theme: Theme) => ({
margin: theme.spacing(2),
},
selectors: {
width: "max-content",
"& .MuiCard-root": {
marginRight: theme.spacing(2),
marginBottom: theme.spacing(2),
width: "max-content",
},
},
video: {
objectFit: "contain",
@ -78,6 +81,11 @@ const Main = ({ cameras, timeZoneName }: Props) => {
/** Selected time range. */
const [range90k, setRange90k] = useState<[number, number] | null>(null);
const [split90k, setSplit90k] = useState<number | undefined>(undefined);
const [trimStartAndEnd, setTrimStartAndEnd] = useState(true);
const [timestampTrack, setTimestampTrack] = useState(true);
const [activeRecording, setActiveRecording] = useState<
[Stream, api.Recording] | null
>(null);
@ -97,6 +105,8 @@ const Main = ({ cameras, timeZoneName }: Props) => {
key={`${s.camera.uuid}-${s.streamType}`}
stream={s}
range90k={range90k}
split90k={split90k}
trimStartAndEnd={trimStartAndEnd}
setActiveRecording={setActiveRecording}
formatTime={formatTime}
/>
@ -125,6 +135,14 @@ const Main = ({ cameras, timeZoneName }: Props) => {
setRange90k={setRange90k}
timeZoneName={timeZoneName}
/>
<DisplaySelector
split90k={split90k}
setSplit90k={setSplit90k}
trimStartAndEnd={trimStartAndEnd}
setTrimStartAndEnd={setTrimStartAndEnd}
timestampTrack={timestampTrack}
setTimestampTrack={setTimestampTrack}
/>
</div>
{videoLists.length > 0 && recordingsTable}
{activeRecording != null && (
@ -138,7 +156,8 @@ const Main = ({ cameras, timeZoneName }: Props) => {
activeRecording[0].camera.uuid,
activeRecording[0].streamType,
activeRecording[1],
range90k!
timestampTrack,
trimStartAndEnd ? range90k! : undefined
)}
/>
</Modal>

View File

@ -315,6 +315,7 @@ export function recordingUrl(
cameraUuid: string,
stream: StreamType,
r: Recording,
timestampTrack: boolean,
trimToRange90k?: [number, number]
): string {
let s = `${r.startId}`;
@ -340,6 +341,6 @@ export function recordingUrl(
}
return withQuery(`/api/cameras/${cameraUuid}/${stream}/view.mp4`, {
s,
ts: true,
ts: timestampTrack,
});
}