实现1对1音视频通话

更新时间: 2023/12/12 09:55:07

本文介绍 1 对 1 音视频通话的实现方法。

前提条件

请确保您已完成以下操作:

开发环境

开发环境要求如下:

环境要求 说明
JDK 版本 1.8.0 及以上版本
Android API 版本 API 21、Android Studio 5.0 及以上版本
CPU架构 ARM 64、ARMV7
IDE Android Studio
其他 依赖 Androidx,不支持 support 库。

示例项目源码

1 对 1 娱乐社交示例项目源码,跑通示例项目的方法请参见跑通示例项目

注意事项

1 对 1 娱乐社交场景方案的呼叫能力基于云信呼叫组件,具体请参见呼叫组件文档

技术原理

一对一通话功能页面结构如下:

页面 描述
CallActivity 呼叫页&&通话页容器,默认加载CallFragment,在呼叫接通后根据音视频类型加载InTheAudioCallFragment或者InTheVideoCallFragment
CallFragment 呼叫页UI,包括主叫与被叫
InTheAudioCallFragment 音频通话页UI,包括主叫与被叫
InTheVideoCallFragment 视频通话页UI,包括主叫与被叫

音视频通话的时序图如下图所示。

sequenceDiagram

    actor  A as 用户A
    participant HJA as 呼叫组件A
    participant G2 as G2服务器
    participant XL as 信令服务器
    participant HJB as 呼叫组件B
    participant server as 业务服务器
    actor B as 用户B


    A ->> server: 请求校验是否可以发起呼叫
    server -->> A: 返回校验结果,分配ChannelName、uid等信息(可选)

    Note over A,B: 呼叫
    rect rgb(191, 223, 255)
    A ->> HJA: A发起呼叫邀请
    HJA ->> XL: 呼叫组件发起呼叫邀请
    XL -->> HJB: A邀请B的信令
    HJB -->> B: A邀请B
    B ->> HJB: B接听
    HJB ->> XL: B接听
    HJB ->> G2: B加入RTC
    XL -->> HJA: B接听的信令
    HJA ->> G2: A加入RTC
    HJA -->> A: B接听
    end

    Note over A,B: 计费
    G2 ->> server: 发送用户A 加入房间的抄送
    G2 ->> server: 发送用户B 加入房间的抄送
    server ->> server: 开始计费
    A ->> server: 通话中客户端向业务服务器发送计费心跳(可选)

    alt 一方挂断电话 
    G2 ->> server: 发送用户离开房间的抄送
    server ->> server: 停止计费
    server -->> A: 发送通话话单

    else 出现欠费
    server -->> G2: 出现欠费,销毁房间
    G2 -->> HJA: 通话结束
    HJA -->> A: 通话结束
    G2 -->> HJB: 通话结束
    HJB -->> B: 通话结束
    end

步骤1 集成呼叫组件 SDK

1 对 1 娱乐社交场景方案的呼叫能力基于云信呼叫组件来进行实现,详细步骤请参考实现1对1呼叫(含UI集成-V1)

  1. 在工程根部目录的 build.gradle 文件中添加如下代码。

    allprojects {
        repositories {
            //...
            mavenCentral()
            //...
        }
    }
    
  2. 在主工程 build.gradle 文件中添加如下代码,引入呼叫组件。

    // 若出现 More than one file was found with OS independent path 'lib/arm64-v8a/libc++_shared.so'.
    // 可以在主 module 的 build.gradle 文件中 android 闭包内追加如下 packageOptions 配置
    android{
        //......
        packagingOptions {
        pickFirst 'lib/arm64-v8a/libc++_shared.so'
        pickFirst 'lib/armeabi-v7a/libc++_shared.so'
        }
    }
    
    dependencies {
        // 集成呼叫组件  https://doc.yunxin.163.com/nertccallkit/docs/DMzOTI3NTA?platform=android
        implementation 'com.netease.yunxin.kit.call:call-ui:x.x.x'  
    }
    

步骤2 初始化 IM SDK

ApplicationonCreate 中,调用 init 方法进行初始化。

详细信息可参考初始化 IM SDK

示例代码

  public class SampleApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        NIMClient.init(this, null, options());
    }
    // 如果返回值为 null,则全部使用默认参数。
    private SDKOptions options() {
        SDKOptions options = new SDKOptions();
        //此处仅设置appkey,其他设置请自行参考信令的初始化文档:https://doc.yunxin.163.com/docs/DA5MjI4NDY/zY0NDU2OTE?platformId=80002
        options.appKey = AppConstants.APP_KEY;
        return options;
    }

}

