实现语音消息功能
更新时间: 2026/06/08 17:51:51
IM UIKit 支持语音消息功能。本文档旨在帮助开发者快速将 IM UIKit 的语音消息功能集成到您的应用中,主要介绍语音消息从录制、发送、消息加载解析、附件下载到播放的完整流程。
功能介绍
IM UIKit 的默认界面已经实现了语音消息的发送按钮、UI 展示和接收逻辑。
UIKit 内部实现语音消息的逻辑如下:
%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#E3F2FD', 'primaryTextColor': '#1565C0', 'primaryBorderColor': '#1976D2', 'lineColor': '#42A5F5', 'secondaryColor': '#FFE0B2', 'tertiaryColor': '#F5F5F5' }}}%%
flowchart LR
A["长按语音按钮"] --> B["触发录制"]
B --> C{"≥1秒?"}
C -->|否| D["过短"]
C -->|是| E["创建语音消息"]
E --> F["发送"]
F --> G["接收并解析附件信息"]
G --> H["显示气泡"]
H --> I["点击语音消息"]
I --> J{"本地存在?"}
J -->|否| K["下载"]
J -->|是| L["播放"]
K --> L
L --> M["已读"]
style A fill:#E3F2FD,stroke:#1976D2,stroke-width:2px
style F fill:#C8E6C9,stroke:#388E3C,stroke-width:2px
style L fill:#FFF9C4,stroke:#F57C00,stroke-width:2px
style D fill:#FFCDD2,stroke:#D32F2F,stroke-width:2px
- 用户在聊天输入区按住语音按钮,UI 层触发录音。
- 聊天页面通过
NIMSDK.shared().mediaManager录制本地音频文件。 - 录音结束后,SDK 回调文件路径,业务层读取音频时长并做最小时长校验。
- ViewModel 使用
V2NIMMessageCreator.createAudioMessage创建 SDK 语音消息,并统一实现消息发送流程。 - 消息列表加载时,通过
V2NIMMessageAudioAttachment解析语音时长、本地路径、远端 URL 等附件信息。 - 语音消息模型根据时长计算气泡宽度,Cell 展示播放动画和语音时长。
- 接收方点击语音消息时,优先使用本地附件文件;本地文件不存在时,根据附件 URL 下载到目标路径。
- 附件下载成功后使用
AVAudioPlayer播放,并在播放完成、页面退出或切换播放目标时停止播放和动画。
前提条件
开始使用语音消息前,请确保您已:
- 集成 IM UIKit。
- 集成会话界面(
ChatViewController或FunChatViewController)。 - 开启麦克风权限。
实现流程
按照 UIKit 内部实现语音消息的逻辑,你需要关注以下关键流程:
%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#AFDDFF', 'primaryTextColor': '#5409DA', 'primaryBorderColor': '#4E71FF', 'lineColor': '#4E71FF', 'secondaryColor': '#FF9149', 'tertiaryColor': '#F8FAFC' }}}%%
flowchart LR
A("1.按住语音按钮") --> B("2.触发录制流程") --> C("3.创建语音消息并发送") --> D("4.消息加载与解析") --> E("5.下载语音消息") --> F("6.播放语音消息")
实现步骤
录音流程

