mirror of https://github.com/jetkvm/kvm.git
Fix: USB Gadgets updates
This commit is contained in:
parent
bc53523fbb
commit
2afe2ca539
|
@ -23,6 +23,7 @@ const (
|
||||||
AudioEventMicrophoneMetrics AudioEventType = "microphone-metrics-update"
|
AudioEventMicrophoneMetrics AudioEventType = "microphone-metrics-update"
|
||||||
AudioEventProcessMetrics AudioEventType = "audio-process-metrics"
|
AudioEventProcessMetrics AudioEventType = "audio-process-metrics"
|
||||||
AudioEventMicProcessMetrics AudioEventType = "microphone-process-metrics"
|
AudioEventMicProcessMetrics AudioEventType = "microphone-process-metrics"
|
||||||
|
AudioEventDeviceChanged AudioEventType = "audio-device-changed"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AudioEvent represents a WebSocket audio event
|
// AudioEvent represents a WebSocket audio event
|
||||||
|
@ -73,6 +74,12 @@ type ProcessMetricsData struct {
|
||||||
ProcessName string `json:"process_name"`
|
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
|
// AudioEventSubscriber represents a WebSocket connection subscribed to audio events
|
||||||
type AudioEventSubscriber struct {
|
type AudioEventSubscriber struct {
|
||||||
conn *websocket.Conn
|
conn *websocket.Conn
|
||||||
|
@ -164,6 +171,15 @@ func (aeb *AudioEventBroadcaster) BroadcastMicrophoneStateChanged(running, sessi
|
||||||
aeb.broadcast(event)
|
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
|
// sendInitialState sends current audio state to a new subscriber
|
||||||
func (aeb *AudioEventBroadcaster) sendInitialState(connectionID string) {
|
func (aeb *AudioEventBroadcaster) sendInitialState(connectionID string) {
|
||||||
aeb.mutex.RLock()
|
aeb.mutex.RLock()
|
||||||
|
|
11
jsonrpc.go
11
jsonrpc.go
|
@ -15,6 +15,7 @@ import (
|
||||||
"github.com/pion/webrtc/v4"
|
"github.com/pion/webrtc/v4"
|
||||||
"go.bug.st/serial"
|
"go.bug.st/serial"
|
||||||
|
|
||||||
|
"github.com/jetkvm/kvm/internal/audio"
|
||||||
"github.com/jetkvm/kvm/internal/usbgadget"
|
"github.com/jetkvm/kvm/internal/usbgadget"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -956,6 +957,11 @@ func rpcSetUsbDevices(usbDevices usbgadget.Devices) error {
|
||||||
if err := audioSupervisor.Start(); err != nil {
|
if err := audioSupervisor.Start(); err != nil {
|
||||||
logger.Error().Err(err).Msg("failed to start audio supervisor")
|
logger.Error().Err(err).Msg("failed to start audio supervisor")
|
||||||
// Don't return error here as USB reconfiguration was successful
|
// 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")
|
logger.Info().Msg("starting audio processes due to audio device being enabled")
|
||||||
if err := audioSupervisor.Start(); err != nil {
|
if err := audioSupervisor.Start(); err != nil {
|
||||||
logger.Error().Err(err).Msg("failed to start audio supervisor")
|
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
|
config.UsbDevices.Audio = enabled
|
||||||
|
|
|
@ -8,7 +8,8 @@ export type AudioEventType =
|
||||||
| 'microphone-state-changed'
|
| 'microphone-state-changed'
|
||||||
| 'microphone-metrics-update'
|
| 'microphone-metrics-update'
|
||||||
| 'audio-process-metrics'
|
| 'audio-process-metrics'
|
||||||
| 'microphone-process-metrics';
|
| 'microphone-process-metrics'
|
||||||
|
| 'audio-device-changed';
|
||||||
|
|
||||||
// Audio event data interfaces
|
// Audio event data interfaces
|
||||||
export interface AudioMuteData {
|
export interface AudioMuteData {
|
||||||
|
@ -48,10 +49,15 @@ export interface ProcessMetricsData {
|
||||||
process_name: string;
|
process_name: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface AudioDeviceChangedData {
|
||||||
|
enabled: boolean;
|
||||||
|
reason: string;
|
||||||
|
}
|
||||||
|
|
||||||
// Audio event structure
|
// Audio event structure
|
||||||
export interface AudioEvent {
|
export interface AudioEvent {
|
||||||
type: AudioEventType;
|
type: AudioEventType;
|
||||||
data: AudioMuteData | AudioMetricsData | MicrophoneStateData | MicrophoneMetricsData | ProcessMetricsData;
|
data: AudioMuteData | AudioMetricsData | MicrophoneStateData | MicrophoneMetricsData | ProcessMetricsData | AudioDeviceChangedData;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hook return type
|
// Hook return type
|
||||||
|
@ -72,6 +78,9 @@ export interface UseAudioEventsReturn {
|
||||||
audioProcessMetrics: ProcessMetricsData | null;
|
audioProcessMetrics: ProcessMetricsData | null;
|
||||||
microphoneProcessMetrics: ProcessMetricsData | null;
|
microphoneProcessMetrics: ProcessMetricsData | null;
|
||||||
|
|
||||||
|
// Device change events
|
||||||
|
onAudioDeviceChanged?: (data: AudioDeviceChangedData) => void;
|
||||||
|
|
||||||
// Manual subscription control
|
// Manual subscription control
|
||||||
subscribe: () => void;
|
subscribe: () => void;
|
||||||
unsubscribe: () => void;
|
unsubscribe: () => void;
|
||||||
|
@ -84,7 +93,7 @@ const globalSubscriptionState = {
|
||||||
connectionId: null as string | null
|
connectionId: null as string | null
|
||||||
};
|
};
|
||||||
|
|
||||||
export function useAudioEvents(): UseAudioEventsReturn {
|
export function useAudioEvents(onAudioDeviceChanged?: (data: AudioDeviceChangedData) => void): UseAudioEventsReturn {
|
||||||
// State for audio data
|
// State for audio data
|
||||||
const [audioMuted, setAudioMuted] = useState<boolean | null>(null);
|
const [audioMuted, setAudioMuted] = useState<boolean | null>(null);
|
||||||
const [audioMetrics, setAudioMetrics] = useState<AudioMetricsData | null>(null);
|
const [audioMetrics, setAudioMetrics] = useState<AudioMetricsData | null>(null);
|
||||||
|
@ -244,6 +253,15 @@ export function useAudioEvents(): UseAudioEventsReturn {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case 'audio-device-changed': {
|
||||||
|
const deviceChangedData = audioEvent.data as AudioDeviceChangedData;
|
||||||
|
console.log('[AudioEvents] Audio device changed:', deviceChangedData);
|
||||||
|
if (onAudioDeviceChanged) {
|
||||||
|
onAudioDeviceChanged(deviceChangedData);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// Ignore other message types (WebRTC signaling, etc.)
|
// Ignore other message types (WebRTC signaling, etc.)
|
||||||
break;
|
break;
|
||||||
|
@ -256,7 +274,7 @@ export function useAudioEvents(): UseAudioEventsReturn {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [lastMessage]);
|
}, [lastMessage, onAudioDeviceChanged]);
|
||||||
|
|
||||||
// Auto-subscribe when connected
|
// Auto-subscribe when connected
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -309,6 +327,9 @@ export function useAudioEvents(): UseAudioEventsReturn {
|
||||||
audioProcessMetrics,
|
audioProcessMetrics,
|
||||||
microphoneProcessMetrics,
|
microphoneProcessMetrics,
|
||||||
|
|
||||||
|
// Device change events
|
||||||
|
onAudioDeviceChanged,
|
||||||
|
|
||||||
// Manual subscription control
|
// Manual subscription control
|
||||||
subscribe,
|
subscribe,
|
||||||
unsubscribe,
|
unsubscribe,
|
||||||
|
|
|
@ -34,6 +34,7 @@ import {
|
||||||
VideoState,
|
VideoState,
|
||||||
} from "@/hooks/stores";
|
} from "@/hooks/stores";
|
||||||
import { useMicrophone } from "@/hooks/useMicrophone";
|
import { useMicrophone } from "@/hooks/useMicrophone";
|
||||||
|
import { useAudioEvents } from "@/hooks/useAudioEvents";
|
||||||
import WebRTCVideo from "@components/WebRTCVideo";
|
import WebRTCVideo from "@components/WebRTCVideo";
|
||||||
import { checkAuth, isInCloud, isOnDevice } from "@/main";
|
import { checkAuth, isInCloud, isOnDevice } from "@/main";
|
||||||
import DashboardNavbar from "@components/Header";
|
import DashboardNavbar from "@components/Header";
|
||||||
|
@ -655,6 +656,9 @@ export default function KvmIdRoute() {
|
||||||
const rpcDataChannel = useRTCStore(state => state.rpcDataChannel);
|
const rpcDataChannel = useRTCStore(state => state.rpcDataChannel);
|
||||||
const { send } = useJsonRpc(onJsonRpcRequest);
|
const { send } = useJsonRpc(onJsonRpcRequest);
|
||||||
|
|
||||||
|
// Use audio events hook without device change handler to avoid subscription loops
|
||||||
|
useAudioEvents();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (rpcDataChannel?.readyState !== "open") return;
|
if (rpcDataChannel?.readyState !== "open") return;
|
||||||
send("getVideoState", {}, (resp: JsonRpcResponse) => {
|
send("getVideoState", {}, (resp: JsonRpcResponse) => {
|
||||||
|
|
Loading…
Reference in New Issue