diff --git a/web/src/App.tsx b/web/src/App.tsx index 2f0853200..21ad0200e 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -7,6 +7,7 @@ import { isDesktop, isMobile } from "react-device-detect"; import Statusbar from "./components/Statusbar"; import Bottombar from "./components/navigation/Bottombar"; import { Suspense, lazy } from "react"; +import { Redirect } from "./components/navigation/Redirect"; const Live = lazy(() => import("@/pages/Live")); const Events = lazy(() => import("@/pages/Events")); @@ -35,7 +36,8 @@ function App() { } /> - } /> + } /> + } /> } /> } /> } /> diff --git a/web/src/components/card/AnimatedEventCard.tsx b/web/src/components/card/AnimatedEventCard.tsx index 03199e01a..85cfe3c3c 100644 --- a/web/src/components/card/AnimatedEventCard.tsx +++ b/web/src/components/card/AnimatedEventCard.tsx @@ -20,7 +20,7 @@ export function AnimatedEventCard({ event }: AnimatedEventCardProps) { const navigate = useNavigate(); const onOpenReview = useCallback(() => { - navigate("events", { + navigate("review", { state: { severity: event.severity, recording: { diff --git a/web/src/components/navigation/Redirect.tsx b/web/src/components/navigation/Redirect.tsx new file mode 100644 index 000000000..d905666a9 --- /dev/null +++ b/web/src/components/navigation/Redirect.tsx @@ -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
; +} diff --git a/web/src/components/player/HlsVideoPlayer.tsx b/web/src/components/player/HlsVideoPlayer.tsx index 2bbf4d801..4c496fcb5 100644 --- a/web/src/components/player/HlsVideoPlayer.tsx +++ b/web/src/components/player/HlsVideoPlayer.tsx @@ -19,7 +19,6 @@ const unsupportedErrorCodes = [ ]; type HlsVideoPlayerProps = { - className: string; children?: ReactNode; videoRef: MutableRefObject; 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 ( -
{ - setControls(true); - } - : undefined - } - onMouseOut={ - isDesktop - ? () => { - setControls(controlsOpen); - } - : undefined - } - onClick={isDesktop ? undefined : () => setControls(!controls)} + - - - { - 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} -
+
{ + setControls(true); + } + : undefined + } + onMouseOut={ + isDesktop + ? () => { + setControls(controlsOpen); + } + : undefined + } + onClick={isDesktop ? undefined : () => setControls(!controls)} + > +
+ { + 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} +
+
+
); } diff --git a/web/src/components/player/LivePlayer.tsx b/web/src/components/player/LivePlayer.tsx index 22559105a..925d7c88f 100644 --- a/web/src/components/player/LivePlayer.tsx +++ b/web/src/components/player/LivePlayer.tsx @@ -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({ />
-
- {(activeMotion || - (cameraConfig.audio.enabled_in_config && activeAudio)) && ( - - )} -
-
- {recording == "ON" && ( + {activeMotion && ( )}
diff --git a/web/src/components/player/VideoControls.tsx b/web/src/components/player/VideoControls.tsx index 153cd59f5..429a4185b 100644 --- a/web/src/components/player/VideoControls.tsx +++ b/web/src/components/player/VideoControls.tsx @@ -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 && ( -
+
{ diff --git a/web/src/components/player/dynamic/DynamicVideoPlayer.tsx b/web/src/components/player/dynamic/DynamicVideoPlayer.tsx index f0c72032c..f9d208fd0 100644 --- a/web/src/components/player/dynamic/DynamicVideoPlayer.tsx +++ b/web/src/components/player/dynamic/DynamicVideoPlayer.tsx @@ -150,7 +150,6 @@ export default function DynamicVideoPlayer({ return ( <> { if (!event) { @@ -63,9 +57,6 @@ export function useCameraActivity( return { activeTracking: hasActiveObjects, activeMotion: detectingMotion == "ON", - activeAudio: camera.audio.enabled_in_config - ? audioRms >= camera.audio.min_volume - : false, }; } diff --git a/web/src/pages/site-navigation.ts b/web/src/pages/site-navigation.ts index 2810c8371..18fd82e4d 100644 --- a/web/src/pages/site-navigation.ts +++ b/web/src/pages/site-navigation.ts @@ -1,6 +1,7 @@ import Logo from "@/components/Logo"; -import { FaCompactDisc, FaFlag, FaVideo } from "react-icons/fa"; +import { FaCompactDisc, FaVideo } from "react-icons/fa"; import { LuConstruction } from "react-icons/lu"; +import { MdVideoLibrary } from "react-icons/md"; export const navbarLinks = [ { @@ -11,9 +12,9 @@ export const navbarLinks = [ }, { id: 2, - icon: FaFlag, - title: "Events", - url: "/events", + icon: MdVideoLibrary, + title: "Review", + url: "/review", }, { id: 3, diff --git a/web/src/views/live/LiveCameraView.tsx b/web/src/views/live/LiveCameraView.tsx index d0e3509c0..bc1d8c2b7 100644 --- a/web/src/views/live/LiveCameraView.tsx +++ b/web/src/views/live/LiveCameraView.tsx @@ -235,7 +235,7 @@ export default function LiveCameraView({ camera }: LiveCameraViewProps) { className="flex items-center gap-2.5 rounded-lg" size="sm" onClick={() => { - navigate("events", { + navigate("review", { state: { severity: "alert", recording: {