跳到主要内容

与 IM 业务集成

您可以将 CallPlus 集成到基于 IMLib SDK 或 IMKit 的应用程序中,为用户提供使用通话和聊天服务的无缝体验。

在会话页面插入通话结束消息

应用程序可以通话结束时返回聊天视图,并在会话页面中展示通话结束的消息。

为便于与 IMLib SDK 和 IMKit 集成,从 2.1.1 版本开始,CallPlus SDK 可在通话结束时返回一个 IM 消息对象(RCMessage),其中包含消息内容对象 RCCallPlusSummaryMessageContent。应用程序可以通过调用 IMLib SDK 或 IMKit 的接口直接将该消息内容对象插入到会话中。如果使用 IMKit,请按照本文档创建一个专用的消息展示模板。

获取通话结束消息

重要

CallPlus SDK 暂仅支持在一对一通话(通话类型为 RCCallPlusType.PRIVATE)结束时返回用于展示通话记录的消息内容。暂不支持支持多人通话。

通话正常、异常结束,或在未接听时挂断,CallPlus SDK 会触发 IRCCallPlusEventListeneronReceivedCallPlusSummaryMessage 回调方法,可在该回调方法获取到通话结束的消息内容对象。

如果被叫时用户不在线,CallPlus SDK 会在下次成功连接后立即获取离线时产生的 RCCallPlusSummaryMessageContent。为了确保 CallPlus SDK 可在连接成功后立即获取到这些消息,请在连接之前调用 setCallPlusEventListener 注册监听器。

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

}

/**
* 己方参与过的通话结束时,通话消息监听回调
*
* <p>目前,本回调仅在单人通话结束时(通话类型为{@link RCCallPlusType#PRIVATE})触发,并返回相应的信息。
*
* <p>在 IM Lib 登录成功后,将获取离线时产生的通话消息({@link
* cn.rongcloud.callplus.api.RCCallPlusSummaryMessageContent })。为了确保在登录成功后立即获取到这些消息,请在IM
* Lib初始化之前注册监听器:{@link RCCallPlusClient#setCallPlusEventListener(IRCCallPlusEventListener)}
*
* @param message {@link cn.rongcloud.callplus.api.RCCallPlusSummaryMessageContent }
*/
@Override
public void onReceivedCallPlusSummaryMessage(Message message) {
IRCCallPlusEventListener.super.onReceivedCallPlusSummaryMessage(message);
insertIntoDatabase(message);
}
});

在 IMKit 会话页面中插入通话结束消息

