Media3(ExoPlayer)+ Compose:视频与音频播放器实现

发布: (2026年3月2日 GMT+8 09:18)
4 分钟阅读
原文: Dev.to

I’m happy to translate the article for you, but I don’t have the ability to retrieve the content from external links. Could you please paste the text you’d like translated (excluding any code blocks or URLs you want to keep unchanged)? Once you provide the content, I’ll translate it into Simplified Chinese while preserving the original formatting and markdown.

ExoPlayer.Builder 设置

创建并配置 ExoPlayer 实例:

import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector

val exoPlayer = ExoPlayer.Builder(context)
    .setTrackSelector(DefaultTrackSelector(context))
    .build()

加载 MediaItems

添加一个或多个媒体项以播放:

import androidx.media3.common.MediaItem
import androidx.media3.common.MimeTypes

// Single video
val mediaItem = MediaItem.Builder()
    .setUri("https://example.com/video.mp4")
    .setMimeType(MimeTypes.APPLICATION_MP4)
    .build()
exoPlayer.setMediaItem(mediaItem)

// Playlist with multiple items
val playlist = listOf(
    MediaItem.fromUri("https://example.com/video1.mp4"),
    MediaItem.fromUri("https://example.com/video2.mp4"),
    MediaItem.fromUri("https://example.com/audio.mp3")
)
exoPlayer.setMediaItems(playlist)
exoPlayer.prepare()

AndroidView 中的 PlayerView

在 Compose 中使用 AndroidView 嵌入 PlayerView

import androidx.media3.ui.PlayerView
import androidx.compose.ui.viewinterop.AndroidView
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp

AndroidView(
    factory = { context ->
        PlayerView(context).apply {
            player = exoPlayer
            useController = true
            controllerShowTimeoutMs = 5000
            controllerHideTimeoutMs = 3000
        }
    },
    modifier = Modifier
        .fillMaxWidth()
        .height(300.dp)
)

Compose 中的自定义音频播放器 UI

import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.Alignment

val currentPosition by exoPlayer.currentPosition.collectAsState()
val duration by exoPlayer.duration.collectAsState()
val isPlaying by exoPlayer.isPlaying.collectAsState()

Column(
    modifier = Modifier
        .padding(16.dp)
        .fillMaxWidth(),
    verticalArrangement = Arrangement.spacedBy(12.dp)
) {
    // Play/Pause Button
    Button(onClick = {
        if (isPlaying) exoPlayer.pause() else exoPlayer.play()
    }) {
        Text(if (isPlaying) "⏸ Pause" else "▶ Play")
    }

    // Progress Slider
    Slider(
        value = currentPosition.toFloat(),
        onValueChange = { exoPlayer.seekTo(it.toLong()) },
        valueRange = 0f..duration.toFloat(),
        modifier = Modifier.fillMaxWidth()
    )

    // Time Display
    Row(
        modifier = Modifier.fillMaxWidth(),
        horizontalArrangement = Arrangement.SpaceBetween
    ) {
        Text(formatTime(currentPosition))
        Text(formatTime(duration))
    }
}

fun formatTime(ms: Long): String {
    val seconds = (ms / 1000) % 60
    val minutes = (ms / 60000) % 60
    return "%02d:%02d".format(minutes, seconds)
}

生命周期感知的播放

当应用进入后台时暂停播放:

import androidx.compose.runtime.*
import androidx.lifecycle.*

DisposableEffect(Unit) {
    val lifecycleObserver = LifecycleEventObserver { _, event ->
        when (event) {
            Lifecycle.Event.ON_RESUME -> exoPlayer.play()
            Lifecycle.Event.ON_PAUSE -> exoPlayer.pause()
            else -> {}
        }
    }

    val lifecycle = LocalLifecycleOwner.current.lifecycle
    lifecycle.addObserver(lifecycleObserver)

    onDispose {
        lifecycle.removeObserver(lifecycleObserver)
    }
}

使用 DisposableEffect 正确释放资源

在 Compose 组件被销毁时释放播放器:

DisposableEffect(Unit) {
    onDispose {
        exoPlayer.release()
    }
}

播放列表管理

在播放列表中控制播放流程:

// Move to next item
Button(onClick = { exoPlayer.seekToNextMediaItem() }) {
    Text("Next Track")
}

// Move to previous item
Button(onClick = { exoPlayer.seekToPreviousMediaItem() }) {
    Text("Previous Track")
}

// Repeat modes
IconButton(onClick = {
    exoPlayer.repeatMode = when (exoPlayer.repeatMode) {
        Player.REPEAT_MODE_OFF -> Player.REPEAT_MODE_ALL
        Player.REPEAT_MODE_ALL -> Player.REPEAT_MODE_ONE
        else -> Player.REPEAT_MODE_OFF
    }
}) {
    Text("Repeat: ${exoPlayer.repeatMode}")
}

最佳实践

  • 始终使用 DisposableEffect 来释放播放器资源。
  • 当应用进入后台时暂停播放,以节省电池。
  • 使用生命周期观察者进行正确的状态管理。
  • 处理网络错误并提供本地播放的回退方案。
  • 使用各种媒体格式进行测试(MP4、WebM、DASH、HLS)。
  • 为用户提供播放控制和进度指示。

Media3 为音频和视频播放提供了坚实的基础。结合 Compose 的声明式 UI,您可以以最少的样板代码构建现代媒体体验。

探索更多 Android 开发模式: 8 Android App Templates →

0 浏览
Back to Blog

相关文章

阅读更多 »