技术教程 | 如何实现1v1音视频通话(含源码)

今天,给大家讲一下怎么实现1v1音视频通话,以下是教程内容:

开发环境

开发环境要求如下:

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

注意事项

1 对 1 娱乐社交场景方案的呼叫能力基于云信呼叫组件,

技术原理

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

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

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

用户A呼叫组件AG2服务器信令服务器呼叫组件B业务服务器用户B呼叫计费alt[一方挂断电话][出现欠费]请求校验是否可以发起呼叫返回校验结果,分配ChannelName、uid等信息(可选)A发起呼叫邀请呼叫组件发起呼叫邀请A邀请B的信令A邀请BB接听B接听B加入RTCB接听的信令A加入RTCB接听发送用户A 加入房间的抄送发送用户B 加入房间的抄送开始计费通话中客户端向业务服务器发送计费心跳(可选)发送用户离开房间的抄送停止计费发送通话话单出现欠费,销毁房间通话结束通话结束通话结束通话结束用户A呼叫组件AG2服务器信令服务器呼叫组件B业务服务器用户B

步骤1 集成呼叫组件 SDK

1 对 1 娱乐社交场景方案的呼叫能力基于云信呼叫组件来进行实现。

  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

在 Application 的 onCreate 中,调用init方法进行初始化。

示例代码

  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 登录以及自动登录的实现

public class LoginActivity extends Activity {
	public void doLogin() {
		LoginInfo info = new LoginInfo(); 
		RequestCallback callback =
			new RequestCallback() {
			        @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 登录成功后,初始化呼叫组件。

示例代码(✉LTT936可以获完整源码)

    在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 callback) {
                        HttpService.requestRtcToken(uid).subscribe(new ResourceSingleObserver() {
                            @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() {
    @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();


技术教程 | 如何实现1v1音视频通话(含源码)_第1张图片

最终的呈现效果是这样的,大家速速体验!更详细的源码可以戳底部名片呀~~

你可能感兴趣的:(WebRTC,音视频,ai,人工智能,实时音视频,语音识别,实时互动,信息与通信)