直播:铺满画面与黑屏重播、弹幕礼物与全站特效、礼物列表与 WebRTC nudge
Made-with: Cursor
This commit is contained in:
@@ -15,6 +15,23 @@ import (
|
||||
|
||||
const maxDanmakuRunes = 120
|
||||
|
||||
var allowedGifts = map[string]struct{}{
|
||||
"rocket": {},
|
||||
"sports_car": {},
|
||||
"plane": {},
|
||||
"carnival": {},
|
||||
"rose": {},
|
||||
"heart": {},
|
||||
"star": {},
|
||||
"clap": {},
|
||||
"cake": {},
|
||||
"crown": {},
|
||||
"fireworks": {},
|
||||
"gift_box": {},
|
||||
"beer": {},
|
||||
"mic": {},
|
||||
}
|
||||
|
||||
var (
|
||||
danmakuClientsMu sync.Mutex
|
||||
danmakuClients = make(map[*websocket.Conn]struct{})
|
||||
@@ -28,7 +45,19 @@ func writeDanmakuJSON(ws *websocket.Conn, v any) error {
|
||||
return ws.WriteMessage(websocket.TextMessage, b)
|
||||
}
|
||||
|
||||
// handleDanmakuWS 弹幕:收 JSON {"text":"..."};未带有效 token 仅可收广播不可发。广播 {"type":"dm","text","from","ts"},不落库
|
||||
func clipDanmakuText(t string) string {
|
||||
t = strings.TrimSpace(t)
|
||||
if t == "" {
|
||||
return ""
|
||||
}
|
||||
if utf8.RuneCountInString(t) <= maxDanmakuRunes {
|
||||
return t
|
||||
}
|
||||
runes := []rune(t)
|
||||
return string(runes[:maxDanmakuRunes])
|
||||
}
|
||||
|
||||
// handleDanmakuWS 弹幕 / 礼物:未带有效 token 仅可收广播。礼物 JSON {"type":"gift","gift":"rocket"};弹幕 {"text":"..."}
|
||||
func handleDanmakuWS(c *gin.Context) {
|
||||
claims, tokenOK := handlers.ParseSiteClaims(c.Query("token"))
|
||||
canSend := tokenOK
|
||||
@@ -66,11 +95,41 @@ func handleDanmakuWS(c *gin.Context) {
|
||||
_ = writeDanmakuJSON(ws, map[string]interface{}{
|
||||
"type": "error",
|
||||
"code": "login_required",
|
||||
"message": "请先登录或注册后再发弹幕",
|
||||
"message": "请先登录或注册后再发弹幕或礼物",
|
||||
})
|
||||
continue
|
||||
}
|
||||
text := extractDanmakuText(payload)
|
||||
var envelope struct {
|
||||
Type string `json:"type"`
|
||||
Text string `json:"text"`
|
||||
Gift string `json:"gift"`
|
||||
}
|
||||
if err := json.Unmarshal(payload, &envelope); err != nil {
|
||||
continue
|
||||
}
|
||||
if strings.EqualFold(strings.TrimSpace(envelope.Type), "gift") {
|
||||
gid := strings.TrimSpace(envelope.Gift)
|
||||
if _, ok := allowedGifts[gid]; !ok {
|
||||
_ = writeDanmakuJSON(ws, map[string]interface{}{
|
||||
"type": "error",
|
||||
"code": "bad_gift",
|
||||
"message": "无效的礼物",
|
||||
})
|
||||
continue
|
||||
}
|
||||
out, err := json.Marshal(map[string]interface{}{
|
||||
"type": "gift",
|
||||
"gift": gid,
|
||||
"from": fromDisplay,
|
||||
"ts": time.Now().UnixMilli(),
|
||||
})
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
danmakuBroadcast(out)
|
||||
continue
|
||||
}
|
||||
text := clipDanmakuText(envelope.Text)
|
||||
if text == "" {
|
||||
continue
|
||||
}
|
||||
@@ -87,24 +146,6 @@ func handleDanmakuWS(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
|
||||
Reference in New Issue
Block a user