使用融云 CallPlus SDK,一小时实现一款 1V1 视频应用

9 月 21 日,融云直播课 社交泛娱乐出海最短变现路径如何快速实现一款 1V1 视频应用? 欢迎点击小程序报名~

1V1 音视频、远程服务类应用的实现利器------融云 CallPlus SDK 上线! 关注【融云全球互联网通信云】了解更多

作为新一代音视频通话场景化 SDK,融云 CallPlus 完整封装了拨打、接听、挂断等整套呼叫流程,支持一对一及多人音视频通话,功能齐全、体验丝滑,且契合海外用户的交互偏好。

本文将以 Android 端集成为例,分享实战教程:

使用融云 CallPlus SDK,一小时集成 1V1 视频通话能力。

一个 RTC 实时音视频底层零经验开发者 ,只需 3 个核心 API、4 步 即可轻松实现音视频通话能力。并且,融云提供 Quick Demo 源码供开发者集成参考。


前置条件

创建融云开发者账号

创建融云开发者账号,获取 App Key

开始之前,需创建融云开发者账号并获取 App Key。在开发者后台,系统会自动为新账号创建一个应用。默认使用国内数据中心,并提供开发环境。如果您已经有融云开发者账号,可以直接创建新应用。

导入 SDK

打开根目录下的 build.gradle(新版 Android studio 为 settings.gradle),Project 视图下,声明融云的 Maven 代码库。

复制代码
allprojects {
  repositories {
      ...
      //融云 maven 仓库地址
      maven {url "https://maven.rongcloud.cn/repository/maven-releases/"}
  }
}

添加依赖项

在应用的 build.gradle 中,添加如下远程依赖项。

注意:融云 CallPlus 业务依赖 IM 通道,所以须同时集成 IMLibCore SDK。

复制代码
dependencies {
  // 请填写具体的 SDK 版本号,新集成用户建议使用最新版。此处以5.6.2版本为例。
  implementation 'cn.rongcloud.sdk:im_libcore:5.6.2'  // 即时通讯基础能力库。
  implementation 'cn.rongcloud.sdk:callplus_lib:1.0.0'// 音视频呼叫能力库(内含 rtc_lib)
}

权限声明

在 AndroidManifest.xml 中声明 SDK 需要的所有权限。

复制代码
<!-- 允许程序访问网络连接 -->
<uses-permission android:name="android.permission.INTERNET" />
<!-- 允许程序获取网络信息状态,如当前的网络连接是否有效 -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<!-- 允许程序获取当前WiFi接入的状态以及WLAN热点的信息 -->
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<!-- 允许程序访问摄像头进行拍照 -->
<uses-permission android:name="android.permission.CAMERA" />
<!-- 允许程序录制声音通过手机或耳机的麦克 -->
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<!-- 允许程序修改声音设置信息 -->
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<!--允许程序访问电话状态,如通话中收到来自SIM卡的来电时,会将SIM卡通话状态通知给远端用户,所以需要该权限-->
<uses-permission android:name="android.permission.READ_PHONE_STATE" />

注意:如果开发应用需要支持 Android 6.0(API 级别 23)或更高版本的设备,还需要在 App 用户使用对应功能时(如发起呼叫、接听)请求摄像头(CAMERA)、麦克风(RECORD_AUDIO)权限。

详见 Android 开发者官方文档运行时权限与请求权限的工作流。


正式集成

在着手实现之前,我们需要关注以下几个关键点:

☑ 如何发起通话

☑ 如何接收来电

☑ 通话接通后,双方通话界面如何显示

要想实现通话功能,必须以双端都已经成功连接融云为基础,参考下图所示 1V1 主叫端和被叫端的流程时序图。

主叫端时序图:

被叫端时序图:

初始化连接融云

CallPlus for Android 依赖融云即时通讯客户端 SDK 提供信令通道,故需先对 IMLibCore 进行初始化,建议放到 Application 中。

复制代码
String appKey = "Your_AppKey"; // example: bos9p5rlcm2ba 创建融云开发者账号,获取 App Key。
InitOption initOption = new InitOption.Builder().build();
RongCoreClient.init(getApplicationContext(), appKey, initOption);

要拨打和接听一对一呼叫或开始多人呼叫,必须先通过 RongCoreClient 的 connect 方法连接融云服务器。

传入用户身份令牌(Token),向融云服务器验证用户身份;连接成功后,使用 RCCallPlusClient.getInstance().init() 方法初始化和配置 CallPlus SDK。

复制代码
String token = "用户Token";// 您在申请开发者账号后,可以在融云后台北极星位置直接生成用户token
RongCoreClient.connect(token, new IRongCoreCallback.ConnectCallback() {
   /**
    * 成功回调
    * @param userId 当前用户 ID
    */
   @Override
   public void onSuccess(String userId) {
      runOnUiThread(new Runnable() {
          @Override
          public void run() {
              //todo 尽管在主线程初始化 RCCallPlusClient 不是必需的,但考虑到代码示例后续对 RCCallPlusClient 的调用都在主线程进行,所以目前选择在主线程进行初始化。
              //todo 请确保在同一个线程进行 RCCallPlusClient 的初始化、反初始化和使用,以确保操作的一致性。
              RCCallPlusConfig config = RCCallPlusConfig.Builder.create().build();
              /**
               * 初始化并设置通话全局配置,重复调用该方法时SDK内部会重新初始化
               * @param config    设置通话全局配置
               * @return 方法调用后同步返回结果,可以在这里得到初始化是否成功
               */
              RCCallPlusResultCode resultCode = RCCallPlusClient.getInstance().init(config);
          }
      });

   }
   /**
    * 错误回调
    * @param errorCode 错误码
    */
   @Override
   public void onError(IRongCoreEnum.ConnectionErrorCode errorCode) {

   }

   /**
    * 数据库回调.
    * @param code 数据库打开状态. DATABASE_OPEN_SUCCESS 数据库打开成功; DATABASE_OPEN_ERROR 数据库打开失败
    */
   @Override
   public void onDatabaseOpened(DatabaseOpenStatus code) {

   }
});

发起呼叫并设置本地和远端视图

使用 startCall 方法来发起一对一通话。

该方法内部会以异步方式执行,并通过 IRCCallPlusResultListener#onStartCall 回调来获取方法的结果。

在发起通话之前需先设置本地和远端视图,在对端接听视频通话时,本端会自动渲染对端的视图。

使用 setCallPlusResultListener 方法添加通话 API 异步结果回调监听。该监听可以接收 startCall、accept、hangup 等方法的结果回调。

发起呼叫:

复制代码
  private void startCall(String remoteUserId) {
        //todo 打开摄像头采集,请提前完成摄像头、麦克风权限的动态申请
        RCCallPlusClient.getInstance().startCamera();
        RCCallPlusClient.getInstance().enableMicrophone(true);

        //设置本端视图
        setLocalVideoView();
        //设置对端视图
        setRemotVideoView(remoteUserId);

        List<String> userIds = new ArrayList<>();
        userIds.add(remoteUserId);//todo remoteUserId 为被呼叫的远端用户userId
        RCCallPlusType callType = RCCallPlusType.PRIVATE;//PRIVATE: 1V1通话
        RCCallPlusMediaType mediaType = RCCallPlusMediaType.VIDEO;
        /**
         * 开始发起呼叫
         * 该方法内部为异步执行,结果回调是注册的{@link RCCallPlusClient#setCallPlusResultListener(IRCCallPlusResultListener)} 监听的 {@link IRCCallPlusResultListener#onStartCall(RCCallPlusCode, String, List)}方法<br>
         */
        RCCallPlusClient.getInstance().startCall(userIds, callType, mediaType);
    }

发起端设置本地视图:

