diff --git a/web/src/components/player/BirdseyeLivePlayer.tsx b/web/src/components/player/BirdseyeLivePlayer.tsx index 25b99a401..127933f09 100644 --- a/web/src/components/player/BirdseyeLivePlayer.tsx +++ b/web/src/components/player/BirdseyeLivePlayer.tsx @@ -5,12 +5,14 @@ import JSMpegPlayer from "./JSMpegPlayer"; import MSEPlayer from "./MsePlayer"; import { LivePlayerMode } from "@/types/live"; import { cn } from "@/lib/utils"; +import React from "react"; type LivePlayerProps = { className?: string; birdseyeConfig: BirdseyeConfig; liveMode: LivePlayerMode; onClick?: () => void; + containerRef?: React.MutableRefObject; }; export default function BirdseyeLivePlayer({ @@ -18,6 +20,7 @@ export default function BirdseyeLivePlayer({ birdseyeConfig, liveMode, onClick, + containerRef, }: LivePlayerProps) { let player; if (liveMode == "webrtc") { @@ -50,6 +53,7 @@ export default function BirdseyeLivePlayer({ camera="birdseye" width={birdseyeConfig.width} height={birdseyeConfig.height} + containerRef={containerRef} /> ); } else { diff --git a/web/src/components/player/JSMpegPlayer.tsx b/web/src/components/player/JSMpegPlayer.tsx index 937502f60..2900defcf 100644 --- a/web/src/components/player/JSMpegPlayer.tsx +++ b/web/src/components/player/JSMpegPlayer.tsx @@ -1,13 +1,15 @@ import { baseUrl } from "@/api/baseUrl"; +import { useResizeObserver } from "@/hooks/resize-observer"; // @ts-expect-error we know this doesn't have types import JSMpeg from "@cycjimmy/jsmpeg-player"; -import { useEffect, useRef } from "react"; +import React, { useEffect, useMemo, useRef } from "react"; type JSMpegPlayerProps = { className?: string; camera: string; width: number; height: number; + containerRef?: React.MutableRefObject; }; export default function JSMpegPlayer({ @@ -15,10 +17,57 @@ export default function JSMpegPlayer({ width, height, className, + containerRef, }: JSMpegPlayerProps) { const url = `${baseUrl.replace(/^http/, "ws")}live/jsmpeg/${camera}`; const playerRef = useRef(null); - const containerRef = useRef(null); + const internalContainerRef = useRef(null); + + const [{ width: containerWidth, height: containerHeight }] = + useResizeObserver(containerRef ?? internalContainerRef); + + const stretch = true; + const aspectRatio = width / height; + + const fitAspect = useMemo( + () => containerWidth / containerHeight, + [containerWidth, containerHeight], + ); + + const scaledHeight = useMemo(() => { + if (containerRef?.current && width && height) { + const scaledHeight = + aspectRatio < (fitAspect ?? 0) + ? Math.floor( + Math.min(containerHeight, containerRef.current?.clientHeight), + ) + : aspectRatio > fitAspect + ? Math.floor(containerWidth / aspectRatio) + : Math.floor(containerWidth / aspectRatio) / 1.5; + const finalHeight = stretch + ? scaledHeight + : Math.min(scaledHeight, height); + + if (finalHeight > 0) { + return finalHeight; + } + } + }, [ + aspectRatio, + containerWidth, + containerHeight, + fitAspect, + height, + width, + stretch, + containerRef, + ]); + + const scaledWidth = useMemo(() => { + if (aspectRatio && scaledHeight) { + return Math.ceil(scaledHeight * aspectRatio); + } + }, [scaledHeight, aspectRatio]); useEffect(() => { if (!playerRef.current) { @@ -28,7 +77,7 @@ export default function JSMpegPlayer({ const video = new JSMpeg.VideoElement( playerRef.current, url, - {}, + { canvas: "#video-canvas" }, { protocols: [], audio: false, videoBufferSize: 1024 * 1024 * 4 }, ); @@ -44,12 +93,16 @@ export default function JSMpegPlayer({ }, [url]); return ( -
-
+
+
+ +
); } diff --git a/web/src/components/player/LivePlayer.tsx b/web/src/components/player/LivePlayer.tsx index 80a382a9e..fbdff3200 100644 --- a/web/src/components/player/LivePlayer.tsx +++ b/web/src/components/player/LivePlayer.tsx @@ -28,6 +28,7 @@ type LivePlayerProps = { pip?: boolean; onClick?: () => void; setFullResolution?: React.Dispatch>; + containerRef?: React.MutableRefObject; }; export default function LivePlayer({ @@ -43,6 +44,7 @@ export default function LivePlayer({ pip, onClick, setFullResolution, + containerRef, }: LivePlayerProps) { // camera activity @@ -138,10 +140,11 @@ export default function LivePlayer({ if (cameraActive || !showStillWithoutActivity) { player = ( ); } else { @@ -156,9 +159,7 @@ export default function LivePlayer({ ref={cameraRef} data-camera={cameraConfig.name} className={cn( - "relative flex justify-center", - liveMode === "jsmpeg" ? "size-full" : "w-full", - "cursor-pointer outline", + "relative flex w-full cursor-pointer justify-center outline", activeTracking ? "outline-3 rounded-lg shadow-severity_alert outline-severity_alert md:rounded-2xl" : "outline-0 outline-background", diff --git a/web/src/views/live/LiveBirdseyeView.tsx b/web/src/views/live/LiveBirdseyeView.tsx index ee61c6688..6adfdd177 100644 --- a/web/src/views/live/LiveBirdseyeView.tsx +++ b/web/src/views/live/LiveBirdseyeView.tsx @@ -23,6 +23,7 @@ export default function LiveBirdseyeView() { const navigate = useNavigate(); const { isPortrait } = useMobileOrientation(); const mainRef = useRef(null); + const containerRef = useRef(null); const [{ width: windowWidth, height: windowHeight }] = useResizeObserver(window); @@ -75,7 +76,7 @@ export default function LiveBirdseyeView() { return "absolute inset-y-2 left-[50%] -translate-x-[50%]"; } } else { - return "absolute top-2 bottom-2 left-[50%] -translate-x-[50%]"; + return "absolute top-0 bottom-0 left-[50%] -translate-x-[50%]"; } }, [cameraAspectRatio, fullscreen, isPortrait]); @@ -159,30 +160,33 @@ export default function LiveBirdseyeView() {
- -
+ - -
-
+
+ +
+ + ); diff --git a/web/src/views/live/LiveCameraView.tsx b/web/src/views/live/LiveCameraView.tsx index 3d3ea23c7..096f0f6f6 100644 --- a/web/src/views/live/LiveCameraView.tsx +++ b/web/src/views/live/LiveCameraView.tsx @@ -85,6 +85,7 @@ export default function LiveCameraView({ const navigate = useNavigate(); const { isPortrait } = useMobileOrientation(); const mainRef = useRef(null); + const containerRef = useRef(null); const [{ width: windowWidth, height: windowHeight }] = useResizeObserver(window); @@ -389,48 +390,51 @@ export default function LiveCameraView({ - -
+ - -
- {camera.onvif.host != "" && ( - - )} -
+
+ +
+ {camera.onvif.host != "" && ( + + )} + + );