@quantabit/live-sdk 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs ADDED
@@ -0,0 +1,1549 @@
1
+ 'use strict';
2
+
3
+ var sdkConfig = require('@quantabit/sdk-config');
4
+ var React = require('react');
5
+
6
+ /**
7
+ * Live SDK - API 客户端
8
+ * 直播系统后端接口封装
9
+ *
10
+ * 使用 BaseApiClient 基类,集成统一的配置管理、Token 和错误处理
11
+ * 支持直播间管理、弹幕礼物、互动功能、WebSocket等
12
+ */
13
+
14
+
15
+ /**
16
+ * 直播 API 客户端
17
+ */
18
+ class LiveClient extends sdkConfig.BaseApiClient {
19
+ constructor(config = {}) {
20
+ super('/live', config);
21
+
22
+ // 初始化 WebSocket 基础 URL (在 BaseApiClient 初始化后 this.baseUrl 已就绪)
23
+ if (config.wsBaseUrl) {
24
+ this.wsBaseUrl = config.wsBaseUrl;
25
+ } else {
26
+ // 从全局配置获取,或者通过 baseUrl 转换
27
+ // 这里的 this.baseUrl 已经是完整的 API 前缀,比如 /api/v1/live 或 http://.../api/v1/live
28
+ this.wsBaseUrl = (this.baseUrl || '').replace('http', 'ws');
29
+ // 注意: 这里简单替换 http 为 ws。如果 baseUrl 只是 relative 路径如 /api/v1/live
30
+ // websocket 将会在连接时根据 window.location 使用相对路径或需要提供绝对路径。
31
+ }
32
+ this.onError = config.onError;
33
+
34
+ // WebSocket 相关
35
+ this.ws = null;
36
+ this.currentRoomId = null;
37
+ this.reconnectAttempts = 0;
38
+ this.maxReconnectAttempts = 5;
39
+ this.reconnectDelay = 3000;
40
+
41
+ // 事件监听器
42
+ this.listeners = {
43
+ message: [],
44
+ gift: [],
45
+ enter: [],
46
+ leave: [],
47
+ status: [],
48
+ like: [],
49
+ follow: [],
50
+ share: [],
51
+ error: [],
52
+ reconnect: []
53
+ };
54
+ }
55
+
56
+ // ============ 直播间管理 ============
57
+
58
+ /**
59
+ * 获取直播列表
60
+ * @param {Object} options - 查询选项
61
+ */
62
+ async getLiveRooms(options = {}) {
63
+ const {
64
+ page = 1,
65
+ pageSize = 20,
66
+ type,
67
+ status,
68
+ category
69
+ } = options;
70
+ const params = {
71
+ page,
72
+ page_size: pageSize
73
+ };
74
+ if (type) params.type = type;
75
+ if (status) params.status = status;
76
+ if (category) params.category = category;
77
+ return this.get('/rooms', params);
78
+ }
79
+
80
+ /**
81
+ * 获取直播间详情
82
+ * @param {string} roomId - 直播间 ID
83
+ */
84
+ async getRoomInfo(roomId) {
85
+ return this.get(`/rooms/${roomId}`);
86
+ }
87
+
88
+ /**
89
+ * 获取推荐直播
90
+ * @param {number} limit - 数量限制
91
+ */
92
+ async getRecommendedRooms(limit = 10) {
93
+ return this.get('/rooms/recommended', {
94
+ limit
95
+ });
96
+ }
97
+
98
+ /**
99
+ * 获取热门直播
100
+ * @param {number} limit - 数量限制
101
+ */
102
+ async getHotRooms(limit = 20) {
103
+ return this.get('/rooms/hot', {
104
+ limit
105
+ });
106
+ }
107
+
108
+ /**
109
+ * 搜索直播间
110
+ * @param {string} keyword - 关键词
111
+ * @param {Object} params - 搜索参数
112
+ */
113
+ async searchRooms(keyword, params = {}) {
114
+ return this.get('/rooms/search', {
115
+ q: keyword,
116
+ ...params
117
+ });
118
+ }
119
+
120
+ /**
121
+ * 获取直播分类
122
+ */
123
+ async getCategories() {
124
+ return this.get('/categories');
125
+ }
126
+
127
+ /**
128
+ * 创建直播间
129
+ * @param {Object} room - 直播间数据
130
+ */
131
+ async createRoom(room) {
132
+ return this.post('/rooms', room);
133
+ }
134
+
135
+ /**
136
+ * 更新直播间信息
137
+ * @param {string} roomId - 直播间 ID
138
+ * @param {Object} updates - 更新数据
139
+ */
140
+ async updateRoom(roomId, updates) {
141
+ return this.put(`/rooms/${roomId}`, updates);
142
+ }
143
+
144
+ /**
145
+ * 开始直播
146
+ * @param {string} roomId - 直播间 ID
147
+ */
148
+ async startLive(roomId) {
149
+ return this.post(`/rooms/${roomId}/start`);
150
+ }
151
+
152
+ /**
153
+ * 结束直播
154
+ * @param {string} roomId - 直播间 ID
155
+ */
156
+ async endLive(roomId) {
157
+ return this.post(`/rooms/${roomId}/end`);
158
+ }
159
+
160
+ /**
161
+ * 获取推流地址
162
+ * @param {string} roomId - 直播间 ID
163
+ */
164
+ async getPushUrl(roomId) {
165
+ return this.get(`/rooms/${roomId}/push-url`);
166
+ }
167
+
168
+ /**
169
+ * 获取拉流地址
170
+ * @param {string} roomId - 直播间 ID
171
+ */
172
+ async getPullUrl(roomId) {
173
+ return this.get(`/rooms/${roomId}/pull-url`);
174
+ }
175
+
176
+ // ============ 进入/离开 ============
177
+
178
+ /**
179
+ * 进入直播间
180
+ * @param {string} roomId - 直播间 ID
181
+ */
182
+ async enterRoom(roomId) {
183
+ this.currentRoomId = roomId;
184
+ await this.post(`/rooms/${roomId}/enter`);
185
+ this.connectWebSocket(roomId);
186
+ }
187
+
188
+ /**
189
+ * 离开直播间
190
+ * @param {string} roomId - 直播间 ID
191
+ */
192
+ async leaveRoom(roomId) {
193
+ await this.post(`/rooms/${roomId}/leave`);
194
+ this.disconnectWebSocket();
195
+ this.currentRoomId = null;
196
+ }
197
+
198
+ /**
199
+ * 获取直播间观众列表
200
+ * @param {string} roomId - 直播间 ID
201
+ * @param {Object} params - 分页参数
202
+ */
203
+ async getViewers(roomId, params = {}) {
204
+ return this.get(`/rooms/${roomId}/viewers`, params);
205
+ }
206
+
207
+ /**
208
+ * 获取在线人数
209
+ * @param {string} roomId - 直播间 ID
210
+ */
211
+ async getOnlineCount(roomId) {
212
+ return this.get(`/rooms/${roomId}/online-count`);
213
+ }
214
+
215
+ // ============ WebSocket ============
216
+
217
+ /**
218
+ * 连接 WebSocket
219
+ * @param {string} roomId - 直播间 ID
220
+ */
221
+ connectWebSocket(roomId) {
222
+ if (this.ws) {
223
+ this.disconnectWebSocket();
224
+ }
225
+
226
+ // 如果 wsBaseUrl 配置里有 live 则不重复加
227
+ // 假设 wsBaseUrl 是 ws://.../api/v1/live
228
+ this.wsBaseUrl.endsWith('/live') ? this.wsBaseUrl.slice(0, -5) : this.wsBaseUrl;
229
+ // 如果 wsBaseUrl 为空或者相对路径,这可能需要更健壮的处理,但这里保持原有逻辑结构
230
+ const wsUrl = `${this.wsBaseUrl}/ws/${roomId}`;
231
+ const token = this.getToken();
232
+ this.ws = new WebSocket(token ? `${wsUrl}?token=${token}` : wsUrl);
233
+ this.ws.onopen = () => {
234
+ this.reconnectAttempts = 0;
235
+ this.emit('status', {
236
+ type: 'connected',
237
+ roomId
238
+ });
239
+ };
240
+ this.ws.onmessage = event => {
241
+ try {
242
+ const data = JSON.parse(event.data);
243
+ this.handleMessage(data);
244
+ } catch (e) {
245
+ console.error('Failed to parse message:', e);
246
+ }
247
+ };
248
+ this.ws.onclose = event => {
249
+ this.emit('status', {
250
+ type: 'disconnected',
251
+ code: event.code
252
+ });
253
+ if (this.currentRoomId && this.reconnectAttempts < this.maxReconnectAttempts) {
254
+ this.reconnectAttempts++;
255
+ this.emit('reconnect', {
256
+ attempt: this.reconnectAttempts
257
+ });
258
+ setTimeout(() => this.connectWebSocket(roomId), this.reconnectDelay);
259
+ }
260
+ };
261
+ this.ws.onerror = error => {
262
+ this.emit('error', error);
263
+ };
264
+ }
265
+
266
+ /**
267
+ * 断开 WebSocket
268
+ */
269
+ disconnectWebSocket() {
270
+ if (this.ws) {
271
+ this.ws.close();
272
+ this.ws = null;
273
+ }
274
+ }
275
+
276
+ /**
277
+ * 处理 WebSocket 消息
278
+ * @param {Object} data - 消息数据
279
+ */
280
+ handleMessage(data) {
281
+ switch (data.type) {
282
+ case 'chat':
283
+ this.emit('message', data);
284
+ break;
285
+ case 'gift':
286
+ this.emit('gift', data);
287
+ break;
288
+ case 'enter':
289
+ this.emit('enter', data);
290
+ break;
291
+ case 'leave':
292
+ this.emit('leave', data);
293
+ break;
294
+ case 'like':
295
+ this.emit('like', data);
296
+ break;
297
+ case 'follow':
298
+ this.emit('follow', data);
299
+ break;
300
+ case 'share':
301
+ this.emit('share', data);
302
+ break;
303
+ case 'status':
304
+ this.emit('status', data);
305
+ break;
306
+ default:
307
+ this.emit(data.type, data);
308
+ }
309
+ }
310
+
311
+ // ============ 弹幕互动 ============
312
+
313
+ /**
314
+ * 发送弹幕
315
+ * @param {string} content - 弹幕内容
316
+ * @param {Object} options - 弹幕选项
317
+ */
318
+ sendMessage(content, options = {}) {
319
+ if (!this.ws || !this.currentRoomId) return;
320
+ this.ws.send(JSON.stringify({
321
+ type: 'chat',
322
+ content,
323
+ ...options
324
+ }));
325
+ }
326
+
327
+ /**
328
+ * 点赞
329
+ */
330
+ sendLike() {
331
+ if (!this.ws || !this.currentRoomId) return;
332
+ this.ws.send(JSON.stringify({
333
+ type: 'like'
334
+ }));
335
+ }
336
+
337
+ /**
338
+ * 发送弹幕(HTTP)
339
+ * @param {string} roomId - 直播间 ID
340
+ * @param {string} content - 弹幕内容
341
+ */
342
+ async sendDanmaku(roomId, content) {
343
+ return this.post(`/rooms/${roomId}/danmaku`, {
344
+ content
345
+ });
346
+ }
347
+
348
+ /**
349
+ * 获取历史弹幕
350
+ * @param {string} roomId - 直播间 ID
351
+ * @param {Object} params - 查询参数
352
+ */
353
+ async getDanmakuHistory(roomId, params = {}) {
354
+ return this.get(`/rooms/${roomId}/danmaku`, params);
355
+ }
356
+
357
+ // ============ 礼物 ============
358
+
359
+ /**
360
+ * 发送礼物
361
+ * @param {string} roomId - 直播间 ID
362
+ * @param {string} giftId - 礼物 ID
363
+ * @param {number} count - 数量
364
+ */
365
+ async sendGift(roomId, giftId, count = 1) {
366
+ return this.post(`/rooms/${roomId}/gift`, {
367
+ gift_id: giftId,
368
+ count
369
+ });
370
+ }
371
+
372
+ /**
373
+ * 获取礼物列表
374
+ */
375
+ async getGifts() {
376
+ return this.get('/gifts');
377
+ }
378
+
379
+ /**
380
+ * 获取礼物榜
381
+ * @param {string} roomId - 直播间 ID
382
+ * @param {string} period - 周期
383
+ */
384
+ async getGiftRanking(roomId, period = 'day') {
385
+ return this.get(`/rooms/${roomId}/gift-ranking`, {
386
+ period
387
+ });
388
+ }
389
+
390
+ /**
391
+ * 获取贡献榜
392
+ * @param {string} roomId - 直播间 ID
393
+ */
394
+ async getContributionRanking(roomId) {
395
+ return this.get(`/rooms/${roomId}/contribution-ranking`);
396
+ }
397
+
398
+ // ============ 关注/订阅 ============
399
+
400
+ /**
401
+ * 关注主播
402
+ * @param {string} roomId - 直播间 ID
403
+ */
404
+ async followAnchor(roomId) {
405
+ return this.post(`/rooms/${roomId}/follow`);
406
+ }
407
+
408
+ /**
409
+ * 取消关注
410
+ * @param {string} roomId - 直播间 ID
411
+ */
412
+ async unfollowAnchor(roomId) {
413
+ return this.delete(`/rooms/${roomId}/follow`);
414
+ }
415
+
416
+ /**
417
+ * 获取关注的主播
418
+ * @param {Object} params - 分页参数
419
+ */
420
+ async getFollowedAnchors(params = {}) {
421
+ return this.get('/following', params);
422
+ }
423
+
424
+ // ============ 直播预约 ============
425
+
426
+ /**
427
+ * 预约直播
428
+ * @param {string} roomId - 直播间 ID
429
+ */
430
+ async reserveLive(roomId) {
431
+ return this.post(`/rooms/${roomId}/reserve`);
432
+ }
433
+
434
+ /**
435
+ * 取消预约
436
+ * @param {string} roomId - 直播间 ID
437
+ */
438
+ async cancelReservation(roomId) {
439
+ return this.delete(`/rooms/${roomId}/reserve`);
440
+ }
441
+
442
+ /**
443
+ * 获取我的预约
444
+ */
445
+ async getMyReservations() {
446
+ return this.get('/reservations');
447
+ }
448
+
449
+ // ============ 回放 ============
450
+
451
+ /**
452
+ * 获取直播回放
453
+ * @param {string} roomId - 直播间 ID
454
+ */
455
+ async getPlayback(roomId) {
456
+ return this.get(`/rooms/${roomId}/playback`);
457
+ }
458
+
459
+ /**
460
+ * 获取主播回放列表
461
+ * @param {string} anchorId - 主播 ID
462
+ * @param {Object} params - 分页参数
463
+ */
464
+ async getPlaybacks(anchorId, params = {}) {
465
+ return this.get(`/anchors/${anchorId}/playbacks`, params);
466
+ }
467
+
468
+ // ============ 管理功能 ============
469
+
470
+ /**
471
+ * 禁言用户
472
+ * @param {string} roomId - 直播间 ID
473
+ * @param {string} userId - 用户 ID
474
+ * @param {number} duration - 禁言时长(秒)
475
+ */
476
+ async muteUser(roomId, userId, duration) {
477
+ return this.post(`/rooms/${roomId}/mute`, {
478
+ user_id: userId,
479
+ duration
480
+ });
481
+ }
482
+
483
+ /**
484
+ * 踢出用户
485
+ * @param {string} roomId - 直播间 ID
486
+ * @param {string} userId - 用户 ID
487
+ */
488
+ async kickUser(roomId, userId) {
489
+ return this.post(`/rooms/${roomId}/kick`, {
490
+ user_id: userId
491
+ });
492
+ }
493
+
494
+ /**
495
+ * 设置管理员
496
+ * @param {string} roomId - 直播间 ID
497
+ * @param {string} userId - 用户 ID
498
+ * @param {boolean} isAdmin - 是否设为管理员
499
+ */
500
+ async setAdmin(roomId, userId, isAdmin) {
501
+ return this.put(`/rooms/${roomId}/admin`, {
502
+ user_id: userId,
503
+ is_admin: isAdmin
504
+ });
505
+ }
506
+
507
+ // ============ 事件系统 ============
508
+
509
+ /**
510
+ * 注册事件监听
511
+ * @param {string} event - 事件名
512
+ * @param {Function} callback - 回调函数
513
+ */
514
+ on(event, callback) {
515
+ if (!this.listeners[event]) {
516
+ this.listeners[event] = [];
517
+ }
518
+ this.listeners[event].push(callback);
519
+ return () => this.off(event, callback);
520
+ }
521
+
522
+ /**
523
+ * 移除事件监听
524
+ * @param {string} event - 事件名
525
+ * @param {Function} callback - 回调函数
526
+ */
527
+ off(event, callback) {
528
+ if (this.listeners[event]) {
529
+ this.listeners[event] = this.listeners[event].filter(cb => cb !== callback);
530
+ }
531
+ }
532
+
533
+ /**
534
+ * 触发事件
535
+ * @param {string} event - 事件名
536
+ * @param {*} data - 数据
537
+ */
538
+ emit(event, data) {
539
+ if (this.listeners[event]) {
540
+ this.listeners[event].forEach(cb => cb(data));
541
+ }
542
+ }
543
+ }
544
+
545
+ // 创建默认实例
546
+ const liveClient = new LiveClient();
547
+
548
+ /**
549
+ * Live SDK - 类型定义
550
+ */
551
+
552
+ // 直播状态
553
+ const LiveStatus = {
554
+ SCHEDULED: 'scheduled',
555
+ LIVE: 'live',
556
+ PAUSED: 'paused',
557
+ ENDED: 'ended'
558
+ };
559
+
560
+ // 直播类型
561
+ const LiveType = {
562
+ NORMAL: 'normal',
563
+ // 普通直播
564
+ GAME: 'game',
565
+ // 游戏直播
566
+ SHOPPING: 'shopping',
567
+ // 带货直播
568
+ EDUCATION: 'education' // 教育直播
569
+ };
570
+
571
+ // 消息类型
572
+ const LiveMessageType = {
573
+ CHAT: 'chat',
574
+ // 聊天
575
+ GIFT: 'gift',
576
+ // 礼物
577
+ ENTER: 'enter',
578
+ // 进入
579
+ LEAVE: 'leave',
580
+ // 离开
581
+ FOLLOW: 'follow',
582
+ // 关注
583
+ SHARE: 'share',
584
+ // 分享
585
+ SYSTEM: 'system' // 系统消息
586
+ };
587
+
588
+ // 礼物类型
589
+ const GiftType = {
590
+ FREE: 'free',
591
+ // 免费
592
+ PAID: 'paid',
593
+ // 付费
594
+ SPECIAL: 'special' // 特效
595
+ };
596
+
597
+ /**
598
+ * Live SDK - 国际化
599
+ */
600
+
601
+ const SUPPORTED_LANGUAGES = ['en', 'zh', 'ja', 'ko'];
602
+ const messages = {
603
+ zh: {
604
+ // 直播状态
605
+ live: '直播中',
606
+ offline: '已结束',
607
+ scheduled: '预约中',
608
+ starting: '即将开始',
609
+ // 直播间
610
+ liveRoom: '直播间',
611
+ follow: '关注',
612
+ followed: '已关注',
613
+ viewers: '观看',
614
+ likes: '点赞',
615
+ // 弹幕
616
+ sendDanmaku: '发送弹幕...',
617
+ send: '发送',
618
+ // 礼物
619
+ gifts: '礼物',
620
+ sendGift: '送礼物',
621
+ freeGifts: '免费礼物',
622
+ paidGifts: '付费礼物',
623
+ // 互动
624
+ enterRoom: '进入直播间',
625
+ leaveRoom: '离开直播间',
626
+ justEntered: '刚刚进入直播间',
627
+ welcome: '欢迎',
628
+ // 主播
629
+ startLive: '开始直播',
630
+ endLive: '结束直播',
631
+ duration: '直播时长',
632
+ earnings: '收益',
633
+ // 连麦
634
+ requestMic: '申请连麦',
635
+ cancelRequest: '取消申请',
636
+ onMic: '连麦中',
637
+ // PK
638
+ pk: 'PK',
639
+ pkInvite: '发起PK',
640
+ pkScore: 'PK分数',
641
+ win: '胜利',
642
+ lose: '失败',
643
+ draw: '平局'
644
+ },
645
+ ja: {
646
+ // 直播状态
647
+ live: '配信中',
648
+ offline: '終了',
649
+ scheduled: '予約中',
650
+ starting: 'まもなく開始',
651
+ // 直播间
652
+ liveRoom: '配信ルーム',
653
+ follow: 'フォロー',
654
+ followed: 'フォロー済み',
655
+ viewers: '視聴',
656
+ likes: 'いいね',
657
+ // 弹幕
658
+ sendDanmaku: 'コメントを送信...',
659
+ send: '送信',
660
+ // 礼物
661
+ gifts: 'ギフト',
662
+ sendGift: 'ギフトを贈る',
663
+ freeGifts: '無料ギフト',
664
+ paidGifts: '有料ギフト',
665
+ // 互动
666
+ enterRoom: '配信ルームに入る',
667
+ leaveRoom: '配信ルームを退出する',
668
+ justEntered: 'が配信ルームに入りました',
669
+ welcome: '歓迎',
670
+ // 主播
671
+ startLive: '配信開始',
672
+ endLive: '配信終了',
673
+ duration: '配信時間',
674
+ earnings: '収益',
675
+ // 连麦
676
+ requestMic: 'コラボ申請',
677
+ cancelRequest: '申請取消',
678
+ onMic: 'コラボ中',
679
+ // PK
680
+ pk: 'PK',
681
+ pkInvite: 'PK開始',
682
+ pkScore: 'PKスコア',
683
+ win: '勝利',
684
+ lose: '敗北',
685
+ draw: '引き分け'
686
+ },
687
+ ko: {
688
+ // 直播状态
689
+ live: '라이브 중',
690
+ offline: '종료됨',
691
+ scheduled: '예약 중',
692
+ starting: '시작 예정',
693
+ // 直播间
694
+ liveRoom: '라이브 룸',
695
+ follow: '팔로우',
696
+ followed: '팔로우함',
697
+ viewers: '시청',
698
+ likes: '좋아요',
699
+ // 弹幕
700
+ sendDanmaku: '댓글 보내기...',
701
+ send: '보내기',
702
+ // 礼物
703
+ gifts: '선물',
704
+ sendGift: '선물 보내기',
705
+ freeGifts: '무료 선물',
706
+ paidGifts: '유료 선물',
707
+ // 互动
708
+ enterRoom: '라이브 룸 입장',
709
+ leaveRoom: '라이브 룸 퇴장',
710
+ justEntered: '방금 라이브 룸에 입장했습니다',
711
+ welcome: '환영합니다',
712
+ // 主播
713
+ startLive: '라이브 시작',
714
+ endLive: '라이브 종료',
715
+ duration: '라이브 지속 시간',
716
+ earnings: '수익',
717
+ // 连麦
718
+ requestMic: '연결 신청',
719
+ cancelRequest: '신청 취소',
720
+ onMic: '연결 중',
721
+ // PK
722
+ pk: 'PK',
723
+ pkInvite: 'PK 시작',
724
+ pkScore: 'PK 점수',
725
+ win: '승리',
726
+ lose: '패배',
727
+ draw: '무승부'
728
+ },
729
+ en: {
730
+ live: 'Live',
731
+ offline: 'Offline',
732
+ scheduled: 'Scheduled',
733
+ starting: 'Starting Soon',
734
+ liveRoom: 'Live Room',
735
+ follow: 'Follow',
736
+ followed: 'Following',
737
+ viewers: 'Viewers',
738
+ likes: 'Likes',
739
+ sendDanmaku: 'Send a message...',
740
+ send: 'Send',
741
+ gifts: 'Gifts',
742
+ sendGift: 'Send Gift',
743
+ freeGifts: 'Free',
744
+ paidGifts: 'Premium',
745
+ enterRoom: 'Enter Room',
746
+ leaveRoom: 'Leave Room',
747
+ justEntered: 'just entered',
748
+ welcome: 'Welcome',
749
+ startLive: 'Go Live',
750
+ endLive: 'End Live',
751
+ duration: 'Duration',
752
+ earnings: 'Earnings',
753
+ requestMic: 'Request Mic',
754
+ cancelRequest: 'Cancel',
755
+ onMic: 'On Mic',
756
+ pk: 'PK',
757
+ pkInvite: 'Start PK',
758
+ pkScore: 'PK Score',
759
+ win: 'Win',
760
+ lose: 'Lose',
761
+ draw: 'Draw'
762
+ }
763
+ };
764
+ let currentLanguage = 'zh';
765
+ function setLanguage(lang) {
766
+ if (SUPPORTED_LANGUAGES.includes(lang)) currentLanguage = lang;
767
+ }
768
+ function getLanguage() {
769
+ return currentLanguage;
770
+ }
771
+ function t(key) {
772
+ return (messages[currentLanguage] || messages.en)[key] || key;
773
+ }
774
+
775
+ /**
776
+ * Live SDK - React Hooks
777
+ */
778
+
779
+
780
+ /**
781
+ * useLiveRooms - 直播列表Hook
782
+ */
783
+ function useLiveRooms(options = {}) {
784
+ const [rooms, setRooms] = React.useState([]);
785
+ const [loading, setLoading] = React.useState(false);
786
+ const [page, setPage] = React.useState(1);
787
+ const [hasMore, setHasMore] = React.useState(true);
788
+ const loadRooms = React.useCallback(async (reset = false) => {
789
+ const currentPage = reset ? 1 : page;
790
+ setLoading(true);
791
+ try {
792
+ const result = await liveClient.getLiveRooms({
793
+ ...options,
794
+ page: currentPage,
795
+ pageSize: 20
796
+ });
797
+ const newRooms = result.rooms || [];
798
+ setRooms(prev => reset ? newRooms : [...prev, ...newRooms]);
799
+ setHasMore(newRooms.length >= 20);
800
+ if (reset) setPage(1);
801
+ } finally {
802
+ setLoading(false);
803
+ }
804
+ }, [options.type, options.status, page]);
805
+ const loadMore = React.useCallback(() => {
806
+ if (hasMore && !loading) {
807
+ setPage(p => p + 1);
808
+ }
809
+ }, [hasMore, loading]);
810
+ React.useEffect(() => {
811
+ loadRooms(true);
812
+ }, [options.type, options.status]);
813
+ React.useEffect(() => {
814
+ if (page > 1) loadRooms();
815
+ }, [page]);
816
+ return {
817
+ rooms,
818
+ loading,
819
+ hasMore,
820
+ loadMore,
821
+ refresh: () => loadRooms(true)
822
+ };
823
+ }
824
+
825
+ /**
826
+ * useLiveRoom - 单个直播间Hook
827
+ */
828
+ function useLiveRoom(roomId) {
829
+ const [room, setRoom] = React.useState(null);
830
+ const [messages, setMessages] = React.useState([]);
831
+ const [gifts, setGifts] = React.useState([]);
832
+ const [viewers, setViewers] = React.useState(0);
833
+ const [loading, setLoading] = React.useState(false);
834
+ const [isInRoom, setIsInRoom] = React.useState(false);
835
+
836
+ // 加载房间信息
837
+ const loadRoom = React.useCallback(async () => {
838
+ if (!roomId) return;
839
+ setLoading(true);
840
+ try {
841
+ const result = await liveClient.getRoomInfo(roomId);
842
+ setRoom(result);
843
+ setViewers(result.viewer_count || 0);
844
+ } finally {
845
+ setLoading(false);
846
+ }
847
+ }, [roomId]);
848
+
849
+ // 进入直播间
850
+ const enterRoom = React.useCallback(async () => {
851
+ if (!roomId || isInRoom) return;
852
+ try {
853
+ await liveClient.enterRoom(roomId);
854
+ setIsInRoom(true);
855
+ } catch (e) {
856
+ console.error('Failed to enter room', e);
857
+ }
858
+ }, [roomId, isInRoom]);
859
+
860
+ // 离开直播间
861
+ const leaveRoom = React.useCallback(async () => {
862
+ if (!roomId || !isInRoom) return;
863
+ try {
864
+ await liveClient.leaveRoom(roomId);
865
+ setIsInRoom(false);
866
+ } catch (e) {
867
+ console.error('Failed to leave room', e);
868
+ }
869
+ }, [roomId, isInRoom]);
870
+
871
+ // 发送弹幕
872
+ const sendMessage = React.useCallback(content => {
873
+ if (!isInRoom || !content.trim()) return;
874
+ liveClient.sendMessage(content);
875
+ }, [isInRoom]);
876
+
877
+ // 发送礼物
878
+ const sendGift = React.useCallback(async (giftId, count = 1) => {
879
+ if (!roomId) return false;
880
+ try {
881
+ await liveClient.sendGift(roomId, giftId, count);
882
+ return true;
883
+ } catch (e) {
884
+ return false;
885
+ }
886
+ }, [roomId]);
887
+
888
+ // 监听事件
889
+ React.useEffect(() => {
890
+ if (!roomId) return;
891
+ const unsubMessage = liveClient.on('message', data => {
892
+ setMessages(prev => [...prev.slice(-100), data]); // 保留最近100条
893
+ });
894
+ const unsubGift = liveClient.on('gift', data => {
895
+ setGifts(prev => [...prev.slice(-20), data]); // 保留最近20条
896
+ });
897
+ const unsubEnter = liveClient.on('enter', data => {
898
+ setViewers(prev => prev + 1);
899
+ setMessages(prev => [...prev.slice(-100), {
900
+ type: 'enter',
901
+ ...data
902
+ }]);
903
+ });
904
+ const unsubLeave = liveClient.on('leave', () => {
905
+ setViewers(prev => Math.max(0, prev - 1));
906
+ });
907
+ return () => {
908
+ unsubMessage();
909
+ unsubGift();
910
+ unsubEnter();
911
+ unsubLeave();
912
+ };
913
+ }, [roomId]);
914
+
915
+ // 初始化
916
+ React.useEffect(() => {
917
+ loadRoom();
918
+ return () => {
919
+ if (isInRoom) leaveRoom();
920
+ };
921
+ }, [roomId]);
922
+ return {
923
+ room,
924
+ messages,
925
+ gifts,
926
+ viewers,
927
+ loading,
928
+ isInRoom,
929
+ enterRoom,
930
+ leaveRoom,
931
+ sendMessage,
932
+ sendGift,
933
+ refresh: loadRoom
934
+ };
935
+ }
936
+
937
+ /**
938
+ * useGifts - 礼物列表Hook
939
+ */
940
+ function useGifts() {
941
+ const [gifts, setGifts] = React.useState([]);
942
+ const [loading, setLoading] = React.useState(false);
943
+ const loadGifts = React.useCallback(async () => {
944
+ setLoading(true);
945
+ try {
946
+ const result = await liveClient.getGifts();
947
+ setGifts(result.gifts || []);
948
+ } finally {
949
+ setLoading(false);
950
+ }
951
+ }, []);
952
+ React.useEffect(() => {
953
+ loadGifts();
954
+ }, []);
955
+ return {
956
+ gifts,
957
+ loading,
958
+ refresh: loadGifts
959
+ };
960
+ }
961
+
962
+ /**
963
+ * Live SDK - 直播间组件
964
+ */
965
+
966
+
967
+ /**
968
+ * 弹幕消息
969
+ */
970
+ function DanmakuMessage({
971
+ message,
972
+ isGift = false
973
+ }) {
974
+ if (message.type === 'enter') {
975
+ return /*#__PURE__*/React.createElement("div", {
976
+ className: "eco-live-danmaku enter"
977
+ }, /*#__PURE__*/React.createElement("span", {
978
+ className: "eco-live-user"
979
+ }, message.user?.name), /*#__PURE__*/React.createElement("span", {
980
+ className: "eco-live-text"
981
+ }, t('justEntered')));
982
+ }
983
+ if (isGift || message.type === 'gift') {
984
+ return /*#__PURE__*/React.createElement("div", {
985
+ className: "eco-live-danmaku gift"
986
+ }, /*#__PURE__*/React.createElement("span", {
987
+ className: "eco-live-user"
988
+ }, message.user?.name), /*#__PURE__*/React.createElement("span", {
989
+ className: "eco-live-text"
990
+ }, "\u9001\u51FA ", /*#__PURE__*/React.createElement("span", {
991
+ className: "eco-live-gift-icon"
992
+ }, message.gift?.icon), message.gift?.name, " x", message.count));
993
+ }
994
+ return /*#__PURE__*/React.createElement("div", {
995
+ className: "eco-live-danmaku"
996
+ }, /*#__PURE__*/React.createElement("span", {
997
+ className: "eco-live-user"
998
+ }, message.user?.name, ":"), /*#__PURE__*/React.createElement("span", {
999
+ className: "eco-live-text"
1000
+ }, message.content));
1001
+ }
1002
+
1003
+ /**
1004
+ * 弹幕列表
1005
+ */
1006
+ function DanmakuList({
1007
+ messages = [],
1008
+ maxHeight = 300
1009
+ }) {
1010
+ const containerRef = React.useRef(null);
1011
+ React.useEffect(() => {
1012
+ if (containerRef.current) {
1013
+ containerRef.current.scrollTop = containerRef.current.scrollHeight;
1014
+ }
1015
+ }, [messages.length]);
1016
+ return /*#__PURE__*/React.createElement("div", {
1017
+ ref: containerRef,
1018
+ className: "eco-live-danmaku-list",
1019
+ style: {
1020
+ maxHeight
1021
+ }
1022
+ }, messages.map((msg, index) => /*#__PURE__*/React.createElement(DanmakuMessage, {
1023
+ key: msg.id || index,
1024
+ message: msg
1025
+ })));
1026
+ }
1027
+
1028
+ /**
1029
+ * 弹幕输入框
1030
+ */
1031
+ function DanmakuInput({
1032
+ onSend,
1033
+ disabled = false
1034
+ }) {
1035
+ const [text, setText] = React.useState('');
1036
+ const handleSend = () => {
1037
+ if (text.trim() && !disabled) {
1038
+ onSend(text.trim());
1039
+ setText('');
1040
+ }
1041
+ };
1042
+ const handleKeyDown = e => {
1043
+ if (e.key === 'Enter') {
1044
+ e.preventDefault();
1045
+ handleSend();
1046
+ }
1047
+ };
1048
+ return /*#__PURE__*/React.createElement("div", {
1049
+ className: "eco-live-danmaku-input"
1050
+ }, /*#__PURE__*/React.createElement("input", {
1051
+ type: "text",
1052
+ value: text,
1053
+ onChange: e => setText(e.target.value),
1054
+ onKeyDown: handleKeyDown,
1055
+ placeholder: t('sendDanmaku'),
1056
+ disabled: disabled
1057
+ }), /*#__PURE__*/React.createElement("button", {
1058
+ onClick: handleSend,
1059
+ disabled: disabled || !text.trim()
1060
+ }, t('send')));
1061
+ }
1062
+
1063
+ /**
1064
+ * 礼物面板
1065
+ */
1066
+ function GiftPanel$1({
1067
+ gifts = [],
1068
+ onSend,
1069
+ selectedId,
1070
+ onSelect
1071
+ }) {
1072
+ const [count, setCount] = React.useState(1);
1073
+ const handleSend = () => {
1074
+ if (selectedId && onSend) {
1075
+ onSend(selectedId, count);
1076
+ setCount(1);
1077
+ }
1078
+ };
1079
+ return /*#__PURE__*/React.createElement("div", {
1080
+ className: "eco-live-gift-panel"
1081
+ }, /*#__PURE__*/React.createElement("div", {
1082
+ className: "eco-live-gift-grid"
1083
+ }, gifts.map(gift => /*#__PURE__*/React.createElement("div", {
1084
+ key: gift.id,
1085
+ className: `eco-live-gift-item ${selectedId === gift.id ? 'selected' : ''}`,
1086
+ onClick: () => onSelect(gift.id)
1087
+ }, /*#__PURE__*/React.createElement("span", {
1088
+ className: "eco-live-gift-icon"
1089
+ }, gift.icon || '🎁'), /*#__PURE__*/React.createElement("span", {
1090
+ className: "eco-live-gift-name"
1091
+ }, gift.name), /*#__PURE__*/React.createElement("span", {
1092
+ className: "eco-live-gift-price"
1093
+ }, gift.price > 0 ? `${gift.price}币` : '免费')))), selectedId && /*#__PURE__*/React.createElement("div", {
1094
+ className: "eco-live-gift-actions"
1095
+ }, /*#__PURE__*/React.createElement("div", {
1096
+ className: "eco-live-gift-count"
1097
+ }, /*#__PURE__*/React.createElement("button", {
1098
+ onClick: () => setCount(Math.max(1, count - 1))
1099
+ }, "-"), /*#__PURE__*/React.createElement("span", null, count), /*#__PURE__*/React.createElement("button", {
1100
+ onClick: () => setCount(count + 1)
1101
+ }, "+")), /*#__PURE__*/React.createElement("button", {
1102
+ className: "eco-live-gift-send",
1103
+ onClick: handleSend
1104
+ }, t('sendGift'))));
1105
+ }
1106
+
1107
+ /**
1108
+ * 直播间卡片
1109
+ */
1110
+ function LiveRoomCard({
1111
+ room,
1112
+ onClick
1113
+ }) {
1114
+ const {
1115
+ id,
1116
+ title,
1117
+ cover,
1118
+ host,
1119
+ viewer_count,
1120
+ status
1121
+ } = room;
1122
+ const isLive = status === LiveStatus.LIVE;
1123
+ return /*#__PURE__*/React.createElement("div", {
1124
+ className: "eco-live-room-card",
1125
+ onClick: () => onClick?.(room)
1126
+ }, /*#__PURE__*/React.createElement("div", {
1127
+ className: "eco-live-room-cover"
1128
+ }, /*#__PURE__*/React.createElement("img", {
1129
+ src: cover || '/placeholder.png',
1130
+ alt: title
1131
+ }), isLive && /*#__PURE__*/React.createElement("span", {
1132
+ className: "eco-live-status live"
1133
+ }, /*#__PURE__*/React.createElement("span", {
1134
+ className: "eco-live-dot"
1135
+ }), t('live')), /*#__PURE__*/React.createElement("span", {
1136
+ className: "eco-live-viewers"
1137
+ }, "\uD83D\uDC41 ", viewer_count || 0)), /*#__PURE__*/React.createElement("div", {
1138
+ className: "eco-live-room-info"
1139
+ }, /*#__PURE__*/React.createElement("h4", {
1140
+ className: "eco-live-room-title"
1141
+ }, title), /*#__PURE__*/React.createElement("div", {
1142
+ className: "eco-live-room-host"
1143
+ }, /*#__PURE__*/React.createElement("img", {
1144
+ src: host?.avatar || '/avatar.png',
1145
+ alt: host?.name
1146
+ }), /*#__PURE__*/React.createElement("span", null, host?.name))));
1147
+ }
1148
+
1149
+ /**
1150
+ * 直播间网格
1151
+ */
1152
+ function LiveRoomGrid({
1153
+ rooms = [],
1154
+ loading,
1155
+ onRoomClick,
1156
+ columns = 2
1157
+ }) {
1158
+ if (loading && rooms.length === 0) {
1159
+ return /*#__PURE__*/React.createElement("div", {
1160
+ className: "eco-live-loading"
1161
+ }, "\u52A0\u8F7D\u4E2D...");
1162
+ }
1163
+ if (rooms.length === 0) {
1164
+ return /*#__PURE__*/React.createElement("div", {
1165
+ className: "eco-live-empty"
1166
+ }, "\u6682\u65E0\u76F4\u64AD");
1167
+ }
1168
+ return /*#__PURE__*/React.createElement("div", {
1169
+ className: "eco-live-room-grid",
1170
+ style: {
1171
+ gridTemplateColumns: `repeat(${columns}, 1fr)`
1172
+ }
1173
+ }, rooms.map(room => /*#__PURE__*/React.createElement(LiveRoomCard, {
1174
+ key: room.id,
1175
+ room: room,
1176
+ onClick: onRoomClick
1177
+ })));
1178
+ }
1179
+
1180
+ /**
1181
+ * Live SDK - 高级组件: 直播播放器、弹幕、礼物面板
1182
+ */
1183
+
1184
+
1185
+ // 直播播放器
1186
+ function LivePlayer({
1187
+ streamUrl,
1188
+ thumbnail,
1189
+ isLive = false,
1190
+ viewerCount = 0,
1191
+ onFullscreen,
1192
+ onPictureInPicture,
1193
+ className = ''
1194
+ }) {
1195
+ const videoRef = React.useRef(null);
1196
+ const [isPlaying, setIsPlaying] = React.useState(false);
1197
+ const [isMuted, setIsMuted] = React.useState(true);
1198
+ const [quality, setQuality] = React.useState('auto');
1199
+ const togglePlay = () => {
1200
+ if (videoRef.current) {
1201
+ if (isPlaying) {
1202
+ videoRef.current.pause();
1203
+ } else {
1204
+ videoRef.current.play();
1205
+ }
1206
+ setIsPlaying(!isPlaying);
1207
+ }
1208
+ };
1209
+ const toggleMute = () => {
1210
+ if (videoRef.current) {
1211
+ videoRef.current.muted = !isMuted;
1212
+ setIsMuted(!isMuted);
1213
+ }
1214
+ };
1215
+ return /*#__PURE__*/React.createElement("div", {
1216
+ className: `live-player ${className}`
1217
+ }, /*#__PURE__*/React.createElement("div", {
1218
+ className: "live-player-container"
1219
+ }, /*#__PURE__*/React.createElement("video", {
1220
+ ref: videoRef,
1221
+ src: streamUrl,
1222
+ poster: thumbnail,
1223
+ muted: isMuted,
1224
+ autoPlay: true,
1225
+ playsInline: true
1226
+ }), /*#__PURE__*/React.createElement("div", {
1227
+ className: "live-player-overlay"
1228
+ }, isLive && /*#__PURE__*/React.createElement("div", {
1229
+ className: "live-badge"
1230
+ }, /*#__PURE__*/React.createElement("span", {
1231
+ className: "live-dot"
1232
+ }), "LIVE"), /*#__PURE__*/React.createElement("div", {
1233
+ className: "live-viewer-count"
1234
+ }, "\uD83D\uDC41\uFE0F ", formatNumber(viewerCount))), /*#__PURE__*/React.createElement("div", {
1235
+ className: "live-player-controls"
1236
+ }, /*#__PURE__*/React.createElement("button", {
1237
+ className: "live-control-btn",
1238
+ onClick: togglePlay
1239
+ }, isPlaying ? '⏸️' : '▶️'), /*#__PURE__*/React.createElement("button", {
1240
+ className: "live-control-btn",
1241
+ onClick: toggleMute
1242
+ }, isMuted ? '🔇' : '🔊'), /*#__PURE__*/React.createElement("div", {
1243
+ className: "live-quality-selector"
1244
+ }, /*#__PURE__*/React.createElement("select", {
1245
+ value: quality,
1246
+ onChange: e => setQuality(e.target.value)
1247
+ }, /*#__PURE__*/React.createElement("option", {
1248
+ value: "auto"
1249
+ }, "Auto"), /*#__PURE__*/React.createElement("option", {
1250
+ value: "1080p"
1251
+ }, "1080p"), /*#__PURE__*/React.createElement("option", {
1252
+ value: "720p"
1253
+ }, "720p"), /*#__PURE__*/React.createElement("option", {
1254
+ value: "480p"
1255
+ }, "480p"))), /*#__PURE__*/React.createElement("div", {
1256
+ className: "live-control-spacer"
1257
+ }), /*#__PURE__*/React.createElement("button", {
1258
+ className: "live-control-btn",
1259
+ onClick: onPictureInPicture
1260
+ }, "\uD83D\uDCFA"), /*#__PURE__*/React.createElement("button", {
1261
+ className: "live-control-btn",
1262
+ onClick: onFullscreen
1263
+ }, "\u26F6"))));
1264
+ }
1265
+
1266
+ // 弹幕系统
1267
+ function Danmaku({
1268
+ messages = [],
1269
+ onSend,
1270
+ enabled = true,
1271
+ speed = 5,
1272
+ className = ''
1273
+ }) {
1274
+ const containerRef = React.useRef(null);
1275
+ const [inputValue, setInputValue] = React.useState('');
1276
+ const [activeMessages, setActiveMessages] = React.useState([]);
1277
+ React.useEffect(() => {
1278
+ if (!enabled) return;
1279
+
1280
+ // 添加新消息到弹幕轨道
1281
+ const newMessages = messages.slice(-5).map((msg, i) => ({
1282
+ ...msg,
1283
+ id: msg.id || Date.now() + i,
1284
+ track: i % 5,
1285
+ left: 100
1286
+ }));
1287
+ setActiveMessages(prev => [...prev, ...newMessages].slice(-50));
1288
+ }, [messages, enabled]);
1289
+ React.useEffect(() => {
1290
+ if (!enabled) return;
1291
+ const interval = setInterval(() => {
1292
+ setActiveMessages(prev => prev.map(m => ({
1293
+ ...m,
1294
+ left: m.left - 2
1295
+ })).filter(m => m.left > -100));
1296
+ }, 50);
1297
+ return () => clearInterval(interval);
1298
+ }, [enabled, speed]);
1299
+ const handleSend = () => {
1300
+ if (!inputValue.trim()) return;
1301
+ onSend?.({
1302
+ text: inputValue.trim()
1303
+ });
1304
+ setInputValue('');
1305
+ };
1306
+ return /*#__PURE__*/React.createElement("div", {
1307
+ className: `live-danmaku ${className}`
1308
+ }, /*#__PURE__*/React.createElement("div", {
1309
+ className: "live-danmaku-container",
1310
+ ref: containerRef
1311
+ }, enabled && activeMessages.map(msg => /*#__PURE__*/React.createElement("div", {
1312
+ key: msg.id,
1313
+ className: "live-danmaku-message",
1314
+ style: {
1315
+ left: `${msg.left}%`,
1316
+ top: `${msg.track * 28 + 10}px`,
1317
+ color: msg.color || '#fff'
1318
+ }
1319
+ }, msg.text))), /*#__PURE__*/React.createElement("div", {
1320
+ className: "live-danmaku-input"
1321
+ }, /*#__PURE__*/React.createElement("input", {
1322
+ type: "text",
1323
+ value: inputValue,
1324
+ onChange: e => setInputValue(e.target.value),
1325
+ onKeyDown: e => e.key === 'Enter' && handleSend(),
1326
+ placeholder: t('sendDanmaku'),
1327
+ maxLength: 50
1328
+ }), /*#__PURE__*/React.createElement("button", {
1329
+ onClick: handleSend
1330
+ }, t('send'))));
1331
+ }
1332
+
1333
+ // 礼物面板
1334
+ function GiftPanel({
1335
+ gifts = [],
1336
+ selectedGift,
1337
+ onSelectGift,
1338
+ onSendGift,
1339
+ balance = 0,
1340
+ loading = false,
1341
+ className = ''
1342
+ }) {
1343
+ const [quantity, setQuantity] = React.useState(1);
1344
+ const handleSend = () => {
1345
+ if (!selectedGift) return;
1346
+ onSendGift?.({
1347
+ gift: selectedGift,
1348
+ quantity
1349
+ });
1350
+ setQuantity(1);
1351
+ };
1352
+ return /*#__PURE__*/React.createElement("div", {
1353
+ className: `live-gift-panel ${className}`
1354
+ }, /*#__PURE__*/React.createElement("div", {
1355
+ className: "live-gift-header"
1356
+ }, /*#__PURE__*/React.createElement("span", {
1357
+ className: "live-gift-title"
1358
+ }, "\uD83C\uDF81 ", t('gifts')), /*#__PURE__*/React.createElement("span", {
1359
+ className: "live-gift-balance"
1360
+ }, "\uD83D\uDCB0 ", balance.toLocaleString())), /*#__PURE__*/React.createElement("div", {
1361
+ className: "live-gift-grid"
1362
+ }, gifts.map(gift => /*#__PURE__*/React.createElement("div", {
1363
+ key: gift.id,
1364
+ className: `live-gift-item ${selectedGift?.id === gift.id ? 'selected' : ''}`,
1365
+ onClick: () => onSelectGift?.(gift)
1366
+ }, /*#__PURE__*/React.createElement("div", {
1367
+ className: "live-gift-icon"
1368
+ }, gift.icon || '🎁'), /*#__PURE__*/React.createElement("div", {
1369
+ className: "live-gift-name"
1370
+ }, gift.name), /*#__PURE__*/React.createElement("div", {
1371
+ className: "live-gift-price"
1372
+ }, "\uD83D\uDCB0 ", gift.price)))), selectedGift && /*#__PURE__*/React.createElement("div", {
1373
+ className: "live-gift-send"
1374
+ }, /*#__PURE__*/React.createElement("div", {
1375
+ className: "live-gift-selected"
1376
+ }, /*#__PURE__*/React.createElement("span", null, selectedGift.icon), /*#__PURE__*/React.createElement("span", null, selectedGift.name)), /*#__PURE__*/React.createElement("div", {
1377
+ className: "live-gift-quantity"
1378
+ }, /*#__PURE__*/React.createElement("button", {
1379
+ onClick: () => setQuantity(Math.max(1, quantity - 1))
1380
+ }, "-"), /*#__PURE__*/React.createElement("span", null, quantity), /*#__PURE__*/React.createElement("button", {
1381
+ onClick: () => setQuantity(quantity + 1)
1382
+ }, "+")), /*#__PURE__*/React.createElement("button", {
1383
+ className: "live-btn live-btn-primary",
1384
+ onClick: handleSend,
1385
+ disabled: loading || balance < selectedGift.price * quantity
1386
+ }, t('send'), " (", selectedGift.price * quantity, ")")));
1387
+ }
1388
+
1389
+ // 直播间信息
1390
+ function LiveRoomInfo({
1391
+ room,
1392
+ host,
1393
+ isFollowing = false,
1394
+ onFollow,
1395
+ onShare,
1396
+ className = ''
1397
+ }) {
1398
+ if (!room) return null;
1399
+ return /*#__PURE__*/React.createElement("div", {
1400
+ className: `live-room-info ${className}`
1401
+ }, /*#__PURE__*/React.createElement("div", {
1402
+ className: "live-host-section"
1403
+ }, /*#__PURE__*/React.createElement("div", {
1404
+ className: "live-host-avatar"
1405
+ }, host?.avatar ? /*#__PURE__*/React.createElement("img", {
1406
+ src: host.avatar,
1407
+ alt: host.name
1408
+ }) : /*#__PURE__*/React.createElement("span", null, "\uD83D\uDC64"), /*#__PURE__*/React.createElement("span", {
1409
+ className: "live-host-live-badge"
1410
+ }, "LIVE")), /*#__PURE__*/React.createElement("div", {
1411
+ className: "live-host-info"
1412
+ }, /*#__PURE__*/React.createElement("div", {
1413
+ className: "live-host-name"
1414
+ }, host?.name || t('anonymous')), /*#__PURE__*/React.createElement("div", {
1415
+ className: "live-host-followers"
1416
+ }, formatNumber(host?.followers || 0), " ", t('followers'))), /*#__PURE__*/React.createElement("button", {
1417
+ className: `live-btn ${isFollowing ? 'live-btn-outline' : 'live-btn-primary'}`,
1418
+ onClick: () => onFollow?.(!isFollowing)
1419
+ }, isFollowing ? t('following') : t('follow')), /*#__PURE__*/React.createElement("button", {
1420
+ className: "live-btn live-btn-outline",
1421
+ onClick: onShare
1422
+ }, "\uD83D\uDCE4 ", t('share'))), /*#__PURE__*/React.createElement("div", {
1423
+ className: "live-room-title"
1424
+ }, room.title), /*#__PURE__*/React.createElement("div", {
1425
+ className: "live-room-tags"
1426
+ }, room.tags?.map((tag, i) => /*#__PURE__*/React.createElement("span", {
1427
+ key: i,
1428
+ className: "live-room-tag"
1429
+ }, "#", tag))), /*#__PURE__*/React.createElement("div", {
1430
+ className: "live-room-stats"
1431
+ }, /*#__PURE__*/React.createElement("span", null, "\uD83D\uDC41\uFE0F ", formatNumber(room.viewers), " ", t('viewers')), /*#__PURE__*/React.createElement("span", null, "\u2764\uFE0F ", formatNumber(room.likes), " ", t('likes')), /*#__PURE__*/React.createElement("span", null, "\uD83D\uDCAC ", formatNumber(room.comments), " ", t('comments'))));
1432
+ }
1433
+
1434
+ // 聊天面板
1435
+ function LiveChat({
1436
+ messages = [],
1437
+ onSendMessage,
1438
+ onMention,
1439
+ className = ''
1440
+ }) {
1441
+ const [inputValue, setInputValue] = React.useState('');
1442
+ const chatRef = React.useRef(null);
1443
+ React.useEffect(() => {
1444
+ if (chatRef.current) {
1445
+ chatRef.current.scrollTop = chatRef.current.scrollHeight;
1446
+ }
1447
+ }, [messages.length]);
1448
+ const handleSend = () => {
1449
+ if (!inputValue.trim()) return;
1450
+ onSendMessage?.(inputValue.trim());
1451
+ setInputValue('');
1452
+ };
1453
+ return /*#__PURE__*/React.createElement("div", {
1454
+ className: `live-chat ${className}`
1455
+ }, /*#__PURE__*/React.createElement("div", {
1456
+ className: "live-chat-header"
1457
+ }, /*#__PURE__*/React.createElement("span", null, "\uD83D\uDCAC ", t('chat')), /*#__PURE__*/React.createElement("span", {
1458
+ className: "live-chat-count"
1459
+ }, messages.length)), /*#__PURE__*/React.createElement("div", {
1460
+ className: "live-chat-messages",
1461
+ ref: chatRef
1462
+ }, messages.map((msg, index) => /*#__PURE__*/React.createElement("div", {
1463
+ key: msg.id || index,
1464
+ className: `live-chat-message ${msg.type || ''}`
1465
+ }, msg.type === 'gift' ? /*#__PURE__*/React.createElement("div", {
1466
+ className: "live-chat-gift"
1467
+ }, /*#__PURE__*/React.createElement("span", {
1468
+ className: "live-chat-user"
1469
+ }, msg.user?.name), /*#__PURE__*/React.createElement("span", null, t('sent')), /*#__PURE__*/React.createElement("span", {
1470
+ className: "live-chat-gift-icon"
1471
+ }, msg.gift?.icon), /*#__PURE__*/React.createElement("span", null, "x", msg.quantity)) : /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("span", {
1472
+ className: "live-chat-user",
1473
+ onClick: () => onMention?.(msg.user)
1474
+ }, msg.user?.name, ":"), /*#__PURE__*/React.createElement("span", {
1475
+ className: "live-chat-text"
1476
+ }, msg.text))))), /*#__PURE__*/React.createElement("div", {
1477
+ className: "live-chat-input"
1478
+ }, /*#__PURE__*/React.createElement("input", {
1479
+ type: "text",
1480
+ value: inputValue,
1481
+ onChange: e => setInputValue(e.target.value),
1482
+ onKeyDown: e => e.key === 'Enter' && handleSend(),
1483
+ placeholder: t('typeMessage')
1484
+ }), /*#__PURE__*/React.createElement("button", {
1485
+ onClick: handleSend
1486
+ }, t('send'))));
1487
+ }
1488
+ function formatNumber(num) {
1489
+ if (num >= 1000000) return (num / 1000000).toFixed(1) + 'M';
1490
+ if (num >= 1000) return (num / 1000).toFixed(1) + 'K';
1491
+ return num.toString();
1492
+ }
1493
+
1494
+ /**
1495
+ * @quantabit/live-sdk
1496
+ * Live SDK - Full Version
1497
+ */
1498
+
1499
+ const getLiveRooms = (...args) => liveClient.getLiveRooms(...args);
1500
+ const getRoomInfo = (...args) => liveClient.getRoomInfo(...args);
1501
+ const createRoom = (...args) => liveClient.createRoom(...args);
1502
+ const startLive = (...args) => liveClient.startLive(...args);
1503
+ const endLive = (...args) => liveClient.endLive(...args);
1504
+ const enterRoom = (...args) => liveClient.enterRoom(...args);
1505
+ const leaveRoom = (...args) => liveClient.leaveRoom(...args);
1506
+ const sendMessage = (...args) => liveClient.sendMessage(...args);
1507
+ const sendGift = (...args) => liveClient.sendGift(...args);
1508
+ const getGifts = (...args) => liveClient.getGifts(...args);
1509
+ const on = (...args) => liveClient.on(...args);
1510
+ const off = (...args) => liveClient.off(...args);
1511
+
1512
+ exports.Danmaku = Danmaku;
1513
+ exports.DanmakuInput = DanmakuInput;
1514
+ exports.DanmakuList = DanmakuList;
1515
+ exports.DanmakuMessage = DanmakuMessage;
1516
+ exports.GiftPanel = GiftPanel$1;
1517
+ exports.GiftPanelAdvanced = GiftPanel;
1518
+ exports.GiftType = GiftType;
1519
+ exports.LiveChat = LiveChat;
1520
+ exports.LiveClient = LiveClient;
1521
+ exports.LiveMessageType = LiveMessageType;
1522
+ exports.LivePlayer = LivePlayer;
1523
+ exports.LiveRoomCard = LiveRoomCard;
1524
+ exports.LiveRoomGrid = LiveRoomGrid;
1525
+ exports.LiveRoomInfo = LiveRoomInfo;
1526
+ exports.LiveStatus = LiveStatus;
1527
+ exports.LiveType = LiveType;
1528
+ exports.SUPPORTED_LANGUAGES = SUPPORTED_LANGUAGES;
1529
+ exports.createRoom = createRoom;
1530
+ exports.endLive = endLive;
1531
+ exports.enterRoom = enterRoom;
1532
+ exports.getGifts = getGifts;
1533
+ exports.getLanguage = getLanguage;
1534
+ exports.getLiveRooms = getLiveRooms;
1535
+ exports.getRoomInfo = getRoomInfo;
1536
+ exports.leaveRoom = leaveRoom;
1537
+ exports.liveClient = liveClient;
1538
+ exports.messages = messages;
1539
+ exports.off = off;
1540
+ exports.on = on;
1541
+ exports.sendGift = sendGift;
1542
+ exports.sendMessage = sendMessage;
1543
+ exports.setLanguage = setLanguage;
1544
+ exports.startLive = startLive;
1545
+ exports.t = t;
1546
+ exports.useGifts = useGifts;
1547
+ exports.useLiveRoom = useLiveRoom;
1548
+ exports.useLiveRooms = useLiveRooms;
1549
+ //# sourceMappingURL=index.cjs.map