// 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 CircularProgress from "@material-ui/core/CircularProgress";
import React from "react";
import * as api from "../api";
import { useSnackbars } from "../snackbars";
import { Stream } from "../types";
import TableBody from "@material-ui/core/TableBody";
import TableCell from "@material-ui/core/TableCell";
import TableRow from "@material-ui/core/TableRow";
interface Props {
stream: Stream;
range90k: [number, number] | null;
setActiveRecording: (recording: [Stream, api.Recording] | null) => void;
formatTime: (time90k: number) => string;
}
const frameRateFmt = new Intl.NumberFormat([], {
maximumFractionDigits: 0,
});
const sizeFmt = new Intl.NumberFormat([], {
maximumFractionDigits: 1,
});
/**
* Creates a TableBody with a list of videos for a given
* stream and range90k.
*
* The parent is responsible for creating the greater table.
*
* When one is clicked, calls setActiveRecording.
*/
const VideoList = ({
stream,
range90k,
setActiveRecording,
formatTime,
}: Props) => {
const snackbars = useSnackbars();
const [
response,
setResponse,
] = React.useState | null>(null);
const [showLoading, setShowLoading] = React.useState(false);
React.useEffect(() => {
const abort = new AbortController();
const doFetch = async (signal: AbortSignal, range90k: [number, number]) => {
const req: api.RecordingsRequest = {
cameraUuid: stream.camera.uuid,
stream: stream.streamType,
startTime90k: range90k[0],
endTime90k: range90k[1],
};
let r = await api.recordings(req, { signal });
if (r.status === "success") {
// Sort recordings in descending order by start time.
r.response.recordings.sort((a, b) => b.startId - a.startId);
}
setResponse(r);
};
if (range90k !== null) {
doFetch(abort.signal, range90k);
const timeout = setTimeout(() => setShowLoading(true), 1000);
return () => {
abort.abort();
clearTimeout(timeout);
};
} else {
setResponse(null);
}
}, [range90k, snackbars, stream]);
let body = null;
if (response === null) {
if (showLoading) {
body = (
);
}
} else if (response.status === "error") {
body = (
Error: {response.status}
);
} else if (response.status === "success") {
const resp = response.response;
body = resp.recordings.map((r: api.Recording) => {
const vse = resp.videoSampleEntries[r.videoSampleEntryId];
const durationSec = (r.endTime90k - r.startTime90k) / 90000;
return (
setActiveRecording([stream, r])}
>
{formatTime(Math.max(r.startTime90k, range90k![0]))}
{formatTime(Math.min(r.endTime90k, range90k![1]))}
{vse.width}x{vse.height}
{frameRateFmt.format(r.videoSamples / durationSec)}
{sizeFmt.format(r.sampleFileBytes / 1048576)} MiB
{sizeFmt.format((r.sampleFileBytes / durationSec) * 0.000008)} Mbps
);
});
}
return {body};
};
export default VideoList;