跳到主要内容

一对一通话

本页介绍了一对一呼叫的主要功能,包括如何从您的应用程序拨打、接听、处理和结束呼叫。


关键类介绍

  • RCCallPlusClientRCCallPlusClient 单例对象是 CallPlus for Android SDK 的核心类,用于管理客户端呼叫行为,例如发起、接听、挂断通话,操作音视频设备,管理通话记录等。
  • RCCallPlusSessionRCCallPlusSession 对象代表一则通话的所有信息,提供 getCallIdgetCallTypegetMediaTypegetRemoteUserList 等获取通话属性的方法。
  • RCCallPlusCallRecordRCCallPlusCallRecord 对象代表一则通话记录,其中包含了与 RCCallPlusSession 类似的通话信息,还提供了通话开始与结束的时间戳、通话时长、通话结束原因等信息。
  • IRCCallPlusEventListener:监听器 IRCCallPlusEventListener 提供了来电事件(onReceivedCall)、通话建立成功(onCallConnected)、收到通话记录(onReceivedCallRecord)等事件相关回调。
  • IRCCallPlusResultListener:监听器 IRCCallPlusResultListener 提供了调用 CallPlus API 后的异步执行结果回调。

设置监听器

CallPlus for Android SDK 提供了两个监听器:

请在应用程序初始化或呼叫模块初始化时设置监听器:

  1. 调用 RCCallPlusClient.getInstance().setCallPlusEventListener 方法设置 IRCCallPlusEventListener 监听器。如需反注册,调用 setCallPlusEventListener(null) 方法,设置为 null 即可。

    RCCallPlusClient.getInstance().setCallPlusEventListener(new IRCCallPlusEventListener() {

    /**
    * 本端用户通过该回调接收到通话呼叫
    *
    * @param callSession 通话实体信息
    */
    @Override
    public void onReceivedCall(RCCallPlusSession callSession, String extra) {
    RCCallPlusSession currentCallSession = RCCallPlusClient.getInstance().getCurrentCallSession();
    if (currentCallSession != null && !TextUtils.equals(callSession.getCallId(), currentCallSession.getCallId())) {
    //可以使用该方法判断出,有正在进行中的通话,又有第二通通话呼入的情况
    //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();
    }
    });
    }

    /**
    * 远端用户状态改变监听
    *
    * @param callId 通话Id
    * @param userId 用户Id
    * @param status 该用户当前状态
    * @param reason 该用户当前状态原因
    */
    @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 IDLE:
    stringBuilder.append("空闲");
    break;
    case CALLING:
    stringBuilder.append("呼叫中");
    break;
    case INVITED:
    stringBuilder.append("被邀请中");
    break;
    case RINGING:
    stringBuilder.append("响铃中");
    break;
    case BUSY_LINE_RINGING:
    stringBuilder.append("忙线(响铃中)");
    break;
    case BUSY_LINE_WAIT:
    stringBuilder.append("忙线(通话中)");
    break;
    case CONNECTING:
    stringBuilder.append("已接听,连接中");
    break;
    case ON_CALL:
    stringBuilder.append("通话中");
    break;
    case ENDED:
    stringBuilder.append("通话已结束");
    break;
    case NO_ANSWER:
    stringBuilder.append("未应答");
    break;
    case MISSED:
    stringBuilder.append("未接听");
    break;
    case CANCELED:
    stringBuilder.append("已取消");
    break;
    case DECLINED:
    stringBuilder.append("已拒绝");
    break;
    case ERROR:
    stringBuilder.append("错误");
    break;
    }
    Toast.makeText(CallPlusActivity.this, stringBuilder.toString(), Toast.LENGTH_SHORT).show();
    }
    });
    }
    });
  2. 调用 RCCallPlusClient.getInstance().setCallPlusResultListener 方法设置 IRCCallPlusResultListener 监听器。如需反注册监听器,调用 setCallPlusResultListener(null) 方法,设置为 null 即可。

    RCCallPlusClient.getInstance().setCallPlusResultListener(new IRCCallPlusResultListener() {

    /**
    * 发起通话方法结果回调
    *
    * @param code 方法请求结果
    * @param callId 通话Id
    * @param busyUserList 呼叫成功后,返回被邀请人列表中的忙线用户列表
    */
    @Override
    public void onStartCall(RCCallPlusCode code, String callId, List<RCCallPlusUser> busyUserList) {
    IRCCallPlusResultListener.super.onStartCall(code, callId, busyUserList);
    }

    /**
    * 接听通话结果回调
    *
    * @param code 方法请求结果
    * @param callId 通话Id
    */
    @Override
    public void onAccept(RCCallPlusCode code, String callId) {
    IRCCallPlusResultListener.super.onAccept(code, callId);
    }

    /**
    * 挂断指定通话结果回调
    *
    * @param code 方法请求结果
    * @param callId 通话Id
    */
    @Override
    public void onHangup(RCCallPlusCode code, String callId) {
    IRCCallPlusResultListener.super.onHangup(code, callId);
    }
    });

