Synchronize connection-owned audio resources

This commit is contained in:
Brandon McGinty (chatgpt)
2026-08-10 10:28:01 -04:00
committed by Brandon McGinty
parent b407a20132
commit 57961ffc9f
3 changed files with 56 additions and 21 deletions
+19 -4
View File
@@ -32,6 +32,7 @@ type Barnard struct {
TLSConfig tls.Config TLSConfig tls.Config
Stream *gumbleopenal.Stream Stream *gumbleopenal.Stream
connectionMutex sync.RWMutex
Tx bool Tx bool
AutoTransmit bool // auto-start transmission on connect AutoTransmit bool // auto-start transmission on connect
Connected bool Connected bool
@@ -96,11 +97,13 @@ type Barnard struct {
// reconnect replaces them. It is intentionally idempotent for repeated // reconnect replaces them. It is intentionally idempotent for repeated
// disconnect notifications. // disconnect notifications.
func (b *Barnard) cleanupConnectionAudio() { func (b *Barnard) cleanupConnectionAudio() {
b.connectionMutex.Lock()
if b.Stream != nil { if b.Stream != nil {
stream := b.Stream stream := b.Stream
b.Stream = nil b.Stream = nil
stream.Destroy() stream.Destroy()
} }
b.connectionMutex.Unlock()
b.FileStreamMutex.Lock() b.FileStreamMutex.Lock()
if b.FileStream != nil { if b.FileStream != nil {
_ = b.FileStream.Stop() _ = b.FileStream.Stop()
@@ -121,9 +124,21 @@ func (b *Barnard) cleanupToneTestAudio() {
} }
func (b *Barnard) updateUserGain(user *gumble.User) { func (b *Barnard) updateUserGain(user *gumble.User) {
if b.Stream != nil { b.withStream(func(stream *gumbleopenal.Stream) {
b.Stream.UpdateUserGain(user) stream.UpdateUserGain(user)
})
} }
// withStream keeps a connection-owned stream alive for the complete operation.
// Reconnect cleanup takes the write lock before destroying or replacing it.
func (b *Barnard) withStream(action func(*gumbleopenal.Stream)) bool {
b.connectionMutex.RLock()
defer b.connectionMutex.RUnlock()
if b.Stream == nil {
return false
}
action(b.Stream)
return true
} }
func (b *Barnard) isChannelMuted(channelID uint32) bool { func (b *Barnard) isChannelMuted(channelID uint32) bool {
@@ -212,8 +227,8 @@ func (b *Barnard) StopTransmission() {
close(b.toneTestStop) close(b.toneTestStop)
b.toneTestStop = nil b.toneTestStop = nil
} }
} else if b.Stream != nil { } else {
b.Stream.StopSource() b.withStream(func(stream *gumbleopenal.Stream) { _ = stream.StopSource() })
} }
} }
} }
+15 -8
View File
@@ -87,11 +87,10 @@ func (b *Barnard) connect(reconnect bool) bool {
b.exitWithError(err) b.exitWithError(err)
return false return false
} }
b.Stream = stream stream.SetMicVolume(b.UserConfig.GetMicVolume(), false)
b.Stream.SetMicVolume(b.UserConfig.GetMicVolume(), false) stream.AttachStream(b.Client)
b.Stream.AttachStream(b.Client) stream.SetNoiseProcessor(b.NoiseSuppressor)
b.Stream.SetNoiseProcessor(b.NoiseSuppressor) stream.SetErrorFunc(func(err error) {
b.Stream.SetErrorFunc(func(err error) {
if err != nil { if err != nil {
b.AddOutputLine(fmt.Sprintf("Microphone: %s", err.Error())) b.AddOutputLine(fmt.Sprintf("Microphone: %s", err.Error()))
} else { } else {
@@ -110,8 +109,11 @@ func (b *Barnard) connect(reconnect bool) bool {
b.Client.DisableStereoEncoder() b.Client.DisableStereoEncoder()
b.AddOutputLine(fmt.Sprintf("File playback: %s", err.Error())) b.AddOutputLine(fmt.Sprintf("File playback: %s", err.Error()))
}) })
b.Stream.SetFilePlayer(b.FileStream) stream.SetFilePlayer(b.FileStream)
b.FileStreamMutex.Unlock() b.FileStreamMutex.Unlock()
b.connectionMutex.Lock()
b.Stream = stream
b.connectionMutex.Unlock()
b.setConnected(true) b.setConnected(true)
// Dial delivers OnConnect before connect creates the OpenAL stream, so // Dial delivers OnConnect before connect creates the OpenAL stream, so
@@ -166,16 +168,21 @@ func (b *Barnard) OnConnect(e *gumble.ConnectEvent) {
} }
func (b *Barnard) startAutoTransmit() { func (b *Barnard) startAutoTransmit() {
if !b.AutoTransmit || b.Stream == nil || b.isTransmitting() { if !b.AutoTransmit || b.isTransmitting() {
return return
} }
if err := b.Stream.StartSource(b.UserConfig.GetInputDevice()); err != nil { started := b.withStream(func(stream *gumbleopenal.Stream) {
if err := stream.StartSource(b.UserConfig.GetInputDevice()); err != nil {
b.AddOutputLine(fmt.Sprintf("auto-transmit failed: %s", err.Error())) b.AddOutputLine(fmt.Sprintf("auto-transmit failed: %s", err.Error()))
return return
} }
b.setTransmitting(true) b.setTransmitting(true)
b.UpdateGeneralStatus(" AutoTx ", true) b.UpdateGeneralStatus(" AutoTx ", true)
b.AddOutputLine("Auto-transmit started") b.AddOutputLine("Auto-transmit started")
})
if !started {
return
}
} }
func (b *Barnard) OnDisconnect(e *gumble.DisconnectEvent) { func (b *Barnard) OnDisconnect(e *gumble.DisconnectEvent) {
+13
View File
@@ -0,0 +1,13 @@
package main
import (
"testing"
"git.stormux.org/storm/barnard/gumble/gumbleopenal"
)
func TestWithStreamHandlesAbsentConnectionResource(t *testing.T) {
if (&Barnard{}).withStream(func(*gumbleopenal.Stream) {}) {
t.Fatal("nil connection stream was treated as available")
}
}