跳到主要内容

语音转文字

Flutter IMKit 支持对语音消息进行转文字展示。用户在聊天界面长按语音消息,在弹出的菜单中点击"转文字"即可调用 IMLib 的语音转文字能力,转换完成后在语音气泡下方显示转写结果。

准备工作

  • 请确保已在融云控制台开启语音转文字功能
  • 使用的 SDK 需支持语音转文字能力(Flutter IMKit SDK 版本 >= 1.0.1)。
  • 语音消息录制建议参数:采样率 8000Hz 或 16000Hz,单声道,编码 aac(默认实现已按此配置),单条语音消息建议不超过 60 秒。

功能说明

  • 长按语音消息气泡 → 菜单出现"转文字"选项。
  • 触发后会立即更新 UI 为"转换中"状态,等待回调结果。
  • 成功后在语音气泡下方展示文字,失败会显示失败提示(可选气泡提示或 toast)。
  • IMKit 会记录"某条消息的转文字是否可见"的 UI 状态,以便二次进入会话时保持显示。

效果展示

组件关系

  • Engine 层:监听 IMLib 回调,派发语音转文字完成事件。
  • Chat Provider:发起转换请求、接收完成事件、维护 UI 状态(可见性/动画/失败提示)。
  • Voice 气泡:在主气泡下方追加一个"转文字"附加气泡,展示转换中/成功/失败的不同状态。

关键代码

1)触发转换请求

从语音消息的长按菜单进入:

message_bubble.dart
      case 'speechToText':
// 处理语音转文字逻辑
if (widget.message is RCIMIWVoiceMessage) {
if (speechToTextVisible || isBubbleAppend) {
context
.read<RCKChatProvider>()
.removeSpeechToTextMessageIdVisible(widget.message.messageId!);
} else {
context
.read<RCKChatProvider>()
.voiceMessageToText(widget.message as RCIMIWVoiceMessage);
}
}
break;

实际发起请求的实现:

rongcloud_im_kit.dart
  Future<void> voiceMessageToText(RCIMIWVoiceMessage message) async {
if (message.messageUId == null || message.messageId == null) {
return;
}
engineProvider.engine?.requestSpeechToTextForMessage(message.messageUId!,
callback: IRCIMIWOperationCallback(
onSuccess: () {
debugPrint('requestSpeechToTextForMessage success');
},
onError: (code) {
debugPrint('requestSpeechToTextForMessage error: $code');
for (int i = 0; i < _messages.length; i++) {
if (_messages[i].messageId == message.messageId) {
(_messages[i] as RCIMIWVoiceMessage).speechToTextInfo?.status =
RCIMIWSpeechToTextStatus.failed;
final ctx = _messageListKey.currentContext;
if (ctx != null && speechToTextFailShowToast) {
ScaffoldMessenger.of(ctx).showSnackBar(
const SnackBar(content: Text('转文字失败,请稍后重试')),
);
}
notifyListeners();
break;
}
}
},
));
for (int i = 0; i < _messages.length; i++) {
if (_messages[i].messageId == message.messageId) {
(_messages[i] as RCIMIWVoiceMessage).speechToTextInfo?.status =
RCIMIWSpeechToTextStatus.converting;
break;
}
}
addSpeechToTextMessageIdVisible(message.messageId!);
notifyListeners();
}

2)接收转换结果回调

Engine 层监听 IMLib 回调并派发:

engine_provider.dart
    engine?.onSpeechToTextCompleted = (info, messageUId, code) {
RCIMIWVoiceMessage voiceMessage = RCIMIWVoiceMessage.fromJson({});
voiceMessage.speechToTextInfo = info;
voiceMessage.messageUId = messageUId;
if (code != 0) {
voiceMessage.speechToTextInfo?.status = RCIMIWSpeechToTextStatus.failed;
}
speechToTextMessageNotifier.value = voiceMessage;
...
};

Chat Provider 同步 UI 状态:

rongcloud_im_kit.dart
  void _onSpeechToTextCompleted() {
final speechToTextMessage =
engineProvider.speechToTextMessageNotifier.value;
if (speechToTextMessage != null) {
for (int i = 0; i < _messages.length; i++) {
if (_messages[i].messageUId == speechToTextMessage.messageUId) {
(_messages[i] as RCIMIWVoiceMessage).speechToTextInfo =
speechToTextMessage.speechToTextInfo;
if (speechToTextMessage.speechToTextInfo?.status ==
RCIMIWSpeechToTextStatus.failed) {
final ctx = _messageListKey.currentContext;
if (ctx != null && speechToTextFailShowToast) {
ScaffoldMessenger.of(ctx).showSnackBar(
const SnackBar(content: Text('转文字失败,请稍后重试')),
);
}
}
break;
}
}
notifyListeners();
}
}

