项目概述

视频处理与流媒体是现代应用的高级功能。在 KMP 框架下,我们需要实现跨平台的视频播放、录制、转码和流媒体传输。本文详细介绍了如何在 Kotlin Multiplatform 中实现高效的视频处理系统,包括视频播放、录制、转码和鸿蒙系统特定的媒体处理方式。

在实际业务中,视频相关能力经常出现在以下场景中:

  • 短视频 / 社交类应用:需要快速加载、播放、拖动、倍速播放与基础编辑。
  • 直播与会议:强调低延迟、网络自适应和多端同时在线。
  • 监控与物联网:更关注长时间稳定推流、节省带宽和设备端的资源占用。

在 KMP 结构下,这一篇的核心目标是:

  • 在 commonMain 中抽象出统一的视频接口(如 VideoPlayerVideoRecorder),保证业务代码可以最大化复用;
  • 在各平台模块中利用原生 API 做具体实现(Android、iOS、鸿蒙),兼顾性能与平台特性;
  • 为后续的高级功能(自适应码率、转码等)留好扩展点,避免后期重构成本过高。

第一部分:视频处理核心概念

视频播放架构

// commonMain/kotlin/com/example/video/VideoPlayer.kt
expect class VideoPlayer {
    suspend fun loadVideo(url: String): Boolean
    suspend fun play(): Boolean
    suspend fun pause(): Boolean
    suspend fun stop(): Boolean
    suspend fun seek(positionMs: Long): Boolean
    suspend fun setVolume(volume: Float): Boolean
    fun getDuration(): Long
    fun getCurrentPosition(): Long
    fun addListener(listener: VideoPlayerListener)
}

interface VideoPlayerListener {
    fun onStateChanged(state: PlayerState)
    fun onProgressChanged(position: Long, duration: Long)
    fun onError(error: String)
    fun onBuffering(progress: Int)
}

enum class PlayerState {
    IDLE, LOADING, PLAYING, PAUSED, STOPPED, ERROR
}

这一部分定义的是跨平台统一的视频播放器接口

  • expect class VideoPlayer 放在 commonMain,用于约束所有平台都必须提供相同能力,例如加载视频、播放、暂停、停止、seek 以及音量控制等。
  • VideoPlayerListener 用于向 UI 层或上层业务主动回调播放状态和进度,便于实现进度条、播放按钮状态联动、错误提示等。
  • PlayerState 将复杂的底层状态简化为几个常用枚举,方便在 Compose 或其他 UI 框架中直接使用。

在设计 KMP 的接口层时,建议注意:

  • 尽量只暴露通用能力,不要在公共层引入特定平台才有的细节(比如某个平台独有的缓冲策略);
  • 对于可能引起卡顿的操作(如 loadVideoplay),统一设计为 suspend 方法,方便在协程中调用,避免阻塞主线程;
  • 通过监听器统一传递错误信息和缓冲进度,避免上层同时关心多个平台的回调差异。

视频录制架构

// commonMain/kotlin/com/example/video/VideoRecorder.kt
expect class VideoRecorder {
    suspend fun startRecording(outputPath: String): Boolean
    suspend fun stopRecording(): Boolean
    suspend fun pauseRecording(): Boolean
    suspend fun resumeRecording(): Boolean
    suspend fun setVideoQuality(quality: VideoQuality): Boolean
    suspend fun setFrameRate(fps: Int): Boolean
    fun isRecording(): Boolean
}

enum class VideoQuality {
    LOW, MEDIUM, HIGH, ULTRA_HD
}

data class VideoRecordingConfig(
    val quality: VideoQuality = VideoQuality.HIGH,
    val frameRate: Int = 30,
    val bitrate: Int = 5000000,
    val audioEnabled: Boolean = true
)

视频录制相对播放来说,对权限、设备性能和存储空间更敏感,因此在公共层就需要提前考虑可配置项:

  • VideoRecorder 提供开始录制、暂停、恢复、停止等核心控制能力,并通过 isRecording 辅助 UI 做状态显示;
  • VideoQuality 枚举预设了几档常见清晰度,实际实现时可以在各平台上映射到对应的分辨率和码率;
  • VideoRecordingConfig 统一描述录制参数,便于在不同平台内部进行转换。例如在 Android 上映射到 MediaRecorder 的 bitrate、frameRate 等,在鸿蒙上映射到 VideoRecorder 配置结构。

在业务实践中,可以:

  • 为不同网络和设备等级预设录制配置(如低端机限制分辨率和帧率);
  • 在开始录制前统一做存储空间检查和权限检查;
  • 结合音频录制模块,实现音视频同步录制或静音录制等场景。

第二部分:平台特定实现

Android 视频处理

