跳到主要内容

实现音视频通话

在本教程中,您将体验如何集成融云 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 方式安装方法

  1. 安装 IMLib 5.X(推荐新集成客户使用 5.X 版本)。

    shell
    # 安装 IMLib v5
    npm install @rongcloud/engine@latest @rongcloud/imlib-next --save
  2. 安装 RTCLib 5.X。

    shell
    # 安装 RTCLib
    npm install @rongcloud/plugin-rtc --save
  3. 安装 CallLib 5.X。

    shell
    # 安装 CallLib
    npm install @rongcloud/plugin-call --save
  4. 安装完成后,即可在代码中导入 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 客户端。

  1. 调用 IMLib 的 init 方法初始化 IM 客户端(IMLib 5.X)。appkey 即您的融云应用 App Key。

    typescript
    // IM 客户端初始化(IMLib 5.X)
    RongIMLib.init({
    appkey: '<your-app-key>',
    });
  2. 初始化 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 服务建立连接,之后再进行音视频呼叫。

typescript
//与 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 方法发起单人通话。

typescript
/**
* 发起单人通话,成功后会产生新的 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 方法发起多人通话。多人通话必须在同一群组中。

typescript
/**
* 发起多人通话,成功后会产生新的 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 方法接通电话。若为音视频通话,接通前可设置视频参数。接通后可将音视频通话转为纯音频通话,支持通话中切换音频输入设备。

通话媒体类型(纯音频或音视频)由发起方决定。

typescript
const { code } = await session.accept();
if (code === RCCallErrorCode.SUCCESS) {
// do something
}

步骤 7:挂断

调用 hungup 方法挂断(或拒绝)通话。SDK 内部会自动告知对方挂断(或拒绝)的原因。

typescript
const { code } = await session.hungup();
if (code === RCCallErrorCode.SUCCESS) {
// do something
}