多端登录
功能简介
ZIM SDK 支持配置自定义多端登录,即用户同一个账号可在多个平台上同时登录,满足用户的会话、消息、群组、房间等数据互通。
- 如需使用此功能,请开通专业版或旗舰版套餐。
- 本功能仅支持 2.11.0 及以上版本的 SDK 使用。
常见的登录策略
多端登录可支持在 Android、iOS、HarmonyOS、Windows、macOS、Linux、iPadOS、Web、小程序等 9 个平台上配置,其中:
- 支持在 Windows、macOS、Linux、Android、HarmonyOS、iOS、iPadOS 和小程序平台配置多设备登录,一个账号在同一时间只能在最多 10 台设备上进行登录。
- 在 Web 平台配置多实例登录,最多不超过 10 个登录实例。
以下为 ZIM SDK 支持的三种登录策略,即单平台登录、双平台登录以及多平台登录:
| 登录策略 | 具体说明 | 
|---|---|
| 单平台登录(默认) | 仅支持用户同时在 1 个平台上登录帐号。如果在其他平台上登录帐号,原平台上的账号会被退出。 | 
| 双平台登录 | 支持用户在 Windows、Mac、Linux、Android、HarmonyOS、iOS、iPad 和小程序的其中 1 个平台上登录帐号,并同时保持帐号在 Web 平台在线。 | 
| 多平台登录 | 是指支持用户同时在 Windows、Mac、Linux、Android、HarmonyOS、iOS、iPad、Web、小程序多平台登录帐号,支持以下配置: 
 | 