以下步骤描述了如何在 IMKit 中为 RCCallPlusSummaryMessageContent 的创建消息展示模板,以及如何将消息插入到本地会话页面中。

  1. 创建通话结束消息展示模版类,所有消息展示模板都继承自 BaseMessageItemProvider<CustomMessage>。需要修改消息展示样式,请参考修改消息的展示样式

    import android.content.Context;
    import android.text.Spannable;
    import android.text.SpannableString;
    import android.text.TextUtils;
    import android.view.LayoutInflater;
    import android.view.View;
    import android.view.ViewGroup;
    import android.widget.TextView;
    import android.widget.Toast;
    import cn.rongcloud.callplus.api.RCCallPlusMediaType;
    import cn.rongcloud.callplus.api.RCCallPlusReason;
    import cn.rongcloud.callplus.api.RCCallPlusSummaryMessageContent;
    import io.rong.imkit.conversation.messgelist.provider.BaseMessageItemProvider;
    import io.rong.imkit.model.UiMessage;
    import io.rong.imkit.widget.adapter.IViewProviderListener;
    import io.rong.imkit.widget.adapter.ViewHolder;
    import io.rong.imlib.RongCoreClient;
    import io.rong.imlib.model.MessageContent;
    import java.util.List;

    public class MyCallPlusRecordMessageItemProvider extends BaseMessageItemProvider<RCCallPlusSummaryMessageContent> {

    @Override
    protected ViewHolder onCreateMessageContentViewHolder(ViewGroup parent, int viewType) {
    //todo R.layout.rc_text_message_item 布局文件来自于IM Kit SDK中
    View textView =
    LayoutInflater.from(parent.getContext())
    .inflate(R.layout.rc_text_message_item, parent, false);
    return new ViewHolder(parent.getContext(), textView);

    }

    @Override
    protected void bindMessageContentViewHolder(ViewHolder holder, ViewHolder parentHolder, RCCallPlusSummaryMessageContent rcCallPlusSummaryMessageContent, UiMessage uiMessage, int position, List<UiMessage> list, IViewProviderListener<UiMessage> listener) {
    final TextView view = holder.getView(io.rong.imkit.R.id.rc_text);
    //todo R.drawable.rc_ic_bubble_right 和 R.drawable.rc_ic_bubble_left 文件来自于IM Kit SDK中
    if (TextUtils.equals(rcCallPlusSummaryMessageContent.getCallerUserId(), RongCoreClient.getInstance().getCurrentUserId())) {
    view.setBackgroundResource(R.drawable.rc_ic_bubble_right);
    } else {
    view.setBackgroundResource(R.drawable.rc_ic_bubble_left);
    }

    view.setText(getCallReasonString(rcCallPlusSummaryMessageContent));
    view.setCompoundDrawablePadding(15);
    }

    @Override
    protected boolean onItemClick(ViewHolder holder, RCCallPlusSummaryMessageContent rcCallPlusSummaryMessageContent, UiMessage uiMessage, int position, List<UiMessage> list, IViewProviderListener<UiMessage> listener) {
    Context context = holder.getContext();
    Toast.makeText(
    context,
    "点击了CallPlus自定义通话消息",
    Toast.LENGTH_SHORT)
    .show();
    return false;

    }

    @Override
    protected boolean isMessageViewType(MessageContent messageContent) {
    return messageContent instanceof RCCallPlusSummaryMessageContent;
    }

    @Override
    public Spannable getSummarySpannable(Context context, RCCallPlusSummaryMessageContent rcCallPlusSummaryMessageContent) {
    if (rcCallPlusSummaryMessageContent.getMediaType() == RCCallPlusMediaType.AUDIO) {
    return new SpannableString("语音通话");
    } else {
    return new SpannableString("视频通话");
    }
    }

    //todo 该实例代码转换挂断原因仅供参考,请按照APP产品定义修改该逻辑
    private String getCallReasonString(RCCallPlusSummaryMessageContent messageContent) {
    RCCallPlusReason reason = messageContent.getEndCallReason();
    String str = reason.name();
    switch (reason) {
    case NONE:
    case IDLE:
    case ENDED:
    case MISSED:
    long duration = messageContent.getEndTime() - messageContent.getCallStartTime();
    str = "通话结束,时长"+ (duration/1000) +" 秒";
    break;
    case BUSY_LINE_RINGING:
    case BUSY_LINE_WAIT:
    str = "忙线中";
    break;
    case CONNECTING:
    case ON_CALL:
    case ON_PHONE:
    case ON_PHONE_ENDED:
    case ON_DEVICE_DISABLE:
    case ON_DEVICE_ENABLE:
    case CALLING:
    case INVITED:
    case RINGING:
    //非挂断原因 不需要转换
    break;
    case BOUNDARY_ENDED:
    str = "异常结束";
    break;
    case NO_ANSWER:
    str = "对方未接听";
    break;
    case CANCELED:
    str = "已取消";
    break;
    case DECLINED:
    str = "已拒绝";
    break;
    case OTHER_CLIENT_CALLING:
    str = "己方其他端正在响铃中";
    break;
    case ACCEPT_BY_OTHER_CLIENT:
    str = "己方其他端已接听通话";
    break;
    case JOIN_RTC_ERROR:
    case PUBLISH_ERROR:
    case SUBSCRIBE_ERROR:
    str = "己方网络异常";
    break;
    case KICKED_BY_SERVER:
    str = "己方已被禁止通话";
    break;
    case CONNECTION_ERROR:
    str = "己方连接断开";
    break;
    case LOGOUT:
    str = "己方退出登录";
    break;
    case OTHER_CLIENT_LOGIN:
    str = "被其他端登录IM挤下线";
    break;
    case OTHER_JOINED_RTC:
    str = "其他端已加入新通话";
    break;
    case OTHER_CLIENT_IN_RTC:
    str = "其他端已在其他通话中";
    break;
    case REMOTE_JOIN_RTC_ERROR:
    case REMOTE_PUBLISH_ERROR:
    case REMOTE_SUBSCRIBE_ERROR:
    str = "对方网络异常";
    break;
    case REMOTE_KICKED_BY_SERVER:
    str = "对方已被禁止通话";
    break;
    case REMOTE_CONNECTION_ERROR:
    str = "对方连接断开";
    break;
    case REMOTE_LOGOUT:
    str = "对方退出登录";
    break;
    case REMOTE_OTHER_CLIENT_LOGIN:
    str = "对方被其他端登录挤下线";
    break;
    case REMOTE_OTHER_JOINED_RTC:
    str = "对方其他端已加入新通话";
    break;
    case REMOTE_OTHER_CLIENT_IN_RTC:
    str = "对方其他端已在其他通话中";
    break;
    case HANGUP_BY_OTHER_CLIENT:
    str = "己方其他端已挂断通话";
    break;
    }
    return str;
    }
    }
  2. 在 IMKit 初始化后,注册自定义的消息展示模板。

    RongConfigCenter.conversationConfig().addMessageProvider(new MyCallPlusRecordMessageItemProvider());
  3. IRCCallPlusEventListeneronReceivedCallPlusSummaryMessage 回调方法中获取 Message 对象中包含的 RCCallPlusSummaryMessageContent,在本地会话页面中插入一条通话结束消息。

    @Override
    public void onReceivedCallPlusSummaryMessage(Message message) {
    IRCCallPlusEventListener.super.onReceivedCallPlusSummaryMessage(message);
    insertIntoDatabase(message);
    }

    以下代码示例实现了一个 insertIntoDatabase 方法,其中使用 IMKit 的核心类 IMCenter 的插入消息方法,IMKit 会负责刷新会话页面。

    private void insertIntoDatabase(Message message) {
    RCCallPlusSummaryMessageContent messageContent = (RCCallPlusSummaryMessageContent) message.getContent();
    //获取目标 id。根据不同的 conversationType,可能是对端用户 id、群组 id。
    String targetId = messageContent.getTargetId();
    //通话发起人 ID
    String callerUserId = messageContent.getCallerUserId();
    long sentTime = message.getSentTime();
    //如果通话发起人是本人,则消息的插入方向为发送
    if (TextUtils.equals(callerUserId, RongCoreClient.getInstance().getCurrentUserId())) {
    Message.SentStatus sentStatus = io.rong.imlib.model.Message.SentStatus.SENT;
    /**
    * 向本地会话中插入一条消息,方向为发送。
    *
    * <p>这条消息只是插入本地会话,不会实际发送给服务器和对方。 插入消息需为入库消息,即 {@link MessageTag#ISPERSISTED},否者会回调 {@link
    * IRongCoreEnum.CoreErrorCode#RC_INVALID_PARAMETER_MSG_TAG}
    *
    * @param type 会话类型。
    * @param targetId 会话 id。比如私人会话时,是对方的 id; 群组会话时,是群 id; 讨论组会话时,则为该讨论组的 id。
    * @param sentStatus 发送状态 {@link Message.SentStatus}
    * @param content 消息内容。如{@link TextMessage} {@link ImageMessage} 等。
    * @param sentTime 消息的发送时间 {@link Message#getSentTime()} 。
    * @param resultCallback 获得消息发送实体的回调。
    * @since 5.0.0
    */
    IMCenter.getInstance()
    .insertOutgoingMessage(ConversationType.PRIVATE, targetId, sentStatus, messageContent, sentTime, new RongIMClient.ResultCallback<Message>() {
    @Override
    public void onSuccess(Message message) {
    }

    @Override
    public void onError(RongIMClient.ErrorCode e) {
    }
    });
    } else {
    Message.ReceivedStatus receivedStatus = new ReceivedStatus(1); // 默认插入的通话记录为已读
    /**
    * 向本地会话中插入一条消息,方向为接收。
    *
    * <p>这条消息只是插入本地会话,不会实际发送给服务器和对方。插入消息需为入库消息,即 {@link MessageTag#ISPERSISTED},否者会回调 {@link
    * IRongCoreEnum.CoreErrorCode#RC_INVALID_PARAMETER_MSG_TAG}。
    *
    * @param type 会话类型。
    * @param targetId 会话 id。比如私人会话时,是对方的 id; 群组会话时,是群 id; 讨论组会话时,则为该讨论组的 id。
    * @param senderUserId 发送方 id
    * @param receivedStatus 接收状态 {@link Message.ReceivedStatus}
    * @param content 消息内容。如 {@link TextMessage} {@link ImageMessage}等。
    * @param sentTime 消息的发送时间 {@link Message#getSentTime()} 。
    * @param resultCallback 获得消息发送实体的回调。
    * @since 5.0.0
    */
    IMCenter.getInstance().insertIncomingMessage(ConversationType.PRIVATE, targetId, callerUserId, receivedStatus, messageContent, sentTime, new RongIMClient.ResultCallback<Message>() {
    @Override
    public void onSuccess(Message message) {
    }

    @Override
    public void onError(RongIMClient.ErrorCode e) {
    }
    });
    }
    }

