mirror of
https://github.com/scottlamb/moonfire-nvr.git
synced 2025-02-25 20:39:14 -05:00
new display options selector
This commit is contained in:
parent
1cb6a19a65
commit
4d6a8c98d9
98
ui/src/List/DisplaySelector.tsx
Normal file
98
ui/src/List/DisplaySelector.tsx
Normal 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;
|
@ -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`}
|
||||
|
@ -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: {
|
||||
marginRight: theme.spacing(2),
|
||||
marginBottom: theme.spacing(2),
|
||||
width: "max-content",
|
||||
"& .MuiCard-root": {
|
||||
marginRight: theme.spacing(2),
|
||||
marginBottom: theme.spacing(2),
|
||||
},
|
||||
},
|
||||
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>
|
||||
|
@ -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,
|
||||
});
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user