实现流程
前往 ZEGO 控制台开通多端登录服务,详情请参考控制台文档 服务配置 - 即时通讯 - 多端登录配置。开通后,即可在多端调用 login 登录 ZIM SDK,实现流程与单端登录无异。如何登录,详情请参考 实现基本消息收发 的“登录 ZIM“。
当登录设备已达到策略规定的上限时,如果在新设备登录帐号,原设备中最早登录的帐号会被踢下线,并通过 connectionStateChanged 回调得知 event 为 KickedOut 。例如:登录策略规定最多只能在 3 台设备上登录,已在设备 A、B、C(按登录时间排序)登录,如果在设备 D 上登录帐号,则设备 A 上的帐号会被踢下线。
当帐号被踢下线后,建议调用 logout 接口登出 ZIM 服务,用户界面切换为登录界面。
多端登录对其他功能的影响
配置多端登录后,以下功能的代码也需要做相应调整。
用户信息管理
ZIM SDK 为多端登录用户提供了用户信息同步的功能。当用户在一端通过 updateUserName 、 updateUserAvatarUrl 、 updateUserExtendedData 接口更新了自己的信息后,其他在线客户端可通过注册 userInfoUpdated 事件监听用户信息被修改。
onUserInfoUpdated 示例
public class ZIMEventHandler {
    public void onUserInfoUpdated(ZIM zim, ZIMUserFullInfo info) {
        // 可以在 info 中获取:用户 ID,用户名,用户头像,用户额外字段
        String userID = info.baseInfo.userID;
        String userName = info.baseInfo.userName;
        String userAvatarUrl = info.userAvatarUrl;
        String extendedData = info.extendedData;
    }
}public class ZIMEventHandler {
    public void onUserInfoUpdated(ZIM zim, ZIMUserFullInfo info) {
        // 可以在 info 中获取:用户 ID,用户名,用户头像,用户额外字段
        String userID = info.baseInfo.userID;
        String userName = info.baseInfo.userName;
        String userAvatarUrl = info.userAvatarUrl;
        String extendedData = info.extendedData;
    }
}ZIMEventHandler.onUserInfoUpdated = (ZIM zim, ZIMUserFullInfo info){
    // 您可以从info中获取以下信息:用户ID、用户名、用户头像、用户附加字段
    String userID = info.baseInfo.userID;
    String userName = info.baseInfo.userName;
    String userAvatarUrl = info.userAvatarUrl;
    String extendedData = info.extendedData;
};ZIMEventHandler.onUserInfoUpdated = (ZIM zim, ZIMUserFullInfo info){
    // 您可以从info中获取以下信息:用户ID、用户名、用户头像、用户附加字段
    String userID = info.baseInfo.userID;
    String userName = info.baseInfo.userName;
    String userAvatarUrl = info.userAvatarUrl;
    String extendedData = info.extendedData;
};- (void)zim:(ZIM *)zim userInfoUpdated:(ZIMUserFullInfo *)info{
         // 可以从info中获取到用户ID、用户名、用户头像、用户附加字段
         NSString *userID = info.baseInfo.userID;
         NSString *userName = info.baseInfo.userName;
         NSString *userAvatarUrl = info.userAvatarUrl;
         NSString *userExtendedData = info.extendedData;
}- (void)zim:(ZIM *)zim userInfoUpdated:(ZIMUserFullInfo *)info{
         // 可以从info中获取到用户ID、用户名、用户头像、用户附加字段
         NSString *userID = info.baseInfo.userID;
         NSString *userName = info.baseInfo.userName;
         NSString *userAvatarUrl = info.userAvatarUrl;
         NSString *userExtendedData = info.extendedData;
}class zim_event_handler : public zim::ZIMEventHandler {
...
virtual void onUserInfoUpdated(zim::ZIM * zim, const zim::ZIMUserFullInfo & info) override {
    // 可以在info中获取:用户ID、用户名、用户头像、用户附加字段
    auto user_id = info.baseInfo.userID;
    auto user_name = info.baseInfo.userName;
    auto user_avatar_url = info.userAvatarUrl;
    auto user_extended_data = info.extendedData;
}
...
}class zim_event_handler : public zim::ZIMEventHandler {
...
virtual void onUserInfoUpdated(zim::ZIM * zim, const zim::ZIMUserFullInfo & info) override {
    // 可以在info中获取:用户ID、用户名、用户头像、用户附加字段
    auto user_id = info.baseInfo.userID;
    auto user_name = info.baseInfo.userName;
    auto user_avatar_url = info.userAvatarUrl;
    auto user_extended_data = info.extendedData;
}
...
}zim.on('userInfoUpdated', (zim: ZIM, data: ZIMEventOfUserInfoUpdatedResult) => {
});zim.on('userInfoUpdated', (zim: ZIM, data: ZIMEventOfUserInfoUpdatedResult) => {
});zim.onUserInfoUpdated((data) => {
});zim.onUserInfoUpdated((data) => {
});会话管理
删除单个服务端会话
当用户在一端通过 deleteConversation 接口删除服务端会话后(即 isAlsoDeleteServerConversation 为 true),其他在线客户端可通过注册 conversationChanged 事件监听会话被删除。
onConversationChanged 示例
public class ZIMEventHandler{
...
    public void onConversationChanged(ZIM zim, ArrayList<ZIMConversationChangeInfo> conversationChangeInfoList){
        for (ZIMConversationChangeInfo convInfo : conversationChangeInfoList) {
            if (convInfo.event == ZIMConversationEvent.DELETED) {
                // 会话被删除
            }
        }
    }
...
}public class ZIMEventHandler{
...
    public void onConversationChanged(ZIM zim, ArrayList<ZIMConversationChangeInfo> conversationChangeInfoList){
        for (ZIMConversationChangeInfo convInfo : conversationChangeInfoList) {
            if (convInfo.event == ZIMConversationEvent.DELETED) {
                // 会话被删除
            }
        }
    }
...
}ZIMEventHandler.onConversationChanged = (ZIM zim, List<ZIMConversationChangeInfo> conversationChangeInfoList){
    for (ZIMConversationChangeInfo convInfo : conversationChangeInfoList) {
        if (convInfo.event == ZIMConversationEvent.delete) {
       // 该对话已被删除
        }
    }
};ZIMEventHandler.onConversationChanged = (ZIM zim, List<ZIMConversationChangeInfo> conversationChangeInfoList){
    for (ZIMConversationChangeInfo convInfo : conversationChangeInfoList) {
        if (convInfo.event == ZIMConversationEvent.delete) {
       // 该对话已被删除
        }
    }
};- (void)zim:(ZIM *)zim
    conversationChanged:(NSArray<ZIMConversationChangeInfo * > *)conversationChangeInfoList{
        for(ZIMConversationChangeInfo *convInfo in conversationChangeInfoList){
            if(convInfo.event == ZIMConversationEventDeleted){
                // 该对话已被删除
            }
        }
}- (void)zim:(ZIM *)zim
    conversationChanged:(NSArray<ZIMConversationChangeInfo * > *)conversationChangeInfoList{
        for(ZIMConversationChangeInfo *convInfo in conversationChangeInfoList){
            if(convInfo.event == ZIMConversationEventDeleted){
                // 该对话已被删除
            }
        }
}class zim_event_handler : public zim::ZIMEventHandler {
...
    virtual void onConversationChanged(zim::ZIM *zim, 
                                       const std::vector<zim::ZIMConversationChangeInfo> &conversationChangeInfoList) override {
        for (const auto &conv_info : conversationChangeInfoList) {
            if (conv_info.event == zim::ZIMConversationEvent::ZIM_CONVERSATION_EVENT_DELETED) {
                // 该对话已被删除
            }
        }
    }
...
}class zim_event_handler : public zim::ZIMEventHandler {
...
    virtual void onConversationChanged(zim::ZIM *zim, 
                                       const std::vector<zim::ZIMConversationChangeInfo> &conversationChangeInfoList) override {
        for (const auto &conv_info : conversationChangeInfoList) {
            if (conv_info.event == zim::ZIMConversationEvent::ZIM_CONVERSATION_EVENT_DELETED) {
                // 该对话已被删除
            }
        }
    }
...
}zim.on('conversationChanged', (zim: ZIM, data: ZIMEventOfConversationChangedResult) => {
    data.infoList.forEach((info) => {
        if (info.event == 3) {
            //对话已被删除
        }
    });
});zim.on('conversationChanged', (zim: ZIM, data: ZIMEventOfConversationChangedResult) => {
    data.infoList.forEach((info) => {
        if (info.event == 3) {
            //对话已被删除
        }
    });
});zim.onConversationChanged((data) => {
    data.infoList.forEach((info) => {
        if (info.event == 3) {
            //对话已被删除
        }
    });
});zim.onConversationChanged((data) => {
    data.infoList.forEach((info) => {
        if (info.event == 3) {
            //对话已被删除
        }
    });
});删除全部服务端会话
当用户在一端通过 deleteAllConversations 接口删除全部服务端会话后(即 isAlsoDeleteServerConversation 为 true),其他客户端可通过 conversationsAllDeleted 事件监听全部会话被删除。
onConversationsAllDeleted 处理示例
@Override
public void onConversationsAllDeleted(ZIM zim, ZIMConversationsAllDeletedInfo info) {
        // 其他端删除了全部会话
}@Override
public void onConversationsAllDeleted(ZIM zim, ZIMConversationsAllDeletedInfo info) {
        // 其他端删除了全部会话
}ZIMEventHandler.onConversationChanged = (ZIM zim, List<ZIMConversationChangeInfo> conversationChangeInfoList){
    for (ZIMConversationChangeInfo convInfo : conversationChangeInfoList) {
        if (convInfo.event == ZIMConversationEvent.delete) {
        // 该对话已被删除
        }
    }
};ZIMEventHandler.onConversationChanged = (ZIM zim, List<ZIMConversationChangeInfo> conversationChangeInfoList){
    for (ZIMConversationChangeInfo convInfo : conversationChangeInfoList) {
        if (convInfo.event == ZIMConversationEvent.delete) {
        // 该对话已被删除
        }
    }
};- (void)zim:(ZIM *)zim
    conversationsAllDeleted:(ZIMConversationsAllDeletedInfo *)info{
        //另一方删除了所有会话。
}- (void)zim:(ZIM *)zim
    conversationsAllDeleted:(ZIMConversationsAllDeletedInfo *)info{
        //另一方删除了所有会话。
}class zim_event_handler : public zim::ZIMEventHandler {
...
    virtual void onConversationsAllDeleted(zim::ZIM *zim, 
                                       const ZIMConversationsAllDeletedInfo &info) override {
        另一方删除了所有会话。
   }
...
}class zim_event_handler : public zim::ZIMEventHandler {
...
    virtual void onConversationsAllDeleted(zim::ZIM *zim, 
                                       const ZIMConversationsAllDeletedInfo &info) override {
        另一方删除了所有会话。
   }
...
}zim.on('conversationsAllDeleted', (zim: ZIM, data: ZIMEventOfConversationsAllDeletedResult) => {
    console.log('已删除的对话数量', data.count);
});zim.on('conversationsAllDeleted', (zim: ZIM, data: ZIMEventOfConversationsAllDeletedResult) => {
    console.log('已删除的对话数量', data.count);
});zim.onConversationsAllDeleted((data) => {
    console.log('已删除的对话数量', data.count);
});zim.onConversationsAllDeleted((data) => {
    console.log('已删除的对话数量', data.count);
});清楚全部会话未读
当用户在一端通过 clearConversationTotalUnreadMessageCount 清除全部会话未读时,其他客户端可通过 conversationTotalUnreadMessageCountUpdated 事件监听所有会话的未读都被清零的通知。
onConversationTotalUnreadMessageCountUpdated 处理示例
@Override
public void onConversationTotalUnreadMessageCountUpdated(ZIM zim, int totalUnreadMessageCount) {
        // 其他端清除全部会话未读后,本端该通知 totalUnreadMessageCount 的值将为 0
}@Override
public void onConversationTotalUnreadMessageCountUpdated(ZIM zim, int totalUnreadMessageCount) {
        // 其他端清除全部会话未读后,本端该通知 totalUnreadMessageCount 的值将为 0
}ZIMEventHandler.onConversationsAllDeleted = (
      ZIM zim, ZIMConversationsAllDeletedInfo info){
        // 所有对话已被另一个客户删除
}ZIMEventHandler.onConversationsAllDeleted = (
      ZIM zim, ZIMConversationsAllDeletedInfo info){
        // 所有对话已被另一个客户删除
}- (void)zim:(ZIM *)zim
conversationTotalUnreadMessageCountUpdated:(unsigned int)totalUnreadMessageCount{
    //在其他设备上清除所有对话中的未读消息后,当前设备上的totalUnreadMessageCount值将为0。
}- (void)zim:(ZIM *)zim
conversationTotalUnreadMessageCountUpdated:(unsigned int)totalUnreadMessageCount{
    //在其他设备上清除所有对话中的未读消息后,当前设备上的totalUnreadMessageCount值将为0。
}class zim_event_handler : public zim::ZIMEventHandler {
...
    virtual void onConversationTotalUnreadMessageCountUpdated(zim::ZIM *zim, 
                                       unsigned int totalUnreadMessageCount) override {
        //在其他设备上清除所有对话中的未读消息后,当前设备上的totalUnreadMessageCount值将为0。
   }
...
}class zim_event_handler : public zim::ZIMEventHandler {
...
    virtual void onConversationTotalUnreadMessageCountUpdated(zim::ZIM *zim, 
                                       unsigned int totalUnreadMessageCount) override {
        //在其他设备上清除所有对话中的未读消息后,当前设备上的totalUnreadMessageCount值将为0。
   }
...
}zim.on('conversationTotalUnreadMessageCountUpdated', (zim: ZIM, data: ZIMEventOfConversationTotalUnreadMessageCountUpdatedResult) => {
    //在其他设备上清除所有对话中的未读消息后,当前设备上的`data.totalUnreadMessageCount`值将为0。
});zim.on('conversationTotalUnreadMessageCountUpdated', (zim: ZIM, data: ZIMEventOfConversationTotalUnreadMessageCountUpdatedResult) => {
    //在其他设备上清除所有对话中的未读消息后,当前设备上的`data.totalUnreadMessageCount`值将为0。
});zim.onConversationTotalUnreadMessageCountUpdated((data) => {
    //在其他设备上清除所有对话中的未读消息后,当前设备上的`data.totalUnreadMessageCount`值将为0。
});zim.onConversationTotalUnreadMessageCountUpdated((data) => {
    //在其他设备上清除所有对话中的未读消息后,当前设备上的`data.totalUnreadMessageCount`值将为0。
});消息管理
消息同步
当用户登录新设备后,SDK 不会自动将旧设备中的已有消息同步到新设备上,用户需要主动调用 queryHistoryMessage 接口,才能获取存储于 ZIM 服务端的消息,详情请参考 获取历史消息。对于存储于旧设备的本地消息,则无法获取。
删除服务端消息
当用户在一端通过 deleteMessages 、 deleteAllMessage 、 接口删除会话的服务端消息后(即 isAlsoDeleteServerMessage 为 true),其他在线客户端可通过注册 messageDeleted 事件监听消息被删除。
调用接口示例
public class ZIMEventHandler{
...
    public void onMessageDeleted(ZIM zim, ZIMMessageDeletedInfo deletedInfo){
      if (deletedInfo.messageDeleteType == ZIMMessageDeleteType.MESSAGE_LIST_DELETED)
          {
              // 某个会话中的多条消息被删除
              for (ZIMMessage message : messageList) {
                  // 遍历每一条被删除的消息
              }
          } else if (deletedInfo.messageDeleteType ==
                    ZIMMessageDeleteType.CONVERSATION_ALL_MESSAGES_DELETED)
          {
              // 某个会话当前所有消息被删除
          } else if (deletedInfo.messageDeleteType ==
                    ZIMMessageDeleteType.ALL_CONVERSATION_MESSAGES_DELETED)
          {
              // 所有会话的所有消息被删除
          }
      }
...
}public class ZIMEventHandler{
...
    public void onMessageDeleted(ZIM zim, ZIMMessageDeletedInfo deletedInfo){
      if (deletedInfo.messageDeleteType == ZIMMessageDeleteType.MESSAGE_LIST_DELETED)
          {
              // 某个会话中的多条消息被删除
              for (ZIMMessage message : messageList) {
                  // 遍历每一条被删除的消息
              }
          } else if (deletedInfo.messageDeleteType ==
                    ZIMMessageDeleteType.CONVERSATION_ALL_MESSAGES_DELETED)
          {
              // 某个会话当前所有消息被删除
          } else if (deletedInfo.messageDeleteType ==
                    ZIMMessageDeleteType.ALL_CONVERSATION_MESSAGES_DELETED)
          {
              // 所有会话的所有消息被删除
          }
      }
...
}ZIMEventHandler.onMessageDeleted = (ZIM zim, ZIMMessageDeletedInfo deletedInfo){
    if (deletedInfo.messageDeleteType == ZIMMessageDeleteType.messageListDeleted)
        {
            // 会话中的多条消息已被删除
            for (var message in messageList) {
                // 遍历每条已删除的消息
            }
        } else if (deletedInfo.messageDeleteType ==
                   ZIMMessageDeleteType.conversationAllMessagesDeleted)
        {
            // 会话中的所有消息已被删除
        } else if (deletedInfo.messageDeleteType ==
                   ZIMMessageDeleteType.allConversationMessagesDeleted)
        {
            // 所有会话中的消息都已被删除
        }
}ZIMEventHandler.onMessageDeleted = (ZIM zim, ZIMMessageDeletedInfo deletedInfo){
    if (deletedInfo.messageDeleteType == ZIMMessageDeleteType.messageListDeleted)
        {
            // 会话中的多条消息已被删除
            for (var message in messageList) {
                // 遍历每条已删除的消息
            }
        } else if (deletedInfo.messageDeleteType ==
                   ZIMMessageDeleteType.conversationAllMessagesDeleted)
        {
            // 会话中的所有消息已被删除
        } else if (deletedInfo.messageDeleteType ==
                   ZIMMessageDeleteType.allConversationMessagesDeleted)
        {
            // 所有会话中的消息都已被删除
        }
}- (void)zim:(ZIM *)zim messageDeleted:(ZIMMessageDeletedInfo *)deletedInfo{
        if (deletedInfo.messageDeleteType == ZIMMessageDeleteTypeMessageListDeleted)
        {
            // 会话中删除了多条消息
            for (ZIMMessage *message in messageList) {
             // 遍历每条被删除的消息
            }
        } else if (deletedInfo.messageDeleteType ==
                   ZIMMessageDeleteTypeConversationAllMessagesDeleted)
        {
            // 删除了会话中的所有当前消息
        } else if (deletedInfo.messageDeleteType ==
                   ZIMMessageDeleteTypeAllConversationMessagesDeleted)
        {
            // 删除了所有会话的所有消息
        }
}- (void)zim:(ZIM *)zim messageDeleted:(ZIMMessageDeletedInfo *)deletedInfo{
        if (deletedInfo.messageDeleteType == ZIMMessageDeleteTypeMessageListDeleted)
        {
            // 会话中删除了多条消息
            for (ZIMMessage *message in messageList) {
             // 遍历每条被删除的消息
            }
        } else if (deletedInfo.messageDeleteType ==
                   ZIMMessageDeleteTypeConversationAllMessagesDeleted)
        {
            // 删除了会话中的所有当前消息
        } else if (deletedInfo.messageDeleteType ==
                   ZIMMessageDeleteTypeAllConversationMessagesDeleted)
        {
            // 删除了所有会话的所有消息
        }
}class zim_event_handler : public zim::ZIMEventHandler {
...
virtual void onMessageDeleted(zim::ZIM * /*zim*/, const zim::ZIMMessageDeletedInfo &deletedInfo) override {
    if (deletedInfo.messageDeleteType == zim::ZIM_MESSAGE_DELETE_TYPE_MESSAGE_LIST_DELETED)
    {
        // 多条消息被删除
        for (const auto& message : deletedInfo.messageList)
        {
            // 遍历每条被删除的消息
        }
    } else if (deletedInfo.messageDeleteType ==
               zim::ZIM_MESSAGE_DELETE_TYPE_CONVERSATION_ALL_MESSAGES_DELETED)
    {
        // 会话中的所有当前消息被删除
    } else if (deletedInfo.messageDeleteType ==
               zim::ZIM_MESSAGE_DELETE_TYPE_ALL_CONVERSATION_MESSAGES_DELETED)
    {
        // 所有会话的所有消息被删除
    }
...
}class zim_event_handler : public zim::ZIMEventHandler {
...
virtual void onMessageDeleted(zim::ZIM * /*zim*/, const zim::ZIMMessageDeletedInfo &deletedInfo) override {
    if (deletedInfo.messageDeleteType == zim::ZIM_MESSAGE_DELETE_TYPE_MESSAGE_LIST_DELETED)
    {
        // 多条消息被删除
        for (const auto& message : deletedInfo.messageList)
        {
            // 遍历每条被删除的消息
        }
    } else if (deletedInfo.messageDeleteType ==
               zim::ZIM_MESSAGE_DELETE_TYPE_CONVERSATION_ALL_MESSAGES_DELETED)
    {
        // 会话中的所有当前消息被删除
    } else if (deletedInfo.messageDeleteType ==
               zim::ZIM_MESSAGE_DELETE_TYPE_ALL_CONVERSATION_MESSAGES_DELETED)
    {
        // 所有会话的所有消息被删除
    }
...
}zim.on('messageDeleted', (zim: ZIM, data: ZIMEventOfMessageDeletedResult) => {
    const messageDeleteType = data.messageDeleteType;
    if (messageDeleteType == 2 ) {
        // 会话中的所有消息都被删除
     } else if (messageDeleteType == 1 ) {
        // 会话中的所有当前消息都被删除
    } else if (messageDeleteType == 0) {
        // 会话中指定的消息被删除
    }
});zim.on('messageDeleted', (zim: ZIM, data: ZIMEventOfMessageDeletedResult) => {
    const messageDeleteType = data.messageDeleteType;
    if (messageDeleteType == 2 ) {
        // 会话中的所有消息都被删除
     } else if (messageDeleteType == 1 ) {
        // 会话中的所有当前消息都被删除
    } else if (messageDeleteType == 0) {
        // 会话中指定的消息被删除
    }
});zim.onMessageDeleted((data) => {
    const messageDeleteType = data.messageDeleteType;
    if (messageDeleteType == 2 ) {
        // 会话中的所有消息都被删除
     } else if (messageDeleteType == 1 ) {
        // 会话中的所有当前消息都被删除
    } else if (messageDeleteType == 0) {
        // 会话中指定的消息被删除
    }
});zim.onMessageDeleted((data) => {
    const messageDeleteType = data.messageDeleteType;
    if (messageDeleteType == 2 ) {
        // 会话中的所有消息都被删除
     } else if (messageDeleteType == 1 ) {
        // 会话中的所有当前消息都被删除
    } else if (messageDeleteType == 0) {
        // 会话中指定的消息被删除
    }
});设置消息回执已读
当用户在一端通过 sendMessageReceiptsRead 、 sendConversationMessageReceiptRead 接口设置消息回执已读后,其他在线客户端可通过注册 messageReceiptChanged 、 conversationMessageReceiptChanged 事件监听本帐号已设置消息回执为已读。
调用接口示例
public class ZIMEventHandler {
...
    public void onMessageReceiptChanged(ZIM zim, ArrayList<ZIMMessageReceiptInfo> infos) {
        for (ZIMMessageReceiptInfo info : infos) {
            if (info.isSelfOperated) {
                // 用户自己设置的消息回执已读
            }
        }
    }
    public void onConversationMessageReceiptChanged(ZIM zim, ArrayList<ZIMMessageReceiptInfo> infos) {
        for (ZIMMessageReceiptInfo info : infos) {
            if (info.isSelfOperated) {
                // 用户自己设置的消息回执已读
            }
        }
    }
...
}public class ZIMEventHandler {
...
    public void onMessageReceiptChanged(ZIM zim, ArrayList<ZIMMessageReceiptInfo> infos) {
        for (ZIMMessageReceiptInfo info : infos) {
            if (info.isSelfOperated) {
                // 用户自己设置的消息回执已读
            }
        }
    }
    public void onConversationMessageReceiptChanged(ZIM zim, ArrayList<ZIMMessageReceiptInfo> infos) {
        for (ZIMMessageReceiptInfo info : infos) {
            if (info.isSelfOperated) {
                // 用户自己设置的消息回执已读
            }
        }
    }
...
}ZIMEventHandler.onMessageReceiptChanged = (ZIM zim, List<ZIMMessageReceiptInfo> infos){
    for (ZIMMessageReceiptInfo info : infos) {
        if (info.isSelfOperated) {
            // 用户已将消息回执设置为已读
        }
    }
};
ZIMEventHandler.onConversationMessageReceiptChanged = (ZIM zim, List<ZIMMessageReceiptInfo> infos){
    for (ZIMMessageReceiptInfo info : infos) {
        if (info.isSelfOperated) {
            // 用户已将消息回执设置为已读
        }
    }
};ZIMEventHandler.onMessageReceiptChanged = (ZIM zim, List<ZIMMessageReceiptInfo> infos){
    for (ZIMMessageReceiptInfo info : infos) {
        if (info.isSelfOperated) {
            // 用户已将消息回执设置为已读
        }
    }
};
ZIMEventHandler.onConversationMessageReceiptChanged = (ZIM zim, List<ZIMMessageReceiptInfo> infos){
    for (ZIMMessageReceiptInfo info : infos) {
        if (info.isSelfOperated) {
            // 用户已将消息回执设置为已读
        }
    }
};- (void)zim:(ZIM *)zim messageReceiptChanged:(NSArray<ZIMMessageReceiptInfo *> *)infos{
        for(ZIMMessageReceiptInfo *info in infos){
            if (info.isSelfOperated) {
                // 用户将消息回执设置为已读
            }
        }
}
- (void)zim:(ZIM *)zim conversationMessageReceiptChanged:(NSArray<ZIMMessageReceiptInfo *> *)infos{
        for (ZIMMessageReceiptInfo *info in infos) {
            if (info.isSelfOperated) {
                // 用户将会话回执设置为已读
            }
        }
}- (void)zim:(ZIM *)zim messageReceiptChanged:(NSArray<ZIMMessageReceiptInfo *> *)infos{
        for(ZIMMessageReceiptInfo *info in infos){
            if (info.isSelfOperated) {
                // 用户将消息回执设置为已读
            }
        }
}
- (void)zim:(ZIM *)zim conversationMessageReceiptChanged:(NSArray<ZIMMessageReceiptInfo *> *)infos{
        for (ZIMMessageReceiptInfo *info in infos) {
            if (info.isSelfOperated) {
                // 用户将会话回执设置为已读
            }
        }
}class zim_event_handler : public zim::ZIMEventHandler {
...
   virtual void onMessageReceiptChanged(zim::ZIM * /*zim*/,
                const std::vector<zim::ZIMMessageReceiptInfo> & infos) override {
        for (const auto &info : infos) {
            if (info.isSelfOperated) {
                // 用户将消息回执设置为已读
            }
        }
    }
    virtual void onConversationMessageReceiptChanged(zim::ZIM *zim, 
                const std::vector<zim::ZIMMessageReceiptInfo> &infos) override {
        for (const auto &info : infos) {
            if (info.isSelfOperated) {
                // 用户将会话回执设置为已读
            }
        }
    }
...
}class zim_event_handler : public zim::ZIMEventHandler {
...
   virtual void onMessageReceiptChanged(zim::ZIM * /*zim*/,
                const std::vector<zim::ZIMMessageReceiptInfo> & infos) override {
        for (const auto &info : infos) {
            if (info.isSelfOperated) {
                // 用户将消息回执设置为已读
            }
        }
    }
    virtual void onConversationMessageReceiptChanged(zim::ZIM *zim, 
                const std::vector<zim::ZIMMessageReceiptInfo> &infos) override {
        for (const auto &info : infos) {
            if (info.isSelfOperated) {
                // 用户将会话回执设置为已读
            }
        }
    }
...
}zim.on('messageReceiptChanged', (zim: ZIM, data: ZIMEventOfMessageReceiptChangedResult) => {
    data.infos.forEach((info) => {
        if (info.isSelfOperated) {
            // 用户将消息回执设置为已读
        }
    });
});
zim.on('conversationMessageReceiptChanged', (zim: ZIM, data: ZIMEventOfMessageReceiptChangedResult) => {
    data.infos.forEach((info) => {
        if (info.isSelfOperated) {
            // 用户将会话回执设置为已读
        }
    });
});zim.on('messageReceiptChanged', (zim: ZIM, data: ZIMEventOfMessageReceiptChangedResult) => {
    data.infos.forEach((info) => {
        if (info.isSelfOperated) {
            // 用户将消息回执设置为已读
        }
    });
});
zim.on('conversationMessageReceiptChanged', (zim: ZIM, data: ZIMEventOfMessageReceiptChangedResult) => {
    data.infos.forEach((info) => {
        if (info.isSelfOperated) {
            // 用户将会话回执设置为已读
        }
    });
});zim.onMessageReceiptChanged((data) => {
    data.infos.forEach((info) => {
        if (info.isSelfOperated) {
            // 用户将消息回执设置为已读
        }
    });
});
zim.onConversationMessageReceiptChanged((data) => {
    data.infos.forEach((info) => {
        if (info.isSelfOperated) {
            // 用户将会话回执设置为已读
        }
    });
});zim.onMessageReceiptChanged((data) => {
    data.infos.forEach((info) => {
        if (info.isSelfOperated) {
            // 用户将消息回执设置为已读
        }
    });
});
zim.onConversationMessageReceiptChanged((data) => {
    data.infos.forEach((info) => {
        if (info.isSelfOperated) {
            // 用户将会话回执设置为已读
        }
    });
});房间管理
房间模块相关接口和事件默认不支持多端登录。用户在 A 设备加入房间后,然后在 B 设备再加入相同的房间后,会把 A 设备踢出房间,A 设备会通过 roomStateChanged 回调收到 event 为 KickedOutByMultiDevice。
public class ZIMEventHandler {
...
    public void onRoomStateChanged(ZIM zim, ZIMRoomState state, ZIMRoomEvent event, JSONObject extendedData, String roomID) {
        if (state == ZIMRoomState.DISCONNECTED &&
            event == ZIMRoomEvent.KICKED_OUT) {
            // 多端登录加入房间,被踢出房间
        }
    }
...
}public class ZIMEventHandler {
...
    public void onRoomStateChanged(ZIM zim, ZIMRoomState state, ZIMRoomEvent event, JSONObject extendedData, String roomID) {
        if (state == ZIMRoomState.DISCONNECTED &&
            event == ZIMRoomEvent.KICKED_OUT) {
            // 多端登录加入房间,被踢出房间
        }
    }
...
}ZIMEventHandler.onRoomStateChanged = (ZIM zim, ZIMRoomState state, ZIMRoomEvent event,
    Map extendedData, String roomID){
        if (state == ZIMRoomState.disconnected &&
            event == ZIMRoomEvent.kickedOut) {
        // 由于多设备登录而被踢出房间
        }
    };ZIMEventHandler.onRoomStateChanged = (ZIM zim, ZIMRoomState state, ZIMRoomEvent event,
    Map extendedData, String roomID){
        if (state == ZIMRoomState.disconnected &&
            event == ZIMRoomEvent.kickedOut) {
        // 由于多设备登录而被踢出房间
        }
    };- (void)zim:(ZIM *)zim
    roomStateChanged:(ZIMRoomState)state
               event:(ZIMRoomEvent)event
        extendedData:(NSDictionary *)extendedData
              roomID:(NSString *)roomID{
        if (state == ZIMConnectionStateDisconnected &&
            event == ZIMRoomEventKickedOutByOtherDevice) {
           // 由于多设备登录而被踢出房间
        }
}- (void)zim:(ZIM *)zim
    roomStateChanged:(ZIMRoomState)state
               event:(ZIMRoomEvent)event
        extendedData:(NSDictionary *)extendedData
              roomID:(NSString *)roomID{
        if (state == ZIMConnectionStateDisconnected &&
            event == ZIMRoomEventKickedOutByOtherDevice) {
           // 由于多设备登录而被踢出房间
        }
}class zim_event_handler : public zim::ZIMEventHandler {
...
    virtual void onRoomStateChanged(zim::ZIM *zim, zim::ZIMRoomState state, zim::ZIMRoomEvent event,
                                    const std::string &extendedData,
                                    const std::string &roomID) override {
        if (state == zim::ZIMRoomState::ZIM_ROOM_STATE_DISCONNECTED &&
            event == zim::ZIMRoomEvent::ZIM_ROOM_EVENT_KICKED_OUT_BY_OTHER_DEVICE) {
           // 由于多设备登录被踢出房间
        }
    }
...
}class zim_event_handler : public zim::ZIMEventHandler {
...
    virtual void onRoomStateChanged(zim::ZIM *zim, zim::ZIMRoomState state, zim::ZIMRoomEvent event,
                                    const std::string &extendedData,
                                    const std::string &roomID) override {
        if (state == zim::ZIMRoomState::ZIM_ROOM_STATE_DISCONNECTED &&
            event == zim::ZIMRoomEvent::ZIM_ROOM_EVENT_KICKED_OUT_BY_OTHER_DEVICE) {
           // 由于多设备登录被踢出房间
        }
    }
...
}// When a user joins a room on device A, and then joins the same room on device B, device A will be kicked out of the room
zim.on('roomStateChanged', (zim: ZIM, data: ZIMEventOfRoomStateChangedResult) => {
    if (data.state == 0 && data.event == 10) {
       // 由于多设备登录被踢出房间
    }
});// When a user joins a room on device A, and then joins the same room on device B, device A will be kicked out of the room
zim.on('roomStateChanged', (zim: ZIM, data: ZIMEventOfRoomStateChangedResult) => {
    if (data.state == 0 && data.event == 10) {
       // 由于多设备登录被踢出房间
    }
});// When a user joins a room on device A, and then joins the same room on device B, device A will be kicked out of the room
zim.onRoomStateChanged((data) => {
    if (data.state == 0 && data.event == 10) {
       // 由于多设备登录被踢出房间
    }
});// When a user joins a room on device A, and then joins the same room on device B, device A will be kicked out of the room
zim.onRoomStateChanged((data) => {
    if (data.state == 0 && data.event == 10) {
       // 由于多设备登录被踢出房间
    }
});如需支持同一用户在多个设备上同时登录房间,可在控制台自行开启该功能,详见控制台文档:多端登录配置。开启后,多个设备以相同 UserID 加入同一房间时将不会互相踢出。此时,你需维护房间成员列表,对 UserID 进行去重。
群组管理
在开通多端登录服务后,ZIM SDK 会自动在多端设备之间同步群组相关数据。
呼叫邀请管理
当用户同时登录设备 A 和 B,用户收到呼叫邀请,在设备 A 上接受邀请(调用 callAccept )或者拒绝邀请(调用 callReject ) 后:
- 设备 A 可以通过相关操作的回调( ZIMCallAcceptanceSentResult 或 ZIMCallRejectionSentResult )得知操作结果,关闭邀请弹窗,实现其他业务操作;
- 设备 B 应当通过 callUserStateChanged 回调,得知本用户的呼叫用户状态( ZIMCallUserState )是 Accepted还是Rejected,关闭邀请弹窗,实现其他业务操作
各个设备能收到该呼叫内的用户状态变更事件 callUserStateChanged 如下表所示:
| callUserState | 设备 A | 设备 B | 
|---|---|---|
| Inviting | ✔️ | ✔️ | 
| Received | ✔️ | ✔️ | 
| Accepted | ✔️ | ✔️ | 
| Rejected | ✔️ | ✔️ | 
| Timeout | ✖ | ✖ | 
| Cancelled | ✖ | ✖ | 
| Quit | ✔️ | ✖ | 
调用接口示例
String selfUserID = "user_id";
String currentCallID = "call_id";
ZIMCallAcceptConfig acceptConfig;
acceptConfig.extendedData = "extra_1";
ZIMCallRejectConfig rejectConfig;
rejectConfig.extendedData = "extra_1";
// 设备 A 接受邀请
zim.callAccept(currentCallID, acceptConfig, new ZIMCallAcceptanceSentCallback() {
     @Override
     public void onCallAcceptanceSent(String callID, ZIMError errorInfo) {
         // 关闭呼叫等待操作的弹框。
     }
});
// 设备 A 拒绝邀请
zim.callReject(currentCallID, rejectConfig, new ZIMCallRejectionSentCallback() {
     @Override
     public void onCallRejectionSent(String callID, ZIMError errorInfo) {
         // 关闭呼叫等待操作的弹框。
     }
});
// 设备 B 监听 onCallUserStateChanged
public class ZIMEventHandler {
...
    public void onCallUserStateChanged(ZIM zim, ZIMCallUserStateChangeInfo info, String callID) {
         if (currentCallID == callID) {
                // 设备 A 已经接受或者拒绝时,关闭呼叫等待操作的弹框
            }
        });
    }String selfUserID = "user_id";
String currentCallID = "call_id";
ZIMCallAcceptConfig acceptConfig;
acceptConfig.extendedData = "extra_1";
ZIMCallRejectConfig rejectConfig;
rejectConfig.extendedData = "extra_1";
// 设备 A 接受邀请
zim.callAccept(currentCallID, acceptConfig, new ZIMCallAcceptanceSentCallback() {
     @Override
     public void onCallAcceptanceSent(String callID, ZIMError errorInfo) {
         // 关闭呼叫等待操作的弹框。
     }
});
// 设备 A 拒绝邀请
zim.callReject(currentCallID, rejectConfig, new ZIMCallRejectionSentCallback() {
     @Override
     public void onCallRejectionSent(String callID, ZIMError errorInfo) {
         // 关闭呼叫等待操作的弹框。
     }
});
// 设备 B 监听 onCallUserStateChanged
public class ZIMEventHandler {
...
    public void onCallUserStateChanged(ZIM zim, ZIMCallUserStateChangeInfo info, String callID) {
         if (currentCallID == callID) {
                // 设备 A 已经接受或者拒绝时,关闭呼叫等待操作的弹框
            }
        });
    }String selfUserID = "user_id";
