Android&iOS&Windows&Mac

最近更新时间:2026-06-18 15:54:06

我的收藏

功能描述

流式消息(Stream Message)是即时通信 IM SDK 提供的一种特殊消息类型,专为 AI 对话等场景设计。在这类场景中,消息内容由服务端逐步生成,SDK 会实时接收内容分片并累加,直到整条消息生成完毕。客户端通过监听消息修改回调,即可实时获取流式消息的最新内容,实现类似 ChatGPT 的逐字输出效果。
说明:
流式消息功能在 8.9 及以上版本支持。
流式消息文本转语音在 9.0 及以上版本支持。
SDK 内部自动完成流拉取、分片排序、ACK 确认等操作,客户端无需手动调用额外接口。
流式消息通过 onRecvMessageModified 回调持续通知 UI 层更新内容。

接口说明

流式消息功能

V2TIMStreamElem 属性

流式消息的 Elem 类型为 V2TIM_ELEM_TYPE_STREAM(值为 11),对应的 Elem 类为 V2TIMStreamElem
V2TIMStreamElem 属性说明如下:
属性
含义
说明
markdown
流式消息文本内容
随着分片到达逐步累加的文本内容,只读。
data
流式消息二进制数据
随着分片到达逐步累加的二进制数据,只读。
isStreamEnded
流式消息是否结束
为 YES/true 时表示服务端已完成所有内容推送。
说明:
markdowndata 均为只读属性,其内容由 SDK 内部自动维护和累加,客户端只需读取即可。
每次 onRecvMessageModified 回调中获取到的 markdown / data 已是累加后的完整内容,无需客户端自行拼接。

添加高级消息监听

流式消息通过高级消息监听器 V2TIMAdvancedMsgListener 接收和更新,需要事先调用 addAdvancedMsgListener 添加监听。
示例代码如下:
Java
Swift
Objective-C
C++
V2TIMManager.getMessageManager().addAdvancedMsgListener(new V2TIMAdvancedMsgListener() {
@Override
public void onRecvNewMessage(V2TIMMessage msg) {
// 收到新消息
}
@Override
public void onRecvMessageModified(V2TIMMessage msg) {
// 消息内容被修改(流式消息内容更新时触发)
}
});
V2TIMManager.shared.addAdvancedMsgListener(listener: self)
[[V2TIMManager sharedInstance] addAdvancedMsgListener:self];
V2TIMManager::GetInstance()->GetMessageManager()->AddAdvancedMsgListener(this);

接收流式消息

当收到一条新的流式消息时,会通过 onRecvNewMessage 回调通知。此时消息中的 streamElem 内容可能还不完整(isStreamEnded 为 NO/false)。
示例代码如下:
Java
Swift
Objective-C
C++
@Override
public void onRecvNewMessage(V2TIMMessage msg) {
if (msg.getElemType() == V2TIMMessage.V2TIM_ELEM_TYPE_STREAM) {
V2TIMStreamElem streamElem = msg.getStreamElem();
// 在 UI 上展示消息,后续内容会通过 onRecvMessageModified 持续更新
}
}

func onRecvNewMessage(_ msg: V2TIMMessage) {
if msg.elemType == .ELEM_TYPE_STREAM {
if let streamElem = msg.streamElem {
// 在 UI 上展示消息,后续内容会通过 onRecvMessageModified 持续更新
}
}
}

- (void)onRecvNewMessage:(V2TIMMessage *)msg {
if (msg.elemType == V2TIM_ELEM_TYPE_STREAM) {
V2TIMStreamElem *streamElem = msg.streamElem;
// 在 UI 上展示消息,后续内容会通过 onRecvMessageModified 持续更新
}
}

void OnRecvNewMessage(const V2TIMMessage &message) override {
if (message.elemList.Size() > 0) {
V2TIMElem *elem = message.elemList[0];
if (elem->elemType == V2TIMElemType::V2TIM_ELEM_TYPE_STREAM) {
auto *streamElem = static_cast<V2TIMStreamElem *>(elem);
// 在 UI 上展示消息,后续内容会通过 OnRecvMessageModified 持续更新
}
}
}

监听流式消息内容更新

