更新记录
1.1.0(2026-06-27) 下载此版本
新增
NativeSignalAdapter新增「单 PC 多路复用」媒体协商能力:每对 peer 共享一条RTCPeerConnection,节省 ICE/DTLS 握手开销。- 暴露
getPeerConnection / getPeerConnections / onPeerConnection / onPeerConnectionClose / onBeforeAnswer / onAfterAnswer / onMediaSourceMap / sendMediaSourceMap / renegotiate等钩子。 - 引入
media-offer / media-answer / media-candidate / media-source-map四类信令,与NativeNegotiator的offer/answer/candidate/close解耦互不干扰。 - 内置 Perfect Negotiation(polite/impolite)抗 glare:host 端为 polite 让步方,client 端为 impolite 强势方,自动回滚冲突 offer。
- 暴露
NativeMediaConnectionManager正式纳入ConnectCore:通过signal.useUnifiedMediaPc: true启用,与 PeerJS-styleMediaConnectionManager二选一。- 支持 host 中心服转发其他客户端流,并通过
media-source-map信令告知接收端trackId → sourcePeerId/viaPeerId映射。
- 支持 host 中心服转发其他客户端流,并通过
ConnectCore.initMedia双路径分发:根据signal.useUnifiedMediaPc与适配器能力选择媒体管理器实现。ConnectCore.wireEvents:peer-leave触发时自动调用signal._closeMediaPc(peerId)回收共享媒体 PC。
修复
MediaConnectionManager.setLocalStream(null)兼容空流,避免读取null.getVideoTracks()崩溃;同时支持热切换:对已有 outgoing call 通过replaceTrack替换轨道而不重协商。MediaConnectionManager._bindCallEvents修正中心服转发判定:仅对direction=incoming且relayed=false的来电做 relay,避免 host 自身 outgoing call 被错误转发以及 relayed call 被二次扩散造成环路。MediaConnectionManager._callsslot 补全direction(outgoing/incoming)与relayed字段;startAudioCall / answerCall / startVideoCall / answerVideoCall统一写入。MediaConnectionManager.relayRemoteStream加固:排除 sourcePeer 自身与 host 自己;relay outgoing call 错误时清理_relayCalls记录,便于重试。MediaConnectionManager._bindCallEvents.on('close'):host 端在来源端断开时同步stopRelay(peerId),避免遗留出向 call。NativeMediaConnectionManager.setLocalStream(null)兼容空流。NativeMediaConnectionManager._handlePeerClose:清理其他 slot 中以离开 peer 为 target 的 relay senders;清理_remoteSources中以离开 peer 为 sourcePeerId 或 viaPeerId 的所有记录,避免幽灵流。NativeNegotiator.handleOffer修复 data 通道异步竞态:等待对端ondatachannel触发后再发送 answer,避免conn未就绪导致数据丢失。NativeNegotiator.handleAnswer / handleCandidate / flushCandidates增加signalingState === 'closed'守卫与 try/catch 防御,迟到的 answer/candidate 不再阻断后续流程。PeerLinkBase.attachConnection:- 同
peerId重连时显式关闭旧 conn、解除心跳订阅,避免事件残留与「幽灵 peer-leave」。 - 握手超时 / 握手期间 close / 错误时回收
conns中残留 slot,防止下次相同 peerId 重连被误判为已存在。 - 握手阶段被对端 close 走 reject 分支,不再误发
peer-leave。
- 同
ConnectCore.relayClientMessage:- 新增排除
type === 'request',防止 1:1 RPC 请求被房主广播放大。 - 冗余加一道
peer.peerId === message.from兜底,避免includeSender: true时回环到发送者自己。
- 新增排除
1.0.4(2026-06-21) 下载此版本
- 重构
native信令模式,新增NativePeer、NativeNegotiator、NativeMediaConnection,使 native 内部连接模型对齐 PeerJS 的Peer / DataConnection / MediaConnection抽象,但不依赖 PeerJS 服务端协议。 native数据连接与媒体连接改为独立RTCPeerConnection,降低 DataChannel 与音视频 track 共用连接导致的重协商风险。custom模式支持两种接入方式:完整 custom adapter 继续直接使用;仅提供 transport 时由插件包装为NativeSignalAdapter + NativePeer,无需引入 PeerJS。ConnectCore媒体初始化统一复用MediaConnectionManager,PeerJS 使用 PeerJS Peer,native/custom transport 使用 NativePeer,对外媒体 API 保持不变。- 增强 Host 中心化多人音视频转发能力,转发时排除原始发送者,避免成员收到自己的回声。
- 修复 relayed audio 自动应答时回传本地麦克风的问题,转发通话默认使用空
MediaStream应答。 - 增强
MessageCodec分片缓存保护,增加 TTL、最大消息数和最大分片数限制。 - 修复 Client 重连监听重复绑定、NativeDataConnection close 重复派发、Client 侧 DataStore 本地写入偏离 Host 权威状态等边界问题。
- 更新 native/custom 多人音视频方案、Peer-like native 重构方案与风险修复清单文档。
1.0.3(2026-06-20) 下载此版本
- 修复已知bug,优化性能。
平台兼容性
uni-app(4.74)
| Vue2 | Vue3 | Chrome | Safari | app-vue | app-nvue | Android | iOS | 鸿蒙 |
|---|---|---|---|---|---|---|---|---|
| - | √ | √ | √ | × | × | × | × | × |
| 微信小程序 | 支付宝小程序 | 抖音小程序 | 百度小程序 | 快手小程序 | 京东小程序 | 鸿蒙元服务 | QQ小程序 | 飞书小程序 | 小红书小程序 | 快应用-华为 | 快应用-联盟 |
|---|---|---|---|---|---|---|---|---|---|---|---|
| × | × | × | × | × | × | × | × | × | × | × | × |
jz-webrtc-connect
jz-webrtc-connect 从 1.0.3 版本开始,是一个面向 uni-app H5 的 WebRTC DataChannel 通讯插件,用于构建房间协作、实时指令同步、局域网或互联网点对点消息传输、音视频通话等场景。
本版本修复已知bug,优化性能。
它聚焦的是"数据通讯 + 媒体能力"而不是 UI:
- 提供统一的连接核心
ConnectCore - 支持
PeerJS、native WebSocket 信令、custom adapter - 提供可选的数据同步层
DataCore - 支持
host / client双角色模型 - 支持心跳、ACK、请求响应、客户端重连、Host 广播
- 支持基于 PeerJS 的音频通话与视频通话
- 支持音量监听、摄像头切换、静音、视频开关
为什么它比纯 WebSocket 更省资源
很多实时业务一开始会直接上 WebSocket,但纯 WebSocket 有一个天然问题:
- 所有业务数据都必须经过服务器转发
- 房间人数一多,服务端带宽、连接数、转发 CPU、内存压力都会持续上升
- 每条消息都要走"客户端 -> 服务器 -> 客户端",延迟路径更长
jz-webrtc-connect 的核心优势在于:
- WebSocket 或 PeerServer 主要只负责"信令交换"
- 真正的业务消息走 WebRTC
DataChannel - 在 NAT 打洞成功时,数据直接在终端之间传输,不经过业务服务器
- 服务器不再承担高频业务包转发,资源占用明显更低
可以简单理解为:
- 纯 WebSocket:服务器既负责建链,也负责所有数据中转
- WebRTC DataChannel:服务器主要负责"介绍双方认识",连通后数据尽量点对点直达
这意味着在打洞成功后,你可以获得这些收益:
- 节约服务器带宽
- 降低消息中转成本
- 降低服务端并发转发压力
- 降低中心节点故障对业务消息面的影响
- 在很多场景下得到更低的传输延迟
重要边界
请务必正确理解 WebRTC 的工作边界:
signal依然是必须的,建连前必须交换offer / answer / candidatenative模式里通常使用 WebSocket 做信令peerjs模式里由 PeerServer 帮你完成信令交换- 如果网络环境复杂,可能需要 TURN 中继
- 一旦退化到 TURN,中继流量会经过 TURN 服务器,不再是完全点对点
- 媒体通话功能(音频/视频)目前仅支持
PeerJS信令模式
所以更准确的表述是:
在 STUN / NAT 打洞成功时,业务数据可绕过业务服务器直连传输;如果必须依赖 TURN,则数据会经过 TURN 中继。
典型场景
- 实时房间控制指令同步
- 多端协作状态同步
- 局域网点对点通讯
- 轻量级实时消息广播
- 低频信令 + 高频业务数据的拆分架构
- 一对一或房间式音频通话
- 视频通话与摄像头切换
插件结构分析
src/uni_modules/jz-webrtc-connect 目录大致分为五层:
1. 入口层
js_sdk/jz-webrtc-connect/index.js
对外暴露统一 API:
configure()保存低频基础配置connect()发起一次真实建连getInstance()获取当前连接核心destroy()销毁当前连接createDataCore()创建可选数据同步层
2. 连接层
connect/ConnectCore.jsconnect/HostPeerLink.jsconnect/ClientPeerLink.jsconnect/PeerLinkBase.jsconnect/HeartbeatMonitor.jsconnect/MessageCodec.js
职责是:
- 根据角色创建
host或client链路 - 管理
DataChannel生命周期 - 提供
send / broadcast / request / kick - 处理心跳、ACK、请求响应、重连等基础能力
3. 信令适配层
connect/adapters/PeerJSSignalAdapter.jsconnect/adapters/NativeSignalAdapter.jsconnect/adapters/SignalAdapterBase.jsconnect/adapters/NativeDataConnection.js
职责是:
- 负责建连前的信令交换
- 对上层输出统一
DataConnection接口 - 屏蔽
PeerJS与原生RTCPeerConnection的差异
4. 媒体层
media/MediaConnectionManager.jsmedia/AudioVolumeMonitor.jsmedia/adapters/NativeMediaAdapter.js
职责是:
- 管理 PeerJS
MediaConnection的生命周期 - 提供音频通话与视频通话 API
- 支持摄像头切换、静音、视频开关
- 实时监测本地与远端音频流音量
5. 数据同步层
data/DataCore.jsdata/ReplicationManager.jsdata/KeyValueStore.jsdata/ListStore.jsdata/MapStore.jsdata/StorageDriver.js
职责是:
- 基于通讯核心做房间数据同步
- Host 作为权威数据源
- Client 通过 patch 或 snapshot 跟随同步
- 支持
kv / list / map三种命名空间数据容器
基础用法
推荐把"低频基础配置"和"高频业务参数"分开:
configure():保存不常变化的基础配置,例如信令地址、ICE 配置、重连策略connect():传入每次连接都可能变化的参数,例如role、roomId
1. 导入
import jzWebrtcConnect from '@/uni_modules/jz-webrtc-connect/js_sdk/jz-webrtc-connect'
2. 初始化基础配置
await jzWebrtcConnect.configure({
signal: {
adapter: 'peerjs',
// 不配置 peerjs 时默认使用 PeerJS 公共云信令(0.peerjs.com),可快速跑通联调
// 生产环境请替换为自建 PeerServer 地址
peerjs: {
host: '0.peerjs.com',
port: 443,
path: '/',
secure: true
}
},
rtcConfig: {
iceServers: [
{ urls: 'stun:stun.qq.com:3478' },
{ urls: 'stun:stun.aliyun.com:3478' }
]
},
reconnect: {
enabled: true,
maxAttempts: 5,
baseDelayMs: 1000,
factor: 2,
maxDelayMs: 16000
}
})
3. Host 创建房间
const core = await jzWebrtcConnect.connect({
role: 'host',
roomId: 'room-001'
})
core.on('ready', event => {
console.log('host ready', event.peerId)
})
core.on('peer-join', event => {
console.log('peer joined', event.peerId)
})
core.on('message', event => {
console.log('message', event)
})
4. Client 加入房间
const core = await jzWebrtcConnect.connect({
role: 'client',
roomId: 'room-001'
})
core.on('ready', event => {
console.log('client ready', event.peerId)
})
消息发送用法
1. 单播消息
await core.send('peer-xxx', 'chat', {
type: 'text',
payload: { text: 'hello' }
})
如果你不想自己写 type / payload 结构,也可以直接传业务对象:
await core.send('peer-xxx', 'chat', { text: 'hello' })
这时内部会自动转成:
{ type: 'msg', payload: { text: 'hello' } }
2. ACK 确认
const result = await core.send('peer-xxx', 'chat', { text: 'need ack' }, {
ack: true,
timeoutMs: 5000
})
console.log(result.acked) // true
3. Host 广播
只有 host 可以调用 broadcast():
await core.broadcast('room', {
type: 'notice',
payload: { text: 'game start' }
})
4. 请求响应
发起方:
const result = await core.request('peer-xxx', 'biz', {
action: 'get-profile'
})
console.log(result)
接收方:
core.on('request', event => {
if (event.channel !== 'biz') return
event.respond({
id: 1,
name: 'demo-user'
})
})
5. Host 踢出客户端
只有 host 可以调用 kick():
core.kick('peer-xxx', 'violation')
6. 消息转发(relay)
Host 收到客户端业务消息后,可自动转发给房间其他客户端:
await jzWebrtcConnect.configure({
relay: {
enabled: true, // 是否开启消息转发
includeSender: true // 转发时是否包含原发送者
}
})
注意:
- 转发发生在 Host 节点上,不经过中心业务服务器
- 系统消息(心跳、ACK、响应)不会被转发
- 适合一个房主带多个成员的房间式架构
数据同步层用法
如果你不只是收发消息,还希望同步一份房间内共享数据,可以创建 DataCore。
const core = await jzWebrtcConnect.connect({
role: 'host',
roomId: 'room-001'
})
const data = jzWebrtcConnect.createDataCore({ core })
const roomStore = data.namespace('room', 'kv')
roomStore.set('status', 'open')
roomStore.set('round', 1)
支持的数据容器
KeyValueStore(kv)
键值对象,适合房间状态、配置项:
const kv = data.namespace('settings', 'kv')
kv.set('maxPlayers', 8)
kv.set('mode', 'competitive')
kv.get('mode') // 'competitive'
kv.remove('mode')
kv.clear()
ListStore(list)
列表容器,适合成员队列、聊天记录:
const list = data.namespace('messages', 'list')
list.push({ from: 'user1', text: 'hello' })
list.push({ from: 'user2', text: 'hi' })
list.values() // [{ from: 'user1', text: 'hello' }, { from: 'user2', text: 'hi' }]
list.get(0) // { from: 'user1', text: 'hello' }
list.set(0, { from: 'user1', text: 'hello!' })
list.remove(0)
list.clear()
MapStore(map)
映射容器,适合按主键存储复杂结构:
const map = data.namespace('players', 'map')
map.set('user-1', { name: 'Alice', score: 100 })
map.set('user-2', { name: 'Bob', score: 80 })
map.get('user-1') // { name: 'Alice', score: 100 }
map.has('user-1') // true
map.values() // [{ name: 'Alice', score: 100 }, { name: 'Bob', score: 80 }]
map.entries() // [['user-1', { name: 'Alice', score: 100 }], ...]
map.remove('user-1')
map.clear()
数据同步机制
- Host 是权威数据源,Host 上的 Store 变更会自动通过
data-sync通道广播 patch 给所有 Client - Client 监听
data-sync通道的 patch 消息,自动应用到本地同名 Store - Client 重连成功后会自动触发
reconcileAll(),向 Host 请求全量快照对账 - 也可以手动调用
reconcileAll()触发对账:
// Client 侧
await data.reconcileAll()
监听 Store 变更
const store = data.namespace('room', 'kv')
store.subscribe(patch => {
console.log('store changed:', patch)
// patch = { namespace, kind, version, op, value, ts }
})
可选持久化
默认使用内存驱动,也可以切换为 UniStorageDriver:
import jzWebrtcConnect, { UniStorageDriver } from '@/uni_modules/jz-webrtc-connect/js_sdk/jz-webrtc-connect'
const data = jzWebrtcConnect.createDataCore({ core, autoPersist: true })
data.use(new UniStorageDriver('demo-room:'))
持久化驱动接口:
class StorageDriver {
async save(key, value) {}
async load(key) { return null }
async remove(key) {}
}
内置驱动:
| 驱动 | 说明 |
|---|---|
MemoryDriver |
默认,数据保存在内存中,页面刷新后丢失 |
UniStorageDriver |
基于 uni.setStorageSync / uni.getStorageSync,适合 uni-app H5 环境 |
从持久化驱动恢复数据:
await data.hydrate('room') // 从驱动加载快照并恢复到 Store
媒体通话用法
媒体通话功能目前仅支持 PeerJS 信令模式。使用前需要先通过 connect() 建立数据通讯连接。
音频通话
1. 设置本地音频流
// 获取麦克风
const stream = await navigator.mediaDevices.getUserMedia({ audio: true })
core.setLocalStream(stream)
2. 发起音频通话
core.startAudioCall('peer-xxx')
3. 接听音频通话
core.on('audio-call-incoming', event => {
console.log('incoming audio call from', event.peerId)
core.answerAudioCall(event.peerId)
})
4. 收到远端音频流
core.on('audio-stream-ready', event => {
console.log('remote audio stream ready', event.peerId, event.stream)
// 将 event.stream 绑定到 <audio> 元素播放
})
5. 停止音频通话
core.stopAudioCall('peer-xxx')
6. 静音控制
// 静音本地麦克风
core.setAudioMuted('local', true)
// 取消静音
core.setAudioMuted('local', false)
视频通话
1. 设置本地视频流
const stream = await navigator.mediaDevices.getUserMedia({
audio: {
echoCancellation: true,
noiseSuppression: true,
autoGainControl: true
},
video: {
facingMode: 'user',
width: { ideal: 640 },
height: { ideal: 480 },
frameRate: { ideal: 30, max: 60 }
}
})
core.setLocalVideoStream(stream)
也可以先设置视频配置再设置流:
core.setVideoConfig({
facingMode: 'user',
width: { ideal: 1280 },
height: { ideal: 720 },
frameRate: { ideal: 30, max: 60 }
})
2. 发起视频通话
core.startVideoCall('peer-xxx')
3. 接听视频通话
core.on('video-call-incoming', event => {
console.log('incoming video call from', event.peerId)
core.answerVideoCall(event.peerId)
})
4. 收到远端视频流
core.on('video-stream-ready', event => {
console.log('remote video stream ready', event.peerId, event.stream)
// 将 event.stream 绑定到 <video> 元素播放
})
5. 停止视频通话
core.stopVideoCall('peer-xxx')
6. 摄像头切换
const newStream = await core.switchCamera()
console.log('switched camera, new facing mode')
摄像头切换使用 RTCRtpSender.replaceTrack(),不会中断正在进行的通话。
7. 视频开关
// 关闭本地视频(不中断通话,对方看到黑屏)
core.setVideoEnabled('local', false)
// 重新开启
core.setVideoEnabled('local', true)
音量监听
音量监听会在设置本地流和收到远端流时自动启动。
core.on('audio-volume', event => {
console.log(`${event.type} volume:`, event.volume) // 0 ~ 1
// event.peerId: peerId 或 'local'
// event.type: 'local' | 'remote'
// event.volume: 0 ~ 1 的归一化音量值
})
获取通话列表
const audioPeers = core.getAudioPeers()
// [{ peerId, muted, hasStream }, ...]
const videoPeers = core.getVideoPeers()
// [{ peerId, muted, videoEnabled, hasStream }, ...]
获取远端流
const remoteStream = core.getRemoteStream('peer-xxx')
自动应答
如果希望收到通话邀请时自动接听,可以在 configure 中设置:
await jzWebrtcConnect.configure({
media: {
autoAnswer: true
}
})
媒体配置项
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
media.autoAnswer |
boolean |
false |
是否自动应答 incoming call |
media.audioVolumeInterval |
number |
100 |
音量监听轮询间隔(毫秒) |
媒体事件说明
| 事件名 | 说明 | 事件数据 |
|---|---|---|
audio-call-incoming |
收到音频通话邀请 | { peerId, call, type } |
audio-stream-ready |
远端音频流就绪 | { peerId, stream } |
audio-stream-close |
音频流关闭 | { peerId } |
audio-volume |
音量变化 | { peerId, volume, type } |
audio-mute-change |
静音状态变化 | { peerId, muted } |
audio-local-stream |
本地音频流设置完成 | { stream } |
audio-error |
音频相关错误 | { peerId, error } |
video-call-incoming |
收到视频通话邀请 | { peerId, call, type } |
video-stream-ready |
远端视频流就绪 | { peerId, stream } |
video-stream-close |
视频流关闭 | { peerId } |
video-camera-switched |
摄像头切换完成 | { facingMode, stream } |
video-enabled-change |
视频开关变化 | { peerId, enabled } |
video-local-stream |
本地视频流设置完成 | { stream } |
video-call-started |
视频通话发起 | { peerId } |
video-call-stopped |
视频通话停止 | { peerId } |
video-error |
视频相关错误 | { peerId, error } |
配置项说明
下面按实际源码能力说明常用配置项。
configure(baseConfig)
低频基础配置,内部只缓存,不会立即建连。
connect(sessionConfig)
会把 sessionConfig 与此前 configure() 的配置做合并,然后真正创建连接。
顶层配置
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
role |
'host' \| 'client' |
无 | 必填,当前节点角色 |
roomId |
string |
无 | 房间 ID;peerjs 模式下 Host 默认会把它作为 peerId |
peerId |
string |
自动生成或由适配器生成 | 当前节点自定义 ID |
hostPeerId |
string |
roomId |
Client 连接的 Host peerId;不传时通常回退到 roomId |
maxClients |
number |
8 |
Host 最大接入人数 |
handshakeTimeoutMs |
number |
15000 |
DataChannel 握手超时毫秒数 |
heartbeatIntervalMs |
number |
5000 |
心跳发送间隔 |
heartbeatMissThreshold |
number |
3 |
连续丢失多少次心跳判定离线 |
codec |
string |
'json' |
消息编解码方式,支持 'json' 和 'msgpack' |
msgpack |
object |
null |
MessagePack 实现,需提供 encode / decode 方法 |
rtcConfig |
RTCConfiguration |
{} |
WebRTC ICE 配置 |
reconnect |
object |
见下文 | Client 重连策略 |
relay |
object |
{ enabled: true, includeSender: true } |
Host 是否把客户端消息继续转发给房间其他成员 |
signal |
object |
{ adapter: 'peerjs' } |
信令适配器配置 |
media |
object |
见媒体配置 | 媒体相关配置 |
rtcConfig
这是 WebRTC 能否成功直连的关键配置之一,通常至少需要配置 iceServers:
rtcConfig: {
iceServers: [
{ urls: 'stun:stun.qq.com:3478' },
{ urls: 'stun:stun.aliyun.com:3478' },
{
urls: 'turn:turn.example.com:3478',
username: 'demo',
credential: 'demo-password'
}
]
}
说明:
- STUN 用于探测公网候选地址,帮助打洞
- TURN 用于复杂网络下的中继兜底
- 如果你非常强调"打洞成功后不走服务器",就必须重视 STUN/TURN 配置质量
- 公共 STUN 仅适合测试,生产环境建议自建或购买稳定 TURN 服务
- 插件默认使用国内可用性更好的公共 STUN(stun.qq.com、stun.aliyun.com 等),不使用 Google STUN
reconnect
仅客户端侧有效:
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
enabled |
boolean |
true |
Host 断开后是否自动重连 |
maxAttempts |
number |
5 |
最大重试次数 |
baseDelayMs |
number |
1000 |
首次重连延迟 |
factor |
number |
2 |
指数退避倍数 |
maxDelayMs |
number |
16000 |
最大重连延迟 |
运行时调整重连策略:
core.setReconnect({ maxAttempts: 10, baseDelayMs: 2000 })
relay
Host 收到客户端业务消息后,可继续通过 WebRTC 数据通道转发给房间其他客户端:
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
enabled |
boolean |
true |
是否开启消息转发 |
includeSender |
boolean |
true |
转发时是否包含原发送者 |
注意:
- 这里的"转发"发生在房主节点
host上 - 它依然不经过中心业务服务器
- 适合一个房主带多个成员的房间式架构
signal 配置说明
1. peerjs 模式
适合:
- 希望快速接入
- 已有 PeerServer
- 不想自己处理
offer / answer / candidate - 需要使用媒体通话功能
示例:
await jzWebrtcConnect.configure({
signal: {
adapter: 'peerjs',
// 不配置 peerjs 时默认使用 PeerJS 公共云信令(0.peerjs.com),可快速跑通联调
// 生产环境请替换为自建 PeerServer 地址
peerjs: {
host: '0.peerjs.com',
port: 443,
path: '/',
secure: true
}
},
rtcConfig: {
iceServers: [{ urls: 'stun:stun.qq.com:3478' }]
}
})
signal.peerjs 会透传给 PeerJS 构造配置,常见字段包括:
| 配置项 | 说明 |
|---|---|
host |
PeerServer 域名或 IP |
port |
PeerServer 端口 |
path |
PeerServer 路径 |
secure |
是否使用 HTTPS / WSS |
debug |
PeerJS 调试级别(0=关闭,1=错误,2=警告,3=全部) |
| 其他 PeerJS 支持字段 | 会一起透传给 PeerJS |
说明:
- 不配置
signal.peerjs时,默认使用 PeerJS 公共云信令0.peerjs.com,可快速跑通联调 - PeerJS 负责的是信令和 DataConnection 封装
- WebRTC 的 STUN / TURN 仍然由
rtcConfig决定 - Host 若未手动传
peerId,默认会优先使用roomId - PeerJS 初始化失败时会自动重试(最多 3 次,指数退避),不可恢复的错误(如 ID 无效、浏览器不兼容)不会重试
PeerJS 常见错误提示
插件内置了 PeerJS 错误类型的友好提示映射:
| 错误类型 | 说明 |
|---|---|
unavailable-id |
信令连接失败,可能是网络问题或房间 ID 已被占用 |
invalid-id |
房间 ID 无效 |
browser-incompatible |
当前浏览器不支持 WebRTC |
ssl-unavailable |
需要 HTTPS 环境 |
api-unavailable |
PeerJS 信令服务暂时不可用 |
network |
网络连接失败 |
server-error |
信令服务器错误 |
2. native 模式
适合:
- 自己掌控信令服务器
- 希望完全使用原生
RTCPeerConnection - 想把信令与业务数据彻底分离
示例:
await jzWebrtcConnect.configure({
signal: {
adapter: 'native',
native: {
url: 'wss://signal.example.com/ws',
token: 'login-token'
}
},
rtcConfig: {
iceServers: [
{ urls: 'stun:stun.qq.com:3478' },
{ urls: 'stun:stun.aliyun.com:3478' }
]
}
})
const core = await jzWebrtcConnect.connect({
role: 'client',
roomId: 'room-001'
})
signal.native 配置项:
| 配置项 | 类型 | 说明 |
|---|---|---|
url |
string |
WebSocket 信令服务地址 |
token |
string |
可选认证字段,发送信令时会自动附带 |
transport |
object |
自定义传输对象,优先级高于 url |
hostPeerId |
string |
可选,Client 未显式传远端时的默认 Host peerId |
native 模式服务端需要做什么
服务端逻辑非常轻:
- 按
roomId区分房间 - 按
to定向转发信令包 - 无需理解 SDP 内容
- 无需转发业务数据
插件内部会处理的信令类型包括:
host-ready:Host 上线通知join:Client 加入房间offer:SDP offeranswer:SDP answercandidate:ICE candidateleave:节点离开
也就是说,WebSocket 在这个架构中主要只是"建连协调员",不是"业务转发通道"。
自定义 transport 接口
如果不想直接使用默认 WebSocket,可以传一个自定义对象,插件会按下面的最小接口调用:
const transport = {
async connect() {},
onMessage(handler) {},
send(payload) {},
close() {}
}
3. custom 模式
适合:
- 你已经有自己的信令适配器
- 你希望复用插件上层的
ConnectCore / PeerLink / DataCore
示例:
import jzWebrtcConnect, { SignalAdapterBase } from '@/uni_modules/jz-webrtc-connect/js_sdk/jz-webrtc-connect'
class MySignalAdapter extends SignalAdapterBase {
async init() {
this.peerId = 'custom-peer-id'
return this.peerId
}
connect(remotePeerId) {
return createYourOwnDataConnection(remotePeerId)
}
}
await jzWebrtcConnect.configure({
signal: {
adapter: 'custom',
custom: new MySignalAdapter()
}
})
自定义适配器最少需要实现:
init():初始化信令通道,返回 peerIdconnect(remotePeerId):主动连接远端,返回 DataConnectiononIncoming(handler):监听入站连接onDisconnected(handler):监听信令断开onError(handler):监听信令错误destroy():释放资源
DataConnection 最小接口约定:
peer:远端 peerId(string)on(event, handler):注册事件监听(open / data / close / error)send(data):发送数据close():关闭连接
事件说明
ConnectCore 通讯事件
| 事件名 | 说明 | 事件数据 |
|---|---|---|
ready |
当前节点初始化完成 | { peerId } |
peer-join |
有远端节点接入 | { peerId, role } |
peer-leave |
有远端节点离开 | { peerId, code, reason } |
message |
收到普通消息 | { from, channel, type, payload, envelopeId, replyTo } |
request |
收到 RPC 请求 | { from, channel, payload, envelopeId, respond } |
error |
连接或编解码相关错误 | { code, message, peerId?, error? } |
state-change |
核心状态变化 | { prev, next } |
reconnect-attempt |
客户端开始重连 | { attempt, delayMs } |
reconnect-success |
客户端重连成功 | { peerId } |
reconnect-fail |
客户端重连失败 | { attempts } |
常见监听方式:
core.on('peer-join', event => {
console.log(event.peerId, event.role)
})
core.on('peer-leave', event => {
console.log(event.peerId, event.code, event.reason)
})
core.on('error', event => {
console.error(event.code, event.message)
})
ConnectCore API 一览
| 方法 | 说明 | 参数 |
|---|---|---|
send(peerId, channel, data, options?) |
单播消息 | options: { ack, timeoutMs, codec } |
broadcast(channel, data, options?) |
Host 广播 | options: { codec } |
request(peerId, channel, data, options?) |
RPC 请求 | options: { timeoutMs, codec } |
kick(peerId, reason?) |
Host 踢出客户端 | reason: string |
setReconnect(policy) |
运行时调整重连策略 | policy: { enabled?, maxAttempts?, ... } |
on(event, handler) |
注册事件监听 | 返回取消订阅函数 |
off(event, handler) |
移除事件监听 | |
once(event, handler) |
注册一次性事件监听 | 返回取消订阅函数 |
getPeers() |
获取当前已连接 peer 列表 | 返回 [{ peerId, role, state, rtt, joinedAt }] |
getState() |
获取核心状态 | 返回 IDLE / READY / OPEN / CLOSING / CLOSED |
dispose() |
销毁连接 |
媒体 API
| 方法 | 说明 |
|---|---|
setLocalStream(stream) |
设置本地音频流 |
startAudioCall(peerId) |
发起音频通话 |
answerAudioCall(peerId, stream?) |
应答音频通话 |
stopAudioCall(peerId) |
停止音频通话 |
setAudioMuted(peerId, muted) |
设置静音(peerId='local' 控制本地) |
setVideoConfig(config) |
设置视频配置 |
setLocalVideoStream(stream, videoConfig?) |
设置本地视频流 |
startVideoCall(peerId) |
发起视频通话 |
answerVideoCall(peerId, stream?) |
应答视频通话 |
stopVideoCall(peerId) |
停止视频通话 |
switchCamera() |
切换摄像头 |
setVideoEnabled(peerId, enabled) |
设置视频开关(peerId='local' 控制本地) |
getRemoteStream(peerId) |
获取远端流 |
getAudioPeers() |
获取音频通话列表 |
getVideoPeers() |
获取视频通话列表 |
消息编解码器
MessageCodec 负责消息的编码、解码、分片与重组:
- 默认使用 JSON 编码
- 支持 msgpack 编码(需通过
configure({ codec: 'msgpack', msgpack: yourMsgpack })传入) - 单条消息超过 16KB 时自动分片,接收端自动重组
- 支持 ArrayBuffer / TypedArray 等二进制 payload,会自动 base64 编解码
// 使用 msgpack
await jzWebrtcConnect.configure({
codec: 'msgpack',
msgpack: {
encode: msgpack5.encode,
decode: msgpack5.decode
}
})
心跳检测
HeartbeatMonitor 自动管理 peer 心跳:
- 周期性发送
sys/heartbeat消息 - 收到对端心跳后回复
sys/heartbeat-ack - 计算往返时间 RTT
- 连续丢失达到
heartbeatMissThreshold后判定 peer 超时离线
心跳配置:
await jzWebrtcConnect.configure({
heartbeatIntervalMs: 5000, // 心跳发送间隔
heartbeatMissThreshold: 3 // 连续丢失次数阈值
})
获取 peer RTT:
const peers = core.getPeers()
peers.forEach(p => {
console.log(p.peerId, 'rtt:', p.rtt)
})
错误码说明
| 错误码 | 说明 |
|---|---|
E_NOT_READY |
模块尚未初始化或当前状态不允许调用 |
E_ALREADY_INITIALIZED |
单例已存在,重复初始化 |
E_ROLE_INVALID |
角色无效或调用了当前角色不支持的 API |
E_SIGNAL_INIT |
信令初始化失败 |
E_SIGNAL_DISCONNECT |
信令连接断开或异常 |
E_SIGNAL_UNSUPPORTED |
当前 signal.adapter 不支持或未实现 |
E_HANDSHAKE_TIMEOUT |
DataChannel 握手超时 |
E_CHANNEL_CLOSED |
DataChannel 未打开或已关闭 |
E_CONN_CLOSED |
连接被正常关闭 |
E_PEER_TIMEOUT |
心跳超时,判断 peer 离线 |
E_PEER_KICKED |
Host 主动踢出 peer |
E_CODEC_FAIL |
消息编码或解码失败 |
E_REQUEST_TIMEOUT |
RPC request 或 ACK 等待超时 |
E_DISPOSED |
实例已销毁 |
E_NOT_FOUND |
资源未找到 |
E_DATA_STORE_NOT_FOUND |
数据同步命名空间不存在 |
错误对象结构:
try {
await core.send('peer-xxx', 'chat', { text: 'hello' })
} catch (error) {
console.log(error.code) // 错误码,如 'E_CHANNEL_CLOSED'
console.log(error.message) // 错误描述
console.log(error.retriable) // 是否可重试
console.log(error.cause) // 原始错误
}
SDK 入口 API
| 方法 | 说明 |
|---|---|
configure(baseConfig) |
保存低频基础配置 |
getConfig() |
获取当前缓存的基础配置 |
resetConfig() |
重置基础配置 |
connect(sessionConfig) |
发起真实建连,返回 ConnectCore 实例 |
getInstance() |
获取当前 ConnectCore 单例 |
destroy() |
销毁当前 ConnectCore 单例 |
createDataCore(options) |
创建数据同步层,返回 DataCore 实例 |
导出模块
import jzWebrtcConnect, {
// 通讯层
ConnectCore,
CORE_STATE,
MessageCodec,
PROTOCOL_VERSION,
CHUNK_THRESHOLD,
HeartbeatMonitor,
PeerLinkBase,
LINK_STATE,
HostPeerLink,
ClientPeerLink,
SignalAdapterBase,
PeerJSSignalAdapter,
NativeSignalAdapter,
NativeDataConnection,
// 媒体层
MediaConnectionManager,
AudioVolumeMonitor,
NativeMediaAdapter,
MEDIA_EVENTS,
// 数据同步层
DataCore,
DataStoreBase,
KeyValueStore,
ListStore,
MapStore,
ReplicationManager,
StorageDriver,
MemoryDriver,
UniStorageDriver,
// 通用
EventBus,
ConnectError,
ERROR_CODES,
version
} from '@/uni_modules/jz-webrtc-connect/js_sdk/jz-webrtc-connect'
实际工作流程
以 native + WebSocket 信令 为例,完整链路通常是:
- Host 连接 WebSocket 信令服务,发送
host-ready - Client 连接同一个信令服务并发送
join - 双方通过 WebSocket 交换
offer / answer / candidate - WebRTC 尝试打洞,建立
RTCPeerConnection DataChannel打开后,业务消息开始直连传输- 后续高频数据不再依赖信令服务器
以 peerjs 信令 + 媒体通话为例:
- Host 通过
connect()创建房间 - Client 通过
connect()加入房间 - DataChannel 建立后,双方可收发消息
- Host 调用
setLocalStream()设置本地音频流 - Host 调用
startAudioCall(peerId)发起音频通话 - Client 监听
audio-call-incoming事件,调用answerAudioCall()接听 - 双方监听
audio-stream-ready事件获取远端流并播放 - 视频通话流程类似,使用
startVideoCall/answerVideoCall
这就是这个插件最值得强调的价值点:
- WebSocket 只承担建连阶段和必要控制面通信
- 高频业务数据尽量走终端直连
- 打洞成功后,服务器资源消耗远小于纯 WebSocket 实时转发方案
适合怎么设计业务
推荐把架构拆成两层:
- 低频控制层:登录、鉴权、房间匹配、信令准备
- 高频数据层:通过
jz-webrtc-connect走 WebRTCDataChannel
这种设计相较于"所有实时数据全部走 WebSocket"更适合:
- 房间内消息频率高
- 服务端不想承受持续中转压力
- 对实时性和成本更敏感
- 需要音视频通话能力
常见注意事项
- 插件只负责通讯能力,不负责业务 UI
- Host / Client 的连接拓扑是"房主中心化房间模型",不是全量 mesh
broadcast()仅 Host 可用kick()仅 Host 可用DataCore不是必需模块,只做消息收发可不创建- 媒体通话功能目前仅支持
PeerJS信令模式 - 生产环境请认真配置 STUN / TURN,不要只依赖公共 STUN
- 如果业务极度依赖"完全不走服务器",需要接受部分网络环境下可能退化到 TURN 的现实
- 同一页面同时只支持一个 ConnectCore 实例(单房间设计)
- DataCore 同样是单例设计,如需切换房间请先
destroy()再重新connect()
总结
jz-webrtc-connect 的核心价值,不只是"能建 WebRTC 连接",而是:
- 用统一 API 屏蔽
PeerJS / native / custom差异 - 把高频业务流量从中心服务器转移到终端之间
- 在打洞成功时实现"业务数据不走服务器"的资源节约效果
- 提供完整的音频/视频通话能力
- 提供可选的数据同步层,支持 Host 权威数据源下的自动 patch/snapshot 同步
- 让
uni-app项目可以更清晰地构建"低频信令 + 高频点对点数据"的实时架构

收藏人数:
下载插件并导入HBuilderX
下载插件ZIP
赞赏(0)
下载 2585
赞赏 0
下载 12477513
赞赏 1936
赞赏
京公网安备:11010802035340号