* Cleanup live activity indicators for cameras

* Rename to reviews and redirect events to reviews

* Use reviews

* Remove plural

* Simplify recordings view

* Adjust icon
This commit is contained in:
Nicolas Mowen
2024-04-10 07:40:17 -06:00
committed by GitHub
parent 503dfba719
commit 3d43c5e811
10 changed files with 131 additions and 136 deletions

View File

@@ -20,7 +20,7 @@ export function AnimatedEventCard({ event }: AnimatedEventCardProps) {
const navigate = useNavigate();
const onOpenReview = useCallback(() => {
navigate("events", {
navigate("review", {
state: {
severity: event.severity,
recording: {

View File

@@ -0,0 +1,14 @@
import { useEffect } from "react";
import { useNavigate } from "react-router-dom";
type RedirectProps = {
to: string;
};
export function Redirect({ to }: RedirectProps) {
const navigate = useNavigate();
useEffect(() => {
navigate(to);
}, [to, navigate]);
return <div />;
}

View File

@@ -19,7 +19,6 @@ const unsupportedErrorCodes = [
];
type HlsVideoPlayerProps = {
className: string;
children?: ReactNode;
videoRef: MutableRefObject<HTMLVideoElement | null>;
visible: boolean;
@@ -31,7 +30,6 @@ type HlsVideoPlayerProps = {
onPlaying?: () => void;
};
export default function HlsVideoPlayer({
className,
children,
videoRef,
visible,
@@ -91,116 +89,118 @@ export default function HlsVideoPlayer({
return (
<TransformWrapper minScale={1.0}>
<div
className={`relative ${className ?? ""} ${visible ? "visible" : "hidden"}`}
onMouseOver={
isDesktop
? () => {
setControls(true);
}
: undefined
}
onMouseOut={
isDesktop
? () => {
setControls(controlsOpen);
}
: undefined
}
onClick={isDesktop ? undefined : () => setControls(!controls)}
<TransformComponent
wrapperStyle={{
position: "relative",
display: visible ? undefined : "none",
width: "100%",
height: "100%",
}}
contentStyle={{
width: "100%",
height: isMobile ? "100%" : undefined,
}}
>
<TransformComponent
wrapperStyle={{
width: "100%",
height: "100%",
}}
contentStyle={{
width: "100%",
height: isMobile ? "100%" : undefined,
}}
>
<video
ref={videoRef}
className={`size-full bg-black rounded-2xl ${loadedMetadata ? "" : "invisible"}`}
preload="auto"
autoPlay
controls={false}
playsInline
muted
onPlay={() => {
setIsPlaying(true);
<video
ref={videoRef}
className={`size-full bg-black rounded-2xl ${loadedMetadata ? "" : "invisible"}`}
preload="auto"
autoPlay
controls={false}
playsInline
muted
onPlay={() => {
setIsPlaying(true);
if (isMobile) {
setControls(true);
setMobileCtrlTimeout(
setTimeout(() => setControls(false), 4000),
);
}
}}
onPlaying={onPlaying}
onPause={() => {
setIsPlaying(false);
if (isMobile && mobileCtrlTimeout) {
clearTimeout(mobileCtrlTimeout);
}
}}
onTimeUpdate={() =>
onTimeUpdate && videoRef.current
? onTimeUpdate(videoRef.current.currentTime)
: undefined
}
onLoadedData={onPlayerLoaded}
onLoadedMetadata={() => setLoadedMetadata(true)}
onEnded={onClipEnded}
onError={(e) => {
if (
!hlsRef.current &&
// @ts-expect-error code does exist
unsupportedErrorCodes.includes(e.target.error.code) &&
videoRef.current
) {
setLoadedMetadata(false);
setUseHlsCompat(true);
}
}}
/>
</TransformComponent>
<VideoControls
className="absolute bottom-5 left-1/2 -translate-x-1/2"
video={videoRef.current}
isPlaying={isPlaying}
show={controls}
controlsOpen={controlsOpen}
setControlsOpen={setControlsOpen}
playbackRate={videoRef.current?.playbackRate ?? 1}
hotKeys={hotKeys}
onPlayPause={(play) => {
if (!videoRef.current) {
return;
}
if (play) {
videoRef.current.play();
} else {
videoRef.current.pause();
if (isMobile) {
setControls(true);
setMobileCtrlTimeout(setTimeout(() => setControls(false), 4000));
}
}}
onSeek={(diff) => {
const currentTime = videoRef.current?.currentTime;
onPlaying={onPlaying}
onPause={() => {
setIsPlaying(false);
if (!videoRef.current || !currentTime) {
return;
if (isMobile && mobileCtrlTimeout) {
clearTimeout(mobileCtrlTimeout);
}
videoRef.current.currentTime = Math.max(0, currentTime + diff);
}}
onSetPlaybackRate={(rate) =>
videoRef.current ? (videoRef.current.playbackRate = rate) : null
onTimeUpdate={() =>
onTimeUpdate && videoRef.current
? onTimeUpdate(videoRef.current.currentTime)
: undefined
}
onLoadedData={onPlayerLoaded}
onLoadedMetadata={() => setLoadedMetadata(true)}
onEnded={onClipEnded}
onError={(e) => {
if (
!hlsRef.current &&
// @ts-expect-error code does exist
unsupportedErrorCodes.includes(e.target.error.code) &&
videoRef.current
) {
setLoadedMetadata(false);
setUseHlsCompat(true);
}
}}
/>
{children}
</div>
<div
className="absolute inset-0"
onMouseOver={
isDesktop
? () => {
setControls(true);
}
: undefined
}
onMouseOut={
isDesktop
? () => {
setControls(controlsOpen);
}
: undefined
}
onClick={isDesktop ? undefined : () => setControls(!controls)}
>
<div className={`size-full relative ${visible ? "" : "hidden"}`}>
<VideoControls
className="absolute bottom-5 left-1/2 -translate-x-1/2"
video={videoRef.current}
isPlaying={isPlaying}
show={controls}
controlsOpen={controlsOpen}
setControlsOpen={setControlsOpen}
playbackRate={videoRef.current?.playbackRate ?? 1}
hotKeys={hotKeys}
onPlayPause={(play) => {
if (!videoRef.current) {
return;
}
if (play) {
videoRef.current.play();
} else {
videoRef.current.pause();
}
}}
onSeek={(diff) => {
const currentTime = videoRef.current?.currentTime;
if (!videoRef.current || !currentTime) {
return;
}
videoRef.current.currentTime = Math.max(0, currentTime + diff);
}}
onSetPlaybackRate={(rate) =>
videoRef.current ? (videoRef.current.playbackRate = rate) : null
}
/>
{children}
</div>
</div>
</TransformComponent>
</TransformWrapper>
);
}

View File

@@ -7,10 +7,8 @@ import MSEPlayer from "./MsePlayer";
import JSMpegPlayer from "./JSMpegPlayer";
import { MdCircle } from "react-icons/md";
import { useCameraActivity } from "@/hooks/use-camera-activity";
import { useRecordingsState } from "@/api/ws";
import { LivePlayerMode } from "@/types/live";
import useCameraLiveMode from "@/hooks/use-camera-live-mode";
import CameraActivityIndicator from "../indicators/CameraActivityIndicator";
type LivePlayerProps = {
cameraRef?: (ref: HTMLDivElement | null) => void;
@@ -41,8 +39,7 @@ export default function LivePlayer({
}: LivePlayerProps) {
// camera activity
const { activeMotion, activeAudio, activeTracking } =
useCameraActivity(cameraConfig);
const { activeMotion, activeTracking } = useCameraActivity(cameraConfig);
const cameraActive = useMemo(
() =>
@@ -72,8 +69,6 @@ export default function LivePlayer({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [cameraActive, liveReady]);
const { payload: recording } = useRecordingsState(cameraConfig.name);
// camera still state
const stillReloadInterval = useMemo(() => {
@@ -171,15 +166,8 @@ export default function LivePlayer({
/>
</div>
<div className="absolute right-2 bottom-2 w-[40px]">
{(activeMotion ||
(cameraConfig.audio.enabled_in_config && activeAudio)) && (
<CameraActivityIndicator />
)}
</div>
<div className="absolute right-2 top-2 size-4">
{recording == "ON" && (
{activeMotion && (
<MdCircle className="size-2 drop-shadow-md shadow-danger text-danger animate-pulse" />
)}
</div>

View File

@@ -145,7 +145,7 @@ export default function VideoControls({
className={`px-4 py-2 flex justify-between items-center gap-8 text-primary z-50 bg-background/60 rounded-lg ${className ?? ""}`}
>
{video && features.volume && (
<div className="flex justify-normal items-center gap-2">
<div className="flex justify-normal items-center gap-2 cursor-pointer">
<VolumeIcon
className="size-5"
onClick={(e: React.MouseEvent) => {

View File

@@ -150,7 +150,6 @@ export default function DynamicVideoPlayer({
return (
<>
<HlsVideoPlayer
className={className ?? ""}
videoRef={playerRef}
visible={!(isScrubbing || isLoading)}
currentSource={source}