步骤3 登录 IM

调用 login 方法登录 IM。

本文以实现静态 Token 登录为例,动态 Token 登录以及自动登录的实现方法请参见云信 IM 登录

示例代码

public class LoginActivity extends Activity {
	public void doLogin() {
		LoginInfo info = new LoginInfo(); 
		RequestCallback<LoginInfo> callback =
			new RequestCallback<LoginInfo>() {
			        @Override
                    public void onSuccess(LoginInfo param) {
                        LogUtil.i(TAG, "login success");
                        // your code
                    }

                    @Override
                    public void onFailed(int code) {
                        if (code == 302) {
                            LogUtil.i(TAG, "账号密码错误");
                            // your code
                        } else {
                            // your code
                        }
                    }

                    @Override
                    public void onException(Throwable exception) {
                        // your code
                    }
		};

        //执行手动登录
		NIMClient.getService(AuthService.class).login(info).setCallback(callback);
	}
}

步骤4 初始化呼叫组件

IM 登录成功后,初始化呼叫组件。

示例代码

    在IM登录成功之后,初始化呼叫组件
    if (ProcessUtils.isMainProcess()){
            initCallKit();
        }
        private void initCallKit() {
        CallKitUIOptions options = new CallKitUIOptions.Builder()
                // 必要:音视频通话 sdk appKey,用于通话中使用
                .rtcAppKey(AppConstants.APP_KEY)
                // 必要:当前用户 AccId
                .currentUserAccId(UserInfoManager.getSelfImAccid())
                // 此处为 收到来电时展示的 notification 相关配置,如图标,提示语等。
                .notificationConfigFetcher(invitedInfo -> new CallKitNotificationConfig(R.mipmap.ic_launcher))
                // 收到被叫时若 app 在后台,在恢复到前台时是否自动唤起被叫页面,默认为 true
                .resumeBGInvitation(true)
                .rtcTokenService(new TokenService() {
                    @Override
                    public void getToken(long uid, RequestCallback<String> callback) {
                        HttpService.requestRtcToken(uid).subscribe(new ResourceSingleObserver<BaseResponse>() {
                            @Override
                            public void onSuccess(BaseResponse response) {
                                LogUtil.d("getToken", "response:" + response);
                                if (response.isSuccessful()) {
                                    callback.onSuccess((String) response.data);
                                } else {
                                    callback.onFailed(response.code);
                                }
                            }

                            @Override
                            public void onError(Throwable e) {
                                LogUtil.e("getToken", "e:" + e);
                                callback.onException(e);
                            }
                        });

                    }
                }) // 自己实现的 token 请求方法
                .rtcSdkOption(new NERtcOption())
                // 呼叫组件初始化 rtc 范围,true-全局初始化,false-每次通话进行初始化以及销毁
                // 全局初始化有助于更快进入首帧页面,当结合其他组件使用时存在rtc初始化冲突可设置false
                .rtcInitScope(true)
                // 配置音频呼叫页
                .p2pAudioActivity(CallActivity.class)
                //配置视频呼叫页
                .p2pVideoActivity(CallActivity.class)
                .build();
// 若重复初始化会销毁之前的初始化实例,重新初始化
        PstnCallKitOptions pstnCallKitOptions = new PstnCallKitOptions.Builder(options)
                .timeOutMillisecond(CallConfig.CALL_TOTAL_WAIT_TIMEOUT)
                .transOutMillisecond(CallConfig.CALL_PSTN_WAIT_MILLISECONDS).build();
        PstnUIHelper.init(getApplicationContext(), pstnCallKitOptions);
    }

步骤5 实现呼叫功能

示例代码

   // 唤起呼叫页面 
   CallParam param = CallParam.createSingleCallParam(ChannelType.VIDEO.getValue(), UserInfoManager.getSelfImAccid(), userModel.imAccid, extraInfo.toString());
   CallKitUI.startSingleCall(context, param);
   //开始呼叫 
   doCall(new JoinChannelCallBack() {
            @Override
            public void onJoinChannel(ChannelFullInfo channelFullInfo) {
                LogUtil.i(TAG, "rtcCall onJoinChannel");
                callback.onSuccess(channelFullInfo);
            }

            @Override
            public void onJoinFail(String msg, int code) {
                LogUtil.e(TAG, "rtcCall,onJoinFail msg:" + msg + ",code:" + code);
                callback.onError(code, msg);
            }
        });

