diff options
| author | Grail Finder <wohilas@gmail.com> | 2026-03-07 16:24:39 +0300 |
|---|---|---|
| committer | Grail Finder <wohilas@gmail.com> | 2026-03-07 16:24:39 +0300 |
| commit | 0f0c43f32701c314e2472ef1f9a1ec8a68ab0d1a (patch) | |
| tree | 92b37336495972334c7c7f8ef4fe8fa29545f9c4 /extra/kokoro.go | |
| parent | 0e55e44f624d2839dc51fa293a18b323c497a6b1 (diff) | |
Dep: remove beep/portaudio dependancy
Diffstat (limited to 'extra/kokoro.go')
| -rw-r--r-- | extra/kokoro.go | 42 |
1 files changed, 12 insertions, 30 deletions
diff --git a/extra/kokoro.go b/extra/kokoro.go index 15b173b..e3ca047 100644 --- a/extra/kokoro.go +++ b/extra/kokoro.go @@ -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") |