连接融云服务器

要使用 CallPlus SDK 的通话能力,必须先通过 RongCoreClientconnect 方法连接融云服务器,传入用户身份令牌(Token),向融云服务器验证用户身份。连接成功后,使用 RCCallPlusClient.getInstance().init() 方法初始化和配置 CallPlus SDK。

注意

必须在连接成功之后调用 RCCallPlusClient.getInstance().init 方法初始化 CallPlus SDK。反初始化使用 unInit() 方法。

String 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) {

}
});

处理本地与远端视频视图

在主叫方发起通话前,被叫方接听通话时需要使用 setVideoView 方法设置视频视图:

删除已设置的用户视频视图请调用 removeVideoView 方法。若远端用户没有设置视频渲染视图,则不会产生该用户的视频流的下行流量。

本地视频视图

您可以在应用程序的实现一个 setLocalVideoView 方法,在该方法调用 CallPlus SDK 的 setVideoView 方法,传入 RCCallPlusLocalVideoView,设置本地摄像头渲染视图。

/**
* 设置本地视频渲染视图
*/
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);
}

远端视频视图

您可以在应用程序的实现一个 setRemoteUserVideoView 方法,在该方法中调用 CallPlus SDK 的 setVideoView 方法,传入 RCCallPlusRemoteVideoView 列表,设置远端用户视频流的渲染视图。

注意

如果远程视图需要置于最顶层显示,为解决其被其他布局遮挡的问题,可以使用 setZOrderOnTopsetZOrderMediaOverlay 方法进行设置。

/**
* 设置远端用户视频渲染视图
*/
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);
}

发起呼叫

注意

从 CallPlus 2.0 开始,新增 startCall 重载方法,支持在发起呼叫时通过配置推送属性自定义远程推送标题等属性。支持携带自定义数据。

CallPlus 定义了一对一通话类型(RCCallPlusType.PRIVATE)。您可以使用 startCall 方法来发起一对一通话。

List<String> userIds = new ArrayList<>();
userIds.add(remoteUserId);
RCCallPlusPushConfig pushConfig = RCCallPlusPushConfig.Builder
.create()
.build();
RCCallPlusClient.getInstance().startCall(userIds, RCCallPlusType.PRIVATE, RCCallPlusMediaType.VIDEO, pushConfig, "extra");

该方法调用后,SDK 内部会以异步方式执行。在主叫与被叫端触发以下回调:

本地主叫用户将通过 IRCCallPlusEventListeneronRemoteUserStateChanged 回调获取到被叫用户状态变更,其中 RCCallPlusUserSessionStatus 枚举为 CONNECTING 时表示用户已经调用 accept 方法接听,通话正在连接中。

通话建立成功后,主叫端已经设置的 RCCallPlusRemoteVideoView 中会自动渲染远端被叫用户的视图。

