mirror of https://github.com/jetkvm/kvm.git
Compare commits
2 Commits
89946c2db8
...
91d25f2c7f
| Author | SHA1 | Date |
|---|---|---|
|
|
91d25f2c7f | |
|
|
14b741c3dd |
85
audio.go
85
audio.go
|
|
@ -12,35 +12,27 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
audioMutex sync.Mutex
|
||||
outputSource audio.AudioSource
|
||||
inputSource audio.AudioSource
|
||||
outputRelay *audio.OutputRelay
|
||||
inputRelay *audio.InputRelay
|
||||
audioInitialized bool
|
||||
activeConnections atomic.Int32
|
||||
audioLogger zerolog.Logger
|
||||
currentAudioTrack *webrtc.TrackLocalStaticSample
|
||||
inputTrackHandling atomic.Bool
|
||||
useUSBForAudioOutput atomic.Bool
|
||||
audioOutputEnabled atomic.Bool
|
||||
audioInputEnabled atomic.Bool
|
||||
audioMutex sync.Mutex
|
||||
outputSource audio.AudioSource
|
||||
inputSource audio.AudioSource
|
||||
outputRelay *audio.OutputRelay
|
||||
inputRelay *audio.InputRelay
|
||||
audioInitialized bool
|
||||
activeConnections atomic.Int32
|
||||
audioLogger zerolog.Logger
|
||||
currentAudioTrack *webrtc.TrackLocalStaticSample
|
||||
inputTrackHandling atomic.Bool
|
||||
audioOutputEnabled atomic.Bool
|
||||
audioInputEnabled atomic.Bool
|
||||
)
|
||||
|
||||
func initAudio() {
|
||||
audioLogger = logging.GetDefaultLogger().With().Str("component", "audio-manager").Logger()
|
||||
|
||||
// Load audio output source from config
|
||||
ensureConfigLoaded()
|
||||
useUSBForAudioOutput.Store(config.AudioOutputSource == "usb")
|
||||
|
||||
// Enable both by default
|
||||
audioOutputEnabled.Store(true)
|
||||
audioInputEnabled.Store(true)
|
||||
|
||||
audioLogger.Debug().
|
||||
Str("source", config.AudioOutputSource).
|
||||
Msg("Audio subsystem initialized")
|
||||
audioLogger.Debug().Msg("Audio subsystem initialized")
|
||||
audioInitialized = true
|
||||
}
|
||||
|
||||
|
|
@ -56,12 +48,8 @@ func startAudio() error {
|
|||
|
||||
// Start output audio if not running and enabled
|
||||
if outputSource == nil && audioOutputEnabled.Load() {
|
||||
alsaDevice := "hw:0,0" // HDMI
|
||||
if useUSBForAudioOutput.Load() {
|
||||
alsaDevice = "hw:1,0" // USB
|
||||
}
|
||||
alsaDevice := "hw:1,0" // USB audio
|
||||
|
||||
// Create CGO audio source
|
||||
outputSource = audio.NewCgoOutputSource(alsaDevice)
|
||||
|
||||
if currentAudioTrack != nil {
|
||||
|
|
@ -162,51 +150,6 @@ func setAudioTrack(audioTrack *webrtc.TrackLocalStaticSample) {
|
|||
}
|
||||
}
|
||||
|
||||
// SetAudioOutputSource switches between HDMI and USB audio output
|
||||
func SetAudioOutputSource(useUSB bool) error {
|
||||
audioMutex.Lock()
|
||||
defer audioMutex.Unlock()
|
||||
|
||||
if useUSBForAudioOutput.Load() == useUSB {
|
||||
return nil
|
||||
}
|
||||
|
||||
audioLogger.Info().
|
||||
Bool("old_usb", useUSBForAudioOutput.Load()).
|
||||
Bool("new_usb", useUSB).
|
||||
Msg("Switching audio output source")
|
||||
|
||||
oldValue := useUSBForAudioOutput.Load()
|
||||
useUSBForAudioOutput.Store(useUSB)
|
||||
|
||||
ensureConfigLoaded()
|
||||
if useUSB {
|
||||
config.AudioOutputSource = "usb"
|
||||
} else {
|
||||
config.AudioOutputSource = "hdmi"
|
||||
}
|
||||
if err := SaveConfig(); err != nil {
|
||||
audioLogger.Error().Err(err).Msg("Failed to save config")
|
||||
useUSBForAudioOutput.Store(oldValue)
|
||||
return err
|
||||
}
|
||||
|
||||
stopOutputLocked()
|
||||
|
||||
// Restart if there are active connections
|
||||
if activeConnections.Load() > 0 {
|
||||
audioMutex.Unlock()
|
||||
err := startAudio()
|
||||
audioMutex.Lock()
|
||||
if err != nil {
|
||||
audioLogger.Error().Err(err).Msg("Failed to restart audio output")
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func setPendingInputTrack(track *webrtc.TrackRemote) {
|
||||
audioMutex.Lock()
|
||||
defer audioMutex.Unlock()
|
||||
|
|
|
|||
|
|
@ -107,7 +107,6 @@ type Config struct {
|
|||
DefaultLogLevel string `json:"default_log_level"`
|
||||
VideoSleepAfterSec int `json:"video_sleep_after_sec"`
|
||||
VideoQualityFactor float64 `json:"video_quality_factor"`
|
||||
AudioOutputSource string `json:"audio_output_source"` // "hdmi" or "usb"
|
||||
}
|
||||
|
||||
func (c *Config) GetDisplayRotation() uint16 {
|
||||
|
|
@ -180,7 +179,6 @@ func getDefaultConfig() Config {
|
|||
return c
|
||||
}(),
|
||||
DefaultLogLevel: "INFO",
|
||||
AudioOutputSource: "usb",
|
||||
VideoQualityFactor: 1.0,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
49
jsonrpc.go
49
jsonrpc.go
|
|
@ -894,33 +894,9 @@ func rpcGetUsbDevices() (usbgadget.Devices, error) {
|
|||
func updateUsbRelatedConfig(wasAudioEnabled bool) error {
|
||||
ensureConfigLoaded()
|
||||
|
||||
audioSourceChanged := false
|
||||
|
||||
// If USB audio is being disabled and audio output source is USB, switch to HDMI
|
||||
if config.UsbDevices != nil && !config.UsbDevices.Audio && config.AudioOutputSource == "usb" {
|
||||
audioMutex.Lock()
|
||||
config.AudioOutputSource = "hdmi"
|
||||
useUSBForAudioOutput.Store(false)
|
||||
audioSourceChanged = true
|
||||
audioMutex.Unlock()
|
||||
}
|
||||
|
||||
// If USB audio is being enabled (was disabled, now enabled), switch to USB
|
||||
if config.UsbDevices != nil && config.UsbDevices.Audio && !wasAudioEnabled {
|
||||
audioMutex.Lock()
|
||||
config.AudioOutputSource = "usb"
|
||||
useUSBForAudioOutput.Store(true)
|
||||
audioSourceChanged = true
|
||||
audioMutex.Unlock()
|
||||
}
|
||||
|
||||
// Stop audio before USB reconfiguration
|
||||
// Input always uses USB, output depends on audioSourceChanged
|
||||
// Stop input audio before USB reconfiguration (input uses USB)
|
||||
audioMutex.Lock()
|
||||
stopInputLocked()
|
||||
if audioSourceChanged {
|
||||
stopOutputLocked()
|
||||
}
|
||||
audioMutex.Unlock()
|
||||
|
||||
if err := gadget.UpdateGadgetConfig(); err != nil {
|
||||
|
|
@ -931,9 +907,8 @@ func updateUsbRelatedConfig(wasAudioEnabled bool) error {
|
|||
return fmt.Errorf("failed to save config: %w", err)
|
||||
}
|
||||
|
||||
// Restart audio if source changed or USB audio is enabled with active connections
|
||||
// The relay handles device readiness via retry logic
|
||||
if activeConnections.Load() > 0 && (audioSourceChanged || (config.UsbDevices != nil && config.UsbDevices.Audio)) {
|
||||
// Restart audio if USB audio is enabled with active connections
|
||||
if activeConnections.Load() > 0 && config.UsbDevices != nil && config.UsbDevices.Audio {
|
||||
if err := startAudio(); err != nil {
|
||||
logger.Warn().Err(err).Msg("Failed to restart audio after USB reconfiguration")
|
||||
}
|
||||
|
|
@ -970,22 +945,6 @@ func rpcSetUsbDeviceState(device string, enabled bool) error {
|
|||
return updateUsbRelatedConfig(wasAudioEnabled)
|
||||
}
|
||||
|
||||
func rpcGetAudioOutputSource() (string, error) {
|
||||
if useUSBForAudioOutput.Load() {
|
||||
return "usb", nil
|
||||
}
|
||||
return "hdmi", nil
|
||||
}
|
||||
|
||||
func rpcSetAudioOutputSource(source string) error {
|
||||
if source != "hdmi" && source != "usb" {
|
||||
return fmt.Errorf("invalid audio output source: %s (must be 'hdmi' or 'usb')", source)
|
||||
}
|
||||
|
||||
useUSB := source == "usb"
|
||||
return SetAudioOutputSource(useUSB)
|
||||
}
|
||||
|
||||
func rpcGetAudioOutputEnabled() (bool, error) {
|
||||
return audioOutputEnabled.Load(), nil
|
||||
}
|
||||
|
|
@ -1320,8 +1279,6 @@ var rpcHandlers = map[string]RPCHandler{
|
|||
"getUsbDevices": {Func: rpcGetUsbDevices},
|
||||
"setUsbDevices": {Func: rpcSetUsbDevices, Params: []string{"devices"}},
|
||||
"setUsbDeviceState": {Func: rpcSetUsbDeviceState, Params: []string{"device", "enabled"}},
|
||||
"getAudioOutputSource": {Func: rpcGetAudioOutputSource},
|
||||
"setAudioOutputSource": {Func: rpcSetAudioOutputSource, Params: []string{"source"}},
|
||||
"getAudioOutputEnabled": {Func: rpcGetAudioOutputEnabled},
|
||||
"setAudioOutputEnabled": {Func: rpcSetAudioOutputEnabled, Params: []string{"enabled"}},
|
||||
"getAudioInputEnabled": {Func: rpcGetAudioInputEnabled},
|
||||
|
|
|
|||
|
|
@ -370,8 +370,6 @@ export interface SettingsState {
|
|||
setVideoContrast: (value: number) => void;
|
||||
|
||||
// Audio settings
|
||||
audioOutputSource: string;
|
||||
setAudioOutputSource: (source: string) => void;
|
||||
audioOutputEnabled: boolean;
|
||||
setAudioOutputEnabled: (enabled: boolean) => void;
|
||||
audioInputEnabled: boolean;
|
||||
|
|
@ -425,8 +423,6 @@ export const useSettingsStore = create(
|
|||
setVideoContrast: (value: number) => set({ videoContrast: value }),
|
||||
|
||||
// Audio settings with defaults
|
||||
audioOutputSource: "usb",
|
||||
setAudioOutputSource: (source: string) => set({ audioOutputSource: source }),
|
||||
audioOutputEnabled: true,
|
||||
setAudioOutputEnabled: (enabled: boolean) => set({ audioOutputEnabled: enabled }),
|
||||
audioInputEnabled: true,
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { SettingsItem } from "@components/SettingsItem";
|
|||
import { SettingsPageHeader } from "@components/SettingsPageheader";
|
||||
import { useSettingsStore } from "@/hooks/stores";
|
||||
import { JsonRpcResponse, useJsonRpc } from "@/hooks/useJsonRpc";
|
||||
import { SelectMenuBasic } from "@components/SelectMenuBasic";
|
||||
// import { SelectMenuBasic } from "@components/SelectMenuBasic";
|
||||
import Checkbox from "@components/Checkbox";
|
||||
import { m } from "@localizations/messages.js";
|
||||
|
||||
|
|
@ -14,15 +14,7 @@ export default function SettingsAudioRoute() {
|
|||
const { send } = useJsonRpc();
|
||||
const settings = useSettingsStore();
|
||||
|
||||
// Fetch current audio settings on mount
|
||||
useEffect(() => {
|
||||
send("getAudioOutputSource", {}, (resp: JsonRpcResponse) => {
|
||||
if ("error" in resp) {
|
||||
return;
|
||||
}
|
||||
settings.setAudioOutputSource(resp.result as string);
|
||||
});
|
||||
|
||||
send("getAudioOutputEnabled", {}, (resp: JsonRpcResponse) => {
|
||||
if ("error" in resp) {
|
||||
return;
|
||||
|
|
@ -39,41 +31,6 @@ export default function SettingsAudioRoute() {
|
|||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [send]);
|
||||
|
||||
const handleAudioOutputSourceChange = (source: string) => {
|
||||
// Update UI immediately for better responsiveness
|
||||
settings.setAudioOutputSource(source);
|
||||
|
||||
send("setAudioOutputSource", { source }, (resp: JsonRpcResponse) => {
|
||||
if ("error" in resp) {
|
||||
// Revert on error by fetching current value from backend
|
||||
send("getAudioOutputSource", {}, (getResp: JsonRpcResponse) => {
|
||||
if ("result" in getResp) {
|
||||
settings.setAudioOutputSource(getResp.result as string);
|
||||
}
|
||||
});
|
||||
notifications.error(
|
||||
m.audio_settings_output_source_failed({ error: String(resp.error.data || m.unknown_error()) }),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Verify the change was applied by fetching the actual value
|
||||
send("getAudioOutputSource", {}, (getResp: JsonRpcResponse) => {
|
||||
if ("result" in getResp) {
|
||||
const actualSource = getResp.result as string;
|
||||
settings.setAudioOutputSource(actualSource);
|
||||
if (actualSource === source) {
|
||||
notifications.success(m.audio_settings_output_source_success());
|
||||
} else {
|
||||
notifications.error(
|
||||
m.audio_settings_output_source_failed({ error: `Expected ${source}, got ${actualSource}` }),
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const handleAudioOutputEnabledChange = (enabled: boolean) => {
|
||||
send("setAudioOutputEnabled", { enabled }, (resp: JsonRpcResponse) => {
|
||||
if ("error" in resp) {
|
||||
|
|
@ -121,26 +78,6 @@ export default function SettingsAudioRoute() {
|
|||
/>
|
||||
</SettingsItem>
|
||||
|
||||
{settings.audioOutputEnabled && (
|
||||
<SettingsItem
|
||||
title={m.audio_settings_output_source_title()}
|
||||
description={m.audio_settings_output_source_description()}
|
||||
>
|
||||
<SelectMenuBasic
|
||||
size="SM"
|
||||
label=""
|
||||
value={settings.audioOutputSource || "usb"}
|
||||
options={[
|
||||
{ value: "hdmi", label: m.audio_settings_hdmi_label() },
|
||||
{ value: "usb", label: m.audio_settings_usb_label() },
|
||||
]}
|
||||
onChange={e => {
|
||||
handleAudioOutputSourceChange(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</SettingsItem>
|
||||
)}
|
||||
|
||||
<SettingsItem
|
||||
title={m.audio_settings_input_title()}
|
||||
description={m.audio_settings_input_description()}
|
||||
|
|
|
|||
Loading…
Reference in New Issue