String currentCallID = "call_id";
ZIMCallAcceptConfig acceptConfig;
acceptConfig.extendedData = "extra_1";
ZIMCallRejectConfig rejectConfig;
rejectConfig.extendedData = "extra_1";
// 设备A接受邀请
ZIM.getInstance()!.callAccept(currentCallID, acceptConfig).then((value) {
    // 关闭呼叫等待操作的弹窗。
}).catchError((onError){
    // 处理异常
});
// 设备A拒绝邀请
ZIM.getInstance()!.callReject(currentCallID, rejectConfig).then((value) {
    // 关闭呼叫等待操作的弹窗。
}).catchError((onError){
    // 处理异常
});
// 设备B监听onCallUserStateChanged事件
ZIMEventHandler.onCallUserStateChanged = (ZIM zim, ZIMCallUserStateChangeInfo callUserStateChangeInfo, String callID){
    if (currentCallID == callID) {
         // 当设备A接受或拒绝时,关闭呼叫等待操作的弹窗。
    }
};String selfUserID = "user_id";
String currentCallID = "call_id";
ZIMCallAcceptConfig acceptConfig;
acceptConfig.extendedData = "extra_1";
ZIMCallRejectConfig rejectConfig;
rejectConfig.extendedData = "extra_1";
// 设备A接受邀请
ZIM.getInstance()!.callAccept(currentCallID, acceptConfig).then((value) {
    // 关闭呼叫等待操作的弹窗。
}).catchError((onError){
    // 处理异常
});
// 设备A拒绝邀请
ZIM.getInstance()!.callReject(currentCallID, rejectConfig).then((value) {
    // 关闭呼叫等待操作的弹窗。
}).catchError((onError){
    // 处理异常
});
// 设备B监听onCallUserStateChanged事件
ZIMEventHandler.onCallUserStateChanged = (ZIM zim, ZIMCallUserStateChangeInfo callUserStateChangeInfo, String callID){
    if (currentCallID == callID) {
         // 当设备A接受或拒绝时,关闭呼叫等待操作的弹窗。
    }
};NSString *selfUserId = @"user_id";
NSString *currentCallId = @"call_id";
ZIMCallAcceptConfig *acceptConfig = [[ZIMCallAcceptConfig alloc] init];
acceptConfig.extendedData = @"extra_1";
ZIMCallRejectConfig *rejectConfig = [[ZIMCallRejectConfig alloc] init];
rejectConfig.extendedData = @"extra_1";
// 设备A接受邀请
[[ZIM getInstance] callAcceptWithCallID:currentCallId config:acceptConfig callback:^(NSString * _Nonnull callID, ZIMError * _Nonnull errorInfo) {
        // 关闭呼叫等待操作的弹出框,呼叫结束。
 }];
