mirror of
https://github.com/blakeblackshear/frigate.git
synced 2024-12-19 19:06:16 +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 { 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<HTMLDivElement | null>;
|
||||
};
|
||||
|
||||
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 {
|
||||
|
@ -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<HTMLDivElement | null>;
|
||||
};
|
||||
|
||||
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<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(() => {
|
||||
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 (
|
||||
<div className={className} ref={containerRef}>
|
||||
<div
|
||||
ref={playerRef}
|
||||
className="jsmpeg h-full"
|
||||
style={{ aspectRatio: width / height }}
|
||||
/>
|
||||
<div className={className} ref={internalContainerRef}>
|
||||
<div ref={playerRef} className="jsmpeg">
|
||||
<canvas
|
||||
id="video-canvas"
|
||||
style={{
|
||||
width: scaledWidth ?? width,
|
||||
height: scaledHeight ?? height,
|
||||
}}
|
||||
></canvas>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -28,6 +28,7 @@ type LivePlayerProps = {
|
||||
pip?: boolean;
|
||||
onClick?: () => void;
|
||||
setFullResolution?: React.Dispatch<React.SetStateAction<VideoResolutionType>>;
|
||||
containerRef?: React.MutableRefObject<HTMLDivElement | null>;
|
||||
};
|
||||
|
||||
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 = (
|
||||
<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}
|
||||
width={cameraConfig.detect.width}
|
||||
height={cameraConfig.detect.height}
|
||||
containerRef={containerRef}
|
||||
/>
|
||||
);
|
||||
} 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",
|
||||
|
@ -23,6 +23,7 @@ export default function LiveBirdseyeView() {
|
||||
const navigate = useNavigate();
|
||||
const { isPortrait } = useMobileOrientation();
|
||||
const mainRef = useRef<HTMLDivElement | null>(null);
|
||||
const containerRef = useRef<HTMLDivElement>(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() {
|
||||
</div>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
<TransformComponent
|
||||
wrapperStyle={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
}}
|
||||
contentStyle={{
|
||||
position: "relative",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className={growClassName}
|
||||
style={{
|
||||
aspectRatio: aspectRatio,
|
||||
<div id="player-container" className="size-full" ref={containerRef}>
|
||||
<TransformComponent
|
||||
wrapperStyle={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
}}
|
||||
contentStyle={{
|
||||
position: "relative",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
}}
|
||||
>
|
||||
<BirdseyeLivePlayer
|
||||
className="h-full"
|
||||
birdseyeConfig={config.birdseye}
|
||||
liveMode={preferredLiveMode}
|
||||
/>
|
||||
</div>
|
||||
</TransformComponent>
|
||||
<div
|
||||
className={growClassName}
|
||||
style={{
|
||||
aspectRatio: aspectRatio,
|
||||
}}
|
||||
>
|
||||
<BirdseyeLivePlayer
|
||||
className={`${fullscreen ? "*:rounded-none" : ""}`}
|
||||
birdseyeConfig={config.birdseye}
|
||||
liveMode={preferredLiveMode}
|
||||
containerRef={containerRef}
|
||||
/>
|
||||
</div>
|
||||
</TransformComponent>
|
||||
</div>
|
||||
</div>
|
||||
</TransformWrapper>
|
||||
);
|
||||
|
@ -85,6 +85,7 @@ export default function LiveCameraView({
|
||||
const navigate = useNavigate();
|
||||
const { isPortrait } = useMobileOrientation();
|
||||
const mainRef = useRef<HTMLDivElement | null>(null);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const [{ width: windowWidth, height: windowHeight }] =
|
||||
useResizeObserver(window);
|
||||
|
||||
@ -389,48 +390,51 @@ export default function LiveCameraView({
|
||||
</div>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
<TransformComponent
|
||||
wrapperStyle={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
}}
|
||||
contentStyle={{
|
||||
position: "relative",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
padding: "8px",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className={`flex flex-col items-center justify-center ${growClassName}`}
|
||||
ref={clickOverlayRef}
|
||||
onClick={handleOverlayClick}
|
||||
style={{
|
||||
aspectRatio: aspectRatio,
|
||||
<div id="player-container" className="size-full" ref={containerRef}>
|
||||
<TransformComponent
|
||||
wrapperStyle={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
}}
|
||||
contentStyle={{
|
||||
position: "relative",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
padding: "8px",
|
||||
}}
|
||||
>
|
||||
<LivePlayer
|
||||
key={camera.name}
|
||||
className={`m-1 ${fullscreen ? "*:rounded-none" : ""}`}
|
||||
windowVisible
|
||||
showStillWithoutActivity={false}
|
||||
cameraConfig={camera}
|
||||
playAudio={audio}
|
||||
micEnabled={mic}
|
||||
iOSCompatFullScreen={isIOS}
|
||||
preferredLiveMode={preferredLiveMode}
|
||||
pip={pip}
|
||||
setFullResolution={setFullResolution}
|
||||
/>
|
||||
</div>
|
||||
{camera.onvif.host != "" && (
|
||||
<PtzControlPanel
|
||||
camera={camera.name}
|
||||
clickOverlay={clickOverlay}
|
||||
setClickOverlay={setClickOverlay}
|
||||
/>
|
||||
)}
|
||||
</TransformComponent>
|
||||
<div
|
||||
className={`flex flex-col items-center justify-center ${growClassName}`}
|
||||
ref={clickOverlayRef}
|
||||
onClick={handleOverlayClick}
|
||||
style={{
|
||||
aspectRatio: aspectRatio,
|
||||
}}
|
||||
>
|
||||
<LivePlayer
|
||||
key={camera.name}
|
||||
className={`${fullscreen ? "*:rounded-none" : ""}`}
|
||||
windowVisible
|
||||
showStillWithoutActivity={false}
|
||||
cameraConfig={camera}
|
||||
playAudio={audio}
|
||||
micEnabled={mic}
|
||||
iOSCompatFullScreen={isIOS}
|
||||
preferredLiveMode={preferredLiveMode}
|
||||
pip={pip}
|
||||
setFullResolution={setFullResolution}
|
||||
containerRef={containerRef}
|
||||
/>
|
||||
</div>
|
||||
{camera.onvif.host != "" && (
|
||||
<PtzControlPanel
|
||||
camera={camera.name}
|
||||
clickOverlay={clickOverlay}
|
||||
setClickOverlay={setClickOverlay}
|
||||
/>
|
||||
)}
|
||||
</TransformComponent>
|
||||
</div>
|
||||
</div>
|
||||
</TransformWrapper>
|
||||
);
|
||||
|
Loading…
Reference in New Issue
Block a user