mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-11-14 01:23:01 +03:00
hold the last frame on screen while switching live streams
Switching streams in the single camera view blanked the player until the new stream played, because the old player unmounts in the same render and the hidden new player gave the card no height. The outgoing video's last frame is now drawn to a canvas before the remount and covers the gap until the new stream plays, and the card takes the camera's aspect ratio so it keeps its size while the video is hidden.
This commit is contained in:
@@ -248,10 +248,18 @@ export default function LivePlayer({
|
||||
// remounts them in the same render and the new player is hidden until it
|
||||
// plays. jsmpeg plays the camera, not the stream, and keeps playing
|
||||
const [renderedStream, setRenderedStream] = useState(streamName);
|
||||
const [switchFrame, setSwitchFrame] = useState<HTMLCanvasElement>();
|
||||
if (renderedStream !== streamName) {
|
||||
setRenderedStream(streamName);
|
||||
if (preferredLiveMode !== "jsmpeg") {
|
||||
setLiveReady(false);
|
||||
|
||||
// the old video is still in the DOM until this render commits, so its
|
||||
// last frame can cover the gap until the new stream plays
|
||||
const frame = captureVideoFrame(overlayRef.current);
|
||||
if (frame) {
|
||||
setSwitchFrame(frame);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -263,6 +271,7 @@ export default function LivePlayer({
|
||||
|
||||
const playerIsPlaying = useCallback(() => {
|
||||
setLiveReady(true);
|
||||
setSwitchFrame(undefined);
|
||||
}, []);
|
||||
|
||||
// enabled states
|
||||
@@ -416,6 +425,12 @@ export default function LivePlayer({
|
||||
</div>
|
||||
)}
|
||||
{player}
|
||||
{switchFrame && !liveReady && (
|
||||
<div
|
||||
className="absolute inset-0"
|
||||
ref={(node) => node?.replaceChildren(switchFrame)}
|
||||
/>
|
||||
)}
|
||||
{cameraEnabled &&
|
||||
!offline &&
|
||||
(!showStillWithoutActivity || isReEnabling) &&
|
||||
@@ -582,3 +597,19 @@ export default function LivePlayer({
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function captureVideoFrame(
|
||||
container: HTMLElement | null,
|
||||
): HTMLCanvasElement | undefined {
|
||||
const video = container?.querySelector("video");
|
||||
if (!video || video.readyState < HTMLMediaElement.HAVE_CURRENT_DATA) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const canvas = document.createElement("canvas");
|
||||
canvas.width = video.videoWidth;
|
||||
canvas.height = video.videoHeight;
|
||||
canvas.className = "size-full object-contain";
|
||||
canvas.getContext("2d")?.drawImage(video, 0, 0);
|
||||
return canvas;
|
||||
}
|
||||
|
||||
@@ -873,9 +873,12 @@ export default function LiveCameraView({
|
||||
growClassName,
|
||||
)}
|
||||
ref={clickOverlayRef}
|
||||
style={{
|
||||
aspectRatio: constrainedAspectRatio,
|
||||
}}
|
||||
style={
|
||||
{
|
||||
aspectRatio: constrainedAspectRatio,
|
||||
"--camera-aspect": cameraAspectRatio,
|
||||
} as React.CSSProperties
|
||||
}
|
||||
>
|
||||
{clickOverlay && overlaySize.width > 0 && (
|
||||
<div
|
||||
@@ -914,7 +917,10 @@ export default function LiveCameraView({
|
||||
)}
|
||||
<LivePlayer
|
||||
key={camera.name}
|
||||
className={fullscreen ? "rounded-none" : ""}
|
||||
className={cn(
|
||||
"aspect-[var(--camera-aspect)]",
|
||||
fullscreen && "rounded-none",
|
||||
)}
|
||||
windowVisible
|
||||
showStillWithoutActivity={false}
|
||||
alwaysShowCameraName={false}
|
||||
|
||||
Reference in New Issue
Block a user