Files
game-driver/pkg/tts/aliyun.go
mapleafgo 5ee8e15965
All checks were successful
ci/woodpecker/tag/woodpecker Pipeline was successful
fix(audio): 修复音频播放死循环并增强错误日志
- 修复 PlayWav 和 PlayMP3 在 context 取消时的死循环 bug
- 添加 WAV/MP3 解码失败的错误日志
- 添加 TTS 播放开始/完成的日志,便于排查问题

修复前 context 取消会导致无限循环,阻塞后续任务执行。

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 11:34:27 +08:00

132 lines
2.9 KiB
Go

package tts
import (
"bytes"
"context"
"fmt"
"game-driver/config"
"game-driver/leaf"
"game-driver/pkg/audio"
"game-driver/pkg/errorsx"
"go.uber.org/zap"
"io"
"log"
"time"
nls "github.com/aliyun/alibabacloud-nls-go-sdk"
)
type AliTTS struct {
config.AliyunConfig
ctx context.Context
tokenResult nls.TokenResult
}
type result struct {
Data io.ReadWriter
Error error
}
var DefaultTTS = &AliTTS{}
// onTaskFailed 识别过程中的错误处理回调参数
func (tts *AliTTS) onTaskFailed(text string, param interface{}) {
p, _ := param.(*result)
p.Error = fmt.Errorf("语音合成异常: %v", text)
}
// onSynthesisResult 语音合成数据回调参数
func (tts *AliTTS) onSynthesisResult(data []byte, param interface{}) {
p, _ := param.(*result)
p.Data.Write(data)
}
func (tts *AliTTS) Sound(text string) {
if text == "" {
return
}
zap.S().Infof("开始播放TTS: %s", text)
buf, err := tts.Get(text)
if err == nil && buf != nil {
audio.PlayWav(tts.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
}
tts.tokenResult = nls.TokenResult{}
resultMessage, err := nls.GetToken("cn-shanghai", "nls-meta.cn-shanghai.aliyuncs.com", tts.AccessKeyID, tts.AccessKeySecret, "2019-02-28")
if err != nil {
return err
} else if resultMessage.ErrMsg != "" {
zap.S().Errorf("获取Token失败: %s", resultMessage.ErrMsg)
return errorsx.ThirdPartyErr
}
tts.tokenResult = resultMessage.TokenResult
return nil
}
func (tts *AliTTS) Get(text string) (io.Reader, error) {
param := nls.DefaultSpeechSynthesisParam()
param.Volume = tts.Volume
param.Voice = tts.Voice
param.SpeechRate = tts.SpeechRate
err := tts.getToken()
if err != nil {
return nil, err
}
connectConfig := nls.NewConnectionConfigWithToken(nls.DEFAULT_URL, tts.AppKey, tts.tokenResult.Id)
logger := nls.NewNlsLogger(leaf.DefaultWriter, "", log.LstdFlags|log.Ltime)
logger.SetLogSil(false)
logger.SetDebug(true)
ttsData := &result{
Data: &bytes.Buffer{},
}
synthesis, err := nls.NewSpeechSynthesis(
connectConfig, logger, false,
tts.onTaskFailed, tts.onSynthesisResult, nil,
nil, nil, ttsData,
)
if err != nil {
return ttsData.Data, err
}
defer synthesis.Shutdown()
ch, err := synthesis.Start(text, param, nil)
if err != nil {
return ttsData.Data, err
}
// 等待语音合成结束
select {
case done := <-ch:
{
if !done {
return ttsData.Data, errorsx.ThirdPartyErr
}
return ttsData.Data, nil
}
case <-time.After(time.Duration(tts.Timeout) * time.Second):
return ttsData.Data, errorsx.DriverTimeoutErr
case <-tts.ctx.Done():
return ttsData.Data, errorsx.DriverCancelErr
}
}
func New(ctx context.Context, config config.AliyunConfig) *AliTTS {
return &AliTTS{
ctx: ctx,
AliyunConfig: config,
}
}