tencent cloud

即时通信 IM

动态与公告
产品动态
公告
产品简介
产品概述
基本概念
应用场景
功能介绍
账号系统
用户资料与关系链
消息管理
群组相关
公众号系统
音视频通话 Call
使用限制
购买指南
计费概述
价格说明
购买指引
续费指引
停服说明
退费说明
开发指引
Demo 专区
开通服务
体验 Demo
快速跑通
下载中心
SDK & Demo 源码
更新日志
聊天互动(含 UI)
TUIKit 组件介绍
快速开始
全功能接入
单功能接入
AI 集成
构建基础界面
更多特性
定义外观
国际化界面语言
推送服务(Push)
服务概述
名词解释
开通服务
快速跑通
厂商通道
数据统计
排查工具
客户端 API
服务端 API
推送回调
高级功能
更新日志
错误码
常见问题
智能客服
功能概述
快速入门
集成指引
管理员操作手册
客服操作手册
更多实践
直播间搭建
AI 聊天机器人方案
超大娱乐协作社群
Discord 实现指南
游戏内集成 Chat 指南
类 WhatsApp Channel 搭建方案
发送红包
Chat 应对防火墙限制相关
无 UI 集成
快速开始
集成 SDK
初始化
登录登出
消息相关
会话相关
群组相关
社群话题
用户管理
离线推送
云端搜索
本地搜索
公众号
客户端 API
JavaScript
Android
iOS & macOS
Swift
Flutter
Electron
Unity
React Native
C 接口
C++
服务端 API
生成 UserSig
REST API
第三方回调
控制台指南
新版控制台介绍
创建并升级应用
基本配置
功能配置
账号管理
群组管理
公众号管理
回调配置
用量统计
资源包查看指南
实时监控
开发辅助工具
访问管理
高级功能
常见问题
uni-app 常见问题
购买相关问题
SDK 相关问题
账号鉴权相关问题
用户资料与关系链相关问题
消息相关问题
群组相关问题
直播群相关问题
昵称头像相关问题
协议与认证
服务等级协议
安全合规认证
IM 政策
隐私政策
数据隐私和安全协议
平滑迁移方案
平滑迁移完整版
平滑迁移简化版
错误码
联系我们

Android&iOS&Windows&Mac

PDF
聚焦模式
字号
最后更新时间: 2025-01-13 17:27:05

功能描述

通过 addSimpleMsgListener 监听接收文本、自定义消息,相关回调在 V2TIMSimpleMsgListener 协议中定义。
通过 addAdvancedMsgListener 监听接收所有类型消息(文本、自定义、富媒体消息),相关回调在 V2TIMAdvancedMsgListener 协议中定义。

设置消息监听器

SDK 提供了 2 种消息监听器,简单消息监听器 V2TIMSimpleMsgListener 和高级消息监听器 V2TIMAdvancedMsgListener。 两者的区别在于:
1. 简单消息监听器只能接收文本、自定义消息。如果您的业务只需要这两种消息,可以仅使用简单消息监听器。
2. 高级消息监听器可以接收所有类型的消息。如果您的业务还需要支持富媒体、合并消息等其他类型,请使用高级消息监听器。
注意
1. addSimpleMsgListeneraddAdvancedMsgListener 请使用其中之一,切勿混用,以免产生不可预知的逻辑 bug。
2. 如果想要正常接收下面各种类型的消息,必须先添加消息监听器,否则无法正常接收。

简单消息监听器

添加监听器

接收方调用 addSimpleMsgListener (Java / Swift / Objective-C / C++) 添加简单消息监听器。一般建议在比较靠前的时间点调用,例如聊天消息界面初始化后,确保 App 能及时收到消息。
示例代码如下:
Java
Swift
Objective-C
C++
V2TIMManager.getInstance().addSimpleMsgListener(simpleMsgListener);
V2TIMManager.shared.addSimpleMsgListener(listener: self)
// self 为 id<V2TIMSignalingListener>
[[V2TIMManager sharedInstance] addSimpleMsgListener:self];
class SimpleMsgListener final : public V2TIMSimpleMsgListener {
// 成员 ...
};

// 添加基本消息的事件监听器,注意在移除监听器之前需要保持 simpleMsgListener 的生命期,以免接收不到事件回调
SimpleMsgListener simpleMsgListener;
V2TIMManager::GetInstance()->AddSimpleMsgListener(&simpleMsgListener);

监听器回调事件

添加成功简单消息监听器后,接收方可以在 V2TIMSimpleMsgListener (Java / Swift / Objective-C / C++) 的回调中接收不同类型消息,说明如下:
Java
Swift
Objective-C
C++
public abstract class V2TIMSimpleMsgListener {
// 收到 C2C 文本消息
public void onRecvC2CTextMessage(String msgID, V2TIMUserInfo sender, String text) {}

// 收到 C2C 自定义(信令)消息
public void onRecvC2CCustomMessage(String msgID, V2TIMUserInfo sender, byte[] customData) {}

// 收到群文本消息
public void onRecvGroupTextMessage(String msgID, String groupID, V2TIMGroupMemberInfo sender, String text) {}

// 收到群自定义(信令)消息
public void onRecvGroupCustomMessage(String msgID, String groupID, V2TIMGroupMemberInfo sender, byte[] customData) {}
}
public protocol V2TIMSimpleMsgListener : AnyObject {
/// 收到 C2C 文本消息
func onRecvC2CTextMessage(msgID: String, sender: V2TIMUserInfo, text: String?)
/// 收到 C2C 自定义(信令)消息
func onRecvC2CCustomMessage(msgID: String, sender: V2TIMUserInfo, customData: Data?)
/// 收到群文本消息
func onRecvGroupTextMessage(msgID: String, groupID: String, sender: V2TIMGroupMemberInfo, text: String?)
/// 收到群自定义(信令)消息
func onRecvGroupCustomMessage(msgID: String, groupID: String, sender: V2TIMGroupMemberInfo, customData: Data?)

}

/// IMSDK 基本消息回调
@protocol V2TIMSimpleMsgListener <NSObject>
@optional

/// 收到 C2C 文本消息
- (void)onRecvC2CTextMessage:(NSString *)msgID sender:(V2TIMUserInfo *)info text:(NSString *)text;

/// 收到 C2C 自定义(信令)消息
- (void)onRecvC2CCustomMessage:(NSString *)msgID sender:(V2TIMUserInfo *)info customData:(NSData *)data;

/// 收到群文本消息
- (void)onRecvGroupTextMessage:(NSString *)msgID groupID:(NSString *)groupID sender:(V2TIMGroupMemberInfo *)info text:(NSString *)text;

/// 收到群自定义(信令)消息
- (void)onRecvGroupCustomMessage:(NSString *)msgID groupID:(NSString *)groupID sender:(V2TIMGroupMemberInfo *)info customData:(NSData *)data;
@end
class SimpleMsgListener final : public V2TIMSimpleMsgListener {
public:
SimpleMsgListener() = default;
~SimpleMsgListener() override = default;

// 收到 C2C 文本消息
void OnRecvC2CTextMessage(const V2TIMString& msgID, const V2TIMUserFullInfo& sender,
const V2TIMString& text) override {}

// 收到 C2C 自定义(信令)消息
void OnRecvC2CCustomMessage(const V2TIMString& msgID, const V2TIMUserFullInfo& sender,
const V2TIMBuffer& customData) override {}

// 收到群文本消息
void OnRecvGroupTextMessage(const V2TIMString& msgID, const V2TIMString& groupID,
const V2TIMGroupMemberFullInfo& sender, const V2TIMString& text) override {}

// 收到群自定义(信令)消息
void OnRecvGroupCustomMessage(const V2TIMString& msgID, const V2TIMString& groupID,
const V2TIMGroupMemberFullInfo& sender,
const V2TIMBuffer& customData) override {}
};

移除监听器

如果想停止接收消息,接收方可调用 removeSimpleMsgListener (Java / Swift / Objective-C / C++) 移除简单消息监听器。
示例代码如下:
Java
Swift
Objective-C
C++
V2TIMManager.getInstance().removeSimpleMsgListener(simpleMsgListener);
V2TIMManager.shared.removeSimpleMsgListener(listener: self)
// self 为 id<V2TIMSignalingListener>
[[V2TIMManager sharedInstance] removeSimpleMsgListener:self];
class SimpleMsgListener final : public V2TIMSimpleMsgListener {
// 成员 ...
};

// simpleMsgListener 是 SimpleMsgListener 的实例
V2TIMManager::GetInstance()->RemoveSimpleMsgListener(&simpleMsgListener);

高级消息监听器

添加监听器

接收方调用 addAdvancedMsgListener (Java / Swift / Objective-C / C++) 添加高级消息监听器。一般建议在比较靠前的时间点调用,例如例如聊天消息界面初始化后,确保 App 能及时收到消息。
示例代码如下:
Java
Swift
Objective-C
C++
V2TIMManager.getMessageManager().addAdvancedMsgListener(advancedMsgListener);
V2TIMManager.shared.addAdvancedMsgListener(listener: self)
// self 为 id<V2TIMAdvancedMsgListener>
[[V2TIMManager sharedInstance] addAdvancedMsgListener:self];
class AdvancedMsgListener final : public V2TIMAdvancedMsgListener {
// 成员 ...
};

// 添加高级消息的事件监听器,注意在移除监听器之前需要保持 advancedMsgListener 的生命期,以免接收不到事件回调
AdvancedMsgListener advancedMsgListener;
V2TIMManager::GetInstance()->GetMessageManager()->AddAdvancedMsgListener(&advancedMsgListener);

监听器回调事件

添加成功高级消息监听器后,接收方可以在 V2TIMAdvancedMsgListener (Java / Swift / Objective-C / C++) 的回调中接收不同类型消息,说明如下:
Java
Swift
Objective-C
C++
public abstract class V2TIMAdvancedMsgListener {
// 收到新消息
public void onRecvNewMessage(V2TIMMessage msg) {}

// C2C 对端用户会话已读通知(对端用户调用 markC2CMessageAsRead,自己会收到该通知)
public void onRecvC2CReadReceipt(List<V2TIMMessageReceipt> receiptList) {}

// 消息已读回执通知(如果自己发送的消息支持已读回执,消息接收端调用 sendMessageReadReceipts,自己会收到该通知)
public void onRecvMessageReadReceipts(List<V2TIMMessageReceipt> receiptList) {}

// 收到消息撤回的通知
public void onRecvMessageRevoked(String msgID) {}

// 消息内容被修改
public void onRecvMessageModified(V2TIMMessage msg) {}
}
public protocol V2TIMAdvancedMsgListener: AnyObject {
/// 收到新消息
func onRecvNewMessage(msg: V2TIMMessage)
/// 消息已读回执通知(如果自己发的消息支持已读回执,消息接收端调用了 sendMessageReadReceipts 接口,自己会收到该回调)
func onRecvMessageReadReceipts(receiptList: Array<V2TIMMessageReceipt>)
/// C2C 对端用户会话已读通知(如果对端用户调用 markC2CMessageAsRead 接口,自己会收到该通知)
func onRecvC2CReadReceipt(receiptList: Array<V2TIMMessageReceipt>)
/// 收到消息撤回
func onRecvMessageRevoked(msgID: String, operateUser: V2TIMUserInfo, reason: String?)
/// 消息内容被修改
func onRecvMessageModified(msg: V2TIMMessage)
}
/// 高级消息监听器
@protocol V2TIMAdvancedMsgListener <NSObject>
@optional
/// 收到新消息
- (void)onRecvNewMessage:(V2TIMMessage *)msg;