// 设备A拒绝邀请
[[ZIM getInstance] callRejectWithCallID:currentCallId config:rejectConfig callback:^(NSString * _Nonnull callID, ZIMError * _Nonnull errorInfo) {
        // 关闭呼叫等待操作的弹出框,呼叫结束。
 }];
// 设备B监听callUserStateChanged
- (void)zim:(ZIM *)zim
    callUserStateChanged:(ZIMCallUserStateChangeInfo *)info
                  callID:(NSString *)callID{
        for(ZIMCallUserInfo *userInfo in info.callUserList){
            if(userInfo.userID == selfUserId && (userInfo.state == ZIMCallUserStateAccepted ||  userInfo.state == ZIMCallUserStateRejected)){
                 // 当设备A接受或拒绝时,关闭呼叫等待操作的弹出框。
            }
            }
        }
}NSString *selfUserId = @"user_id";
NSString *currentCallId = @"call_id";
ZIMCallAcceptConfig *acceptConfig = [[ZIMCallAcceptConfig alloc] init];
acceptConfig.extendedData = @"extra_1";
ZIMCallRejectConfig *rejectConfig = [[ZIMCallRejectConfig alloc] init];
rejectConfig.extendedData = @"extra_1";
// 设备A接受邀请
[[ZIM getInstance] callAcceptWithCallID:currentCallId config:acceptConfig callback:^(NSString * _Nonnull callID, ZIMError * _Nonnull errorInfo) {
        // 关闭呼叫等待操作的弹出框,呼叫结束。
 }];
