mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-03-18 06:08:22 +03:00
Apply requested live view/player tuning changes
This commit is contained in:
parent
adb91dfa9e
commit
9f05a677e2
@ -109,12 +109,41 @@ export default function LivePlayer({
|
||||
offline,
|
||||
} = useCameraActivity(cameraConfig);
|
||||
|
||||
const cameraActive = useMemo(
|
||||
() =>
|
||||
!showStillWithoutActivity ||
|
||||
(windowVisible && (activeMotion || activeTracking)),
|
||||
[activeMotion, activeTracking, showStillWithoutActivity, windowVisible],
|
||||
);
|
||||
const [stickyActive, setStickyActive] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (showStillWithoutActivity) {
|
||||
setStickyActive(true);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (activeMotion || activeTracking) {
|
||||
setStickyActive(true);
|
||||
}
|
||||
}, [activeMotion, activeTracking]);
|
||||
|
||||
const cameraActive = useMemo(() => {
|
||||
// continuous: всегда активна
|
||||
if (!showStillWithoutActivity) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// smart, но уже "залипла" → ведём себя как continuous
|
||||
if (stickyActive) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// обычный smart до первого движения
|
||||
return windowVisible && (activeMotion || activeTracking);
|
||||
}, [
|
||||
activeMotion,
|
||||
activeTracking,
|
||||
showStillWithoutActivity,
|
||||
windowVisible,
|
||||
stickyActive,
|
||||
]);
|
||||
|
||||
// camera live state
|
||||
|
||||
|
||||
@ -465,11 +465,11 @@ function MSEPlayer({
|
||||
bufLen = 0;
|
||||
sb.appendBuffer(data);
|
||||
} else if (sb.buffered && sb.buffered.length) {
|
||||
const end = sb.buffered.end(sb.buffered.length - 1) - 15;
|
||||
const end = sb.buffered.end(sb.buffered.length - 1) - 10;
|
||||
const start = sb.buffered.start(0);
|
||||
if (end > start) {
|
||||
sb.remove(start, end);
|
||||
msRef.current?.setLiveSeekableRange(end, end + 15);
|
||||
msRef.current?.setLiveSeekableRange(end, end + 10);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
@ -511,7 +511,7 @@ function MSEPlayer({
|
||||
if (buffered.length > 0) {
|
||||
const liveEdge = buffered.end(buffered.length - 1);
|
||||
// Jump to the live edge
|
||||
videoRef.current.currentTime = liveEdge - 0.75;
|
||||
videoRef.current.currentTime = liveEdge - 0.45;
|
||||
lastJumpTimeRef.current = Date.now();
|
||||
}
|
||||
};
|
||||
@ -520,21 +520,21 @@ function MSEPlayer({
|
||||
const filledEntries = bufferTimes.current.length;
|
||||
const sum = bufferTimes.current.reduce((a, b) => a + b, 0);
|
||||
const averageBufferTime = filledEntries ? sum / filledEntries : 0;
|
||||
return averageBufferTime * (isSafari || isIOS ? 3 : 1.5);
|
||||
return averageBufferTime * (isSafari || isIOS ? 3 : 0.6);
|
||||
};
|
||||
|
||||
const calculateAdaptivePlaybackRate = (
|
||||
bufferTime: number,
|
||||
bufferThreshold: number,
|
||||
) => {
|
||||
const alpha = 0.2; // aggressiveness of playback rate increase
|
||||
const beta = 0.5; // steepness of exponential growth
|
||||
const alpha = 0.1; // aggressiveness of playback rate increase
|
||||
const beta = 0.3; // steepness of exponential growth
|
||||
|
||||
// don't adjust playback rate if we're close enough to live
|
||||
// or if we just started streaming
|
||||
if (
|
||||
((bufferTime <= bufferThreshold && bufferThreshold < 3) ||
|
||||
bufferTime < 3) &&
|
||||
((bufferTime <= bufferThreshold && bufferThreshold < 1) ||
|
||||
bufferTime < 1) &&
|
||||
bufferTimes.current.length <= MAX_BUFFER_ENTRIES
|
||||
) {
|
||||
return 1;
|
||||
@ -548,7 +548,7 @@ function MSEPlayer({
|
||||
|
||||
if (
|
||||
videoRef.current &&
|
||||
(videoRef.current.playbackRate === 1 || bufferTime < 3)
|
||||
(videoRef.current.playbackRate === 1 || bufferTime < 1)
|
||||
) {
|
||||
if (bufferTimes.current.length < MAX_BUFFER_ENTRIES) {
|
||||
bufferTimes.current.push(bufferTime);
|
||||
@ -563,7 +563,7 @@ function MSEPlayer({
|
||||
// if we have > 3 seconds of buffered data and we're still not playing,
|
||||
// something might be wrong - maybe codec issue, no audio, etc
|
||||
// so mark the player as playing so that error handlers will fire
|
||||
if (!isPlaying && playbackEnabled && bufferTime > 3) {
|
||||
if (!isPlaying && playbackEnabled && bufferTime > 1) {
|
||||
setIsPlaying(true);
|
||||
lastJumpTimeRef.current = Date.now();
|
||||
onPlaying?.();
|
||||
@ -592,8 +592,7 @@ function MSEPlayer({
|
||||
// time
|
||||
if (videoRef.current && isPlaying && playbackEnabled) {
|
||||
if (
|
||||
(isSafari || isIOS) &&
|
||||
bufferTime > 3 &&
|
||||
bufferTime > 0.4 &&
|
||||
Date.now() - lastJumpTimeRef.current > BUFFERING_COOLDOWN_TIMEOUT
|
||||
) {
|
||||
// Jump to live on Safari/iOS due to a change of playback rate causing re-buffering
|
||||
|
||||
@ -35,7 +35,7 @@ import useSWR from "swr";
|
||||
import { isDesktop, isMobile } from "react-device-detect";
|
||||
import BirdseyeLivePlayer from "@/components/player/BirdseyeLivePlayer";
|
||||
import LivePlayer from "@/components/player/LivePlayer";
|
||||
import { IoClose } from "react-icons/io5";
|
||||
import { IoClose, IoStatsChart } from "react-icons/io5";
|
||||
import { LuLayoutDashboard, LuPencil } from "react-icons/lu";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { EditGroupDialog } from "@/components/filter/CameraGroupSelector";
|
||||
@ -404,16 +404,44 @@ export default function DraggableGridLayout({
|
||||
};
|
||||
|
||||
// audio and stats states
|
||||
const [globalStreamStatsEnabled, setGlobalStreamStatsEnabled] =
|
||||
useState(false);
|
||||
|
||||
const getStreamStatsFromStorage = (): boolean => {
|
||||
const storedValue = localStorage.getItem("globalStreamStatsEnabled");
|
||||
return storedValue === "true";
|
||||
};
|
||||
|
||||
const setStreamStatsToStorage = (value: boolean): void => {
|
||||
localStorage.setItem("globalStreamStatsEnabled", value.toString());
|
||||
};
|
||||
|
||||
const toggleGlobalStreamStats = () => {
|
||||
setGlobalStreamStatsEnabled((prevState) => {
|
||||
const newState = !prevState;
|
||||
setStreamStatsToStorage(newState);
|
||||
return newState;
|
||||
});
|
||||
};
|
||||
|
||||
const [audioStates, setAudioStates] = useState<AudioState>({});
|
||||
const [volumeStates, setVolumeStates] = useState<VolumeState>({});
|
||||
const [statsStates, setStatsStates] = useState<StatsState>(() => {
|
||||
const initialStates: StatsState = {};
|
||||
const [statsStates, setStatsStates] = useState<StatsState>({});
|
||||
|
||||
useEffect(() => {
|
||||
const initialStreamStatsState = getStreamStatsFromStorage();
|
||||
setGlobalStreamStatsEnabled(initialStreamStatsState);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const updatedStatsState: StatsState = {};
|
||||
|
||||
cameras.forEach((camera) => {
|
||||
initialStates[camera.name] = false;
|
||||
updatedStatsState[camera.name] = globalStreamStatsEnabled;
|
||||
});
|
||||
return initialStates;
|
||||
});
|
||||
|
||||
setStatsStates(updatedStatsState);
|
||||
}, [globalStreamStatsEnabled, cameras]);
|
||||
|
||||
const toggleStats = (cameraName: string): void => {
|
||||
setStatsStates((prev) => ({
|
||||
@ -628,7 +656,7 @@ export default function DraggableGridLayout({
|
||||
}
|
||||
audioState={audioStates[camera.name]}
|
||||
toggleAudio={() => toggleAudio(camera.name)}
|
||||
statsState={statsStates[camera.name]}
|
||||
statsState={statsStates[camera.name] ?? true}
|
||||
toggleStats={() => toggleStats(camera.name)}
|
||||
volumeState={volumeStates[camera.name]}
|
||||
setVolumeState={(value) =>
|
||||
@ -665,7 +693,7 @@ export default function DraggableGridLayout({
|
||||
cameraConfig={camera}
|
||||
preferredLiveMode={preferredLiveModes[camera.name] ?? "mse"}
|
||||
playInBackground={false}
|
||||
showStats={statsStates[camera.name]}
|
||||
showStats={statsStates[camera.name] ?? true}
|
||||
onClick={() => {
|
||||
!isEditMode && onSelectCamera(camera.name);
|
||||
}}
|
||||
@ -673,9 +701,7 @@ export default function DraggableGridLayout({
|
||||
setPreferredLiveModes((prevModes) => {
|
||||
const newModes = { ...prevModes };
|
||||
if (e === "mse-decode") {
|
||||
newModes[camera.name] = "webrtc";
|
||||
} else {
|
||||
newModes[camera.name] = "jsmpeg";
|
||||
delete newModes[camera.name];
|
||||
}
|
||||
return newModes;
|
||||
});
|
||||
@ -699,6 +725,21 @@ export default function DraggableGridLayout({
|
||||
"z-50 flex flex-row gap-2",
|
||||
)}
|
||||
>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div
|
||||
className="cursor-pointer rounded-lg bg-secondary text-secondary-foreground opacity-60 transition-all duration-300 hover:bg-muted hover:opacity-100"
|
||||
onClick={toggleGlobalStreamStats}
|
||||
>
|
||||
<IoStatsChart className="size-5 md:m-[6px]" />
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
{globalStreamStatsEnabled
|
||||
? t("streamStats.disable")
|
||||
: t("streamStats.enable")}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div
|
||||
|
||||
@ -150,7 +150,7 @@ export default function LiveCameraView({
|
||||
const [streamName, setStreamName, streamNameLoaded] =
|
||||
useUserPersistence<string>(
|
||||
`${camera.name}-stream`,
|
||||
Object.values(camera.live.streams)[0],
|
||||
Object.values(camera.live.streams)[1],
|
||||
);
|
||||
|
||||
const isRestreamed = useMemo(
|
||||
@ -273,7 +273,7 @@ export default function LiveCameraView({
|
||||
false,
|
||||
);
|
||||
|
||||
const [showStats, setShowStats] = useState(false);
|
||||
const [showStats, setShowStats] = useState(true);
|
||||
const [debug, setDebug] = useState(false);
|
||||
|
||||
useSearchEffect("debug", (value: string) => {
|
||||
|
||||
@ -566,7 +566,7 @@ export default function LiveDashboardView({
|
||||
}
|
||||
audioState={audioStates[camera.name]}
|
||||
toggleAudio={() => toggleAudio(camera.name)}
|
||||
statsState={statsStates[camera.name]}
|
||||
statsState={statsStates[camera.name] ?? true}
|
||||
toggleStats={() => toggleStats(camera.name)}
|
||||
volumeState={volumeStates[camera.name] ?? 1}
|
||||
setVolumeState={(value) =>
|
||||
@ -599,7 +599,7 @@ export default function LiveDashboardView({
|
||||
alwaysShowCameraName={displayCameraNames}
|
||||
useWebGL={useWebGL}
|
||||
playInBackground={false}
|
||||
showStats={statsStates[camera.name]}
|
||||
showStats={statsStates[camera.name] ?? true}
|
||||
streamName={streamName}
|
||||
onClick={() => onSelectCamera(camera.name)}
|
||||
onError={(e) => handleError(camera.name, e)}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user