更新记录

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 四类信令,与 NativeNegotiatoroffer/answer/candidate/close 解耦互不干扰。
    • 内置 Perfect Negotiation(polite/impolite)抗 glare:host 端为 polite 让步方,client 端为 impolite 强势方,自动回滚冲突 offer。
  • NativeMediaConnectionManager 正式纳入 ConnectCore:通过 signal.useUnifiedMediaPc: true 启用,与 PeerJS-style MediaConnectionManager 二选一。
    • 支持 host 中心服转发其他客户端流,并通过 media-source-map 信令告知接收端 trackId → sourcePeerId/viaPeerId 映射。
  • ConnectCore.initMedia 双路径分发:根据 signal.useUnifiedMediaPc 与适配器能力选择媒体管理器实现。
  • ConnectCore.wireEventspeer-leave 触发时自动调用 signal._closeMediaPc(peerId) 回收共享媒体 PC。

修复

  • MediaConnectionManager.setLocalStream(null) 兼容空流,避免读取 null.getVideoTracks() 崩溃;同时支持热切换:对已有 outgoing call 通过 replaceTrack 替换轨道而不重协商。
  • MediaConnectionManager._bindCallEvents 修正中心服转发判定:仅对 direction=incomingrelayed=false 的来电做 relay,避免 host 自身 outgoing call 被错误转发以及 relayed call 被二次扩散造成环路。
  • MediaConnectionManager._calls slot 补全 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 信令模式,新增 NativePeerNativeNegotiatorNativeMediaConnection,使 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-connect1.0.3 版本开始,是一个面向 uni-app H5 的 WebRTC DataChannel 通讯插件,用于构建房间协作、实时指令同步、局域网或互联网点对点消息传输、音视频通话等场景。

本版本修复已知bug,优化性能。

它聚焦的是"数据通讯 + 媒体能力"而不是 UI:

  • 提供统一的连接核心 ConnectCore
  • 支持 PeerJSnative 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 / candidate
  • native 模式里通常使用 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.js
  • connect/HostPeerLink.js
  • connect/ClientPeerLink.js
  • connect/PeerLinkBase.js
  • connect/HeartbeatMonitor.js
  • connect/MessageCodec.js

职责是:

  • 根据角色创建 hostclient 链路
  • 管理 DataChannel 生命周期
  • 提供 send / broadcast / request / kick
  • 处理心跳、ACK、请求响应、重连等基础能力

3. 信令适配层

  • connect/adapters/PeerJSSignalAdapter.js
  • connect/adapters/NativeSignalAdapter.js
  • connect/adapters/SignalAdapterBase.js
  • connect/adapters/NativeDataConnection.js

职责是:

  • 负责建连前的信令交换
  • 对上层输出统一 DataConnection 接口
  • 屏蔽 PeerJS 与原生 RTCPeerConnection 的差异

4. 媒体层

  • media/MediaConnectionManager.js
  • media/AudioVolumeMonitor.js
  • media/adapters/NativeMediaAdapter.js

职责是:

  • 管理 PeerJS MediaConnection 的生命周期
  • 提供音频通话与视频通话 API
  • 支持摄像头切换、静音、视频开关
  • 实时监测本地与远端音频流音量

5. 数据同步层

  • data/DataCore.js
  • data/ReplicationManager.js
  • data/KeyValueStore.js
  • data/ListStore.js
  • data/MapStore.js
  • data/StorageDriver.js

职责是:

  • 基于通讯核心做房间数据同步
  • Host 作为权威数据源
  • Client 通过 patch 或 snapshot 跟随同步
  • 支持 kv / list / map 三种命名空间数据容器

基础用法

推荐把"低频基础配置"和"高频业务参数"分开:

  • configure():保存不常变化的基础配置,例如信令地址、ICE 配置、重连策略
  • connect():传入每次连接都可能变化的参数,例如 roleroomId

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 offer
  • answer:SDP answer
  • candidate:ICE candidate
  • leave:节点离开

也就是说,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():初始化信令通道,返回 peerId
  • connect(remotePeerId):主动连接远端,返回 DataConnection
  • onIncoming(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 信令 为例,完整链路通常是:

  1. Host 连接 WebSocket 信令服务,发送 host-ready
  2. Client 连接同一个信令服务并发送 join
  3. 双方通过 WebSocket 交换 offer / answer / candidate
  4. WebRTC 尝试打洞,建立 RTCPeerConnection
  5. DataChannel 打开后,业务消息开始直连传输
  6. 后续高频数据不再依赖信令服务器

peerjs 信令 + 媒体通话为例:

  1. Host 通过 connect() 创建房间
  2. Client 通过 connect() 加入房间
  3. DataChannel 建立后,双方可收发消息
  4. Host 调用 setLocalStream() 设置本地音频流
  5. Host 调用 startAudioCall(peerId) 发起音频通话
  6. Client 监听 audio-call-incoming 事件,调用 answerAudioCall() 接听
  7. 双方监听 audio-stream-ready 事件获取远端流并播放
  8. 视频通话流程类似,使用 startVideoCall / answerVideoCall

这就是这个插件最值得强调的价值点:

  • WebSocket 只承担建连阶段和必要控制面通信
  • 高频业务数据尽量走终端直连
  • 打洞成功后,服务器资源消耗远小于纯 WebSocket 实时转发方案

适合怎么设计业务

推荐把架构拆成两层:

  • 低频控制层:登录、鉴权、房间匹配、信令准备
  • 高频数据层:通过 jz-webrtc-connect 走 WebRTC DataChannel

这种设计相较于"所有实时数据全部走 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 项目可以更清晰地构建"低频信令 + 高频点对点数据"的实时架构

隐私、权限声明

1. 本插件需要申请的系统权限列表:

2. 本插件采集的数据、发送的服务器地址、以及数据用途说明:

3. 本插件是否包含广告,如包含需详细说明广告表达方式、展示频率:

许可协议

MIT协议

暂无用户评论。