mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-02-17 16:44:29 +03:00
stream selection on single camera live view
This commit is contained in:
parent
922d16fa4c
commit
cbffb97ae5
@ -26,6 +26,7 @@ type LivePlayerProps = {
|
||||
containerRef?: React.MutableRefObject<HTMLDivElement | null>;
|
||||
className?: string;
|
||||
cameraConfig: CameraConfig;
|
||||
streamName: string;
|
||||
preferredLiveMode: LivePlayerMode;
|
||||
showStillWithoutActivity?: boolean;
|
||||
windowVisible?: boolean;
|
||||
@ -45,6 +46,7 @@ export default function LivePlayer({
|
||||
containerRef,
|
||||
className,
|
||||
cameraConfig,
|
||||
streamName,
|
||||
preferredLiveMode,
|
||||
showStillWithoutActivity = true,
|
||||
windowVisible = true,
|
||||
@ -144,6 +146,19 @@ export default function LivePlayer({
|
||||
setLiveReady(false);
|
||||
}, [preferredLiveMode]);
|
||||
|
||||
const [key, setKey] = useState(0);
|
||||
|
||||
const resetPlayer = () => {
|
||||
setLiveReady(false);
|
||||
setKey((prevKey) => prevKey + 1);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (streamName) {
|
||||
resetPlayer();
|
||||
}
|
||||
}, [streamName]);
|
||||
|
||||
const playerIsPlaying = useCallback(() => {
|
||||
setLiveReady(true);
|
||||
}, []);
|
||||
@ -153,13 +168,14 @@ export default function LivePlayer({
|
||||
}
|
||||
|
||||
let player;
|
||||
if (!autoLive) {
|
||||
if (!autoLive || !streamName) {
|
||||
player = null;
|
||||
} else if (preferredLiveMode == "webrtc") {
|
||||
player = (
|
||||
<WebRtcPlayer
|
||||
key={"webrtc_" + key}
|
||||
className={`size-full rounded-lg md:rounded-2xl ${liveReady ? "" : "hidden"}`}
|
||||
camera={cameraConfig.live.stream_name}
|
||||
camera={streamName}
|
||||
playbackEnabled={cameraActive || liveReady}
|
||||
audioEnabled={playAudio}
|
||||
microphoneEnabled={micEnabled}
|
||||
@ -173,8 +189,9 @@ export default function LivePlayer({
|
||||
if ("MediaSource" in window || "ManagedMediaSource" in window) {
|
||||
player = (
|
||||
<MSEPlayer
|
||||
key={"mse_" + key}
|
||||
className={`size-full rounded-lg md:rounded-2xl ${liveReady ? "" : "hidden"}`}
|
||||
camera={cameraConfig.live.stream_name}
|
||||
camera={streamName}
|
||||
playbackEnabled={cameraActive || liveReady}
|
||||
audioEnabled={playAudio}
|
||||
onPlaying={playerIsPlaying}
|
||||
@ -194,6 +211,7 @@ export default function LivePlayer({
|
||||
if (cameraActive || !showStillWithoutActivity || liveReady) {
|
||||
player = (
|
||||
<JSMpegPlayer
|
||||
key={"jsmpeg_" + key}
|
||||
className="flex justify-center overflow-hidden rounded-lg md:rounded-2xl"
|
||||
camera={cameraConfig.name}
|
||||
width={cameraConfig.detect.width}
|
||||
|
||||
@ -87,7 +87,7 @@ export interface CameraConfig {
|
||||
live: {
|
||||
height: number;
|
||||
quality: number;
|
||||
stream_name: string;
|
||||
streams: { [key: string]: string };
|
||||
};
|
||||
motion: {
|
||||
contour_area: number;
|
||||
@ -320,12 +320,6 @@ export interface FrigateConfig {
|
||||
|
||||
camera_groups: { [groupName: string]: CameraGroupConfig };
|
||||
|
||||
live: {
|
||||
height: number;
|
||||
quality: number;
|
||||
stream_name: string;
|
||||
};
|
||||
|
||||
logger: {
|
||||
default: string;
|
||||
logs: Record<string, string>;
|
||||
|
||||
@ -57,7 +57,7 @@ import {
|
||||
} from "react-icons/fa";
|
||||
import { GiSpeaker, GiSpeakerOff } from "react-icons/gi";
|
||||
import { TbViewfinder, TbViewfinderOff } from "react-icons/tb";
|
||||
import { IoMdArrowRoundBack } from "react-icons/io";
|
||||
import { IoIosWarning, IoMdArrowRoundBack } from "react-icons/io";
|
||||
import {
|
||||
LuEar,
|
||||
LuEarOff,
|
||||
@ -79,6 +79,20 @@ import { TransformWrapper, TransformComponent } from "react-zoom-pan-pinch";
|
||||
import useSWR from "swr";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useSessionPersistence } from "@/hooks/use-session-persistence";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectGroup,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
} from "@/components/ui/select";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import { usePersistence } from "@/hooks/use-persistence";
|
||||
import { TooltipPortal } from "@radix-ui/react-tooltip";
|
||||
|
||||
type LiveCameraViewProps = {
|
||||
config?: FrigateConfig;
|
||||
@ -103,17 +117,20 @@ export default function LiveCameraView({
|
||||
|
||||
// supported features
|
||||
|
||||
const [streamName, setStreamName] = usePersistence<string>(
|
||||
`${camera.name}-stream`,
|
||||
Object.values(camera.live.streams)[0],
|
||||
);
|
||||
|
||||
const isRestreamed = useMemo(
|
||||
() =>
|
||||
config &&
|
||||
Object.keys(config.go2rtc.streams || {}).includes(
|
||||
camera.live.stream_name,
|
||||
),
|
||||
[camera, config],
|
||||
Object.keys(config.go2rtc.streams || {}).includes(streamName ?? ""),
|
||||
[config, streamName],
|
||||
);
|
||||
|
||||
const { data: cameraMetadata } = useSWR<LiveStreamMetadata>(
|
||||
isRestreamed ? `go2rtc/streams/${camera.live.stream_name}` : null,
|
||||
isRestreamed ? `go2rtc/streams/${streamName}` : null,
|
||||
{
|
||||
revalidateOnFocus: false,
|
||||
},
|
||||
@ -454,13 +471,16 @@ export default function LiveCameraView({
|
||||
/>
|
||||
)}
|
||||
<FrigateCameraFeatures
|
||||
camera={camera.name}
|
||||
camera={camera}
|
||||
recordingEnabled={camera.record.enabled_in_config}
|
||||
audioDetectEnabled={camera.audio.enabled_in_config}
|
||||
autotrackingEnabled={
|
||||
camera.onvif.autotracking.enabled_in_config
|
||||
}
|
||||
fullscreen={fullscreen}
|
||||
streamName={streamName ?? ""}
|
||||
setStreamName={setStreamName}
|
||||
preferredLiveMode={preferredLiveMode}
|
||||
/>
|
||||
</div>
|
||||
</TooltipProvider>
|
||||
@ -496,6 +516,7 @@ export default function LiveCameraView({
|
||||
micEnabled={mic}
|
||||
iOSCompatFullScreen={isIOS}
|
||||
preferredLiveMode={preferredLiveMode}
|
||||
streamName={streamName ?? ""}
|
||||
pip={pip}
|
||||
containerRef={containerRef}
|
||||
setFullResolution={setFullResolution}
|
||||
@ -749,11 +770,14 @@ function PtzControlPanel({
|
||||
}
|
||||
|
||||
type FrigateCameraFeaturesProps = {
|
||||
camera: string;
|
||||
camera: CameraConfig;
|
||||
recordingEnabled: boolean;
|
||||
audioDetectEnabled: boolean;
|
||||
autotrackingEnabled: boolean;
|
||||
fullscreen: boolean;
|
||||
streamName: string;
|
||||
setStreamName?: (value: string | undefined) => void;
|
||||
preferredLiveMode: string;
|
||||
};
|
||||
function FrigateCameraFeatures({
|
||||
camera,
|
||||
@ -761,14 +785,22 @@ function FrigateCameraFeatures({
|
||||
audioDetectEnabled,
|
||||
autotrackingEnabled,
|
||||
fullscreen,
|
||||
streamName,
|
||||
setStreamName,
|
||||
preferredLiveMode,
|
||||
}: FrigateCameraFeaturesProps) {
|
||||
const { payload: detectState, send: sendDetect } = useDetectState(camera);
|
||||
const { payload: recordState, send: sendRecord } = useRecordingsState(camera);
|
||||
const { payload: snapshotState, send: sendSnapshot } =
|
||||
useSnapshotsState(camera);
|
||||
const { payload: audioState, send: sendAudio } = useAudioState(camera);
|
||||
const { payload: detectState, send: sendDetect } = useDetectState(
|
||||
camera.name,
|
||||
);
|
||||
const { payload: recordState, send: sendRecord } = useRecordingsState(
|
||||
camera.name,
|
||||
);
|
||||
const { payload: snapshotState, send: sendSnapshot } = useSnapshotsState(
|
||||
camera.name,
|
||||
);
|
||||
const { payload: audioState, send: sendAudio } = useAudioState(camera.name);
|
||||
const { payload: autotrackingState, send: sendAutotracking } =
|
||||
useAutotrackingState(camera);
|
||||
useAutotrackingState(camera.name);
|
||||
|
||||
// desktop shows icons part of row
|
||||
if (isDesktop || isTablet) {
|
||||
@ -820,6 +852,47 @@ function FrigateCameraFeatures({
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{Object.values(camera.live.streams).length > 1 && (
|
||||
<Select
|
||||
value={streamName}
|
||||
onValueChange={(value) => {
|
||||
setStreamName?.(value);
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className="w-full">
|
||||
{preferredLiveMode == "jsmpeg" && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<IoIosWarning className="mr-1 size-5 text-danger" />
|
||||
</TooltipTrigger>
|
||||
<TooltipPortal>
|
||||
<TooltipContent className="max-w-52">
|
||||
Live view is in low-bandwidth mode due to buffering or
|
||||
stream errors
|
||||
</TooltipContent>
|
||||
</TooltipPortal>
|
||||
</Tooltip>
|
||||
)}
|
||||
{Object.keys(camera.live.streams).find(
|
||||
(key) => camera.live.streams[key] === streamName,
|
||||
)}
|
||||
</SelectTrigger>
|
||||
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
{Object.entries(camera.live.streams).map(([stream, name]) => (
|
||||
<SelectItem
|
||||
key={stream}
|
||||
className="cursor-pointer"
|
||||
value={name}
|
||||
>
|
||||
{stream}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -878,6 +951,40 @@ function FrigateCameraFeatures({
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{Object.values(camera.live.streams).length > 1 && (
|
||||
<div className="mt-1 p-2">
|
||||
<div className="mb-1 text-sm">Live stream selection</div>
|
||||
<Select
|
||||
value={streamName}
|
||||
onValueChange={(value) => {
|
||||
setStreamName?.(value);
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className="w-full">
|
||||
{preferredLiveMode == "jsmpeg" && (
|
||||
<IoIosWarning className="mr-1 size-5 text-danger" />
|
||||
)}
|
||||
{Object.keys(camera.live.streams).find(
|
||||
(key) => camera.live.streams[key] === streamName,
|
||||
)}
|
||||
</SelectTrigger>
|
||||
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
{Object.entries(camera.live.streams).map(([stream, name]) => (
|
||||
<SelectItem
|
||||
key={stream}
|
||||
className="cursor-pointer"
|
||||
value={name}
|
||||
>
|
||||
{stream}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
)}
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user