mirror of https://github.com/jetkvm/kvm.git
Compare commits
1 Commits
afce9afb4d
...
f91620170d
| Author | SHA1 | Date |
|---|---|---|
|
|
f91620170d |
6
Makefile
6
Makefile
|
|
@ -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
|
||||||
|
|
|
||||||
35
audio.go
35
audio.go
|
|
@ -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")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
2
main.go
2
main.go
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue