KMM项目中如何在iOS端用Ktor上传30MB以上的视频?
KMM大视频流式上传解决方案
针对30MB+的大视频上传,核心思路是流式上传避免全量加载文件到内存,通过KMM的expect/actual机制封装平台特定的文件流处理,统一跨平台上传逻辑。
1. 跨平台文件封装(Common层)
先定义平台无关的文件抽象,通过expect声明类,两端分别实现:
// commonMain/kotlin/com/yourpackage/PlatformFile.kt expect class PlatformFile(path: String) { fun asUploadPart(): PartData.FileItem } // 共用上传接口 suspend fun uploadVideo( file: PlatformFile, uploadUrl: String, contentType: ContentType = ContentType.Video.MP4 ): Result<Unit> { return runCatching { val client = HttpClient(getPlatformClient()) client.post(uploadUrl) { setBody(MultiPartFormDataContent(formData { append( "video_file", file.asUploadPart(), headersOf(HttpHeaders.ContentType, contentType.toString()) ) })) // 添加其他必要请求头,比如Authorization headers { append(HttpHeaders.Authorization, "Bearer YOUR_TOKEN") } } } } expect fun getPlatformClient(): HttpClientEngine
2. Android端实现(AndroidMain)
Android直接用File和输入流转换为ByteReadChannel:
// androidMain/kotlin/com/yourpackage/PlatformFile.kt actual class PlatformFile actual constructor(path: String) { private val file = File(path) actual fun asUploadPart(): PartData.FileItem { return PartData.FileItem( provider = { file.inputStream().toByteReadChannel() }, headers = Headers.build { append(HttpHeaders.ContentDisposition, "filename=\"${file.name}\"") } ) } } actual fun getPlatformClient(): HttpClientEngine = OkHttp.create()
3. iOS端实现(iOSMain)
iOS通过NSURL获取文件输入流,再转换为Kotlin协程的ByteReadChannel:
// iosMain/kotlin/com/yourpackage/PlatformFile.kt import kotlinx.coroutines.io.reader import kotlinx.coroutines.io.writeFully import kotlinx.coroutines.io.ByteReadChannel import platform.Foundation.NSFileHandle import platform.Foundation.NSURL import platform.Foundation.NSInputStream actual class PlatformFile actual constructor(path: String) { private val fileUrl = NSURL.fileURLWithPath(path) actual fun asUploadPart(): PartData.FileItem { val inputStream = NSFileHandle.fileHandleForReadingFromURL(fileUrl, null)?.inputStream() ?: error("Failed to open file for reading: $path") return PartData.FileItem( provider = { inputStream.toByteReadChannel() }, headers = Headers.build { append(HttpHeaders.ContentDisposition, "filename=\"${fileUrl.lastPathComponent}\"") } ) } } // 扩展方法:将NSInputStream转换为ByteReadChannel private fun NSInputStream.toByteReadChannel(): ByteReadChannel = reader { val buffer = ByteArray(8192) // 8KB缓冲区,可根据需求调整 open() try { while (true) { val bytesRead = read(buffer, 0, buffer.size.toULong()).toInt() if (bytesRead <= 0) break channel.writeFully(buffer, 0, bytesRead) } } finally { close() } } actual fun getPlatformClient(): HttpClientEngine = Ios.create()
关键说明
- 流式处理:两端均使用固定大小缓冲区读取文件,避免加载全量文件到内存,解决大文件OOM问题。
- MultiPart上传:采用标准的表单上传格式,适配大多数后端接口,同时支持断点续传扩展(需后端配合)。
- 权限处理:iOS端需确保文件路径在沙箱可访问范围内(如Documents目录),并在Info.plist中声明必要的文件访问权限。
内容的提问来源于stack exchange,提问作者Akshat
相关产品推荐
相关产品推荐