/// 消息已读回执通知(如果自己发的消息支持已读回执,消息接收端调用了 sendMessageReadReceipts 接口,自己会收到该回调)
- (void)onRecvMessageReadReceipts:(NSArray<V2TIMMessageReceipt *> *)receiptList;

/// C2C 对端用户会话已读通知(如果对端用户调用 markC2CMessageAsRead 接口,自己会收到该通知)
- (void)onRecvC2CReadReceipt:(NSArray<V2TIMMessageReceipt *> *)receiptList;

/// 收到消息撤回
- (void)onRecvMessageRevoked:(NSString *)msgID;

/// 消息内容被修改
- (void)onRecvMessageModified:(V2TIMMessage *)msg;
@end
class AdvancedMsgListener final : public V2TIMAdvancedMsgListener {
public:
AdvancedMsgListener() = default;
~AdvancedMsgListener() override = default;

// 收到新消息
void OnRecvNewMessage(const V2TIMMessage& message) override {}

// C2C 对端用户会话已读通知(对端用户调用 markC2CMessageAsRead,自己会收到该通知)
void OnRecvC2CReadReceipt(const V2TIMMessageReceiptVector& receiptList) override {}

// 消息已读回执通知(如果自己发送的消息支持已读回执,消息接收端调用
// sendMessageReadReceipts,自己会收到该通知)
void OnRecvMessageReadReceipts(const V2TIMMessageReceiptVector& receiptList) override {}

// 收到消息撤回的通知
void OnRecvMessageRevoked(const V2TIMString& messageID) override {}

// 消息内容被修改
void OnRecvMessageModified(const V2TIMMessage& message) override {}
};

移除监听器

如果想停止接收消息,接收方可调用 removeAdvancedMsgListener (Java / Swift / Objective-C / C++) 移除高级消息监听器。
示例代码如下:
Java
Swift
Objective-C
C++
V2TIMManager.getMessageManager().removeAdvancedMsgListener(advancedMsgListener);
V2TIMManager.shared.removeAdvancedMsgListener(listener: self)
// self 为 id<V2TIMAdvancedMsgListener>
[[V2TIMManager sharedInstance] removeAdvancedMsgListener:self];
class AdvancedMsgListener final : public V2TIMAdvancedMsgListener {
// 成员 ...
};

// advancedMsgListener 是 AdvancedMsgListener 的实例
V2TIMManager::GetInstance()->GetMessageManager()->RemoveAdvancedMsgListener(&advancedMsgListener);

接收文本消息

使用简单消息监听器接收

单聊文本消息

接收方使用简单消息监听器接收单聊文本消息,需要以下几步:
1. 调用 addSimpleMsgListener 设置事件监听器。
2. 监听 onRecvC2CTextMessage (Java / Swift / Objective-C / C++) 回调,在其中接收文本消息。
3. 希望停止接收消息,调用 removeSimpleMsgListener 移除监听。该步骤不是必须的,客户可以按照业务需求调用。
代码示例如下:
Java
Swift
Objective-C
C++
// 设置事件监听器
V2TIMManager.getInstance().addSimpleMsgListener(simpleMsgListener);

// 接收单聊文本消息
/**
* 收到 C2C 文本消息
*
* @param msgID 消息唯一标识
* @param sender 发送方信息
* @param text 发送内容
*/
public void onRecvC2CTextMessage(String msgID, V2TIMUserInfo sender, String text) {
// 可解析消息并展示到 UI
}
// 设置事件监听器
V2TIMManager.shared.addSimpleMsgListener(listener: self)

/// 接收单聊文本消息
/// @param msgID 消息 Id
/// @param sender 发送者信息
/// @param text 文本内容
func onRecvC2CTextMessage(msgID: String, sender: V2TIMUserInfo, text: String?) {
// 可解析消息并展示到 UI
}

// 设置事件监听器
[[V2TIMManager sharedInstance] addSimpleMsgListener:self];

/// 接收单聊文本消息
/// @param msgID 消息 Id
/// @param info 发送者信息
/// @param text 文本内容
- (void)onRecvC2CTextMessage:(NSString *)msgID sender:(V2TIMUserInfo *)info text:(NSString *)text {
// 可解析消息并展示到 UI
}
class SimpleMsgListener final : public V2TIMSimpleMsgListener {
public:
/**
* 收到 C2C 文本消息
*
* @param msgID 消息唯一标识
* @param sender 发送方信息
* @param text 发送内容
*/
void OnRecvC2CTextMessage(const V2TIMString& msgID, const V2TIMUserFullInfo& sender,
const V2TIMString& text) override {
// 可以解析消息并展示到 UI,比如:
std::cout << "text:" << std::string{text.CString(), text.Size()} << std::endl;
}
// 其他成员 ...
};

// 添加基本消息的事件监听器,注意在移除监听器之前需要保持 simpleMsgListener 的生命期,以免接收不到事件回调
SimpleMsgListener simpleMsgListener;
V2TIMManager::GetInstance()->AddSimpleMsgListener(&simpleMsgListener);

群聊文本消息

接收方使用简单消息监听器接收群聊文本消息,需要以下几步:
1. 调用 addSimpleMsgListener 设置事件监听器。
2. 监听 onRecvGroupTextMessage (Java / Swift / Objective-C / C++) 回调,在其中接收文本消息。
3. 希望停止接收消息,调用 removeSimpleMsgListener 移除监听。该步骤不是必须的,客户可以按照业务需求调用。
代码示例如下:
Java
Swift
Objective-C
C++
// 设置事件监听器
V2TIMManager.getInstance().addSimpleMsgListener(simpleMsgListener);

