mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-08-06 19:07:20 +03:00
Improve live streaming (#16447)
* config file changes * config migrator * stream selection on single camera live view * camera streaming settings dialog * manage persistent group streaming settings * apply streaming settings in camera groups * add ability to clear all streaming settings from settings * docs * update reference config * fixes * clarify docs * use first stream as default in dialog * ensure still image is visible after switching stream type to none * docs * clarify docs * add ability to continue playing stream in background * fix props * put stream selection inside dropdown on desktop * add capabilities to live mode hook * live context menu component * resize observer: only return new dimensions if they've actually changed * pass volume prop to players * fix slider bug, https://github.com/shadcn-ui/ui/issues/1448 * update react-grid-layout * prevent animated transitions on draggable grid layout * add context menu to dashboards * use provider * streaming dialog from context menu * docs * add jsmpeg warning to context menu * audio and two way talk indicators in single camera view * add link to debug view * don't use hook * create manual events from live camera view * maintain grow classes on grid items * fix initial volume state on default dashboard * fix pointer events causing context menu to end up underneath image on iOS * mobile drawer tweaks * stream stats * show settings menu for non-restreamed cameras * consistent settings icon * tweaks * optional stats to fix birdseye player * add toaster to live camera view * fix crash on initial save in streaming dialog * don't require restreaming for context menu streaming settings * add debug view to context menu * stats fixes * update docs * always show stream info when restreamed * update camera streaming dialog * make note of no h265 support for webrtc * docs clarity * ensure docs show streams as a dict * docs clarity * fix css file * tweaks
This commit is contained in:
@@ -11,6 +11,7 @@ import { useCameraActivity } from "@/hooks/use-camera-activity";
|
||||
import {
|
||||
LivePlayerError,
|
||||
LivePlayerMode,
|
||||
PlayerStatsType,
|
||||
VideoResolutionType,
|
||||
} from "@/types/live";
|
||||
import { getIconForLabel } from "@/utils/iconUtil";
|
||||
@@ -20,20 +21,26 @@ import { cn } from "@/lib/utils";
|
||||
import { TbExclamationCircle } from "react-icons/tb";
|
||||
import { TooltipPortal } from "@radix-ui/react-tooltip";
|
||||
import { baseUrl } from "@/api/baseUrl";
|
||||
import { PlayerStats } from "./PlayerStats";
|
||||
|
||||
type LivePlayerProps = {
|
||||
cameraRef?: (ref: HTMLDivElement | null) => void;
|
||||
containerRef?: React.MutableRefObject<HTMLDivElement | null>;
|
||||
className?: string;
|
||||
cameraConfig: CameraConfig;
|
||||
streamName: string;
|
||||
preferredLiveMode: LivePlayerMode;
|
||||
showStillWithoutActivity?: boolean;
|
||||
useWebGL: boolean;
|
||||
windowVisible?: boolean;
|
||||
playAudio?: boolean;
|
||||
volume?: number;
|
||||
playInBackground: boolean;
|
||||
micEnabled?: boolean; // only webrtc supports mic
|
||||
iOSCompatFullScreen?: boolean;
|
||||
pip?: boolean;
|
||||
autoLive?: boolean;
|
||||
showStats?: boolean;
|
||||
onClick?: () => void;
|
||||
setFullResolution?: React.Dispatch<React.SetStateAction<VideoResolutionType>>;
|
||||
onError?: (error: LivePlayerError) => void;
|
||||
@@ -45,14 +52,19 @@ export default function LivePlayer({
|
||||
containerRef,
|
||||
className,
|
||||
cameraConfig,
|
||||
streamName,
|
||||
preferredLiveMode,
|
||||
showStillWithoutActivity = true,
|
||||
useWebGL = false,
|
||||
windowVisible = true,
|
||||
playAudio = false,
|
||||
volume,
|
||||
playInBackground = false,
|
||||
micEnabled = false,
|
||||
iOSCompatFullScreen = false,
|
||||
pip,
|
||||
autoLive = true,
|
||||
showStats = false,
|
||||
onClick,
|
||||
setFullResolution,
|
||||
onError,
|
||||
@@ -60,6 +72,18 @@ export default function LivePlayer({
|
||||
}: LivePlayerProps) {
|
||||
const internalContainerRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
// stats
|
||||
|
||||
const [stats, setStats] = useState<PlayerStatsType>({
|
||||
streamType: "-",
|
||||
bandwidth: 0, // in kbps
|
||||
latency: undefined, // in seconds
|
||||
totalFrames: 0,
|
||||
droppedFrames: undefined,
|
||||
decodedFrames: 0,
|
||||
droppedFrameRate: 0, // percentage
|
||||
});
|
||||
|
||||
// camera activity
|
||||
|
||||
const { activeMotion, activeTracking, objects, offline } =
|
||||
@@ -144,6 +168,25 @@ 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]);
|
||||
|
||||
useEffect(() => {
|
||||
if (showStillWithoutActivity && !autoLive) {
|
||||
setLiveReady(false);
|
||||
}
|
||||
}, [showStillWithoutActivity, autoLive]);
|
||||
|
||||
const playerIsPlaying = useCallback(() => {
|
||||
setLiveReady(true);
|
||||
}, []);
|
||||
@@ -153,15 +196,19 @@ 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}
|
||||
getStats={showStats}
|
||||
setStats={setStats}
|
||||
audioEnabled={playAudio}
|
||||
volume={volume}
|
||||
microphoneEnabled={micEnabled}
|
||||
iOSCompatFullScreen={iOSCompatFullScreen}
|
||||
onPlaying={playerIsPlaying}
|
||||
@@ -173,10 +220,15 @@ 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}
|
||||
volume={volume}
|
||||
playInBackground={playInBackground}
|
||||
getStats={showStats}
|
||||
setStats={setStats}
|
||||
onPlaying={playerIsPlaying}
|
||||
pip={pip}
|
||||
setFullResolution={setFullResolution}
|
||||
@@ -194,6 +246,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}
|
||||
@@ -201,6 +254,8 @@ export default function LivePlayer({
|
||||
playbackEnabled={
|
||||
cameraActive || !showStillWithoutActivity || liveReady
|
||||
}
|
||||
useWebGL={useWebGL}
|
||||
setStats={setStats}
|
||||
containerRef={containerRef ?? internalContainerRef}
|
||||
onPlaying={playerIsPlaying}
|
||||
/>
|
||||
@@ -293,7 +348,7 @@ export default function LivePlayer({
|
||||
)}
|
||||
>
|
||||
<AutoUpdatingCameraImage
|
||||
className="size-full"
|
||||
className="pointer-events-none size-full"
|
||||
cameraClasses="relative size-full flex justify-center"
|
||||
camera={cameraConfig.name}
|
||||
showFps={false}
|
||||
@@ -331,6 +386,9 @@ export default function LivePlayer({
|
||||
</Chip>
|
||||
)}
|
||||
</div>
|
||||
{showStats && (
|
||||
<PlayerStats stats={stats} minimal={cameraRef !== undefined} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user