From 2331d0c73f282e4edc0c3e4d04f7676f0e7e2600 Mon Sep 17 00:00:00 2001 From: mapleafgo Date: Wed, 8 Apr 2026 12:07:39 +0800 Subject: [PATCH] =?UTF-8?q?fix(tts):=20=E4=BF=AE=E5=A4=8D=20TTS=20?= =?UTF-8?q?=E6=92=AD=E6=94=BE=E5=8D=A1=E6=AD=BB=E9=97=AE=E9=A2=98=E5=B9=B6?= =?UTF-8?q?=E5=A2=9E=E5=BC=BA=E6=97=A5=E5=BF=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加 SoundWithContext 方法,使用请求 context 而非全局 context - 修复 TTS 使用服务器全局 context 导致无法取消的问题 - 添加详细的诊断日志(解码、播放、TTS 合成各阶段) - 检测并记录 TTS 合成数据为空的情况 修复前 TTS 播放使用全局 context,当播放卡住时无法通过超时 或取消机制中断,导致后续任务永远无法执行。 Co-Authored-By: Claude Sonnet 4.6 --- internal/middleware/sound_start.go | 16 ++++++++++++---- pkg/audio/play.go | 6 ++++++ pkg/tts/aliyun.go | 26 ++++++++++++++++++++++++++ 3 files changed, 44 insertions(+), 4 deletions(-) diff --git a/internal/middleware/sound_start.go b/internal/middleware/sound_start.go index de6e98f..2bb3fe7 100644 --- a/internal/middleware/sound_start.go +++ b/internal/middleware/sound_start.go @@ -10,16 +10,24 @@ import ( func SoundStart() leaf.HandlerFunc { return func(c *leaf.Context) { pm := leaf.Value[*schema.PlayModal](c, PayloadJSONKey) - tts.DefaultTTS.Sound(pm.TTS.Start) + + // 使用请求的 context,支持取消和超时 + if pm.TTS.Start != "" { + tts.DefaultTTS.SoundWithContext(c, pm.TTS.Start) + } defer func() { + var text string switch leaf.Value[leaf.EndType](c, leaf.EndKey) { case leaf.End: - tts.DefaultTTS.Sound(pm.TTS.End) + text = pm.TTS.End case leaf.EndTimeout: - tts.DefaultTTS.Sound(pm.TTS.Timeout) + text = pm.TTS.Timeout case leaf.EndStop: - tts.DefaultTTS.Sound(pm.TTS.Stop) + text = pm.TTS.Stop + } + if text != "" { + tts.DefaultTTS.SoundWithContext(c, text) } }() diff --git a/pkg/audio/play.go b/pkg/audio/play.go index 55af1b4..ef28586 100644 --- a/pkg/audio/play.go +++ b/pkg/audio/play.go @@ -22,6 +22,7 @@ func init() { } func PlayWav(c context.Context, r io.Reader) { + zap.S().Debugln("开始 WAV 解码") streamer, format, err := wav.Decode(r) if err != nil { zap.S().Errorln("WAV解码失败: ", err) @@ -29,20 +30,25 @@ func PlayWav(c context.Context, r io.Reader) { } defer streamer.Close() + zap.S().Debugln("WAV解码成功,采样率:", format.SampleRate) 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() { + zap.S().Debugln("音频播放完成") close(done) }))) + zap.S().Debugln("等待音频播放完成...") for { select { case <-done: + zap.S().Infoln("音频播放正常结束") return case <-c.Done(): { + zap.S().Infoln("音频播放被 context 取消") speaker.Lock() ctrl.Streamer = nil speaker.Unlock() diff --git a/pkg/tts/aliyun.go b/pkg/tts/aliyun.go index a476d21..18f231b 100644 --- a/pkg/tts/aliyun.go +++ b/pkg/tts/aliyun.go @@ -48,6 +48,7 @@ func (tts *AliTTS) Sound(text string) { zap.S().Infof("开始播放TTS: %s", text) buf, err := tts.Get(text) if err == nil && buf != nil { + zap.S().Debugln("TTS合成成功,开始播放") audio.PlayWav(tts.ctx, buf) zap.S().Infof("TTS播放完成: %s", text) } else { @@ -55,6 +56,22 @@ func (tts *AliTTS) Sound(text string) { } } +// SoundWithContext 使用指定的 context 播放 TTS,支持取消和超时 +func (tts *AliTTS) SoundWithContext(ctx context.Context, text string) { + if text == "" { + return + } + zap.S().Infof("开始播放TTS: %s", text) + buf, err := tts.Get(text) + if err == nil && buf != nil { + zap.S().Debugln("TTS合成成功,开始播放") + audio.PlayWav(ctx, buf) + zap.S().Infof("TTS播放完成: %s", text) + } else { + zap.S().Errorln("AliTTS 请求异常: ", err) + } +} + func (tts *AliTTS) getToken() error { if tts.tokenResult.ExpireTime != 0 && time.Unix(tts.tokenResult.ExpireTime, 0).After(time.Now()) { return nil @@ -112,13 +129,22 @@ func (tts *AliTTS) Get(text string) (io.Reader, error) { case done := <-ch: { if !done { + zap.S().Errorln("TTS合成失败: done=false") + return ttsData.Data, errorsx.ThirdPartyErr + } + size := ttsData.Data.(*bytes.Buffer).Len() + zap.S().Debugf("TTS合成成功,数据大小: %d bytes", size) + if size == 0 { + zap.S().Errorln("TTS合成数据为空") return ttsData.Data, errorsx.ThirdPartyErr } return ttsData.Data, nil } case <-time.After(time.Duration(tts.Timeout) * time.Second): + zap.S().Errorln("TTS合成超时") return ttsData.Data, errorsx.DriverTimeoutErr case <-tts.ctx.Done(): + zap.S().Errorln("TTS合成被取消") return ttsData.Data, errorsx.DriverCancelErr } }