复制代码
   /**
    * 设置本地视频渲染视图
    */
   private void setLocalVideoView() {
       //创建本地视图对象
       RCCallPlusLocalVideoView localVideoView = new RCCallPlusLocalVideoView(this.getApplicationContext());
       //FIT: 视频帧通过保持宽高比(可能显示黑色边框)来缩放以适应视图的大小
       localVideoView.setRenderMode(RCCallPlusRenderMode.FIT);
       //设置本地视图给  SDK
       RCCallPlusClient.getInstance().setVideoView(localVideoView);

       FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT);
       params.gravity = Gravity.CENTER_HORIZONTAL;//在父布局中横向居中显示
       //将本地视图添加到XML中显示
       //示例代码中 mLocalVideoViewFrameLayout 为 android.widget.FrameLayout 对象
       mLocalVideoViewFrameLayout.removeAllViews();
       mLocalVideoViewFrameLayout.addView(localVideoView, params);
   }

发起端设置对端视图:

复制代码
   /**
    * 发起通话时设置对端视频渲染视图
    */

private void setRemotVideoView(String remoteUserId) {
    //创建远端视图对象 remoteUserId为远端用户userId
    RCCallPlusRemoteVideoView remoteVideoView = new RCCallPlusRemoteVideoView(remoteUserId, this.getApplicationContext(), false);
    //FIT: 视频帧通过保持宽高比(可能显示黑色边框)来缩放以适应视图的大小
    remoteVideoView.setRenderMode(RCCallPlusRenderMode.FIT);
    //因为远端视图显示在最顶层,为了防止远端视频视图被底部控件遮挡,所以添加如下设置:
    remoteVideoView.setZOrderOnTop(true);
    remoteVideoView.setZOrderMediaOverlay(true);

    List<RCCallPlusRemoteVideoView> remoteVideoViewList = new ArrayList<>(); remoteVideoViewList.add(remoteVideoView);
    //设置远端视图给SDK
    RCCallPlusClient.getInstance().setVideoView(remoteVideoViewList);

    FrameLayout.LayoutParams remoteVideoViewParams = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT);
    remoteVideoViewParams.gravity = Gravity.CENTER_HORIZONTAL;
    //将远端视图添加到XML中显示
    //示例代码中 mRemoteVideoViewFrameLayout 为 android.widget.FrameLayout 对象
    mRemoteVideoViewFrameLayout.removeAllViews();
    mRemoteVideoViewFrameLayout.addView(remoteVideoView, remoteVideoViewParams);
}

接收端接听通话并设置本地和远端视图

可选择接听或挂断来电,若要接听电话,请使用 accept 方法;若要挂断来电,请使用 RCCallPlusClient.getInstance().hangup() 方法。

可以通过 RCCallPlusSession#getCallId() 方法获取执行接听和挂断操作所需的 CallId 值。

要接收远端呼叫通知,必须确保已经注册了 IRCCallPlusEventListener,并实现了 onReceivedCall(RCCallPlusSession callSession) 方法。被叫用户通过与融云服务端的连接或者离线推送通知(离线推送 App 必须已集成第三方厂商推送,详见推送 2.0 集成概述)接收来电通知。

使用 setCallPlusEventListener 方法添加通话事件监听,提供来电事件、通话状态、通话记录等事件相关回调。

添加通话事件监听:

复制代码
RCCallPlusClient.getInstance().setCallPlusEventListener(new IRCCallPlusEventListener() {

    /**
     * 用户通过该回调接收到通话呼叫 在这个回调中,可以接听通话
     *
     * @param callSession   通话实体信息<br>
     */
    @Override
    public void onReceivedCall(RCCallPlusSession callSession) {
        RCCallPlusSession currentCallSession = RCCallPlusClient.getInstance().getCurrentCallSession();
        if (currentCallSession != null && !TextUtils.equals(callSession.getCallId(), currentCallSession.getCallId())) {
            //可以使用该方法判断出,有正在进行中的通话,又有第二通通话呼入的情况<br>
            //todo 第二通通话可以直接调用 RCCallPlusClient.getInstance().accept 方法接听,SDK内部会将第一通通话挂断
        }

        //todo SDK 的回调均为子线程调用,showDialog() 方法中存在UI操作,所以切换到主线程执行
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                //todo 打开摄像头采集,请提前完成摄像头、麦克风权限的动态申请
                RCCallPlusClient.getInstance().startCamera();
                RCCallPlusClient.getInstance().enableMicrophone(true);

                setLocalVideoView();//复用发起通话逻辑中的 设置本地视频渲染视图 方法

                showDialog(CallPlusActivity.this, "收到通话,是否接听?", "接听", new OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialogInterface, int i) {
                        acceptCall(callSession);
                    }
                }, "挂断", new OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialogInterface, int i) {
                        RCCallPlusClient.getInstance().hangup();
                    }
                });
            }
        });
    }

    @Override
    public void onCallEnded(RCCallPlusSession session, RCCallPlusReason reason) {
        IRCCallPlusEventListener.super.onCallEnded(session, reason);
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                Toast.makeText(CallPlusActivity.this,"通话结束,callId: "+session.getCallId() +" 通话结束原因:"+ reason.getValue(), Toast.LENGTH_SHORT).show();
            }
        });
    }

    /**
     * 远端用户状态改变监听<br>
     *
     * @param callId 通话Id<br>
     * @param userId 用户Id<br>
     * @param status 该用户当前状态<br>
     * @param reason 该用户当前状态原因<br>
     */
    @Override
    public void onRemoteUserStateChanged(String callId, String userId, RCCallPlusUserSessionStatus status, RCCallPlusReason reason) {
        IRCCallPlusEventListener.super.onRemoteUserStateChanged(callId, userId, status, reason);
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                StringBuilder stringBuilder = new StringBuilder("通话 ");
                stringBuilder.append(callId).append(" 中的远端用户 ").append(userId).append(" 当前状态为 ");
                switch (status) {
                    case CALLING:
                        stringBuilder.append("呼叫中");
                        break;
                    case INVITED:
                        stringBuilder.append("被邀请中");
                        break;
                    case CONNECTING:
                        stringBuilder.append("已接听,连接中");
                        break;
                    case ON_CALL:
                        stringBuilder.append("通话中");
                        break;
                    case ENDED:
                        stringBuilder.append("通话已结束");
                        break;
                }
                Toast.makeText(CallPlusActivity.this, stringBuilder.toString(), Toast.LENGTH_SHORT).show();
            }
        });
    }
});

具体接听通话的方法:

复制代码
private void acceptCall(RCCallPlusSession callSession) {
    setRemoteUserVideoView(callSession.getRemoteUserList());
    /**
     * 开始接听通话
     * 该方法内部为异步执行,结果回调是注册的{@link RCCallPlusClient#setCallPlusResultListener(IRCCallPlusResultListener)} 监听的 {@link IRCCallPlusResultListener#onAccept(RCCallPlusCode, String)}方法<br>
     */
    RCCallPlusClient.getInstance().accept(callSession.getCallId());
}

接收端设置本地和远端视图:

复制代码
/**
 * 接听方设置远端用户视频渲染视图
 */
