2023-12-16 00:24:50 +01:00
|
|
|
import WebRtcPlayer from "./WebRTCPlayer";
|
|
|
|
import { CameraConfig } from "@/types/frigateConfig";
|
|
|
|
import AutoUpdatingCameraImage from "../camera/AutoUpdatingCameraImage";
|
|
|
|
import ActivityIndicator from "../ui/activity-indicator";
|
2024-02-13 02:28:36 +01:00
|
|
|
import { useEffect, useMemo, useState } from "react";
|
2024-01-04 00:38:52 +01:00
|
|
|
import MSEPlayer from "./MsePlayer";
|
2023-12-20 15:34:27 +01:00
|
|
|
import JSMpegPlayer from "./JSMpegPlayer";
|
2024-02-10 13:30:53 +01:00
|
|
|
import { MdCircle, MdLeakAdd } from "react-icons/md";
|
|
|
|
import { BsSoundwave } from "react-icons/bs";
|
|
|
|
import Chip from "../Chip";
|
|
|
|
import useCameraActivity from "@/hooks/use-camera-activity";
|
|
|
|
import { useRecordingsState } from "@/api/ws";
|
|
|
|
import { LivePlayerMode } from "@/types/live";
|
|
|
|
import useCameraLiveMode from "@/hooks/use-camera-live-mode";
|
2024-02-27 22:39:05 +01:00
|
|
|
import { isDesktop } from "react-device-detect";
|
2023-12-16 00:24:50 +01:00
|
|
|
|
|
|
|
type LivePlayerProps = {
|
2024-02-10 13:30:53 +01:00
|
|
|
className?: string;
|
2023-12-16 00:24:50 +01:00
|
|
|
cameraConfig: CameraConfig;
|
2024-02-10 13:30:53 +01:00
|
|
|
preferredLiveMode?: LivePlayerMode;
|
|
|
|
showStillWithoutActivity?: boolean;
|
2024-02-13 02:28:36 +01:00
|
|
|
windowVisible?: boolean;
|
2023-12-16 00:24:50 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
export default function LivePlayer({
|
2024-02-10 13:30:53 +01:00
|
|
|
className,
|
2023-12-16 00:24:50 +01:00
|
|
|
cameraConfig,
|
2024-02-10 13:30:53 +01:00
|
|
|
preferredLiveMode,
|
|
|
|
showStillWithoutActivity = true,
|
2024-02-13 02:28:36 +01:00
|
|
|
windowVisible = true,
|
2023-12-16 00:24:50 +01:00
|
|
|
}: LivePlayerProps) {
|
2024-02-10 13:30:53 +01:00
|
|
|
// camera activity
|
2024-02-13 02:28:36 +01:00
|
|
|
|
2024-02-10 13:30:53 +01:00
|
|
|
const { activeMotion, activeAudio, activeTracking } =
|
|
|
|
useCameraActivity(cameraConfig);
|
|
|
|
|
2024-02-13 00:34:45 +01:00
|
|
|
const cameraActive = useMemo(
|
2024-02-13 02:28:36 +01:00
|
|
|
() => windowVisible && (activeMotion || activeTracking),
|
2024-02-28 23:23:56 +01:00
|
|
|
[activeMotion, activeTracking, windowVisible],
|
2024-02-13 00:34:45 +01:00
|
|
|
);
|
2024-02-13 02:28:36 +01:00
|
|
|
|
|
|
|
// camera live state
|
|
|
|
|
2024-02-10 13:30:53 +01:00
|
|
|
const liveMode = useCameraLiveMode(cameraConfig, preferredLiveMode);
|
|
|
|
|
|
|
|
const [liveReady, setLiveReady] = useState(false);
|
|
|
|
useEffect(() => {
|
|
|
|
if (!liveReady) {
|
2024-02-11 14:23:45 +01:00
|
|
|
if (cameraActive && liveMode == "jsmpeg") {
|
2024-02-10 13:30:53 +01:00
|
|
|
setLiveReady(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2024-02-11 14:23:45 +01:00
|
|
|
if (!cameraActive) {
|
2024-02-10 13:30:53 +01:00
|
|
|
setLiveReady(false);
|
|
|
|
}
|
2024-02-28 23:23:56 +01:00
|
|
|
// live mode won't change
|
|
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
2024-02-11 14:23:45 +01:00
|
|
|
}, [cameraActive, liveReady]);
|
2024-02-10 13:30:53 +01:00
|
|
|
|
|
|
|
const { payload: recording } = useRecordingsState(cameraConfig.name);
|
|
|
|
|
2024-02-13 02:28:36 +01:00
|
|
|
// camera still state
|
2023-12-16 00:24:50 +01:00
|
|
|
|
2024-02-13 02:28:36 +01:00
|
|
|
const stillReloadInterval = useMemo(() => {
|
|
|
|
if (!windowVisible) {
|
|
|
|
return -1; // no reason to update the image when the window is not visible
|
|
|
|
}
|
|
|
|
|
|
|
|
if (liveReady) {
|
|
|
|
return 60000;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cameraActive) {
|
|
|
|
return 200;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 30000;
|
2024-02-28 23:23:56 +01:00
|
|
|
}, [liveReady, cameraActive, windowVisible]);
|
2023-12-16 00:24:50 +01:00
|
|
|
|
2024-02-10 13:30:53 +01:00
|
|
|
if (!cameraConfig) {
|
|
|
|
return <ActivityIndicator />;
|
|
|
|
}
|
|
|
|
|
|
|
|
let player;
|
2023-12-16 00:24:50 +01:00
|
|
|
if (liveMode == "webrtc") {
|
2024-02-10 13:30:53 +01:00
|
|
|
player = (
|
|
|
|
<WebRtcPlayer
|
|
|
|
className={`rounded-2xl h-full ${liveReady ? "" : "hidden"}`}
|
|
|
|
camera={cameraConfig.live.stream_name}
|
2024-02-15 01:19:55 +01:00
|
|
|
playbackEnabled={cameraActive}
|
2024-02-10 13:30:53 +01:00
|
|
|
onPlaying={() => setLiveReady(true)}
|
|
|
|
/>
|
2023-12-16 00:24:50 +01:00
|
|
|
);
|
|
|
|
} else if (liveMode == "mse") {
|
2023-12-31 14:31:05 +01:00
|
|
|
if ("MediaSource" in window || "ManagedMediaSource" in window) {
|
2024-02-10 13:30:53 +01:00
|
|
|
player = (
|
|
|
|
<MSEPlayer
|
|
|
|
className={`rounded-2xl h-full ${liveReady ? "" : "hidden"}`}
|
|
|
|
camera={cameraConfig.name}
|
2024-02-13 02:28:36 +01:00
|
|
|
playbackEnabled={cameraActive}
|
2024-02-10 13:30:53 +01:00
|
|
|
onPlaying={() => setLiveReady(true)}
|
|
|
|
/>
|
2023-12-31 14:31:05 +01:00
|
|
|
);
|
|
|
|
} else {
|
2024-02-10 13:30:53 +01:00
|
|
|
player = (
|
2023-12-31 14:31:05 +01:00
|
|
|
<div className="w-5xl text-center text-sm">
|
|
|
|
MSE is only supported on iOS 17.1+. You'll need to update if available
|
|
|
|
or use jsmpeg / webRTC streams. See the docs for more info.
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
2023-12-16 00:24:50 +01:00
|
|
|
} else if (liveMode == "jsmpeg") {
|
2024-02-10 13:30:53 +01:00
|
|
|
player = (
|
|
|
|
<JSMpegPlayer
|
2024-02-14 00:25:28 +01:00
|
|
|
className="w-full flex justify-center rounded-2xl overflow-hidden"
|
2024-02-10 13:30:53 +01:00
|
|
|
camera={cameraConfig.name}
|
|
|
|
width={cameraConfig.detect.width}
|
|
|
|
height={cameraConfig.detect.height}
|
|
|
|
/>
|
2023-12-16 00:24:50 +01:00
|
|
|
);
|
|
|
|
} else {
|
2024-02-10 13:30:53 +01:00
|
|
|
player = <ActivityIndicator />;
|
2023-12-16 00:24:50 +01:00
|
|
|
}
|
2024-02-10 13:30:53 +01:00
|
|
|
|
|
|
|
return (
|
|
|
|
<div
|
|
|
|
className={`relative flex justify-center w-full outline ${
|
|
|
|
activeTracking
|
2024-02-28 14:18:08 +01:00
|
|
|
? "outline-severity_alert outline-1 rounded-2xl shadow-[0_0_6px_2px] shadow-severity_alert"
|
|
|
|
: "outline-0 outline-background"
|
2024-02-10 13:30:53 +01:00
|
|
|
} transition-all duration-500 ${className}`}
|
|
|
|
>
|
2024-02-28 04:40:57 +01:00
|
|
|
<div className="absolute top-0 inset-x-0 rounded-2xl z-10 w-full h-[30%] bg-gradient-to-b from-black/20 to-transparent pointer-events-none"></div>
|
|
|
|
<div className="absolute bottom-0 inset-x-0 rounded-2xl z-10 w-full h-[10%] bg-gradient-to-t from-black/20 to-transparent pointer-events-none"></div>
|
2024-02-13 02:28:36 +01:00
|
|
|
{player}
|
2024-02-10 13:30:53 +01:00
|
|
|
|
|
|
|
<div
|
2024-02-28 04:40:57 +01:00
|
|
|
className={`absolute inset-0 w-full ${
|
2024-02-10 13:30:53 +01:00
|
|
|
showStillWithoutActivity && !liveReady ? "visible" : "invisible"
|
|
|
|
}`}
|
|
|
|
>
|
|
|
|
<AutoUpdatingCameraImage
|
2024-02-28 04:40:57 +01:00
|
|
|
className="size-full"
|
2024-02-10 13:30:53 +01:00
|
|
|
camera={cameraConfig.name}
|
|
|
|
showFps={false}
|
2024-02-13 02:28:36 +01:00
|
|
|
reloadInterval={stillReloadInterval}
|
2024-02-10 13:30:53 +01:00
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div className="absolute flex left-2 top-2 gap-2">
|
|
|
|
<Chip
|
|
|
|
in={activeMotion}
|
2024-02-13 00:34:45 +01:00
|
|
|
className={`bg-gradient-to-br from-gray-400 to-gray-500 bg-gray-500`}
|
2024-02-10 13:30:53 +01:00
|
|
|
>
|
2024-02-28 04:40:57 +01:00
|
|
|
<MdLeakAdd className="size-4 text-motion" />
|
2024-02-27 22:39:05 +01:00
|
|
|
<div className="hidden md:block ml-1 text-white text-xs">Motion</div>
|
2024-02-10 13:30:53 +01:00
|
|
|
</Chip>
|
|
|
|
|
|
|
|
{cameraConfig.audio.enabled_in_config && (
|
|
|
|
<Chip
|
|
|
|
in={activeAudio}
|
2024-02-13 00:34:45 +01:00
|
|
|
className={`bg-gradient-to-br from-gray-400 to-gray-500 bg-gray-500`}
|
2024-02-10 13:30:53 +01:00
|
|
|
>
|
2024-02-28 04:40:57 +01:00
|
|
|
<BsSoundwave className="size-4 text-audio" />
|
2024-02-27 22:39:05 +01:00
|
|
|
<div className="hidden md:block ml-1 text-white text-xs">Sound</div>
|
2024-02-10 13:30:53 +01:00
|
|
|
</Chip>
|
|
|
|
)}
|
|
|
|
</div>
|
|
|
|
|
2024-02-27 22:39:05 +01:00
|
|
|
{isDesktop && (
|
|
|
|
<Chip className="absolute right-2 top-2 bg-gradient-to-br from-gray-400 to-gray-500 bg-gray-500">
|
|
|
|
{recording == "ON" && (
|
2024-02-28 04:40:57 +01:00
|
|
|
<MdCircle className="size-2 drop-shadow-md shadow-danger text-danger" />
|
2024-02-27 22:39:05 +01:00
|
|
|
)}
|
|
|
|
<div className="ml-1 capitalize text-white text-xs">
|
|
|
|
{cameraConfig.name.replaceAll("_", " ")}
|
|
|
|
</div>
|
|
|
|
</Chip>
|
|
|
|
)}
|
2024-02-10 13:30:53 +01:00
|
|
|
</div>
|
|
|
|
);
|
2023-12-16 00:24:50 +01:00
|
|
|
}
|