All checks were successful
ci/woodpecker/tag/woodpecker Pipeline was successful
- 修复 PlayWav 和 PlayMP3 在 context 取消时的死循环 bug - 添加 WAV/MP3 解码失败的错误日志 - 添加 TTS 播放开始/完成的日志,便于排查问题 修复前 context 取消会导致无限循环,阻塞后续任务执行。 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
85 lines
1.5 KiB
Go
85 lines
1.5 KiB
Go
package audio
|
|
|
|
import (
|
|
"context"
|
|
"github.com/gopxl/beep/v2"
|
|
"github.com/gopxl/beep/v2/mp3"
|
|
"github.com/gopxl/beep/v2/speaker"
|
|
"github.com/gopxl/beep/v2/wav"
|
|
"go.uber.org/zap"
|
|
"io"
|
|
"time"
|
|
)
|
|
|
|
var DefaultSampleRate = beep.SampleRate(44100)
|
|
|
|
func init() {
|
|
err := speaker.Init(DefaultSampleRate, DefaultSampleRate.N(time.Second/10))
|
|
if err != nil {
|
|
panic("扬声器初始化异常: " + err.Error())
|
|
}
|
|
zap.S().Infoln("扬声器初始化完成")
|
|
}
|
|
|
|
func PlayWav(c context.Context, r io.Reader) {
|
|
streamer, format, err := wav.Decode(r)
|
|
if err != nil {
|
|
zap.S().Errorln("WAV解码失败: ", err)
|
|
return
|
|
}
|
|
defer streamer.Close()
|
|
|
|
s := beep.Resample(4, format.SampleRate, DefaultSampleRate, streamer)
|
|
|
|
ctrl := &beep.Ctrl{Streamer: s}
|
|
done := make(chan struct{})
|
|
speaker.Play(beep.Seq(ctrl, beep.Callback(func() {
|
|
close(done)
|
|
})))
|
|
|
|
for {
|
|
select {
|
|
case <-done:
|
|
return
|
|
case <-c.Done():
|
|
{
|
|
speaker.Lock()
|
|
ctrl.Streamer = nil
|
|
speaker.Unlock()
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func PlayMP3(c context.Context, r io.ReadCloser) {
|
|
streamer, format, err := mp3.Decode(r)
|
|
if err != nil {
|
|
zap.S().Errorln("MP3解码失败: ", err)
|
|
return
|
|
}
|
|
defer streamer.Close()
|
|
|
|
s := beep.Resample(4, format.SampleRate, DefaultSampleRate, streamer)
|
|
|
|
ctrl := &beep.Ctrl{Streamer: s}
|
|
done := make(chan struct{})
|
|
speaker.Play(beep.Seq(ctrl, beep.Callback(func() {
|
|
close(done)
|
|
})))
|
|
|
|
for {
|
|
select {
|
|
case <-done:
|
|
return
|
|
case <-c.Done():
|
|
{
|
|
speaker.Lock()
|
|
ctrl.Streamer = nil
|
|
speaker.Unlock()
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|