private void setRemoteUserVideoView(List<RCCallPlusUser> remoteUserList) {
    List<RCCallPlusRemoteVideoView> remoteVideoViewList = new ArrayList<>();
    for (RCCallPlusUser callPlusUser : remoteUserList) {
        RCCallPlusRemoteVideoView remoteVideoView = new RCCallPlusRemoteVideoView(callPlusUser.getUserId(), this.getApplicationContext(), false);
        //视频帧通过保持宽高比(可能显示黑色边框)来缩放以适应视图的大小
        remoteVideoView.setRenderMode(RCCallPlusRenderMode.FIT);
        remoteVideoViewList.add(remoteVideoView);
        //本示例代码中,因为远端视图显示在最顶层,为了防止远端视频视图被底部控件(视图)遮挡,所以添加如下设置:
        remoteVideoView.setZOrderOnTop(true);
        remoteVideoView.setZOrderMediaOverlay(true);

        FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT);
        params.gravity = Gravity.CENTER_HORIZONTAL;
        //todo 将每个远端视图(remoteVideoView)添加到XML中显示,远端为多人时,需要添加给多个控件显示,本示例代码仅展示一个远端用户情况
        mRemoteVideoViewFrameLayout.removeAllViews();
        mRemoteVideoViewFrameLayout.addView(remoteVideoView, params);
    }
    /**
     * 设置远端用户视频流渲染视图给SDK
     * 若没有为远端用户设置视频渲染视图,则不会产生该用户的视频流的下行流量
     */
    RCCallPlusClient.getInstance().setVideoView(remoteVideoViewList);
}
private AlertDialog showDialog(Context context, String content, String positiveBtn, final DialogInterface.OnClickListener positiveListener, final String negativeBtn, final DialogInterface.OnClickListener negativeListener) {
    AlertDialog.Builder builder = new AlertDialog.Builder(context);
    builder = builder.setMessage(content);
    builder.setCancelable(false);
    if (!TextUtils.isEmpty(positiveBtn)) {
        builder.setPositiveButton(positiveBtn, new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                if (positiveListener != null) {
                    positiveListener.onClick(dialog, which);
                } else {
                    dialog.dismiss();
                }
            }
        });
    } if (!TextUtils.isEmpty(negativeBtn)) {
        builder.setNegativeButton(negativeBtn, new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                if (negativeListener != null) {
                    negativeListener.onClick(dialog, which);
                } else {
                    dialog.dismiss();
                }
            }
        });
    } return builder.show();
}

运行项目发起通话,当被叫端接听并且主叫端能够看到双方视频,说明 1V1 音视频通话核心能力已经实现了,全程仅需 4 个步骤、3 个核心 API。


其他功能

切换前后摄像头

成功打开摄像头后,可以使用 switchCamera 方法切换前后摄像头。

switchCamera 方法是异步调用的,支持通过 IRCCallPlusResultListener 的 onSwitchCamera 回调来获取调用结果。

复制代码
RCCallPlusClient.getInstance().switchCamera();

RCCallPlusClient.getInstance().setCallPlusResultListener(new IRCCallPlusResultListener() {
    /**
     * 切换前后摄像头方法结果回调<br>
     *
     * @param code 方法请求结果<br>
     * @param isFrontCamera 当前开启的摄像头是否是前置摄像头<br>
     */
    @Override
    public void onSwitchCamera(RCCallPlusCode code, boolean isFrontCamera) {
        IRCCallPlusResultListener.super.onSwitchCamera(code, isFrontCamera);
    }
});

美颜

融云 SDK 已经接入专业三方美颜服务,三步即可实现美颜功能。

  1. 在应用的 build.gradle 中添加如下远程依赖项。

    复制代码
    implementation 'cn.rongcloud.sdk:fu_beautifier:5.6.2'
    implementation 'androidx.core:core-ktx:1.7.0'
  2. 提供有效的美颜授权文件(感兴趣可联系融云商务详细咨询:131 6185 6839)。

  3. 初始化美颜插件,初始化时请提供有效的美颜授权文件。应用运行期间只调用一次即可,建议在 Application#onCreate 中初始化。

下面代码块中的 authpackNew 即为相关美颜服务的授权文件。

复制代码
RCRTCFUBeautifierEngine.getInstance()
        .register(
                getApplicationContext(),
                null,
                authpackNew.A(),
                new FUBeautifierResultCallback() {
                    @Override
                    public void onSuccess() {
                        setBeautyEnable();
                        SetBeautyParameters();
                    }

                    @Override
                    public void onFailed(int code) {
                    }
                });

