基于 ZLMRTCClient 实现 WebRTC 低延迟视频播放(Vue3 实战)
前言
在安防、低空监管、工业监控等场景里,页面往往需要秒开、低延迟的实时画面。传统 FLV/HLS 延迟通常在几秒甚至十几秒,而WebRTC可以把端到端延迟压到亚秒级。
ZLMediaKit(简称 ZLM)提供了 HTTP 接口做 WebRTC 信令交换,官方配套的浏览器 SDK 就是ZLMRTCClient。本文基于 Vue3,说明如何用ZLMRTCClient.Endpoint实现一个可复用的 WebRTC 播放器。
一、整体架构:ZLM + WebRTC 播放链路
播放端并不直接“拉 RTSP”,而是走 WebRTC:
摄像头 / 推流端 ──► ZLMediaKit(转 WebRTC) │ │ HTTP 交换 SDP(Offer / Answer) ▼ 浏览器 ZLMRTCClient.Endpoint │ ▼ <video> 渲染画面关键点只有两个:
- 信令:浏览器把本地 SDP Offer POST 到 ZLM 的 WebRTC API,换回 Answer。
- 媒体:通过 ICE/DTLS/SRTP 建立 PeerConnection,把远端
MediaStream挂到<video>。
ZLM 的播放地址一般类似:
http://{host}:{port}/index/api/webrtc?app=live&stream=test&type=play| 参数 | 含义 |
|---|---|
app | 应用名(如live) |
stream | 流 ID(如test) |
type=play | 播放(拉流);推流则用push |
这个 URL 就是 SDK 里的zlmsdpUrl。
二、引入 ZLMRTCClient
官方 SDK 常以 UMD 形式挂到全局,例如放到public/ZLMRTCClient.js,在index.html引入:
<scriptsrc="/ZLMRTCClient.js"></script>页面里可直接使用全局变量ZLMRTCClient(Endpoint、Events)。
也可用 npm / ES Module,本文按全局脚本方式,和多数 ZLM Demo 一致。
模板里放一个<video>:
<videoref="videoRef"style="width:100%;height:auto;"muted></video>muted很重要:浏览器自动播放策略下,静音更容易play()成功。
三、核心 API:ZLMRTCClient.Endpoint
播放器本质就是创建一个Endpoint:
constplayer=ref(null)constvideoRef=ref(null)constinitVideo=(url)=>{closePlayer()constvideoDom=videoRef.value player.value=newZLMRTCClient.Endpoint({element:videoDom,// 绑定的 <video>debug:false,// 调试日志zlmsdpUrl:url,// ZLM WebRTC 信令地址simulecast:false,// 是否 Simulcast(多码率)useCamera:false,// 是否采集本地摄像头audioEnable:false,// 是否收/发音频videoEnable:true,// 是否收/发视频recvOnly:true,// 仅接收(纯播放必开)usedatachannel:false,// 是否使用 DataChannel// resolution: { w: 600, h: 340 }, // 可选:期望分辨率})// ... 事件监听见下一节}参数说明(纯播放场景)
| 配置项 | 推荐值 | 说明 |
|---|---|---|
element | <video>节点 | SDK 会把远端流挂到该元素 |
zlmsdpUrl | ZLM webrtc API | Offer/Answer 交换入口 |
recvOnly | true | 只看不推,监控预览标准配置 |
useCamera | false | 播放端不采本地摄像头 |
audioEnable | 按需 | 只要画面可关,减少权限与策略干扰 |
videoEnable | true | 必须开 |
simulecast | false | 一般单路流关闭即可(注意官方拼写) |
usedatachannel | false | 不做自定义消息时关闭 |
debug | 开发true | 排障时看 ICE/SDP 日志 |
纯播放记住一句:recvOnly: true+useCamera: false。
四、事件监听:从“连上”到“能播”
Endpoint通过on(Event, handler)订阅生命周期:
// ICE 候选失败(网络/防火墙/NAT 常见)player.value.on(ZLMRTCClient.Events.WEBRTC_ICE_CANDIDATE_ERROR,(e)=>{console.log('ICE 协商出错',e)})// 拿到远端流 —— 可以播了player.value.on(ZLMRTCClient.Events.WEBRTC_ON_REMOTE_STREAMS,(e)=>{console.log('播放成功',e.streams)videoDom.addEventListener('canplay',()=>{videoDom.play()})})// SDP Offer/Answer 交换失败(流不存在、地址错、服务挂)player.value.on(ZLMRTCClient.Events.WEBRTC_OFFER_ANWSER_EXCHANGE_FAILED,(e)=>{console.log('offer answer 交换失败',e)})// 本地流(推流场景更有用;纯播放通常可忽略)player.value.on(ZLMRTCClient.Events.WEBRTC_ON_LOCAL_STREAM,(s)=>{console.log('获取到了本地流',s)})// 采集本地流失败(useCamera=true 时)player.value.on(ZLMRTCClient.Events.CAPTURE_STREAM_FAILED,()=>{console.log('获取本地流失败')})// PeerConnection 状态:new / connecting / connected / disconnected / failed / closedplayer.value.on(ZLMRTCClient.Events.WEBRTC_ON_CONNECTION_STATE_CHANGE,(state)=>{console.log('当前状态==>',state)})// DataChannel(usedatachannel=true 时)player.value.on(ZLMRTCClient.Events.WEBRTC_ON_DATA_CHANNEL_OPEN,(event)=>{console.log('datachannel 打开',event)})player.value.on(ZLMRTCClient.Events.WEBRTC_ON_DATA_CHANNEL_MSG,(event)=>{console.log('datachannel 消息',event.data)})player.value.on(ZLMRTCClient.Events.WEBRTC_ON_DATA_CHANNEL_ERR,(event)=>{console.log('datachannel 错误',event)})player.value.on(ZLMRTCClient.Events.WEBRTC_ON_DATA_CHANNEL_CLOSE,(event)=>{console.log('datachannel 关闭',event)})事件优先级(排查顺序)
WEBRTC_OFFER_ANWSER_EXCHANGE_FAILED→ 先查zlmsdpUrl、流是否在线、跨域/HTTP。WEBRTC_ICE_CANDIDATE_ERROR/connectionState === 'failed'→ 查 ICE、端口、NAT、TURN。WEBRTC_ON_REMOTE_STREAMS有了但黑屏 → 查play()、静音策略、srcObject是否挂上。
五、Vue3 弹窗预览完整流程
设备列表点「视频预览」时:打开 Dialog →nextTick等 DOM 就绪 →initVideo(url)。
import{ref,nextTick}from'vue'constvisible=ref(false)constvideoRef=ref(null)constplayer=ref(null)functionopenDialog(row){if(!row)returnvisible.value=truenextTick(()=>{// 实际项目里用接口返回的 webrtc 地址initVideo('http://192.168.1.120:8001/index/api/webrtc?app=live&stream=test&type=play')})}constclosePlayer=()=>{if(player.value){// 关闭底层 RTCPeerConnection,避免泄漏player.value?.pc?.close()player.value=null}}functionhandleClosed(){closePlayer()// 重置业务状态...}要点:
- 必须
nextTick:Dialog 未渲染完时videoRef可能是null。 - 先
closePlayer再新建:切换流或重复打开,避免多个 PeerConnection。 - 弹窗关闭务必释放:否则后台还在收流、占带宽。
六、资源释放与封装建议
最小关闭方式:
constclosePlayer=()=>{if(player.value){player.value?.pc?.close()player.value=null}}更稳妥一点可以:
functionstop(){if(player){// 若 SDK 提供 close/destroy,优先用官方方法player.close?.()||player.destroy?.()||player.pc?.close()player=null}if(video.value){video.value.srcObject=nullvideo.value.load()}}多页面复用时,建议抽成组件(如WebRTCPlayer):
- props:
zlmsdpUrl、muted、audioEnable… - emit:
connected/failed/statechange/closed onMounted建连,onUnmounted销毁,watch(url)换流重连
业务页只关心传地址,不必每次抄一遍事件。
七、常见问题与踩坑
1. 有远端流但画面不播
浏览器限制未交互自动播放。处理:
<video muted playsinline autoplay>play()失败时再强制muted = true重试
2. Offer/Answer 失败
核对:
app/stream是否与推流一致- ZLM 是否已有该流(可用 ZLM API / 控制台看)
- HTTP/HTTPS 混用、证书、跨域
3. ICE failed
内网调试常通,公网/跨网段要配TURN。看WEBRTC_ON_CONNECTION_STATE_CHANGE是否落到failed/disconnected。
4. 拼写注意
部分版本配置是simulecast(少了一个l),事件名是WEBRTC_OFFER_ANWSER_EXCHANGE_FAILED(ANSWER写成了ANWSER)。以你引入的ZLMRTCClient.js为准,不要“纠正”官方拼写。
5. HTTPS 与安全上下文
生产环境页面尽量 HTTPS;部分浏览器对非安全上下文限制 WebRTC/媒体能力。
6. 监听泄漏
canplay若每次addEventListener不移除,重复打开会叠多个回调。可用{ once: true }或先removeEventListener。
八、最小可运行示例(精简版)
<videoid="video"mutedautoplayplaysinlinestyle="width:640px;background:#000"></video><scriptsrc="/ZLMRTCClient.js"></script><script>constvideo=document.getElementById('video')consturl='http://127.0.0.1:80/index/api/webrtc?app=live&stream=test&type=play'constendpoint=newZLMRTCClient.Endpoint({element:video,debug:true,zlmsdpUrl:url,useCamera:false,audioEnable:false,videoEnable:true,recvOnly:true,})endpoint.on(ZLMRTCClient.Events.WEBRTC_ON_REMOTE_STREAMS,()=>{video.play().catch(console.warn)})endpoint.on(ZLMRTCClient.Events.WEBRTC_OFFER_ANWSER_EXCHANGE_FAILED,(e)=>{console.error('信令失败',e)})endpoint.on(ZLMRTCClient.Events.WEBRTC_ON_CONNECTION_STATE_CHANGE,(state)=>{console.log('PC state:',state)})</script>先确认 ZLM 上该流在线,再用浏览器打开此页。
九、总结
用 ZLMRTCClient 做 WebRTC 播放,闭环其实很短:
- 引入 SDK,准备
<video> new Endpoint({ zlmsdpUrl, recvOnly: true, ... })- 监听
WEBRTC_ON_REMOTE_STREAMS再play() - 监听信令失败 / ICE / connectionState 做排障
- 离开页面或关弹窗时关闭 PeerConnection
适合监控预览、设备联调、低延迟大屏。若还要兼容旧浏览器或弱网,可再做WebRTC 优先、FLV/HLS 降级的双通道方案。
参考
- ZLMediaKit
- MDN: RTCPeerConnection.connectionState
- ZLMRTCClient 官方 Demo(随 ZLM www 目录或官方仓库)