diff --git a/ui/src/List/DisplaySelector.tsx b/ui/src/List/DisplaySelector.tsx
new file mode 100644
index 0000000..8d47719
--- /dev/null
+++ b/ui/src/List/DisplaySelector.tsx
@@ -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 (
+
+ {/* setSplit90k(e.target.value)}
+ variant="outlined"
+ >
+
+
+
+
+ */}
+
+
+ Max video duration
+
+
+
+
+ props.setTrimStartAndEnd(checked)
+ }
+ name="trim-start-and-end"
+ />
+ }
+ label="Trim start and end"
+ />
+ props.setTimestampTrack(checked)}
+ name="timestamp-track"
+ />
+ }
+ label="Timestamp track"
+ />
+
+ );
+};
+
+export default DisplaySelector;
diff --git a/ui/src/List/VideoList.tsx b/ui/src/List/VideoList.tsx
index dc2661f..743485e 100644
--- a/ui/src/List/VideoList.tsx
+++ b/ui/src/List/VideoList.tsx
@@ -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 (
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`}
diff --git a/ui/src/List/index.tsx b/ui/src/List/index.tsx
index 5bed262..1e6f04f 100644
--- a/ui/src/List/index.tsx
+++ b/ui/src/List/index.tsx
@@ -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(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}
/>
+
{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
)}
/>
diff --git a/ui/src/api.ts b/ui/src/api.ts
index 99cb2ec..204c4d7 100644
--- a/ui/src/api.ts
+++ b/ui/src/api.ts
@@ -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,
});
}