// androidMain/kotlin/com/example/video/VideoPlayer.kt
import android.media.MediaPlayer
import android.view.SurfaceView
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.common.MediaItem

actual class VideoPlayer(private val context: Context) {
    private val exoPlayer = ExoPlayer.Builder(context).build()
    private val listeners = mutableListOf<VideoPlayerListener>()
    
    actual suspend fun loadVideo(url: String): Boolean {
        return try {
            val mediaItem = MediaItem.fromUri(url)
            exoPlayer.setMediaItem(mediaItem)
            exoPlayer.prepare()
            true
        } catch (e: Exception) {
            listeners.forEach { it.onError(e.message ?: "Failed to load video") }
            false
        }
    }
    
    actual suspend fun play(): Boolean {
        return try {
            exoPlayer.play()
            listeners.forEach { it.onStateChanged(PlayerState.PLAYING) }
            true
        } catch (e: Exception) {
            false
        }
    }
    
    actual suspend fun pause(): Boolean {
        return try {
            exoPlayer.pause()
            listeners.forEach { it.onStateChanged(PlayerState.PAUSED) }
            true
        } catch (e: Exception) {
            false
        }
    }
    
    actual fun getDuration(): Long = exoPlayer.duration
    
    actual fun getCurrentPosition(): Long = exoPlayer.currentPosition
    
    actual fun addListener(listener: VideoPlayerListener) {
        listeners.add(listener)
    }
}

// Android 视频录制
actual class VideoRecorder(private val context: Context) {
    private val mediaRecorder = MediaRecorder()
    private var isRecordingFlag = false
    
    actual suspend fun startRecording(outputPath: String): Boolean {
        return try {
            mediaRecorder.apply {
                setAudioSource(MediaRecorder.AudioSource.MIC)
                setVideoSource(MediaRecorder.VideoSource.CAMERA)
                setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
                setAudioEncoder(MediaRecorder.AudioEncoder.AAC)
                setVideoEncoder(MediaRecorder.VideoEncoder.H264)
                setOutputFile(outputPath)
                prepare()
                start()
            }
            isRecordingFlag = true
            true
        } catch (e: Exception) {
            false
        }
    }
    
    actual suspend fun stopRecording(): Boolean {
        return try {
            mediaRecorder.stop()
            mediaRecorder.release()
            isRecordingFlag = false
            true
        } catch (e: Exception) {
            false
        }
    }
    
    actual fun isRecording(): Boolean = isRecordingFlag
}

Android 端的实现示例中:

  • 视频播放使用 ExoPlayer,它相比系统 MediaPlayer 更适合流媒体场景,并支持更丰富的格式和自适应流;
  • 通过 listeners 手动分发状态变化,确保与 commonMain 中定义的 VideoPlayerListener 对齐;
  • 录制部分使用 MediaRecorder,展示了最基本的音视频源配置、编码格式与输出文件路径设置流程。

在实际项目中,你可以:

  • ExoPlayer 与 UI(如 Compose、XML、或鸿蒙跨端 UI 层)结合,提供播放视图;
  • MediaRecorder 增加更多配置(分辨率、比特率、对焦模式等),并在开始录制前检查摄像头和麦克风权限;
  • 使用协程对录制过程进行封装,向上提供统一的挂起接口和错误处理逻辑。

iOS 视频处理

// iosMain/kotlin/com/example/video/VideoPlayer.kt
import platform.AVFoundation.*
import platform.MediaPlayer.*

actual class VideoPlayer {
    private val player = AVPlayer()
    private val listeners = mutableListOf<VideoPlayerListener>()
    
    actual suspend fun loadVideo(url: String): Boolean {
        return try {
            val nsUrl = NSURL(string = url)
            val playerItem = AVPlayerItem(URL = nsUrl)
            player.replaceCurrentItemWithPlayerItem(playerItem)
            true
        } catch (e: Exception) {
            listeners.forEach { it.onError(e.message ?: "Failed to load video") }
            false
        }
    }
    
    actual suspend fun play(): Boolean {
        return try {
            player.play()
            listeners.forEach { it.onStateChanged(PlayerState.PLAYING) }
            true
        } catch (e: Exception) {
            false
        }
    }
    
    actual fun getDuration(): Long {
        val duration = player.currentItem?.duration?.value ?: 0
        return (duration * 1000).toLong()
    }
    
    actual fun getCurrentPosition(): Long {
        val time = player.currentTime().value
        return (time * 1000).toLong()
    }
}

// iOS 视频录制
actual class VideoRecorder {
    private val captureSession = AVCaptureSession()
    private var movieFileOutput: AVCaptureMovieFileOutput? = null
    
    actual suspend fun startRecording(outputPath: String): Boolean {
        return try {
            val fileUrl = NSURL(fileURLWithPath = outputPath)
            movieFileOutput?.startRecordingToOutputFileURL(fileUrl, recordingDelegate = null)
            true
        } catch (e: Exception) {
            false
        }
    }
}