录音入口从输入栏开始:
-
ChatRecordView监听长按录音按钮。 -
用户按下录音按钮时,调用
ChatViewController的delegate?.startRecord(),开始录音。ChatViewController.startRecord()先处理 UI 和权限:- 调用
jumpDownMessage()收起新消息提示。 - 通过
NEAuthManager.hasAudioAuthoriztion()检查麦克风权限。 - 已授权时调用 SDK 开始录制。
- 未授权时调用
NEAuthManager.requestAudioAuthorization(...)申请权限。
- 调用
-
结束、取消或移出时调用
ChatViewController的delegate?.endRecord(insideView:)结束或取消录音。insideView == true,调用stopRecord()。insideView == false,调用cancelRecord()。
-
NEBaseChatInputView将录音事件继续转发给聊天页面。
- 语音消息发送必须先由录音回调拿到真实本地文件路径,再创建
V2NIMMessage。 - 录音开始前应停止当前语音播放,避免录音和播放同时进行。
发送语音消息
ChatViewController 实现 SDK 录音回调。
| 回调 | 业务处理 |
|---|---|
recordAudio(_:didBeganWithError:) |
录音开始,停止当前语音播放 |
recordAudio(_:didCompletedWithError:) |
录音完成,停止录音动画,校验文件和时长 |
recordAudioDidCancelled() |
录音取消 |
recordAudioProgress(_:) |
录音进度 |
recordAudioInterruptionBegin() |
录音被系统中断 |
录音完成后,如果 filePath 为空,则展示错误。若不为空,则使用 recordDuration(filePath:) 通过 AVURLAsset 读取录音时长。
- 如果时长大于 1 秒,调用
viewModel.sendAudioMessage(filePath: fp)。 - 如果时长不足,展示
chatLocalizable("record_too_short")。
创建并发送 SDK 语音消息:
创建语音消息时不要手写附件对象,应统一使用 V2NIMMessageCreator.createAudioMessage。
swiftlet message = MessageUtils.audioMessage(filePath: filePath, name: nil, sceneName: nil, duration: 0)
let params = getSendMessageParams(nil, message)
sendMessage(message: message, conversationId: conversationId, params: params) { _, error, pro in
completion(error)
}
MessageUtils.audioMessage(...) 内部调用:
swiftV2NIMMessageCreator.createAudioMessage(
filePath,
name: name,
sceneName: sceneName ?? V2NIMStorageSceneConfig.default_IM().sceneName,
duration: duration
)
语音消息加载与解析
语音消息模型为 MessageAudioModel,继承 MessageContentModel。
初始化时从 SDK 消息中解析附件:
swiftif let obj = message?.attachment as? V2NIMMessageAudioAttachment {
duration = Int((Double(obj.duration) / 1000).rounded())
if duration > 2 {
audioW = min(Double(duration) * 8 + audioW, audio_max_width)
}
}
解析内容:
| 数据 | 来源 |
|---|---|
| 消息对象 | V2NIMMessage |
| 语音附件 | message.attachment as? V2NIMMessageAudioAttachment |
| 语音时长 | V2NIMMessageAudioAttachment.duration |
| 本地路径 | V2NIMMessageAudioAttachment.path 或 ChatMessageHelper.createFilePath(message) |
| 远端 URL | V2NIMMessageAudioAttachment.url |
| 气泡宽度 | duration + audio_max_width |
type设置为.audio。- 附件时长由毫秒转换为秒。
- 小于等于 2 秒使用默认宽度;大于 2 秒后按时长增加宽度。
contentSize和height根据语音气泡宽度、基础高度、昵称高度、Pin 高度计算。- 消息展示时必须通过
V2NIMMessageAudioAttachment解析时长和路径,不要依赖自定义字段。
下载语音消息
点击语音消息时由 ChatViewController.didTapAudioMessage(_:_:) 处理。

- 从
model?.message?.attachment解析V2NIMMessageAudioAttachment。 - 获取
audioObject.path,为空时使用ChatMessageHelper.createFilePath(model?.message)生成本地目标路径。 - 如果本地文件存在,直接调用
startPlay(...);如果本地文件不存在,读取audioObject.url。 - 调用
viewModel.downLoad(urlString, path, nil)下载。 - 下载成功后调用
startPlay(cell:model:);下载失败时展示错误。
下载链路:
textChatViewController.didTapAudioMessage
-> ChatViewModel.downLoad
-> ResourceRepo.downLoadFile
-> StorageProvider.downloadFile
-> NIMSDK.shared().v2StorageService.downloadFile
StorageProvider.downloadFile(...) 使用 SDK 下载接口:
swiftNIMSDK.shared().v2StorageService.downloadFile(urlString, filePath: filePath) { localPath in
completion?(localPath, nil)
} failure: { error in
completion?(nil, error.nserror as NSError)
} progress: { pro in
progress?(pro)
}
- 播放前必须优先检查本地文件是否存在,不存在再下载,避免播放器直接使用远端 URL。
- 页面退出、切换消息、开始录音、播放结束时都要停止播放动画和播放器。
- 语音下载应走
ChatViewModel -> ResourceRepo -> StorageProvider链路,保持ViewController不直接调用 SDK 存储服务。
播放语音消息
播放语音消息由 ChatViewController 中的 AVAudioPlayer 管理。