SDK 收到新的分片数据后,会自动将内容累加到 streamElem 中,并通过 onRecvMessageModified 回调通知上层。客户端只需在该回调中读取最新的 markdown / data 内容并刷新 UI 即可。
说明:
onRecvMessageModified 回调可能触发多次,每收到一批有效分片都会触发一次,请确保 UI 刷新逻辑高效。
isStreamEnded 为 YES/true 时,表示流式消息已结束,可以停止加载动画等 UI 状态。
示例代码如下:
Java
Swift
Objective-C
C++
@Override
public void onRecvMessageModified(V2TIMMessage msg) {
if (msg.getElemType() == V2TIMMessage.V2TIM_ELEM_TYPE_STREAM) {
V2TIMStreamElem streamElem = msg.getStreamElem();
// 更新 UI 上展示的内容
updateMessageUI(msg.getMsgID(), streamElem.getMarkdown());

if (streamElem.isStreamEnded()) {
// 流式消息已结束,可以做最终处理
}
}
}
func onRecvMessageModified(_ msg: V2TIMMessage) {
if msg.elemType == .ELEM_TYPE_STREAM {
if let streamElem = msg.streamElem {
// 更新 UI 上展示的内容
updateMessageUI(msgID: msg.msgID, content: streamElem.markdown)

if streamElem.isStreamEnded {
// 流式消息已结束,可以做最终处理
}
}
}
}
- (void)onRecvMessageModified:(V2TIMMessage *)msg {
if (msg.elemType == V2TIM_ELEM_TYPE_STREAM) {
V2TIMStreamElem *streamElem = msg.streamElem;
// 更新 UI 上展示的内容
[self updateMessageUI:msg.msgID withContent:streamElem.markdown];

if (streamElem.isStreamEnded) {
// 流式消息已结束,可以做最终处理
}
}
}
void OnRecvMessageModified(const V2TIMMessage &message) override {
if (message.elemList.Size() > 0) {
V2TIMElem *elem = message.elemList[0];
if (elem->elemType == V2TIMElemType::V2TIM_ELEM_TYPE_STREAM) {
auto *streamElem = static_cast<V2TIMStreamElem *>(elem);
// 更新 UI 上展示的内容
// streamElem->markdown 为当前已累加的完整文本

if (streamElem->isStreamEnded) {
// 流式消息已结束,可以做最终处理
}
}
}
}

历史消息中的流式消息

当拉取历史消息时:
若消息中包含未完成的流式消息,SDK 会自动恢复拉流操作,并通过 onRecvMessageModified 回调继续更新内容,客户端无需额外处理。
若消息中的流式消息已全部拉取完毕,拉取历史消息后可直接展示完整内容,且不会再次触发 onRecvMessageModified 回调。

流式消息文本转语音功能

V2TIMTTSVoiceFormat 属性

流式消息转语音前,可设置语音格式,对应类为 V2TIMTTSVoiceFormat,属性说明如下:
属性
含义
说明
voiceID
音色 ID
可选。参考文档 TTS 精品音色,选择合适的 voice_id。
speed
语速
可选。在 [0.5, 2.0] 之间取值,默认 1.0。

启动 TTS

startTTS 用于对当前流式消息发起实时 TTS 语音合成。调用成功表示 TTS 请求已成功发起,合成后的音频分片会通过高级消息监听器的 onRecvTTSResponse 回调返回。
Java
Swift
Objective-C
C++
V2TIMStreamElem streamElem = message.getStreamElem();
if (streamElem == null) {
return;
}

V2TIMTTSVoiceFormat voiceFormat = new V2TIMTTSVoiceFormat();
voiceFormat.setVoiceID("VOICE_ID");
voiceFormat.setSpeed(1.0f);

streamElem.startTTS(voiceFormat, new V2TIMCallback() {
@Override
public void onSuccess() {}

@Override
public void onError(int code, String desc) {}
});
guard let streamElem = message.streamElem else {
return
}

let voiceFormat = V2TIMTTSVoiceFormat()
voiceFormat.voiceID = "VOICE_ID"
voiceFormat.speed = 1.0

streamElem.startTTS(voiceFormat: voiceFormat, succ: {
// TTS 请求发起成功。
}, fail: { code, desc in
// TTS 请求发起失败。
})
V2TIMStreamElem *streamElem = message.streamElem;
if (streamElem == nil) {
return;
}

V2TIMTTSVoiceFormat *voiceFormat = [[V2TIMTTSVoiceFormat alloc] init];
voiceFormat.voiceID = @"VOICE_ID";
voiceFormat.speed = 1.0;

