随着电商带货、在线教育、秀场社交等业务爆发式增长,直播已经从"锦上添花"变成"业务刚需"。对前端团队来说,用 uniapp 一套代码覆盖 H5、微信小程序、iOS、Android 是性价比最高的选择。但直播涉及音视频采集、编解码、实时传输、CDN 分发等复杂环节,从零自建难度极高。本文从方案选型到代码落地,完整讲清楚 uniapp 实现直播的工程化路径,并以即构科技的 ZEGO Express SDK 为例给出实战代码。
一、uniapp 直播的技术方案对比
uniapp 实现直播的技术方案一般有三种,如下:
- 方案一:基于小程序原生组件
live-pusher/live-player; - 方案二:第三方 RTC SDK,如即构科技(ZEGO) 、腾讯云/阿里云等;
- 方案三:WebRTC / HLS 自建。
选型阶段先搞清楚方案的差异,避免踩坑后再返工。
1.1 主流方案全景对比
| 对比维度 | 方案一:原生组件live-pusher/live-player | 方案二:第三方 RTC SDK / 即构科技(ZEGO) 、腾讯云等 | 方案三:WebRTC / HLS 自建 |
|---|---|---|---|
| 技术原理 | 基于小程序宿主能力,RTMP 推流 + FLV/RTMP 拉流 | 商用 RTC 引擎,底层自研协议 + CDN | 原生 WebRTC + 自建信令 + CDN |
| 支持平台 | 仅微信小程序、QQ 小程序、部分 App 端 | 全端(H5 / 小程序 / App / Web) | H5 / App(需自行适配) |
| 延迟 | 1-3 秒(RTMP) | 200-400ms(RTC)、1-3 秒(CDN 直播) | 300ms-2s(依实现而定) |
| 连麦互动 | 仅支持主播-观众单向,不支持多人连麦 | 支持一对一、多人连麦、PK 连麦 | 需自行实现混流、信令 |
| 接入成本 | 低,直接用 uni 内置标签 | 中,引入 SDK + 调用 API | 高,需音视频团队 |
| 并发能力 | 依赖小程序平台限制 | 商用级,百万级并发 | 取决于自建规模 |
| 美颜 / 滤镜 / 特效 | 基础美颜 | SDK 内置 + 可扩展 AI 特效 | 需自行集成 |
| 鉴权与安全 | 需自行搭建 RTMP 鉴权 | 官方 Token 鉴权、房间隔离 | 自行设计鉴权体系 |
| 弱网优化 | 依赖基础协议,较弱 | 有专门抗弱网算法(NACK / FEC / 自适应码率) | 取决于实现 |
| 费用 | 免费(仅用带宽) | 按用量计费(分钟数 / 流量) | 带宽 + 服务器 + 人力 |
| 上线周期 | 1-3 天 | 1-2 周 | 1-3 个月 |
| 适用场景 | 简单秀场直播、只在微信内使用 | 电商带货、在线教育、互动直播、连麦 PK | 有特殊定制需求的大厂 |
1.2 选型决策流程
只在微信小程序内使用? │ ┌────┴────┐ 是 否 │ │ 需要连麦? 需要低延迟互动? │ │ 是 / 否 是 / 否 │ │ 方案二 / 方案一 方案二(RTC) / 方案二(CDN)结论:绝大多数"需要跨端 + 互动直播"的业务,方案二是最合理的选择。下面进入实战。
二、核心概念速览
- 推流(Publish):主播将本地采集的音视频上传到服务器
- 拉流(Play):观众从服务器下载并播放音视频流
- 房间(Room):一次直播的逻辑容器,主播和观众通过 roomID 关联
- RTC vs CDN 直播:RTC 走实时协议,延迟 400ms 内,支持双向互动;CDN 直播走 HLS/FLV,延迟 1-3 秒,成本更低
- Token:登录房间的鉴权凭证,必须由服务端生成
三、实战:基于 ZEGO Express SDK 在 uniapp 中实现直播
ZEGO Express SDK 是即构科技(ZEGO)开发的一款实时音视频互动服务产品,能够为开发者提供便捷接入、高可靠、多平台互通的音视频服务。通过低至 200 ms 的端到端平均时延,业内领先的保障弱网质量的 QoS 策略,并结合强大的 3A 处理能力,完美支持一对多、多对多的实时音视频通话、直播、会议等场景。
本节给出一个可跑通的最小直播 Demo:主播开播 + 观众观看,覆盖 Web / 小程序 / App 三端。
3.1 环境准备
版本要求:
- HBuilderX 3.8.0+
- Vue 3(推荐)或 Vue 2
- uniapp 最新稳定版
控制台申请:
- 登录 ZEGO 控制台创建项目,获取
AppID和ServerSecret - 开通"实时音视频"服务
ServerSecret只在服务端使用,不要放在客户端
3.2 引入 SDK
uniapp 的多端特性决定了 SDK 引入方式要按端区分:
# Web 端 npm i zego-express-engine-webrtm # 微信小程序端 npm i zego-express-engine-miniprogram # App 端(原生插件) # 在 HBuilderX 中:manifest.json → App 原生插件配置 → 选择 ZEGO Express 插件建议封装一个统一的引擎入口,用条件编译分发:
// utils/zego-engine.js // #ifdef H5 import ZegoExpressEngine from 'zego-express-engine-webrtm' // #endif // #ifdef MP-WEIXIN import ZegoExpressEngine from 'zego-express-engine-miniprogram' // #endif // #ifdef APP-PLUS const ZegoExpressEngine = uni.requireNativePlugin('ZegoExpressUniPlugin') // #endif export default ZegoExpressEngine3.3 权限与基础配置
manifest.json中开启必要权限:
{ "app-plus": { "distribute": { "android": { "permissions": [ "<uses-permission android:name=\"android.permission.CAMERA\"/>", "<uses-permission android:name=\"android.permission.RECORD_AUDIO\"/>", "<uses-permission android:name=\"android.permission.INTERNET\"/>", "<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>" ] }, "ios": { "privacyDescription": { "NSCameraUsageDescription": "用于直播画面采集", "NSMicrophoneUsageDescription": "用于直播声音采集" } } } }, "mp-weixin": { "requiredBackgroundModes": ["audio"], "requiredPrivateInfos": ["getLocation"] } }小程序后台还要在"服务器域名"中配置 ZEGO 的 wss 与 https 白名单。
3.4 封装 ZEGO Engine 单例
// services/zegoService.js import ZegoExpressEngine from '@/utils/zego-engine.js' class ZegoService { constructor() { this.engine = null this.appID = 1234567890 // 替换为你的 AppID this.server = 'wss://webliveroom-api.zego.im/ws' } async init() { if (this.engine) return this.engine this.engine = new ZegoExpressEngine(this.appID, this.server) this._bindEvents() return this.engine } _bindEvents() { this.engine.on('roomStateUpdate', (roomID, state, errorCode) => { console.log('房间状态:', state, errorCode) }) this.engine.on('roomStreamUpdate', (roomID, updateType, streamList) => { if (updateType === 'ADD') { uni.$emit('stream-add', streamList) } else { uni.$emit('stream-delete', streamList) } }) this.engine.on('publisherStateUpdate', (result) => { console.log('推流状态:', result) }) } async loginRoom(roomID, user, token) { return this.engine.loginRoom(roomID, token, { userID: user.userID, userName: user.userName }) } logoutRoom(roomID) { return this.engine.logoutRoom(roomID) } destroy() { if (this.engine) { this.engine.destroyEngine?.() this.engine = null } } } export default new ZegoService()3.5 主播端:创建直播间并推流
<!-- pages/anchor/index.vue --> <template> <view class="anchor-page"> <!-- #ifdef H5 || MP-WEIXIN --> <view id="local-preview" class="video-box"></view> <!-- #endif --> <!-- #ifdef APP-PLUS --> <ZegoView class="video-box" :streamID="localStreamID" :viewMode="0" /> <!-- #endif --> <view class="controls"> <button @click="startLive" v-if="!isLiving">开始直播</button> <button @click="stopLive" v-else>结束直播</button> </view> </view> </template> <script setup> import { ref, onUnmounted } from 'vue' import zegoService from '@/services/zegoService.js' import { fetchZegoToken } from '@/api/auth.js' const isLiving = ref(false) const localStreamID = ref('') const roomID = 'live_room_001' const user = { userID: 'anchor_001', userName: '主播A' } async function startLive() { await zegoService.init() // 1. 从服务端获取 Token const token = await fetchZegoToken(user.userID) // 2. 登录房间 await zegoService.loginRoom(roomID, user, token) // 3. 开启本地预览 const streamID = `stream_${user.userID}_${Date.now()}` localStreamID.value = streamID // #ifdef H5 || MP-WEIXIN await zegoService.engine.startPreview('local-preview') // #endif // 4. 推流 await zegoService.engine.startPublishingStream(streamID) isLiving.value = true } async function stopLive() { await zegoService.engine.stopPublishingStream() await zegoService.engine.stopPreview?.() await zegoService.logoutRoom(roomID) isLiving.value = false } onUnmounted(() => { if (isLiving.value) stopLive() }) </script> <style> .video-box { width: 750rpx; height: 1000rpx; background: #000; } </style>3.6 观众端:进入直播间并拉流
<!-- pages/audience/index.vue --> <template> <view class="audience-page"> <!-- #ifdef H5 || MP-WEIXIN --> <view id="remote-view" class="video-box"></view> <!-- #endif --> <!-- #ifdef APP-PLUS --> <ZegoView class="video-box" :streamID="remoteStreamID" /> <!-- #endif --> <view class="status">{{ statusText }}</view> </view> </template> <script setup> import { ref, onMounted, onUnmounted } from 'vue' import zegoService from '@/services/zegoService.js' import { fetchZegoToken } from '@/api/auth.js' const remoteStreamID = ref('') const statusText = ref('连接中...') const roomID = 'live_room_001' const user = { userID: 'viewer_' + Date.now(), userName: '观众' } onMounted(async () => { await zegoService.init() const token = await fetchZegoToken(user.userID) await zegoService.loginRoom(roomID, user, token) uni.$on('stream-add', onStreamAdd) uni.$on('stream-delete', onStreamDelete) }) async function onStreamAdd(streamList) { const stream = streamList[0] remoteStreamID.value = stream.streamID // #ifdef H5 || MP-WEIXIN await zegoService.engine.startPlayingStream(stream.streamID, 'remote-view') // #endif statusText.value = '直播中' } async function onStreamDelete(streamList) { await zegoService.engine.stopPlayingStream(streamList[0].streamID) statusText.value = '主播已离开' } onUnmounted(() => { uni.$off('stream-add', onStreamAdd) uni.$off('stream-delete', onStreamDelete) zegoService.logoutRoom(roomID) }) </script>3.7 互动能力扩展思路
- 连麦:观众调用
startPublishingStream,主播订阅stream-add事件拉对方流即可 - 弹幕 / 礼物:引入 ZIM SDK 做消息通道,直播房间内广播 IM 消息
- 美颜 / 滤镜:App 端通过 SDK 的美颜模块或第三方插件(FaceUnity、商汤)集成
- 混流转 CDN:主播侧开启混流任务,观众端通过
<video>或 live-player 播放 FLV / HLS 拉流地址,降低大并发成本
3.8 多端差异清单
| 差异点 | Web | 小程序 | App |
|---|---|---|---|
| 视频渲染容器 | <view id> | <view id> | 原生ZegoView组件 |
| 权限申请时机 | 浏览器首次推流时 | 打开 Camera 组件时 | 首次初始化时 |
| 后台策略 | Tab 切走自动暂停 | 小程序后台 5 分钟断连 | 需申请后台音频能力 |
| 包体积影响 | 约 300KB | 约 400KB | 原生插件需云打包 |
四、常见问题排查
1. 推流黑屏 / 无声
- 先确认权限是否授予(摄像头、麦克风)
- 检查
startPreview传入的 DOM 节点是否已渲染 - 小程序端检查是否在真机调试,模拟器不支持音视频采集
2. 小程序提示"不在以下合法域名列表中"
- 微信后台 → 开发 → 服务器域名 → 加入 ZEGO 提供的 wss / https 域名
3. iOS App 端后台推流中断
manifest.json中配置UIBackgroundModes: audio- 提示用户回到前台,移动端系统级限制无法完全规避
4. 延迟高、画面卡顿
- 降低分辨率(从 720p 降到 540p)
- 打开自适应码率
- 检查上行带宽,优先保证推流端网络
5. Token 过期导致踢出房间
- 监听
roomTokenWillExpire事件,提前 30 秒从服务端刷新 Token 并调用renewToken
五、性能与上线建议
5.1 码率分辨率推荐
| 场景 | 推荐值 |
|---|---|
| 视频通话 | 分辨率 360 × 640、帧率 15 fps、码率 600 Kbps |
| 直播 | 分辨率 540 x 960、帧率 24 fps、码率 1500 Kbps |
5.2 安全与合规
- Token 必须服务端签发:客户端只拿到短时效 Token,
ServerSecret绝不外泄 - 接入内容审核:开播鉴黄、实时截图审核,避免违规封号
- 隐私合规:App 首次启动前完成隐私协议弹窗,再初始化 SDK
5.3 上线前 Checklist
- AppID / Token 区分测试和正式环境
- 三端真机各跑通一遍开播、观看、退出
- 弱网场景测试(4G / 模拟 200ms 丢包 5%)
- 监控告警:推流失败、拉流失败、房间登录失败
- 配置内容审核和录制留存
六、结语
uniapp 做直播的核心挑战不在"能不能跑起来",而在"多端一致的稳定性"和"弱网下的体验"。选一个成熟的商用 RTC SDK,把复杂的音视频栈交给专业方案,团队就能专注在业务互动玩法上。
本文演示的最小直播链路已经能覆盖 Web、小程序、App 三端。下一步如果要做连麦 PK、直播带货、AI 数字人主播这类高阶玩法,可以在这个骨架上继续扩展。
完整的 API 参数、事件回调、错误码,建议对照 ZEGO 官方文档进行查阅。