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 <noreply@anthropic.com>
This commit is contained in:
m3ta-chironandClaude Fable 5.1 committed 2026-10-24 12:26:38 +02:00
1 parent 00eb76128b
commit a0f91c2d64
2 files changed
+12 -1

No files matched your search

+6 -1
View File
@@ -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 {
+6
View File
@@ -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