Cleanup: remove polling fallback for /audio/mute status

This commit is contained in:
Alex P 2025-08-22 22:54:05 +00:00
parent 97bcb3c1ea
commit 32055f5762
3 changed files with 16 additions and 130 deletions

View File

@ -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">

View File

@ -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
View File

@ -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"`