diff --git a/internal/audio/input_ipc_manager.go b/internal/audio/input_ipc_manager.go index 27a333c..2986d4b 100644 --- a/internal/audio/input_ipc_manager.go +++ b/internal/audio/input_ipc_manager.go @@ -1,7 +1,6 @@ package audio import ( - "context" "sync/atomic" "time" @@ -16,18 +15,13 @@ type AudioInputIPCManager struct { supervisor *AudioInputSupervisor logger zerolog.Logger running int32 - ctx context.Context - cancel context.CancelFunc } // NewAudioInputIPCManager creates a new IPC-based audio input manager func NewAudioInputIPCManager() *AudioInputIPCManager { - ctx, cancel := context.WithCancel(context.Background()) return &AudioInputIPCManager{ supervisor: NewAudioInputSupervisor(), logger: logging.GetDefaultLogger().With().Str("component", "audio-input-ipc").Logger(), - ctx: ctx, - cancel: cancel, } } @@ -52,14 +46,8 @@ func (aim *AudioInputIPCManager) Start() error { FrameSize: 960, } - // Wait with timeout for subprocess readiness - select { - case <-time.After(200 * time.Millisecond): - case <-aim.ctx.Done(): - aim.supervisor.Stop() - atomic.StoreInt32(&aim.running, 0) - return aim.ctx.Err() - } + // Wait for subprocess readiness + time.Sleep(200 * time.Millisecond) err = aim.supervisor.SendConfig(config) if err != nil { @@ -77,7 +65,6 @@ func (aim *AudioInputIPCManager) Stop() { } aim.logger.Info().Msg("Stopping IPC-based audio input system") - aim.cancel() aim.supervisor.Stop() aim.logger.Info().Msg("IPC-based audio input system stopped") } diff --git a/ui/src/routes/devices.$id.tsx b/ui/src/routes/devices.$id.tsx index 31ed8f0..4401ceb 100644 --- a/ui/src/routes/devices.$id.tsx +++ b/ui/src/routes/devices.$id.tsx @@ -146,6 +146,8 @@ export default function KvmIdRoute() { // Microphone hook - moved here to prevent unmounting when popover closes const microphoneHook = useMicrophone(); + // Extract syncMicrophoneState to avoid dependency issues + const { syncMicrophoneState } = microphoneHook; const isLegacySignalingEnabled = useRef(false); @@ -656,8 +658,20 @@ 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(); + // Handle audio device changes to sync microphone state + const handleAudioDeviceChanged = useCallback((data: { enabled: boolean; reason: string }) => { + console.log('[AudioDeviceChanged] Audio device changed:', data); + // Sync microphone state when audio device configuration changes + // This ensures the microphone state is properly synchronized after USB audio reconfiguration + if (syncMicrophoneState) { + setTimeout(() => { + syncMicrophoneState(); + }, 500); // Small delay to ensure backend state is settled + } + }, [syncMicrophoneState]); + + // Use audio events hook with device change handler + useAudioEvents(handleAudioDeviceChanged); useEffect(() => { if (rpcDataChannel?.readyState !== "open") return;