repair-agent 134ccb70f3
All checks were successful
Build and Deploy Backend / build-and-deploy (push) Successful in 5m41s
fix 音频并发优化
2026-03-03 17:21:46 +08:00

87 lines
2.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package handler
import (
"context"
"log"
"time"
"github.com/qy/hw-ws-service/internal/audio"
"github.com/qy/hw-ws-service/internal/connection"
"github.com/qy/hw-ws-service/internal/rtcclient"
)
// HandleStory 处理硬件发来的 {"type":"story"} 指令。
// 在独立 goroutine 中调用,不阻塞消息读取循环。
func HandleStory(conn *connection.Connection, client *rtcclient.Client) {
tag := "[story][" + conn.DeviceID + "]"
// 整个故事播放流程最长允许 10 分钟
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
defer cancel()
// 1. 通知硬件TTS 开始
if err := conn.SendJSON(map[string]string{"type": "tts", "state": "start"}); err != nil {
log.Printf("%s send start failed: %v", tag, err)
return
}
// 确保异常退出时也发送 stop避免硬件卡住
defer func() {
conn.StopPlayback()
conn.SendJSON(map[string]string{"type": "tts", "state": "stop"}) //nolint:errcheck
}()
// 2. 调用 RTC 后端获取故事
story, err := client.FetchStoryByMAC(ctx, conn.DeviceID)
if err != nil {
log.Printf("%s fetch story error: %v", tag, err)
return
}
if story == nil {
log.Printf("%s no story available", tag)
return
}
log.Printf("%s playing: %s", tag, story.Title)
// 3. 获取 Opus 帧:优先使用预转码数据,否则实时 ffmpeg 转码
var frames [][]byte
if story.OpusURL != "" {
frames, err = audio.FetchOpusFrames(ctx, story.OpusURL)
if err != nil {
log.Printf("%s fetch pre-converted opus failed, fallback to ffmpeg: %v", tag, err)
frames = nil // 确保 fallback
} else {
log.Printf("%s loaded %d pre-converted frames (~%.1fs)", tag, len(frames),
float64(len(frames)*audio.FrameDurationMs)/1000)
}
}
if frames == nil {
frames, err = audio.MP3URLToOpusFrames(ctx, story.AudioURL)
if err != nil {
log.Printf("%s audio convert error: %v", tag, err)
return
}
log.Printf("%s converted %d frames (~%.1fs)", tag, len(frames),
float64(len(frames)*audio.FrameDurationMs)/1000)
}
// 4. 通知硬件:句子开始(发送故事标题)
if err := conn.SendJSON(map[string]any{
"type": "tts",
"state": "sentence_start",
"text": story.Title,
}); err != nil {
log.Printf("%s send sentence_start failed: %v", tag, err)
return
}
// 5. 开始播放,获取打断 channel
abortCh := conn.StartPlayback()
// 6. 流控推送 Opus 帧
SendOpusStream(conn, frames, abortCh)
log.Printf("%s playback finished", tag)
// defer 会发送 stop 并调用 StopPlayback
}