文件与图片
im.files 负责文件的上传和下载:上传头像、群文件,或先上传再自己组织消息;把消息中的文件地址换成可以加载的下载地址,或下载到本地;查看和删除群文件。deeprespond_im_flutter 另外提供显示图片的 DRImage 和录制语音的 VoiceRecorder。
发送图片、语音、视频和文件消息时不需要先调用本页的方法:把文件直接交给 im.messages.send(),SDK 会完成上传前的处理、上传和发送,App 被杀后重新启动也会继续上传,见消息。本页的方法用于消息之外的场景。
文件的用途、大小上限、保留时间、格式检查等规则与服务端相同,见服务端的文件概述。
文件的用途与上限
每次上传都要指定用途 purpose(常量见 UploadPurpose),它决定允许的类型、大小上限和谁能下载:
purpose | 常量 | 用于 | 允许的 kind | 大小上限 | 上传后得到 |
|---|---|---|---|---|---|
attachment | UploadPurpose.attachment | 消息中的图片、语音、视频、文件 | image、voice、video、file | 运行策略 max_upload_bytes(默认 100 MB);图片另不超过 20 MB,语音另不超过 5 MB | 文件地址,要换取下载地址才能加载 |
user_avatar | UploadPurpose.userAvatar | 本人的头像 | image | 5 MB | 公开地址,可以直接加载 |
group_avatar | UploadPurpose.groupAvatar | 群头像 | image | 5 MB | 公开地址 |
chatroom_avatar | UploadPurpose.chatroomAvatar | 聊天室的封面 | image | 5 MB | 公开地址 |
group_file | UploadPurpose.groupFile | 群文件 | image、video、file | 同 attachment | 文件地址 |
类型 kind 的常量见 FileKind(FileKind.image、voice、video、file)。
上传前 SDK 先在本地检查用途、类型和大小,不符合的不会开始上传,以 local_validation 拒绝:大小超出时 details['reason'] 为 too_large,details['max_bytes'] 为上限;用途与类型不匹配时为 invalid_purpose。服务端还会检查应用的存储额度,额度已满时以 limit_exceeded(reason 为 storage_quota)拒绝。
应用所在的部署没有提供文件服务时(运行配置的 media_url_prefix 为空),上传以 unsupported(reason 为 media_disabled)拒绝。
本地文件 DRFile
上传和发送的文件都用 DRFile 表示,有两种来源:
import 'package:deeprespond_im/deeprespond_im.dart';
// 本地文件的路径,如相册、文件选择器返回的路径
final photo = DRFile(pickedPath, mimeType: 'image/jpeg');
// 内存中的内容,如截图:必须给出文件名,SDK 先写入临时文件再上传
final shot = DRFile.fromBytes(pngBytes, name: 'screenshot.png', mimeType: 'image/png');- 路径:SDK 只读取,不修改也不删除原文件。路径不存在时以
local_validation(reason为invalid_value,field为file)拒绝。 mimeType:可选,用于判断默认的类型(见下文);省略时按文件开头的内容判断。name:文件名,file类型和群文件下载时以这个名字保存;省略时取路径中的文件名。
上传文件
im.files.upload() 立即返回一个上传任务 UploadTask:result 在上传完成后得到 FileInfo,progress 发出上传进度,cancel() 取消上传。
final task = im.files.upload(
DRFile(pickedPath, mimeType: 'application/pdf'),
purpose: UploadPurpose.attachment,
kind: FileKind.file,
onProgress: (loaded, total) => debugPrint('${(loaded * 100 / total).round()}%'),
);
final info = await task.result;
debugPrint('${info.url} ${info.size}');- 类型:省略
kind时,给出了mimeType的按它的大类判断:image/*为image,video/*为video,audio/*为voice,其他为file;没有给出mimeType的按文件开头的内容判断,不认识的为file。 - 文件名:省略
name时用DRFile的名字。SDK 按服务端的规则处理文件名:去掉控制字符、/、\和改变文字方向的字符,超长的截断到 255 个字符,为空时用默认名(如file.pdf)。 - 进度:
onProgress(loaded, total)和task.progress(广播流,只收到订阅之后的进度,上传结束后关闭)按字节报告,total是处理后实际上传的大小(图片压缩后可能比原文件小)。两者任选其一。 - 返回的
FileInfo中,url是文件地址(头像为公开地址),图片另有缩略图地址thumbnailUrl;size、width、height以它为准。
上传只得到文件,不发送消息,也不修改资料。设置头像见用户、好友与在线状态和群组,如上传后调用 im.users.updateMe(avatarUrl: info.url)。
取消上传
调用任务的 cancel(),或传入 cancel: DRCancelToken 后取消这个令牌。result 以 aborted 失败,SDK 同时通知服务端取消这次上传。
final token = DRCancelToken();
final task = im.files.upload(DRFile(pickedPath), purpose: UploadPurpose.attachment, cancel: token);
// 用户点击“取消”时:task.cancel() 或 token.cancel()
task.cancel();
try {
await task.result;
} on DRException catch (e) {
if (e.code == LocalErrorCode.aborted) showToast('已取消');
}大文件与中断后的处理
- 不超过 32 MB 的文件一次上传;更大的分片上传,每片 16 MB,同时上传 3 片。
- 文件内容直接传到存储服务,不经过 IM 服务的接口,请求不带 IM 的登录凭证。
- 网络中断:每次上传内容遇到网络错误时自动重试 3 次;完成上传时服务端报告缺少内容的,SDK 自动补传缺少的分片后再完成。服务端繁忙、限流(等待时间不超过 60 秒的)时 SDK 等待后重试,
result只在得到最终结果时完成。 - 切到后台、App 被杀:
upload()的上传只在内存中进行,状态不保存。切到后台后在系统允许的时间内继续,之后可能因 App 被挂起而失败;App 被杀后不能续传,需要重新上传。没有完成的上传由服务端过期后清理(不计入存储用量)。随消息发送的附件不同:它们保存在发送队列中,App 重新启动后从中断处续传,见消息。
上传前的处理
upload() 和发送消息时,SDK 在上传前自动处理文件。处理用系统能力完成:iOS、macOS 用 ImageIO 和 AVFoundation,Android 用 BitmapFactory、ImageDecoder 和 MediaMetadataRetriever;Windows、Linux 用 Dart 实现的图片处理(较慢,在独立的 isolate 中执行)。
| 内容 | 处理 |
|---|---|
| 图片 | 按 EXIF 的方向转正;长边超过 2048 像素的缩小到 2048;重新编码为 JPEG(质量 85),有透明通道的编码为 PNG。重新编码的结果不含 EXIF 等元数据(如拍摄地点)。GIF 不压缩。长边和质量可以用 MediaOptions 修改 |
图片(original: true) | JPEG、PNG、GIF、WebP 不缩小、不重新编码,只去掉 JPEG 的 EXIF(保留方向)和 XMP、PNG 的文本块,保留颜色配置。服务端不接受的格式(BMP、TIFF、AVIF 等)不缩小地转为 JPEG 或 PNG |
| HEIC、HEIF | 转为 JPEG:iOS、macOS,Android 8 及以上。Windows、Linux 不能转换,不能作为图片上传 |
| 视频 | 不转码。MP4 和 MOV 去掉拍摄地点等元数据(需要时复制一份再修改,不改变原文件,大小不变);读出宽高和时长,截取第一帧作为封面(Windows、Linux 不截取封面) |
| 语音 | 不转码,只检查格式 |
| 文件 | 原样上传 |
格式按文件开头的内容判断,不看扩展名。服务端接受的格式:图片 JPEG、PNG、GIF、WebP;视频 MP4、MOV、WebM、3GP;语音 AAC、M4A、MP3、AMR、3GP、Ogg、WAV、WebM。
修改图片压缩的参数(创建客户端时):
import 'package:deeprespond_im_flutter/deeprespond_im_flutter.dart';
final client = await DRClient.create(DRClientOptions(
appKey: '1575529652#demo',
apiUrl: Uri.parse('https://im.example.com'),
platform: DRFlutterPlatform.instance,
media: const MediaOptions(imageMaxSide: 1600, jpegQuality: 80),
));以下情况不能按请求的类型上传,upload() 以 local_validation 拒绝,由你决定是否改为 kind: FileKind.file 重新上传(随消息发送的附件由 SDK 自动改为文件消息):
reason | 情况 |
|---|---|
image_too_large | 图片超过 5000 万像素,或处理后仍超过 20 MB |
unsupported_type | 格式不被服务端接受,如 MKV、AVI 视频,或不能转换的 HEIC |
Future<FileInfo> uploadImage(DRClient im, DRFile file) async {
try {
return await im.files.upload(file, purpose: UploadPurpose.attachment, kind: FileKind.image).result;
} on DRException catch (e) {
if (e.code == LocalErrorCode.localValidation && (e.reason == 'image_too_large' || e.reason == 'unsupported_type')) {
return im.files.upload(file, purpose: UploadPurpose.attachment, kind: FileKind.file).result; // 作为文件上传
}
rethrow;
}
}随消息发送的附件
用 im.messages.send() 发送图片、语音、视频和文件时,附件按同样的规则处理,另有以下不同:
- 自动改为文件消息:图片超过 5000 万像素或处理后仍超过 20 MB、不能转换的 HEIC、服务端不接受的图片、视频和语音格式,不会以
local_validation拒绝,而是自动作为file消息发送。 - 附件的保存:写入发送队列时,SDK 把附件复制到自己的目录中(
DRFile.fromBytes的内容写成文件),所以send()返回后可以删除原文件,App 被杀后重新启动也能从中断处继续上传。超过OutboxOptions.persistAttachmentMaxBytes(默认 200 MB)的附件不复制,只记下原文件的路径;如果原文件在发出之前被删除或移动,这条消息以send_abandoned(reason为attachment_lost)失败,需要用户重新选择文件。 - 发送成功或删除消息(
im.messages.discard())后,SDK 删除自己保存的副本。
final client = await DRClient.create(DRClientOptions(
appKey: '1575529652#demo',
apiUrl: Uri.parse('https://im.example.com'),
platform: DRFlutterPlatform.instance,
outbox: const OutboxOptions(persistAttachmentMaxBytes: 500 * 1024 * 1024), // 不超过 500 MB 的附件都复制
));消息中的附件字段和发送方法见消息。
单独使用处理结果
im.files.prepare() 只做上面的处理,不上传,也不需要登录。可以用它预览压缩后的图片、取得视频的封面和时长,或在上传前判断是否要改为文件:
final workDir = await Directory.systemTemp.createTemp('preview_');
try {
final prepared = await im.files.prepare(DRFile(pickedPath), FileKind.video, workDir: workDir.path);
if (prepared.kind != FileKind.video) {
showToast('这个视频格式不能在线播放,将作为文件发送');
} else {
debugPrint('${prepared.width}x${prepared.height},${prepared.durationSeconds} 秒,封面 ${prepared.thumbnailPath}');
}
} finally {
await workDir.delete(recursive: true); // 处理后的文件由你删除
}prepare()不会拒绝不能按请求类型处理的文件,而是在结果的kind中给出建议的类型(file),downgraded说明原因。- 处理后的文件写在
workDir中,省略时在系统临时目录中新建一个,用完后由你删除。不需要改写的文件(如不改动的视频、语音和文件),path就是原文件的路径,所以请删除workDir,不要直接删除path。 upload()不会上传视频的封面;需要封面时,把thumbnailPath以kind: FileKind.image另外上传。
显示与下载文件
文件地址与下载地址
消息附件和群文件的地址(文件地址)不能直接加载,要先换成短期有效的下载地址。头像的公开地址、你自己的外部地址可以直接使用。im.files.isFileUrl(url) 判断一个地址是不是需要换取的文件地址:
Future<String> playableUrl(DRClient im, String url) async {
if (!im.files.isFileUrl(url)) return url;
final resolved = await im.files.resolveUrl(url);
return resolved.downloadUrl; // 交给视频、音频播放器
}- 缩略图:图片的缩略图地址(文件地址加
/thumb,即FileInfo的thumbnailUrl和图片消息body中的thumbnail_url)同样要换取,得到长边不超过 480 像素的缩略图。 - 有效期:下载地址的有效期为 60 到 90 分钟,
expiresAt为到期时间。SDK 按到期时间缓存(保存在本地数据库中,App 重新启动后仍可使用),到期前 5 分钟内再调用时重新换取。不要把下载地址保存下来或写进消息,保存不变的文件地址,每次使用时调用resolveUrl。 - 合并请求:20 毫秒内的多次换取合并成一次请求(每次最多 100 个),列表中逐个调用
resolveUrl即可。也可以用resolveUrls一次换取多个,每项单独给出结果。 - 换取每个用户每分钟最多 600 个文件,单项被限流时 SDK 在 5 到 10 秒后自动重试。
- 视频、语音的下载地址支持
Range请求,交给播放器后可以拖动进度。
换取失败时的错误码:
code | 说明 | 建议的提示 |
|---|---|---|
file_expired | 文件已过期或已被删除 | 文件已过期 |
not_found | 地址无效,或文件已删除很久 | 文件已过期 |
file_blocked | 文件因违规被屏蔽 | 文件已被屏蔽 |
not_group_member | 群文件,本人已不是群成员 | 你已不在群中 |
final results = await im.files.resolveUrls(urls);
for (final r in results) {
final error = r.error;
if (error != null) {
debugPrint('${r.url}:${error.code == 'file_blocked' ? '文件已被屏蔽' : '文件已过期'}');
} else {
debugPrint('${r.url} → ${r.downloadUrl}');
}
}用 DRImage 显示图片
DRImage 是 Flutter 的 ImageProvider:传入文件地址或缩略图地址,它自动换取下载地址、下载并按地址缓存在磁盘中;不是文件地址的(如外部的头像地址)直接请求。可以用在任何接受 ImageProvider 的地方(Image、CircleAvatar、DecorationImage):
import 'package:deeprespond_im_flutter/deeprespond_im_flutter.dart';
import 'package:flutter/material.dart';
class ImageBubble extends StatelessWidget {
const ImageBubble({super.key, required this.im, required this.message});
final DRClient im;
final MessageView message;
@override
Widget build(BuildContext context) {
final body = message.body ?? const {};
final url = (body['thumbnail_url'] ?? body['url']) as String?; // 列表中先显示缩略图
if (url == null) return const SizedBox(width: 120, height: 120);
return Image(
image: DRImage(url, client: im),
width: 200,
fit: BoxFit.cover,
loadingBuilder: (context, child, progress) => progress == null ? child : const SizedBox(width: 200, height: 150, child: Center(child: CircularProgressIndicator())),
errorBuilder: (context, error, stack) => const Icon(Icons.broken_image),
);
}
}- 图片内容缓存在系统缓存目录的
deeprespond_im/images中,超过MediaOptions.imageCacheBytes(默认 200 MB)时按最近使用淘汰到 80%。同一地址同时只下载一次。 - 下载失败(如文件已过期、已被屏蔽)时由
errorBuilder显示,error是DRException。 DRImageCache.instance.clear()清除全部缓存,可以在设置页中提供“清除缓存”,或在退出登录时调用。- 视频的封面、文件的预览同样可以用
DRImageCache.instance.fetch(im, url)取得缓存在本地的文件。
下载到本地
im.files.download() 把文件保存到指定的路径,可以报告进度、中途取消;文件地址先换取下载地址,不是文件地址的直接请求这个地址。请求不带任何登录凭证。
final token = DRCancelToken();
final saved = await im.files.download(
fileUrl,
'$downloadsDir/$fileName',
onProgress: (loaded, total) => debugPrint(total > 0 ? '${(loaded * 100 / total).round()}%' : '$loaded 字节'),
cancel: token,
);
debugPrint('已保存到 ${saved.path},${saved.size} 字节');- 内容先写入
savePath.part,完成后改名为savePath;失败或取消时删除.part,不会留下不完整的文件。所在的目录不存在时自动创建。 - 保存的位置由你决定,如
path_provider的应用文档目录;SDK 不清理下载的文件。
群文件
群文件是群成员共享的文件,默认不过期(应用可以设置保留天数),群解散时全部删除。群成员可以上传;上传者本人、群主和管理员可以删除。应用在运行策略中关闭了群文件(运行配置的 group_file_enabled 为 false)时,客户端不能上传,upload() 以 permission_denied(reason 为 group_file_disabled)拒绝。
// 上传:purpose 为 group_file,必须带 groupId;不能是语音
final info = await im.files
.upload(DRFile(pickedPath), purpose: UploadPurpose.groupFile, groupId: groupId, name: '会议纪要.pdf')
.result;
// 列表:按上传时间从新到旧
final page = await im.files.groupFiles(groupId, limit: 50);
debugPrint('共 ${page.fileCount} 个文件,${(page.usedBytes / 1024 / 1024).toStringAsFixed(1)} MB');
for (final f in page.items) {
debugPrint('${f.name} ${f.size} ${f.ownerNickname} ${f.createdAt}');
}
// 删除
await im.files.deleteGroupFile(groupId, info.fileId);- 每个群最多 1000 个群文件。只有当前的群成员能查看列表和下载,离开群之后不能再下载。
- 群文件的变化没有实时通知,打开群文件页面时重新查询。
- 群文件的地址同样要换取下载地址,或用
download()下载。
录制语音
deeprespond_im_flutter 的 VoiceRecorder 用系统能力录音(iOS 的 AVAudioRecorder、Android 的 MediaRecorder),直接录成 AAC(M4A 容器,单声道 64 kbps),各端都能播放,可以直接作为语音消息发送。录音只支持 iOS 和 Android,其他平台以 unsupported 拒绝。
import 'package:deeprespond_im_flutter/deeprespond_im_flutter.dart';
// 用户按下按钮时
try {
await VoiceRecorder.start(); // 第一次使用时请求麦克风权限
} on DRException catch (e) {
if (e.code == LocalErrorCode.permissionRequired) {
showToast('请在系统设置中允许使用麦克风');
} else if (e.code == LocalErrorCode.deviceError) {
showToast('麦克风不可用');
}
return;
}
// 用户松开按钮时
final recording = await VoiceRecorder.stop();
await im.messages.send(SendTarget.user(peer), recording.toContent());
// 用户上滑取消时改为调用
// await VoiceRecorder.cancel();VoiceRecorder的方法都是静态的,同时只能有一个录音:正在录音时再调用start()以invalid_state(reason为recording)拒绝,没有在录音时调用stop()以invalid_state(not_recording)拒绝。cancel()放弃录音并删除文件,没有在录音时什么也不做。- 录音文件默认写在系统的临时目录中,可以用
start(directory: ...)指定目录。发送后 SDK 已在发送队列中保存了自己的副本,录音文件可以由你删除。 - 时长不足 1 秒的按 1 秒。录音器不限制时长,语音文件不能超过 5 MB(约 10 分钟),请在界面上限制最长的录音时间(如 60 秒)。
- 不用
toContent()时,也可以自己组织:OutgoingContent.voice(DRFile(recording.path, mimeType: 'audio/mp4'), duration: recording.duration)。
麦克风权限
start() 在没有权限时先向用户请求;用户拒绝的以 permission_required 拒绝,details['permissions'] 为 ['microphone']。用户选择了“不再询问”后系统不会再弹出请求,请引导用户到系统设置中开启。
- iOS:必须在
Info.plist中写明使用麦克风的理由,否则 App 在请求权限时会被系统终止:
<key>NSMicrophoneUsageDescription</key>
<string>用于录制语音消息</string>- Android:
RECORD_AUDIO权限已在deeprespond_im_flutter的清单中声明,构建时自动合并,不需要另外配置。
接口参考
im.files.upload()
上传一个文件。上传前按上传前的处理处理文件。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
file | DRFile | 是 | 文件 |
purpose | String | 是(命名参数) | 用途,见 UploadPurpose:attachment、user_avatar、group_avatar、chatroom_avatar、group_file |
kind | String? | 否(命名参数) | 类型,见 FileKind;默认按 mimeType 或内容判断。三种头像只能是 image,群文件不能是 voice |
name | String? | 否(命名参数) | 文件名,默认为 DRFile 的名字 |
groupId | String? | 群文件必填(命名参数) | 群 ID;其他用途不能带 |
original | bool | 否(命名参数) | 图片不压缩,只去掉元数据。默认 false |
onProgress | void Function(int loaded, int total)? | 否(命名参数) | 上传进度 |
cancel | DRCancelToken? | 否(命名参数) | 用于取消 |
返回值:UploadTask,result 为 Future<FileInfo>。
可能的错误(result 以 DRException 失败):
code | 说明 |
|---|---|
not_signed_in | 没有登录 |
local_validation | reason:too_large 超出大小上限;invalid_purpose 用途与类型不匹配;required(field 为 group_id)群文件缺少群 ID;invalid_value 其他用途带了 groupId,或 purpose、kind 不认识,或文件不存在;image_too_large、unsupported_type 不能按请求的类型上传 |
unsupported | 部署没有提供文件服务(media_disabled) |
permission_denied | 应用关闭了群文件(group_file_disabled) |
aborted | 已取消 |
limit_exceeded | 应用的存储额度已满(storage_quota),或群文件数已满 |
rate_limited | 上传过于频繁,需要等待的时间超过 60 秒(通常是当天的额度已用完) |
invalid_argument | 服务端检查不通过,如 unsupported_type、invalid_image |
not_found、not_group_member | 群不存在,或本人不是群成员 |
storage_error | 读写本地文件出错,如临时目录空间不足 |
network_error、timeout | 自动重试后仍失败 |
im.files.isFileUrl()
判断一个地址是不是需要换取下载地址的文件地址(包括缩略图地址)。同步执行。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
url | String | 是 | 地址 |
返回值:bool。部署没有提供文件服务、或还没有登录(没有运行配置)时一律为 false。
im.files.resolveUrl()
把一个文件地址换成下载地址,结果按有效期缓存。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
url | String | 是 | 文件地址或缩略图地址 |
返回值:Future<DownloadUrl>。
可能的错误:file_expired、file_blocked、not_found、not_group_member,not_signed_in,以及网络错误。
im.files.resolveUrls()
把多个文件地址换成下载地址,每项单独给出结果。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
urls | List<String> | 是 | 文件地址 |
返回值:Future<List<ResolvedUrl>>,按请求的顺序。
im.files.download()
把文件下载到本地。文件地址先换取下载地址,其他地址直接请求(只支持 http、https)。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
url | String | 是 | 地址 |
savePath | String | 是 | 保存的路径,已有的文件被覆盖 |
onProgress | void Function(int loaded, int total)? | 否(命名参数) | 下载进度;服务端没有给出大小时 total 为 0 |
cancel | DRCancelToken? | 否(命名参数) | 用于取消 |
返回值:Future<DownloadedFile>。
可能的错误:换取下载地址的错误;local_validation(savePath 为空,或地址不是 http、https);not_found(下载地址返回 404)、download_failed、internal(存储服务返回 5xx)、network_error、aborted。
im.files.groupFiles()
查询群文件列表,按上传时间从新到旧。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
groupId | String | 是 | 群 ID |
cursor | String? | 否(命名参数) | 下一页的游标,即上一页的 nextCursor |
limit | int? | 否(命名参数) | 每页条数,默认 20,最大 100 |
返回值:Future<GroupFilesPage>。
可能的错误:not_found、not_group_member。
im.files.deleteGroupFile()
删除一个群文件:上传者本人、群主或管理员。已删除的同样成功。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
groupId | String | 是 | 群 ID |
fileId | String | 是 | 文件 ID |
返回值:Future<void>。
可能的错误:permission_denied(not_uploader:不是上传者,也不是群主或管理员)、not_found、not_group_member、file_blocked(被屏蔽的文件不能删除)。
im.files.prepare()
按上传前的规则处理文件,不上传。不需要登录。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
file | DRFile | 是 | 文件 |
kind | String | 是 | 希望的类型,见 FileKind |
original | bool | 否(命名参数) | 图片不压缩,只去掉元数据。默认 false |
workDir | String? | 否(命名参数) | 处理后的文件写在这个目录中;默认在系统临时目录中新建一个 |
返回值:Future<PreparedFile>。
可能的错误:local_validation(kind 不认识,或文件不存在)。
VoiceRecorder.start()
静态方法。请求麦克风权限(没有时)并开始录音。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
directory | String? | 否(命名参数) | 录音文件的目录,默认系统的临时目录 |
返回值:Future<void>。
可能的错误:unsupported(不是 iOS、Android,reason 为 platform)、invalid_state(recording:已在录音)、permission_required(用户没有允许使用麦克风)、device_error(麦克风不可用,如被其他 App 占用)。
VoiceRecorder.stop()
静态方法。结束录音,释放麦克风。
返回值:Future<VoiceRecording>。
可能的错误:invalid_state(not_recording:没有在录音)、device_error。
VoiceRecorder.cancel()
静态方法。放弃录音并删除录音文件;没有在录音时什么也不做。
返回值:Future<void>。
VoiceRecorder.isRecording
静态属性,bool:是否正在录音。
DRImageCache
DRImageCache.instance 是 DRImage 使用的磁盘缓存。
| 方法 | 说明 |
|---|---|
fetch(DRClient client, String url, {onProgress}) | 返回 Future<File>:缓存中有的直接返回,没有的下载后返回。错误同 im.files.download() |
trim(int maxBytes) | 超过 maxBytes 时按最近使用淘汰到 80%。SDK 下载时会自动按 imageCacheBytes 调用 |
clear() | 清除全部缓存 |
数据结构
DRFile
| 构造函数 / 字段 | 类型 | 说明 |
|---|---|---|
DRFile(path, {mimeType, name}) | 本地文件 | |
DRFile.fromBytes(bytes, {required name, mimeType}) | 内存中的内容 | |
path | String? | 文件路径 |
bytes | Uint8List? | 文件内容 |
mimeType | String? | MIME 类型,用于判断默认的类型 |
name | String? | 文件名 |
FileInfo
上传完成后的文件信息。原始 JSON 在 raw 中。
| 字段 | 类型 | 说明 |
|---|---|---|
fileId | String | 文件 ID |
purpose | String | 用途 |
kind | String | 类型:image、voice、video、file |
status | String | 状态,上传完成后为 active |
url | String | 消息附件和群文件为文件地址,头像为公开地址 |
thumbnailUrl | String? | 缩略图地址,只有图片类型的消息附件和群文件有 |
name | String? | 文件名 |
contentType | String | 服务端按内容识别出的格式,如 image/jpeg |
size | int | 大小,字节 |
width | int? | 图片和头像的宽度,像素 |
height | int? | 图片和头像的高度,像素 |
owner | String? | 上传者的用户名 |
groupId | String? | 群文件所在的群 ID |
via | String | 上传方式,客户端上传的为 client |
createdAt | DateTime? | 上传时间,保留期从这时算起 |
expiresAt | DateTime? | 过期时间:消息附件和设置了保留天数的群文件有值 |
GroupFile
群文件,在 FileInfo 的字段之外另有:
| 字段 | 类型 | 说明 |
|---|---|---|
ownerNickname | String? | 上传者的昵称 |
ownerAvatarUrl | String? | 上传者的头像 |
GroupFilesPage
| 字段 | 类型 | 说明 |
|---|---|---|
items | List<GroupFile> | 这一页的群文件 |
nextCursor | String? | 下一页的游标;为 null 表示没有下一页 |
fileCount | int | 这个群的群文件总数 |
usedBytes | int | 这个群的群文件占用的字节数 |
UploadTask
| 成员 | 类型 | 说明 |
|---|---|---|
result | Future<FileInfo> | 上传的结果;取消后以 aborted 失败 |
progress | Stream<UploadProgress> | 上传进度(广播流),上传结束后关闭 |
cancel() | void | 取消上传;已经结束的没有影响 |
UploadProgress 有 loaded、total 两个字段(int,字节)。
PreparedFile
| 字段 | 类型 | 说明 |
|---|---|---|
path | String | 处理后要上传的文件;不需要改写时为原文件的路径 |
kind | String | 建议的类型;不能按请求的类型处理时为 file |
name | String | 处理后的文件名;图片重新编码后扩展名随之改变 |
format | String | 小写的格式名,如 jpeg、png、mp4;file 为扩展名 |
contentType | String | MIME 类型 |
size | int | 处理后的大小,字节 |
width | int? | 图片、视频的宽度,读不出时为 null |
height | int? | 图片、视频的高度 |
durationSeconds | int? | 视频的时长,秒 |
thumbnailPath | String? | 视频第一帧的 JPEG 图片;Windows、Linux 没有 |
downgraded | String? | kind 改为 file 的原因:image_too_large 或 unsupported_type |
DownloadUrl
| 字段 | 类型 | 说明 |
|---|---|---|
downloadUrl | String | 下载地址,直接请求,不带任何登录凭证 |
expiresAt | DateTime | 到期时间(UTC) |
ResolvedUrl
| 字段 | 类型 | 说明 |
|---|---|---|
url | String | 请求的文件地址 |
downloadUrl | String? | 下载地址,失败时为 null |
expiresAt | DateTime? | 下载地址的到期时间 |
error | DRException? | 失败时的错误,见文件地址与下载地址 |
DownloadedFile
| 字段 | 类型 | 说明 |
|---|---|---|
path | String | 保存的路径 |
size | int | 写入的字节数 |
contentType | String? | 服务端返回的 Content-Type |
MediaOptions
DRClientOptions.media,创建客户端时给出。
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
imageMaxSide | int | 2048 | 图片压缩后的最长边,像素 |
jpegQuality | int | 85 | 重新编码 JPEG 的质量,1~100 |
imageCacheBytes | int | 200 MB | DRImage 磁盘缓存的上限,字节 |
OutboxOptions
DRClientOptions.outbox,创建客户端时给出。
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
maxAge | Duration | 48 小时 | 未发出的消息最多保留多久,超过的以 send_abandoned(expired)标为失败 |
persistAttachmentMaxBytes | int | 200 MB | 不超过这个大小的附件复制到 SDK 的目录中保存,见随消息发送的附件 |
VoiceRecording
| 成员 | 类型 | 说明 |
|---|---|---|
path | String | 录音文件(M4A) |
duration | Duration | 时长,至少 1 秒 |
toContent() | OutgoingContent | 作为语音消息发送的内容 |
