Skip to content

Commit 5a24303

Browse files
authored
全平台多直播间实时弹幕聚合显示:显示屏UI
1 parent 30eb5de commit 5a24303

File tree

1 file changed

+395
-0
lines changed

1 file changed

+395
-0
lines changed

plugins/aggregate/view.html

Lines changed: 395 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,395 @@
1+
<!DOCTYPE html>
2+
<html lang="zh-CN">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<title>流鹰-弹幕聚合</title>
7+
<script src="https://cdn.iseekfree.com/share/assets/js/reconnecting-websocket.min.js"></script>
8+
<style>
9+
:root {
10+
--bg-color: #0a0c10;
11+
--card-bg: rgba(255, 255, 255, 0.05);
12+
--text-main: #e0e0e0;
13+
--text-dim: #888;
14+
15+
/* 平台颜色 */
16+
--color-taobao: #ff5000;
17+
--color-douyin: #fe2c55;
18+
--color-tiktok: #00f2ea;
19+
--color-wechat: #07c160;
20+
--color-kuaishou: #ff4e00;
21+
}
22+
23+
* {
24+
margin: 0;
25+
padding: 0;
26+
box-sizing: border-box;
27+
font-family: "PingFang SC", "Microsoft YaHei", sans-serif;
28+
}
29+
30+
body {
31+
background-color: var(--bg-color);
32+
color: var(--text-main);
33+
overflow: hidden; /* 防止页面整体滚动 */
34+
height: 100vh;
35+
display: flex;
36+
flex-direction: column;
37+
}
38+
39+
/* 顶部导航 */
40+
header {
41+
padding: 20px;
42+
background: rgba(255, 255, 255, 0.02);
43+
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
44+
backdrop-filter: blur(10px);
45+
display: flex;
46+
justify-content: space-between;
47+
align-items: center;
48+
z-index: 100;
49+
}
50+
51+
.logo-area {
52+
display: flex;
53+
align-items: center;
54+
gap: 12px;
55+
}
56+
57+
.logo-area h1 {
58+
font-size: 1.2rem;
59+
letter-spacing: 1px;
60+
background: linear-gradient(90deg, #fff, #888);
61+
-webkit-background-clip: text;
62+
-webkit-text-fill-color: transparent;
63+
}
64+
65+
.status-tag {
66+
font-size: 12px;
67+
padding: 4px 10px;
68+
border-radius: 20px;
69+
background: rgba(0, 255, 127, 0.1);
70+
color: #00ff7f;
71+
border: 1px solid rgba(0, 255, 127, 0.3);
72+
display: flex;
73+
align-items: center;
74+
gap: 5px;
75+
}
76+
77+
.status-dot {
78+
width: 8px;
79+
height: 8px;
80+
background: #00ff7f;
81+
border-radius: 50%;
82+
animation: blink 1.5s infinite;
83+
}
84+
85+
/* 弹幕主容器 */
86+
#danmaku-container {
87+
flex: 1;
88+
padding: 20px;
89+
overflow-y: auto;
90+
display: flex;
91+
flex-direction: column;
92+
gap: 12px;
93+
mask-image: linear-gradient(to bottom, transparent 0%, black 5%, black 95%, transparent 100%);
94+
}
95+
96+
/* 隐藏滚动条 */
97+
#danmaku-container::-webkit-scrollbar {
98+
width: 4px;
99+
}
100+
#danmaku-container::-webkit-scrollbar-thumb {
101+
background: rgba(255,255,255,0.1);
102+
border-radius: 10px;
103+
}
104+
105+
/* 弹幕卡片样式 */
106+
.msg-card {
107+
display: flex;
108+
align-items: flex-start;
109+
gap: 12px;
110+
padding: 12px 16px;
111+
background: var(--card-bg);
112+
border-radius: 12px;
113+
border-left: 4px solid transparent;
114+
backdrop-filter: blur(5px);
115+
animation: slideIn 0.3s ease-out forwards;
116+
max-width: 800px;
117+
margin: 0 auto;
118+
width: 100%;
119+
transition: transform 0.2s;
120+
}
121+
122+
.msg-card:hover {
123+
background: rgba(255, 255, 255, 0.08);
124+
transform: translateX(5px);
125+
}
126+
127+
/* 平台标识 */
128+
.platform-badge {
129+
font-size: 11px;
130+
font-weight: bold;
131+
padding: 2px 8px;
132+
border-radius: 4px;
133+
text-transform: uppercase;
134+
flex-shrink: 0;
135+
margin-top: 2px;
136+
}
137+
138+
/* 内容区 */
139+
.msg-content {
140+
display: flex;
141+
flex-direction: column;
142+
gap: 4px;
143+
}
144+
145+
.user-info {
146+
display: flex;
147+
align-items: center;
148+
gap: 8px;
149+
}
150+
151+
.user-name {
152+
font-weight: 600;
153+
color: var(--text-dim);
154+
font-size: 13px;
155+
}
156+
157+
.message-text {
158+
color: var(--text-main);
159+
line-height: 1.5;
160+
font-size: 15px;
161+
word-break: break-all;
162+
}
163+
164+
/* 动态平台颜色注入 */
165+
.msg-taobao { border-left-color: var(--color-taobao); }
166+
.msg-taobao .platform-badge { background: var(--color-taobao); color: white; }
167+
168+
.msg-bilibili { border-left-color: var(--color-taobao); }
169+
.msg-bilibili .platform-badge { background: var(--color-taobao); color: white; }
170+
171+
.msg-douyin { border-left-color: var(--color-douyin); }
172+
.msg-douyin .platform-badge { background: var(--color-douyin); color: white; }
173+
174+
.msg-tiktok { border-left-color: var(--color-tiktok); }
175+
.msg-tiktok .platform-badge { background: var(--color-tiktok); color: #000; }
176+
177+
.msg-wechat { border-left-color: var(--color-wechat); }
178+
.msg-wechat .platform-badge { background: var(--color-wechat); color: white; }
179+
180+
.msg-kuaishou { border-left-color: var(--color-kuaishou); }
181+
.msg-kuaishou .platform-badge { background: var(--color-kuaishou); color: white; }
182+
183+
/* 动画 */
184+
@keyframes slideIn {
185+
from { opacity: 0; transform: translateY(20px); }
186+
to { opacity: 1; transform: translateY(0); }
187+
}
188+
189+
@keyframes blink {
190+
0% { opacity: 1; }
191+
50% { opacity: 0.4; }
192+
100% { opacity: 1; }
193+
}
194+
195+
/* 底部统计(PC端显示) */
196+
footer {
197+
padding: 15px 20px;
198+
background: rgba(0, 0, 0, 0.3);
199+
display: flex;
200+
gap: 20px;
201+
font-size: 12px;
202+
color: var(--text-dim);
203+
}
204+
205+
/* 移动端适配 */
206+
@media (max-width: 768px) {
207+
.msg-card {
208+
max-width: 100%;
209+
}
210+
header h1 {
211+
font-size: 1rem;
212+
}
213+
footer {
214+
display: none; /* 移动端隐藏底部统计以增加空间 */
215+
}
216+
}
217+
</style>
218+
</head>
219+
<body>
220+
221+
<header>
222+
<div class="logo-area">
223+
<h1>流鹰</h1>
224+
</div>
225+
<div class="status-tag">
226+
<div class="status-dot"></div>
227+
<span>推送中</span>
228+
</div>
229+
</header>
230+
231+
<main id="danmaku-container">
232+
<!-- 弹幕将通过 JS 动态插入 -->
233+
</main>
234+
235+
<script>
236+
// Get URL parameter
237+
function getUrlParameter(name) {
238+
const urlParams = new URLSearchParams(window.location.search);
239+
return urlParams.get(name);
240+
}
241+
242+
const platforms = [
243+
{ id: 'taobao', name: '淘宝', class: 'msg-taobao' },
244+
{ id: 'douyin', name: '抖音', class: 'msg-douyin' },
245+
{ id: 'tiktok', name: 'TikTok', class: 'msg-tiktok' },
246+
{ id: 'bilibili', name: 'BiliBili', class: 'msg-bilibili' },
247+
{ id: 'wemedia', name: '视频号', class: 'msg-wechat' },
248+
{ id: 'kuaishou', name: '快手', class: 'msg-kuaishou' }
249+
];
250+
const visitToken = getUrlParameter("visitToken");
251+
const container = document.getElementById('danmaku-container');
252+
let ws = null;
253+
254+
255+
function findPlatformName(platform) {
256+
var index = platforms.findIndex(p => p.id === platform);
257+
return index >= 0 ? platforms[index].name : platform;
258+
}
259+
260+
function findPlatformClass(platform) {
261+
var index = platforms.findIndex(p => p.id === platform);
262+
return index >= 0 ? platforms[index].class : 'msg-taobao';
263+
}
264+
265+
// 创建弹幕函数
266+
function addMessage(message) {
267+
// const platform = platforms[Math.floor(Math.random() * platforms.length)];
268+
// const user = mockUsers[Math.floor(Math.random() * mockUsers.length)];
269+
// const text = mockMessages[Math.floor(Math.random() * mockMessages.length)];
270+
const platformName = findPlatformName(message.platform);
271+
const platformClass = findPlatformClass(message.platform);
272+
273+
const card = document.createElement('div');
274+
card.className = `msg-card ${platformClass}`;
275+
276+
card.innerHTML = `
277+
<span class="platform-badge">${platformName}</span>
278+
<div class="msg-content">
279+
<div class="user-info">
280+
<span class="user-name">@${message.userName}</span>
281+
</div>
282+
<div class="message-text">${message.content}</div>
283+
</div>
284+
`;
285+
286+
container.appendChild(card);
287+
288+
// 自动滚动到底部
289+
container.scrollTo({
290+
top: container.scrollHeight,
291+
behavior: 'smooth'
292+
});
293+
294+
// 保持页面性能,删除多余节点(超过100条时)
295+
if (container.children.length > 1000) {
296+
container.removeChild(container.firstChild);
297+
}
298+
}
299+
300+
// 获取WSS连接地址
301+
async function getWssUrl() {
302+
try {
303+
let baseUrl = 'https://iseekfree.com/hero';
304+
const response = await fetch(`${baseUrl}/api/hawk/aggregate/emitlink`, {
305+
method: 'POST',
306+
headers: {
307+
'Content-Type': 'application/json',
308+
},
309+
body: JSON.stringify({
310+
visitToken: visitToken
311+
})
312+
});
313+
314+
if(!response.ok){
315+
throw new Error(`HTTP error! status: ${response.status}`);
316+
}
317+
318+
const result = await response.json();
319+
//alert("getWssUrl resp:" + JSON.stringify(result));
320+
if (result.code === 0 && result.data) {
321+
console.log('WSS URL retrieved successfully:', result.data);
322+
return result.data;
323+
} else {
324+
throw new Error(`Failed to get WSS URL. Server response: ${result.msg || 'Unknown error'}`);
325+
}
326+
} catch (error) {
327+
console.error('Error getting WSS URL:', error);
328+
//alert("getWssUrl error:" + error);
329+
return null;
330+
}
331+
}
332+
333+
334+
// 初始化WebSocket连接
335+
async function startAggregateWebSocket() {
336+
console.log("Starting aggregate WebSocket connection...");
337+
if(!visitToken){
338+
console.error('Missing visitToken for WebSocket connection');
339+
return;
340+
}
341+
342+
// 获取WSS连接地址
343+
const wssUrl = await getWssUrl();
344+
if (!wssUrl) {
345+
console.error('Failed to retrieve WSS URL');
346+
return;
347+
}
348+
349+
// 关闭现有连接(如果存在)
350+
if(ws){
351+
ws.close();
352+
}
353+
354+
// 创建WebSocket连接
355+
ws = new ReconnectingWebSocket(wssUrl, null, {
356+
connectionTimeout: 3000,
357+
maxRetries: 50,
358+
debug: false
359+
});
360+
ws.onopen = function () {
361+
console.log('WebSocket connection opened');
362+
ws.send(JSON.stringify({"cmd":4004}));//加入房间
363+
};
364+
ws.onmessage = function (event) {
365+
//console.log('WebSocket message from server:', event.data);
366+
try{
367+
const data = JSON.parse(event.data);
368+
if(data.cmd == 4014){
369+
var message = data.payload;
370+
//var message = {id:item.id,'icon':formatIcon(item.type),'roomId':route.query.roomId,platform: route.query.platform,roomName:route.query.roomName,type:item.type,userId:item.userId,'userName':item.userName,userAvatar:item.userAvatar,userShortId:item.userShortId,userGender:item.userGender,userFollowers:item.userFollowers,userFollowing:item.userFollowing,userPosts:item.userPosts,userCountry:item.userCountry,userProvince:item.userProvince,userCity:item.userCity,userDescription:item.userDescription,'content':item.content,'statusText':'🔄️'};
371+
addMessage(message);
372+
}
373+
374+
}catch (e) {
375+
console.error('WebSocket parsing message error:', e);
376+
}
377+
};
378+
ws.onclose = function () {
379+
console.log('WebSocket connection closed');
380+
};
381+
}
382+
//初始化
383+
startAggregateWebSocket();
384+
385+
function heartBeat(){
386+
if(ws && ws.readyState === ReconnectingWebSocket.OPEN){
387+
ws.send(JSON.stringify({"cmd":8002}));
388+
}
389+
}
390+
setInterval(() => {
391+
heartBeat();
392+
},30000);
393+
</script>
394+
</body>
395+
</html>

0 commit comments

Comments
 (0)