mirror of
https://github.com/blakeblackshear/frigate.git
synced 2024-11-21 19:07:46 +01:00
Make jsmpeg players fully responsive (#11567)
* make jsmpeg canvas responsive * make birdseye responsive too
This commit is contained in:
parent
5900a2a4ba
commit
c1330704cf
@ -5,12 +5,14 @@ import JSMpegPlayer from "./JSMpegPlayer";
|
|||||||
import MSEPlayer from "./MsePlayer";
|
import MSEPlayer from "./MsePlayer";
|
||||||
import { LivePlayerMode } from "@/types/live";
|
import { LivePlayerMode } from "@/types/live";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
type LivePlayerProps = {
|
type LivePlayerProps = {
|
||||||
className?: string;
|
className?: string;
|
||||||
birdseyeConfig: BirdseyeConfig;
|
birdseyeConfig: BirdseyeConfig;
|
||||||
liveMode: LivePlayerMode;
|
liveMode: LivePlayerMode;
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
|
containerRef?: React.MutableRefObject<HTMLDivElement | null>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function BirdseyeLivePlayer({
|
export default function BirdseyeLivePlayer({
|
||||||
@ -18,6 +20,7 @@ export default function BirdseyeLivePlayer({
|
|||||||
birdseyeConfig,
|
birdseyeConfig,
|
||||||
liveMode,
|
liveMode,
|
||||||
onClick,
|
onClick,
|
||||||
|
containerRef,
|
||||||
}: LivePlayerProps) {
|
}: LivePlayerProps) {
|
||||||
let player;
|
let player;
|
||||||
if (liveMode == "webrtc") {
|
if (liveMode == "webrtc") {
|
||||||
@ -50,6 +53,7 @@ export default function BirdseyeLivePlayer({
|
|||||||
camera="birdseye"
|
camera="birdseye"
|
||||||
width={birdseyeConfig.width}
|
width={birdseyeConfig.width}
|
||||||
height={birdseyeConfig.height}
|
height={birdseyeConfig.height}
|
||||||
|
containerRef={containerRef}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,13 +1,15 @@
|
|||||||
import { baseUrl } from "@/api/baseUrl";
|
import { baseUrl } from "@/api/baseUrl";
|
||||||
|
import { useResizeObserver } from "@/hooks/resize-observer";
|
||||||
// @ts-expect-error we know this doesn't have types
|
// @ts-expect-error we know this doesn't have types
|
||||||
import JSMpeg from "@cycjimmy/jsmpeg-player";
|
import JSMpeg from "@cycjimmy/jsmpeg-player";
|
||||||
import { useEffect, useRef } from "react";
|
import React, { useEffect, useMemo, useRef } from "react";
|
||||||
|
|
||||||
type JSMpegPlayerProps = {
|
type JSMpegPlayerProps = {
|
||||||
className?: string;
|
className?: string;
|
||||||
camera: string;
|
camera: string;
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
|
containerRef?: React.MutableRefObject<HTMLDivElement | null>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function JSMpegPlayer({
|
export default function JSMpegPlayer({
|
||||||
@ -15,10 +17,57 @@ export default function JSMpegPlayer({
|
|||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
className,
|
className,
|
||||||
|
containerRef,
|
||||||
}: JSMpegPlayerProps) {
|
}: JSMpegPlayerProps) {
|
||||||
const url = `${baseUrl.replace(/^http/, "ws")}live/jsmpeg/${camera}`;
|
const url = `${baseUrl.replace(/^http/, "ws")}live/jsmpeg/${camera}`;
|
||||||
const playerRef = useRef<HTMLDivElement | null>(null);
|
const playerRef = useRef<HTMLDivElement | null>(null);
|
||||||
const containerRef = useRef<HTMLDivElement | null>(null);
|
const internalContainerRef = useRef<HTMLDivElement | null>(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(() => {
|
useEffect(() => {
|
||||||
if (!playerRef.current) {
|
if (!playerRef.current) {
|
||||||
@ -28,7 +77,7 @@ export default function JSMpegPlayer({
|
|||||||
const video = new JSMpeg.VideoElement(
|
const video = new JSMpeg.VideoElement(
|
||||||
playerRef.current,
|
playerRef.current,
|
||||||
url,
|
url,
|
||||||
{},
|
{ canvas: "#video-canvas" },
|
||||||
{ protocols: [], audio: false, videoBufferSize: 1024 * 1024 * 4 },
|
{ protocols: [], audio: false, videoBufferSize: 1024 * 1024 * 4 },
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -44,12 +93,16 @@ export default function JSMpegPlayer({
|
|||||||
}, [url]);
|
}, [url]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={className} ref={containerRef}>
|
<div className={className} ref={internalContainerRef}>
|
||||||
<div
|
<div ref={playerRef} className="jsmpeg">
|
||||||
ref={playerRef}
|
<canvas
|
||||||
className="jsmpeg h-full"
|
id="video-canvas"
|
||||||
style={{ aspectRatio: width / height }}
|
style={{
|
||||||
/>
|
width: scaledWidth ?? width,
|
||||||
|
height: scaledHeight ?? height,
|
||||||
|
}}
|
||||||
|
></canvas>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -28,6 +28,7 @@ type LivePlayerProps = {
|
|||||||
pip?: boolean;
|
pip?: boolean;
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
setFullResolution?: React.Dispatch<React.SetStateAction<VideoResolutionType>>;
|
setFullResolution?: React.Dispatch<React.SetStateAction<VideoResolutionType>>;
|
||||||
|
containerRef?: React.MutableRefObject<HTMLDivElement | null>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function LivePlayer({
|
export default function LivePlayer({
|
||||||
@ -43,6 +44,7 @@ export default function LivePlayer({
|
|||||||
pip,
|
pip,
|
||||||
onClick,
|
onClick,
|
||||||
setFullResolution,
|
setFullResolution,
|
||||||
|
containerRef,
|
||||||
}: LivePlayerProps) {
|
}: LivePlayerProps) {
|
||||||
// camera activity
|
// camera activity
|
||||||
|
|
||||||
@ -138,10 +140,11 @@ export default function LivePlayer({
|
|||||||
if (cameraActive || !showStillWithoutActivity) {
|
if (cameraActive || !showStillWithoutActivity) {
|
||||||
player = (
|
player = (
|
||||||
<JSMpegPlayer
|
<JSMpegPlayer
|
||||||
className="flex size-full justify-center overflow-hidden rounded-lg md:rounded-2xl"
|
className="flex justify-center overflow-hidden rounded-lg md:rounded-2xl"
|
||||||
camera={cameraConfig.live.stream_name}
|
camera={cameraConfig.live.stream_name}
|
||||||
width={cameraConfig.detect.width}
|
width={cameraConfig.detect.width}
|
||||||
height={cameraConfig.detect.height}
|
height={cameraConfig.detect.height}
|
||||||
|
containerRef={containerRef}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@ -156,9 +159,7 @@ export default function LivePlayer({
|
|||||||
ref={cameraRef}
|
ref={cameraRef}
|
||||||
data-camera={cameraConfig.name}
|
data-camera={cameraConfig.name}
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative flex justify-center",
|
"relative flex w-full cursor-pointer justify-center outline",
|
||||||
liveMode === "jsmpeg" ? "size-full" : "w-full",
|
|
||||||
"cursor-pointer outline",
|
|
||||||
activeTracking
|
activeTracking
|
||||||
? "outline-3 rounded-lg shadow-severity_alert outline-severity_alert md:rounded-2xl"
|
? "outline-3 rounded-lg shadow-severity_alert outline-severity_alert md:rounded-2xl"
|
||||||
: "outline-0 outline-background",
|
: "outline-0 outline-background",
|
||||||
|
@ -23,6 +23,7 @@ export default function LiveBirdseyeView() {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { isPortrait } = useMobileOrientation();
|
const { isPortrait } = useMobileOrientation();
|
||||||
const mainRef = useRef<HTMLDivElement | null>(null);
|
const mainRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
const [{ width: windowWidth, height: windowHeight }] =
|
const [{ width: windowWidth, height: windowHeight }] =
|
||||||
useResizeObserver(window);
|
useResizeObserver(window);
|
||||||
|
|
||||||
@ -75,7 +76,7 @@ export default function LiveBirdseyeView() {
|
|||||||
return "absolute inset-y-2 left-[50%] -translate-x-[50%]";
|
return "absolute inset-y-2 left-[50%] -translate-x-[50%]";
|
||||||
}
|
}
|
||||||
} else {
|
} 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]);
|
}, [cameraAspectRatio, fullscreen, isPortrait]);
|
||||||
|
|
||||||
@ -159,6 +160,7 @@ export default function LiveBirdseyeView() {
|
|||||||
</div>
|
</div>
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="player-container" className="size-full" ref={containerRef}>
|
||||||
<TransformComponent
|
<TransformComponent
|
||||||
wrapperStyle={{
|
wrapperStyle={{
|
||||||
width: "100%",
|
width: "100%",
|
||||||
@ -177,13 +179,15 @@ export default function LiveBirdseyeView() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<BirdseyeLivePlayer
|
<BirdseyeLivePlayer
|
||||||
className="h-full"
|
className={`${fullscreen ? "*:rounded-none" : ""}`}
|
||||||
birdseyeConfig={config.birdseye}
|
birdseyeConfig={config.birdseye}
|
||||||
liveMode={preferredLiveMode}
|
liveMode={preferredLiveMode}
|
||||||
|
containerRef={containerRef}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</TransformComponent>
|
</TransformComponent>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</TransformWrapper>
|
</TransformWrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -85,6 +85,7 @@ export default function LiveCameraView({
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { isPortrait } = useMobileOrientation();
|
const { isPortrait } = useMobileOrientation();
|
||||||
const mainRef = useRef<HTMLDivElement | null>(null);
|
const mainRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
const [{ width: windowWidth, height: windowHeight }] =
|
const [{ width: windowWidth, height: windowHeight }] =
|
||||||
useResizeObserver(window);
|
useResizeObserver(window);
|
||||||
|
|
||||||
@ -389,6 +390,7 @@ export default function LiveCameraView({
|
|||||||
</div>
|
</div>
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="player-container" className="size-full" ref={containerRef}>
|
||||||
<TransformComponent
|
<TransformComponent
|
||||||
wrapperStyle={{
|
wrapperStyle={{
|
||||||
width: "100%",
|
width: "100%",
|
||||||
@ -411,7 +413,7 @@ export default function LiveCameraView({
|
|||||||
>
|
>
|
||||||
<LivePlayer
|
<LivePlayer
|
||||||
key={camera.name}
|
key={camera.name}
|
||||||
className={`m-1 ${fullscreen ? "*:rounded-none" : ""}`}
|
className={`${fullscreen ? "*:rounded-none" : ""}`}
|
||||||
windowVisible
|
windowVisible
|
||||||
showStillWithoutActivity={false}
|
showStillWithoutActivity={false}
|
||||||
cameraConfig={camera}
|
cameraConfig={camera}
|
||||||
@ -421,6 +423,7 @@ export default function LiveCameraView({
|
|||||||
preferredLiveMode={preferredLiveMode}
|
preferredLiveMode={preferredLiveMode}
|
||||||
pip={pip}
|
pip={pip}
|
||||||
setFullResolution={setFullResolution}
|
setFullResolution={setFullResolution}
|
||||||
|
containerRef={containerRef}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{camera.onvif.host != "" && (
|
{camera.onvif.host != "" && (
|
||||||
@ -432,6 +435,7 @@ export default function LiveCameraView({
|
|||||||
)}
|
)}
|
||||||
</TransformComponent>
|
</TransformComponent>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</TransformWrapper>
|
</TransformWrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user