今年体育直播赛道持续火热,世界杯、五大联赛流量巨大。我们投入三个月时间,从零开发了星逐赛事APP,目前已上架苹果商店,源码覆盖H5和APP双端。
下面重点谈直播间推拉流和实时交互系统的技术实现方案。
一、直播推拉流整体架构
直播功能是体育赛事APP的核心,技术链路涉及推流端、服务端、播放端三个环节。
推流端:主播通过OBS或手机App推流,使用RTMP协议将视频流发送到流媒体服务器。推流地址格式为rtmp://domain/live/{streamKey},streamKey由后端动态生成,每次推流不同,防止盗推。
流媒体服务端:采用ZLMediaKit作为流媒体服务器,负责接收RTMP推流、实时转码、HLS切片分发。ZLMediaKit是国产开源流媒体方案,性能优秀,文档友好,非常适合国内直播项目。
播放端:用户通过HLS协议从CDN拉流观看。HLS兼容性最好,几乎所有浏览器和播放器都支持,延迟控制在3-5秒。
二、推流端技术实现
2.1 推流地址生成与鉴权
推流地址由后端动态生成,确保每次推流唯一性:
java
public String generateStreamKey(Long userId) { String streamKey = UUID.randomUUID().toString().replace("-", ""); redisTemplate.opsForValue().set("stream:" + streamKey, userId, 24, TimeUnit.HOURS); return streamKey; }推流地址:rtmp://domain/live/{streamKey}
推流时,流媒体服务器回调后端接口校验streamKey合法性,同时校验用户是否有推流权限。验证不通过则拒绝推流,有效防止盗推。
2.2 推流端编码配置
编码标准:H.264是主流选择,兼容性最好。推流码率建议2000-4000Kbps,分辨率720P或1080P,帧率30fps。
关键帧策略:GOP(Group of Pictures)大小设2-4秒,平衡首屏加载速度和编码效率。
推流协议:RTMP是推流端最常用的协议,延迟低、兼容性好。同时也支持SRT协议作为备选,在弱网环境下自动切换以保证推流稳定。
三、服务端流媒体处理
3.1 ZLMediaKit核心配置
流媒体服务器的关键配置项:
ini
[rtmp] port=1935 [hls] segDuration=4 # 切片时长4秒 segNum=3 # 缓存数量3个 hlsPath=/var/www/hls/ [hook] on_publish=http://backend/api/callback/publish on_unpublish=http://backend/api/callback/unpublish on_record_mp4=http://backend/api/callback/record切片时长是影响延迟的关键参数。4秒切片延迟控制在3-5秒,平衡了实时性和兼容性。
3.2 多清晰度转码
为适应不同网络环境,服务端对源流进行实时转码,输出多档清晰度:
| 清晰度 | 分辨率 | 码率 | 适用场景 |
|---|---|---|---|
| 原画 | 源分辨率 | 源码率 | 高端设备 |
| 1080P | 1920×1080 | 2-4Mbps | 主流手机 |
| 720P | 1280×720 | 1.5-2Mbps | 中端设备 |
| 480P | 854×480 | 800Kbps | 弱网环境 |
转码是计算密集型任务,通过GPU加速可大幅提升转码效率。
四、播放端实现
4.1 H5播放器
基于hls.js封装播放器,支持HLS协议播放:
javascript
import Hls from 'hls.js'; export class LivePlayer { constructor(videoElement) { this.video = videoElement; this.hls = new Hls({ enableWorker: true, lowLatencyMode: true, fragLoadingTimeOut: 10000 }); this.hls.attachMedia(this.video); } load(url) { this.hls.loadSource(url); this.hls.on(Hls.Events.MANIFEST_PARSED, () => { this.video.play(); }); } destroy() { this.hls.destroy(); } }4.2 Android端播放器
使用ExoPlayer官方播放器框架,支持HLS、DASH等多种协议:
java
private void initPlayer() { player = new ExoPlayer.Builder(context) .setMediaSourceFactory(new HlsMediaSource.Factory(dataSourceFactory)) .build(); playerView.setPlayer(player); } private void loadStream(String url) { HlsMediaSource hlsSource = new HlsMediaSource.Factory(dataSourceFactory) .setAllowChunklessPreparation(true) .createMediaSource(MediaItem.fromUri(url)); player.setMediaSource(hlsSource); player.prepare(); player.setPlayWhenReady(true); }4.3 iOS端播放器
使用AVPlayer系统原生播放框架:
objectivec
- (void)initPlayer { AVPlayerItem *item = [AVPlayerItem playerItemWithURL:[NSURL URLWithString:self.streamURL]]; self.player = [AVPlayer playerWithPlayerItem:item]; self.playerLayer = [AVPlayerLayer playerLayerWithPlayer:self.player]; self.playerLayer.frame = self.videoView.bounds; [self.videoView.layer addSublayer:self.playerLayer]; [self.player play]; }五、实时交互系统
5.1 WebSocket连接管理
弹幕、在线人数、礼物等实时互动功能基于WebSocket实现:
java
@Component @ServerEndpoint("/ws/live/{roomId}") public class LiveWebSocket { private static final Map<String, Set<Session>> roomSessions = new ConcurrentHashMap<>(); @OnOpen public void onOpen(Session session, @PathParam("roomId") String roomId) { roomSessions.computeIfAbsent(roomId, k -> ConcurrentHashMap.newKeySet()).add(session); } @OnMessage public void onMessage(String message, @PathParam("roomId") String roomId) { // 广播给同房间所有用户 broadcast(roomId, message); } }5.2 分布式跨节点广播
分布式部署时,用户连接分散在不同服务器。通过Redis Pub/Sub实现跨节点消息广播:
任一节点收到消息后发布到Redis频道
所有订阅了该频道的节点收到消息
各节点推送给连接到本机的用户
5.3 弹幕限流
按用户IP和userId双维度限流,每秒消息数超过阈值返回429:
java
private boolean checkRateLimit(Long userId, String roomId) { String key = "rate:danmaku:" + userId + ":" + roomId + ":" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")); Long count = redisTemplate.opsForValue().increment(key); if (count == 1) { redisTemplate.expire(key, 1, TimeUnit.SECONDS); } return count <= 3; // 每秒最多3条 }六、多线路CDN容灾
为保证直播流高可用,配置多条CDN线路:
| 线路 | 提供商 | 角色 |
|---|---|---|
| 主线路 | 阿里云 | 默认分发 |
| 备线路1 | 腾讯云 | 主线路故障时切换 |
| 备线路2 | 网宿 | 极端情况备用 |
播放器每5秒检测当前线路加载速度,加载时间超过3秒触发自动切换。切换过程无缝衔接,用户无感知。
七、踩坑记录与解决方案
坑1:HLS延迟过高
默认10秒切片导致延迟10-15秒。解决方案:切片时长从10秒调到4秒,播放器buffer从6秒降到2秒,延迟降至3-5秒。
坑2:WebSocket跨节点推送
单机正常,部署多台服务器后消息无法跨节点推送。解决方案:Redis Pub/Sub做消息广播。
坑3:推流防盗链
推流地址固定导致被盗推。解决方案:streamKey动态生成,流媒体服务器回调后端验证token合法性。
八、总结
星逐赛事APP的直播系统基于以下技术方案实现:
推流端:RTMP协议 + 动态streamKey鉴权 + H.264编码
服务端:ZLMediaKit流媒体服务器 + HLS切片分发 + 多清晰度转码
播放端:HLS协议 + hls.js(H5)/ExoPlayer(Android)/AVPlayer(iOS)
实时交互:WebSocket长连接 + Redis Pub/Sub跨节点广播
分发加速:多线路CDN容灾 + 智能调度