[streamElem startTTS:voiceFormat succ:^{
// TTS 请求发起成功。
} fail:^(int code, NSString *desc) {
// TTS 请求发起失败。
}];
class TTSCallback : public V2TIMCallback {
public:
void OnSuccess() override {}
void OnError(int errorCode, const V2TIMString &errorMessage) override {}
};

V2TIMString msgID = "MSG_ID";
V2TIMString streamMsgID = "STREAM_MSG_ID";

V2TIMTTSVoiceFormat voiceFormat;
voiceFormat.voiceID = "VOICE_ID";
voiceFormat.speed = 1.0f;

TTSCallback callback;
V2TIMManager::GetInstance()->GetMessageManager()->StartTTS(
msgID, streamMsgID, 0, voiceFormat, &callback);

停止 TTS

stopTTS 用于停止当前 TTS 语音合成任务。停止后,业务侧应同步停止本地解码和播放流程。
Java
Swift
Objective-C
C++
V2TIMStreamElem streamElem = message.getStreamElem();
if (streamElem == null) {
return;
}

streamElem.stopTTS(new V2TIMCallback() {
@Override
public void onSuccess() {}

@Override
public void onError(int code, String desc) {}
});
guard let streamElem = message.streamElem else {
return
}

streamElem.stopTTS(succ: {
// TTS 停止成功。
}, fail: { code, desc in
// TTS 停止失败。
})
V2TIMStreamElem *streamElem = message.streamElem;
if (streamElem == nil) {
return;
}

[streamElem stopTTS:^{
// TTS 停止成功。
} fail:^(int code, NSString *desc) {
// TTS 停止失败。
}];
class TTSCallback : public V2TIMCallback {
public:
void OnSuccess() override {}
void OnError(int errorCode, const V2TIMString &errorMessage) override {}
};

V2TIMString msgID = "MSG_ID";
V2TIMString streamMsgID = "STREAM_MSG_ID";

TTSCallback callback;
V2TIMManager::GetInstance()->GetMessageManager()->StopTTS(
msgID, streamMsgID, &callback);

接收 TTS 语音合成音频分片

onRecvTTSResponse 用于接收 TTS 语音合成音频分片。每次回调只携带当前 chunk 的 Opus 音频分片列表,音频格式为 Opus / 16K / Mono,不是完整拼接结果。onRecvTTSResponse 通过高级消息监听器 V2TIMAdvancedMsgListener 接收和更新,需要事先调用 addAdvancedMsgListener 添加监听。
注意:
业务侧需要根据 msgID 过滤目标消息,并自行完成 Opus 解码与播放。
isFinished = true 表示当前 TTS 合成任务已结束。
Java
Swift
Objective-C
C++
V2TIMManager.getMessageManager().addAdvancedMsgListener(new V2TIMAdvancedMsgListener() {
@Override
public void onRecvTTSResponse(V2TIMTTSResponse response) {
if (response == null || !message.getMsgID().equals(response.getMsgID())) {
return;
}

List<byte[]> audioDataList = response.getAudioDataList();
boolean isFinished = response.isFinished();

// 业务侧在这里解码并播放 audioDataList。
}
});
V2TIMManager.sharedInstance().addAdvancedMsgListener(listener: self)

func onRecvTTSResponse(response: V2TIMTTSResponse) {
guard response.msgID == message.msgID else {
return
}

let audioDataList = response.audioDataList ?? []
let isFinished = response.isFinished

// 业务侧在这里解码并播放 audioDataList。
}
[[V2TIMManager sharedInstance] addAdvancedMsgListener:self];

- (void)onRecvTTSResponse:(V2TIMTTSResponse *)response {
if (![response.msgID isEqualToString:message.msgID]) {
return;
}

NSArray<NSData *> *audioDataList = response.audioDataList;
BOOL isFinished = response.isFinished;

// 业务侧在这里解码并播放 audioDataList。
}
class TTSListener : public V2TIMAdvancedMsgListener {
public:
explicit TTSListener(const V2TIMString &targetMsgID) : targetMsgID_(targetMsgID) {}

void OnRecvTTSResponse(const V2TIMTTSResponse &response) override {
if (response.msgID != targetMsgID_) {
return;
}

V2TIMBufferVector audioDataList = response.audioDataList;
bool isFinished = response.isFinished;

// 业务侧在这里解码并播放 audioDataList。
}

private:
V2TIMString targetMsgID_;
};

TTSListener listener("MSG_ID");
V2TIMManager::GetInstance()->GetMessageManager()->AddAdvancedMsgListener(&listener);