Dep: remove beep/portaudio dependancy

This commit is contained in:
Grail Finder
2026-03-07 16:24:39 +03:00
parent 0e55e44f62
commit 0f0c43f327
5 changed files with 226 additions and 237 deletions

View File

@@ -40,17 +40,13 @@ func (o *KokoroOrator) GetLogger() *slog.Logger {
return o.logger
}
// Speak streams audio directly to an external player
func (o *KokoroOrator) Speak(text string) error {
o.logger.Debug("fn: Speak is called", "text-len", len(text))
// 1. Get the audio stream (still an io.ReadCloser)
body, err := o.requestSound(text)
if err != nil {
return fmt.Errorf("request failed: %w", err)
}
defer body.Close()
// 2. Prepare external player (ffplay as example)
// -i pipe:0 tells ffplay to read from stdin
cmd := exec.Command("ffplay", "-nodisp", "-autoexit", "-i", "pipe:0")
stdin, err := cmd.StdinPipe()
if err != nil {
@@ -60,60 +56,46 @@ func (o *KokoroOrator) Speak(text string) error {
o.cmd = cmd
o.stopCh = make(chan struct{})
o.cmdMu.Unlock()
// 3. Start the player
if err := cmd.Start(); err != nil {
return fmt.Errorf("failed to start ffplay: %w", err)
}
// 4. Copy audio data to stdin in a goroutine
// Copy audio in background
copyErr := make(chan error, 1)
go func() {
_, err := io.Copy(stdin, body)
stdin.Close() // signal EOF to player
stdin.Close()
copyErr <- err
}()
// 5. Wait for player to finish or stop signal
// Wait for player in background
done := make(chan error, 1)
go func() {
done <- cmd.Wait()
}()
// Wait for BOTH copy and player, but ensure we block until done
select {
case <-o.stopCh:
// Stop requested: kill the player
// Stop requested: kill player and wait for it to exit
if o.cmd != nil && o.cmd.Process != nil {
o.cmd.Process.Kill()
}
<-done // wait for process to exit
<-done // Wait for process to actually exit
return nil
case err := <-done:
// Playback finished normally
return err
case copyErrVal := <-copyErr:
if copyErrVal != nil {
// Copy failed kill the player
// Copy failed: kill player and wait
if o.cmd != nil && o.cmd.Process != nil {
o.cmd.Process.Kill()
}
<-done
return copyErrVal
}
return nil
// Copy succeeded, now wait for playback to complete
return <-done
case err := <-done:
// Playback finished normally (copy must have succeeded or player would have exited early)
return err
}
}
// // Stop interrupts ongoing playback
// func (o *KokoroOrator) Stop() {
// o.cmdMu.Lock()
// defer o.cmdMu.Unlock()
// if o.stopCh != nil {
// close(o.stopCh)
// }
// // Also clear the buffer and set interrupt flag as before
// o.mu.Lock()
// o.textBuffer.Reset()
// o.interrupt = true
// o.mu.Unlock()
// }
func (o *KokoroOrator) requestSound(text string) (io.ReadCloser, error) {
if o.URL == "" {
return nil, fmt.Errorf("TTS URL is empty")