Fix: USB Gadgets updates

This commit is contained in:
Alex P 2025-08-25 10:41:53 +00:00
parent bc53523fbb
commit 2afe2ca539
4 changed files with 56 additions and 4 deletions

View File

@ -23,6 +23,7 @@ const (
AudioEventMicrophoneMetrics AudioEventType = "microphone-metrics-update"
AudioEventProcessMetrics AudioEventType = "audio-process-metrics"
AudioEventMicProcessMetrics AudioEventType = "microphone-process-metrics"
AudioEventDeviceChanged AudioEventType = "audio-device-changed"
)
// AudioEvent represents a WebSocket audio event
@ -73,6 +74,12 @@ type ProcessMetricsData struct {
ProcessName string `json:"process_name"`
}
// AudioDeviceChangedData represents audio device configuration change data
type AudioDeviceChangedData struct {
Enabled bool `json:"enabled"`
Reason string `json:"reason"`
}
// AudioEventSubscriber represents a WebSocket connection subscribed to audio events
type AudioEventSubscriber struct {
conn *websocket.Conn
@ -164,6 +171,15 @@ func (aeb *AudioEventBroadcaster) BroadcastMicrophoneStateChanged(running, sessi
aeb.broadcast(event)
}
// BroadcastAudioDeviceChanged broadcasts audio device configuration changes
func (aeb *AudioEventBroadcaster) BroadcastAudioDeviceChanged(enabled bool, reason string) {
event := createAudioEvent(AudioEventDeviceChanged, AudioDeviceChangedData{
Enabled: enabled,
Reason: reason,
})
aeb.broadcast(event)
}
// sendInitialState sends current audio state to a new subscriber
func (aeb *AudioEventBroadcaster) sendInitialState(connectionID string) {
aeb.mutex.RLock()

View File

@ -15,6 +15,7 @@ import (
"github.com/pion/webrtc/v4"
"go.bug.st/serial"
"github.com/jetkvm/kvm/internal/audio"
"github.com/jetkvm/kvm/internal/usbgadget"
)
@ -956,6 +957,11 @@ func rpcSetUsbDevices(usbDevices usbgadget.Devices) error {
if err := audioSupervisor.Start(); err != nil {
logger.Error().Err(err).Msg("failed to start audio supervisor")
// Don't return error here as USB reconfiguration was successful
} else {
// Broadcast audio device change event to notify WebRTC session
broadcaster := audio.GetAudioEventBroadcaster()
broadcaster.BroadcastAudioDeviceChanged(true, "usb_reconfiguration")
logger.Info().Msg("broadcasted audio device change event after USB reconfiguration")
}
}
@ -999,6 +1005,11 @@ func rpcSetUsbDeviceState(device string, enabled bool) error {
logger.Info().Msg("starting audio processes due to audio device being enabled")
if err := audioSupervisor.Start(); err != nil {
logger.Error().Err(err).Msg("failed to start audio supervisor")
} else {
// Broadcast audio device change event to notify WebRTC session
broadcaster := audio.GetAudioEventBroadcaster()
broadcaster.BroadcastAudioDeviceChanged(true, "device_enabled")
logger.Info().Msg("broadcasted audio device change event after enabling audio device")
}
}
config.UsbDevices.Audio = enabled

View File

@ -8,7 +8,8 @@ export type AudioEventType =
| 'microphone-state-changed'
| 'microphone-metrics-update'
| 'audio-process-metrics'
| 'microphone-process-metrics';
| 'microphone-process-metrics'
| 'audio-device-changed';
// Audio event data interfaces
export interface AudioMuteData {
@ -48,10 +49,15 @@ export interface ProcessMetricsData {
process_name: string;
}
export interface AudioDeviceChangedData {
enabled: boolean;
reason: string;
}
// Audio event structure
export interface AudioEvent {
type: AudioEventType;
data: AudioMuteData | AudioMetricsData | MicrophoneStateData | MicrophoneMetricsData | ProcessMetricsData;
data: AudioMuteData | AudioMetricsData | MicrophoneStateData | MicrophoneMetricsData | ProcessMetricsData | AudioDeviceChangedData;
}
// Hook return type
@ -72,6 +78,9 @@ export interface UseAudioEventsReturn {
audioProcessMetrics: ProcessMetricsData | null;
microphoneProcessMetrics: ProcessMetricsData | null;
// Device change events
onAudioDeviceChanged?: (data: AudioDeviceChangedData) => void;
// Manual subscription control
subscribe: () => void;
unsubscribe: () => void;
@ -84,7 +93,7 @@ const globalSubscriptionState = {
connectionId: null as string | null
};
export function useAudioEvents(): UseAudioEventsReturn {
export function useAudioEvents(onAudioDeviceChanged?: (data: AudioDeviceChangedData) => void): UseAudioEventsReturn {
// State for audio data
const [audioMuted, setAudioMuted] = useState<boolean | null>(null);
const [audioMetrics, setAudioMetrics] = useState<AudioMetricsData | null>(null);
@ -244,6 +253,15 @@ export function useAudioEvents(): UseAudioEventsReturn {
break;
}
case 'audio-device-changed': {
const deviceChangedData = audioEvent.data as AudioDeviceChangedData;
console.log('[AudioEvents] Audio device changed:', deviceChangedData);
if (onAudioDeviceChanged) {
onAudioDeviceChanged(deviceChangedData);
}
break;
}
default:
// Ignore other message types (WebRTC signaling, etc.)
break;
@ -256,7 +274,7 @@ export function useAudioEvents(): UseAudioEventsReturn {
}
}
}
}, [lastMessage]);
}, [lastMessage, onAudioDeviceChanged]);
// Auto-subscribe when connected
useEffect(() => {
@ -309,6 +327,9 @@ export function useAudioEvents(): UseAudioEventsReturn {
audioProcessMetrics,
microphoneProcessMetrics,
// Device change events
onAudioDeviceChanged,
// Manual subscription control
subscribe,
unsubscribe,

View File

@ -34,6 +34,7 @@ import {
VideoState,
} from "@/hooks/stores";
import { useMicrophone } from "@/hooks/useMicrophone";
import { useAudioEvents } from "@/hooks/useAudioEvents";
import WebRTCVideo from "@components/WebRTCVideo";
import { checkAuth, isInCloud, isOnDevice } from "@/main";
import DashboardNavbar from "@components/Header";
@ -655,6 +656,9 @@ export default function KvmIdRoute() {
const rpcDataChannel = useRTCStore(state => state.rpcDataChannel);
const { send } = useJsonRpc(onJsonRpcRequest);
// Use audio events hook without device change handler to avoid subscription loops
useAudioEvents();
useEffect(() => {
if (rpcDataChannel?.readyState !== "open") return;
send("getVideoState", {}, (resp: JsonRpcResponse) => {