iOS 平台的实现与 Android 类似,同样遵循 expect/actual 的模式:

  • 播放部分使用 AVPlayer,通过 AVPlayerItem 加载远程或本地视频资源;
  • getDurationgetCurrentPosition 将 iOS 的时间单位(通常是基于帧或时间戳)转换为毫秒,保证与公共接口一致;
  • 录制部分通过 AVCaptureSessionAVCaptureMovieFileOutput 组合,可以扩展为更复杂的拍摄场景(前后摄像头切换、对焦、曝光控制等)。

在 KMP 设计中,这类 iOS 端实现可以封装在 iosMain,上层只关心统一的 VideoRecorder/VideoPlayer 能力,而不直接接触 AVFoundation 的复杂细节。

第三部分:编译后的代码形式

JavaScript 编译版本

// 视频播放器 (JavaScript/Web)
class VideoPlayer {
    constructor(videoElement) {
        this.video = videoElement;
        this.listeners = [];
    }
    
    async loadVideo(url) {
        try {
            this.video.src = url;
            this.video.addEventListener('loadedmetadata', () => {
                this.notifyListeners('onStateChanged', 'LOADED');
            });
            return true;
        } catch (error) {
            this.notifyListeners('onError', error.message);
            return false;
        }
    }
    
    async play() {
        try {
            await this.video.play();
            this.notifyListeners('onStateChanged', 'PLAYING');
            return true;
        } catch (error) {
            return false;
        }
    }
    
    async pause() {
        this.video.pause();
        this.notifyListeners('onStateChanged', 'PAUSED');
        return true;
    }
    
    getDuration() {
        return this.video.duration * 1000;
    }
    
    getCurrentPosition() {
        return this.video.currentTime * 1000;
    }
    
    addListener(listener) {
        this.listeners.push(listener);
    }
    
    notifyListeners(method, ...args) {
        this.listeners.forEach(listener => {
            if (listener[method]) {
                listener[method](...args);
            }
        });
    }
}

// 视频录制器
class VideoRecorder {
    async startRecording(outputPath) {
        try {
            const stream = await navigator.mediaDevices.getUserMedia({
                video: true,
                audio: true
            });
            
            this.mediaRecorder = new MediaRecorder(stream);
            this.chunks = [];
            
            this.mediaRecorder.ondataavailable = (e) => {
                this.chunks.push(e.data);
            };
            
            this.mediaRecorder.start();
            return true;
        } catch (error) {
            return false;
        }
    }
    
    async stopRecording() {
        return new Promise((resolve) => {
            this.mediaRecorder.onstop = () => {
                const blob = new Blob(this.chunks, { type: 'video/mp4' });
                // Handle blob (download, upload, etc.)
                resolve(true);
            };
            this.mediaRecorder.stop();
        });
    }
}

第四部分:鸿蒙系统实际应用

鸿蒙视频处理

// ohos/kotlin/com/example/video/VideoPlayer.kt
import ohos.media.player.Player
import ohos.media.player.PlayerCallback
import ohos.app.Context

actual class VideoPlayer(private val context: Context) {
    private val player = Player(context)
    private val listeners = mutableListOf<VideoPlayerListener>()
    
    actual suspend fun loadVideo(url: String): Boolean {
        return try {
            player.source = url
            player.setPlayerCallback(object : PlayerCallback {
                override fun onPlaybackComplete() {
                    listeners.forEach { it.onStateChanged(PlayerState.STOPPED) }
                }
                
                override fun onError(errorCode: Int, errorMsg: String) {
                    listeners.forEach { it.onError(errorMsg) }
                }
            })
            true
        } catch (e: Exception) {
            false
        }
    }
    
    actual suspend fun play(): Boolean {
        return try {
            player.play()
            listeners.forEach { it.onStateChanged(PlayerState.PLAYING) }
            true
        } catch (e: Exception) {
            false
        }
    }
    
    actual fun getDuration(): Long = player.duration
    
    actual fun getCurrentPosition(): Long = player.currentTime
}

// 鸿蒙视频录制
actual class VideoRecorder(private val context: Context) {
    private val recorder = ohos.media.recorder.VideoRecorder()
    
    actual suspend fun startRecording(outputPath: String): Boolean {
        return try {
            recorder.prepare(VideoRecordingConfig().apply {
                outputPath = outputPath
                videoFrameWidth = 1920
                videoFrameHeight = 1080
                videoFrameRate = 30
            })
            recorder.start()
            true
        } catch (e: Exception) {
            false
        }
    }
    
    actual suspend fun stopRecording(): Boolean {
        return try {
            recorder.stop()
            recorder.release()
            true
        } catch (e: Exception) {
            false
        }
    }
}

