Files, images and voice recording
im.files handles uploading and downloading attachments, avatars and group files, plus voice recording. A file's URL is a long-lived identifier you can store; to actually download, the SDK exchanges it for a short-lived download URL. For purposes and size limits, see Media files in the server docs.
| You want to | See |
|---|---|
| Get a file to upload from a file, memory or the system picker | File sources |
| Send image and file messages | Uploading with messages |
| Upload avatars and group files | Avatars and group files |
| Download files, show images | Downloads and cache, Coil image loading |
| Adjust image compression | Image and video preprocessing |
| Hold to talk | Voice recording |
File sources
import com.deeprespond.im.*
import java.io.File
val local = DRFile.fromFile(File(context.cacheDir, "report.pdf"), "report.pdf", "application/pdf")
val memory = DRFile.fromBytes("你好".toByteArray(), "note.txt", "text/plain")
val selected = DRFile.fromUri(context, uri)fromUriis an Android-only extension function;import com.deeprespond.im.*includes it.- Your app provides the file picker. If you need a system URI to keep working after the app restarts, request persistent access the way Android requires.
- Once a message is in the send queue, the SDK keeps its own copy of the file; until then, the URI must stay readable.
- There's also
fromSourcefor adapting other platforms; regular apps only need the three above.
Uploading with messages
im.messages.send(SendTarget.User("bob"), OutgoingContent.Image(file))
im.messages.send(SendTarget.User("bob"), OutgoingContent.File(file, name = "报告.pdf"))The SDK uploads and sends, and progress is in the local message's status. To upload a file without sending a message, upload returns an upload task:
import com.deeprespond.im.*
val task = im.files.upload(file, UploadPurpose.ATTACHMENT, FileKind.FILE, name = "报告.pdf")
val uploaded = task.await()
println(uploaded.url)UploadTask.progressis aStateFlow;Progress.loaded/totalare bytes, andtotalis-1when the size is unknown.- After
task.cancel(),awaitfails withaborted. - Cancelling the coroutine that waits doesn't cancel the upload: it only stops waiting for the result while the upload continues. To really cancel, call
task.cancel().
Avatars and group files
Upload avatars with the matching purpose, then write the returned url to the matching profile:
| Purpose | Where it goes |
|---|---|
USER_AVATAR | users.updateMe |
GROUP_AVATAR | groups.update |
CHATROOM_AVATAR | The chat room's manage.update |
Group files need a groupId:
val task = im.files.upload(file, UploadPurpose.GROUP_FILE, FileKind.FILE, groupId = groupId)
val uploaded = task.await()
val page = im.files.groupFiles(groupId)
println(uploaded.fileId)
println(page.fileCount)groupFiles returns items, nextCursor, fileCount (number of files) and usedBytes (bytes used); deleteGroupFile deletes a group file (the uploader, the owner or an admin). Uploading a group file doesn't send a message.
Downloads and cache
import com.deeprespond.im.*
val task = im.files.download(DownloadSource.File(fileUrl), conflict = Conflict.RENAME)
val saved = task.await()
println(saved.name)- Download sources can also be
DownloadSource.Thumbnail(url)(a thumbnail) andDownloadSource.Message(message)(a message's attachment). saveTosets the target directory (aFile);Conflictdecides what happens with an existing file of the same name:RENAME,OVERWRITEorFAIL.DownloadTask.cancel()deletes the unfinished temporary file, andawaitfails withaborted.resolveUrlexchanges for a download URL, returningdownloadUrland its expiryexpiresAt;resolveUrlsdoes several at once, each with its ownerror. Download URLs expire, so don't store them as long-term image identifiers.localPath(url)returns the local path of an image the SDK cached for the current user, andclearImageCache()clears the image cache.- Files from groups the user has left, or blocked by content moderation, may no longer be downloadable.
Image and video preprocessing
MediaOptionscompresses images by default: long side up to 2048 pixels, JPEG quality 85, and a 300 MiB image cache.compressImages = falseonly turns off compression; the default metadata stripping (such as location) still happens.- A custom
preprocessruns on the IO thread and must strip any unwanted metadata itself. files.prepare(file, kind, original)processes without uploading and returns aPreparedFile: the processed file,kind,format,size, width and height,duration,thumbnail,downgradedandmetadataKept. If the file can't be processed as its original media type,kindmay becomeFILE; show it based on the return value.- If a
PreparedFileisn't uploaded or sent in the end, delete the temporary files you don't need. DRFile.withKeepMetadata()explicitly keeps metadata; use it only when your business really needs it.
Coil image loading
Add im-coil, use Coil 3 and register the SDK component:
import coil3.ImageLoader
import com.deeprespond.im.coil.*
val loader = ImageLoader.Builder(context)
.components { addDRImage(im) }
.build()
val model = DRImage(fileUrl)Pass model to your Coil ImageView or Compose component. DRImage.of(message, thumbnail = true) takes the thumbnail from a message, or null if the message has no image. The image cache is separated by the current user; don't use a short-lived download URL as a Coil model.
Voice recording
Declare RECORD_AUDIO in the manifest and get the microphone permission before recording:
import com.deeprespond.im.*
import kotlin.time.Duration.Companion.seconds
val recording = VoiceRecorder.start(context, maxDuration = 60.seconds)
// 用户点击停止时;不是开始后立即停止。
val voice = recording.stop()
im.messages.send(SendTarget.User("bob"), voice.toContent())Recording.levelis aFlowof the volume, for drawing a level meter.endedmeans the recording has finished, withreasonMAX_DURATION(reached the maximum length) orINTERRUPTED. Going to the background or losing audio focus can interrupt recording, so listen toendedto update the UI.cancel()discards and deletes the recording.- Recording can't start while another recording is running, during a call, without the microphone permission, or when the device errors.
- Output is AAC (m4a), with no extra FFmpeg needed.
For the full file API, see FilesModule; for recording, see Recording.
