package audio import ( "context" "io" "time" "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" ) 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) { zap.S().Debugln("开始 WAV 解码") streamer, format, err := wav.Decode(r) if err != nil { zap.S().Errorln("WAV解码失败: ", err) return } defer streamer.Close() // 获取音频长度信息 totalSamples := streamer.Len() zap.S().Debugf("WAV解码成功,采样率: %d, 总样本数: %d, 预计时长: %.2f秒", format.SampleRate, totalSamples, float64(totalSamples)/float64(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("等待音频播放完成...") ticker := time.NewTicker(1 * time.Second) defer ticker.Stop() lastPos := 0 for { select { case <-done: zap.S().Infoln("音频播放正常结束") return case <-c.Done(): zap.S().Debugf("音频播放被 context 取消: %v", c.Err()) speaker.Lock() ctrl.Streamer = nil speaker.Unlock() return case <-ticker.C: // 获取当前播放位置 pos := streamer.Position() if pos != lastPos { progress := float64(pos) / float64(totalSamples) * 100 currentTime := float64(pos) / float64(format.SampleRate) zap.S().Debugf("播放进度: %d/%d (%.1f%%), %.2f秒", pos, totalSamples, progress, currentTime) lastPos = pos } else { zap.S().Debugf("播放停滞在位置: %d/%d, Streamer状态: %v", pos, totalSamples, ctrl.Streamer != nil) } } } } 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 } } }