Some checks failed
Build and Deploy Backend / build-and-deploy (push) Failing after 4m28s
## 变更内容
### k8s/ingress.yaml
- 新增 /xiaozhi/v1/ 路径规则,将 WebSocket 流量路由到 hw-ws-svc:8888
- Traefik 最长前缀优先,/xiaozhi/v1/ 不影响 / 下的 Django 路由
### hw_service_go/k8s/service.yaml
- Service 类型由 LoadBalancer 改为 ClusterIP
- 移除阿里云 SLB 注解(通过 Traefik Ingress 统一暴露,不再需要独立公网 IP)
### hw_service_go/k8s/deployment.yaml
- 镜像地址改为 ${CI_REGISTRY_IMAGE}/hw-ws-service:latest 占位符
- CI/CD 部署时统一通过 sed 替换为华为云 SWR 实际地址
### hw_service_go/internal/server/server.go
- 新增 GET /xiaozhi/v1/healthz 接口,返回 {"status":"ok","active_connections":N}
- 用于部署后验证服务存活及当前连接数
### .gitea/workflows/deploy.yaml
- 新增 Build and Push HW WebSocket Service 步骤,构建并推送 hw_service_go 镜像
- 部署步骤新增 kubectl apply hw_service_go/k8s/deployment.yaml 和 service.yaml
- 新增 kubectl rollout restart deployment/hw-ws-service
### run.sh
- 本地同时启动 Django + hw_service_go 的开发脚本
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
64 lines
1.5 KiB
Go
64 lines
1.5 KiB
Go
package handler
|
||
|
||
import (
|
||
"time"
|
||
|
||
"github.com/qy/hw-ws-service/internal/audio"
|
||
"github.com/qy/hw-ws-service/internal/connection"
|
||
)
|
||
|
||
// SendOpusStream 将 Opus 帧列表按 60ms/帧的节奏流控发送给硬件。
|
||
//
|
||
// 流控策略:
|
||
// 1. 预缓冲:前 PreBufferCount 帧立即发送,减少硬件首帧延迟
|
||
// 2. 时序流控:按 (帧序号 × 60ms) 计算期望发送时间,select 等待
|
||
// 3. 打断:监听 abortCh,收到关闭信号立即返回
|
||
func SendOpusStream(conn *connection.Connection, frames [][]byte, abortCh <-chan struct{}) {
|
||
if len(frames) == 0 {
|
||
return
|
||
}
|
||
|
||
startTime := time.Now()
|
||
playedMs := 0
|
||
|
||
// 阶段1:预缓冲,快速发送前 N 帧
|
||
pre := audio.PreBufferCount
|
||
if pre > len(frames) {
|
||
pre = len(frames)
|
||
}
|
||
for _, f := range frames[:pre] {
|
||
select {
|
||
case <-abortCh:
|
||
return
|
||
default:
|
||
}
|
||
conn.SendBinary(f) //nolint:errcheck // 连接断开时下一次 ReadMessage 会返回错误
|
||
}
|
||
playedMs = pre * audio.FrameDurationMs
|
||
|
||
// 阶段2:时序流控
|
||
for _, f := range frames[pre:] {
|
||
expectedAt := startTime.Add(time.Duration(playedMs) * time.Millisecond)
|
||
delay := time.Until(expectedAt)
|
||
|
||
if delay > 0 {
|
||
select {
|
||
case <-time.After(delay):
|
||
// 到达预期发送时间,继续
|
||
case <-abortCh:
|
||
return
|
||
}
|
||
}
|
||
|
||
// delay <= 0:处理比预期慢,追赶进度,直接发送
|
||
select {
|
||
case <-abortCh:
|
||
return
|
||
default:
|
||
}
|
||
|
||
conn.SendBinary(f) //nolint:errcheck
|
||
playedMs += audio.FrameDurationMs
|
||
}
|
||
}
|