mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-07-31 08:09:02 +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:
@@ -1,5 +1,5 @@
|
||||
import { baseUrl } from "@/api/baseUrl";
|
||||
import { LivePlayerError } from "@/types/live";
|
||||
import { LivePlayerError, PlayerStatsType } from "@/types/live";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
|
||||
type WebRtcPlayerProps = {
|
||||
@@ -7,9 +7,12 @@ type WebRtcPlayerProps = {
|
||||
camera: string;
|
||||
playbackEnabled?: boolean;
|
||||
audioEnabled?: boolean;
|
||||
volume?: number;
|
||||
microphoneEnabled?: boolean;
|
||||
iOSCompatFullScreen?: boolean; // ios doesn't support fullscreen divs so we must support the video element
|
||||
pip?: boolean;
|
||||
getStats?: boolean;
|
||||
setStats?: (stats: PlayerStatsType) => void;
|
||||
onPlaying?: () => void;
|
||||
onError?: (error: LivePlayerError) => void;
|
||||
};
|
||||
@@ -19,9 +22,12 @@ export default function WebRtcPlayer({
|
||||
camera,
|
||||
playbackEnabled = true,
|
||||
audioEnabled = false,
|
||||
volume,
|
||||
microphoneEnabled = false,
|
||||
iOSCompatFullScreen = false,
|
||||
pip = false,
|
||||
getStats = false,
|
||||
setStats,
|
||||
onPlaying,
|
||||
onError,
|
||||
}: WebRtcPlayerProps) {
|
||||
@@ -194,6 +200,16 @@ export default function WebRtcPlayer({
|
||||
videoRef.current.requestPictureInPicture();
|
||||
}, [pip, videoRef]);
|
||||
|
||||
// control volume
|
||||
|
||||
useEffect(() => {
|
||||
if (!videoRef.current || volume == undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
videoRef.current.volume = volume;
|
||||
}, [volume, videoRef]);
|
||||
|
||||
useEffect(() => {
|
||||
videoLoadTimeoutRef.current = setTimeout(() => {
|
||||
onError?.("stalled");
|
||||
@@ -215,6 +231,75 @@ export default function WebRtcPlayer({
|
||||
onPlaying?.();
|
||||
};
|
||||
|
||||
// stats
|
||||
|
||||
useEffect(() => {
|
||||
if (!pcRef.current || !getStats) return;
|
||||
|
||||
let lastBytesReceived = 0;
|
||||
let lastTimestamp = 0;
|
||||
|
||||
const interval = setInterval(async () => {
|
||||
if (pcRef.current && videoRef.current && !videoRef.current.paused) {
|
||||
const report = await pcRef.current.getStats();
|
||||
let bytesReceived = 0;
|
||||
let timestamp = 0;
|
||||
let roundTripTime = 0;
|
||||
let framesReceived = 0;
|
||||
let framesDropped = 0;
|
||||
let framesDecoded = 0;
|
||||
|
||||
report.forEach((stat) => {
|
||||
if (stat.type === "inbound-rtp" && stat.kind === "video") {
|
||||
bytesReceived = stat.bytesReceived;
|
||||
timestamp = stat.timestamp;
|
||||
framesReceived = stat.framesReceived;
|
||||
framesDropped = stat.framesDropped;
|
||||
framesDecoded = stat.framesDecoded;
|
||||
}
|
||||
if (stat.type === "candidate-pair" && stat.state === "succeeded") {
|
||||
roundTripTime = stat.currentRoundTripTime;
|
||||
}
|
||||
});
|
||||
|
||||
const timeDiff = (timestamp - lastTimestamp) / 1000; // in seconds
|
||||
const bitrate =
|
||||
timeDiff > 0
|
||||
? (bytesReceived - lastBytesReceived) / timeDiff / 1000
|
||||
: 0; // in kbps
|
||||
|
||||
setStats?.({
|
||||
streamType: "WebRTC",
|
||||
bandwidth: Math.round(bitrate),
|
||||
latency: roundTripTime,
|
||||
totalFrames: framesReceived,
|
||||
droppedFrames: framesDropped,
|
||||
decodedFrames: framesDecoded,
|
||||
droppedFrameRate:
|
||||
framesReceived > 0 ? (framesDropped / framesReceived) * 100 : 0,
|
||||
});
|
||||
|
||||
lastBytesReceived = bytesReceived;
|
||||
lastTimestamp = timestamp;
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
return () => {
|
||||
clearInterval(interval);
|
||||
setStats?.({
|
||||
streamType: "-",
|
||||
bandwidth: 0,
|
||||
latency: undefined,
|
||||
totalFrames: 0,
|
||||
droppedFrames: undefined,
|
||||
decodedFrames: 0,
|
||||
droppedFrameRate: 0,
|
||||
});
|
||||
};
|
||||
// we need to listen on the value of the ref
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [pcRef, pcRef.current, getStats]);
|
||||
|
||||
return (
|
||||
<video
|
||||
ref={videoRef}
|
||||
|
||||
Reference in New Issue
Block a user