mirror of https://github.com/jetkvm/kvm.git
Fix: make sure audio output enable / disable doesn't need a refresh in order for audio to become audible again
This commit is contained in:
parent
439f57c3c8
commit
a84f63c0c4
11
cloud.go
11
cloud.go
|
@ -20,6 +20,7 @@ import (
|
|||
|
||||
"github.com/coder/websocket"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/jetkvm/kvm/internal/audio"
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
|
@ -480,6 +481,16 @@ func handleSessionRequest(
|
|||
cancelKeyboardMacro()
|
||||
|
||||
currentSession = session
|
||||
|
||||
// Set up audio relay callback to get current session's audio track
|
||||
// This is needed for audio output to work after enable/disable cycles
|
||||
audio.SetCurrentSessionCallback(func() audio.AudioTrackWriter {
|
||||
if currentSession != nil {
|
||||
return currentSession.AudioTrack
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
_ = wsjson.Write(context.Background(), c, gin.H{"type": "answer", "data": sd})
|
||||
return nil
|
||||
}
|
||||
|
|
36
main.go
36
main.go
|
@ -21,16 +21,6 @@ var (
|
|||
audioSupervisor *audio.AudioOutputSupervisor
|
||||
)
|
||||
|
||||
// runAudioServer is now handled by audio.RunAudioOutputServer
|
||||
// This function is kept for backward compatibility but delegates to the audio package
|
||||
func runAudioServer() {
|
||||
err := audio.RunAudioOutputServer()
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Msg("audio output server failed")
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func startAudioSubprocess() error {
|
||||
// Initialize validation cache for optimal performance
|
||||
audio.InitValidationCache()
|
||||
|
@ -47,14 +37,14 @@ func startAudioSubprocess() error {
|
|||
audio.SetAudioInputSupervisor(audioInputSupervisor)
|
||||
|
||||
// Set default OPUS configuration for audio input supervisor (low quality for single-core RV1106)
|
||||
config := audio.Config
|
||||
audioConfig := audio.Config
|
||||
audioInputSupervisor.SetOpusConfig(
|
||||
config.AudioQualityLowInputBitrate*1000, // Convert kbps to bps
|
||||
config.AudioQualityLowOpusComplexity,
|
||||
config.AudioQualityLowOpusVBR,
|
||||
config.AudioQualityLowOpusSignalType,
|
||||
config.AudioQualityLowOpusBandwidth,
|
||||
config.AudioQualityLowOpusDTX,
|
||||
audioConfig.AudioQualityLowInputBitrate*1000, // Convert kbps to bps
|
||||
audioConfig.AudioQualityLowOpusComplexity,
|
||||
audioConfig.AudioQualityLowOpusVBR,
|
||||
audioConfig.AudioQualityLowOpusSignalType,
|
||||
audioConfig.AudioQualityLowOpusBandwidth,
|
||||
audioConfig.AudioQualityLowOpusDTX,
|
||||
)
|
||||
|
||||
// Note: Audio input supervisor is NOT started here - it will be started on-demand
|
||||
|
@ -110,6 +100,12 @@ func startAudioSubprocess() error {
|
|||
},
|
||||
)
|
||||
|
||||
// Check if USB audio device is enabled before starting audio processes
|
||||
if config.UsbDevices == nil || !config.UsbDevices.Audio {
|
||||
logger.Info().Msg("USB audio device disabled - skipping audio supervisor startup")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Start the supervisor
|
||||
if err := audioSupervisor.Start(); err != nil {
|
||||
return fmt.Errorf("failed to start audio supervisor: %w", err)
|
||||
|
@ -137,7 +133,11 @@ func Main(audioServer bool, audioInputServer bool) {
|
|||
|
||||
// If running as audio server, only initialize audio processing
|
||||
if isAudioServer {
|
||||
runAudioServer()
|
||||
err := audio.RunAudioOutputServer()
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Msg("audio output server failed")
|
||||
os.Exit(1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -113,14 +113,12 @@ export function useMicrophone() {
|
|||
// Debounce sync calls to prevent race conditions
|
||||
const now = Date.now();
|
||||
if (now - lastSyncRef.current < AUDIO_CONFIG.SYNC_DEBOUNCE_MS) {
|
||||
devLog("Skipping sync - too frequent");
|
||||
return;
|
||||
}
|
||||
lastSyncRef.current = now;
|
||||
|
||||
// Don't sync if we're in the middle of starting the microphone
|
||||
if (isStartingRef.current) {
|
||||
devLog("Skipping sync - microphone is starting");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -197,7 +195,6 @@ export function useMicrophone() {
|
|||
audioConstraints.deviceId = { exact: deviceId };
|
||||
}
|
||||
|
||||
devLog("Requesting microphone with constraints:", audioConstraints);
|
||||
const stream = await navigator.mediaDevices.getUserMedia({
|
||||
audio: audioConstraints
|
||||
});
|
||||
|
@ -265,7 +262,6 @@ export function useMicrophone() {
|
|||
}
|
||||
|
||||
// Notify backend that microphone is started
|
||||
devLog("Notifying backend about microphone start...");
|
||||
|
||||
// Retry logic for backend failures
|
||||
let backendSuccess = false;
|
||||
|
@ -274,7 +270,6 @@ export function useMicrophone() {
|
|||
for (let attempt = 1; attempt <= 3; attempt++) {
|
||||
// If this is a retry, first try to reset the backend microphone state
|
||||
if (attempt > 1) {
|
||||
devLog(`Backend start attempt ${attempt}, first trying to reset backend state...`);
|
||||
try {
|
||||
// Use RPC for reset (cloud-compatible)
|
||||
if (rpcDataChannel?.readyState === "open") {
|
||||
|
@ -290,7 +285,6 @@ export function useMicrophone() {
|
|||
resolve(); // Continue even if both fail
|
||||
});
|
||||
} else {
|
||||
devLog("RPC microphone reset successful");
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
|
@ -315,7 +309,6 @@ export function useMicrophone() {
|
|||
|
||||
// For RPC errors, try again after a short delay
|
||||
if (attempt < 3) {
|
||||
devLog(`Retrying backend start in 500ms (attempt ${attempt + 1}/3)...`);
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
continue;
|
||||
}
|
||||
|
@ -556,7 +549,6 @@ export function useMicrophone() {
|
|||
const autoRestoreMicrophone = async () => {
|
||||
// Wait for RPC connection to be ready before attempting any operations
|
||||
if (rpcDataChannel?.readyState !== "open") {
|
||||
devLog("RPC connection not ready for microphone auto-restore, skipping");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -565,7 +557,6 @@ export function useMicrophone() {
|
|||
|
||||
// If microphone was enabled before page reload and is not currently active, restore it
|
||||
if (microphoneWasEnabled && !isMicrophoneActive && peerConnection) {
|
||||
devLog("Auto-restoring microphone after page reload");
|
||||
try {
|
||||
const result = await startMicrophone();
|
||||
if (result.success) {
|
||||
|
|
11
web.go
11
web.go
|
@ -20,6 +20,7 @@ import (
|
|||
gin_logger "github.com/gin-contrib/logger"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"github.com/jetkvm/kvm/internal/audio"
|
||||
"github.com/jetkvm/kvm/internal/logging"
|
||||
"github.com/pion/webrtc/v4"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
|
@ -233,6 +234,16 @@ func handleWebRTCSession(c *gin.Context) {
|
|||
cancelKeyboardMacro()
|
||||
|
||||
currentSession = session
|
||||
|
||||
// Set up audio relay callback to get current session's audio track
|
||||
// This is needed for audio output to work after enable/disable cycles
|
||||
audio.SetCurrentSessionCallback(func() audio.AudioTrackWriter {
|
||||
if currentSession != nil {
|
||||
return currentSession.AudioTrack
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"sd": sd})
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue