从零搭建:基于 SpringBoot + SRS + WebRTC 的多人互动直播课堂
1. 标题与引言
标题: 低延迟互动直播实战:SpringBoot + SRS 实现多人在线课堂(含全套源码与 Nginx 配置)
引言: 传统的直播方案(RTMP/HLS)动辄数秒甚至十秒的延迟,在互动教学场景下简直是灾难。作为后端开发者,如何利用 WebRTC 实现超低延迟的多人互动?本文将带你实战一套完整的“互动课堂”方案,解决推流控制、房间自动清理、以及公网 HTTPS 部署等核心痛点。
2. 技术架构图
在开始编码前,先看整体数据流向:
-
信令流:客户端 <-> SpringBoot (WebSocket) <-> 控制状态
-
媒体流:客户端 <-> SRS (WebRTC) <-> 转发音视频
3. 第一步:环境搭建(SRS 与 Nginx)
WebRTC 在公网环境下必须运行在 HTTPS 之上。我们使用 Nginx 进行 SSL 卸载并转发信令。(推流必须在https环境,使用自签证书也可)
Nginx 核心配置:
server {
listen 443 ssl;
server_name your_domain.com;
ssl_certificate /etc/nginx/cert/your_cert.crt;
ssl_certificate_key /etc/nginx/cert/your_cert.key;
# 转发 SRS 的 WebRTC HTTP API (信令端口)
location /rtc/v1/ {
proxy_pass http://127.0.0.1:1985/rtc/v1/;
proxy_set_header Host $host;
}
# 静态资源或 API 转发...
}
4. 第二步:定义核心模型(Room & User)
我们需要在内存中维护房间和用户状态,使用线程安全的容器是关键。
@Data
public class Room {
private String id;
private String name;
// 使用 CopyOnWriteArrayList 避免并发修改异常
private List<User> users = new CopyOnWriteArrayList<>();
}
@Data
public class User {
private String userId;
private String username;
private String streamUrl; // 活跃推流地址
}
5. 第三步:编写 RoomService(业务心脏)
这里实现了两个核心逻辑:全局广播和自动清理。
@Service
public class RoomService {
private final Map<String, Room> roomMap = new ConcurrentHashMap<>();
// 1. 用户加入房间
public String joinRoom(String roomId, User user) {
Room room = roomMap.get(roomId);
if (room != null) {
room.getUsers().add(user);
log.info("用户{}加入房间{}", user.getUserId(), roomId);
}
return roomId;
}
// 2. 流状态激活(关键:推流成功后触发广播)
public void setStreamActive(String roomId, String userId, String streamUrl) {
Room room = roomMap.get(roomId);
if (room != null) {
room.getUsers().stream()
.filter(u -> u.getUserId().equals(userId))
.findFirst()
.ifPresent(u -> {
u.setStreamUrl(streamUrl);
// 全局通知所有人:有新流上线,协议号 1
ExamWebSocketHandler.broadcastToAll(1, u);
});
}
}
// 3. 核心:被动退出清理逻辑
public void globalCleanUser(String userId) {
roomMap.forEach((roomId, room) -> {
boolean removed = room.getUsers().removeIf(u -> u.getUserId().equals(userId));
if (removed) {
// 通知所有人该流已下线,协议号 0
ExamWebSocketHandler.broadcastToAll(0, userId);
if (room.getUsers().isEmpty()) roomMap.remove(roomId);
}
});
}
}
6. 第四步:WebSocket 实时同步
利用 WebSocket 保持客户端与后端的长连接,确保指令“秒达”。
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
String clientId = (String) session.getAttributes().get("clientId");
if (clientId != null) {
clientSessions.remove(clientId);
// 如果 WebSocket 断开(关浏览器),自动清理房间防止“僵尸流”
roomService.globalCleanUser(clientId);
}
}
public static void broadcastToAll(Integer protocolHead, Object data) {
// 遍历所有在线 Session 广播 ProtocolMessage...
}
7. 第五步:实战流程对比
在开发过程中,我对比了重构前后的差异:
| 步骤 | 传统逻辑 | 重构后(本方案) |
| 感知他人加入 | 手动刷新列表 | Socket 实时弹窗/渲染 |
| 流上线 | 等待 HLS 切片 (10s+) | WebRTC 瞬间通知并拉流 |
| 异常断线 | 房间信息残留,流黑屏 | 后台自动检测并全员销毁窗口 |
8. 避坑指南(干货)
-
UDP 端口开通:云服务器不仅要开 TCP 1985,必须开 UDP 8000 及其 Candidate 指定的端口范围。
-
WebSocket 跨域:在
registerWebSocketHandlers中配置.setAllowedOriginPatterns("*"),否则前端连接会报 403。 -
白名单:记得在 Spring Security 或 Filter 中放行
/exam/**路径。
9. 结语
通过这套方案,我们不仅实现了一个低延迟的互动系统,更通过 SpringBoot 完成了对 SRS 这种 C++ 媒体服务器的“精细化管控”。
Debug笔记
记录我在代码背后踩过的坑,也分享那些曾经提升过我效率的关键思路。
微信搜索公众号:Debug笔记,关注我,一起成为更优雅的程序员。
更多推荐
所有评论(0)