// 设备A拒绝邀请
[[ZIM getInstance] callRejectWithCallID:currentCallId config:rejectConfig callback:^(NSString * _Nonnull callID, ZIMError * _Nonnull errorInfo) {
        // 关闭呼叫等待操作的弹出框,呼叫结束。
 }];
// 设备B监听callUserStateChanged
- (void)zim:(ZIM *)zim
    callUserStateChanged:(ZIMCallUserStateChangeInfo *)info
                  callID:(NSString *)callID{
        for(ZIMCallUserInfo *userInfo in info.callUserList){
            if(userInfo.userID == selfUserId && (userInfo.state == ZIMCallUserStateAccepted ||  userInfo.state == ZIMCallUserStateRejected)){
                 // 当设备A接受或拒绝时,关闭呼叫等待操作的弹出框。
            }
            }
        }
}const std::string self_user_id = "user_id";
const std::string current_call_id = "call_id";
zim::ZIMCallAcceptConfig accept_config;
accept_config.extendedData = "extra_1";
zim::ZIMCallRejectConfig reject_config;
reject_config.extendedData = "extra_1";
// 设备A接受邀请
zim_->callAccept(current_call_id, accept_config, [=] (const std::string &callID, const ZIMError &errorInfo) {
        // 关闭呼叫等待操作的弹出框。呼叫结束。
});
// 设备A拒绝邀请
zim_->callReject(current_call_id, reject_config, [=] (const std::string &callID, const ZIMError &errorInfo) {
        // 关闭呼叫等待操作的弹出框。呼叫结束。
 }];
