跳到主要内容

实现音视频通话

在本教程中,您可以体验集成融云 Web 端音视频通话(呼叫)SDK CallLib 的集成流程。 CallLib 支持单人音视频呼叫和基于群组的多人呼叫场景。

示例项目

融云音视频通话 QuickDemo(GitHub · Gitee)演示了融云产品音视频通话在 Web 端的功能,以便开发者体验产品,快速集成,实现单群聊、音视频通话等场景需求。

QuickDemo 开放源代码,您可以对感兴趣的部分进行代码改造,以便进一步了解细节。具体实现详见运行示例项目

前置条件

注意

浏览器页面地址必须为 https 协议地址,或使用 localhost 域名。

CallLib 概念说明

注意

通过 onSessionClose 监听函数获得通话结束。

步骤 1:开通服务

您在融云创建的应用不会默认启用音视频服务。在使用融云提供的任何音视频服务前,您需要前往控制台,为应用开通音视频服务。

具体步骤请参阅控制台文档开通音视频服务

注意

服务开通、关闭等设置完成后 30 分钟后生效。

步骤 2:导入 SDK

您可以使用 NPM 安装 CallLib 与其依赖的 IMLib 与 RTCLib,或者使用 CDN 方式安装。

CallLib 相关业务依赖 IMLib 作为信令通道。因此,开发音视频通话必须安装融云音视频核心能力库 RTCLib,即时通讯能力库 IMLib。您可选用 2.X 或 4.X 或 5.X 版本的 IMLib。

本部分仅针对新集成客户简要说明 NPM 方式安装方法。以下步骤中均以 IMLib 5.X 为例说明。

注意

CallLib 配合使用 IMLib 2.X,4.X 的详细说明,以及 CDN 方式安装方法,请参阅 安装 CallLib SDK

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

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

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

    # 安装 CallLib
    npm install @rongcloud/plugin-call --save
  4. 全部下载安装完成后,即可在代码中导入 IMLib(5.X)、RTCLib 与 CallLib 库。


    // 导入 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 客户端的初始化方式。

注意

注意,CallLib 配合使用 IMLib 2.X,4.X 时,IM、RTC 与 CallLib 客户端初始化方式均有不同。详见初始化

请依次初始化 IM 客户端、RTC 客户端与 CallLib 客户端。

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

    // IM 客户端初始化(IMLib 5.X)
    RongIMLib.init({
    appkey: '<your-app-key>',
    });
  2. 初始化 RTC 客户端与 CallLib 客户端。在初始化 CallLib 客户端实例时,传入 onSession 函数监听来电。初始化完成后,可获取 RCCallClient 实例对象 callerRCCallClient 主要用于发起呼叫。

    // 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)
}
})

注意

注意,如 CallLib 配合使用 IMLib 2.X 或 4.X 版本,建立 IM 连接的方式如下:

// 与 IM 服务建立连接(IMLib 4.X)
imClient.connect({ token: '<user-token>' })
.then((user) => {
console.log("链接成功, 链接用户 id 为: ", user.id);

})
.catch((error) => {
console.log("链接失败: ", error.code, error.msg);
});


// 与 IM 服务建立连接(IMLib 2.X)
RongIMClient.connect(('<user-token>', {
onSuccess: (userId) => {
console.log('连接成功, 用户 ID 为', userId);
},
onTokenIncorrect: function() {
console.log('连接失败, 失败原因: token 无效');
},
onError: function(errorCode) {
console.log('连接失败, 失败原因: ', errorCode);
}
});

步骤 5:发起呼叫

主动呼叫分为发起单人通话和发起多人通话,可根据实际需求调用。多人通话的场景必须在一个群组内。

发起单人通话

成功建立 IM 连接后,使用初始化 CallLib 时获取的 RCCallClient 实例的 call 方法发起单人通话。

/**
* 发起单人通话,如果成功后会产生一个新的session
* @param targetId 被呼叫一方的用户 id 必填
* @param mediaType 1->音频呼叫 or 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->音频呼叫 or 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
}