实现语音消息功能

更新时间: 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
  1. 用户在聊天输入区按住语音按钮,UI 层触发录音。
  2. 聊天页面通过 NIMSDK.shared().mediaManager 录制本地音频文件。
  3. 录音结束后,SDK 回调文件路径,业务层读取音频时长并做最小时长校验。
  4. ViewModel 使用 V2NIMMessageCreator.createAudioMessage 创建 SDK 语音消息,并统一实现消息发送流程。
  5. 消息列表加载时,通过 V2NIMMessageAudioAttachment 解析语音时长、本地路径、远端 URL 等附件信息。
  6. 语音消息模型根据时长计算气泡宽度,Cell 展示播放动画和语音时长。
  7. 接收方点击语音消息时,优先使用本地附件文件;本地文件不存在时,根据附件 URL 下载到目标路径。
  8. 附件下载成功后使用 AVAudioPlayer 播放,并在播放完成、页面退出或切换播放目标时停止播放和动画。

前提条件

开始使用语音消息前,请确保您已:

实现流程

按照 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.播放语音消息")

实现步骤

录音流程

ios录音.png

录音入口从输入栏开始:

  1. ChatRecordView 监听长按录音按钮。

  2. 用户按下录音按钮时,调用 ChatViewControllerdelegate?.startRecord(),开始录音。

    ChatViewController.startRecord() 先处理 UI 和权限:

    1. 调用 jumpDownMessage() 收起新消息提示。
    2. 通过 NEAuthManager.hasAudioAuthoriztion() 检查麦克风权限。
    3. 已授权时调用 SDK 开始录制。
    4. 未授权时调用 NEAuthManager.requestAudioAuthorization(...) 申请权限。
  3. 结束、取消或移出时调用 ChatViewControllerdelegate?.endRecord(insideView:) 结束或取消录音。

    • insideView == true,调用 stopRecord()
    • insideView == false,调用 cancelRecord()
  4. 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.pathChatMessageHelper.createFilePath(message)
远端 URL V2NIMMessageAudioAttachment.url
气泡宽度 duration + audio_max_width
  • type 设置为 .audio
  • 附件时长由毫秒转换为秒。
  • 小于等于 2 秒使用默认宽度;大于 2 秒后按时长增加宽度。
  • contentSizeheight 根据语音气泡宽度、基础高度、昵称高度、Pin 高度计算。
  • 消息展示时必须通过 V2NIMMessageAudioAttachment 解析时长和路径,不要依赖自定义字段。

下载语音消息

点击语音消息时由 ChatViewController.didTapAudioMessage(_:_:) 处理。

ios下载.png

  1. model?.message?.attachment 解析 V2NIMMessageAudioAttachment
  2. 获取 audioObject.path,为空时使用 ChatMessageHelper.createFilePath(model?.message) 生成本地目标路径。
  3. 如果本地文件存在,直接调用 startPlay(...);如果本地文件不存在,读取 audioObject.url
  4. 调用 viewModel.downLoad(urlString, path, nil) 下载。
  5. 下载成功后调用 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 管理。

ios播放.png

  1. 点击语音消息后,会先调用 didTapAudioMessage 确保本地文件存在,不存在则先下载。

  2. startPlay(cell:model:) 判断当前点击的语音是否正在播放。

  3. 如果点击的是当前播放项且播放器正在播放,则调用 stopPlay();如果点击的是新语音,先停止旧播放,再记录 playingCellplayingModel

  4. 调用 startPlaying(audioMessage:isSend:)

    1. 解析 V2NIMMessageAudioAttachment
    2. 调用 playingCell?.startAnimation(byRight: isSend) 开始 Cell 动画。
    3. 根据 viewModel.getHandSetEnable() 切换听筒或扬声器。
    4. 获取本地音频路径:audio.path ?? ChatMessageHelper.createFilePath(message)
    5. 文件存在时用 AVAudioPlayer(contentsOf:) 创建播放器。
    6. 设置 audioPlayer?.delegate = self
    7. 调用 audioPlayer?.play() 开始播放。
    8. 文件不存在或播放器创建失败时停止 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:) 解码失败时停止播放动画并清理播放状态
此文档是否对你有帮助?
有帮助
去反馈
  • 功能介绍
  • 前提条件
  • 实现流程
  • 实现步骤
  • 录音流程
  • 发送语音消息
  • 语音消息加载与解析
  • 下载语音消息
  • 播放语音消息
  • 注意说明
  • 参考信息
  • 关键文件
  • 涉及的接口