// 设备B监听callUserStateChanged事件
class zim_event_handler : public zim::ZIMEventHandler {
...
    virtual void onCallUserStateChanged(zim::ZIM * /*zim*/,
                                               const zim::ZIMCallUserStateChangeInfo &info,
                                               const std::string &callID) override {
        for (const auto &user_info : callUserList) {
            if (user_info.userID == self_user_id &&
                (user_info.state == zim::ZIMCallUserState::ZIM_CALL_USER_STATE_ACCEPTED ||
                 user_info.state == zim::ZIMCallUserState::ZIM_CALL_USER_STATE_REJECTED)) {
                 // 当设备A接受或拒绝时,关闭呼叫等待操作的弹出框。
            }
        });
    }
...
}const std::string self_user_id = "user_id";
const std::string current_call_id = "call_id";
zim::ZIMCallAcceptConfig accept_config;
accept_config.extendedData = "extra_1";
zim::ZIMCallRejectConfig reject_config;
reject_config.extendedData = "extra_1";
// 设备A接受邀请
zim_->callAccept(current_call_id, accept_config, [=] (const std::string &callID, const ZIMError &errorInfo) {
        // 关闭呼叫等待操作的弹出框。呼叫结束。
});
// 设备A拒绝邀请
zim_->callReject(current_call_id, reject_config, [=] (const std::string &callID, const ZIMError &errorInfo) {
        // 关闭呼叫等待操作的弹出框。呼叫结束。
 }];