注意

当发生网络断开或 IM 连接断开等情况导致请求失败时,startCall 方法会根据 SDK 内部的策略进行重试。如果重试一直失败,最长等待时间约为 47 秒,然后返回失败监听。


接听通话

重要

非兼容性变更:从 CallPlus 2.0 开始,onReceivedCall 方法发生非兼容变更,新的方法会携带 extra 字段。请您在升级时注意修改相关代码。

被叫方的客户端应用程序中必须先注册 IRCCallPlusEventListener 监听器,才能通过 onReceivedCall 接收来电通知。onReceivedCall 方法中会返回 RCCallPlusSession 对象,通过 getCallId() 可获取 Call ID,使用 getCallType() 可获取通话类型。

调用以下方法选择是否接听来电:

  • 接听来电,使用 RCCallPlusClient.getInstance().accept(callId) 方法。如果呼叫被接受,CallPlus SDK 将自动建立媒体会话。
  • 挂断来电,使用 RCCallPlusClient.getInstance().hangup(callId) 方法。

注意

当发生网络断开或 IM 连接断开等情况导致请求失败时,accept 方法会根据 SDK 内部的策略进行重试。如果重试一直失败,最长等待时间约为 47 秒,然后返回失败监听。

您可以在收到来电事件后打开摄像头采集,并完成本地视图设置。