// 接收群聊文本消息
/**
* 收到群文本消息
*
* @param msgID 消息唯一标识
* @param groupID 群 ID
* @param sender 发送方群成员信息
* @param text 发送内容
*/
public void onRecvGroupTextMessage(String msgID, String groupID, V2TIMGroupMemberInfo sender, String text) {
// 可解析消息并展示到 UI
}
// 设置事件监听器
V2TIMManager.shared.addSimpleMsgListener(listener: self)
/// 接收群聊文本消息
/// @param msgID 消息 Id
/// @param groupID 群组 ID
/// @param sender 发送者信息
/// @param text 文本内容
func onRecvGroupTextMessage(msgID: String, groupID: String, sender: V2TIMGroupMemberInfo, text:String?
{
// 可解析消息并展示到 UI
}
// 设置事件监听器
[[V2TIMManager sharedInstance] addSimpleMsgListener:self];

/// 接收群聊文本消息
/// @param msgID 消息 Id
/// @param groupID 群组 ID
/// @param info 发送者信息
/// @param text 文本内容
- (void)onRecvGroupTextMessage:(NSString *)msgID groupID:(NSString *)groupID sender:(V2TIMGroupMemberInfo *)info text:(NSString *)text {
// 可解析消息并展示到 UI
}
class SimpleMsgListener final : public V2TIMSimpleMsgListener {
public:
/**
* 收到群文本消息
*
* @param msgID 消息唯一标识
* @param groupID 群 ID
* @param sender 发送方群成员信息
* @param text 发送内容
*/
void OnRecvGroupTextMessage(const V2TIMString& msgID, const V2TIMString& groupID,
const V2TIMGroupMemberFullInfo& sender, const V2TIMString& text) override {
// 可以解析消息并展示到 UI,比如:
std::cout << "text:" << std::string{text.CString(), text.Size()} << std::endl;
}
// 其他成员 ...
};

// 添加基本消息的事件监听器,注意在移除监听器之前需要保持 simpleMsgListener 的生命期,以免接收不到事件回调
SimpleMsgListener simpleMsgListener;
V2TIMManager::GetInstance()->AddSimpleMsgListener(&simpleMsgListener);


使用高级消息监听器接收

接收方使用高级消息监听器接收单聊、群聊文本消息,需要以下几步:
1. 调用 addAdvancedMsgListener 设置事件监听器。
2. 监听 onRecvNewMessage (Java / Swift / Objective-C / C++) 回调,在其中接收文本消息。
3. 希望停止接收消息,调用 removeAdvancedMsgListener 移除监听。该步骤不是必须的,客户可以按照业务需求调用。
代码示例如下:
Java
Swift
Objective-C
C++
// 设置事件监听器
V2TIMManager.getMessageManager().addAdvancedMsgListener(advancedMsgListener);

/**
* 收到新消息
* @param msg 消息
*/
public void onRecvNewMessage(V2TIMMessage msg) {
// 解析出 groupID 和 userID
String groupID = msg.getGroupID();
String userID = msg.getUserID();

// 判断当前是单聊还是群聊:
// 如果 groupID 不为空,表示此消息为群聊;如果 userID 不为空,表示此消息为单聊

// 解析出 msg 中的文本消息
if (msg.getElemType() == V2TIMMessage.V2TIM_ELEM_TYPE_TEXT) {
V2TIMTextElem textElem = msg.getTextElem();
String text = textElem.getText();
Log.i("onRecvNewMessage", "text:" + text);
}
}
// 设置事件监听器
V2TIMManager.shared.addAdvancedMsgListener(listener: self)

func onRecvNewMessage(_ msg: V2TIMMessage) {
// 解析出 groupID 和 userID
let groupID = msg.groupID
let userID = msg.userID

// 判断当前是单聊还是群聊
// 如果 groupID 不为空,表示此消息为群聊;如果 userID 不为空,表示此消息为单聊
if !groupID.isEmpty {
print("Received a group message in group: \\(groupID)")
} else if !userID.isEmpty {
print("Received a one-on-one message from user: \\(userID)")
} else {
print("Received a message with no identifiable sender.")
}

// 解析出 msg 中的文本消息
if msg.elemType == .V2TIM_ELEM_TYPE_TEXT {
if let textElem = msg.textElem {
let text = textElem.text
print("onRecvNewMessage, text: \\(text)")
}
} else {
print("Received a non-text message.")
}
}

// 设置事件监听器
[[V2TIMManager sharedInstance] addAdvancedMsgListener:self];

/// 接收消息
/// @param msg 消息对象
- (void)onRecvNewMessage:(V2TIMMessage *)msg {
// 解析出 groupID 和 userID
NSString *groupID = msg.groupID;
NSString *userID = msg.userID;

// 判断当前是单聊还是群聊:
// 如果 groupID 不为空,表示此消息为群聊;如果 userID 不为空,表示此消息为单聊

// 解析出 msg 中的文本消息
if (msg.elemType == V2TIM_ELEM_TYPE_TEXT) {
V2TIMTextElem *textElem = msg.textElem;
NSString *text = textElem.text;
NSLog(@"onRecvNewMessage, text: %@", text);
}
}
class AdvancedMsgListener final : public V2TIMAdvancedMsgListener {
public:
/**
* 收到新消息
*
* @param message 消息
*/
void OnRecvNewMessage(const V2TIMMessage& message) override {
// 解析出 groupID 和 userID
V2TIMString groupID = message.groupID;
V2TIMString userID = message.userID;

// 判断当前是单聊还是群聊:
// 如果 groupID 不为空,表示此消息为群聊;如果 userID 不为空,表示此消息为单聊

// 解析出 message 中的文本消息
if (message.elemList.Size() == 1) {
V2TIMElem* elem = message.elemList[0];
if (elem->elemType == V2TIMElemType::V2TIM_ELEM_TYPE_TEXT) {
// 文本消息
auto textElem = static_cast<V2TIMTextElem*>(elem);
// 消息文本
V2TIMString text = textElem->text;
// 可以解析消息并展示到 UI,比如:
std::cout << "text:" << std::string{text.CString(), text.Size()} << std::endl;
}
}
}
// 其他成员 ...
};

// 添加高级消息的事件监听器,注意在移除监听器之前需要保持 advancedMsgListener 的生命期,以免接收不到事件回调
AdvancedMsgListener advancedMsgListener;
V2TIMManager::GetInstance()->GetMessageManager()->AddAdvancedMsgListener(&advancedMsgListener);

接收自定义消息

使用简单消息监听器接收

单聊自定义消息

接收方使用简单消息监听器接收单聊自定义消息,需要以下几步:
1. 调用 addSimpleMsgListener 设置事件监听器。
2. 监听 onRecvC2CCustomMessage (Java / Swift / Objective-C / C++) 回调,在其中接收单聊自定义消息。
3. 希望停止接收消息,调用 removeSimpleMsgListener 移除监听。该步骤不是必须的,客户可以按照业务需求调用。
代码示例如下:
Java
Swift
Objective-C
C++
/**
* 接收单聊自定义消息
* @param msgID 消息 ID
* @param sender 发送方信息
* @param customData 发送内容
*/
public void onRecvC2CCustomMessage(String msgID, V2TIMUserInfo sender, byte[] customData) {
Log.i("onRecvC2CCustomMessage", "msgID:" + msgID + ", from:" + sender.getNickName() + ", content:" + new String(customData));
}
/// 接收单聊自定义消息
/// @param msgID 消息 ID
/// @param sender 发送者信息
/// @param customData 自定义消息二进制内容
func onRecvC2CCustomMessage(msgID: String, sender: V2TIMUserInfo, customData: Data?) {

}

/// 接收单聊自定义消息
/// @param msgID 消息 ID
/// @param info 发送者信息
/// @param data 自定义消息二进制内容
- (void)onRecvC2CCustomMessage:(NSString *)msgID sender:(V2TIMUserInfo *)info customData:(NSData *)data {
NSLog(@"onRecvC2CCustomMessage, msgID: %@, sender: %@, customData: %@", msgID, info, data);
}
class SimpleMsgListener final : public V2TIMSimpleMsgListener {
public:
/**
* 收到 C2C 自定义(信令)消息
*
* @param msgID 消息唯一标识
* @param sender 发送方信息
* @param customData 发送内容
*/
void OnRecvC2CCustomMessage(const V2TIMString& msgID, const V2TIMUserFullInfo& sender,
const V2TIMBuffer& customData) override {
// 可以解析消息并展示到 UI,比如当 customData 为文本时:
std::cout << "customData:"
<< std::string{reinterpret_cast<const char*>(customData.Data()), customData.Size()}
<< std::endl;
}
// 其他成员 ...
};

// 添加基本消息的事件监听器,注意在移除监听器之前需要保持 simpleMsgListener 的生命期,以免接收不到事件回调
SimpleMsgListener simpleMsgListener;
V2TIMManager::GetInstance()->AddSimpleMsgListener(&simpleMsgListener);

群聊自定义消息

接收方使用简单消息监听器接收群聊自定义消息,需要以下几步:
1. 调用 addSimpleMsgListener 设置事件监听器。
2. 监听 onRecvGroupCustomMessage (Java / Swift / Objective-C / C++) 回调,在其中接收群聊自定义消息。
3. 希望停止接收消息,调用 removeSimpleMsgListener 移除监听。该步骤不是必须的,客户可以按照业务需求调用。
Java
Swift
Objective-C
C++
/**
* 接收群聊自定义消息
* @param msgID 消息 ID
* @param groupID 群 ID
* @param sender 发送方群成员信息
* @param customData 发送内容
*/
public void onRecvGroupCustomMessage(String msgID, String groupID, V2TIMGroupMemberInfo sender, byte[] customData) {
Log.i("onRecvGroupCustomMessage", "msgID:" + msgID + ", groupID:" + groupID + ", from:" + sender.getNickName() + ", content:" + new String(customData));
}
/// 接收群聊自定义消息
/// @param msgID 消息 ID
/// @param groupID 群组 ID
/// @param sender 发送者信息
/// @param customData 自定义消息二进制内容
func onRecvGroupCustomMessage(msgID: String, groupID: String, sender: V2TIMGroupMemberInfo, customData: Data?) {

}

/// 接收群聊自定义消息
/// @param msgID 消息 ID
/// @param groupID 群组 ID
/// @param info 发送者信息
/// @param text 自定义消息二进制内容
- (void)onRecvGroupCustomMessage:(NSString *)msgID groupID:(NSString *)groupID sender:(V2TIMGroupMemberInfo *)info customData:(NSData *)data {
NSLog(@"onRecvGroupCustomMessage, msgID: %@, groupID: %@, sender: %@, customData: %@", msgID, groupID, info, data);
}
class SimpleMsgListener final : public V2TIMSimpleMsgListener {
public:
/**
* 收到群自定义(信令)消息
*
* @param msgID 消息唯一标识
* @param groupID 群 ID
* @param sender 发送方群成员信息
* @param customData 发送内容
*/
void OnRecvGroupCustomMessage(const V2TIMString& msgID, const V2TIMString& groupID,
const V2TIMGroupMemberFullInfo& sender,
const V2TIMBuffer& customData) override {
// 可以解析消息并展示到 UI,比如当 customData 为文本时:
std::cout << "customData:"
<< std::string{reinterpret_cast<const char*>(customData.Data()), customData.Size()}
<< std::endl;
}
// 其他成员 ...
};

// 添加基本消息的事件监听器,注意在移除监听器之前需要保持 simpleMsgListener 的生命期,以免接收不到事件回调
SimpleMsgListener simpleMsgListener;
V2TIMManager::GetInstance()->AddSimpleMsgListener(&simpleMsgListener);


使用高级消息监听器接收

接收方使用高级消息监听器接收单聊、群聊自定义消息,需要以下几步:
1. 调用 addAdvancedMsgListener 设置事件监听器。
2. 监听 onRecvNewMessage (Java / Swift / Objective-C / C++) 回调,在其中接收自定义消息。
3. 希望停止接收消息,调用 removeAdvancedMsgListener 移除监听。该步骤不是必须的,客户可以按照业务需求调用。
代码示例如下:
Java
Swift
Objective-C
C++
// 设置事件监听器
V2TIMManager.getMessageManager().addAdvancedMsgListener(v2TIMAdvancedMsgListener);

// 接收消息
public void onRecvNewMessage(V2TIMMessage msg) {
// 解析出 groupID 和 userID
String groupID = msg.getGroupID();
String userID = msg.getUserID();

// 判断当前是单聊还是群聊:
// 如果 groupID 不为空,表示此消息为群聊;如果 userID 不为空,表示此消息为单聊

// 解析出 msg 中的自定义消息
if (msg.getElemType() == V2TIMMessage.V2TIM_ELEM_TYPE_CUSTOM) {
V2TIMCustomElem customElem = msg.getCustomElem();
String data = new String(customElem.getData());
Log.i("onRecvNewMessage", "customData:" + data);
}
}
// 设置事件监听器
V2TIMManager.shared.addAdvancedMsgListener(listener: self)

/// 接收消息
/// @param msg 消息对象
func onRecvNewMessage(_ msg: V2TIMMessage) {
// 解析出 groupID 和 userID
let groupID = msg.groupID
let userID = msg.userID

// 判断当前是单聊还是群聊
// 如果 groupID 不为空,表示此消息为群聊;如果 userID 不为空,表示此消息为单聊
if !groupID.isEmpty {
print("Received a group message in group: \\(groupID)")
} else if !userID.isEmpty {
print("Received a one-on-one message from user: \\(userID)")
} else {
print("Received a message with no identifiable sender.")
}

// 解析出 msg 中的文本消息
if msg.elemType == .V2TIM_ELEM_TYPE_TEXT {
if let textElem = msg.textElem {
let text = textElem.text
print("onRecvNewMessage, text: \\(text)")
}
} else {
print("Received a non-text message.")
}
}
// 设置事件监听器
[[V2TIMManager sharedInstance] addAdvancedMsgListener:self];

/// 接收消息
/// @param msg 消息对象
- (void)onRecvNewMessage:(V2TIMMessage *)msg {
// 解析出 groupID 和 userID
NSString *groupID = msg.groupID;
NSString *userID = msg.userID;

// 判断当前是单聊还是群聊:
// 如果 groupID 不为空,表示此消息为群聊;如果 userID 不为空,表示此消息为单聊

// 解析出 msg 中的自定义消息
if (msg.elemType == V2TIM_ELEM_TYPE_CUSTOM) {
V2TIMCustomElem *customElem = msg.customElem;
NSData *customData = customElem.data;
NSLog(@"onRecvNewMessage, customData: %@", customData);
}
}
class AdvancedMsgListener final : public V2TIMAdvancedMsgListener {
public:
/**
* 收到新消息
*
* @param message 消息
*/
void OnRecvNewMessage(const V2TIMMessage& message) override {
// 解析出 groupID 和 userID
V2TIMString groupID = message.groupID;
V2TIMString userID = message.userID;

// 判断当前是单聊还是群聊:
// 如果 groupID 不为空,表示此消息为群聊;如果 userID 不为空,表示此消息为单聊

// 解析出 message 中的自定义消息
if (message.elemList.Size() == 1) {
V2TIMElem* elem = message.elemList[0];
if (elem->elemType == V2TIMElemType::V2TIM_ELEM_TYPE_CUSTOM) {
// 自定义消息
auto customElem = static_cast<V2TIMCustomElem*>(elem);
// 自定义消息二进制数据
V2TIMBuffer data = customElem->data;
// 可以解析消息并展示到 UI,比如当 data 为文本时:
std::cout << "data:"
<< std::string{reinterpret_cast<const char*>(data.Data()), data.Size()}
<< std::endl;
}
}
}
// 其他成员 ...
};

// 添加高级消息的事件监听器,注意在移除监听器之前需要保持 advancedMsgListener 的生命期,以免接收不到事件回调
AdvancedMsgListener advancedMsgListener;
V2TIMManager::GetInstance()->GetMessageManager()->AddAdvancedMsgListener(&advancedMsgListener);


接收富媒体消息

接收富媒体消息只能使用高级消息监听器,需要以下几步:
1. 接收方调用 addAdvancedMsgListener 接口设置高级消息监听。
2. 接收方通过监听回调 onRecvNewMessage (Java / Swift / Objective-C / C++) 获取消息 V2TIMMessage。
3. 接收方解析 V2TIMMessage 消息中的 elemType 属性,并根据其类型进行二次解析,获取消息内部 Elem 中的具体内容。
4. 希望停止接收消息,调用 removeAdvancedMsgListener 移除监听。该步骤不是必须的,客户可以按照业务需求调用。

图片消息

一个图片消息会包含三种格式大小的图片,分别为原图、大图、微缩图(SDK 会在发送图片消息的时候自动生成微缩图、大图,客户不需要关心):
大图:将原图等比压缩。压缩后宽、高中较小的一个等于 720 像素。
缩略图:将原图等比压缩。压缩后宽、高中较小的一个等于 198 像素。
接收端收到图片消息后,我们推荐您调用 SDK 的 downloadImage (Java / Swift / Objective-C / C++) 将图片下载到本地,再取出图片渲染到 UI 层。
为了避免重复下载,节省资源,我们推荐您将 V2TIMImage 对象的 uuid 属性值设置到图片的下载路径中,作为图片的标识。
示例代码向您演示如何从 V2TIMMessage 中解析出图片消息内容:
Java
Swift
Objective-C
C++
public void onRecvNewMessage(V2TIMMessage msg) {
if (msg.getElemType() == V2TIMMessage.V2TIM_ELEM_TYPE_IMAGE) {
// 图片消息
V2TIMImageElem v2TIMImageElem = msg.getImageElem();
// 一个图片消息会包含三种格式大小的图片,分别为原图、大图、微缩图(SDK 会在发送图片消息的时候自动生成微缩图、大图,客户不需要关心)
// 大图:是将原图等比压缩,压缩后宽、高中较小的一个等于720像素。
// 缩略图:是将原图等比压缩,压缩后宽、高中较小的一个等于198像素。
List<V2TIMImageElem.V2TIMImage> imageList = v2TIMImageElem.getImageList();
for (V2TIMImageElem.V2TIMImage v2TIMImage : imageList) {
// 图片 ID,内部标识,可用于外部缓存 key
String uuid = v2TIMImage.getUUID();
// 图片类型,三种类型,分别为 V2TIM_IMAGE_TYPE_ORIGIN(原图),V2TIM_IMAGE_TYPE_THUMB(缩略图)和 V2TIM_IMAGE_TYPE_LARGE(大图)
int imageType = v2TIMImage.getType();
// 图片大小(字节)
int size = v2TIMImage.getSize();
// 图片宽度
int width = v2TIMImage.getWidth();
// 图片高度
int height = v2TIMImage.getHeight();
// 图片的原始下载地址
String url = v2TIMImage.getUrl();
// 设置图片下载路径 imagePath,这里可以用 uuid 作为标识,避免重复下载
String imagePath = "/sdcard/im/image/" + "myUserID" + uuid;
File imageFile = new File(imagePath);
// 判断 imagePath 下有没有已经下载过的图片文件
if (!imageFile.exists()) {
// 下载图片
v2TIMImage.downloadImage(imagePath, new V2TIMDownloadCallback() {
@Override
public void onProgress(V2TIMElem.V2ProgressInfo progressInfo) {
// 下载进度回调:已下载大小 v2ProgressInfo.getCurrentSize();总文件大小 v2ProgressInfo.getTotalSize()
}
@Override
public void onError(int code, String desc) {
// 下载失败
}
@Override
public void onSuccess() {
// 下载完成
}
});
} else {
// 图片已存在
}
}
}
}
func onRecvNewMessage(_ msg: V2TIMMessage) {
if msg.elemType == .V2TIM_ELEM_TYPE_IMAGE {
guard let imageElem = msg.imageElem else { return }
// 原图、大图、微缩图列表
let imageList = imageElem.imageList
for timImage in imageList {
// 图片 ID,内部标识,可用于外部缓存 key
let uuid = timImage.uuid
// 图片类型
let type = timImage.type
// 图片大小(字节)
let size = timImage.size
// 图片宽度
let width = timImage.width
// 图片高度
let height = timImage.height
// 图片的原始下载地址
let url = timImage.url
// 设置图片下载路径 imagePath,这里可以用 uuid 作为标识,避免重复下载
let imagePath = NSTemporaryDirectory().appending("testImage\\(timImage.uuid)")
// 判断 imagePath 下有没有已经下载过的图片文件
if !FileManager.default.fileExists(atPath: imagePath) {
// 下载图片
timImage.downloadImage(path: imagePath, progress: { curSize, totalSize in
// 下载进度
print("下载图片进度:curSize:\\(curSize), totalSize: \\(totalSize)")
}, succ: {
// 下载成功
print("下载图片完成")
}, fail: { code, msg in
// 下载失败
print("下载图片失败:code:\\(code), msg: \\(msg)")
})
} else {
// 图片已存在
print("图片已存在:\\(imagePath)")
}
print("图片信息:uuid: \\(uuid), type: \\(type.rawValue), size: \\(size), width: \\(width), height: \\(height)")
}
}
}
- (void)onRecvNewMessage:(V2TIMMessage *)msg {
if (msg.elemType == V2TIM_ELEM_TYPE_IMAGE) {
V2TIMImageElem *imageElem = msg.imageElem;
// 原图、大图、微缩图列表
NSArray<V2TIMImage *> *imageList = imageElem.imageList;
for (V2TIMImage *timImage in imageList) {
// 图片 ID,内部标识,可用于外部缓存 key
NSString *uuid = timImage.uuid;
// 图片类型
V2TIMImageType type = timImage.type;
// 图片大小(字节)
int size = timImage.size;
// 图片宽度
int width = timImage.width;
// 图片高度
int height = timImage.height;
// 图片的原始下载地址
NSString * url = timImage.url;
// 设置图片下载路径 imagePath,这里可以用 uuid 作为标识,避免重复下载
NSString *imagePath = [NSTemporaryDirectory() stringByAppendingPathComponent:[NSString stringWithFormat: @"testImage%@", timImage.uuid]];
// 判断 imagePath 下有没有已经下载过的图片文件
if (![[NSFileManager defaultManager] fileExistsAtPath:imagePath]) {
// 下载图片
[timImage downloadImage:imagePath progress:^(NSInteger curSize, NSInteger totalSize) {
// 下载进度
NSLog(@"下载图片进度:curSize:%lu,totalSize:%lu",curSize,totalSize);
} succ:^{
// 下载成功
NSLog(@"下载图片完成");
} fail:^(int code, NSString *msg) {
// 下载失败
NSLog(@"下载图片失败:code:%d,msg:%@",code,msg);
}];
} else {
// 图片已存在
}
NSLog(@"图片信息:uuid:%@, type:%ld, size:%d, width:%d, height:%d", uuid, (long)type, size, width, height);
}
}
}
class DownloadCallback final : public V2TIMDownloadCallback {
public:
using SuccessCallback = std::function<void()>;
using ErrorCallback = std::function<void(int, const V2TIMString&)>;
using DownLoadProgressCallback = std::function<void(uint64_t, uint64_t)>;

DownloadCallback() = default;
~DownloadCallback() override = default;

void SetCallback(SuccessCallback success_callback, ErrorCallback error_callback,
DownLoadProgressCallback download_progress_callback) {
success_callback_ = std::move(success_callback);
error_callback_ = std::move(error_callback);
download_progress_callback_ = std::move(download_progress_callback);
}
void OnSuccess() override {
if (success_callback_) {
success_callback_();
}
}
void OnError(int error_code, const V2TIMString& error_message) override {
if (error_callback_) {
error_callback_(error_code, error_message);
}
}
void OnDownLoadProgress(uint64_t currentSize, uint64_t totalSize) override {
if (download_progress_callback_) {
download_progress_callback_(currentSize, totalSize);
}
}

private:
SuccessCallback success_callback_;
ErrorCallback error_callback_;
DownLoadProgressCallback download_progress_callback_;
};

class AdvancedMsgListener final : public V2TIMAdvancedMsgListener {
public:
/**
* 收到新消息
*
* @param message 消息
*/
void OnRecvNewMessage(const V2TIMMessage& message) override {
// 解析出 groupID 和 userID
V2TIMString groupID = message.groupID;
V2TIMString userID = message.userID;

// 判断当前是单聊还是群聊:
// 如果 groupID 不为空,表示此消息为群聊;如果 userID 不为空,表示此消息为单聊

// 解析出 message 中的图片消息
if (message.elemList.Size() == 1) {
V2TIMElem* elem = message.elemList[0];
if (elem->elemType == V2TIMElemType::V2TIM_ELEM_TYPE_IMAGE) {
// 图片消息
auto imageElem = static_cast<V2TIMImageElem*>(elem);
// 一个图片消息会包含三种格式大小的图片,分别为原图、大图、微缩图(SDK
// 会在发送图片消息的时候自动生成微缩图、大图,客户不需要关心)
// 大图:是将原图等比压缩,压缩后宽、高中较小的一个等于720像素。
// 缩略图:是将原图等比压缩,压缩后宽、高中较小的一个等于198像素。
V2TIMImageVector imageList = imageElem->imageList;
for (size_t i = 0; i < imageList.Size(); ++i) {
V2TIMImage& image = imageList[i];
/// 图片 ID,内部标识,可用于外部缓存 key
V2TIMString uuid = image.uuid;
/// 图片类型
V2TIMImageType type = image.type;
/// 图片大小(type == V2TIMImageType::V2TIM_IMAGE_TYPE_ORIGIN 有效)
uint64_t size = image.size;
/// 图片宽度
uint32_t width = image.width;
/// 图片高度
uint32_t height = image.height;
// 图片的原始下载地址
V2TIMString url = image.url;
// 设置图片下载路径 path,这里可以用 uuid 作为标识,避免重复下载
std::filesystem::path imagePath = u8"./File/Image/"s + uuid.CString();
// 判断 imagePath 下有没有已经下载过的图片文件
if (!std::filesystem::exists(imagePath)) {
std::filesystem::create_directories(imagePath.parent_path());
auto callback = new DownloadCallback{};
callback->SetCallback(
[=]() {
// 下载完成
delete callback;
},
[=](int error_code, const V2TIMString& error_message) {
// 下载失败
delete callback;
},
[=](uint64_t currentSize, uint64_t totalSize) {
// 下载进度回调:已下载大小 currentSize; 总文件大小 totalSize
});
image.DownloadImage(imagePath.string().c_str(), callback);
} else {
// 图片已存在
}
}
}
}
}
// 其他成员 ...
};

// 添加高级消息的事件监听器,注意在移除监听器之前需要保持 advancedMsgListener 的生命期,以免接收不到事件回调
AdvancedMsgListener advancedMsgListener;
V2TIMManager::GetInstance()->GetMessageManager()->AddAdvancedMsgListener(&advancedMsgListener);


视频消息

接收方收到视频消息后,一般需要在聊天界面显示一个视频预览图,当用户点击消息后,才会触发视频的播放。 所以这里需要两步:
1. 下载视频截图。我们推荐您调用 SDK 的 downloadSnapshot (Java / Swift / Objective-C / C++) 进行下载。
2. 下载视频。我们推荐您调用 SDK 的 downloadVideo (Java / Swift / Objective-C / C++) 进行下载。
为了避免重复下载,节省资源,我们推荐您将 V2TIMVideoElem 对象的 videoUUID 属性值设置到视频的下载路径中,作为视频的标识。
示例代码向您演示如何从 V2TIMMessage 中解析出视频消息内容:
Java
Swift
Objective-C
C++
public void onRecvNewMessage(V2TIMMessage msg) {
if (msg.getElemType() == V2TIMMessage.V2TIM_ELEM_TYPE_VIDEO) {
// 视频消息
V2TIMVideoElem v2TIMVideoElem = msg.getVideoElem();
// 视频截图 ID,内部标识,可用于外部缓存 key
String snapshotUUID = v2TIMVideoElem.getSnapshotUUID();
// 视频截图文件大小
int snapshotSize = v2TIMVideoElem.getSnapshotSize();
// 视频截图宽
int snapshotWidth = v2TIMVideoElem.getSnapshotWidth();
// 视频截图高
int snapshotHeight = v2TIMVideoElem.getSnapshotHeight();
// 视频 ID,内部标识,可用于外部缓存 key
String videoUUID = v2TIMVideoElem.getVideoUUID();
// 视频文件大小
int videoSize = v2TIMVideoElem.getVideoSize();
// 视频时长
int duration = v2TIMVideoElem.getDuration();
// 设置视频截图文件路径,这里可以用 uuid 作为标识,避免重复下载
String snapshotPath = "/sdcard/im/snapshot/" + "myUserID" + snapshotUUID;
File snapshotFile = new File(snapshotPath);
if (!snapshotFile.exists()) {
v2TIMVideoElem.downloadSnapshot(snapshotPath, new V2TIMDownloadCallback() {
@Override
public void onProgress(V2TIMElem.V2ProgressInfo progressInfo) {
// 下载进度回调:已下载大小 v2ProgressInfo.getCurrentSize();总文件大小 v2ProgressInfo.getTotalSize()
}
@Override
public void onError(int code, String desc) {
// 下载失败
}
@Override
public void onSuccess() {
// 下载完成
}
});
} else {
// 文件已存在
}

// 设置视频文件路径,这里可以用 uuid 作为标识,避免重复下载
String videoPath = "/sdcard/im/video/" + "myUserID" + videoUUID;
File videoFile = new File(videoPath);
if (!videoFile.exists()) {
v2TIMVideoElem.downloadVideo(videoPath, new V2TIMDownloadCallback() {
@Override
public void onProgress(V2TIMElem.V2ProgressInfo progressInfo) {
// 下载进度回调:已下载大小 v2ProgressInfo.getCurrentSize();总文件大小 v2ProgressInfo.getTotalSize()
}
@Override
public void onError(int code, String desc) {
// 下载失败
}
@Override
public void onSuccess() {
// 下载完成
}
});
} else {
// 文件已存在
}
}
}
func onRecvNewMessage(_ msg: V2TIMMessage) {
if msg.elemType == .V2TIM_ELEM_TYPE_VIDEO {
let videoElem = msg.videoElem
// 视频截图 ID, 内部标识,可用于外部缓存 key
let snapshotUUID = videoElem?.snapshotUUID
// 视频截图文件大小
let snapshotSize = videoElem?.snapshotSize
// 视频截图宽
let snapshotWidth = videoElem?.snapshotWidth
// 视频截图高
let snapshotHeight = videoElem?.snapshotHeight
// 视频 ID, 内部标识,可用于外部缓存 key
let videoUUID = videoElem?.videoUUID
// 视频文件大小
let videoSize = videoElem?.videoSize
// 视频时长
let duration = videoElem?.duration
// 设置视频截图文件路径,这里可以用 uuid 作为标识,避免重复下载
let snapshotPath = NSTemporaryDirectory().appending("testVideoSnapshot\\(snapshotUUID)")
if !FileManager.default.fileExists(atPath: snapshotPath) {
// 下载视频截图
videoElem?.downloadSnapshot(path: snapshotPath, progress: { curSize, totalSize in
// 下载进度
print("下载视频截图进度:curSize:\\(curSize), totalSize: \\(totalSize)")
}, succ: {
// 下载成功
print("下载视频截图完成")
}, fail: { code, msg in
// 下载失败
print("下载视频截图失败:code:\\(code), msg: \\(msg)")
})
} else {
// 视频截图已存在
}
print("视频截图信息:snapshotUUID: \\(snapshotUUID), snapshotSize: \\(snapshotSize), snapshotWidth: \\(snapshotWidth), snapshotHeight: \\(snapshotHeight), snapshotPath: \\(snapshotPath)")

// 设置视频文件路径,这里可以用 uuid 作为标识,避免重复下载
let videoPath = NSTemporaryDirectory().appending("testVideo\\(videoUUID)")
if !FileManager.default.fileExists(atPath: videoPath) {
// 下载视频
videoElem?.downloadVideo(path: videoPath, progress: { curSize, totalSize in
// 下载进度
print("下载视频进度:curSize:\\(curSize), totalSize: \\(totalSize)")
}, succ: {
// 下载成功
print("下载视频完成")
}, fail: { code, msg in
// 下载失败
print("下载视频失败:code:\\(code), msg: \\(msg)")
})
} else {
// 视频已存在
}
print("视频信息:videoUUID: \\(videoUUID), videoSize: \\(videoSize), duration: \\(duration), videoPath: \\(videoPath)")
}
}
- (void)onRecvNewMessage:(V2TIMMessage *)msg {
if (msg.elemType == V2TIM_ELEM_TYPE_VIDEO) {
V2TIMVideoElem *videoElem = msg.videoElem;
// 视频截图 ID,内部标识,可用于外部缓存 key
NSString *snapshotUUID = videoElem.snapshotUUID;
// 视频截图文件大小
int snapshotSize = videoElem.snapshotSize;
// 视频截图宽
int snapshotWidth = videoElem.snapshotWidth;
// 视频截图高
int snapshotHeight = videoElem.snapshotHeight;
// 视频 ID,内部标识,可用于外部缓存 key
NSString *videoUUID = videoElem.videoUUID;
// 视频文件大小
int videoSize = videoElem.videoSize;
// 视频时长
int duration = videoElem.duration;
// 设置视频截图文件路径,这里可以用 uuid 作为标识,避免重复下载
NSString *snapshotPath = [NSTemporaryDirectory() stringByAppendingPathComponent:[NSString stringWithFormat: @"testVideoSnapshot%@",snapshotUUID]];
if (![[NSFileManager defaultManager] fileExistsAtPath:snapshotPath]) {
// 下载视频截图
[videoElem downloadSnapshot:snapshotPath progress:^(NSInteger curSize, NSInteger totalSize) {
// 下载进度
NSLog(@"%@", [NSString stringWithFormat:@"下载视频截图进度:curSize:%lu,totalSize:%lu",curSize,totalSize]);
} succ:^{
// 下载成功
NSLog(@"下载视频截图完成");
} fail:^(int code, NSString *msg) {
// 下载失败
NSLog(@"%@", [NSString stringWithFormat:@"下载视频截图失败:code:%d,msg:%@",code,msg]);
}];
} else {
// 视频截图已存在
}
NSLog(@"视频截图信息:snapshotUUID:%@, snapshotSize:%d, snapshotWidth:%d, snapshotWidth:%d, snapshotPath:%@", snapshotUUID, snapshotSize, snapshotWidth, snapshotHeight, snapshotPath);

// 设置视频文件路径,这里可以用 uuid 作为标识,避免重复下载
NSString *videoPath = [NSTemporaryDirectory() stringByAppendingPathComponent:[NSString stringWithFormat: @"testVideo%@",videoUUID]];
if (![[NSFileManager defaultManager] fileExistsAtPath:videoPath]) {
// 下载视频
[videoElem downloadVideo:videoPath progress:^(NSInteger curSize, NSInteger totalSize) {
// 下载进度
NSLog(@"%@", [NSString stringWithFormat:@"下载视频进度:curSize:%lu,totalSize:%lu",curSize,totalSize]);
} succ:^{
// 下载成功
NSLog(@"下载视频完成");
} fail:^(int code, NSString *msg) {
// 下载失败
NSLog(@"%@", [NSString stringWithFormat:@"下载视频失败:code:%d,msg:%@",code,msg]);
}];
} else {
// 视频已存在
}
NSLog(@"视频信息:videoUUID:%@, videoSize:%d, duration:%d, videoPath:%@", videoUUID, videoSize, duration, videoPath);
}
}
class DownloadCallback final : public V2TIMDownloadCallback {
public:
using SuccessCallback = std::function<void()>;
using ErrorCallback = std::function<void(int, const V2TIMString&)>;
using DownLoadProgressCallback = std::function<void(uint64_t, uint64_t)>;

DownloadCallback() = default;
~DownloadCallback() override = default;

void SetCallback(SuccessCallback success_callback, ErrorCallback error_callback,
DownLoadProgressCallback download_progress_callback) {
success_callback_ = std::move(success_callback);
error_callback_ = std::move(error_callback);
download_progress_callback_ = std::move(download_progress_callback);
}
void OnSuccess() override {
if (success_callback_) {
success_callback_();
}
}
void OnError(int error_code, const V2TIMString& error_message) override {
if (error_callback_) {
error_callback_(error_code, error_message);
}
}
void OnDownLoadProgress(uint64_t currentSize, uint64_t totalSize) override {
if (download_progress_callback_) {
download_progress_callback_(currentSize, totalSize);
}
}

private:
SuccessCallback success_callback_;
ErrorCallback error_callback_;
DownLoadProgressCallback download_progress_callback_;
};

class AdvancedMsgListener final : public V2TIMAdvancedMsgListener {
public:
/**
* 收到新消息
*
* @param message 消息
*/
void OnRecvNewMessage(const V2TIMMessage& message) override {
// 解析出 groupID 和 userID
V2TIMString groupID = message.groupID;
V2TIMString userID = message.userID;

// 判断当前是单聊还是群聊:
// 如果 groupID 不为空,表示此消息为群聊;如果 userID 不为空,表示此消息为单聊

// 解析出 message 中的视频消息
if (message.elemList.Size() == 1) {
V2TIMElem* elem = message.elemList[0];
if (elem->elemType == V2TIMElemType::V2TIM_ELEM_TYPE_VIDEO) {
// 视频消息
auto videoElem = static_cast<V2TIMVideoElem*>(elem);
// 视频 ID,内部标识,可用于外部缓存 key
V2TIMString videoUUID = videoElem->videoUUID;
// 视频大小
uint64_t videoSize = videoElem->videoSize;
// 视频类型
V2TIMString videoType = videoElem->videoType;
// 视频时长
uint32_t duration = videoElem->duration;
// 截图 ID,内部标识,可用于外部缓存 key
V2TIMString snapshotUUID = videoElem->snapshotUUID;
// 截图 size
uint64_t snapshotSize = videoElem->snapshotSize;
// 截图宽
uint32_t snapshotWidth = videoElem->snapshotWidth;
// 截图高
uint32_t snapshotHeight = videoElem->snapshotHeight;

// 设置视频文件路径,这里可以用 uuid 作为标识,避免重复下载
std::filesystem::path videoPath = u8"./File/Video/"s + videoUUID.CString();
// 判断 videoPath 下有没有已经下载过的视频文件
if (!std::filesystem::exists(videoPath)) {
std::filesystem::create_directories(videoPath.parent_path());
auto callback = new DownloadCallback{};
callback->SetCallback(
[=]() {
// 下载完成
delete callback;
},
[=](int error_code, const V2TIMString& error_message) {
// 下载失败
delete callback;
},
[=](uint64_t currentSize, uint64_t totalSize) {
// 下载进度回调:已下载大小 currentSize; 总文件大小 totalSize
});
videoElem->DownloadVideo(videoPath.string().c_str(), callback);
} else {
// 视频文件已存在
}

// 设置视频截图文件路径,这里可以用 uuid 作为标识,避免重复下载
std::filesystem::path snapshotPath = u8"./File/Snapshot/"s + snapshotUUID.CString();
// 判断 snapshotPath 下有没有已经下载过的视频截图文件
if (!std::filesystem::exists(snapshotPath)) {
std::filesystem::create_directories(snapshotPath.parent_path());
auto callback = new DownloadCallback{};
callback->SetCallback(
[=]() {
// 下载完成
delete callback;
},
[=](int error_code, const V2TIMString& error_message) {
// 下载失败
delete callback;
},
[=](uint64_t currentSize, uint64_t totalSize) {
// 下载进度回调:已下载大小 currentSize; 总文件大小 totalSize
});
videoElem->DownloadSnapshot(snapshotPath.string().c_str(), callback);
} else {
//视频截图文件已存在
}
}
}
}
// 其他成员 ...
};

// 添加高级消息的事件监听器,注意在移除监听器之前需要保持 advancedMsgListener 的生命期,以免接收不到事件回调
AdvancedMsgListener advancedMsgListener;
V2TIMManager::GetInstance()->GetMessageManager()->AddAdvancedMsgListener(&advancedMsgListener);


语音消息

接收端收到语音消息后,我们推荐您调用 SDK 的 downloadSound (Java / Swift / Objective-C / C++) 将语音下载到本地,再获取本地语音文件播放。
为了避免重复下载,节省资源,我们推荐您将 V2TIMSoundElem 对象的 uuid 属性值设置到语音的下载路径中,作为语音的标识。
示例代码向您演示如何从 V2TIMMessage 中解析出语音消息内容:
Java
Swift
Objective-C
C++
public void onRecvNewMessage(V2TIMMessage msg) {
if (msg.getElemType() == V2TIMMessage.V2TIM_ELEM_TYPE_SOUND) {
// 语音消息
V2TIMSoundElem v2TIMSoundElem = msg.getSoundElem();
// 语音 ID,内部标识,可用于外部缓存 key
String uuid = v2TIMSoundElem.getUUID();
// 语音文件大小
int dataSize = v2TIMSoundElem.getDataSize();
// 语音时长
int duration = v2TIMSoundElem.getDuration();
// 设置语音文件路径 soundPath,这里可以用 uuid 作为标识,避免重复下载
String soundPath = "/sdcard/im/sound/" + "myUserID" + uuid;
File imageFile = new File(soundPath);
// 判断 soundPath 下有没有已经下载过的语音文件
if (!imageFile.exists()) {
v2TIMSoundElem.downloadSound(soundPath, new V2TIMDownloadCallback() {
@Override
public void onProgress(V2TIMElem.V2ProgressInfo progressInfo) {
// 下载进度回调:已下载大小 v2ProgressInfo.getCurrentSize();总文件大小 v2ProgressInfo.getTotalSize()
}
@Override
public void onError(int code, String desc) {
// 下载失败
}
@Override
public void onSuccess() {
// 下载完成
}
});
} else {
// 文件已存在
}
}
}
func onRecvNewMessage(_ msg: V2TIMMessage) {
if msg.elemType == .V2TIM_ELEM_TYPE_SOUND {
guard let soundElem = msg.soundElem else { return }
// 语音 ID,内部标识,可用于外部缓存 key
let uuid = soundElem.uuid
// 语音文件大小
let dataSize = soundElem.dataSize
// 语音时长
let duration = soundElem.duration
// 设置语音文件路径 soundPath,这里可以用 uuid 作为标识,避免重复下载
let soundPath = NSTemporaryDirectory().appending("testSound\\(uuid)")
// 判断 soundPath 下有没有已经下载过的语音文件
if !FileManager.default.fileExists(atPath: soundPath) {
// 下载语音
soundElem.downloadSound(path: soundPath, progress: { curSize, totalSize in
// 下载进度
print("下载语音进度:curSize:\\(curSize), totalSize: \\(totalSize)")
}, succ: {
// 下载成功
print("下载语音完成")
}, fail: { code, msg in
// 下载失败
print("下载语音失败:code:\\(code), msg: \\(msg)")
})
} else {
// 语音已存在
print("语音已存在:\\(soundPath)")
}
print("语音信息:uuid: \\(uuid), dataSize: \\(dataSize), duration: \\(duration), soundPath: \\(soundPath)")
}
}
- (void)onRecvNewMessage:(V2TIMMessage *)msg {
if (msg.elemType == V2TIM_ELEM_TYPE_SOUND) {
V2TIMSoundElem *soundElem = msg.soundElem;
// 语音 ID,内部标识,可用于外部缓存 key
NSString *uuid = soundElem.uuid;
// 语音文件大小
int dataSize = soundElem.dataSize;
// 语音时长
int duration = soundElem.duration;
// 设置语音文件路径 soundPath,这里可以用 uuid 作为标识,避免重复下载
NSString *soundPath = [NSTemporaryDirectory() stringByAppendingPathComponent:[NSString stringWithFormat: @"testSound%@",uuid]];
// 判断 soundPath 下有没有已经下载过的语音文件
if (![[NSFileManager defaultManager] fileExistsAtPath:soundPath]) {
// 下载语音
[soundElem downloadSound:soundPath progress:^(NSInteger curSize, NSInteger totalSize) {
// 下载进度
NSLog(@"下载语音进度:curSize:%lu,totalSize:%lu",curSize,totalSize);
} succ:^{
// 下载成功
NSLog(@"下载语音完成");
} fail:^(int code, NSString *msg) {
// 下载失败
NSLog(@"下载语音失败:code:%d,msg:%@",code,msg);
}];
} else {
// 语音已存在
}
NSLog(@"语音信息:uuid:%@, dataSize:%d, duration:%d, soundPath:%@", uuid, dataSize, duration, soundPath);
}
}
class DownloadCallback final : public V2TIMDownloadCallback {
public:
using SuccessCallback = std::function<void()>;
using ErrorCallback = std::function<void(int, const V2TIMString&)>;
using DownLoadProgressCallback = std::function<void(uint64_t, uint64_t)>;

DownloadCallback() = default;
~DownloadCallback() override = default;

void SetCallback(SuccessCallback success_callback, ErrorCallback error_callback,
DownLoadProgressCallback download_progress_callback) {
success_callback_ = std::move(success_callback);
error_callback_ = std::move(error_callback);
download_progress_callback_ = std::move(download_progress_callback);
}
void OnSuccess() override {
if (success_callback_) {
success_callback_();
}
}
void OnError(int error_code, const V2TIMString& error_message) override {
if (error_callback_) {
error_callback_(error_code, error_message);
}
}
void OnDownLoadProgress(uint64_t currentSize, uint64_t totalSize) override {
if (download_progress_callback_) {
download_progress_callback_(currentSize, totalSize);
}
}

private:
SuccessCallback success_callback_;
ErrorCallback error_callback_;
DownLoadProgressCallback download_progress_callback_;
};

class AdvancedMsgListener final : public V2TIMAdvancedMsgListener {
public:
/**
* 收到新消息
*
* @param message 消息
*/
void OnRecvNewMessage(const V2TIMMessage& message) override {
// 解析出 groupID 和 userID
V2TIMString groupID = message.groupID;
V2TIMString userID = message.userID;

// 判断当前是单聊还是群聊:
// 如果 groupID 不为空,表示此消息为群聊;如果 userID 不为空,表示此消息为单聊

// 解析出 message 中的语音消息
if (message.elemList.Size() == 1) {
V2TIMElem* elem = message.elemList[0];
if (elem->elemType == V2TIMElemType::V2TIM_ELEM_TYPE_SOUND) {
// 语音消息
auto soundElem = static_cast<V2TIMSoundElem*>(elem);
// 语音消息内部 ID
V2TIMString uuid = soundElem->uuid;
// 语音数据大小
uint64_t dataSize = soundElem->dataSize;
// 语音长度(秒)
uint32_t duration = soundElem->duration;
// 设置语音文件路径,这里可以用 uuid 作为标识,避免重复下载
std::filesystem::path soundPath = u8"./File/Sound/"s + uuid.CString();
// 判断 soundPath 下有没有已经下载过的语音文件
if (!std::filesystem::exists(soundPath)) {
std::filesystem::create_directories(soundPath.parent_path());
auto callback = new DownloadCallback{};
callback->SetCallback(
[=]() {
// 下载完成
delete callback;
},
[=](int error_code, const V2TIMString& error_message) {
// 下载失败
delete callback;
},
[=](uint64_t currentSize, uint64_t totalSize) {
// 下载进度回调:已下载大小 currentSize; 总文件大小 totalSize
});
soundElem->DownloadSound(soundPath.string().c_str(), callback);
} else {
// 语音文件已存在
}
}
}
}
// 其他成员 ...
};

// 添加高级消息的事件监听器,注意在移除监听器之前需要保持 advancedMsgListener 的生命期,以免接收不到事件回调
AdvancedMsgListener advancedMsgListener;
V2TIMManager::GetInstance()->GetMessageManager()->AddAdvancedMsgListener(&advancedMsgListener);


文件消息

接收端收到文件消息后,我们推荐您调用 SDK 的 downloadFile (Java / Swift / Objective-C / C++) 将文件下载到本地,再获取本地文件展示。
为了避免重复下载,节省资源,我们推荐您将 V2TIMFileElem 对象的 uuid 属性值设置到文件的下载路径中,作为文件的标识。
示例代码向您演示如何从 V2TIMMessage 中解析出文件消息内容:
Java
Swift
Objective-C
C++
public void onRecvNewMessage(V2TIMMessage msg) {
if (msg.getElemType() == V2TIMMessage.V2TIM_ELEM_TYPE_FILE) {
// 文件消息
V2TIMFileElem v2TIMFileElem = msg.getFileElem();
// 文件 ID,内部标识,可用于外部缓存 key
String uuid = v2TIMFileElem.getUUID();
// 文件名称
String fileName = v2TIMFileElem.getFileName();
// 文件大小
int fileSize = v2TIMFileElem.getFileSize();
// 设置文件路径,这里可以用 uuid 作为标识,避免重复下载
String filePath = "/sdcard/im/file/" + "myUserID" + uuid;
File file = new File(filePath);
if (!file.exists()) {
v2TIMFileElem.downloadFile(filePath, new V2TIMDownloadCallback() {
@Override
public void onProgress(V2TIMElem.V2ProgressInfo progressInfo) {
// 下载进度回调:已下载大小 v2ProgressInfo.getCurrentSize();总文件大小 v2ProgressInfo.getTotalSize()
}
@Override
public void onError(int code, String desc) {
// 下载失败
}
@Override
public void onSuccess() {
// 下载完成
}
});
} else {
// 文件已存在
}
}
}
func onRecvNewMessage(_ msg: V2TIMMessage) {
if msg.elemType == .V2TIM_ELEM_TYPE_FILE {
guard let fileElem = msg.fileElem else { return }
// 文件 ID,内部标识,可用于外部缓存 key
let uuid = fileElem.uuid
// 文件名称
let filename = fileElem.filename
// 文件大小
let fileSize = fileElem.fileSize
// 设置文件路径,这里可以用 uuid 作为标识,避免重复下载
let filePath = NSTemporaryDirectory().appending("testFile\\(uuid)")
if !FileManager.default.fileExists(atPath: filePath) {
// 下载文件
fileElem.downloadFile(path: filePath, progress: { curSize, totalSize in
// 下载进度
print("下载文件进度:curSize:\\(curSize), totalSize: \\(totalSize)")
}, succ: {
// 下载成功
print("下载文件完成")
}, fail: { code, msg in
// 下载失败
print("下载文件失败:code:\\(code), msg: \\(msg)")
})
} else {
// 文件已存在
print("文件已存在:\\(filePath)")
}
print("文件信息:uuid: \\(uuid), filename: \\(filename), fileSize: \\(fileSize), filePath: \\(filePath)")
}
}
- (void)onRecvNewMessage:(V2TIMMessage *)msg {
if (msg.elemType == V2TIM_ELEM_TYPE_FILE) {
V2TIMFileElem *fileElem = msg.fileElem;
// 文件 ID,内部标识,可用于外部缓存 key
NSString *uuid = fileElem.uuid;
// 文件名称
NSString *filename = fileElem.filename;
// 文件大小
int fileSize = fileElem.fileSize;
// 设置文件路径,这里可以用 uuid 作为标识,避免重复下载
NSString *filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:[NSString stringWithFormat: @"testFile%@",uuid]];
if (![[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
// 下载文件
[fileElem downloadFile:filePath progress:^(NSInteger curSize, NSInteger totalSize) {
// 下载进度
NSLog(@"%@", [NSString stringWithFormat:@"下载文件进度:curSize:%lu,totalSize:%lu",curSize,totalSize]);
} succ:^{
// 下载成功
NSLog(@"下载文件完成");
} fail:^(int code, NSString *msg) {
// 下载失败
NSLog(@"%@", [NSString stringWithFormat:@"下载文件失败:code:%d,msg:%@",code,msg]);
}];
} else {
// 文件已存在
}
NSLog(@"文件信息:uuid:%@, filename:%@, fileSize:%d, filePath:%@", uuid, filename, fileSize, filePath);
}
}
class DownloadCallback final : public V2TIMDownloadCallback {
public:
using SuccessCallback = std::function<void()>;
using ErrorCallback = std::function<void(int, const V2TIMString&)>;
using DownLoadProgressCallback = std::function<void(uint64_t, uint64_t)>;

DownloadCallback() = default;
~DownloadCallback() override = default;

void SetCallback(SuccessCallback success_callback, ErrorCallback error_callback,
DownLoadProgressCallback download_progress_callback) {
success_callback_ = std::move(success_callback);
error_callback_ = std::move(error_callback);
download_progress_callback_ = std::move(download_progress_callback);
}
void OnSuccess() override {
if (success_callback_) {
success_callback_();
}
}
void OnError(int error_code, const V2TIMString& error_message) override {
if (error_callback_) {
error_callback_(error_code, error_message);
}
}
void OnDownLoadProgress(uint64_t currentSize, uint64_t totalSize) override {
if (download_progress_callback_) {
download_progress_callback_(currentSize, totalSize);
}
}

private:
SuccessCallback success_callback_;
ErrorCallback error_callback_;
DownLoadProgressCallback download_progress_callback_;
};

class AdvancedMsgListener final : public V2TIMAdvancedMsgListener {
public:
/**
* 收到新消息
*
* @param message 消息
*/
void OnRecvNewMessage(const V2TIMMessage& message) override {
// 解析出 groupID 和 userID
V2TIMString groupID = message.groupID;
V2TIMString userID = message.userID;

// 判断当前是单聊还是群聊:
// 如果 groupID 不为空,表示此消息为群聊;如果 userID 不为空,表示此消息为单聊

// 解析出 message 中的文件消息
if (message.elemList.Size() == 1) {
V2TIMElem* elem = message.elemList[0];
if (elem->elemType == V2TIMElemType::V2TIM_ELEM_TYPE_FILE) {
// 文件消息
auto fileElem = static_cast<V2TIMFileElem*>(elem);
// 文件 ID,内部标识,可用于外部缓存 key
V2TIMString uuid = fileElem->uuid;
// 文件显示名称
V2TIMString filename = fileElem->filename;
// 文件大小
uint64_t fileSize = fileElem->fileSize;
// 设置文件路径,这里可以用 uuid 作为标识,避免重复下载
std::filesystem::path filePath = u8"./File/File/"s + uuid.CString();
// 判断 filePath 下有没有已经下载过的文件
if (!std::filesystem::exists(filePath)) {
std::filesystem::create_directories(filePath.parent_path());
auto callback = new DownloadCallback{};
callback->SetCallback(
[=]() {
// 下载完成
delete callback;
},
[=](int error_code, const V2TIMString& error_message) {
// 下载失败
delete callback;
},
[=](uint64_t currentSize, uint64_t totalSize) {
// 下载进度回调:已下载大小 currentSize; 总文件大小 totalSize
});
fileElem->DownloadFile(filePath.string().c_str(), callback);
} else {
// 文件已存在
}
}
}
}
// 其他成员 ...
};

// 添加高级消息的事件监听器,注意在移除监听器之前需要保持 advancedMsgListener 的生命期,以免接收不到事件回调
AdvancedMsgListener advancedMsgListener;
V2TIMManager::GetInstance()->GetMessageManager()->AddAdvancedMsgListener(&advancedMsgListener);


地理位置消息

接收到地理位置消息后,接收放可直接从 V2TIMLocationElem 中解析出经纬度信息。 示例代码向您演示如何从 V2TIMMessage 中解析出地理位置消息内容:
Java
Swift
Objective-C
C++
public void onRecvNewMessage(V2TIMMessage msg) {
if (msg.getElemType() == V2TIMMessage.V2TIM_ELEM_TYPE_LOCATION) {
// 地理位置消息
V2TIMLocationElem v2TIMLocationElem = msg.getLocationElem();
// 地理位置信息描述
String desc = v2TIMLocationElem.getDesc();
// 经度
double longitude = v2TIMLocationElem.getLongitude();
// 纬度
double latitude = v2TIMLocationElem.getLatitude();
}
}
func onRecvNewMessage(_ msg: V2TIMMessage) {
if msg.elemType == .V2TIM_ELEM_TYPE_LOCATION {
let locationElem = msg.locationElem
// 地理位置信息描述
let desc = locationElem?.desc
// 经度
let longitude = locationElem?.longitude
// 纬度
let latitude = locationElem?.latitude
print("地理位置信息:desc:\\(desc), longitude:\\(longitude), latitude:\\(latitude)")
}
}
- (void)onRecvNewMessage:(V2TIMMessage *)msg {
if (msg.elemType == V2TIM_ELEM_TYPE_LOCATION) {
V2TIMLocationElem *locationElem = msg.locationElem;
// 地理位置信息描述
NSString *desc = locationElem.desc;
// 经度
double longitude = locationElem.longitude;
// 纬度
double latitude = locationElem.latitude;
NSLog(@"地理位置信息:desc:%@, longitude:%f, latitude:%f", desc, longitude, latitude);
}
}
class AdvancedMsgListener final : public V2TIMAdvancedMsgListener {
public:
/**
* 收到新消息
*
* @param message 消息
*/
void OnRecvNewMessage(const V2TIMMessage& message) override {
// 解析出 groupID 和 userID
V2TIMString groupID = message.groupID;
V2TIMString userID = message.userID;

// 判断当前是单聊还是群聊:
// 如果 groupID 不为空,表示此消息为群聊;如果 userID 不为空,表示此消息为单聊

// 解析出 message 中的地理位置消息
if (message.elemList.Size() == 1) {
V2TIMElem* elem = message.elemList[0];
if (elem->elemType == V2TIMElemType::V2TIM_ELEM_TYPE_LOCATION) {
// 地理位置消息
auto locationElem = static_cast<V2TIMLocationElem*>(elem);
// 地理位置描述信息
V2TIMString desc = locationElem->desc;
// 经度,发送消息时设置
double longitude = locationElem->longitude;
// 纬度,发送消息时设置
double latitude = locationElem->latitude;
}
}
}
// 其他成员 ...
};

// 添加高级消息的事件监听器,注意在移除监听器之前需要保持 advancedMsgListener 的生命期,以免接收不到事件回调
AdvancedMsgListener advancedMsgListener;
V2TIMManager::GetInstance()->GetMessageManager()->AddAdvancedMsgListener(&advancedMsgListener);


表情消息

SDK 仅为表情消息提供消息透传的通道,消息内容字段参考 V2TIMFaceElem (Java / SwiftObjective-C / C++) 定义。其中 index 和 data 的内容由客户自定义。
例如发送方可设置 index = 1, data = "x12345",表示 “微笑” 表情。 接收方收到表情消息后解析出 1 和 "x12345",按照预设的规则将其展示为 “微笑” 表情。
示例代码向您演示如何从 V2TIMMessage 中解析出表情消息内容:
Java
Swift
Objective-C
C++
public void onRecvNewMessage(V2TIMMessage msg) {
if (msg.getElemType() == V2TIMMessage.V2TIM_ELEM_TYPE_FACE) {
// 表情消息
V2TIMFaceElem v2TIMFaceElem = msg.getFaceElem();
// 表情所在的位置
int index = v2TIMFaceElem.getIndex();
// 表情自定义数据
byte[] data = v2TIMFaceElem.getData();
}
}
func onRecvNewMessage(_ msg: V2TIMMessage) {
guard msg.elemType == .V2TIM_ELEM_TYPE_FACE, let faceElem = msg.faceElem else {
return
}
// 表情所在的位置
let index = faceElem.index
// 表情自定义数据
guard let data = faceElem.data else {
print("表情数据为空")
return
}
print("表情信息:index: \\(index), data: \\(data)")
}
- (void)onRecvNewMessage:(V2TIMMessage *)msg {
if (msg.elemType == V2TIM_ELEM_TYPE_FACE) {
V2TIMFaceElem *faceElem = msg.faceElem;
// 表情所在的位置
int index = faceElem.index;
// 表情自定义数据
NSData *data = faceElem.data;
NSLog(@"表情信息:index: %d, data: %@", index, data);
}
}
class AdvancedMsgListener final : public V2TIMAdvancedMsgListener {
public:
/**
* 收到新消息
*
* @param message 消息
*/
void OnRecvNewMessage(const V2TIMMessage& message) override {
// 解析出 groupID 和 userID
V2TIMString groupID = message.groupID;
V2TIMString userID = message.userID;

// 判断当前是单聊还是群聊:
// 如果 groupID 不为空,表示此消息为群聊;如果 userID 不为空,表示此消息为单聊

// 解析出 message 中的表情消息
if (message.elemList.Size() == 1) {
V2TIMElem* elem = message.elemList[0];
if (elem->elemType == V2TIMElemType::V2TIM_ELEM_TYPE_FACE) {
// 表情消息
auto faceElem = static_cast<V2TIMFaceElem*>(elem);
// 表情所在的位置
uint32_t index = faceElem->index;
// 表情自定义数据
V2TIMBuffer data = faceElem->data;
}
}
}
// 其他成员 ...
};

// 添加高级消息的事件监听器,注意在移除监听器之前需要保持 advancedMsgListener 的生命期,以免接收不到事件回调
AdvancedMsgListener advancedMsgListener;
V2TIMManager::GetInstance()->GetMessageManager()->AddAdvancedMsgListener(&advancedMsgListener);

接收广播消息

您只能调用 REST API 直播群广播消息 向同一个 AppID 下面所有的直播群发送广播消息,所有直播群在线群成员均能收到此消息。
SDK 不能发送广播消息,只能接收来自于 REST API 的消息。
接收消息依然是在上文所述的 onRecvNewMessage 通知里。您可以在收到消息后,根据消息的 isBroadcastMessage 判断该消息是否是广播消息。
注意:
1. 该功能仅对专业版、专业版plus、企业版客户开放,购买专业版、专业版plus、企业版后可使用。
2. 仅增强版 SDK 6.5.2803 及以上版本支持。
3. 仅直播群支持广播消息,其他类型群组暂不支持。
示例代码如下:
Java
Swift
Objective-C
C++
public void onRecvNewMessage(V2TIMMessage msg) {
if (msg.isBroadcastMessage) {
// 收到了广播消息
}
}
func onRecvNewMessage(_ msg: V2TIMMessage) {
if msg.isBroadcastMessage {
// 收到了广播消息
}
}
- (void)onRecvNewMessage:(V2TIMMessage *)msg {
if (msg.isBroadcastMessage) {
// 收到了广播消息
}
}
class AdvancedMsgListener final : public V2TIMAdvancedMsgListener {
public:
/**
* 收到新消息
*
* @param message 消息
*/
void OnRecvNewMessage(const V2TIMMessage& message) override {
if (message.isBroadcastMessage) {
// 收到了广播消息
}
}
// 其他成员 ...
};

// 添加高级消息的事件监听器,注意在移除监听器之前需要保持 advancedMsgListener 的生命期,以免接收不到事件回调
AdvancedMsgListener advancedMsgListener;
V2TIMManager::GetInstance()->GetMessageManager()->AddAdvancedMsgListener(&advancedMsgListener);

接收多个 Elem 的消息

1. 通过 Message 对象正常解析出第一个 Elem 对象。
2. 通过第一个 Elem 对象的 nextElem 方法获取下一个 Elem 对象,如果下一个 Elem 对象存在,会返回 Elem 对象实例,如果不存在,会返回 nil/null。
示例代码如下:
Java
Swift
Objective-C
C++
@Override
public void onRecvNewMessage(V2TIMMessage msg) {
// 查看第一个 Elem
int elemType = msg.getElemType();
if (elemType == V2TIMMessage.V2TIM_ELEM_TYPE_TEXT) {
// 文本消息
V2TIMTextElem v2TIMTextElem = msg.getTextElem();
String text = v2TIMTextElem.getText();
// 查看 v2TIMTextElem 后面还有没有更多 elem
V2TIMElem elem = v2TIMTextElem.getNextElem();
while (elem != null) {
// 判断 elem 类型,以 V2TIMCustomElem 为例
if (elem instanceof V2TIMCustomElem) {
V2TIMCustomElem customElem = (V2TIMCustomElem) elem;
byte[] data = customElem.getData();
}
// 继续查看当前 elem 后面还有没更多 elem
elem = elem.getNextElem();
}
// elem 如果为 null,表示所有 elem 都已经解析完
}
}
func onRecvNewMessage(_ msg: V2TIMMessage) {
// 查看第一个 Elem
if msg.elemType == .V2TIM_ELEM_TYPE_TEXT {
let textElem = msg.textElem
let text = textElem?.text
print("文本信息 : \\(text)")
// 查看 textElem 后面还有没更多 Elem
var elem: V2TIMElem? = textElem?.getNextElem()
while elem != nil {
// 判断 elem 类型
if let customElem = elem as? V2TIMCustomElem {
let customData = customElem.data
print("自定义信息 : \\(customData)")
}
// 继续查看当前 elem 后面还有没更多 elem
elem = elem?.getNextElem()
}
// elem 如果为 nil,表示所有 elem 都已经解析完
}
}
- (void)onRecvNewMessage:(V2TIMMessage *)msg {
// 查看第一个 Elem
if (msg.elemType == V2TIM_ELEM_TYPE_TEXT) {
V2TIMTextElem *textElem = msg.textElem;
NSString *text = textElem.text;
NSLog(@"文本信息 : %@", text);
// 查看 textElem 后面还有没更多 Elem
V2TIMElem *elem = textElem.nextElem;
while (elem != nil) {
// 判断 elem 类型
if ([elem isKindOfClass:[V2TIMCustomElem class]]) {
V2TIMCustomElem *customElem = (V2TIMCustomElem *)elem;
NSData *customData = customElem.data;
NSLog(@"自定义信息 : %@",customData);
}
// 继续查看当前 elem 后面还有没更多 elem
elem = elem.nextElem;
}
// elem 如果为 nil,表示所有 elem 都已经解析完
}
}
class AdvancedMsgListener final : public V2TIMAdvancedMsgListener {
public:
/**
* 收到新消息
*
* @param message 消息
*/
void OnRecvNewMessage(const V2TIMMessage& message) override {
// 解析出 groupID 和 userID
V2TIMString groupID = message.groupID;
V2TIMString userID = message.userID;

// 判断当前是单聊还是群聊:
// 如果 groupID 不为空,表示此消息为群聊;如果 userID 不为空,表示此消息为单聊

// 解析出 message 中的多个 Elem 消息
// 所有的 Elem 都保存在 message.elemList 中,访问这些 Elem 需要遍历 message.elemList
for (size_t i = 0; i < message.elemList.Size(); ++i) {
V2TIMElem* elem = message.elemList[i];
switch (elem->elemType) {
case V2TIMElemType::V2TIM_ELEM_TYPE_NONE: {
// 未知消息
} break;
case V2TIMElemType::V2TIM_ELEM_TYPE_TEXT: {
// 文本消息
auto textElem = static_cast<V2TIMTextElem*>(elem);
} break;
case V2TIMElemType::V2TIM_ELEM_TYPE_CUSTOM: {
// 自定义消息
auto customElem = static_cast<V2TIMCustomElem*>(elem);
} break;
case V2TIMElemType::V2TIM_ELEM_TYPE_IMAGE: {
// 图片消息
auto imageElem = static_cast<V2TIMImageElem*>(elem);
} break;
case V2TIMElemType::V2TIM_ELEM_TYPE_SOUND: {
// 语音消息
auto soundElem = static_cast<V2TIMSoundElem*>(elem);
} break;
case V2TIMElemType::V2TIM_ELEM_TYPE_VIDEO: {
// 视频消息
auto videoElem = static_cast<V2TIMVideoElem*>(elem);
} break;
case V2TIMElemType::V2TIM_ELEM_TYPE_FILE: {
// 文件消息
auto fileElem = static_cast<V2TIMFileElem*>(elem);
} break;
case V2TIMElemType::V2TIM_ELEM_TYPE_LOCATION: {
// 地理位置消息
auto locationElem = static_cast<V2TIMLocationElem*>(elem);
} break;
case V2TIMElemType::V2TIM_ELEM_TYPE_FACE: {
// 表情消息
auto faceElem = static_cast<V2TIMFaceElem*>(elem);
} break;
case V2TIMElemType::V2TIM_ELEM_TYPE_GROUP_TIPS: {
// 群 Tips 消息
auto mergerElem = static_cast<V2TIMMergerElem*>(elem);
} break;
case V2TIMElemType::V2TIM_ELEM_TYPE_MERGER: {
// 合并消息
auto groupTipsElem = static_cast<V2TIMGroupTipsElem*>(elem);
} break;
default: {
} break;
}
}
}
// 其他成员 ...
};

// 添加高级消息的事件监听器,注意在移除监听器之前需要保持 advancedMsgListener 的生命期,以免接收不到事件回调
AdvancedMsgListener advancedMsgListener;
V2TIMManager::GetInstance()->GetMessageManager()->AddAdvancedMsgListener(&advancedMsgListener)



帮助和支持

本页内容是否解决了您的问题?

填写满意度调查问卷,共创更好文档体验。

文档反馈