播放音频与视频
在 Android 中实现简单的媒体播放
播放音频与视频 是 CoddyKit 上的免费 Android Academy 课时。 这是第 4 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Android Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Android Academy 课程共包含 4 节课。
超越图片:媒体播放
Coil 可以出色地显示图片,但应用还需要播放声音和视频:通知提示音、播客、背景视频片段或视频信息流。Android 为媒体提供了专用 API。
目前推荐使用的现代化库是 Media3 (ExoPlayer)。与旧版 MediaPlayer 相比,它在流式传输、格式处理、缓冲和播放控制方面表现更好。本课将介绍如何在 Compose 应用中播放简单的音频和视频。
添加 Media3 依赖
Media3 被拆分为多个模块。对于大多数应用,您需要 media3-exoplayer(播放器引擎)和 media3-ui(现成的播放器视图)。对于 Compose 视频,您还可以通过 AndroidView 使用 PlayerView。
// build.gradle.kts (module level)
dependencies {
implementation("androidx.media3:media3-exoplayer:1.5.0")
implementation("androidx.media3:media3-ui:1.5.0")
}创建 ExoPlayer
ExoPlayer 根据 Context 创建。您为它提供一个 MediaItem(URL 或本地文件),调用 prepare(),然后开始播放。它会在自己的线程中运行,因此不会阻塞界面。
需要特别注意的是,播放器会占用系统资源,因此必须释放;稍后会详细介绍。
import android.content.Context
import androidx.media3.common.MediaItem
import androidx.media3.exoplayer.ExoPlayer
fun createPlayer(context: Context, url: String): ExoPlayer {
val player = ExoPlayer.Builder(context).build()
player.setMediaItem(MediaItem.fromUri(url))
player.prepare()
player.playWhenReady = true
return player
}播放音频
仅播放音频时,设置方式完全相同,只需让 MediaItem 指向音频 URL(mp3、aac 等)。由于没有需要显示的视图,您可以通过播放器对象控制它:play()、pause()、seekTo()。
这些功能足以构建播客或音乐播放器。
import androidx.media3.common.MediaItem
import androidx.media3.exoplayer.ExoPlayer
fun playPodcast(player: ExoPlayer, audioUrl: String) {
player.setMediaItem(MediaItem.fromUri(audioUrl))
player.prepare()
player.play()
}
fun togglePause(player: ExoPlayer) {
if (player.isPlaying) player.pause() else player.play()
}在 Compose 中记住播放器
在 Compose 中,您应该只创建一次播放器,并让它在多次重组之间保持不变。请使用 remember,避免每次重组都创建新的播放器。
请将其与正确的清理操作结合使用(下一节会介绍),以免泄漏资源。
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
import androidx.media3.common.MediaItem
import androidx.media3.exoplayer.ExoPlayer
@Composable
fun rememberPlayer(url: String): ExoPlayer {
val context = LocalContext.current
return remember {
ExoPlayer.Builder(context).build().apply {
setMediaItem(MediaItem.fromUri(url))
prepare()
}
}
}释放播放器
播放器会持续占用编解码器、缓冲区和音频焦点。如果忘记释放它,就会造成内存泄漏,甚至可能在屏幕消失后仍继续播放。在 Compose 中,当可组合项离开组合时,DisposableEffect 会执行清理操作。
请务必在那里调用 player.release()。
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.media3.exoplayer.ExoPlayer
@Composable
fun ManagedPlayer(player: ExoPlayer) {
DisposableEffect(player) {
onDispose {
player.release()
}
}
// ... use player here
}使用 PlayerView 显示视频
Media3 提供了带控制功能的现成 PlayerView。Compose 没有原生的视频可组合项,因此您需要使用 AndroidView 嵌入视图并附加播放器。
这样就可以直接获得播放/暂停、进度条和全屏功能。
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.viewinterop.AndroidView
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.ui.PlayerView
@Composable
fun VideoPlayer(player: ExoPlayer, modifier: Modifier = Modifier) {
AndroidView(
factory = { context ->
PlayerView(context).apply {
this.player = player
}
},
modifier = modifier
)
}完整的视频屏幕
让我们把这些内容整合起来:创建播放器、记住播放器、将其附加到 PlayerView,并在释放时销毁它。这就是一个可以重复使用的独立视频屏幕。
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import androidx.media3.common.MediaItem
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.ui.PlayerView
@Composable
fun VideoScreen(videoUrl: String) {
val context = LocalContext.current
val player = remember {
ExoPlayer.Builder(context).build().apply {
setMediaItem(MediaItem.fromUri(videoUrl))
prepare()
playWhenReady = true
}
}
DisposableEffect(Unit) { onDispose { player.release() } }
AndroidView(
factory = { PlayerView(it).apply { this.player = player } },
modifier = Modifier.fillMaxWidth().height(220.dp)
)
}跟踪播放状态
要更新界面(例如播放/暂停图标或进度条),您可以通过 Player.Listener 监听播放器。它会报告缓冲、准备就绪和播放结束等状态变化,以及当前是否正在播放。
在屏幕出现时添加监听器,并在释放时移除它。
import androidx.media3.common.Player
import androidx.media3.exoplayer.ExoPlayer
fun observe(player: ExoPlayer, onPlaying: (Boolean) -> Unit): Player.Listener {
val listener = object : Player.Listener {
override fun onIsPlayingChanged(isPlaying: Boolean) {
onPlaying(isPlaying)
}
override fun onPlaybackStateChanged(state: Int) {
when (state) {
Player.STATE_BUFFERING -> { /* show spinner */ }
Player.STATE_READY -> { /* ready */ }
Player.STATE_ENDED -> { /* finished */ }
}
}
}
player.addListener(listener)
return listener
}应用进入后台时暂停
对于内嵌视频(而非音乐应用),通常希望用户离开屏幕时暂停,返回时继续播放。您可以通过观察 Lifecycle 将播放与 Compose 生命周期关联起来,并在 ON_PAUSE 时暂停。
这样可以避免播放用户看不到的视频,同时节省电量。
import androidx.compose.runtime.*
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.media3.exoplayer.ExoPlayer
@Composable
fun PauseWithLifecycle(player: ExoPlayer) {
val lifecycleOwner = LocalLifecycleOwner.current
DisposableEffect(lifecycleOwner) {
val observer = LifecycleEventObserver { _, event ->
when (event) {
Lifecycle.Event.ON_PAUSE -> player.pause()
Lifecycle.Event.ON_RESUME -> player.play()
else -> {}
}
}
lifecycleOwner.lifecycle.addObserver(observer)
onDispose { lifecycleOwner.lifecycle.removeObserver(observer) }
}
}使用 MediaSession 播放后台音频
对于应用进入后台后仍应继续播放的音频(音乐、播客),请将播放功能封装在 MediaSessionService 中。这样可以与系统控制、锁屏和蓝牙按钮集成。
实现过程更复杂一些,但核心思路是通过 MediaSession 对外提供播放器。
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.session.MediaSession
import androidx.media3.session.MediaSessionService
class PlaybackService : MediaSessionService() {
private var session: MediaSession? = null
override fun onCreate() {
super.onCreate()
val player = ExoPlayer.Builder(this).build()
session = MediaSession.Builder(this, player).build()
}
override fun onGetSession(controllerInfo: MediaSession.ControllerInfo) = session
override fun onDestroy() {
session?.run { player.release(); release() }
session = null
super.onDestroy()
}
}快速检查
在包含 ExoPlayer 的 Compose 屏幕中,当屏幕离开组合时,应使用哪个 Compose API 来释放播放器?
回顾
您已经添加了音频和视频播放功能:
- 使用现代化的 Media3 ExoPlayer,它是 MediaPlayer 的替代方案。
- 创建播放器,设置
MediaItem,调用prepare(),然后调用play()。 - 在 Compose 中使用
remember记住播放器,并在DisposableEffect的onDispose中释放它。 - 通过使用
AndroidView嵌入PlayerView来显示视频。 - 使用
Player.Listener跟踪状态;使用MediaSessionService实现后台音频。
至此,您已经完成了“使用 Coil 加载图片和媒体”课程。现在,您可以流畅地加载图片,并以现代 Android 方式播放媒体。
常见问题解答
「播放音频与视频」课时是免费的吗?
是的 — 「播放音频与视频」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Android Academy 课程的其余内容,请升级到 CoddyKit PRO。 Android Academy 课程共包含 4 节课。
「播放音频与视频」这节课中我会学到什么?
在 Android 中实现简单的媒体播放 你通过在浏览器中直接运行的动手代码来练习 Android Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 Android Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 Android Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 4 节课,共 4 节。
「播放音频与视频」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 Android Academy 课中编写并运行代码吗?
能。每节 Android Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。
此课程中的所有课时
- 使用 Coil 加载图片
- 占位符与错误状态
- 缓存与性能
- 播放音频与视频