随着电商带货、在线教育、秀场社交等业务爆发式增长,直播已经从"锦上添花"变成"业务刚需"。对前端团队来说,用 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 ZegoExpressEngine
3.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 官方文档进行查阅。