
一、前言:小程序做视频会议,先搞清楚这几件事
微信小程序的音视频能力和普通 Web 页面有所区别。浏览器里可以直接用 WebRTC,但小程序运行在微信的沙箱环境中,无法直接使用标准的 WebRTC API 。微信提供了专门的原生组件 live-pusher(推流)和 live-player(拉流)来实现实时音视频,但这两个组件有一个绕不过去的门槛:需要特定类目的小程序才能使用。
可使用实时音视频的类目(部分):
- 社交 > 直播
- 教育 > 在线教育
- 医疗 > 互联网医院、在线问诊
- 金融 > 银行、保险、证券等
更多类目,请查看微信小程序开放的服务类目。
如果你的小程序不在这些类目内,live-pusher 和 live-player 组件会直接渲染失败,这是很多开发者踩的第一个坑。
本文适用场景示例:
- 1v1 视频通话:客服、问诊、在线辅导等
- 多人视频会议:团队会议、在线课堂等。
二、方案选型:自研 vs 第三方 RTC SDK
2.1 自研方案
自研意味着你需要:
- 搭建 WebSocket 信令服务器(处理房间管理、用户进出通知)
- 对接 TURN/STUN 服务器(处理 NAT 穿透)
- 自行管理推拉流地址(通常是 RTMP 或 WebRTC 地址)
适合场景:有专职音视频团队、对数据私有化要求极高、长期维护成本可控。
2.2 第三方 RTC SDK
对于大多数业务团队,第三方 SDK 是更务实的选择。主流方案对比:
| 维度 | 即构科技(ZEGO) | 腾讯云 TRTC | 阿里云 RTC |
|---|---|---|---|
| 小程序原生支持 | 支持 | 支持 | 支持 |
| 免费额度 | 每月 10000 分钟 | 每月 10000 分钟 | 少量免费试用 |
| 国内平均延迟 | < 200ms | < 300ms | 200-500ms |
| 接入复杂度 | 低 | 低 | 中 |
本文选用 ZEGO RTC SDK 为例。原因:小程序端接入步骤简洁,官方文档有完整的微信小程序示例,免费额度对中小项目可以零成本启动。
三、环境准备与资质申请
3.1 微信小程序类目申请
登录微信公众平台,进入 设置 > 基本设置 > 服务类目,添加支持实时音视频的类目。审核通常需要 1-3 个工作日,建议在开发前就提交申请,避免联调阶段被卡住。
3.2 开发环境配置
在 app.json 中声明所需权限:
{
"permission": {
"scope.camera": {
"desc": "视频会议需要使用摄像头"
},
"scope.record": {
"desc": "视频会议需要使用麦克风"
}
}
}
在微信公众平台 开发 > 开发管理 > 开发设置 > 服务器域名 中,将 SDK 所需域名加入白名单(具体域名列表参考ZEGO 官方文档)。
3.3 获取 AppID 和 AppSign
前往 ZEGO 控制台注册账号,创建项目后获取 AppID 和 AppSign,后续代码中会用到。
3.4 引入 SDK
npm install zego-express-engine-miniprogram
在微信开发者工具中点击 工具 > 构建 npm,完成后即可在页面中引用。
四、核心实现:1v1 视频通话
4.1 整体架构
用户A ──推流──▶ ZEGO RTC 服务器 ──拉流──▶ 用户B
◀──拉流── ◀──推流──
信令流:用户A/B 通过 SDK 事件感知对方进出房间,动态更新拉流列表
4.2 页面结构(WXML)
<span><</span>!-- meeting.wxml --<span>></span>
<span><</span>view class="container"<span>></span>
<span><</span>!-- 本地推流(自己的画面) --<span>></span>
<span><</span>live-pusher
id="local-pusher"
url="{{pushUrl}}"
mode="RTC"
autopush
muted="{{isMuted}}"
enable-camera="{{cameraOn}}"
class="local-video"
/<span>></span>
<span><</span>!-- 远端拉流(对方的画面) --<span>></span>
<span><</span>live-player
wx:for="{{remoteStreams}}"
wx:key="streamID"
src="{{item.playUrl}}"
mode="RTC"
autoplay
class="remote-video"
/<span>></span>
<span><</span>/view<span>></span>
4.3 初始化 SDK
// meeting.js
const ZegoExpressEngine = require('../../miniprogram_npm/zego-express-engine-miniprogram/index');
const APP_ID = 123456789; // 替换为你的 AppID
const APP_SIGN = 'xxxxxxxx...'; // 替换为你的 AppSign
Page({
data: {
pushUrl: '',
remoteStreams: [],
isMuted: false,
cameraOn: true,
},
onLoad(options) {
this.roomID = options.roomID || 'room_001';
this.userID = options.userID || `user_${Date.now()}`;
this.streamID = `stream_${this.userID}`;
this.initEngine();
},
initEngine() {
this.engine = ZegoExpressEngine.createEngine(APP_ID, APP_SIGN, true, {});
this.registerCallbacks();
this.joinRoom();
},
4.4 注册事件回调
registerCallbacks() {
this.engine.on('roomStreamUpdate', (roomID, updateType, streamList) => {
if (updateType === 'ADD') {
const newStreams = streamList.map(stream => ({
streamID: stream.streamID,
playUrl: this.engine.startPlayingStream(stream.streamID),
}));
this.setData({
remoteStreams: [...this.data.remoteStreams, ...newStreams],
});
} else if (updateType === 'DELETE') {
const deletedIDs = new Set(streamList.map(s => s.streamID));
this.setData({
remoteStreams: this.data.remoteStreams.filter(s => !deletedIDs.has(s.streamID)),
});
}
});
this.engine.on('roomStateUpdate', (roomID, state, errorCode) => {
if (state === 'DISCONNECTED' && errorCode !== 0) {
wx.showToast({ title: '连接断开,请检查网络', icon: 'none' });
}
});
},
4.5 加入房间并推流
async joinRoom() {
const token = await this.fetchToken(this.userID, this.roomID);
await this.engine.loginRoom(this.roomID, token, {
userID: this.userID,
userName: `用户${this.userID}`,
});
// 拉取已在房间内的流(不会触发 ADD 事件)
const existingStreams = await this.engine.getRoomStreamList(this.roomID);
const remoteStreams = existingStreams.map(stream => ({
streamID: stream.streamID,
playUrl: this.engine.startPlayingStream(stream.streamID),
}));
this.setData({ remoteStreams });
// 开始推送本地流
const pushUrl = this.engine.startPublishingStream(this.streamID);
this.setData({ pushUrl });
},
// Token 应由服务端生成,不要在前端硬编码 AppSign
async fetchToken(userID, roomID) {
const res = await wx.request({
url: 'https://your-server.com/api/zego-token',
method: 'POST',
data: { userID, roomID },
});
return res.data.token;
},
4.6 退出房间
onUnload() {
this.leaveRoom();
},
leaveRoom() {
this.engine.stopPublishingStream();
this.engine.logoutRoom(this.roomID);
ZegoExpressEngine.destroyEngine();
},
});
五、进阶:多人视频会议室
5.1 九宫格 / 演讲者布局
根据在线人数动态切换布局类名:
<span><</span>view class="grid grid-{{remoteStreams.length <= 1 ? 'single' : remoteStreams.length <= 3 ? 'quad' : 'nine'}}"<span>></span>
<span><</span>live-player
wx:for="{{remoteStreams}}"
wx:key="streamID"
src="{{item.playUrl}}"
mode="RTC"
autoplay
/<span>></span>
<span><</span>/view<span>></span>
/* meeting.wxss */
.grid-single live-player { width: 100%; height: 100%; }
.grid-quad live-player { width: 50%; height: 50%; float: left; }
.grid-nine live-player { width: 33.33%; height: 33.33%; float: left; }
5.2 常用会议控制
toggleMute() {
this.setData({ isMuted: !this.data.isMuted });
// live-pusher 的 muted 属性绑定了 isMuted,自动生效
},
toggleCamera() {
this.setData({ cameraOn: !this.data.cameraOn });
// live-pusher 的 enable-camera 属性绑定了 cameraOn,自动生效
},
switchCamera() {
wx.createLivePusherContext('local-pusher').switchCamera();
},
5.3 性能优化建议
人数超过 4 人时,可通过 SDK 接口动态降低非主讲人的分辨率和帧率,减少下行带宽压力:
setLowQualityForBackground(streamID) {
// 具体 API 参考 SDK 文档中的 setPlayStreamVideoType
this.engine.setPlayStreamVideoType(streamID, 'low');
},
六、常见问题与解决方案
| 问题现象 | 原因 | 解决方法 |
|---|---|---|
live-pusher 不渲染,显示空白 | 小程序类目不支持实时音视频 | 在公众平台申请对应服务类目 |
| 推流成功但对方看不到画面 | SDK 域名未加入白名单 | 在开发设置中配置合法域名 |
| 真机黑屏,模拟器正常 | 未申请摄像头/麦克风权限 | 在 app.json 中声明 permission |
| 进入房间看不到已有成员 | 未主动拉取已有流列表 | loginRoom 后调用 getRoomStreamList |
| 多人时画面卡顿 | 上行带宽不足或码率过高 | 动态降低非主讲人分辨率 |
| iOS 和 Android 画面比例不一致 | 系统渲染差异 | 统一设置 object-fit: fillCrop |
七、总结
核心流程三步:申请类目资质 → 初始化 SDK 加入房间 → 用 live-pusher 推流 + live-player 拉流。多人会议在此基础上动态管理流列表即可。
延伸方向:
- 云端录制:通过 ZEGO 服务端 API 开启混流录制,录制文件存储到 OSS。
- 美颜滤镜:SDK 内置基础美颜,高级效果可接入第三方美颜 SDK。

原创文章,作者:ZEGO即构科技,如若转载,请注明出处:https://market-blogs.zego.im/reports-baike/3436/