Android&iOS&Windows&Mac

最近更新时间:2024-12-26 12:26:33

我的收藏

功能描述

通过 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::