private void setBeautyEnable() {
    // 打开美颜开关后设置的美颜效果才会生效;关闭开关美颜会失效。
    RCRTCFUBeautifierEngine.getInstance().setBeautyEnable(true, new FUBeautifierResultCallback() {
        @Override
        public void onSuccess() {
        }

        @Override
        public void onFailed(int code) {

        }
    });
}
// 设置美颜参数
private void SetBeautyParameters() {
    RCRTCFUBeautifierEngine.getInstance().setBlurIntensity(6); 范围[0-6]
    RCRTCFUBeautifierEngine.getInstance().setColorIntensity(2); // 范围 [0-2]
    RCRTCFUBeautifierEngine.getInstance().setRedIntensity(2);// 范围 [0-2]
    RCRTCFUBeautifierEngine.getInstance().setSharpenIntensity(1);// 范围 [0-1]
    RCRTCFUBeautifierEngine.getInstance().setEyeBrightIntensity(1); // 范围 [0-1]
    RCRTCFUBeautifierEngine.getInstance().setToothIntensity(1);// 范围 [0-1]
    RCRTCFUBeautifierEngine.getInstance().setRemovePouchIntensity(1);// 范围 [0-1]
    RCRTCFUBeautifierEngine.getInstance().setRemoveLawPatternIntensity(1);// 范围 [0-1]
}

在业务开发集成、上线运营等全过程中,融云都将提供全流程一站式技术服务支持,开发者可提交工单与融云工程师交流。欢迎来电咨询:131 6185 6839


最后,callback 一下融云 CallPlus SDK 的核心优势:

**完整封装:**提供完整的呼叫功能方案,包括连接、呼叫、接听、拒接、挂断、呼叫状态通知等。

**集成便捷:**接口设计贴近业务且简洁明了,结合 Quick Demo 源码,开发者只需使用 3 个核心接口,一小时即可实现音视频通话核心功能。

**场景全面:**支持 iOS、Android、Web 等平台,可以满足陌生人社交、在线招聘、远程医疗、线上咨询、售后客服等多种单人和多人通话场景使用。

服务稳定: 100% 可靠必达的音视频呼叫信令能力 ,保证连接安全可靠;音频弱网抗丢包 80%,视频弱网抗丢包 60%,并有 3A 算法,保证通话清晰稳定。

**周边完善:**提供业务场景所需的丰富高级功能,包括内容审核、云端录制、高级美颜等,让开发者的业务开展无忧且高效。

**性价比高:**目前月功能费仅为 1500 元/月,含 200,000 分钟免费时长。场景灵活度高,不限音视频,视频最高分辨率可支持 2K+;真正省心透明,支持 RTC 与 IM 服务单独采购,且不单独收取呼叫信令费用。

相关推荐
我也要当昏君1 小时前
6.3 文件传输协议 (答案见原书 P277)
网络
Greedy Alg1 小时前
Socket编程学习记录
网络·websocket·学习
刘逸潇20052 小时前
FastAPI(二)——请求与响应
网络·python·fastapi
Mongnewer3 小时前
通过虚拟串口和网络UDP进行数据收发的Delphi7, Lazarus, VB6和VisualFreeBasic实践
网络
我也要当昏君3 小时前
6.5 万维网(答案见原书P294)
网络
嶔某4 小时前
网络:传输层协议UDP和TCP
网络·tcp/ip·udp
文火冰糖的硅基工坊4 小时前
[嵌入式系统-154]:各种工业现场总线比较
网络·自动驾驶·硬件架构
以己之4 小时前
详解TCP(详细版)
java·网络·tcp/ip
音视频牛哥4 小时前
从“小而美”到“大而强”:音视频直播SDK的技术进化逻辑
机器学习·计算机视觉·音视频·大牛直播sdk·人工智能+·rtsp播放器rtmp播放器·rtmp同屏推流
空影星4 小时前
GridPlayer,一个好用的多视频同步播放器
python·flask·电脑·音视频