stop file playback processes and their children reliably
Kill ffmpeg's whole process group rather than just the process. ffmpeg spawns helpers for some inputs, so stopping playback left them running and holding the output pipe open. The player now starts the command in its own process group and signals the group. Wait for the playback worker to finish before starting a new file. Playing a second file while the first was shutting down left two workers writing to the same stream. Make pausing nonblocking. The pause path could block on a full pipe and hang the UI thread that requested it. Install and reset the stereo encoder under the client lock. File playback swapped the encoder field directly while a voice frame could be encoding with it, and a finished file left the encoder carrying state into the next one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
fbb6a148ff
commit
c5baaae6a7
+13
-1
@@ -396,6 +396,13 @@ func (c *Client) Send(message Message) {
|
||||
message.writeMessage(c)
|
||||
}
|
||||
|
||||
// SetStereoEncoder installs the encoder used for stereo file playback.
|
||||
func (c *Client) SetStereoEncoder(encoder AudioEncoder) {
|
||||
c.volatile.Lock()
|
||||
defer c.volatile.Unlock()
|
||||
c.AudioEncoderStereo = encoder
|
||||
}
|
||||
|
||||
// EnableStereoEncoder switches to stereo encoding for file playback.
|
||||
func (c *Client) EnableStereoEncoder() {
|
||||
c.volatile.Lock()
|
||||
@@ -448,11 +455,16 @@ func (c *Client) UDPActive() bool {
|
||||
return c.udpActive
|
||||
}
|
||||
|
||||
// DisableStereoEncoder switches back to mono encoding for voice.
|
||||
// DisableStereoEncoder switches back to mono encoding for voice and
|
||||
// resets the stereo encoder so stale state does not bleed into the
|
||||
// next file playback.
|
||||
func (c *Client) DisableStereoEncoder() {
|
||||
c.volatile.Lock()
|
||||
defer c.volatile.Unlock()
|
||||
c.useStereoEncoder = false
|
||||
if c.AudioEncoderStereo != nil {
|
||||
c.AudioEncoderStereo.Reset()
|
||||
}
|
||||
}
|
||||
|
||||
// IsStereoEncoderEnabled returns true if stereo encoding is currently active.
|
||||
|
||||
@@ -57,7 +57,7 @@ func New(client *gumble.Client, source Source) *Stream {
|
||||
Volume: 1.0,
|
||||
Source: source,
|
||||
Command: "ffmpeg",
|
||||
pause: make(chan struct{}),
|
||||
pause: make(chan struct{}, 1),
|
||||
state: StateInitial,
|
||||
}
|
||||
}
|
||||
@@ -124,7 +124,12 @@ func (s *Stream) Pause() error {
|
||||
}
|
||||
s.state = StatePaused
|
||||
s.l.Unlock()
|
||||
s.pause <- struct{}{}
|
||||
// The process can exit after the state check. A buffered, coalesced pause
|
||||
// request preserves the state transition without blocking the caller.
|
||||
select {
|
||||
case s.pause <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -176,9 +181,12 @@ func (s *Stream) process() {
|
||||
return
|
||||
}
|
||||
int16Buffer := make([]int16, frameSize)
|
||||
s.l.Lock()
|
||||
volume := s.Volume
|
||||
s.l.Unlock()
|
||||
for i := range int16Buffer {
|
||||
float := float32(int16(binary.LittleEndian.Uint16(byteBuffer[i*2 : (i+1)*2])))
|
||||
int16Buffer[i] = int16(s.Volume * float)
|
||||
int16Buffer[i] = int16(volume * float)
|
||||
}
|
||||
atomic.AddInt64(&s.elapsed, int64(interval))
|
||||
outgoing <- gumble.AudioBuffer(int16Buffer)
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package gumbleffmpeg
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Regression: Pause sent on an unbuffered channel after the process had
|
||||
// exited, leaving callers blocked forever.
|
||||
func TestPauseDoesNotBlockWhenProcessHasExited(t *testing.T) {
|
||||
s := &Stream{state: StatePlaying, pause: make(chan struct{}, 1)}
|
||||
done := make(chan error, 1)
|
||||
go func() { done <- s.Pause() }()
|
||||
select {
|
||||
case err := <-done:
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("Pause blocked after process exit")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user