mirror of https://github.com/jetkvm/kvm.git
Cleanup: remove polling fallback for /audio/mute status
This commit is contained in:
parent
97bcb3c1ea
commit
32055f5762
|
@ -2,7 +2,7 @@ import { MdOutlineContentPasteGo, MdVolumeOff, MdVolumeUp, MdGraphicEq } from "r
|
||||||
import { LuCable, LuHardDrive, LuMaximize, LuSettings, LuSignal } from "react-icons/lu";
|
import { LuCable, LuHardDrive, LuMaximize, LuSettings, LuSignal } from "react-icons/lu";
|
||||||
import { FaKeyboard } from "react-icons/fa6";
|
import { FaKeyboard } from "react-icons/fa6";
|
||||||
import { Popover, PopoverButton, PopoverPanel } from "@headlessui/react";
|
import { Popover, PopoverButton, PopoverPanel } from "@headlessui/react";
|
||||||
import { Fragment, useCallback, useEffect, useRef, useState } from "react";
|
import { Fragment, useCallback, useRef } from "react";
|
||||||
import { CommandLineIcon } from "@heroicons/react/20/solid";
|
import { CommandLineIcon } from "@heroicons/react/20/solid";
|
||||||
|
|
||||||
import { Button } from "@components/Button";
|
import { Button } from "@components/Button";
|
||||||
|
@ -21,7 +21,7 @@ import ExtensionPopover from "@/components/popovers/ExtensionPopover";
|
||||||
import AudioControlPopover from "@/components/popovers/AudioControlPopover";
|
import AudioControlPopover from "@/components/popovers/AudioControlPopover";
|
||||||
import { useDeviceUiNavigation } from "@/hooks/useAppNavigation";
|
import { useDeviceUiNavigation } from "@/hooks/useAppNavigation";
|
||||||
import { useAudioEvents } from "@/hooks/useAudioEvents";
|
import { useAudioEvents } from "@/hooks/useAudioEvents";
|
||||||
import api from "@/api";
|
|
||||||
|
|
||||||
// Type for microphone error
|
// Type for microphone error
|
||||||
interface MicrophoneError {
|
interface MicrophoneError {
|
||||||
|
@ -83,35 +83,10 @@ export default function Actionbar({
|
||||||
);
|
);
|
||||||
|
|
||||||
// Use WebSocket-based audio events for real-time updates
|
// Use WebSocket-based audio events for real-time updates
|
||||||
const { audioMuted, isConnected } = useAudioEvents();
|
const { audioMuted } = useAudioEvents();
|
||||||
|
|
||||||
// Fallback to polling if WebSocket is not connected
|
// Use WebSocket data exclusively - no polling fallback
|
||||||
const [fallbackMuted, setFallbackMuted] = useState(false);
|
const isMuted = audioMuted ?? false; // Default to false if WebSocket data not available yet
|
||||||
useEffect(() => {
|
|
||||||
if (!isConnected) {
|
|
||||||
// Load initial state
|
|
||||||
api.GET("/audio/mute").then(async resp => {
|
|
||||||
if (resp.ok) {
|
|
||||||
const data = await resp.json();
|
|
||||||
setFallbackMuted(!!data.muted);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Fallback polling when WebSocket is not available
|
|
||||||
const interval = setInterval(async () => {
|
|
||||||
const resp = await api.GET("/audio/mute");
|
|
||||||
if (resp.ok) {
|
|
||||||
const data = await resp.json();
|
|
||||||
setFallbackMuted(!!data.muted);
|
|
||||||
}
|
|
||||||
}, 1000);
|
|
||||||
|
|
||||||
return () => clearInterval(interval);
|
|
||||||
}
|
|
||||||
}, [isConnected]);
|
|
||||||
|
|
||||||
// Use WebSocket data when available, fallback to polling data otherwise
|
|
||||||
const isMuted = isConnected && audioMuted !== null ? audioMuted : fallbackMuted;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container className="border-b border-b-slate-800/20 bg-white dark:border-b-slate-300/20 dark:bg-slate-900">
|
<Container className="border-b border-b-slate-800/20 bg-white dark:border-b-slate-300/20 dark:bg-slate-900">
|
||||||
|
|
|
@ -41,23 +41,7 @@ interface AudioConfig {
|
||||||
FrameSize: string;
|
FrameSize: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AudioMetrics {
|
|
||||||
frames_received: number;
|
|
||||||
frames_dropped: number;
|
|
||||||
bytes_processed: number;
|
|
||||||
last_frame_time: string;
|
|
||||||
connection_drops: number;
|
|
||||||
average_latency: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface MicrophoneMetrics {
|
|
||||||
frames_sent: number;
|
|
||||||
frames_dropped: number;
|
|
||||||
bytes_processed: number;
|
|
||||||
last_frame_time: string;
|
|
||||||
connection_drops: number;
|
|
||||||
average_latency: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -94,11 +78,7 @@ export default function AudioControlPopover({ microphone, open }: AudioControlPo
|
||||||
isConnected: wsConnected
|
isConnected: wsConnected
|
||||||
} = useAudioEvents();
|
} = useAudioEvents();
|
||||||
|
|
||||||
// Fallback state for when WebSocket is not connected
|
// WebSocket-only implementation - no fallback polling
|
||||||
const [fallbackMuted, setFallbackMuted] = useState(false);
|
|
||||||
const [fallbackMetrics, setFallbackMetrics] = useState<AudioMetrics | null>(null);
|
|
||||||
const [fallbackMicMetrics, setFallbackMicMetrics] = useState<MicrophoneMetrics | null>(null);
|
|
||||||
const [fallbackConnected, setFallbackConnected] = useState(false);
|
|
||||||
|
|
||||||
// Microphone state from props
|
// Microphone state from props
|
||||||
const {
|
const {
|
||||||
|
@ -115,11 +95,11 @@ export default function AudioControlPopover({ microphone, open }: AudioControlPo
|
||||||
isToggling,
|
isToggling,
|
||||||
} = microphone;
|
} = microphone;
|
||||||
|
|
||||||
// Use WebSocket data when available, fallback to polling data otherwise
|
// Use WebSocket data exclusively - no polling fallback
|
||||||
const isMuted = wsConnected && audioMuted !== null ? audioMuted : fallbackMuted;
|
const isMuted = audioMuted ?? false;
|
||||||
const metrics = wsConnected && audioMetrics !== null ? audioMetrics : fallbackMetrics;
|
const metrics = audioMetrics;
|
||||||
const micMetrics = wsConnected && microphoneMetrics !== null ? microphoneMetrics : fallbackMicMetrics;
|
const micMetrics = microphoneMetrics;
|
||||||
const isConnected = wsConnected ? wsConnected : fallbackConnected;
|
const isConnected = wsConnected;
|
||||||
|
|
||||||
// Audio level monitoring - enable only when popover is open and microphone is active to save resources
|
// Audio level monitoring - enable only when popover is open and microphone is active to save resources
|
||||||
const analysisEnabled = (open ?? true) && isMicrophoneActive;
|
const analysisEnabled = (open ?? true) && isMicrophoneActive;
|
||||||
|
@ -150,34 +130,15 @@ export default function AudioControlPopover({ microphone, open }: AudioControlPo
|
||||||
}
|
}
|
||||||
}, [configsLoaded]);
|
}, [configsLoaded]);
|
||||||
|
|
||||||
// Optimize fallback polling - only run when WebSocket is not connected
|
// WebSocket-only implementation - sync microphone state when needed
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!wsConnected && !configsLoaded) {
|
|
||||||
// Load state once if configs aren't loaded yet
|
|
||||||
loadAudioState();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!wsConnected) {
|
|
||||||
loadAudioMetrics();
|
|
||||||
loadMicrophoneMetrics();
|
|
||||||
|
|
||||||
// Reduced frequency for fallback polling (every 3 seconds instead of 2)
|
|
||||||
const metricsInterval = setInterval(() => {
|
|
||||||
if (!wsConnected) { // Double-check to prevent unnecessary requests
|
|
||||||
loadAudioMetrics();
|
|
||||||
loadMicrophoneMetrics();
|
|
||||||
}
|
|
||||||
}, 3000);
|
|
||||||
return () => clearInterval(metricsInterval);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Always sync microphone state, but debounce it
|
// Always sync microphone state, but debounce it
|
||||||
const syncTimeout = setTimeout(() => {
|
const syncTimeout = setTimeout(() => {
|
||||||
syncMicrophoneState();
|
syncMicrophoneState();
|
||||||
}, 500);
|
}, 500);
|
||||||
|
|
||||||
return () => clearTimeout(syncTimeout);
|
return () => clearTimeout(syncTimeout);
|
||||||
}, [wsConnected, syncMicrophoneState, configsLoaded]);
|
}, [syncMicrophoneState]);
|
||||||
|
|
||||||
const loadAudioConfigurations = async () => {
|
const loadAudioConfigurations = async () => {
|
||||||
try {
|
try {
|
||||||
|
@ -203,60 +164,14 @@ export default function AudioControlPopover({ microphone, open }: AudioControlPo
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const loadAudioState = async () => {
|
|
||||||
try {
|
|
||||||
// Load mute state only (configurations are loaded separately)
|
|
||||||
const muteResp = await api.GET("/audio/mute");
|
|
||||||
if (muteResp.ok) {
|
|
||||||
const muteData = await muteResp.json();
|
|
||||||
setFallbackMuted(!!muteData.muted);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Failed to load audio state:", error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const loadAudioMetrics = async () => {
|
|
||||||
try {
|
|
||||||
const resp = await api.GET("/audio/metrics");
|
|
||||||
if (resp.ok) {
|
|
||||||
const data = await resp.json();
|
|
||||||
setFallbackMetrics(data);
|
|
||||||
// Consider connected if API call succeeds, regardless of frame count
|
|
||||||
setFallbackConnected(true);
|
|
||||||
} else {
|
|
||||||
setFallbackConnected(false);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Failed to load audio metrics:", error);
|
|
||||||
setFallbackConnected(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const loadMicrophoneMetrics = async () => {
|
|
||||||
try {
|
|
||||||
const resp = await api.GET("/microphone/metrics");
|
|
||||||
if (resp.ok) {
|
|
||||||
const data = await resp.json();
|
|
||||||
setFallbackMicMetrics(data);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Failed to load microphone metrics:", error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleToggleMute = async () => {
|
const handleToggleMute = async () => {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
try {
|
try {
|
||||||
const resp = await api.POST("/audio/mute", { muted: !isMuted });
|
const resp = await api.POST("/audio/mute", { muted: !isMuted });
|
||||||
if (resp.ok) {
|
if (!resp.ok) {
|
||||||
// WebSocket will handle the state update, but update fallback for immediate feedback
|
console.error("Failed to toggle mute:", resp.statusText);
|
||||||
if (!wsConnected) {
|
|
||||||
setFallbackMuted(!isMuted);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
// WebSocket will handle the state update automatically
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to toggle mute:", error);
|
console.error("Failed to toggle mute:", error);
|
||||||
} finally {
|
} finally {
|
||||||
|
|
4
web.go
4
web.go
|
@ -159,10 +159,6 @@ func setupRouter() *gin.Engine {
|
||||||
protected.POST("/storage/upload", handleUploadHttp)
|
protected.POST("/storage/upload", handleUploadHttp)
|
||||||
}
|
}
|
||||||
|
|
||||||
protected.GET("/audio/mute", func(c *gin.Context) {
|
|
||||||
c.JSON(200, gin.H{"muted": audio.IsAudioMuted()})
|
|
||||||
})
|
|
||||||
|
|
||||||
protected.POST("/audio/mute", func(c *gin.Context) {
|
protected.POST("/audio/mute", func(c *gin.Context) {
|
||||||
type muteReq struct {
|
type muteReq struct {
|
||||||
Muted bool `json:"muted"`
|
Muted bool `json:"muted"`
|
||||||
|
|
Loading…
Reference in New Issue