今天,给大家讲一下怎么实现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 对 1 娱乐社交场景方案的呼叫能力基于云信呼叫组件来进行实现。
在工程根部目录的 build.gradle
文件中添加如下代码。
allprojects {
repositories {
//...
mavenCentral()
//...
}
}
在主工程 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'
}
在 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;
}
}
调用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);
}
}
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);
}
示例代码
// 唤起呼叫页面
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);
}
});
// 可以在通话接通成功之后,每1分钟上报一次信息给您的APP应用服务器,做计费处理
Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
// 将本次通话信息上报给您的APP服务器
}
},1000,60*1000,TimeUnit.MILLISECONDS);
示例代码
doHangup(new RequestCallbackWrapper() {
@Override
public void onResult(int code, Void result, Throwable exception) {
LogUtil.i(TAG, "rtcHangup,code:" + code + ",exception:" + exception);
callback.onSuccess(code);
}
});
示例代码
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);
}
});
示例代码
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);
示例代码
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();
最终的呈现效果是这样的,大家速速体验!更详细的源码可以戳底部名片呀~~