feat(audio): add real-time USB audio config updates and validation

- Add audio device change listener in UI to update USB config
- Implement audio configuration validation before enabling USB audio
- Broadcast audio device change events for all state changes
This commit is contained in:
Alex P 2025-08-25 14:21:49 +00:00
parent c89d678963
commit d1c192bf8b
2 changed files with 60 additions and 5 deletions

View File

@ -908,7 +908,38 @@ func updateUsbRelatedConfig() error {
return nil return nil
} }
// validateAudioConfiguration checks if audio functionality can be enabled
func validateAudioConfiguration(enabled bool) error {
if !enabled {
return nil // Disabling audio is always allowed
}
// Check if audio supervisor is available
if audioSupervisor == nil {
return fmt.Errorf("audio supervisor not initialized - audio functionality not available")
}
// Check if ALSA devices are available by attempting to list them
// This is a basic check to ensure the system has audio capabilities
if _, err := os.Stat("/proc/asound/cards"); os.IsNotExist(err) {
return fmt.Errorf("no ALSA sound cards detected - audio hardware not available")
}
// Check if USB gadget audio function is supported
if _, err := os.Stat("/sys/kernel/config/usb_gadget"); os.IsNotExist(err) {
return fmt.Errorf("USB gadget configfs not available - cannot enable USB audio")
}
return nil
}
func rpcSetUsbDevices(usbDevices usbgadget.Devices) error { func rpcSetUsbDevices(usbDevices usbgadget.Devices) error {
// Validate audio configuration before proceeding
if err := validateAudioConfiguration(usbDevices.Audio); err != nil {
logger.Warn().Err(err).Msg("audio configuration validation failed")
return fmt.Errorf("audio validation failed: %w", err)
}
// Check if audio state is changing // Check if audio state is changing
previousAudioEnabled := config.UsbDevices != nil && config.UsbDevices.Audio previousAudioEnabled := config.UsbDevices != nil && config.UsbDevices.Audio
newAudioEnabled := usbDevices.Audio newAudioEnabled := usbDevices.Audio
@ -984,6 +1015,11 @@ func rpcSetUsbDevices(usbDevices usbgadget.Devices) error {
broadcaster.BroadcastAudioDeviceChanged(true, "usb_reconfiguration") broadcaster.BroadcastAudioDeviceChanged(true, "usb_reconfiguration")
logger.Info().Msg("broadcasted audio device change event after USB reconfiguration") logger.Info().Msg("broadcasted audio device change event after USB reconfiguration")
} }
} else if previousAudioEnabled != newAudioEnabled {
// Broadcast audio device change event for disabling audio
broadcaster := audio.GetAudioEventBroadcaster()
broadcaster.BroadcastAudioDeviceChanged(newAudioEnabled, "usb_reconfiguration")
logger.Info().Bool("enabled", newAudioEnabled).Msg("broadcasted audio device change event after USB reconfiguration")
} }
return nil return nil
@ -1000,6 +1036,11 @@ func rpcSetUsbDeviceState(device string, enabled bool) error {
case "massStorage": case "massStorage":
config.UsbDevices.MassStorage = enabled config.UsbDevices.MassStorage = enabled
case "audio": case "audio":
// Validate audio configuration before proceeding
if err := validateAudioConfiguration(enabled); err != nil {
logger.Warn().Err(err).Msg("audio device state validation failed")
return fmt.Errorf("audio validation failed: %w", err)
}
// Handle audio process management // Handle audio process management
if !enabled { if !enabled {
// Stop audio processes when audio is disabled // Stop audio processes when audio is disabled
@ -1047,11 +1088,15 @@ func rpcSetUsbDeviceState(device string, enabled bool) 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")
} else { } else {
// Broadcast audio device change event to notify WebRTC session // Broadcast audio device change event to notify WebRTC session
broadcaster := audio.GetAudioEventBroadcaster() broadcaster := audio.GetAudioEventBroadcaster()
broadcaster.BroadcastAudioDeviceChanged(true, "device_enabled") broadcaster.BroadcastAudioDeviceChanged(true, "device_enabled")
logger.Info().Msg("broadcasted audio device change event after enabling audio device") logger.Info().Msg("broadcasted audio device change event after enabling audio device")
} }
// Always broadcast the audio device change event regardless of enable/disable
broadcaster := audio.GetAudioEventBroadcaster()
broadcaster.BroadcastAudioDeviceChanged(enabled, "device_state_changed")
logger.Info().Bool("enabled", enabled).Msg("broadcasted audio device state change event")
} }
config.UsbDevices.Audio = enabled config.UsbDevices.Audio = enabled
default: default:

View File

@ -1,6 +1,7 @@
import { useCallback, useEffect, useState } from "react"; import { useCallback, useEffect, useState } from "react";
import { JsonRpcResponse, useJsonRpc } from "./useJsonRpc"; import { JsonRpcResponse, useJsonRpc } from "./useJsonRpc";
import { useAudioEvents } from "./useAudioEvents";
export interface UsbDeviceConfig { export interface UsbDeviceConfig {
keyboard: boolean; keyboard: boolean;
@ -35,6 +36,15 @@ export function useUsbDeviceConfig() {
}); });
}, [send]); }, [send]);
// Listen for audio device changes to update USB config in real-time
const handleAudioDeviceChanged = useCallback(() => {
console.log('[useUsbDeviceConfig] Audio device changed, refetching USB config');
fetchUsbDeviceConfig();
}, [fetchUsbDeviceConfig]);
// Subscribe to audio events for real-time updates
useAudioEvents(handleAudioDeviceChanged);
useEffect(() => { useEffect(() => {
fetchUsbDeviceConfig(); fetchUsbDeviceConfig();
}, [fetchUsbDeviceConfig]); }, [fetchUsbDeviceConfig]);