这一节是本篇的重点:演示如何在鸿蒙系统中实现与其他平台保持一致的视频能力。

  • 播放部分使用鸿蒙的 ohos.media.player.Player,通过设置 PlayerCallback 监听播放完成与错误事件,并转换为通用的 PlayerStateonError 回调;
  • 录制部分使用 ohos.media.recorder.VideoRecorder,并通过配置对象设定输出路径、分辨率和帧率等参数;
  • 与 Android、iOS 一样,这些实现都被封装在 actual class VideoPlayer / VideoRecorder 中,对上层透明。

在真实项目落地时,可以进一步:

  • 抽取鸿蒙端的权限申请逻辑(相机、麦克风、存储)到一层统一的权限管理模块;
  • 针对鸿蒙设备的硬件特性(如部分设备分辨率或硬编解码能力),预置不同的视频质量档位;
  • 与应用的 UI 交互层打通,实现本地相册选择、拍摄视频后直接预览和上传等完整流程。

第五部分:高级功能

自适应流媒体

// commonMain/kotlin/com/example/video/AdaptiveStreaming.kt
class AdaptiveStreamingManager(private val videoPlayer: VideoPlayer) {
    private var currentBitrate = 2500000
    private val bitrateOptions = listOf(500000, 1000000, 2500000, 5000000)
    
    suspend fun adjustQuality(networkSpeed: Int) {
        val newBitrate = when {
            networkSpeed < 1000 -> bitrateOptions[0]
            networkSpeed < 3000 -> bitrateOptions[1]
            networkSpeed < 5000 -> bitrateOptions[2]
            else -> bitrateOptions[3]
        }
        
        if (newBitrate != currentBitrate) {
            currentBitrate = newBitrate
            // Switch to new quality stream
        }
    }
}

// 视频转码
class VideoTranscoder(private val context: Context) {
    suspend fun transcode(
        inputPath: String,
        outputPath: String,
        quality: VideoQuality
    ): Boolean {
        return try {
            val config = when (quality) {
                VideoQuality.LOW -> TranscodeConfig(bitrate = 500000, fps = 24)
                VideoQuality.MEDIUM -> TranscodeConfig(bitrate = 1500000, fps = 30)
                VideoQuality.HIGH -> TranscodeConfig(bitrate = 5000000, fps = 30)
                VideoQuality.ULTRA_HD -> TranscodeConfig(bitrate = 10000000, fps = 60)
            }
            
            // Perform transcoding
            true
        } catch (e: Exception) {
            false
        }
    }
}

data class TranscodeConfig(val bitrate: Int, val fps: Int)

高级功能部分为后续扩展提供了思路:

  • AdaptiveStreamingManager 模拟了自适应码率的逻辑,根据当前网络带宽选择不同的码率选项,可以与 HLS/DASH 等协议结合使用;
  • VideoTranscoder 展示了一个转码接口的雏形,通过不同的 VideoQuality 预设不同的转码参数,便于后台任务或离线处理;
  • TranscodeConfig 把关键参数打包,方便在不同平台上使用各自的转码库(如 FFmpeg、平台自带转码 API 等)。

在实际业务中,这些能力可以应用在:

  • 上传前转码(例如将原始视频压缩为多码率版本);
  • 后台批量处理(例如夜间批量生成预览图、转码不同清晰度);
  • 与 CDN/流媒体服务联动,根据终端和网络自动选择最优播放流。

在这里插入图片描述

总结

视频处理与流媒体是现代应用的高级功能。通过 KMP 框架,我们可以在共享代码中定义统一的视频播放和录制接口,同时在各平台上利用原生 API 实现高效的处理。鸿蒙系统提供了完整的视频处理 API,使得跨平台实现相对平顺。关键是要合理处理网络带宽、设备性能和用户体验之间的平衡。

在工程实践中,建议你在阅读本篇示例代码时,重点思考以下几个问题:

  • 公共层接口是否足够抽象、足够稳定,能否支撑未来功能的演进?
  • 平台特定实现中是否有可以进一步封装、对上层隐藏的细节?
  • 在引入自适应流、转码等高级能力时,如何设计任务调度与错误恢复机制?

理解了这一篇的结构后,你可以尝试:

  • 按照相同的模式,为项目中其他多媒体场景(如音频、屏幕录制)设计 expect/actual 架构;
  • 在 demo 中接入真实的视频资源与简单的 UI,打通从“选择视频 → 播放/录制 → 预览/上传”的完整链路;
  • 根据实际业务指标(启动时间、卡顿率、失败率等)逐步优化和迭代视频模块的实现。
Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