步骤6 实现计费逻辑

    // 可以在通话接通成功之后,每1分钟上报一次信息给您的APP应用服务器,做计费处理
        Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                // 将本次通话信息上报给您的APP服务器
            }
        },1000,60*1000,TimeUnit.MILLISECONDS);

步骤7 实现挂断功能

示例代码

doHangup(new RequestCallbackWrapper<Void>() {
    @Override
    public void onResult(int code, Void result, Throwable exception) {
        LogUtil.i(TAG, "rtcHangup,code:" + code + ",exception:" + exception);
        callback.onSuccess(code);
    }
});

步骤8 实现接听功能

示例代码

doAccept(new JoinChannelCallBack() {
    @Override
    public void onJoinChannel(ChannelFullInfo channelFullInfo) {
        LogUtil.i(TAG, "rtcAccept onJoinChannel");
    }

    @Override
    public void onJoinFail(String msg, int code) {
        LogUtil.e(TAG, "rtcAccept,onJoinFail msg:" + msg + ",code:" + code);
    }
});

步骤9 添加呼叫监听

示例代码

 private final NERtcCallDelegate neRtcCallDelegate = new NERtcCallDelegate() {

        @Override
        public void onFirstVideoFrameDecoded(@Nullable String userId, int width, int height) {
            super.onFirstVideoFrameDecoded(userId, width, height);
            // 视频首帧回调
        }

        @Override
        public void onVideoMuted(String userId, boolean isMuted) {
            super.onVideoMuted(userId, isMuted);
            // 对端视频mute的回调
        }

        @Override
        public void onUserEnter(@Nullable String userId) {
            super.onUserEnter(userId);
            LogUtil.i(TAG, "onUserEnter,userId:" + userId);
            // 用户进入通话回调
        }

        @Override
        public void onCallEnd(@Nullable String userId) {
            super.onCallEnd(userId);
            LogUtil.i(TAG, "onCallEnd,userId:" + userId);
            // 通话结束回调
        }

        @Override
        public void onRejectByUserId(@Nullable String userId) {
            super.onRejectByUserId(userId);
            LogUtil.i(TAG, "onRejectByUserId,userId:" + userId);
            // 拒绝通话的回调
        }

        @Override
        public void onUserBusy(@Nullable String userId) {
            super.onUserBusy(userId);
            LogUtil.i(TAG, "onUserBusy,userId:" + userId);
            // 对方占线的回调
        }

        @Override
        public void onCancelByUserId(@Nullable String userId) {
            super.onCancelByUserId(userId);
            LogUtil.i(TAG, "onCancelByUserId,userId:" + userId);
            // 对方取消呼叫的回调
        }


        @Override
        public void timeOut() {
            LogUtil.i(TAG, "timeOut");
            // 呼叫超时的回调
        }
    };
    NERTCVideoCall.sharedInstance().addDelegate(neRtcCallDelegate);

如果您需要移除呼叫监听,请参考如下示例代码:

NERTCVideoCall.sharedInstance().removeDelegate(neRtcCallDelegate);

步骤10 实现小窗功能

示例代码

java  // 重写CommonCallActivity的provideUIConfig方法
  @Nullable
  @Override
  protected P2PUIConfig provideUIConfig(@Nullable CallParam callParam) {
    ALog.d(TAG, new ParameterMap("provideUIConfig").append("param", callParam).toValue());
    return new P2PUIConfig.Builder()
        .foregroundNotificationConfig(new CallKitNotificationConfig(R.mipmap.ic_launcher))
        // 设置小窗功能
        .enableFloatingWindow(true)
        // 设置通话页面切到Home页时自动小窗功能
        .enableAutoFloatingWindowWhenHome(true)
        .enableVirtualBlur(true)
        .build();
  }
  // 切换到小窗模式
  doShowFloatingWindow();


小窗

进阶功能

此文档是否对你有帮助?
有帮助
去反馈
  • 前提条件
  • 开发环境
  • 示例项目源码
  • 注意事项
  • 技术原理
  • 步骤1 集成呼叫组件 SDK
  • 步骤2 初始化 IM SDK
  • 步骤3 登录 IM
  • 步骤4 初始化呼叫组件
  • 步骤5 实现呼叫功能
  • 步骤6 实现计费逻辑
  • 步骤7 实现挂断功能
  • 步骤8 实现接听功能
  • 步骤9 添加呼叫监听
  • 步骤10 实现小窗功能
  • 进阶功能