package weblive import ( "encoding/json" "strings" "sync" "time" "unicode/utf8" "github.com/gin-gonic/gin" "github.com/gorilla/websocket" ) const maxDanmakuRunes = 120 var ( danmakuClientsMu sync.Mutex danmakuClients = make(map[*websocket.Conn]struct{}) ) // handleDanmakuWS 弹幕:客户端发 JSON {"text":"..."},服务端立刻向所有连接广播 {"type":"dm","text","ts"},不落库 func handleDanmakuWS(c *gin.Context) { ws, err := upgrader.Upgrade(c.Writer, c.Request, nil) if err != nil { return } ws.SetReadLimit(4096) danmakuClientsMu.Lock() danmakuClients[ws] = struct{}{} danmakuClientsMu.Unlock() defer func() { danmakuClientsMu.Lock() delete(danmakuClients, ws) danmakuClientsMu.Unlock() _ = ws.Close() }() for { mt, payload, err := ws.ReadMessage() if err != nil { return } if mt != websocket.TextMessage { continue } text := extractDanmakuText(payload) if text == "" { continue } out, err := json.Marshal(map[string]interface{}{ "type": "dm", "text": text, "ts": time.Now().UnixMilli(), }) if err != nil { continue } danmakuBroadcast(out) } } func extractDanmakuText(payload []byte) string { var v struct { Text string `json:"text"` } if err := json.Unmarshal(payload, &v); err != nil { return "" } t := strings.TrimSpace(v.Text) if t == "" { return "" } if utf8.RuneCountInString(t) <= maxDanmakuRunes { return t } runes := []rune(t) return string(runes[:maxDanmakuRunes]) } func danmakuBroadcast(b []byte) { danmakuClientsMu.Lock() defer danmakuClientsMu.Unlock() dead := make([]*websocket.Conn, 0) for conn := range danmakuClients { _ = conn.SetWriteDeadline(time.Now().Add(8 * time.Second)) if err := conn.WriteMessage(websocket.TextMessage, b); err != nil { dead = append(dead, conn) } } for _, conn := range dead { delete(danmakuClients, conn) _ = conn.Close() } }