Compare commits

..

1 Commits

Author SHA1 Message Date
Alex f91620170d
Merge 6078cdab66 into 6e1b84f39b 2025-11-07 12:23:58 +00:00
5 changed files with 23 additions and 30 deletions

View File

@ -205,12 +205,14 @@ lint-go-fix: build_audio_deps
# Run UI linting locally (mirrors GitHub workflow ui-lint.yml) # Run UI linting locally (mirrors GitHub workflow ui-lint.yml)
lint-ui: lint-ui:
@echo "Running UI lint..." @echo "Running UI lint..."
@cd ui && npm ci && npm run lint @cd ui && npm ci
@cd ui && npm run lint
# Run UI linting with auto-fix # Run UI linting with auto-fix
lint-ui-fix: lint-ui-fix:
@echo "Running UI lint with auto-fix..." @echo "Running UI lint with auto-fix..."
@cd ui && npm ci && npm run lint:fix @cd ui && npm ci
@cd ui && npm run lint:fix
# Legacy alias for UI linting (for backward compatibility) # Legacy alias for UI linting (for backward compatibility)
ui-lint: lint-ui ui-lint: lint-ui

View File

@ -14,7 +14,7 @@ import (
var ( var (
audioMutex sync.Mutex audioMutex sync.Mutex
outputSource audio.AudioSource outputSource audio.AudioSource
inputSource atomic.Pointer[audio.AudioSource] inputSource audio.AudioSource
outputRelay *audio.OutputRelay outputRelay *audio.OutputRelay
inputRelay *audio.InputRelay inputRelay *audio.InputRelay
audioInitialized bool audioInitialized bool
@ -63,15 +63,13 @@ func startAudio() error {
// Start input audio if not running, USB audio enabled, and input enabled // Start input audio if not running, USB audio enabled, and input enabled
ensureConfigLoaded() ensureConfigLoaded()
if inputSource.Load() == nil && audioInputEnabled.Load() && config.UsbDevices != nil && config.UsbDevices.Audio { if inputSource == nil && audioInputEnabled.Load() && config.UsbDevices != nil && config.UsbDevices.Audio {
alsaPlaybackDevice := "hw:1,0" // USB speakers alsaPlaybackDevice := "hw:1,0" // USB speakers
// Create CGO audio source // Create CGO audio source
newInputSource := audio.NewCgoInputSource(alsaPlaybackDevice) inputSource = audio.NewCgoInputSource(alsaPlaybackDevice)
var audioSrc audio.AudioSource = newInputSource
inputSource.Store(&audioSrc)
inputRelay = audio.NewInputRelay(newInputSource) inputRelay = audio.NewInputRelay(inputSource)
if err := inputRelay.Start(); err != nil { if err := inputRelay.Start(); err != nil {
audioLogger.Error().Err(err).Msg("Failed to start input relay") audioLogger.Error().Err(err).Msg("Failed to start input relay")
} }
@ -98,9 +96,9 @@ func stopInputLocked() {
inputRelay.Stop() inputRelay.Stop()
inputRelay = nil inputRelay = nil
} }
if source := inputSource.Load(); source != nil { if inputSource != nil {
(*source).Disconnect() inputSource.Disconnect()
inputSource.Store(nil) inputSource = nil
} }
} }
@ -171,8 +169,8 @@ func SetAudioOutputEnabled(enabled bool) error {
} }
} else { } else {
audioMutex.Lock() audioMutex.Lock()
defer audioMutex.Unlock()
stopOutputLocked() stopOutputLocked()
audioMutex.Unlock()
} }
return nil return nil
@ -190,8 +188,8 @@ func SetAudioInputEnabled(enabled bool) error {
} }
} else { } else {
audioMutex.Lock() audioMutex.Lock()
defer audioMutex.Unlock()
stopInputLocked() stopInputLocked()
audioMutex.Unlock()
} }
return nil return nil
@ -240,22 +238,23 @@ func handleInputTrackForSession(track *webrtc.TrackRemote) {
continue // Drop frame but keep reading continue // Drop frame but keep reading
} }
// Get source atomically (hot path optimization) // Get source in single mutex operation (hot path optimization)
source := inputSource.Load() audioMutex.Lock()
source := inputSource
audioMutex.Unlock()
if source == nil { if source == nil {
continue // No relay, drop frame but keep reading continue // No relay, drop frame but keep reading
} }
if !(*source).IsConnected() { if !source.IsConnected() {
if err := (*source).Connect(); err != nil { if err := source.Connect(); err != nil {
continue continue
} }
} }
if err := (*source).WriteMessage(0, opusData); err != nil { if err := source.WriteMessage(0, opusData); err != nil {
(*source).Disconnect() source.Disconnect()
audioLogger.Warn().Err(err).Str("track_id", myTrackID).Msg("failed to write audio message")
} }
} }
} }

View File

@ -133,7 +133,7 @@ func Main() {
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
<-sigs <-sigs
logger.Log().Msg("JetKVM Shutting Down") logger.Info().Msg("JetKVM Shutting Down")
stopAudio() stopAudio()

View File

@ -620,7 +620,7 @@ export default function KvmIdRoute() {
echoCancellation: true, echoCancellation: true,
noiseSuppression: true, noiseSuppression: true,
autoGainControl: true, autoGainControl: true,
channelCount: 1, channelCount: 2,
} }
}).then((stream) => { }).then((stream) => {
microphoneRequestInProgress.current = false; microphoneRequestInProgress.current = false;
@ -652,13 +652,6 @@ export default function KvmIdRoute() {
audioTransceiver.sender.replaceTrack(null); audioTransceiver.sender.replaceTrack(null);
} }
} }
// Cleanup on unmount or when dependencies change
return () => {
if (audioTransceiver?.sender.track) {
audioTransceiver.sender.track.stop();
}
};
}, [microphoneEnabled, audioTransceiver, peerConnection, setMicrophoneEnabled]); }, [microphoneEnabled, audioTransceiver, peerConnection, setMicrophoneEnabled]);
// Cleanup effect // Cleanup effect

View File

@ -332,7 +332,6 @@ func newSession(config SessionConfig) (*Session, error) {
) )
if err != nil { if err != nil {
scopedLogger.Warn().Err(err).Msg("Failed to create AudioTrack (non-fatal)") scopedLogger.Warn().Err(err).Msg("Failed to create AudioTrack (non-fatal)")
session.AudioTrack = nil
} else { } else {
_, err = peerConnection.AddTransceiverFromTrack(session.AudioTrack, webrtc.RTPTransceiverInit{ _, err = peerConnection.AddTransceiverFromTrack(session.AudioTrack, webrtc.RTPTransceiverInit{
Direction: webrtc.RTPTransceiverDirectionSendrecv, Direction: webrtc.RTPTransceiverDirectionSendrecv,