主题
媒体与离线 API
1. 推荐的媒体发送接口
| api-id | 用途 | 必要元数据 | 返回 |
|---|---|---|---|
media.send_image | 发送图片 | 本地文件、MIME、宽、高 | Apple XHIMMediaSendResult;其他端 XHIMMediaTask |
media.send_video | 发送视频 | 本地文件、MIME、时长、宽、高 | Apple XHIMMediaSendResult;其他端 XHIMMediaTask |
media.send_audio | 发送音频 | 本地文件、MIME、时长、可选 waveform | Apple XHIMMediaSendResult;其他端 XHIMMediaTask |
media.send_file | 发送文件 | 本地文件、MIME、展示文件名 | Apple XHIMMediaSendResult;其他端 XHIMMediaTask |
这些便利接口先计算内容哈希,再把上传任务和依赖消息意图一起持久化给 Core。 成功返回表示“任务已可靠接受”,随后即使 App 重启,Core 仍可按策略继续分片、 审核、提交对象和入队消息。
聊天 UI 可以在可靠受理之前先显示一个仅存在于宿主内存的乐观视频气泡:先读取 首帧、时长和宽高,并预生成 clientMessageID;文件接管、哈希和上传放到后台, 最终以相同 ID 的耐久消息替换占位。不要把占位写成另一条文字 [视频] 消息。
平台入口:
- iOS:
client.sendImageMessage/sendVideoMessage/sendAudioMessage/sendFileMessage; - macOS:
client.sendImageMessage/sendVideoMessage/sendAudioMessage/sendFileMessage; - Android:
client.sendImage/sendVideo/sendAudio/sendFile的File重载; - Windows:
XHIMMediaMessaging.SendImageAsync/...; - HarmonyOS:
XHIMMediaMessaging.sendImage/...。
Apple 返回 .durable(XHIMMediaTask) 时表示 Core 持久化受理;返回 .sent(XHIMSendReceipt) 时表示 SDK Facade 已完成服务端授权上传并把媒体消息 交给可靠 Outbox。两种模式都由 SDK 管理 Credential、哈希、Prepare、上传、 Complete 和消息组装。直接传入 uploader 的重载只用于自定义 Product Adapter; 普通接入不要自行上传到临时 URL 再组装消息。
服务端本地对象存储使用可续传 PATCH + Content-Range + Upload-Offset,S3 兼容存储通常使用签名 PUT。Apple Facade 同时支持两种协议;业务代码不能把 授权方法硬编码为 PUT。
2. durable media task
| api-id | 用途 | 主要输入 | 返回 |
|---|---|---|---|
media.accept_upload | 接受一个完整上传意图 | XHIMMediaUploadIntent | XHIMMediaTask |
media.accept_download | 接受一个下载/缓存意图 | XHIMMediaDownloadIntent | XHIMMediaTask |
media.get_task | 查询任务快照 | taskID | XHIMMediaTask |
media.cancel_task | 取消持久化媒体工作流 | taskID | XHIMMediaTask |
cancelMediaTask 和“取消等待某次 API”不是一回事:
- 取消 Swift Task / Coroutine / CancellationToken / requestId,只停止当前调用;
cancelMediaTask(taskID)会改变持久化业务任务状态,并阻止依赖消息继续提交。
XHIMMediaTask.state:
text
pending → authorizing → transferring → verifying → completed
↕ paused └──────→ failed
任意未终态 ────────────────────────────────→ cancelled未知 native state 必须保留 task ID 并稍后重查,不能强行映射为 failed。 messageEnqueued == true 表示媒体引用已经进入依赖消息;最终消息投递状态仍从消息 查询判断。
3. 媒体缓存读取
Apple 普通业务页面优先使用收到即下载的便利接口:
swift
let content = try XHIMMediaMessageContent.decode(message: message)
let downloaded = try await client.downloadMediaMessage(
message,
cacheDirectoryURL: mediaCacheDirectory
)
preview(downloaded.localFileURL, content: content)它支持图片、视频、语音和文件消息,内部完成账号下载授权、禁止重定向、大小和 SHA-256 校验、临时文件清理以及缓存命中。只有自定义 Product Adapter、后台 持久任务或需要流式随机读的页面才使用下面的低层任务/Reader。
聊天 UI 收到 .audio 后,应把已校验的 localFileURL 交给页面级单实例 AVAudioPlayer,点击气泡切换播放/暂停,切换消息时停止上一条;不要把语音发送 到 Quick Look 或单独的播放页面。播放状态必须按消息 ID 管理,不能保存在可复用 Cell 内。页面退出、开始录音、App 退到后台或音频中断时应停止播放并释放 AVAudioSession。这只是 UI 消费方式,不改变 SDK 的媒体下载合同。
fallbackText 只属于通知、会话摘要和未知类型降级。聊天详情页必须按 contentType + contentVersion 使用 XHIMMediaMessageContent.decode,再选择 图片、视频、语音或文件 Renderer;不要先判断 fallbackText 是否为空,否则 有效媒体会错误显示成 [图片]、[视频] 等普通文字。位置、名片分别使用 XHIMLocationContent、XHIMContactCardContent 解码。解码失败应显示不支持或 内容损坏卡片,不能吞掉错误后伪装成文本消息。
media.open_cache
输入 cacheKey,返回 XHIMMediaCacheReader。Reader 公开:
| api-id / 操作 | iOS | macOS | Android | Windows | HarmonyOS |
|---|---|---|---|---|---|
| 大小 | byteSize | byteSize | byteSize | ByteSize | byteSize |
media.cache_read 读取范围 | read(offset:length:) | read(offset:length:) | read(offset,length) | ReadAsync(offset,length) | read(offset,length) |
media.cache_chunks 分块消费 | chunks(...) | chunks(...) | chunks(...) | ReadChunksAsync(...) | stream(...) |
media.cache_close 关闭 | close() | close() | close() | DisposeAsync() | close() |
Reader 读取 SDK 管理的缓存对象,不暴露数据库路径、对象存储密钥或临时下载 凭证。读取结束后必须显式关闭。UI 播放器需要随机读时保持一个 Reader,不要每帧 重新打开。
4. 媒体消息模型
XHIMMediaMessageFactory 提供:
| api-id | 工厂 |
|---|---|
media.factory_image | image / Windows Image |
media.factory_audio | audio / Windows Audio |
media.factory_video | video / Windows Video |
media.factory_file | file / Windows File |
生成的仍是 XHIMOutgoingMessage,可直接交给 sendMessage。媒体 payload 只包含 XHIMMediaRef 和展示元数据:
mediaIDcontentHashSHA256byteSizemimeTypestorageRevision- 可选
XHIMMediaEncryption
永远不要把本地文件路径、系统相册 URI、管理员密钥或即将过期的 signed URL 写入消息 payload。
5. 系统 Picker 的职责
SDK UI Kit 提供相机、相册、录音和文件入口;相册入口可以同时选择图片和视频。 系统权限及 Picker 仍属于 宿主 App:
- 用户在宿主页面触发 Picker;
- 宿主取得有权限的本地文件/URI;
- 调用媒体便利接口;
- 立即释放临时系统授权,长期任务由 SDK Core 接管;
- 订阅
mediaTaskUpdated更新进度和失败态。
购买方可以替换 Picker 与 UI,不需要修改 Core。
iOS 商业 Demo 使用 30 秒拍摄上限;直接可读的 AVURLAsset 不做中等质量 二次转码,只有组合资源才使用 passthrough 导出。列表渲染应按图片、视频、 语音、位置和通用卡片隔离复用池,并在复用时取消旧下载、停用旧尺寸约束和清空 缩略图,避免滚动或输入面板动画时出现压扁气泡。
6. 离线只读 Reader
当后台扩展、通知扩展、桌面快速搜索或受限进程不允许启动网络 Client 时,使用 XHIMOfflineReader。它只打开同一账号的加密本地投影,不启动登录、WebSocket、 同步或 Outbox。
| api-id | 用途 | 输入 | 返回 |
|---|---|---|---|
offline.open | 打开只读数据库 | storage、App ID、数据库 Key Provider | XHIMOfflineReader |
offline.messages | 会话消息分页 | conversation、cursor、limit | XHIMMessagePage |
offline.search_messages | 本地消息搜索 | query、cursor、limit | XHIMMessagePage |
offline.conversations | 会话分页 | cursor、limit | XHIMConversationPage |
offline.friend_requests | 好友申请分页 | cursor、limit | XHIMSocialPage |
offline.friendships | 好友分页 | cursor、limit | XHIMSocialPage |
offline.groups | 群组分页 | cursor、limit | XHIMSocialPage |
offline.group_members | 群成员分页 | conversation、cursor、limit | XHIMSocialPage |
offline.blocks | 黑名单分页 | cursor、limit | XHIMSocialPage |
offline.group_join_requests | 入群申请分页 | cursor、limit | XHIMSocialPage |
offline.close | 关闭 Reader | 无 | Void |
离线 Reader 与在线 Client 共享模型,但不保证读取瞬间包含刚到达服务器的数据。 需要最新状态时回到主 App,由在线 Client 完成同步后再查。
7. 数据库 Key
Reader 的 Key Provider 必须从平台安全存储读取当前账号数据库 Key:
- iOS Keychain;
- macOS Keychain;
- Android Keystore 包装后的 Key;
- Windows DPAPI / Credential Locker;
- HarmonyOS HUKS。
不要把 Key 写进源码、UserDefaults、SharedPreferences、注册表明文或普通配置 文件。Key 错误时 Reader 应失败关闭,不能退回明文 SQLite。