|
|
vor 5 Tagen | |
|---|---|---|
| .vscode | vor 3 Monaten | |
| public | vor 6 Tagen | |
| src | vor 6 Tagen | |
| types | vor 3 Monaten | |
| .env | vor 3 Monaten | |
| .env.development | vor 6 Tagen | |
| .env.production | vor 6 Tagen | |
| .eslintignore | vor 3 Monaten | |
| .eslintrc.cjs | vor 3 Monaten | |
| .gitignore | vor 3 Monaten | |
| .prettierignore | vor 3 Monaten | |
| .prettierrc.json | vor 3 Monaten | |
| README.md | vor 5 Tagen | |
| auto-imports.d.ts | vor 3 Monaten | |
| components.d.ts | vor 6 Tagen | |
| env.d.ts | vor 6 Tagen | |
| index.html | vor 6 Tagen | |
| package-lock.json | vor 6 Tagen | |
| package.json | vor 6 Tagen | |
| tsconfig.app.json | vor 3 Monaten | |
| tsconfig.json | vor 3 Monaten | |
| tsconfig.node.json | vor 3 Monaten | |
| vite.config.ts | vor 3 Monaten |
WebRTCVideo 是一个基于 WebRTC 的实时视频直播 Vue 3 组件,用于从远端摄像头设备接收音视频流并在浏览器中播放。组件通过 WebSocket 信令服务器与设备建立 P2P 连接,支持自动重连、画面冻结检测、截图、扬声器/麦克风控制等功能。
技术栈:Vue 3 Composition API + TypeScript + Element Plus。
整个直播连接分为三个阶段:信令协商 → ICE 连接 → 媒体播放。
组件挂载
│
├─ 1. 建立 WebSocket 信令连接
│ ws://{host}:{port}/wswebclient/{meid}
│
├─ 2. WebSocket 打开后发送 __connectto(告诉服务器要连哪个设备)
│
├─ 3. 服务器返回 _create(设备在线状态 + ICE 服务器配置)
│
├─ 4. 发送 __call(发起呼叫,携带音视频方向、认证信息)
│
├─ 5. 服务器返回 _call 或 _offer(SDP 协商开始)
│ ├─ 收到 _call → 创建 PeerConnection → createOffer → 发送 __offer
│ └─ 收到 _offer → 创建 PeerConnection → createAnswer → 发送 __answer
│
├─ 6. 收到 _answer → setRemoteDescription(SDP 协商完成)
│
├─ 7. ICE Candidate 交换(__ice_candidate / _ice_candidate)
│
├─ 8. ICE 连接建立(checking → connected)
│
├─ 9. 远端 Track 到达 → video.srcObject 赋值 → video.play()
│
└─ 10. 画面显示,进入稳定播放状态
正常情况下,从 WebSocket 打开到画面显示约 100-500ms。
| 方向 | 消息 | 说明 |
|---|---|---|
| → 发送 | __connectto |
请求连接指定设备 |
| ← 接收 | _create |
设备状态(online/offline)及 ICE 配置 |
| → 发送 | __call |
发起呼叫,携带音视频方向和认证信息 |
| ← 接收 | _call / _offer |
服务器要求创建 Offer 或直接下发 Offer |
| → 发送 | __offer / __answer |
SDP 协商 |
| ← 接收 | _answer |
远端 SDP 应答 |
| ↔ 双向 | __ice_candidate / _ice_candidate |
ICE 候选地址交换 |
| → 发送 | __ping |
心跳保活(每 30 秒) |
| → 发送 | __disconnected |
主动断开通知 |
| ← 接收 | _session_failed |
会话失败,需要重新连接 |
| ← 接收 | _session_disconnected |
远端断开通知 |
| ← 接收 | _connectinfo |
连接信息(调试用) |
_session_failed 后立即用新 sessionId 重新发起连接__disconnected,让服务器立即清理旧 session每 3 秒通过 RTCPeerConnection.getStats() 检查视频流的 bytesReceived:
createOffer({ iceRestart: true }))恢复连接<video> 元素是否意外暂停,自动恢复播放组件默认以 recvonly 模式连接(只接收远端音视频),不请求本地摄像头/麦克风权限,避免浏览器弹出权限弹窗影响连接速度。只有用户主动点击"开启麦克风"时才调用 getUserMedia 获取音频轨道,并动态添加到 PeerConnection。
所有关键步骤带有时间戳日志,格式为 [WebRTC][tag]+XXXms,从 WebSocket 开始连接计时。示例:
[WebRTC][ws]+0ms connecting... ws://192.168.1.20:8080/wswebclient/xxx
[WebRTC][ws]+3ms opened
[WebRTC][signal]+3ms sendConnect to ANSJ-00-...
[WebRTC][msg]+5ms _create
[WebRTC][signal]+5ms callDevice
[WebRTC][msg]+20ms _offer
[WebRTC][pc]+21ms PeerConnection created
[WebRTC][track]+23ms remote track received, kind: video
[WebRTC][ice]+70ms checking
[WebRTC][ice]+120ms connected
[WebRTC][play]+120ms attempting video.play()
[WebRTC][play]+200ms playing!
| 属性 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
serno |
string |
✅ | — | 设备序列号 |
wsUrl |
string |
— | 自动推导 | 信令 WebSocket 地址,不传则根据环境变量拼接 |
iceServers |
RTCIceServer[] |
— | Google STUN | ICE 服务器配置,通常由信令服务器下发 |
mode |
string |
— | 'live' |
连接模式:live(直播)/ playback(回放) |
source |
string |
— | 'MainStream' |
码流:MainStream(主码流)/ SubStream(子码流) |
enableDataChannel |
boolean |
— | true |
是否启用 DataChannel |
dragFlag |
boolean |
— | false |
是否允许鼠标拖拽和滚轮缩放 |
sessionType |
string |
— | 'IE' |
会话类型标识 |
user |
string |
— | 'admin' |
设备认证用户名 |
pwd |
string |
— | 'admin' |
设备认证密码 |
| 事件 | 参数 | 说明 |
|---|---|---|
connected |
— | ICE 连接建立成功 |
disconnected |
— | 连接断开 |
error |
Error \| string |
连接或播放错误 |
datachannel-message |
any |
收到 DataChannel 消息 |
| 方法/属性 | 类型 | 说明 |
|---|---|---|
reconnect() |
function |
手动断开并重新连接 |
captureSnapshot() |
function |
截取当前画面并下载为 PNG |
sendDataChannelMessage(msg) |
function |
通过 DataChannel 发送消息 |
toggleSpeaker() |
function |
切换扬声器开关 |
toggleMic() |
function |
切换麦克风开关(首次会请求权限) |
isPlaying |
Ref<boolean> |
是否正在播放(只读) |
speakerEnabled |
Ref<boolean> |
扬声器是否开启(只读) |
micEnabled |
Ref<boolean> |
麦克风是否开启(只读) |
connectionStatus |
Ref<string> |
连接状态(只读):idle / connecting / connected / disconnected / failed |
<template>
<WebRTCVideo serno="ANSJ-00-I7N3-QINU-00000027" />
</template>
<script setup>
import WebRTCVideo from '@/components/WebRTCVideo.vue'
</script>
<template>
<WebRTCVideo
ref="videoRef"
:serno="deviceSerno"
mode="live"
source="MainStream"
session-type="IE"
user="admin"
pwd="admin123"
:drag-flag="true"
:enable-data-channel="true"
@connected="onConnected"
@disconnected="onDisconnected"
@error="onError"
@datachannel-message="onMessage"
/>
</template>
<script setup>
import { ref } from 'vue'
import WebRTCVideo from '@/components/WebRTCVideo.vue'
const videoRef = ref()
const deviceSerno = ref('ANSJ-00-I7N3-QINU-00000027')
function onConnected() {
console.log('直播已连接')
}
function onDisconnected() {
console.log('直播已断开')
}
function onError(err) {
console.error('直播错误:', err)
}
function onMessage(msg) {
console.log('DataChannel 消息:', msg)
}
// 通过 ref 调用组件方法
function handleScreenshot() {
videoRef.value?.captureSnapshot()
}
function handleReconnect() {
videoRef.value?.reconnect()
}
</script>
访问 http://localhost:3333/preview?serno=YOUR_DEVICE_SERNO 即可连接指定设备。
在 .env.development 和 .env.production 中配置:
# WebRTC 信令服务器 IP(开发环境使用)
VITE_WEBRTC_SIGNAL_IP=192.168.1.20
# WebRTC 信令服务器端口
VITE_WEBRTC_SIGNAL_PORT=8080
VITE_WEBRTC_SIGNAL_IP:VITE_WEBRTC_SIGNAL_PORTsrc/
├── components/
│ └── WebRTCVideo.vue # WebRTC 直播组件
├── views/
│ └── preview/
│ └── index.vue # 直播预览页面(使用 WebRTCVideo)