Merge pull request #38 from ibs0d/codex/fix-live-grid-zoom-behavior

Expose mediaContentStyle on LivePlayer and pass through to MSEPlayer and still image
This commit is contained in:
ibs0d 2026-03-09 15:07:56 +11:00 committed by GitHub
commit 05775ed20e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 61 additions and 52 deletions

View File

@ -3,6 +3,7 @@ import { CameraConfig } from "@/types/frigateConfig";
import AutoUpdatingCameraImage from "../camera/AutoUpdatingCameraImage";
import ActivityIndicator from "../indicators/activity-indicator";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import type { CSSProperties } from "react";
import MSEPlayer from "./MsePlayer";
import JSMpegPlayer from "./JSMpegPlayer";
import { MdCircle } from "react-icons/md";
@ -48,6 +49,7 @@ type LivePlayerProps = {
autoLive?: boolean;
showStats?: boolean;
onClick?: () => void;
mediaContentStyle?: CSSProperties;
setFullResolution?: React.Dispatch<React.SetStateAction<VideoResolutionType>>;
onError?: (error: LivePlayerError) => void;
onResetLiveMode?: () => void;
@ -73,6 +75,7 @@ export default function LivePlayer({
autoLive = true,
showStats = false,
onClick,
mediaContentStyle,
setFullResolution,
onError,
onResetLiveMode,
@ -299,6 +302,7 @@ export default function LivePlayer({
<MSEPlayer
key={"mse_" + key}
className={`size-full rounded-lg md:rounded-2xl ${liveReady ? "" : "hidden"}`}
style={mediaContentStyle}
camera={streamName}
playbackEnabled={cameraActive || liveReady}
audioEnabled={playAudio}
@ -383,6 +387,7 @@ export default function LivePlayer({
? "visible"
: "invisible",
)}
style={mediaContentStyle}
>
<AutoUpdatingCameraImage
className="pointer-events-none size-full"

View File

@ -19,6 +19,7 @@ import { isIOS, isSafari } from "react-device-detect";
type MSEPlayerProps = {
camera: string;
className?: string;
style?: CSSProperties;
playbackEnabled?: boolean;
audioEnabled?: boolean;
volume?: number;
@ -34,6 +35,7 @@ type MSEPlayerProps = {
function MSEPlayer({
camera,
className,
style,
playbackEnabled = true,
audioEnabled = false,
volume,
@ -843,15 +845,21 @@ function MSEPlayer({
};
}, [isRotatedGrid]);
const rotationTransform =
"var(--frigate-mse-grid-rotation, none)" as CSSProperties["transform"];
const combinedTransform = style?.transform
? `${style.transform} ${rotationTransform}`
: rotationTransform;
const videoElement = (
<video
ref={videoRef}
className={className}
style={{
...style,
objectFit:
"var(--frigate-mse-object-fit, fill)" as CSSProperties["objectFit"],
transform:
"var(--frigate-mse-grid-rotation, none)" as CSSProperties["transform"],
transform: combinedTransform,
transformOrigin:
"var(--frigate-mse-grid-rotation-origin, center center)" as CSSProperties["transformOrigin"],
width: isRotatedGrid ? "100%" : undefined,

View File

@ -828,59 +828,55 @@ export default function DraggableGridLayout({
hydrateCameraZoomFromStorage(camera.name);
}}
>
<div
className="size-full"
style={{
<LivePlayer
key={camera.name}
streamName={streamName}
autoLive={autoLive ?? globalAutoLive}
showStillWithoutActivity={
showStillWithoutActivity ?? true
}
alwaysShowCameraName={displayCameraNames}
useWebGL={useWebGL}
cameraRef={cameraRef}
className={cn(
"draggable-live-grid-mse-cover size-full rounded-lg bg-black md:rounded-2xl",
camera.ui?.rotate &&
"draggable-live-grid-rotated [--frigate-mse-grid-rotated:1] [--frigate-mse-grid-rotation:rotate(90deg)]",
isEditMode &&
showCircles &&
"outline-2 outline-muted-foreground hover:cursor-grab hover:outline-4 active:cursor-grabbing",
)}
windowVisible={
windowVisible && visibleCameras.includes(camera.name)
}
cameraConfig={camera}
preferredLiveMode={
preferredLiveModes[camera.name] ?? "mse"
}
playInBackground={false}
showStats={statsStates[camera.name] ?? true}
mediaContentStyle={{
transform: `translate3d(${cameraZoomTransform.positionX}px, ${cameraZoomTransform.positionY}px, 0) scale(${cameraZoomTransform.scale})`,
transformOrigin: "top left",
}}
>
<LivePlayer
key={camera.name}
streamName={streamName}
autoLive={autoLive ?? globalAutoLive}
showStillWithoutActivity={
showStillWithoutActivity ?? true
}
alwaysShowCameraName={displayCameraNames}
useWebGL={useWebGL}
cameraRef={cameraRef}
className={cn(
"draggable-live-grid-mse-cover size-full rounded-lg bg-black md:rounded-2xl",
camera.ui?.rotate &&
"draggable-live-grid-rotated [--frigate-mse-grid-rotated:1] [--frigate-mse-grid-rotation:rotate(90deg)]",
isEditMode &&
showCircles &&
"outline-2 outline-muted-foreground hover:cursor-grab hover:outline-4 active:cursor-grabbing",
)}
windowVisible={
windowVisible && visibleCameras.includes(camera.name)
}
cameraConfig={camera}
preferredLiveMode={
preferredLiveModes[camera.name] ?? "mse"
}
playInBackground={false}
showStats={statsStates[camera.name] ?? true}
onClick={() => {
!isEditMode && onSelectCamera(camera.name);
}}
onError={(e) => {
setPreferredLiveModes((prevModes) => {
const newModes = { ...prevModes };
if (e === "mse-decode") {
delete newModes[camera.name];
}
return newModes;
});
}}
onResetLiveMode={() =>
resetPreferredLiveMode(camera.name)
}
playAudio={audioStates[camera.name]}
volume={volumeStates[camera.name]}
/>
</div>
onClick={() => {
!isEditMode && onSelectCamera(camera.name);
}}
onError={(e) => {
setPreferredLiveModes((prevModes) => {
const newModes = { ...prevModes };
if (e === "mse-decode") {
delete newModes[camera.name];
}
return newModes;
});
}}
onResetLiveMode={() =>
resetPreferredLiveMode(camera.name)
}
playAudio={audioStates[camera.name]}
volume={volumeStates[camera.name]}
/>
</div>
{isEditMode && showCircles && <CornerCircles />}
</GridLiveContextMenu>