// 设备B监听callUserStateChanged事件
class zim_event_handler : public zim::ZIMEventHandler {
...
    virtual void onCallUserStateChanged(zim::ZIM * /*zim*/,
                                               const zim::ZIMCallUserStateChangeInfo &info,
                                               const std::string &callID) override {
        for (const auto &user_info : callUserList) {
            if (user_info.userID == self_user_id &&
                (user_info.state == zim::ZIMCallUserState::ZIM_CALL_USER_STATE_ACCEPTED ||
                 user_info.state == zim::ZIMCallUserState::ZIM_CALL_USER_STATE_REJECTED)) {
                 // 当设备A接受或拒绝时,关闭呼叫等待操作的弹出框。
            }
        });
    }
...
}const selfUserID = '';
const curCallID = '';
// 设备A接受邀请
zim.callAccept(curCallID, { extendedData: '设备A接受邀请' } as ZIMCallAcceptConfig)
    .then((res: ZIMCallAcceptanceSentResult) => {
        // 关闭呼叫等待操作的弹出框。开始流发布和播放,进行音视频通话。
    });
// 设备A拒绝邀请
zim.callReject(curCallID, { extendedData: '设备A拒绝邀请' } as ZIMCallRejectConfig)
    .then((res: ZIMCallRejectionSentResult) => {
        // 关闭呼叫等待操作的弹出框。通话结束。
    });