RCCallPlusClient.getInstance().setCallPlusEventListener(new IRCCallPlusEventListener() {

/**
* 本端用户通过该回调接收到通话呼叫
*
* @param callSession 通话实体信息
*/
@Override
public void onReceivedCall(RCCallPlusSession callSession, String extra) {
RCCallPlusSession currentCallSession = RCCallPlusClient.getInstance().getCurrentCallSession();
if (currentCallSession != null && !TextUtils.equals(callSession.getCallId(), currentCallSession.getCallId())) {
//可以使用该方法判断出,有正在进行中的通话,又有第二通通话呼入的情况
//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) {
}

@Override
public void onRemoteUserStateChanged(String callId, String userId, RCCallPlusUserSessionStatus status, RCCallPlusReason reason) {

}
});

调用 accept 方法接听前,需要提前设置远端视频视图:

private void acceptCall(RCCallPlusSession callSession) {
setRemoteUserVideoView(callSession.getRemoteUserList());

/**
* 开始接听通话
* 该方法内部为异步执行,结果回调是注册的{@link RCCallPlusClient#setCallPlusResultListener(IRCCallPlusResultListener)} 监听的 {@link IRCCallPlusResultListener#onAccept(RCCallPlusCode, String)}方法
*/
RCCallPlusClient.getInstance().accept(callSession.getCallId());
}

处理当前通话

开关麦克风

在通话过程中,您可以使用 enableMicrophone 方法来开关麦克风。可通过 IRCCallPlusEventListener 获取麦克风状态改变、音量变化等通知。

//开启本端麦克风  请提前完成麦克风权限的动态申请
RCCallPlusClient.getInstance().enableMicrophone(true);

当麦克风状态改变时,另一方将通过 IRCCallPlusEventListeneronRemoteMicrophoneStateChanged 回调方法接收事件回调。

RCCallPlusClient.getInstance().setCallPlusEventListener(new IRCCallPlusEventListener() {
@Override
public void onReceivedCall(RCCallPlusSession callSession, String extra) {

}

/**
* 远端用户麦克风状态改变监听
*
* @param callId 通话Id
* @param userId 用户Id
* @param disabled 麦克风是否可用,true:麦克风为关闭状态。false:麦克风为开启状态。
*/
@Override
public void onRemoteMicrophoneStateChanged(String callId, String userId, boolean disabled) {
IRCCallPlusEventListener.super.onRemoteMicrophoneStateChanged(callId, userId, disabled);
}

/**
* 用户音量改变回调,每秒回调一次
*
* @param hashMap key:用户Id value:音量值大小(取值:0-9)
*/
@Override
public void onUserAudioLevelChanged(HashMap<String, Integer> hashMap) {
IRCCallPlusEventListener.super.onUserAudioLevelChanged(hashMap);
}

});

开关摄像头

在通话过程中,您可以通过使用 startCamerastopCamera 方法来开启或关闭摄像头。可通过 IRCCallPlusEventListener 获取摄像头状态改变的通知。

//开启摄像头数据采集  请提前完成摄像头权限的动态申请
RCCallPlusClient.getInstance().startCamera();
//关闭摄像头数据采集
RCCallPlusClient.getInstance().stopCamera();

另一方将通过 IRCCallPlusEventListeneronRemoteCameraStateChanged 回调方法接收事件回调。

RCCallPlusClient.getInstance().setCallPlusEventListener(new IRCCallPlusEventListener() {
@Override
public void onReceivedCall(RCCallPlusSession callSession, String extra) {

}

/**
* 远端用户摄像头状态改变监听
*
* @param callId 通话Id
* @param userId 用户Id
* @param disabled 摄像头是否可用,true:摄像头为关闭状态。false:摄像头为开启状态。
*/
@Override
public void onRemoteCameraStateChanged(String callId, String userId, boolean disabled) {
IRCCallPlusEventListener.super.onRemoteCameraStateChanged(callId, userId, disabled);
}

});

切换前后摄像头

成功打开摄像头后,您可以使用 switchCamera 方法切换前后摄像头。该方法是异步调用的,您可以通过 IRCCallPlusResultListeneronSwitchCamera 回调来获取调用结果。

RCCallPlusClient.getInstance().setCallPlusResultListener(new IRCCallPlusResultListener() {

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

});
RCCallPlusClient.getInstance().switchCamera();

配置视频设置

在视频通话开始前或通话过程中,您可以使用 setVideoConfig 方法来调整摄像头采集的配置信息,调整本端发送视频流的分辨率、码率、帧率:

RCCallPlusVideoConfig videoConfig = RCCallPlusVideoConfig.Builder
.create()
//设置视频发送的分辨率
.setVideoResolution(RCCallPlusVideoResolution.RESOLUTION_480_640)
//设置视频发送的帧率
.setVideoFps(RCCallPlusVideoFps.Fps_15)
//设置视频发送的最小码率
.setMinRate(200)
//设置视频发送的最大码率
.setMaxRate(900)
.build();
RCCallPlusClient.getInstance().setVideoConfig(videoConfig);

切换媒体类型

仅在一对一通话过程中,支持切换通话媒体类型。

  • 当从视频通话切换为音频通话后,SDK 内部会停止发送本端的视频流,并停止接收远端用户的视频流。这样做只会消耗音频流量,不再涉及视频传输。
  • 当从音频通话切换为视频通话时,只有在成功调用 startCamera 开启摄像头后,才会开始产生上行的视频流量。同时,只有在调用 RCCallPlusClient.getInstance().setVideoView(List<RCCallPlusRemoteVideoView> remoteVideoViewList); 方法渲染出远端用户的视频视图后,才会产生下行的视频流量消耗。

发起请求

在一对一通话过程中,主叫方和被叫方用户可以随时切换通话的媒体类型。使用 requestChangeMediaType 方法来发起媒体切换请求。SDK 内部以异步方式执行该方法,调用该方法后,触发以下回调:

注意

当发生网络断开或 IM 连接断开等情况导致请求失败时,该方法会根据 SDK 内部的策略进行重试。如果重试一直失败,最长等待时间约为 47 秒,然后返回失败监听。

//先注册结果回调监听
RCCallPlusClient.getInstance().setCallPlusResultListener(new IRCCallPlusResultListener() {

/**
* 通话中请求切换媒体类型方法结果回调
*
* @param code 方法请求结果
* @param callId 通话Id
* @param transactionId 事务Id
* @param mediaType 媒体类型
*/
@Override
public void onRequestChangeMediaType(RCCallPlusCode code, String callId, String transactionId, RCCallPlusMediaType mediaType) {
IRCCallPlusResultListener.super.onRequestChangeMediaType(code, callId, transactionId, mediaType);
}
});

//请求切换为视频通话
RCCallPlusClient.getInstance().requestChangeMediaType(RCCallPlusMediaType.VIDEO);

响应请求

如果本地用户在通话过程中收到媒体切换请求,使用 replyChangeMediaType 方法来同意或拒绝该请求。SDK 内部以异步方式执行该方法,调用该方法后,触发以下回调:

  • 本地用户通过 IRCCallPlusResultListeneronReplyChangeMediaType 回调来获取异步调用结果。
  • 如果本地用户同意、拒绝请求,对端用户会会收到 IRCCallPlusEventListeneronReceivedChangeMediaTypeResult 回调。应用程序可以通过该回调中的 RCCallPlusMediaTypeChangeResult 参数来查看媒体切换的结果。

注意

当发生网络断开或 IM 连接断开等情况导致请求失败时,该方法会根据 SDK 内部的策略进行重试。如果重试一直失败,最长等待时间约为 47 秒,然后返回失败监听。

RCCallPlusClient.getInstance().setCallPlusResultListener(new IRCCallPlusResultListener() {

/**
* 响应通话中的切换媒体类型方法结果回调
*
* @param code 方法请求结果
* @param callId 通话Id
* @param transactionId 事务Id
*/
@Override
public void onReplyChangeMediaType(RCCallPlusCode code, String callId, String transactionId, boolean isAgreed) {
IRCCallPlusResultListener.super.onReplyChangeMediaType(code, callId, transactionId, isAgreed);
}
});
RCCallPlusClient.getInstance().setCallPlusEventListener(new IRCCallPlusEventListener() {
@Override
public void onReceivedCall(RCCallPlusSession callSession, String extra) {
}

/**
* 远端用户调用请求切换媒体 {@link RCCallPlusClient#requestChangeMediaType(RCCallPlusMediaType)} 成功后收到
*
* @param transactionId 事务id
* @param userId 发起人id
* @param mediaType 媒体类型
*/
@Override
public void onReceivedChangeMediaTypeRequest(String transactionId, String userId, RCCallPlusMediaType mediaType) {
IRCCallPlusEventListener.super.onReceivedChangeMediaTypeRequest(transactionId, userId, mediaType);
runOnUiThread(new Runnable() {
@Override
public void run() {
StringBuilder stringBuilder = new StringBuilder("远端用户 ");
stringBuilder.append(userId).append(" 请求切换媒体类型为 ").append(mediaType.name());
stringBuilder.append(",").append("是否同意?");
//todo showDialog 方法实现在快速集成文档中有示例代码
showDialog(CallPlusActivity.this, stringBuilder.toString(), "同意", new OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
//响应通话中的切换媒体类型请求
RCCallPlusClient.getInstance().replyChangeMediaType(transactionId, true);
if (mediaType == RCCallPlusMediaType.AUDIO) {
RCCallPlusClient.getInstance().stopCamera();
} else {
RCCallPlusClient.getInstance().startCamera();
}
}
}, "拒绝", new OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
//响应通话中的切换媒体类型请求
RCCallPlusClient.getInstance().replyChangeMediaType(transactionId, false);
}
});
}
});
}

