// 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 Box from "@mui/material/Box"; import Modal from "@mui/material/Modal"; import Paper from "@mui/material/Paper"; import { Theme } from "@mui/material/styles"; import { makeStyles } from "@mui/styles"; import Table from "@mui/material/Table"; import TableContainer from "@mui/material/TableContainer"; import utcToZonedTime from "date-fns-tz/utcToZonedTime"; import format from "date-fns/format"; import React, { useMemo, useReducer, useState } from "react"; import * as api from "../api"; import { Stream } from "../types"; import DisplaySelector, { DEFAULT_DURATION } from "./DisplaySelector"; import StreamMultiSelector from "./StreamMultiSelector"; import TimerangeSelector from "./TimerangeSelector"; import VideoList from "./VideoList"; import { useLayoutEffect } from "react"; import { fillAspect } from "../aspect"; import useResizeObserver from "@react-hook/resize-observer"; import { useSearchParams } from "react-router-dom"; import { FrameProps } from "../App"; import IconButton from "@mui/material/IconButton"; import FilterList from "@mui/icons-material/FilterList"; const useStyles = makeStyles((theme: Theme) => ({ root: { display: "flex", flexWrap: "wrap", margin: theme.spacing(2), }, selectors: { width: "max-content", "& .MuiCard-root": { marginRight: theme.spacing(2), marginBottom: theme.spacing(2), }, }, videoTable: { flexGrow: 1, width: "max-content", height: "max-content", "& .streamHeader": { background: theme.palette.primary.light, color: theme.palette.primary.contrastText, }, "& .MuiTableBody-root:not(:last-child):after": { content: "''", display: "block", height: theme.spacing(2), }, "& tbody .recording": { cursor: "pointer", }, "& .opt": { [theme.breakpoints.down("lg")]: { display: "none", }, }, }, // When there's a video modal up, make the content as large as possible // without distorting it. Center it in the screen and ensure that the video // element only takes up the space actually used by the content, so that // clicking outside it will dismiss the modal. videoModal: { display: "flex", alignItems: "center", justifyContent: "center", "& video": { objectFit: "fill", }, }, })); interface FullScreenVideoProps { src: string; aspect: [number, number]; } /** * A video sized for the entire document window constrained to aspect ratio. * This is particularly helpful for Firefox (89), which doesn't honor the * pixel aspect ratio specified in .mp4 files. Thus we need to specify it * out-of-band. */ const FullScreenVideo = ({ src, aspect }: FullScreenVideoProps) => { const ref = React.useRef(null); useLayoutEffect(() => { fillAspect(document.body.getBoundingClientRect(), ref, aspect); }); useResizeObserver(document.body, (entry: ResizeObserverEntry) => { fillAspect(entry.contentRect, ref, aspect); }); return