Terminate file playback process groups

This commit is contained in:
Brandon McGinty (chatgpt)
2026-08-10 10:25:25 -04:00
committed by Brandon McGinty
parent 1d6ab79ed0
commit cf9e1c6d0a
4 changed files with 56 additions and 2 deletions
+8 -2
View File
@@ -21,6 +21,7 @@ type Player struct {
stopChan chan struct{} stopChan chan struct{}
ctx context.Context ctx context.Context
cancel context.CancelFunc cancel context.CancelFunc
cmd *exec.Cmd
mutex sync.Mutex mutex sync.Mutex
wg sync.WaitGroup wg sync.WaitGroup
playing bool playing bool
@@ -99,6 +100,7 @@ func (p *Player) Stop() error {
if p.cancel != nil { if p.cancel != nil {
p.cancel() p.cancel()
} }
terminateProcessGroup(p.cmd)
} }
p.mutex.Unlock() p.mutex.Unlock()
@@ -106,7 +108,7 @@ func (p *Player) Stop() error {
// worker have exited, otherwise old audio can enter the new playback. // worker have exited, otherwise old audio can enter the new playback.
p.wg.Wait() p.wg.Wait()
p.mutex.Lock() p.mutex.Lock()
p.playing, p.stopping, p.cancel = false, false, nil p.playing, p.stopping, p.cancel, p.cmd = false, false, nil, nil
localPlayback := p.localPlayback localPlayback := p.localPlayback
p.mutex.Unlock() p.mutex.Unlock()
if localPlayback != nil { if localPlayback != nil {
@@ -159,6 +161,7 @@ func (p *Player) readFileAudio() {
ctx := p.ctx ctx := p.ctx
p.mutex.Unlock() p.mutex.Unlock()
cmd := exec.CommandContext(ctx, "ffmpeg", args...) cmd := exec.CommandContext(ctx, "ffmpeg", args...)
configureProcessGroup(cmd)
pipe, err := cmd.StdoutPipe() pipe, err := cmd.StdoutPipe()
if err != nil { if err != nil {
p.mutex.Lock() p.mutex.Lock()
@@ -175,6 +178,9 @@ func (p *Player) readFileAudio() {
p.reportError(errors.New("failed to start ffmpeg: " + err.Error())) p.reportError(errors.New("failed to start ffmpeg: " + err.Error()))
return return
} }
p.mutex.Lock()
p.cmd = cmd
p.mutex.Unlock()
// Stereo has 2 channels, so we need twice the buffer size // Stereo has 2 channels, so we need twice the buffer size
byteBuffer := make([]byte, frameSize*2*2) // frameSize * 2 channels * 2 bytes per sample byteBuffer := make([]byte, frameSize*2*2) // frameSize * 2 channels * 2 bytes per sample
@@ -185,7 +191,7 @@ func (p *Player) readFileAudio() {
for { for {
select { select {
case <-p.stopChan: case <-p.stopChan:
cmd.Process.Kill() terminateProcessGroup(cmd)
cmd.Wait() cmd.Wait()
return return
case <-ticker.C: case <-ticker.C:
+13
View File
@@ -0,0 +1,13 @@
//go:build !(aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris)
package fileplayback
import "os/exec"
func configureProcessGroup(cmd *exec.Cmd) {}
func terminateProcessGroup(cmd *exec.Cmd) {
if cmd != nil && cmd.Process != nil {
_ = cmd.Process.Kill()
}
}
+19
View File
@@ -0,0 +1,19 @@
//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris
package fileplayback
import (
"os/exec"
"syscall"
)
func configureProcessGroup(cmd *exec.Cmd) {
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
}
func terminateProcessGroup(cmd *exec.Cmd) {
if cmd == nil || cmd.Process == nil {
return
}
_ = syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)
}
+16
View File
@@ -0,0 +1,16 @@
//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris
package fileplayback
import (
"os/exec"
"testing"
)
func TestConfigureProcessGroupCreatesSeparateGroup(t *testing.T) {
cmd := exec.Command("true")
configureProcessGroup(cmd)
if cmd.SysProcAttr == nil || !cmd.SysProcAttr.Setpgid {
t.Fatal("ffmpeg process was not configured to lead its own process group")
}
}