mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-08-02 17:12:16 +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,6 +1,7 @@
|
||||
import { baseUrl } from "@/api/baseUrl";
|
||||
import { useResizeObserver } from "@/hooks/resize-observer";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { PlayerStatsType } from "@/types/live";
|
||||
// @ts-expect-error we know this doesn't have types
|
||||
import JSMpeg from "@cycjimmy/jsmpeg-player";
|
||||
import React, { useEffect, useMemo, useRef, useState } from "react";
|
||||
@@ -12,6 +13,8 @@ type JSMpegPlayerProps = {
|
||||
height: number;
|
||||
containerRef: React.MutableRefObject<HTMLDivElement | null>;
|
||||
playbackEnabled: boolean;
|
||||
useWebGL: boolean;
|
||||
setStats?: (stats: PlayerStatsType) => void;
|
||||
onPlaying?: () => void;
|
||||
};
|
||||
|
||||
@@ -22,6 +25,8 @@ export default function JSMpegPlayer({
|
||||
className,
|
||||
containerRef,
|
||||
playbackEnabled,
|
||||
useWebGL = false,
|
||||
setStats,
|
||||
onPlaying,
|
||||
}: JSMpegPlayerProps) {
|
||||
const url = `${baseUrl.replace(/^http/, "ws")}live/jsmpeg/${camera}`;
|
||||
@@ -33,6 +38,9 @@ export default function JSMpegPlayer({
|
||||
const [hasData, setHasData] = useState(false);
|
||||
const hasDataRef = useRef(hasData);
|
||||
const [dimensionsReady, setDimensionsReady] = useState(false);
|
||||
const bytesReceivedRef = useRef(0);
|
||||
const lastTimestampRef = useRef(Date.now());
|
||||
const statsIntervalRef = useRef<NodeJS.Timeout | null>(null);
|
||||
|
||||
const selectedContainerRef = useMemo(
|
||||
() => (containerRef.current ? containerRef : internalContainerRef),
|
||||
@@ -111,6 +119,8 @@ export default function JSMpegPlayer({
|
||||
const canvas = canvasRef.current;
|
||||
let videoElement: JSMpeg.VideoElement | null = null;
|
||||
|
||||
let frameCount = 0;
|
||||
|
||||
setHasData(false);
|
||||
|
||||
if (videoWrapper && playbackEnabled) {
|
||||
@@ -123,21 +133,68 @@ export default function JSMpegPlayer({
|
||||
{
|
||||
protocols: [],
|
||||
audio: false,
|
||||
disableGl: camera != "birdseye",
|
||||
disableWebAssembly: camera != "birdseye",
|
||||
disableGl: !useWebGL,
|
||||
disableWebAssembly: !useWebGL,
|
||||
videoBufferSize: 1024 * 1024 * 4,
|
||||
onVideoDecode: () => {
|
||||
if (!hasDataRef.current) {
|
||||
setHasData(true);
|
||||
onPlayingRef.current?.();
|
||||
}
|
||||
frameCount++;
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
// Set up WebSocket message handler
|
||||
if (
|
||||
videoElement.player &&
|
||||
videoElement.player.source &&
|
||||
videoElement.player.source.socket
|
||||
) {
|
||||
const socket = videoElement.player.source.socket;
|
||||
socket.addEventListener("message", (event: MessageEvent) => {
|
||||
if (event.data instanceof ArrayBuffer) {
|
||||
bytesReceivedRef.current += event.data.byteLength;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Update stats every second
|
||||
statsIntervalRef.current = setInterval(() => {
|
||||
const currentTimestamp = Date.now();
|
||||
const timeDiff = (currentTimestamp - lastTimestampRef.current) / 1000; // in seconds
|
||||
const bitrate = (bytesReceivedRef.current * 8) / timeDiff / 1000; // in kbps
|
||||
|
||||
setStats?.({
|
||||
streamType: "jsmpeg",
|
||||
bandwidth: Math.round(bitrate),
|
||||
totalFrames: frameCount,
|
||||
latency: undefined,
|
||||
droppedFrames: undefined,
|
||||
decodedFrames: undefined,
|
||||
droppedFrameRate: undefined,
|
||||
});
|
||||
|
||||
bytesReceivedRef.current = 0;
|
||||
lastTimestampRef.current = currentTimestamp;
|
||||
}, 1000);
|
||||
|
||||
return () => {
|
||||
if (statsIntervalRef.current) {
|
||||
clearInterval(statsIntervalRef.current);
|
||||
frameCount = 0;
|
||||
statsIntervalRef.current = null;
|
||||
}
|
||||
};
|
||||
}, 0);
|
||||
|
||||
return () => {
|
||||
clearTimeout(initPlayer);
|
||||
if (statsIntervalRef.current) {
|
||||
clearInterval(statsIntervalRef.current);
|
||||
statsIntervalRef.current = null;
|
||||
}
|
||||
if (videoElement) {
|
||||
try {
|
||||
// this causes issues in react strict mode
|
||||
|
||||
Reference in New Issue
Block a user