Live view tweaks and jsmpeg bugfix (#11584)

* live view tweaks and jsmpeg bugfix

* use container aspect in check
This commit is contained in:
Josh Hawkins 2024-05-28 08:11:35 -05:00 committed by GitHub
parent 6913cc6abc
commit 4165639308
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 65 additions and 87 deletions

View File

@ -77,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" }, { canvas: `#${camera}-canvas` },
{ protocols: [], audio: false, videoBufferSize: 1024 * 1024 * 4 }, { protocols: [], audio: false, videoBufferSize: 1024 * 1024 * 4 },
); );
@ -90,13 +90,13 @@ export default function JSMpegPlayer({
playerRef.current = null; playerRef.current = null;
} }
}; };
}, [url]); }, [url, camera]);
return ( return (
<div className={className} ref={internalContainerRef}> <div className={className} ref={internalContainerRef}>
<div ref={playerRef} className="jsmpeg"> <div ref={playerRef} className="jsmpeg">
<canvas <canvas
id="video-canvas" id={`${camera}-canvas`}
style={{ style={{
width: scaledWidth ?? width, width: scaledWidth ?? width,
height: scaledHeight ?? height, height: scaledHeight ?? height,

View File

@ -4,8 +4,9 @@ import BirdseyeLivePlayer from "@/components/player/BirdseyeLivePlayer";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { TooltipProvider } from "@/components/ui/tooltip"; import { TooltipProvider } from "@/components/ui/tooltip";
import { useResizeObserver } from "@/hooks/resize-observer"; import { useResizeObserver } from "@/hooks/resize-observer";
import { useFullscreen } from "@/hooks/use-fullscreen";
import { FrigateConfig } from "@/types/frigateConfig"; import { FrigateConfig } from "@/types/frigateConfig";
import { useEffect, useMemo, useRef, useState } from "react"; import { useMemo, useRef } from "react";
import { import {
isDesktop, isDesktop,
isMobile, isMobile,
@ -29,25 +30,10 @@ export default function LiveBirdseyeView() {
// fullscreen state // fullscreen state
useEffect(() => { const { fullscreen, toggleFullscreen } = useFullscreen(mainRef);
if (mainRef.current == null) {
return;
}
const listener = () => {
setFullscreen(document.fullscreenElement != null);
};
document.addEventListener("fullscreenchange", listener);
return () => {
document.removeEventListener("fullscreenchange", listener);
};
}, [mainRef]);
// playback state // playback state
const [fullscreen, setFullscreen] = useState(false);
const cameraAspectRatio = useMemo(() => { const cameraAspectRatio = useMemo(() => {
if (!config) { if (!config) {
return 16 / 9; return 16 / 9;
@ -96,15 +82,23 @@ export default function LiveBirdseyeView() {
return windowWidth / windowHeight; return windowWidth / windowHeight;
}, [windowWidth, windowHeight]); }, [windowWidth, windowHeight]);
const containerAspectRatio = useMemo(() => {
if (!containerRef.current) {
return windowAspectRatio;
}
return containerRef.current.clientWidth / containerRef.current.clientHeight;
}, [windowAspectRatio, containerRef]);
const aspectRatio = useMemo<number>(() => { const aspectRatio = useMemo<number>(() => {
if (isMobile || fullscreen) { if (isMobile || fullscreen) {
return cameraAspectRatio; return cameraAspectRatio;
} else { } else {
return windowAspectRatio < cameraAspectRatio return containerAspectRatio < cameraAspectRatio
? windowAspectRatio - 0.05 ? containerAspectRatio
: cameraAspectRatio - 0.03; : cameraAspectRatio;
} }
}, [cameraAspectRatio, windowAspectRatio, fullscreen]); }, [cameraAspectRatio, containerAspectRatio, fullscreen]);
if (!config) { if (!config) {
return <ActivityIndicator />; return <ActivityIndicator />;
@ -149,13 +143,7 @@ export default function LiveBirdseyeView() {
Icon={fullscreen ? FaCompress : FaExpand} Icon={fullscreen ? FaCompress : FaExpand}
isActive={fullscreen} isActive={fullscreen}
title={fullscreen ? "Close" : "Fullscreen"} title={fullscreen ? "Close" : "Fullscreen"}
onClick={() => { onClick={toggleFullscreen}
if (fullscreen) {
document.exitFullscreen();
} else {
mainRef.current?.requestFullscreen();
}
}}
/> />
</div> </div>
</TooltipProvider> </TooltipProvider>

View File

@ -73,6 +73,7 @@ import {
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { TransformWrapper, TransformComponent } from "react-zoom-pan-pinch"; import { TransformWrapper, TransformComponent } from "react-zoom-pan-pinch";
import useSWR from "swr"; import useSWR from "swr";
import { useFullscreen } from "@/hooks/use-fullscreen";
type LiveCameraViewProps = { type LiveCameraViewProps = {
config?: FrigateConfig; config?: FrigateConfig;
@ -177,19 +178,7 @@ export default function LiveCameraView({
// fullscreen / pip state // fullscreen / pip state
useEffect(() => { const { fullscreen, toggleFullscreen } = useFullscreen(mainRef);
if (mainRef.current == null) {
return;
}
const fsListener = () => {
setFullscreen(document.fullscreenElement != null);
};
document.addEventListener("fullscreenchange", fsListener);
return () => {
document.removeEventListener("fullscreenchange", fsListener);
};
}, [mainRef]);
useEffect(() => { useEffect(() => {
setPip(document.pictureInPictureElement != null); setPip(document.pictureInPictureElement != null);
@ -201,7 +190,6 @@ export default function LiveCameraView({
const [audio, setAudio] = useState(false); const [audio, setAudio] = useState(false);
const [mic, setMic] = useState(false); const [mic, setMic] = useState(false);
const [fullscreen, setFullscreen] = useState(false);
const [pip, setPip] = useState(false); const [pip, setPip] = useState(false);
const [fullResolution, setFullResolution] = useState<VideoResolutionType>({ const [fullResolution, setFullResolution] = useState<VideoResolutionType>({
@ -209,37 +197,6 @@ export default function LiveCameraView({
height: 0, height: 0,
}); });
const growClassName = useMemo(() => {
let aspect;
if (fullResolution.width && fullResolution.height) {
aspect = fullResolution.width / fullResolution.height;
} else {
aspect = camera.detect.width / camera.detect.height;
}
if (isMobile) {
if (isPortrait) {
return "absolute left-2 right-2 top-[50%] -translate-y-[50%]";
} else {
if (aspect > 1.5) {
return "p-2 absolute left-0 top-[50%] -translate-y-[50%]";
} else {
return "p-2 absolute top-2 bottom-2 left-[50%] -translate-x-[50%]";
}
}
}
if (fullscreen) {
if (aspect > 1.5) {
return "absolute inset-x-2 top-[50%] -translate-y-[50%]";
} else {
return "absolute inset-y-2 left-[50%] -translate-x-[50%]";
}
} else {
return "absolute top-2 bottom-2 left-[50%] -translate-x-[50%]";
}
}, [camera, fullscreen, isPortrait, fullResolution]);
const preferredLiveMode = useMemo(() => { const preferredLiveMode = useMemo(() => {
if (isSafari || mic) { if (isSafari || mic) {
return "webrtc"; return "webrtc";
@ -252,6 +209,14 @@ export default function LiveCameraView({
return windowWidth / windowHeight; return windowWidth / windowHeight;
}, [windowWidth, windowHeight]); }, [windowWidth, windowHeight]);
const containerAspectRatio = useMemo(() => {
if (!containerRef.current) {
return windowAspectRatio;
}
return containerRef.current.clientWidth / containerRef.current.clientHeight;
}, [windowAspectRatio, containerRef]);
const cameraAspectRatio = useMemo(() => { const cameraAspectRatio = useMemo(() => {
if (fullResolution.width && fullResolution.height) { if (fullResolution.width && fullResolution.height) {
return fullResolution.width / fullResolution.height; return fullResolution.width / fullResolution.height;
@ -264,11 +229,42 @@ export default function LiveCameraView({
if (isMobile || fullscreen) { if (isMobile || fullscreen) {
return cameraAspectRatio; return cameraAspectRatio;
} else { } else {
return windowAspectRatio < cameraAspectRatio return containerAspectRatio < cameraAspectRatio
? windowAspectRatio - 0.05 ? containerAspectRatio
: cameraAspectRatio - 0.03; : cameraAspectRatio;
} }
}, [cameraAspectRatio, windowAspectRatio, fullscreen]); }, [cameraAspectRatio, containerAspectRatio, fullscreen]);
const growClassName = useMemo(() => {
let aspect;
if (fullResolution.width && fullResolution.height) {
aspect = fullResolution.width / fullResolution.height;
} else {
aspect = camera.detect.width / camera.detect.height;
}
if (isMobile) {
if (isPortrait) {
return "absolute left-0.5 right-0.5 top-[50%] -translate-y-[50%]";
} else {
if (aspect > containerAspectRatio) {
return "p-2 absolute left-0 top-[50%] -translate-y-[50%]";
} else {
return "p-2 absolute top-0.5 bottom-0.5 left-[50%] -translate-x-[50%]";
}
}
}
if (fullscreen) {
if (aspect > containerAspectRatio) {
return "absolute inset-x-2 top-[50%] -translate-y-[50%]";
} else {
return "absolute inset-y-2 left-[50%] -translate-x-[50%]";
}
} else {
return "absolute top-0.5 bottom-0.5 left-[50%] -translate-x-[50%]";
}
}, [camera, fullscreen, isPortrait, fullResolution, containerAspectRatio]);
return ( return (
<TransformWrapper minScale={1.0}> <TransformWrapper minScale={1.0}>
@ -333,13 +329,7 @@ export default function LiveCameraView({
Icon={fullscreen ? FaCompress : FaExpand} Icon={fullscreen ? FaCompress : FaExpand}
isActive={fullscreen} isActive={fullscreen}
title={fullscreen ? "Close" : "Fullscreen"} title={fullscreen ? "Close" : "Fullscreen"}
onClick={() => { onClick={toggleFullscreen}
if (fullscreen) {
document.exitFullscreen();
} else {
mainRef.current?.requestFullscreen();
}
}}
/> />
)} )}
{!isIOS && ( {!isIOS && (