实现1对1音视频通话
更新时间: 2024/08/14 11:41:36
本文介绍 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)。
-
在工程根部目录的
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' }
步骤2 初始化 IM SDK
在 Application
的 onCreate
中,调用 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();
进阶功能
此文档是否对你有帮助?