feat(audio): 使用 oto/v3 重构音频播放系统,移除 beep/v2 依赖

核心变更:
- 实现全局 oto.Context 单例管理(sync.Once)
- 实现一次性播放:PlayWav/PlayMP3(支持 context 取消)
- 实现 BGM 循环播放:PlayMP3Loop(atomic.Bool + WaitGroup)
- 迁移所有业务层到新 API(TTS/BGM/待机音频)
- 添加完整的单元测试(6/6 通过)

技术栈:
- oto/v3 v3.3.2(低级音频播放)
- hajimehoshi/go-mp3 v0.3.4(MP3 解码)
- youpy/go-wav v0.3.2(WAV 解码)

移除依赖:
- gopxl/beep/v2 及所有相关依赖

优化:
- 流式播放,无需预先加载
- 并发安全,无竞态条件
- 资源管理清晰(defer cleanup)
- Sleep 间隔优化(1ms → 10ms,降低 CPU 占用)
This commit is contained in:
2026-04-08 19:21:48 +08:00
parent b5f7c823c8
commit 788327047c
15 changed files with 396 additions and 149 deletions

37
pkg/audio/context.go Normal file
View File

@@ -0,0 +1,37 @@
package audio
import (
"sync"
"github.com/ebitengine/oto/v3"
"go.uber.org/zap"
)
var (
otoCtx *oto.Context
otoOnce sync.Once
)
const (
DefaultSampleRate = 44100 // 采样率
DefaultChannelCount = 2 // 声道数(立体声)
)
func initContext() (*oto.Context, error) {
var initErr error
otoOnce.Do(func() {
op := &oto.NewContextOptions{}
op.SampleRate = DefaultSampleRate
op.ChannelCount = DefaultChannelCount
op.Format = oto.FormatSignedInt16LE
var ready <-chan struct{}
otoCtx, ready, initErr = oto.NewContext(op)
if initErr != nil {
return
}
<-ready
zap.S().Infoln("oto/v3 音频系统就绪")
})
return otoCtx, initErr
}