@Override
public void onReceivedChangeMediaTypeResult(String transactionId, String userId, RCCallPlusMediaType mediaType, RCCallPlusMediaTypeChangeResult result) {
IRCCallPlusEventListener.super.onReceivedChangeMediaTypeResult(transactionId, userId, mediaType, result);
}
});

取消请求

本地用户请求成功后,在远端用户响应之前可以取消请求。应用程序需要从 IRCCallPlusResultListeneronRequestChangeMediaType 回调中获取请求的 transactionId,在调用 cancelChangeMediaType 方法时传入以取消指定请求。

远端用户将通过 IRCCallPlusEventListeneronCancelChangeMediaType 回调方法接收通知,并可以通过该回调中的 RCCallPlusMediaTypeChangeResult 参数来查看媒体切换的结果。

注意

当发生网络断开或 IM 连接断开等情况导致请求失败时,该方法会根据 SDK 内部的策略进行重试。如果重试一直失败,最长等待时间约为 47 秒,然后返回失败监听。

RCCallPlusClient.getInstance().setCallPlusResultListener(new IRCCallPlusResultListener() {

@Override
public void onRequestChangeMediaType(RCCallPlusCode code, String callId, String transactionId, RCCallPlusMediaType mediaType) {
IRCCallPlusResultListener.super.onRequestChangeMediaType(code, callId, transactionId, mediaType);
}

/**
* 取消已经发起的切换媒体类型方法结果回调
*
* @param code 方法请求结果
* @param callId 通话Id
* @param transactionId 事务Id
*/
@Override
public void onCancelChangeMediaType(RCCallPlusCode code, String callId, String transactionId) {
IRCCallPlusResultListener.super.onCancelChangeMediaType(code, callId, transactionId);
}
});
//入参 transactionId 为 onRequestChangeMediaType 回调中 transactionId 参数
RCCallPlusClient.getInstance().cancelChangeMediaType(transactionId);

