import WebRtcPlayer from "./WebRTCPlayer"; import { CameraConfig } from "@/types/frigateConfig"; import AutoUpdatingCameraImage from "../camera/AutoUpdatingCameraImage"; import ActivityIndicator from "../indicators/activity-indicator"; import { useCallback, useEffect, useMemo, useState } from "react"; import MSEPlayer from "./MsePlayer"; import JSMpegPlayer from "./JSMpegPlayer"; import { MdCircle } from "react-icons/md"; import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip"; import { useCameraActivity } from "@/hooks/use-camera-activity"; import { LivePlayerError, LivePlayerMode, VideoResolutionType, } from "@/types/live"; import useCameraLiveMode from "@/hooks/use-camera-live-mode"; import { getIconForLabel } from "@/utils/iconUtil"; import Chip from "../indicators/Chip"; import { capitalizeFirstLetter } from "@/utils/stringUtil"; import { cn } from "@/lib/utils"; type LivePlayerProps = { cameraRef?: (ref: HTMLDivElement | null) => void; containerRef?: React.MutableRefObject; className?: string; cameraConfig: CameraConfig; preferredLiveMode?: LivePlayerMode; showStillWithoutActivity?: boolean; windowVisible?: boolean; playAudio?: boolean; micEnabled?: boolean; // only webrtc supports mic iOSCompatFullScreen?: boolean; pip?: boolean; autoLive?: boolean; onClick?: () => void; setFullResolution?: React.Dispatch>; onError?: (error: LivePlayerError) => void; }; export default function LivePlayer({ cameraRef = undefined, containerRef, className, cameraConfig, preferredLiveMode, showStillWithoutActivity = true, windowVisible = true, playAudio = false, micEnabled = false, iOSCompatFullScreen = false, pip, autoLive = true, onClick, setFullResolution, onError, }: LivePlayerProps) { // camera activity const { activeMotion, activeTracking, objects, offline } = useCameraActivity(cameraConfig); const cameraActive = useMemo( () => !showStillWithoutActivity || (windowVisible && (activeMotion || activeTracking)), [activeMotion, activeTracking, showStillWithoutActivity, windowVisible], ); // camera live state const liveMode = useCameraLiveMode(cameraConfig, preferredLiveMode); const [liveReady, setLiveReady] = useState(false); useEffect(() => { if (!autoLive) { return; } if (!liveReady) { if (cameraActive && liveMode == "jsmpeg") { setLiveReady(true); } return; } if (!cameraActive) { setTimeout(() => setLiveReady(false), 500); } // live mode won't change // eslint-disable-next-line react-hooks/exhaustive-deps }, [autoLive, cameraActive, liveReady]); // camera still state const stillReloadInterval = useMemo(() => { if (!windowVisible || offline || !showStillWithoutActivity) { return -1; // no reason to update the image when the window is not visible } if (liveReady) { return 60000; } if (activeMotion || activeTracking) { if (autoLive) { return 200; } else { return 59000; } } return 30000; }, [ autoLive, showStillWithoutActivity, liveReady, activeMotion, activeTracking, offline, windowVisible, ]); useEffect(() => { setLiveReady(false); }, [preferredLiveMode]); const playerIsPlaying = useCallback(() => { setLiveReady(true); }, []); if (!cameraConfig) { return ; } let player; if (!autoLive) { player = null; } else if (liveMode == "webrtc") { player = ( ); } else if (liveMode == "mse") { if ("MediaSource" in window || "ManagedMediaSource" in window) { player = ( ); } else { player = (
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.
); } } else if (liveMode == "jsmpeg") { if (cameraActive || !showStillWithoutActivity) { player = ( ); } else { player = null; } } else { player = ; } return (
{((showStillWithoutActivity && !liveReady) || liveReady) && ( <>
)} {player} {!offline && !showStillWithoutActivity && !liveReady && ( )} {((showStillWithoutActivity && !liveReady) || liveReady) && objects.length > 0 && (
{[ ...new Set([ ...(objects || []).map(({ label }) => label), ]), ] .map((label) => { return getIconForLabel(label, "size-3 text-white"); }) .sort()}
{[ ...new Set([ ...(objects || []).map(({ label, sub_label }) => label.endsWith("verified") ? sub_label : label, ), ]), ] .filter( (label) => label !== undefined && !label.includes("-verified"), ) .map((label) => capitalizeFirstLetter(label)) .sort() .join(", ") .replaceAll("-verified", "")}
)}
{autoLive && !offline && activeMotion && ((showStillWithoutActivity && !liveReady) || liveReady) && ( )} {offline && ( {cameraConfig.name.replaceAll("_", " ")} )}
); }