在 IMLib 本地会话中插入通话结束消息

如果应用程序仅集成 IMLib SDK,您需要将 RCCallPlusSummaryMessageContent 插入到本地会话中,并自行刷新 UI。

  1. IRCCallPlusEventListeneronReceivedCallPlusSummaryMessage 回调方法中获取 Message 对象中包含的 RCCallPlusSummaryMessageContent,在本地会话页面中插入一条通话结束消息。

    @Override
    public void onReceivedCallPlusSummaryMessage(Message message) {
    IRCCallPlusEventListener.super.onReceivedCallPlusSummaryMessage(message);
    insertIntoDatabase(message);
    }
  2. 以下代码示例实现了一个 insertIntoDatabase 方法,其中使用 IMLib 的核心类 RongCoreClient 的插入消息方法。

    private void insertIntoDatabase(Message message) {
    RCCallPlusSummaryMessageContent messageContent = (RCCallPlusSummaryMessageContent) message.getContent();
    //获取目标 id。根据不同的 conversationType,可能是对端用户 id、群组 id。
    String targetId = messageContent.getTargetId();
    //通话发起人 ID
    String callerUserId = messageContent.getCallerUserId();
    long sentTime = message.getSentTime();
    //如果通话发起人是本人,则消息的插入方向为发送
    if (TextUtils.equals(callerUserId, RongCoreClient.getInstance().getCurrentUserId())) {
    Message.SentStatus sentStatus = io.rong.imlib.model.Message.SentStatus.SENT;
    /**
    * 向本地会话中插入一条消息,方向为发送。
    *
    * <p>这条消息只是插入本地会话,不会实际发送给服务器和对方。 插入消息需为入库消息,即 {@link MessageTag#ISPERSISTED},否者会回调 {@link
    * IRongCoreEnum.CoreErrorCode#RC_INVALID_PARAMETER_MSG_TAG}
    *
    * @param type 会话类型。
    * @param targetId 会话 id。比如私人会话时,是对方的 id; 群组会话时,是群 id; 讨论组会话时,则为该讨论组的 id。
    * @param sentStatus 发送状态 {@link Message.SentStatus}
    * @param content 消息内容。如{@link TextMessage} {@link ImageMessage} 等。
    * @param sentTime 消息的发送时间 {@link Message#getSentTime()} 。
    * @param resultCallback 获得消息发送实体的回调。
    * @since 5.0.0
    */
    RongCoreClient.getInstance()
    .insertOutgoingMessage(ConversationType.PRIVATE, targetId, sentStatus, messageContent, sentTime, new ResultCallback<Message>() {
    @Override
    public void onSuccess(Message message) {
    }

    @Override
    public void onError(CoreErrorCode e) {
    }
    });
    } else {
    Message.ReceivedStatus receivedStatus = new ReceivedStatus(1); // 默认插入的通话记录为已读
    /**
    * 向本地会话中插入一条消息,方向为接收。
    *
    * <p>这条消息只是插入本地会话,不会实际发送给服务器和对方。插入消息需为入库消息,即 {@link MessageTag#ISPERSISTED},否者会回调 {@link
    * IRongCoreEnum.CoreErrorCode#RC_INVALID_PARAMETER_MSG_TAG}。
    *
    * @param type 会话类型。
    * @param targetId 会话 id。比如私人会话时,是对方的 id; 群组会话时,是群 id; 讨论组会话时,则为该讨论组的 id。
    * @param senderUserId 发送方 id
    * @param receivedStatus 接收状态 {@link Message.ReceivedStatus}
    * @param content 消息内容。如 {@link TextMessage} {@link ImageMessage}等。
    * @param sentTime 消息的发送时间 {@link Message#getSentTime()} 。
    * @param resultCallback 获得消息发送实体的回调。
    * @since 5.0.0
    */
    RongCoreClient.getInstance().insertIncomingMessage(ConversationType.PRIVATE, targetId, callerUserId, receivedStatus, messageContent, sentTime, new IRongCoreCallback.ResultCallback<Message>() {
    @Override
    public void onSuccess(Message message) {
    }

    @Override
    public void onError(CoreErrorCode e) {
    }
    });
    }
    }