3)UI 展示:在语音气泡下方追加"转文字"气泡

展示转换中/失败/成功三种状态与动画:

voice_message_bubble.dart
  @override
Widget? buildAppendBubble(BuildContext context) {
final RCIMIWVoiceMessage voiceMessage = message as RCIMIWVoiceMessage;
final int? id = voiceMessage.messageId;
final chatProvider = context.read<RCKChatProvider>();
final bool isVisible =
chatProvider.speechToTextMessageIdsVisible.contains(id);
final String text = voiceMessage.speechToTextInfo?.text ?? '';
final bool isConverting = voiceMessage.speechToTextInfo?.status ==
RCIMIWSpeechToTextStatus.converting;
final bool isFailed = voiceMessage.speechToTextInfo?.status ==
RCIMIWSpeechToTextStatus.failed &&
isVisible;
if (isFailed) { ... }
if (isConverting) { ... return ImageUtil.getImageWidget('speech_loading.png', ...); }
if (id == null || text.isEmpty) return null;
if (!isVisible) { return null; }
final bool isShown =
chatProvider.speechToTextMessageIdsHasShown.contains(id);
...
}

4)可用性开关与菜单展示

语音转文字开关取自 AppSettings:

engine_provider.dart
  Future<void> _setAppSettings() async {
final appSettings = await engine?.getAppSettings();
enableSpeechToText = appSettings?.speechToTextEnable ?? false;
}

弹出菜单中控制"转文字/取消转文字"的展示:

message_bubble.dart
    if (widget.message is RCIMIWVoiceMessage) {
bool speechToTextInfoNoEmpty =
(widget.message as RCIMIWVoiceMessage).speechToTextInfo != null;
final engineProvider = context.read<RCKEngineProvider>();
canSpeechToText =
engineProvider.enableSpeechToText && speechToTextInfoNoEmpty;
speechToTextVisible = chatProvider.speechToTextMessageIdsVisible
.contains(widget.message.messageId) &&
(widget.message as RCIMIWVoiceMessage).speechToTextInfo?.status ==
RCIMIWSpeechToTextStatus.success;
}

使用说明

  1. 在聊天页面集成 ChatPage 与 Provider(示例详见快速开始文档)。
  2. 确保语音消息能正常录制与发送(默认录音配置如下,满足转写要求):
voice_record_provider.dart
  void startRecord(BuildContext context) async {
...
await _recorder.start(
RecordConfig(
encoder: AudioEncoder.aacLc,
sampleRate: 16000,
bitRate: 32000,
numChannels: 1,
),
path: voicePath,
);
...
_volumeTimer = Timer.periodic(const Duration(milliseconds: 20), (timer) async {
if (await _recorder.isRecording()) {
...
if (voiceDuration >= 60 && context.mounted) {
finishRecord(context);
}
notifyListeners();
}
});
}
  1. 进入聊天,长按语音消息,选择"转文字"。
  2. 等待转换完成,结果自动显示于语音气泡下方;再次长按可选择"取消转文字"以隐藏结果。

注意事项

  • 仅语音消息支持转文字;未开启功能或非可转写的消息不会出现菜单项。
  • 录音参数需要满足:16000Hz 或 8000Hz、单声道、AAC 编码、≤ 60 秒。
  • 转写能力由服务端提供,需保证网络可用;失败会回调错误码并在 UI 层提示。
  • UI 侧"是否可见"的状态由本地 SharedPreferences 做轻量持久化,进入会话时会恢复。

常见问题

  • 看不到"转文字"菜单?
    • 检查 AppSettings 中 speechToTextEnable 是否为 true。
    • 检查该条语音消息是否包含 speechToTextInfo(需要新版本 SDK 发送或服务端支持)。
  • 转写一直"转换中"?
    • 检查网络情况与服务端配置;观察 onSpeechToTextCompleted 是否回调。
  • 录音格式不支持?
    • 使用默认录音实现或按推荐参数录音(16000Hz 或 8000Hz、AAC、单声道、≤60s)。