fix: handle audio track recreation to prevent microphone failures

When browsers recreate audio tracks during long sessions, the backend
now properly handles the new track instead of ignoring it. Old track
handlers detect when they've been superseded and exit cleanly to
prevent goroutine leaks.
This commit is contained in:
Alex P 2025-11-04 10:40:35 +02:00
parent 3448663afa
commit ce4ea10551
1 changed files with 18 additions and 12 deletions

View File

@ -21,7 +21,7 @@ var (
activeConnections atomic.Int32 activeConnections atomic.Int32
audioLogger zerolog.Logger audioLogger zerolog.Logger
currentAudioTrack *webrtc.TrackLocalStaticSample currentAudioTrack *webrtc.TrackLocalStaticSample
inputTrackHandling atomic.Bool currentInputTrack atomic.Pointer[string]
audioOutputEnabled atomic.Bool audioOutputEnabled atomic.Bool
audioInputEnabled atomic.Bool audioInputEnabled atomic.Bool
) )
@ -152,14 +152,10 @@ func setAudioTrack(audioTrack *webrtc.TrackLocalStaticSample) {
} }
func setPendingInputTrack(track *webrtc.TrackRemote) { func setPendingInputTrack(track *webrtc.TrackRemote) {
audioMutex.Lock() trackID := track.ID()
defer audioMutex.Unlock() currentInputTrack.Store(&trackID)
// Start input track handler only once per WebRTC session
if inputTrackHandling.CompareAndSwap(false, true) {
go handleInputTrackForSession(track) go handleInputTrackForSession(track)
} }
}
// SetAudioOutputEnabled enables or disables audio output // SetAudioOutputEnabled enables or disables audio output
func SetAudioOutputEnabled(enabled bool) error { func SetAudioOutputEnabled(enabled bool) error {
@ -202,22 +198,32 @@ func SetAudioInputEnabled(enabled bool) error {
// handleInputTrackForSession runs for the entire WebRTC session lifetime // handleInputTrackForSession runs for the entire WebRTC session lifetime
// It continuously reads from the track and sends to whatever relay is currently active // It continuously reads from the track and sends to whatever relay is currently active
func handleInputTrackForSession(track *webrtc.TrackRemote) { func handleInputTrackForSession(track *webrtc.TrackRemote) {
defer inputTrackHandling.Store(false) myTrackID := track.ID()
audioLogger.Debug(). audioLogger.Debug().
Str("codec", track.Codec().MimeType). Str("codec", track.Codec().MimeType).
Str("track_id", track.ID()). Str("track_id", myTrackID).
Msg("starting session-lifetime track handler") Msg("starting session-lifetime track handler")
for { for {
// Check if we've been superseded by a new track
currentTrackID := currentInputTrack.Load()
if currentTrackID != nil && *currentTrackID != myTrackID {
audioLogger.Debug().
Str("my_track_id", myTrackID).
Str("current_track_id", *currentTrackID).
Msg("audio track handler exiting - superseded by new track")
return
}
// Read RTP packet (must always read to keep track alive) // Read RTP packet (must always read to keep track alive)
rtpPacket, _, err := track.ReadRTP() rtpPacket, _, err := track.ReadRTP()
if err != nil { if err != nil {
if err == io.EOF { if err == io.EOF {
audioLogger.Debug().Msg("audio track ended") audioLogger.Debug().Str("track_id", myTrackID).Msg("audio track ended")
return return
} }
audioLogger.Warn().Err(err).Msg("failed to read RTP packet") audioLogger.Warn().Err(err).Str("track_id", myTrackID).Msg("failed to read RTP packet")
continue continue
} }