393 lines
8.3 KiB
Markdown
393 lines
8.3 KiB
Markdown
|
|
# 直播间下播清空在线人数功能说明
|
|||
|
|
|
|||
|
|
> **实现时间**: 2024-12-29
|
|||
|
|
> **功能**: 主播下播时自动清空在线人数并通知观众
|
|||
|
|
> **状态**: ✅ 已完成
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 📋 功能概述
|
|||
|
|
|
|||
|
|
当主播关闭直播时,系统会自动执行以下操作:
|
|||
|
|
|
|||
|
|
1. ✅ **通知所有观众** - 通过WebSocket发送"主播已下播"消息
|
|||
|
|
2. ✅ **清空内存数据** - 清空房间的所有WebSocket连接
|
|||
|
|
3. ✅ **重置数据库** - 将数据库中的`online_count`字段设置为0
|
|||
|
|
4. ✅ **防止数据残留** - 确保下次开播时在线人数从0开始
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🔧 实现细节
|
|||
|
|
|
|||
|
|
### 1. 数据库字段更新
|
|||
|
|
|
|||
|
|
在`LiveRoomServiceImpl.setLiveStatus()`方法中,当直播关闭时:
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
// 如果直播关闭,将数据库中的在线人数设置为0
|
|||
|
|
if (!isLive) {
|
|||
|
|
uw.set(LiveRoom::getOnlineCount, 0);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**SQL执行效果**:
|
|||
|
|
```sql
|
|||
|
|
UPDATE eb_live_room
|
|||
|
|
SET is_live = 0,
|
|||
|
|
started_at = NULL,
|
|||
|
|
online_count = 0 -- 重置在线人数
|
|||
|
|
WHERE stream_key = ?
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2. WebSocket通知观众
|
|||
|
|
|
|||
|
|
在`LiveRoomOnlineServiceImpl.clearRoomAndNotify()`方法中:
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
// 创建直播结束通知消息
|
|||
|
|
Map<String, Object> liveEndedMsg = new HashMap<>();
|
|||
|
|
liveEndedMsg.put("type", "live_ended");
|
|||
|
|
liveEndedMsg.put("roomId", roomId);
|
|||
|
|
liveEndedMsg.put("message", "主播已下播");
|
|||
|
|
liveEndedMsg.put("onlineCount", 0);
|
|||
|
|
liveEndedMsg.put("timestamp", System.currentTimeMillis());
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**前端收到的消息格式**:
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"type": "live_ended",
|
|||
|
|
"roomId": "123",
|
|||
|
|
"message": "主播已下播",
|
|||
|
|
"onlineCount": 0,
|
|||
|
|
"timestamp": 1703836800000
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3. 清空内存数据
|
|||
|
|
|
|||
|
|
系统会清空以下内存数据:
|
|||
|
|
|
|||
|
|
- `roomConnections` - 房间的所有WebSocket连接
|
|||
|
|
- `roomUserCounts` - 房间的在线人数计数器
|
|||
|
|
- `sessionRoomMap` - Session到房间的反向映射
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🔄 执行流程
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
主播点击下播
|
|||
|
|
↓
|
|||
|
|
SRS回调 /srs/on_unpublish
|
|||
|
|
↓
|
|||
|
|
LiveRoomService.setLiveStatus(streamKey, false)
|
|||
|
|
↓
|
|||
|
|
1. 更新数据库:is_live=0, online_count=0
|
|||
|
|
↓
|
|||
|
|
2. 获取房间ID
|
|||
|
|
↓
|
|||
|
|
3. 调用 LiveRoomOnlineService.clearRoomAndNotify(roomId, "主播已下播")
|
|||
|
|
↓
|
|||
|
|
4. 发送WebSocket消息给所有观众
|
|||
|
|
↓
|
|||
|
|
5. 关闭所有WebSocket连接
|
|||
|
|
↓
|
|||
|
|
6. 清空内存中的房间数据
|
|||
|
|
↓
|
|||
|
|
完成
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 📡 前端接收处理
|
|||
|
|
|
|||
|
|
### WebSocket消息监听
|
|||
|
|
|
|||
|
|
前端需要监听`live_ended`类型的消息:
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
websocket.onmessage = function(event) {
|
|||
|
|
const data = JSON.parse(event.data);
|
|||
|
|
|
|||
|
|
if (data.type === 'live_ended') {
|
|||
|
|
// 显示提示
|
|||
|
|
showToast(data.message); // "主播已下播"
|
|||
|
|
|
|||
|
|
// 更新UI
|
|||
|
|
updateOnlineCount(0);
|
|||
|
|
|
|||
|
|
// 关闭直播播放器
|
|||
|
|
stopPlayer();
|
|||
|
|
|
|||
|
|
// 可选:跳转到其他页面
|
|||
|
|
// navigateToLiveList();
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Android端处理示例
|
|||
|
|
|
|||
|
|
```kotlin
|
|||
|
|
when (message.type) {
|
|||
|
|
"live_ended" -> {
|
|||
|
|
// 显示Toast提示
|
|||
|
|
Toast.makeText(context, message.message, Toast.LENGTH_LONG).show()
|
|||
|
|
|
|||
|
|
// 更新在线人数为0
|
|||
|
|
binding.tvOnlineCount.text = "0"
|
|||
|
|
|
|||
|
|
// 停止播放
|
|||
|
|
player.stop()
|
|||
|
|
|
|||
|
|
// 可选:返回直播列表
|
|||
|
|
finish()
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🧪 测试步骤
|
|||
|
|
|
|||
|
|
### 1. 准备测试环境
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# 启动后端服务
|
|||
|
|
cd Zhibo/zhibo-h
|
|||
|
|
mvn spring-boot:run
|
|||
|
|
|
|||
|
|
# 启动SRS服务器
|
|||
|
|
cd live-streaming
|
|||
|
|
./objs/srs -c conf/srs.conf
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2. 测试流程
|
|||
|
|
|
|||
|
|
1. **创建直播间**
|
|||
|
|
```
|
|||
|
|
POST /api/front/live/room/create
|
|||
|
|
{
|
|||
|
|
"title": "测试直播",
|
|||
|
|
"streamerName": "测试主播",
|
|||
|
|
"categoryId": 1
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
2. **开始推流**
|
|||
|
|
- 使用OBS推流到:`rtmp://localhost:1935/live/{streamKey}`
|
|||
|
|
- 观察数据库:`is_live=1`
|
|||
|
|
|
|||
|
|
3. **观众加入**
|
|||
|
|
- 打开多个浏览器/客户端
|
|||
|
|
- 连接WebSocket:`ws://localhost:8080/ws/live/{roomId}?clientId=user_1`
|
|||
|
|
- 观察在线人数增加
|
|||
|
|
|
|||
|
|
4. **停止推流**
|
|||
|
|
- 在OBS中停止推流
|
|||
|
|
- 观察以下变化:
|
|||
|
|
|
|||
|
|
**预期结果**:
|
|||
|
|
|
|||
|
|
✅ **数据库变化**:
|
|||
|
|
```sql
|
|||
|
|
-- 查询直播间状态
|
|||
|
|
SELECT id, is_live, online_count FROM eb_live_room WHERE id = ?;
|
|||
|
|
-- 结果:is_live=0, online_count=0
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
✅ **前端收到消息**:
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"type": "live_ended",
|
|||
|
|
"roomId": "123",
|
|||
|
|
"message": "主播已下播",
|
|||
|
|
"onlineCount": 0,
|
|||
|
|
"timestamp": 1703836800000
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
✅ **WebSocket连接关闭**:
|
|||
|
|
- 所有观众的WebSocket连接被服务器主动关闭
|
|||
|
|
- 前端显示"主播已下播"提示
|
|||
|
|
|
|||
|
|
✅ **内存数据清空**:
|
|||
|
|
```java
|
|||
|
|
// 查询在线人数(应该返回0)
|
|||
|
|
GET /api/live/online/count/{roomId}
|
|||
|
|
// 返回:{"code":200,"data":0}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3. 验证下次开播
|
|||
|
|
|
|||
|
|
1. **再次推流**
|
|||
|
|
- 使用相同的streamKey推流
|
|||
|
|
- 观察在线人数从0开始
|
|||
|
|
|
|||
|
|
2. **观众重新加入**
|
|||
|
|
- 观众重新连接WebSocket
|
|||
|
|
- 在线人数从1开始递增
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 📊 数据库表结构
|
|||
|
|
|
|||
|
|
### eb_live_room 表
|
|||
|
|
|
|||
|
|
| 字段 | 类型 | 说明 | 下播时的值 |
|
|||
|
|
|------|------|------|-----------|
|
|||
|
|
| id | INT | 主键 | 不变 |
|
|||
|
|
| is_live | TINYINT | 是否直播中 | **0** |
|
|||
|
|
| online_count | INT | 在线人数 | **0** ← 重置 |
|
|||
|
|
| started_at | DATETIME | 开始时间 | **NULL** |
|
|||
|
|
| stream_key | VARCHAR | 推流密钥 | 不变 |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🔍 日志输出
|
|||
|
|
|
|||
|
|
### 正常流程日志
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
[INFO] 直播间关闭,清空房间 123 的在线人数并通知观众
|
|||
|
|
[INFO] 清空房间 123 的所有在线用户,当前人数: 5
|
|||
|
|
[INFO] 已通知客户端 session-001 直播结束: 主播已下播
|
|||
|
|
[INFO] 已通知客户端 session-002 直播结束: 主播已下播
|
|||
|
|
[INFO] 已通知客户端 session-003 直播结束: 主播已下播
|
|||
|
|
[INFO] 已通知客户端 session-004 直播结束: 主播已下播
|
|||
|
|
[INFO] 已通知客户端 session-005 直播结束: 主播已下播
|
|||
|
|
[INFO] 房间 123 已清空,所有用户已断开连接
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 异常情况日志
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
[WARN] Invalid roomId, cannot clear room
|
|||
|
|
[ERROR] 发送直播结束通知失败: session-001
|
|||
|
|
[ERROR] 关闭session失败: session-002
|
|||
|
|
[ERROR] 清空房间失败: roomId=123
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## ⚠️ 注意事项
|
|||
|
|
|
|||
|
|
### 1. 消息发送延迟
|
|||
|
|
|
|||
|
|
为确保消息发送完成,代码中添加了500ms延迟:
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
// 等待消息发送完成
|
|||
|
|
try {
|
|||
|
|
Thread.sleep(500);
|
|||
|
|
} catch (InterruptedException e) {
|
|||
|
|
Thread.currentThread().interrupt();
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2. 并发安全
|
|||
|
|
|
|||
|
|
使用`ConcurrentHashMap`和`synchronized`确保线程安全:
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
synchronized (session) {
|
|||
|
|
session.sendMessage(new TextMessage(endMessage));
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3. 异常处理
|
|||
|
|
|
|||
|
|
所有操作都包含异常处理,确保部分失败不影响整体流程:
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
try {
|
|||
|
|
// 发送消息
|
|||
|
|
} catch (Exception e) {
|
|||
|
|
log.error("发送直播结束通知失败", e);
|
|||
|
|
// 继续处理其他客户端
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🚀 扩展功能建议
|
|||
|
|
|
|||
|
|
### 1. 自定义下播消息
|
|||
|
|
|
|||
|
|
可以在创建直播间时设置自定义下播消息:
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
// LiveRoom实体类添加字段
|
|||
|
|
@Column(name = "end_message")
|
|||
|
|
private String endMessage; // "感谢观看,下次再见!"
|
|||
|
|
|
|||
|
|
// 下播时使用自定义消息
|
|||
|
|
String message = room.getEndMessage() != null
|
|||
|
|
? room.getEndMessage()
|
|||
|
|
: "主播已下播";
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2. 下播统计
|
|||
|
|
|
|||
|
|
记录每次直播的统计数据:
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
// 创建直播统计表
|
|||
|
|
CREATE TABLE eb_live_statistics (
|
|||
|
|
id INT PRIMARY KEY AUTO_INCREMENT,
|
|||
|
|
room_id INT,
|
|||
|
|
start_time DATETIME,
|
|||
|
|
end_time DATETIME,
|
|||
|
|
max_online_count INT, -- 最高在线人数
|
|||
|
|
total_viewers INT, -- 总观看人数
|
|||
|
|
duration INT -- 直播时长(秒)
|
|||
|
|
);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3. 推送通知
|
|||
|
|
|
|||
|
|
给关注主播的用户发送推送通知:
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
// 下播时通知关注者
|
|||
|
|
notificationService.sendToFollowers(
|
|||
|
|
streamerId,
|
|||
|
|
"您关注的主播已下播",
|
|||
|
|
"感谢观看,下次再见!"
|
|||
|
|
);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 📝 相关文件
|
|||
|
|
|
|||
|
|
### 修改的文件
|
|||
|
|
|
|||
|
|
1. `LiveRoomService.java` - 添加`getRoomIdByStreamKey()`方法
|
|||
|
|
2. `LiveRoomServiceImpl.java` - 实现下播时重置在线人数
|
|||
|
|
3. `LiveRoomOnlineService.java` - 添加`clearRoomAndNotify()`方法
|
|||
|
|
4. `LiveRoomOnlineServiceImpl.java` - 实现清空房间并通知功能
|
|||
|
|
|
|||
|
|
### 涉及的表
|
|||
|
|
|
|||
|
|
- `eb_live_room` - 直播间表(更新`online_count`字段)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## ✅ 功能检查清单
|
|||
|
|
|
|||
|
|
- [x] 数据库在线人数重置为0
|
|||
|
|
- [x] WebSocket通知所有观众
|
|||
|
|
- [x] 清空内存中的连接数据
|
|||
|
|
- [x] 关闭所有WebSocket连接
|
|||
|
|
- [x] 日志记录完整
|
|||
|
|
- [x] 异常处理完善
|
|||
|
|
- [x] 并发安全保证
|
|||
|
|
- [x] 下次开播人数从0开始
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
**实现完成时间**: 2024-12-29
|
|||
|
|
**开发者**: Kiro AI Assistant
|
|||
|
|
**状态**: ✅ 生产就绪
|