实现音视频通话
更新时间: 2025/07/18 10:31:03
网易云信音视频通话产品的基本功能包括高质量的实时音视频通话。当您成功集成并初始化 NERTC SDK 之后,您可以简单体验本产品的基本业务流程。本文介绍如何通过小程序 Demo 实现音视频通话的基本业务流程。
GitHub 项目
网易云信在 GitHub 上提供以下开源的小程序音视频通话示例项目 netease-kit/NEVideoCall-1to1.git。在搭建自己的小程序项目前,您可以下载体验或参考源代码。
Demo 体验
网易云信为您提供微信小程序的音视频通话 Demo。您可以在手机上安装微信 App,通过微信扫描识别以下二维码,快速体验网易云信的微信小程序音视频通话业务场景。

前提条件
请确认您已完成以下操作:
NERTC SDK 依赖微信小程序原生的媒体组件实现音视频推拉流。因此,建议您在开始前阅读微信官方文档,了解其基本开发要点。
live-pusher
: 用于推送本地音视频流。详情请参考 《微信小程序文档 - live-pusher》。live-player
: 用于播放远端音视频流。详情请参考 《微信小程序文档 - live-player》。wx.createLivePusherContext
: 用于获取live-pusher
组件的上下文,以对其进行操作。详情请参考 《微信小程序文档 - wx.createLivePusherContext》。
第一步:初始化客户端对象
调用 YunXinMiniappSDK.Client
方法创建一个用于控制通话的客户端对象,然后调用 init()
进行初始化。
请务必在调用 init()
方法后,立即注册所有需要监听的事件。否则,可能会错过 SDK 在初始化阶段抛出的事件。
JavaScriptlet client = YunXinMiniappSDK.Client({
debug: true,
appkey: 'YOUR_NETEASE_APPKEY' // 请替换为您的 App Key
});
client.init();
// 立即监听各类事件
this.client.on('clientLeave', (data) => {
const { uid, isBigNumber } = data;
// isBigNumber 标志位用于处理超出 JavaScript number 精度的 UID。
// 若为 true,uid 为 String 类型;否则为 number 类型。
console.log('[clientLeave 通知] 用户离开: ', uid);
});
this.client.on('clientJoin', (data) => {
const { uid, isBigNumber } = data;
console.log('[clientJoin 通知] 用户加入: ', uid);
});
// ... 监听其他事件
第二步:加入房间
调用 client.join 方法加入房间。
在 client.join
的参数中,您需要提供房间名、用户ID(uid
)以及在安全模式下必需的 token
。
网易云信 NERTC SDK 支持用户角色管理,角色包括主播(broadcaster
)和观众(audience
),默认以主播角色加入房间。加入之后,可以通过 setRole()
切换用户角色。如果在主播已 publish
的状态下调用该方法将用户角色设置为观众,会导致之前的推流地址失效,需要重新处理推流逻辑。
JavaScriptlet joinParam = {
channelName: 'YOUR_CHANNEL_NAME', // 请替换为您的房间名
uid: 100, // 如果 uid 超出 number 精度范围,建议以字符串形式传入,SDK 内部会按 BigNumber 处理。
token: 'YOUR_TOKEN', // 安全模式下必填。若为调试模式,请勿设置此字段。
liveEnable: 0, // 是否开启互动直播,请参考 API 文档
recordType: '0', // 服务器录制相关参数,请参考 API 文档
recordAudio: 0,
recordVideo: 0,
isHostSpeaker: 0,
}
client.join(joinParam).then(data => {
console.log('!!!! 加入房间成功');
}).catch(e => {
console.error('!!!! 加入房间失败:', e);
})
第三步:发布本地流
成功加入房间后,主播可以调用 client.publish
方法将本地音视频流发布到房间中。成功发布后,SDK 会返回该路音视频流的推流 URL,您需要将此 URL 绑定到页面的 live-pusher
组件上。
关于 mediaType
参数:
- 传入空字符串
''
(推荐):表示同时发布音频流和视频流。 - 传入
'audio'
:仅发布音频流。 - 传入
'video'
:仅发布视频流。
最佳实践:
如果用户入房后需要立即开启音视频,建议首次调用 publish
时将 mediaType
设置为 ''
。SDK 会为音视频返回同一个推流 URL。后续若需单独控制音视频的开关,可再次调用 publish
或 unpublish
并指定具体的 mediaType
。
JavaScriptlet mediaType = ''; // 推荐,表示同时发布音视频
client.publish(mediaType).then(url => {
console.log('推流成功, 获取推流地址: ', url);
// 在页面的 data 中设置此 url,并绑定到 wxml 的 live-pusher 组件
// this.setData({ pusherUrl: url });
}).catch(e => {
console.log('推流失败,原因: ', e.message);
})
第四步:订阅远端流
当房间内有其他用户发布媒体流时,SDK 会触发 stream-added
事件。您需要监听此事件,并在回调中调用 client.subscribe
来订阅该媒体流。订阅成功后,SDK 会返回拉流 URL,您需要为该用户动态创建一个 live-player
组件并绑定此 URL。
核心逻辑:
- 如果远端用户同时开启了音频和视频,SDK 会触发两次
stream-added
事件,mediaType
分别为'audio'
和'video'
。您需要对这两个事件都调用subscribe
方法,否则服务器不会转发完整的音视频流。 - 对于同一个用户的音视频主流(
'audio'
和'video'
),SDK 返回的拉流 URL 是相同的。因此,您只需要使用一个live-player
组件来播放该用户的音视频。 - 对于辅流(如屏幕共享
'screenShare'
或音频辅流'slaveAudio'
),SDK 会返回独立的 URL,需要为其创建单独的live-player
组件。
JavaScriptclient.on('stream-added', ({uid, mediaType, isBigNumber}) => {
console.log(`用户 ${uid} 发布了 ${mediaType} 流`);
client.subscribe(uid, mediaType).then(data => {
console.log(`订阅用户 ${uid} 的 ${mediaType} 成功,拉流地址: `, data.url);
// 业务层需要将拉流 url 设置到对应的 live-player 组件中。
// 您可以维护一个用户列表,将 url 与 uid 关联起来,并动态渲染 live-player。
}).catch(e => {
console.log(`订阅用户 ${uid} 失败,原因: `, e);
})
});
当远端用户停止发布流(stream-removed
)或离开房间(clientLeave
)时,您需要相应地销毁或清理对应的 live-player
组件。
JavaScriptclient.on("stream-removed", {uid, mediaType, isBigNumber} => {
console.log(`[stream-removed 通知] ${uid} 停止发布自己的 ${mediaType}`)
// 业务层需要判断:
// 1. 如果 mediaType 是 'audio' 或 'video',通常不需要立即移除 live-player,因为对方可能只关闭了其中一个,另一个仍在传输。
// 2. 如果 mediaType 是 'slaveAudio' 或 'screenShare',因为它们是独立流,可以安全地移除对应的 live-player 组件。
});
this.client.on('clientLeave', (data) => {
const { uid, isBigNumber } = data
//如果对端 uid 超出了 number 精度范围,isBigNumber 值为 true,uid 为 String 类型,否则 isBigNumber 为 false,uid 为 number 类型
console.log('[clientLeave 通知] 有人离开了: ', uid)
//业务层需要自己去判断处理,如果这个 uid 的远端用户只发布了 audio 或者 video 需要清除对应 url 的 live-player 组件即可,但是如果该用户也发布过'slaveAudio'或者'screenShare',记得也需要清除这两个拉流 url 对应的 live-payer 组件,总之对方离开房间后,需要清除这个 uid 相关的所有 live-player 组件
})
第五步:事件通知
SDK 内部处理了网络重连等逻辑,并通过事件向您同步状态。合理利用这些事件可以提升应用的用户体验。
成员状态事件
JavaScript// 有人加入房间
client.on('clientJoin', ({uid, isBigNumber}) => {
console.log(`音视频通知:用户 ${uid} 加入`);
});
// 有人离开房间
client.on('clientLeave', ({uid, isBigNumber}) => {
console.log(`音视频通知:用户 ${uid} 离开`);
// 在此清理该用户对应的 UI 资源
});
// 自己被踢出房间
client.on('kicked', (data) => {
console.log('音视频通知:您已被踢出房间');
// 在此处理退出逻辑,如返回上一页
});
// 房间被解散
client.on('liveRoomClose', (data) => {
console.log('音视频通知:房间已解散');
});
连接状态事件
JavaScript// 与服务器的 WebSocket 连接已建立
client.on('open', (data) => {
console.log('音视频通知:信令通道连接成功');
});
// WebSocket 连接已断开
// WebSocket 连接已断开
client.on('disconnect', (data) => {
console.log('音视频通知:信令通道已关闭。可能原因:主动离开房间,或网络断开且重连失败。');
});
// 准备重连
client.on('willreconnect', (data) => {
console.log('音视频通知:网络断开,正在尝试重连...');
});
// 重连成功
client.on('reconnected', () => {
console.log('音视频通知: 信令重连成功');
// 关键操作:重连成功后,需要重新发布本地流以恢复推流。
client.publish('').then(url => {
console.log('重连后重新推流成功, 获取新推流地址: ', url);
// 业务层将新的推流 url 更新到 live-pusher 组件中
// this.setData({ pusherUrl: url });
}).catch(e => {
console.log('重连后推流失败,原因: ', e.message);
});
});
// SDK 信令发送超时
client.on('sendCommandOverTime', (data) => {
console.log('音视频通知:SDK 信令发送超时');
});
流地址更新事件
JavaScript// 推拉流地址因服务调度等原因发生变化
client.on('syncDone', (data) => {
const {uid, url, isBigNumber} = data;
console.log('[syncDone 通知] 推流地址已更新, data: ', data);
// 如果是自己的 uid,则更新 live-pusher 的推流 url
// 如果是远端的 uid,则更新对应 live-player 的拉流 url
});
第六步:监听质量数据
小程序 SDK 的音视频推拉流依赖于微信平台的 live-pusher
和 live-player
组件。为了监控通话质量和排查问题,您需要监听这两个组件的状态事件,并将数据上报给 SDK。
强烈建议您完整实现本章节的功能。将小程序组件的质量数据通过 client.dataReporter
接口上报给 NERTC SDK,是快速定位和解决音视频问题的关键。
live-pusher 组件监听事件
请参考 微信小程序 和 QQ 小程序 live-pusher 文档查看详细信息。重要事件为:
-
bindstatechange:状态变化事件
-
bindnetstatus:网络状态通知
-
binderror:渲染错误事件
XML
<live-pusher wx:if="{{rtmpUrl!==''}}" wx:key="livePusher" style="display:inline-block;width:180px;height:150px;" aspect="16:9" mode="RTC" enable-agc="true" enable-ans="true" orientation="vertical" url="{{rtmpUrl}}" //推流地址,client.publish()接口可以获取 enable-mic="true" enable-camera="true" beauty="0" waiting-image="../../images/cover.png" max-bitrate="500" min-bitrate="200" bindstatechange="pusherStateChangeHandler" bindnetstatus="pusherNetstatusHandler" binderror="pusherErrorHandler" debug="true" autopush="true" >
在对应的 JS 文件中,实现这些回调函数,并调用 client.dataReporter
上报数据。
JavaScriptfunction pusherStateChangeHandler(data) {
let { code } = data.detail
client.dataReporter('push', 'bindstatechange', {
code,
reason: '' //状态码的说明,小程序 live-pusher 组件文档(https://developers.weixin.qq.com/miniprogram/dev/component/live-pusher.html#%E7%8A%B6%E6%80%81%E7%A0%81%EF%BC%88code%EF%BC%89)有详细说明
})
}
function pusherNetstatusHandler(data) {
let { info } = data.detail
client.dataReporter('push', 'bindnetstatus', info)
}
function pusherErrorHandler(data) {
let { errMsg, errCode } = data.detail
client.dataReporter('push', 'binderror', {
errCode,
errMsg
})
}
live-player 组件监听事件
请参考 微信小程序 和 QQ 小程序 live-player 组件文档查看详细信息。重要事件为:
-
bindstatechange:播放状态变化事件
-
bindnetstatus:网络状态通知
XML
<live-player bindtap='videoClickHandler' style="display:inline-block;width:180px;height:150px;" data-user="{{uid}}" //可以设置成 client.subscribe(uid)中的 uid wx:if="{{item.url}}" //要记得及时清除不存在的拉流地址 src="{{item.url}}" //client.subscribe(uid)订阅成功的拉流地址 mode="RTC" debug="true" min-cache="0.2" max-cache="0.8" auto-pause-if-navigate= "true" auto-pause-if-open-native= "true" bindstatechange="pullerStateChangeHandler" bindnetstatus="pullerNetstatusHandler" autoplay="true" >
您可以调用 SDK 的数据上报接口,实时传递给 SDK 这些数据,可以帮助 SDK 进行问题排查、质量优化。也可以自己监听这些信息,进行进一步的业务处理。
示例代码:
JavaScriptfunction pullerStateChangeHandler(data) {
let { code } = data.detail
client.dataReporter('pull', 'bindstatechange', {
code,
reason: '' //状态码的说明,小程序 live-player 组件文档(https://developers.weixin.qq.com/miniprogram/dev/component/live-player.html#%E7%BD%91%E7%BB%9C%E7%8A%B6%E6%80%81%E6%95%B0%E6%8D%AE)有详细说明
})
}
function pullerNetstatusHandler(data) {
let { info } = data.detail
client.dataReporter('pull', 'bindnetstatus', info)
}
第七步:离开房间
当通话结束时,调用 client.leave()
方法离开房间并释放资源。该方法会断开与服务器的连接,并停止所有推拉流活动。
JavaScriptclient.leave().then(() => {
console.log('成功离开房间');
// 在此执行清理工作,例如销毁 client 实例,返回上一页等
}).catch(err => {
console.error('离开房间失败:', err);
});
运行项目
为保证最佳效果,请务必在真机上运行项目。完成开发后,单击微信开发者工具界面的 真机调试。扫描生成的二维码,即可在手机端运行和调试项目。
完整示例
您可以直接参考如下示例代码,将其整合到您的小程序 Page 中,以快速实现核心功能。
JavaScriptniappSDK from '../../sdk/NIM_Web_Netcall_weixin_G2.js'
let client = YunXinMiniappSDK.Client({
debug: true,
appkey: ''
});
client.init();
//监听事件通知
initEvent()
let joinParam = {
channelName: '',
uid: 100,
token: ''
}
client.join(joinParam).then(data => {
console.log('!!!! 房间房间成功:')
client.publish('').then(url => {
console.log('推流成功, 获取推流地址: ', url);
//mediaType 不同分别为'audio'、'video',目前 SDK 的逻辑是'audio'和'video'使用相同的推流 url,如果开发分别使用 mediaType 为'audio'或者'video'作为参数,两次调用 publish()接口去推流,得到的 url 是相同的,因此建议用户如果加入房间后就需要同时开启音频和视频时,将 mediaType 设置为'',中途需要单独控制音频和视频的开关时,在设置 mediaType 为具体的参数
//业务层将推流 url 设置的 live-pusher 组件中
}).catch(e => {
console.log('推流失败,原因: ', e);
})
}).catch(e => {
console.error('加入房间失败,原因:', e)
})
function initEvent() {
client.on('stream-added', ({uid, mediaType, isBigNumber}) => {
console.log(`[stream-added 通知] ${uid} 发布了自己的 ${mediaType}, isBigNumber: ${isBigNumber}`)
client.subscribe(uid, mediaType).then(data => {
console.log('订阅别人成功,获取到拉流地址: ', data.url)
//业务层将拉流 url 设置到对应 live-player 组件中
//注意: 同一个 uid,两次订阅(分别订阅 audio、video)返回的 url 是相同的的,可以重复更新相同 live-player 组件的 url,没有什么影响
//注意: 同一个 uid,如果对方同时发布了 'audio': 音频、'video': 视频、'slaveAudio': 音频辅流、'screenShare',这 4 中类型的媒体数据,理论上需要 4 次订阅,其中订阅 audio、video 返回的 url 是相同的的,可以重复更新相同 live-player 组件的 url,使用同一个 live-player 组件,slaveAudio 需要单独一个 live-player 组件,screenShare 也需要单独一个 live-player 组件,此时有 3 个 live-player 组件去拉取对端所有类型的媒体
}).catch(e => {
console.log('订阅别人失败,原因: ', e)
})
})
//通知应用程序更新后的推流地址和拉流地址。
client.on('syncDone', (data) => {
const {uid, url, isBigNumber} = data
console.log('[syncDone 通知] 推流地址发生了变化, data: ', data)
//此时需要更新 live-pusher 组件中的 url
})
//通知应用程序有人离开房间。
client.on('clientLeave', (data) => {
const { uid, isBigNumber } = data
//如果对端 uid 超出了 number 精度范围,isBigNumber 值为 true,uid 为 String 类型,否则 isBigNumber 为 false,uid 为 number 类型
console.log('[clientLeave 通知] 有人离开了: ', uid)
//业务层需要自己去判断处理,如果这个 uid 的远端用户只发布了 audio 或者 video 需要清除对应 url 的 live-player 组件即可,但是如果该用户也发布过'slaveAudio'或者'screenShare',记得也需要清除这两个拉流 url 对应的 live-payer 组件,总之对方离开房间后,需要清除这个 uid 相关的所有 live-player 组件
})
//通知应用程序有人加入房间。
client.on('clientJoin', (data) => {
const {uid, isBigNumber} = data
//如果对端 uid 超出了 number 精度范围,isBigNumber 值为 true,uid 为 String 类型,否则 isBigNumber 为 false,uid 为 number 类型
console.log('音视频通知:有人加入')
})
//通知应用程序自己被踢出。
client.on('kicked', (data) => {
console.log('音视频通知:被踢')
})
//通知应用程序 socket 建立成功。
client.on('open', (data) => {
console.log('音视频通知:和服务器 socket 建立成功')
})
//通知应用程序音视频 socket 关闭,触发时机:主动离开房间了,或者 SDK 中途断网重连没有成功,SDK 自动离开了房间。
client.on('disconnect', (data) => {
console.log('音视频通知:和服务器 socket 关闭了')
})
//通知应用程序准备重连。
client.on('willreconnect', (data) => {
console.log('音视频通知:准备重新建立和服务器之间的联系')
})
//通知应用程序准备重连。
client.on('reconnected', (data) => {
console.log('音视频通知:已经重新建立和服务器之间的联系')
//此时需要重新执行 publish()
let mediaType = ''//也可以设置为'audio'或者'video'。如果是空字符串'',表示同时发布 audio 音频流和 video 视频流
client.publish().then(url => {
console.log('推流成功, 获取推流地址: ', url);
//业务层将推流 url 更新到 live-pusher 组件中
}).catch(e => {
console.log('推流失败,原因: ', e.message);
})
})
//通知应用程序 SDK 信令发送超时。
client.on('sendCommandOverTime', (data) => {
console.log('音视频通知:SDK 信令发送超时')
})
//通知应用程序房间被解散。
client.on('liveRoomClose', (data) => {
console.log('音视频通知:房间解散了')
})
}
常见问题
小程序用户关闭再开启麦克风,其他端无法感知状态变化
问题现象
房间内均为小程序用户。用户 A 关闭麦克风(调用 unpublish('audio')
),然后再次开启麦克风(调用 publish('audio')
),其他用户 B 和 C 无法收到 stream-added
事件,因此无法听到 A 的声音。
问题原因
这是由小程序底层媒体组件的机制导致的。在 unpublish
后紧接着 publish
,网关可能不会向远端用户下发新的 stream-added
通知。
解决方案
live-pusher 组件使用推流的 URL 作为 key 值控制是否渲染,关闭麦克风的时候,调用 SDK 的接口停止发布音频流。具体实现思路如下:
JavaScriptthis.client.unpublish('audio').then(url => {
//步骤一:将推流的 URL 设置为 null,这样 live-pusher 组件就会停止渲染。
//步骤二:网易云信只关闭了麦克风,而不关闭摄像头,所以需要立即将 URL 设置回去,以重新渲染 live-pusher 组件。同时,需要记得将 live-pusher 组件中的 enable-mic 设置为 false,这样重新渲染的 live-pusher 组件就不会推送音频流。
})
XML<live-pusher
wx:if="{{pusher.url}}"
class="pusher"
url="{{pusher.ur1}}"
mode="RTC"
enable-camera="{{pusher.enableCamera}}"
enable-mic="{{pusher.enableMic}}"
min-bitrate="{{pusher.minBitrate}}"
max-bitrate="{{pusher.maxBitrate}}"
orientation="{{pusher.videoOrientation}}"
aspect="{{pusher.videoAspect}}"
device-position="{{pusher.frontCamera}}"
background-mute="{{pusher.enableBackgroundMute}}"
audio-quality="{{pusher.audioQuality}}"
waiting-image="{{pusher.waitingImage}}"
bindstatechange="_pusherStateChangeHandler"
bindnetstatus="_pusherNetStatusHandler"
binderror="_pusherErrorHandler"
autopush
></live-pusher>
<!-- 本地 uid 显示 -->
为什么调试 Demo 时显示无法使用摄像头和麦克风?
在调试时,若小程序界面提示 “无法使用摄像头和麦克风”,如下图所示,请按以下步骤排查:
-
方法一:使用真机调试 微信开发者工具的模拟器无法调用真实的摄像头和麦克风。请点击工具栏中的 真机调试,扫描二维码在手机上运行。
-
方法二:检查小程序权限 请确保您已授权小程序使用摄像头和麦克风。
- 首次授权:删除手机上已安装的小程序,重新扫码进入,在弹出的授权请求中选择“允许”。
- 手动设置:在小程序右上角点击 “...” -> 设置,检查并开启“摄像头”和“麦克风”的权限开关。
-
方法三:检查小程序后台配置 登录微信公众平台,进入小程序的 开发 -> 开发管理 -> 接口设置 页面,确保已开启 “实时播放音视频流” 和 “实时录制音视频流” 的权限。