mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-10-01 15:02:53 +00:00
fix(remotedevice): 媒体回传落盘 + webui SSE panic + GUI 相机跨平台
1. remotedevice 媒体落盘(核心改动) 设备录像/照片二进制聚合后写入 <data>/device_media/<reqID>.<ext>, cmd_result 返回 file 路径,不再 base64 内联——10s 录像数 MB 的 base64 会撑爆 LLM 上下文与工具结果管道。未配置目录时保持旧内联行为。 新增 TestWSBinaryMediaToFile 覆盖。 2. webui SSE 'send on closed channel' panic(生产单日 4924 次) handleChatEvents 的 defer close(writeCh) 与 Subscribe 回调闭包竞态: handler 退出后总线仍可能异步触发回调向已关闭 channel 发送。 改为 writer goroutine select on done 退出,不 close channel; defer 中等待 writerDone 保证无残余写入。 顺带补 mockPluginMgr.StopAndUnload(3ceb69f 接口变更漏改测试)。 3. GUI camerasue 平台分支 ffmpeg 参数原硬编码 Linux v4l2(/dev/video0),Windows 上必然失败。 现按平台探测:win32=dshow(枚举设备名取第一个视频设备)、 darwin=avfoundation、linux=v4l2;录像编码 Windows 交给 mp4 muxer 默认。
This commit is contained in:
@ -9,6 +9,8 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
@ -207,6 +209,73 @@ func TestWSBinaryChunkUpload(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 媒体落盘:SetMediaDir 后 cmd_result 返回 file 路径而非 base64 内联 =====
|
||||
func TestWSBinaryMediaToFile(t *testing.T) {
|
||||
reg := NewRegistry()
|
||||
token := "test-token-123"
|
||||
reg.SetAcceptToken(func(provided string) bool { return provided == token })
|
||||
mediaDir := t.TempDir()
|
||||
reg.SetMediaDir(mediaDir)
|
||||
|
||||
srv := httptest.NewServer(http.HandlerFunc(reg.ServeWS))
|
||||
defer srv.Close()
|
||||
|
||||
cli := dialTestWS(t, srv.URL, token)
|
||||
defer cli.close()
|
||||
|
||||
cli.sendText([]byte(`{"op":"hello","device":{"device_id":"gui-media","name":"媒体机","kind":"computer","caps":["cmd"]}}`))
|
||||
if _, _, err := cli.readMsg(); err != nil { // hello_ack
|
||||
t.Fatalf("read hello_ack: %v", err)
|
||||
}
|
||||
|
||||
videoData := make([]byte, 30000)
|
||||
for i := range videoData {
|
||||
videoData[i] = byte(i % 253)
|
||||
}
|
||||
go func() {
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
cli.sendText(mustJSON(map[string]interface{}{
|
||||
"op": "cmd_data_start", "req_id": "req-file-1",
|
||||
"kind": "camera_video", "mime": "video/mp4",
|
||||
"total": len(videoData),
|
||||
}))
|
||||
const chunk = 8192
|
||||
for off := 0; off < len(videoData); off += chunk {
|
||||
end := off + chunk
|
||||
if end > len(videoData) {
|
||||
end = len(videoData)
|
||||
}
|
||||
cli.sendBinary(videoData[off:end])
|
||||
}
|
||||
cli.sendText(mustJSON(map[string]interface{}{
|
||||
"op": "cmd_data_end", "req_id": "req-file-1", "status": "ok",
|
||||
}))
|
||||
}()
|
||||
|
||||
res, err := reg.AwaitResult("req-file-1", 5*time.Second)
|
||||
if err != nil {
|
||||
t.Fatalf("await result: %v", err)
|
||||
}
|
||||
// 落盘模式:file 字段存在且内容一致;不应再有 data_base64
|
||||
fp, ok := res["file"].(string)
|
||||
if !ok || fp == "" {
|
||||
t.Fatalf("expected file path in result, got %v", res)
|
||||
}
|
||||
if _, hasB64 := res["data_base64"]; hasB64 {
|
||||
t.Fatal("data_base64 should be absent in file mode")
|
||||
}
|
||||
if want := filepath.Join(mediaDir, "req-file-1.mp4"); fp != want {
|
||||
t.Fatalf("file path = %s, want %s", fp, want)
|
||||
}
|
||||
got, err := os.ReadFile(fp)
|
||||
if err != nil {
|
||||
t.Fatalf("read media file: %v", err)
|
||||
}
|
||||
if string(got) != string(videoData) {
|
||||
t.Fatal("media file content mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 端到端:PushData 下发音频(网关→设备 cmd_speech 协议)=====
|
||||
|
||||
func TestWSPushDataAudio(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user