通话中接听来电

您可以在通话过程中接听新的来电。由于一次只能进行一个通话,您可以通过以下代码逻辑判断收到的通话是否为第二次来电。如果要接听第二次来电,只需调用 RCCallPlusClient.getInstance().accept(String callId); 方法即可;或者,您也可以调用 RCCallPlusClient.getInstance().hangup(String callId); 方法来挂断该通话。

注意

CallPlus SDK 不支持在通话过程中保持和恢复通话。

RCCallPlusClient.getInstance().setCallPlusEventListener(new IRCCallPlusEventListener() {

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

通话中精准计时

CallPlus 服务端可统一下发通话开始时间,确保各端计时准确、一致,具体支持以下两种方案:

CallPlus 会在通话连接成功后将两个时间戳都回调给 SDK 端(可能会因校准行为导致多次回调),应用程序可以通过计算回调的时间戳与本地时间之间的差值得到精确的通话时长。

注意

为减小误差,CallPlus SDK 会根据用户的网络情况以及本地时间的差异,对通话开始的时间戳进行校准,因此可能存在同一个通话多次回调该方法的情况。建议您在使用时注意及时更新。


结束通话

注意

从 CallPlus 2.0 开始,新增 hangup 重载方法,支持在挂断时通过配置推送属性自定义远程推送通知标题等属性。

在通话过程中,您可以使用 hangup 方法来挂断通话。

RCCallPlusClient.getInstance().hangup();

该方法在 SDK 内部是以异步方式调用的,执行后会触发以下回调:

//注册挂断通话API结果回调监听
RCCallPlusClient.getInstance().setCallPlusResultListener(new IRCCallPlusResultListener() {

/**
* 挂断指定通话结果回调
*
* @param code 方法请求结果
* @param callId 通话Id
*/
@Override
public void onHangup(RCCallPlusCode code, String callId) {
IRCCallPlusResultListener.super.onHangup(code, callId);
}
});
RCCallPlusClient.getInstance().setCallPlusEventListener(new IRCCallPlusEventListener() {
@Override
public void onReceivedCall(RCCallPlusSession callSession, String extra) {

}

/**
* 己方参与过的通话结束时,收到该通话结束通知。如果本端正在进行中的通话结束后,{@link RCCallPlusClient#getCurrentCallSession()} 会为空
*
*
* @param session 结束通话的实体对象
* @param reason 通话结束原因
*/
@Override
public void onCallEnded(RCCallPlusSession session, RCCallPlusReason reason) {
IRCCallPlusEventListener.super.onCallEnded(session, reason);
}

/**
* 己方参与过的通话结束时,收到该通话的通话记录
*
* @param record 通话记录对象
*/
@Override
public void onReceivedCallRecord(RCCallPlusCallRecord record) {
IRCCallPlusEventListener.super.onReceivedCallRecord(record);
}
});

管理通话记录

CallPlus 提供服务端通话记录管理能力,包括查询用户通话记录列表、删除通话记录等功能。一则通话记录对应一个 RCCallPlusCallRecord 对象,其中包含通话 ID、参与用户 ID、通话开始与结束的时间戳、通话时长等信息。

注意

CallPlus SDK 不提供本地通话记录。

通话结束后获取通话记录

通话结束(或拒接来电)后可以通过 IRCCallPlusEventListener 的回调方法 onReceivedCallRecord(RCCallPlusCallRecord record) {} 获取到服务器下发的通话记录。

检索服务端通话记录

应用程序可以使用 getCallRecordsFromServer 方法向服务端发起请求,获取当前用户的通话历史记录。

从 2.1.0 版本开始新增重载方法,支持倒序查询通话记录。例如,分页获取最近的通话记录。首次可以将 syncTime 设置为 -1。如果需要继续查询,可以从返回的 RCCallPlusRecordInfo 对象的 getSyncTime() 方法获取下次查询的 syncTime 值。

long syncTime = -1;// 同步时间戳(单位:毫秒) 。首次可传 -1,会根据 `order` 指定的查询顺序,返回最近的 n 条或最远的 n 条数据。后续增量查询传 {@link RCCallPlusRecordInfo#getSyncTime() }
int count = 30;//查询的条数 (最大 100)
RCCallPlusOrder order = RCCallPlusOrder.DESCENDING; // 倒序查询
RCCallPlusClient.getInstance().getCallRecordsFromServer(syncTime, count, order);

如果采用正序查询(RCCallPlusOrder.ASCENDING),可以从最早的记录或指定时间戳开始查询。

应用程序可以从 IRCCallPlusResultListener 监听器的方法 onGetCallRecordsFromServer 获取服务端返回的通话对象列表。以下回调方法为 2.1.0 版本新增,仅在调用支持 order 参数的 getCallRecordsFromServer 方法后触发。

RCCallPlusClient.getInstance().setCallPlusResultListener(new IRCCallPlusResultListener() {

/**
* 获取当前用户通话记录方法结果回调
*
* @param code 方法请求结果
* @param record 获取到的通话记录
* @param order 查询时所使用的排序规则
*/
@Override
public void onGetCallRecordsFromServer(RCCallPlusCode code, RCCallPlusRecordInfo record, RCCallPlusOrder order) {
IRCCallPlusResultListener.super.onGetCallRecordsFromServer(code, record, order);
}
});

删除通话记录

CallPlus SDK 不提供本地通话记录。如果使用以下方法删除通话记录,则从服务端删除属于当前用户的通话记录。批量删除通话记录只需要传入通话 ID,不区分通话类型。

RCCallPlusClient.getInstance().setCallPlusResultListener(new IRCCallPlusResultListener() {

/**
* 批量删除通话记录方法结果回调
*
* @param code 方法请求结果
* @param callIds 需要删除通话记录的通话Id列表
*/
@Override
public void onDeleteCallRecordsFromServer(RCCallPlusCode code, List<String> callIds) {
IRCCallPlusResultListener.super.onDeleteCallRecordsFromServer(code, callIds);
}

/**
* 清除当前用户通话记录方法结果回调
*
* @param code 方法请求结果
*/
@Override
public void onDeleteAllCallRecordsFromServer(RCCallPlusCode code) {
IRCCallPlusResultListener.super.onDeleteAllCallRecordsFromServer(code);
}

});
List<String> callIds = new ArrayList<>();
/**
* 批量删除通话记录
* @param callIds 需要删除通话记录的通话Id列表
*/
RCCallPlusClient.getInstance().deleteCallRecordsFromServer(callIds);

/**
* 清除当前用户通话记录
*/
RCCallPlusClient.getInstance().deleteAllCallRecordsFromServer();

获取视频数据

在通话开始前或通话过程中,您可以使用 setVideoFrameListener 方法注册摄像头流采集回调。

RCCallPlusClient.getInstance().setVideoFrameListener(new IRCCallPlusVideoFrameListener() {

/**
* 视频数据回调接口,用于开发者自定义美颜等的视频处理 回调线程名:Camera SurfaceTextureHelper ;
*
* @param videoFrame
* @return 当使用 {@link RCRTCVideoFrame#getTextureId()} 或{@link RCRTCVideoFrame#getData()}
* 对视频数据处理完之后,需要再设置给RTCVideoFrame 对象并返回, 设置方式:{@link RCRTCVideoFrame#setTextureId(int)} 或
* {@link RCRTCVideoFrame#setData(byte[])}
*/
@Override
public RCRTCVideoFrame onCaptureFrame(RCRTCVideoFrame videoFrame) {
return videoFrame;
}
});

获取音频数据

在通话过程中,您可以使用 setAudioFrameListener 方法注册麦克风音频 PCM 数据采集回调。

RCCallPlusClient.getInstance().setAudioFrameListener(new IRCCallPlusAudioFrameListener() {

/**
* 音频PCM数据采集回调,10ms回调一次,如果开发者需要做录音或其他音频数据操作,请使用{@link RCCallPlusAudioFrame#getBytes()}的拷贝,如
* :audioFrame.getBytes().clone(); 回调线程名:AudioRecordJavaThread ;<b/>
*
* @param audioFrame 音频PCM数据对象<b/>
* @return 对于本地采集音频数据如果开发者对PCM数据做了特殊处理, 需要返回处理后的数据,如果没有处理 则 return rtcAudioFrame.getBytes();<b/>
*/
@Override
public byte[] onAudioFrame(RCCallPlusAudioFrame audioFrame) {
return audioFrame.getBytes();
}
});

管理通话质量

在通话过程中,可使用 setCallPlusStatusReportListener 注册 IRCCallPlusStatusReportListener,接收或发送丢包率等相关的信息。

RCCallPlusClient.getInstance().setCallPlusStatusReportListener(new IRCCallPlusStatusReportListener() {

/**
* 接收丢包率信息回调,每秒回调一次
*
* @param data key:用户Id, value:接收丢包统计数据
*/
@Override
public void onReceivePacketLoss(HashMap<String, RCCallPlusPacketLossStats> data) {

}

/**
* 发送丢包率信息回调,每秒回调一次
*
* @param lossRate 丢包率,0-100
* @param delay 发送端的网络延迟,单位毫秒
*/
@Override
public void onSendPacketLoss(long lossRate, int delay) {

}
});

如需反注册监听器,可调用 setCallPlusStatusReportListener(null),传入 null 即可移除监听器。


音视频首帧回调

可通过 IRCCallPlusEventListener 获取音频、视频首帧通知。

RCCallPlusClient.getInstance().setCallPlusEventListener(new IRCCallPlusEventListener() {
@Override
public void onReceivedCall(RCCallPlusSession callSession, String extra) {

}

/**
* 接收到远端视频流首帧回调
*
* @param userId 用户Id
* @param width 视频宽
* @param height 视频高
*/
@Override
public void onFirstRemoteVideoFrame(String userId, int width, int height) {
IRCCallPlusEventListener.super.onFirstRemoteVideoFrame(userId, width, height);
}

/**
* 接收到远端音频流首帧回调
*
* @param userId 用户Id
*/
@Override
public void onFirstRemoteAudioFrame(String userId) {
IRCCallPlusEventListener.super.onFirstRemoteAudioFrame(userId);
}

});