-
点击语音消息后,会先调用
didTapAudioMessage确保本地文件存在,不存在则先下载。 -
startPlay(cell:model:)判断当前点击的语音是否正在播放。 -
如果点击的是当前播放项且播放器正在播放,则调用
stopPlay();如果点击的是新语音,先停止旧播放,再记录playingCell和playingModel。 -
调用
startPlaying(audioMessage:isSend:)。- 解析
V2NIMMessageAudioAttachment。 - 调用
playingCell?.startAnimation(byRight: isSend)开始 Cell 动画。 - 根据
viewModel.getHandSetEnable()切换听筒或扬声器。 - 获取本地音频路径:
audio.path ?? ChatMessageHelper.createFilePath(message)。 - 文件存在时用
AVAudioPlayer(contentsOf:)创建播放器。 - 设置
audioPlayer?.delegate = self。 - 调用
audioPlayer?.play()开始播放。 - 文件不存在或播放器创建失败时停止 Cell 动画。
- 解析
注意说明
- UI 宽度计算基于语音时长,应保持最小宽度、最大宽度和递增规则在对应皮肤内一致。
- 新增文案必须走
chatLocalizable对应资源,禁止硬编码业务文案。
参考信息
关键文件
语音消息流程主要涉及以下关键文件:
| 功能 | 文件 |
|---|---|
| Normal 录音按钮 View | IMUIKit/NEChatUIKit/NEChatUIKit/Classes/Chat/View/ChatView/ChatRecordView.swift |
| 输入栏录音事件转发 | IMUIKit/NEChatUIKit/NEChatUIKit/Classes/Chat/View/ChatView/NEBaseChatInputView.swift |
| 输入栏录音代理协议 | IMUIKit/NEChatUIKit/NEChatUIKit/Classes/Protocol/ChatInputViewDelegate.swift |
| 聊天页面录音、下载、播放实现 | IMUIKit/NEChatUIKit/NEChatUIKit/Classes/Chat/Controller/ChatViewController.swift |
| Fun 录音入口 | IMUIKit/NEChatUIKit/NEChatUIKit/Classes/FunUI/Controller/FunChatViewController.swift |
| 语音消息创建工具 | IMUIKit/NEChatUIKit/NEChatUIKit/Classes/Chat/Helper/MessageUtils.swift |
| 语音消息发送 ViewModel | IMUIKit/NEChatUIKit/NEChatUIKit/Classes/Chat/ViewModel/ChatViewModel.swift |
| 语音消息 UI 模型 | IMUIKit/NEChatUIKit/NEChatUIKit/Classes/Chat/Model/MessageAudioModel.swift |
| 音频路由管理 | IMUIKit/NEChatUIKit/NEChatUIKit/Classes/Chat/Helper/NEAudioSessionManager.swift |
| 附件下载 Repo | IMUIKit/NEChatKit/NEChatKit/Classes/Repo/ResourceRepo.swift |
| 底层存储 Provider | IMUIKit/NEChatKit/NEChatKit/Classes/Provider/StorageProvider.swift |
涉及的接口
录音过程 中主要涉及的接口如下:
| SDK 接口 | 说明 |
|---|---|
NIMSDK.shared().mediaManager.add(self) |
注册录音回调代理 |
NIMSDK.shared().mediaManager.remove(self) |
移除录音回调代理 |
NIMSDK.shared().mediaManager.record(forDuration:) |
开始录音 |
NIMSDK.shared().mediaManager.stopRecord() |
停止录音并触发完成回调 |
NIMSDK.shared().mediaManager.cancelRecord() |
取消录音 |
发送过程 中主要涉及的接口如下:
| SDK 接口 | 说明 |
|---|---|
V2NIMMessageCreator.createAudioMessage(_:name:sceneName:duration:) |
根据本地音频文件创建语音消息 |
V2NIMStorageSceneConfig.default_IM().sceneName |
默认 IM 存储场景 |
V2NIMMessage |
SDK 消息对象 |
下载过程 中主要涉及的接口如下:
| SDK 接口 | 说明 |
|---|---|
NIMSDK.shared().v2StorageService.downloadFile(_:filePath:success:failure:progress:) |
下载远端附件到本地路径 |
V2NIMProgressCallback |
下载进度回调 |
音频路由处理过程 中主要涉及接口如下:
| 方法 | 功能 |
|---|---|
NEAudioSessionManager.shared.switchToSpeaker() |
切换扬声器 |
NEAudioSessionManager.shared.switchToReceiver() |
切换听筒 |
NEAudioSessionManager.shared.stopProximityMonitoring() |
停止距离传感器监听 |
播放结束回调过程 中主要涉及接口如下:
| 回调 | 业务处理 |
|---|---|
audioPlayerDidFinishPlaying(_:successfully:) |
停止播放动画并清理播放状态 |
audioPlayerDecodeErrorDidOccur(_:error:) |
解码失败时停止播放动画并清理播放状态 |