// 设备B监听callUserStateChanged事件
zim.on('callUserStateChanged', (zim: ZIM, data: ZIMEventOfCallUserStateChangedResult) => {
    if (curCallID == data.callID) {
        data.callUserList.forEach((item) => {
            if (item.userID == selfUserID && (item.state == 1 || item.state == 2)) {
                // 当设备A接受或拒绝时,关闭呼叫等待操作的弹出框。
            }
        });
    }
});const selfUserID = '';
const curCallID = '';
// 设备A接受邀请
zim.callAccept(curCallID, { extendedData: '设备A接受邀请' } as ZIMCallAcceptConfig)
    .then((res: ZIMCallAcceptanceSentResult) => {
        // 关闭呼叫等待操作的弹出框。开始流发布和播放,进行音视频通话。
    });
// 设备A拒绝邀请
zim.callReject(curCallID, { extendedData: '设备A拒绝邀请' } as ZIMCallRejectConfig)
    .then((res: ZIMCallRejectionSentResult) => {
        // 关闭呼叫等待操作的弹出框。通话结束。
    });
// 设备B监听callUserStateChanged事件
zim.on('callUserStateChanged', (zim: ZIM, data: ZIMEventOfCallUserStateChangedResult) => {
    if (curCallID == data.callID) {
        data.callUserList.forEach((item) => {
            if (item.userID == selfUserID && (item.state == 1 || item.state == 2)) {
                // 当设备A接受或拒绝时,关闭呼叫等待操作的弹出框。
            }
        });
    }
});const selfUserID = '';
const curCallID = '';
// 设备A接受邀请
zim.callAccept(curCallID, { extendedData: '设备A接受邀请' } as ZIMCallAcceptConfig)
    .then((res: ZIMCallAcceptanceSentResult) => {
        // 关闭呼叫等待操作的弹出框。开始流发布和播放,进行音视频通话。
    });
// 设备A拒绝邀请
zim.callReject(curCallID, { extendedData: '设备A拒绝邀请' } as ZIMCallRejectConfig)
    .then((res: ZIMCallRejectionSentResult) => {
        // 关闭呼叫等待操作的弹出框。通话结束。
    });
// 设备B监听callUserStateChanged事件
zim.onCallUserStateChanged((data) => {
    if (curCallID == data.callID) {
        data.callUserList.forEach((item) => {
            if (item.userID == selfUserID && (item.state == 1 || item.state == 2)) {
                // 当设备A接受或拒绝时,关闭呼叫等待操作的弹出框。
            }
        });
    }
});const selfUserID = '';
const curCallID = '';
// 设备A接受邀请
zim.callAccept(curCallID, { extendedData: '设备A接受邀请' } as ZIMCallAcceptConfig)
    .then((res: ZIMCallAcceptanceSentResult) => {
        // 关闭呼叫等待操作的弹出框。开始流发布和播放,进行音视频通话。
    });
// 设备A拒绝邀请
zim.callReject(curCallID, { extendedData: '设备A拒绝邀请' } as ZIMCallRejectConfig)
    .then((res: ZIMCallRejectionSentResult) => {
        // 关闭呼叫等待操作的弹出框。通话结束。
    });
// 设备B监听callUserStateChanged事件
zim.onCallUserStateChanged((data) => {
    if (curCallID == data.callID) {
        data.callUserList.forEach((item) => {
            if (item.userID == selfUserID && (item.state == 1 || item.state == 2)) {
                // 当设备A接受或拒绝时,关闭呼叫等待操作的弹出框。
            }
        });
    }
});


