// Package connection 管理单个 ESP32 硬件 WebSocket 连接的状态。 package connection import ( "encoding/json" "fmt" "sync" "github.com/gorilla/websocket" ) // Connection 保存单个硬件连接的状态,所有方法并发安全。 type Connection struct { WS *websocket.Conn DeviceID string // MAC 地址,来自 URL 参数 device-id ClientID string // 来自 URL 参数 client-id mu sync.Mutex isPlaying bool abortCh chan struct{} // close(abortCh) 通知流控 goroutine 中止播放 writeMu sync.Mutex // gorilla/websocket 写操作不并发安全,需独立锁 } // New 创建新连接对象。 func New(ws *websocket.Conn, deviceID, clientID string) *Connection { return &Connection{ WS: ws, DeviceID: deviceID, ClientID: clientID, } } // StartPlayback 开始新一轮播放,返回 abortCh 供流控 goroutine 监听。 // 若已在播放,先中止上一轮再开始新的。 func (c *Connection) StartPlayback() <-chan struct{} { c.mu.Lock() defer c.mu.Unlock() // 中止上一轮播放(若有) if c.isPlaying && c.abortCh != nil { close(c.abortCh) } c.abortCh = make(chan struct{}) c.isPlaying = true return c.abortCh } // StopPlayback 结束播放状态。 func (c *Connection) StopPlayback() { c.mu.Lock() defer c.mu.Unlock() c.isPlaying = false } // IsPlaying 返回当前是否正在播放。 func (c *Connection) IsPlaying() bool { c.mu.Lock() defer c.mu.Unlock() return c.isPlaying } // SendJSON 序列化 v 并以文本帧发送给设备,并发安全。 func (c *Connection) SendJSON(v any) error { data, err := json.Marshal(v) if err != nil { return fmt.Errorf("connection: marshal json: %w", err) } c.writeMu.Lock() defer c.writeMu.Unlock() return c.WS.WriteMessage(websocket.TextMessage, data) } // SendBinary 以二进制帧发送 Opus 数据,并发安全。 func (c *Connection) SendBinary(data []byte) error { c.writeMu.Lock() defer c.writeMu.Unlock() return c.WS.WriteMessage(websocket.BinaryMessage, data) } // Close 关闭底层 WebSocket 连接。 func (c *Connection) Close() { c.WS.Close() }