From a0f91c2d64bc914edaa9dfc3833c5bb2614dcd1c Mon Sep 17 00:00:00 2001 From: m3ta-chiron Date: Sat, 24 Oct 2026 12:26:38 +0200 Subject: [PATCH] fix(audio): a cancelled announcement really stops; oto's Close is a no-op Clicking Cancel reached the daemon and the queue cancelled the item, but the sound played on to the end: oto v3's Player.Close only relies on the garbage collector's finalizer, so the player stayed in the mix. Pause takes it out immediately; only the device buffer (about 100 ms) drains. Same fix covers talk cancel and daemon shutdown. Direct speech via spd-say: killing the waiting client did not stop speech-dispatcher either; on cancel it now sends `spd-say -S`. Co-Authored-By: Claude Fable 5.1 --- internal/audio/oto.go | 7 ++++++- internal/provider/osbuiltin.go | 6 ++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/internal/audio/oto.go b/internal/audio/oto.go index c11f972..35647cc 100644 --- a/internal/audio/oto.go +++ b/internal/audio/oto.go @@ -91,7 +91,12 @@ func (OtoSink) Play(ctx context.Context, data []byte) error { player.Play() for player.IsPlaying() { if err := ctx.Err(); err != nil { - player.Close() + // Cancel: stop the sound NOW. oto's Close is a no-op that + // relies on the garbage collector's finalizer, so a cancelled + // announcement would play on to its end; Pause takes the + // player out of the mix immediately (only the device buffer, + // about 100 ms, drains). + player.Pause() return err } if err := player.Err(); err != nil { diff --git a/internal/provider/osbuiltin.go b/internal/provider/osbuiltin.go index b3e2450..6f1f03e 100644 --- a/internal/provider/osbuiltin.go +++ b/internal/provider/osbuiltin.go @@ -122,6 +122,12 @@ func (p OSBuiltin) SpeakDirect(ctx context.Context, text, lang string) error { } cmd := exec.CommandContext(ctx, "spd-say", "-w", "-l", lang, text) if out, err := cmd.CombinedOutput(); err != nil { + if ctx.Err() != nil { + // Cancelled: killing the waiting client does not stop + // speech-dispatcher's output; tell it to stop explicitly. + _ = exec.Command("spd-say", "-S").Run() + return ctx.Err() + } return fmt.Errorf("osbuiltin: spd-say failed: %w: %s", err, bytes.TrimSpace(out)) } return nil