Files
JKVideo/hooks/useLiveDanmaku.ts

104 lines
3.3 KiB
TypeScript
Raw Normal View History

2026-03-16 21:13:26 +08:00
import { useState, useEffect, useRef } from 'react';
2026-03-17 19:52:26 +08:00
import { getLiveDanmakuHistory } from '../services/bilibili';
2026-03-16 21:13:26 +08:00
import type { DanmakuItem } from '../services/types';
2026-03-17 19:52:26 +08:00
const POLL_INTERVAL = 3000;
2026-03-16 21:13:26 +08:00
2026-03-17 19:52:26 +08:00
// 匹配 admin 消息中的礼物信息,如 "xxx 赠送了 辣条 x5" 或 "xxx 投喂 小心心 ×1"
const GIFT_PATTERN = /(?:赠送|投喂)\s*(?:了\s*)?(.+?)\s*[xX×]\s*(\d+)/;
2026-03-17 19:52:26 +08:00
// 常见礼物名列表,用于匹配
const KNOWN_GIFTS = new Set([
'辣条', '小心心', '打call', '干杯', '比心',
'吃瓜', '花式夸夸', '告白气球', '小电视飞船',
]);
2026-03-17 19:52:26 +08:00
export function useLiveDanmaku(roomId: number): {
danmakus: DanmakuItem[];
giftCounts: Record<string, number>;
} {
2026-03-16 21:13:26 +08:00
const [danmakus, setDanmakus] = useState<DanmakuItem[]>([]);
2026-03-17 19:52:26 +08:00
const [giftCounts, setGiftCounts] = useState<Record<string, number>>({});
const seenTextsRef = useRef<Set<string>>(new Set());
const seenAdminRef = useRef<Set<string>>(new Set());
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
2026-03-16 21:13:26 +08:00
useEffect(() => {
if (!roomId) return;
setDanmakus([]);
2026-03-17 19:52:26 +08:00
setGiftCounts({});
seenTextsRef.current.clear();
seenAdminRef.current.clear();
let cancelled = false;
2026-03-16 21:13:26 +08:00
2026-03-17 19:52:26 +08:00
async function poll() {
2026-03-16 21:13:26 +08:00
try {
2026-03-17 19:52:26 +08:00
const { danmakus: items, adminMsgs } = await getLiveDanmakuHistory(roomId);
if (cancelled) return;
// 去重弹幕
const newItems = items.filter(item => {
const key = `${item.uname ?? ''}:${item.text}`;
if (seenTextsRef.current.has(key)) return false;
seenTextsRef.current.add(key);
return true;
});
if (newItems.length > 0) {
setDanmakus(prev => [...prev, ...newItems]);
}
2026-03-17 18:00:00 +08:00
2026-03-17 19:52:26 +08:00
// 解析 admin 消息中的礼物
const newGifts: Record<string, number> = {};
for (const msg of adminMsgs) {
console.log(msg,123)
// 去重 admin 消息
if (seenAdminRef.current.has(msg)) continue;
seenAdminRef.current.add(msg);
const match = msg.match(GIFT_PATTERN);
if (match) {
const giftName = match[1].trim();
const count = parseInt(match[2], 10);
if (KNOWN_GIFTS.has(giftName) && count > 0) {
newGifts[giftName] = (newGifts[giftName] ?? 0) + count;
}
}
}
if (Object.keys(newGifts).length > 0) {
setGiftCounts(prev => {
const next = { ...prev };
for (const [name, count] of Object.entries(newGifts)) {
next[name] = (next[name] ?? 0) + count;
}
return next;
});
}
2026-03-17 18:00:00 +08:00
2026-03-17 19:52:26 +08:00
// 防止 seen set 无限增长
if (seenTextsRef.current.size > 2000) {
const arr = Array.from(seenTextsRef.current);
seenTextsRef.current = new Set(arr.slice(-1000));
}
if (seenAdminRef.current.size > 500) {
const arr = Array.from(seenAdminRef.current);
seenAdminRef.current = new Set(arr.slice(-250));
}
} catch (e) {
2026-03-17 19:52:26 +08:00
console.warn('[danmaku] poll failed:', e);
}
if (!cancelled) {
timerRef.current = setTimeout(poll, POLL_INTERVAL);
}
}
2026-03-17 19:52:26 +08:00
poll();
2026-03-16 21:13:26 +08:00
return () => {
cancelled = true;
2026-03-17 19:52:26 +08:00
if (timerRef.current) clearTimeout(timerRef.current);
2026-03-16 21:13:26 +08:00
};
}, [roomId]);
2026-03-17 19:52:26 +08:00
return { danmakus, giftCounts };
2026-03-16 21:13:26 +08:00
}