实现音视频通话
在本教程中,您将体验如何集成融云 Web 端音视频通话(呼叫)SDK —— CallLib 的完整流程。CallLib 支持单人音视频呼叫及基于群组的多人呼叫场景。
示例项目
融云音视频通话 QuickDemo(GitHub · Gitee)演示了融云音视频通话产品在 Web 端的功能,便于开发者体验产品、快速集成,实现单聊、群聊、音视频通话等场景需求。
QuickDemo 开源,您可对感兴趣的部分进行代码改造,以进一步了解实现细节。具体实现详见运行示例项目。
前置条件
浏览器页面地址必须为 https 协议,或使用 localhost 域名。
CallLib 概念说明
- 通过 onSessionClose 监听函数获知通话结束。
- 服务开通、关闭等设置完成后 15 分钟后生效。
步骤 1:开通服务
您在融云创建的应用不会默认启用音视频服务。使用融云任何音视频服务前,需前往控制台为应用开通音视频服务。
具体步骤请参阅控制台文档:开通音视频服务。
步骤 2:导入 SDK
您可通过 NPM 安装 CallLib 及其依赖的 IMLib、RTCLib,或采用 CDN 方式 集成。
CallLib 相关业务依赖 IMLib 作为信令通道。开发音视频通话需安装融云音视频核心库 RTCLib 及即时通讯库 IMLib。可选用 2.X、4.X 或 5.X 版本的 IMLib。
本节以 IMLib 5.X 为例,简要说明 NPM 方式安装方法。
-
安装 IMLib 5.X(推荐新集成客户使用 5.X 版本)。
shell# 安装 IMLib v5
npm install @rongcloud/engine@latest @rongcloud/imlib-next --save -
安装 RTCLib 5.X。
shell# 安装 RTCLib
npm install @rongcloud/plugin-rtc --save -
安装 CallLib 5.X。
shell# 安装 CallLib
npm install @rongcloud/plugin-call --save -
安装完成后,即可在代码中导入 IMLib(5.X)、RTCLib、CallLib。
typescript
// 导入 IMLib 5.X
import * as RongIMLib from "@rongcloud/imlib-next";
// 导入 RTCLib
import { installer as rtcInstaller, RCRTCClient, RCTrack, RCFrameRate, RCResolution } from "@rongcloud/plugin-rtc";
// 导入 CallLib
import { installer as callInstaller, RCCallClient, RCCallSession, RCCallErrorCode, ISessionListener, IEndSummary, ISenderInfo, IMuteUser, IInvitedUsers, RCCallLanguage, RCCallEndReason, RCCallMediaType, IOfflineRecord, RCCallSessionState } from "@rongcloud/plugin-call";
步骤 3:初始化
本节以 IMLib 5.X 为例,简要说明 IM 客户端、RTC 客户端、CallLib 客户端的初始化方式。
请依次初始化 IM 客户端、RTC 客户端、CallLib 客户端。
-
调用 IMLib 的
init
方法初始化 IM 客户端(IMLib 5.X)。appkey
即您的融云应用 App Key。typescript// IM 客户端初始化(IMLib 5.X)
RongIMLib.init({
appkey: '<your-app-key>',
}); -
初始化 RTC 客户端与 CallLib 客户端。在初始化 CallLib 客户端实例时,传入
onSession
监听来电。初始化完成后,可获取RCCallClient
实例对象caller
,用于发起呼叫。typescript// RTC 客户端初始化
// RTCLib 全局变量为 RCRTC,CDN 集成方式示例:
// const rtcClient = RongIMLib.installPlugin(RCRTC.installer, { /* 配置项参数 */ })
const rtcClient: RCRTCClient = RongIMLib.installPlugin(rtcInstaller, { /* 配置项参数 */ });
// CallLib 客户端初始化
// CallLib 全局变量为 RCCall,CDN 集成方式示例:
// const caller = RongIMLib.installPlugin(RCCall.installer)
const caller: RCCallClient = RongIMLib.installPlugin(callInstaller, {
// rtcClient 实例(必填)
rtcClient,
/**
* 被动收到邀请(收到远端发起的新会话),会产生新的 session 对象(必填)
*/
onSession(session: RCCallSession){
/**
* **收到新 session 后需立即注册事件监听**
*/
session.registerSessionListener({
/**
* 远端用户已开始响铃,表示对方已收到呼叫请求
* @param sender 已响铃用户
* @param session 当前 session 对象
*/
onRinging(sender: ISenderInfo, session: RCCallSession){
const { userId } = sender;
},
/**
* 远端用户同意接听
* @param sender 远端用户
* @param session 当前 session 对象
*/
onAccept(sender: ISenderInfo, session: RCCallSession){
const { userId } = sender;
},
/**
* 有远端用户挂断
* @param sender 远端用户
* @param reason 挂断原因
* @param session 当前 session 对象
*/
onHungup(sender: ISenderInfo, reason: RCCallEndReason, session: RCCallSession){
const { userId } = sender;
},
/**
* 本端或远端资源已获取
* @param track 本端或远端资源,track 不可设置为 Vue 响应式数据
* @param session 当前 session 对象
*/
onTrackReady(track: RCTrack, session?: RCCallSession){
// track.isLocalTrack() 是否为本地资源
// track.isAudioTrack() 是否为音频
// track.isVideoTrack() 是否为视频
// track.getUserId() 产生该 track 的用户 ID
// 播放音频。远端音频建议直接播放,本端音频建议不播放以减少回音。
if (track.isAudioTrack() && !track.isLocalTrack()) {
track.play();
}
// 视频在对应容器播放
if (track.isVideoTrack()) {
const video = document.getElementById(
"video" + user.userId
) as HTMLVideoElement;
track.play(video);
}
},
});
},
/**
* 满足以下任一条件将触发 onSessionClose:
* 1. 本端用户主动挂断
* 2. 服务端将本端用户踢出 RTC 房间
* 3. 房间人数少于 2 人
*
* @param {RCCallSession} session 被结束的 session 对象
* @param summaryInfo 结束 session 后的汇总信息
*/
onSessionClose(session: RCCallSession, summaryInfo?: IEndSummary){
},
/**
* 接收 IM 离线期间收到的呼叫记录(按需监听)
*/
onOfflineRecord(record: IOfflineRecord){
},
});
步骤 4:建立 IM 连接
音视频用户间信令传输依赖融云即时通讯(IM)服务,因此需先与 IM 服务建立连接,之后再进行音视频呼叫。
//与 IM 服务建立连接(IMLib 5.X)
RongIMLib.connect('<user-token>').then(res => {
if (res.code === 0) {
console.log('连接成功,用户 id 为: ', res.data.userId);
} else {
console.warn('连接失败, code:', res.code)
}
})
步骤 5:发起呼叫
主动呼叫分为发起单人通话和发起多人通话,可根据实际需求调用。多人通话场景必须在同一群组内。
发起单人通话
成功建立 IM 连接后,使用初始化 CallLib 时获取的 RCCallClient
实例的 call 方法发起单人通话。
/**
* 发起单人通话,成功后会产生新的 session
* @param targetId 被呼叫方用户 id(必填)
* @param mediaType 1->音频呼叫,2->音视频呼叫(必填)
* @param listener session 对象上注册的事件(必填)
* @param constraints 获取音频或音视频资源参数(可选)
* @param params.channelId 组织 Id(可选)
*/
const { code, session } = await caller.call({
targetId: this.targetId,
mediaType,
listener: {
/**
* 远端用户已开始响铃,表示对方已收到呼叫请求(必填)
* @param sender 已响铃用户
* @param session 当前 session 对象
*/
onRinging(sender: ISenderInfo, session: RCCallSession){
const { userId } = sender;
},
/**
* 远端用户同意接听(必填)
* @param sender 远端用户
* @param session 当前 session 对象
*/
onAccept(sender: ISenderInfo, session: RCCallSession){
const { userId } = sender;
},
/**
* 有远端用户挂断(必填)
* @param sender 远端用户
* @param reason 挂断原因
* @param session 当前 session 对象
*/
onHungup(sender: ISenderInfo, reason: RCCallEndReason, session: RCCallSession){
const { userId } = sender;
},
/**
* 本端或远端资源已获取(必填)
* @param track 本端或远端资源,track 不可设置为 Vue 响应式数据
* @param session 当前 session 对象
*/
onTrackReady(track: RCTrack, session?: RCCallSession){
// track.isLocalTrack() 是否为本地资源
// track.isAudioTrack() 是否为音频
// track.isVideoTrack() 是否为视频
// track.getUserId() 产生该 track 的用户 ID
// 播放音频。远端音频建议 直接播放,本端音频建议不播放以减少回音。
if (track.isAudioTrack() && !track.isLocalTrack()) {
track.play();
}
// 视频在对应容器播放
if (track.isVideoTrack()) {
const video = document.getElementById(
'video' + user.userId
) as HTMLVideoElement;
track.play(video);
}
},
},
});
if (code === RCCallErrorCode.SUCCESS) {
// do something
}
发起多人通话
成功建立 IM 连接后,使用初始化 CallLib 时获取的 RCCallClient
实例的 callInGroup 方法发起多人通话。多人通话必须在同一群组中。
/**
* 发起多人通话,成功后会产生新的 session
* @param targetId 群组 Id(必填)
* @param userIds 被呼叫的群内成员 Id(必填)
* @param mediaType 0->音频呼叫,2->音视频呼叫(必填)
* @param listener session 对象上注册的事件(必填)
* @param constraints 获取音频或音视频资源参数(可选)
* @param channelId 组织 Id(可选)
*/
const { code, session } = await caller.callInGroup({
targetId: this.targetId,
userIds,
mediaType,
listener: {
/**
* 远端用户已开始响铃,表示对方已收到呼叫请求(必填)
* @param sender 已响铃用户
* @param session 当前 session 对象
*/
onRinging(sender: ISenderInfo, session: RCCallSession){
const { userId } = sender;
},
/**
* 远端用户同意接听(必填)
* @param sender 远端用户
* @param session 当前 session 对象
*/
onAccept(sender: ISenderInfo, session: RCCallSession){
const { userId } = sender;
},
/**
* 有远端用户挂断(必填)
* @param sender 远端用户
* @param reason 挂断原因
* @param session 当前 session 对象
*/
onHungup(sender: ISenderInfo, reason: RCCallEndReason, session: RCCallSession){
const { userId } = sender;
},
/**
* 本端或远端资源已获取(必填)
* @param track 本端或远端资源,track 不可设置为 Vue 响应式数据
* @param session 当前 session 对象
*/
onTrackReady(track: RCTrack, session?: RCCallSession){
// track.isLocalTrack() 是否为本地资源
// track.isAudioTrack() 是否为音频
// track.isVideoTrack() 是否为视频
// track.getUserId() 产生该 track 的用户 ID
// 播放音频。远端音频建议直接播放,本端音频建议不播放以减少回音。
if (track.isAudioTrack() && !track.isLocalTrack()) {
track.play();
}
// 视频在对应容器播放
if (track.isVideoTrack()) {
const video = document.getElementById(
"video" + user.userId
) as HTMLVideoElement;
track.play(video);
}
},
/**
* 群组通话中有其他人被邀请加入(必填)
* @param sender 发送者
* @param invitedUsers 被邀请的用户列表
* @param session 当前的 session 对象
*/
onMemberModify: (sender: ISenderInfo, invitedUsers: IInvitedUsers[], session: RCCallSession) => {
}
},
});
if (code === RCCallErrorCode.SUCCESS) {
// do something
}
步骤 6:接听
调用 accept 方法接通电话。若为音视频通话,接通前可设置视频参数。接通后可将音视频通话转为纯音频通话,支持通话中切换音频输入设备。
通话媒体类型(纯音频或音视频)由发起方决定。
const { code } = await session.accept();
if (code === RCCallErrorCode.SUCCESS) {
// do something
}
步骤 7:挂断
调用 hungup 方法挂断(或拒绝)通话。SDK 内部会自动告知对方挂断(或拒绝)的原因。
const { code } = await session.hungup();
if (code === RCCallErrorCode.SUCCESS) {
// do something
}