这一节了解一下Compose中的ExoPlayer的使用,我们在开发Android应用时,经常会使用到播放器,这个ExoPlayer框架就相对成熟,易上手,现简单总结如下:
1. ExoPlayer 核心类
ExoPlayer 是 ExoPlayer库的核心类,负责管理媒体播放的整个生命周期,包括加载、播放、暂停、停止等操作。
作用:1 创建播放器实例。2 管理播放状态(播放、暂停、停止)。3 控制播放进度(如跳转到指定位置)。4 监听播放事件(如播放完成、错误等)。
2. SimpleExoPlayer
SimpleExoPlayer 是 ExoPlayer 的一个简化实现,适用于大多数基本播放场景。
作用:1 提供了更简单的 API 来管理播放。2 支持音频和视频播放。3 通常用于不需要复杂自定义的场景。
3. Player.Listener
含义:Player.Listener 是 ExoPlayer 的监听器接口,用于接收播放状态变化和事件通知。
作用:1 监听播放状态变化(如播放、暂停、停止)。2 监听播放进度变化。3 监听错误事件。4 监听媒体加载状态(如缓冲完成)。
4. MediaItem
含义:MediaItem 表示要播放的媒体资源,可以是本地文件或网络资源。
作用:1 定义媒体资源的 URI(如本地路径或网络 URL)。2 设置媒体元数据(如标题、描述等)。3添加到播放列表中。
5. PlayerView
含义:PlayerView 是 ExoPlayer 提供的 UI 组件,用于显示视频画面和控制播放。
作用:1 显示视频画面。2 提供内置的播放控制按钮(播放、暂停、进度条等)。3 可以通过Compose的AndroidView或ComposeView集成到ComposeUI中。
6. 播放控制方法
play():开始播放。
pause():暂停播放。
stop():停止播放并重置播放器状态。
seekTo(position: Long):跳转到指定位置(以毫秒为单位)。
setPlayWhenReady(playWhenReady: Boolean):设置播放器是否准备好时自动播放。
7. 播放状态
ExoPlayer 提供了多种播放状态,可以通过 Player.State 或 playbackState 属性获取:
STATE_IDLE:播放器空闲状态,未准备播放。
STATE_BUFFERING:播放器正在缓冲数据。
STATE_READY:播放器已准备好,可以播放。
STATE_ENDED:播放结束。
8. 事件监听(如播放完成)
含义:通过监听器可以捕获播放完成事件。
栗子
app模块下gradle:
implementation ("com.google.android.exoplayer:exoplayer-core:2.19.1")
implementation ("com.google.android.exoplayer:exoplayer-hls:2.19.1")
implementation ("com.google.android.exoplayer:exoplayer-ui:2.19.1")
implementation("androidx.preference:preference-ktx:1.2.1")
<activity
android:name=".testexoplayer.YourVideoActivity"
android:exported="true"
android:theme="@style/Theme.ComposeNavigationDemo"
android:supportsPictureInPicture="true"
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
package com.example.composenavigationdemo.testexoplayer
import android.content.Context
import android.content.pm.ActivityInfo
import android.os.Bundle
import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.viewinterop.AndroidView
import com.google.android.exoplayer2.ExoPlayer
import com.google.android.exoplayer2.MediaItem
import com.google.android.exoplayer2.ui.StyledPlayerView
import androidx.preference.PreferenceManager
class YourVideoActivity : ComponentActivity() {
private lateinit var exoPlayer: ExoPlayer
private val PREF_KEY_PLAYBACK_POSITION = "playback_position"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
VideoPlayerScreen()
}
}
@Composable
fun VideoPlayerScreen() {
val videoUrl = "https://vdept3.bdstatic.com/mda-rbq74um403w6qq1c/cae_h264/1740460540715545764/mda-rbq74um403w6qq1c.mp4?v_from_s=hkapp-haokan-suzhou&auth_key=1743250028-0-0-dbffc551f0aea2bffc89d537dba36202&bcevod_channel=searchbox_feed&cr=0&cd=0&pd=1&pt=3&logid=0428327789&vid=10916160709390273754&klogid=0428327789&abtest=\n"
var isPlaying by remember { mutableStateOf(true) }
val context = LocalContext.current
val playerView = remember {
StyledPlayerView(this).apply {
layoutParams = FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
}
}
val player = remember {
ExoPlayer.Builder(this).build().apply {
setMediaItem(MediaItem.fromUri(videoUrl))
// 读取播放进度
val playbackPosition = getPlaybackPosition(context)
if (playbackPosition > 0) {
seekTo(playbackPosition)
}
prepare()
playWhenReady = isPlaying
}
}
DisposableEffect(Unit) {
playerView.player = player
onDispose {
// 保存播放进度
savePlaybackPosition(context, player.currentPosition)
player.release()
}
}
Box(modifier = Modifier.fillMaxSize()) {
AndroidView(
factory = { playerView },
modifier = Modifier.fillMaxSize()
)
Button(
onClick = {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
enterPictureInPictureMode()
}
},
modifier = Modifier.align(androidx.compose.ui.Alignment.BottomEnd)
) {
Text(text = "进入画中画")
}
}
}
private fun savePlaybackPosition(context: Context, position: Long) {
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
prefs.edit().putLong(PREF_KEY_PLAYBACK_POSITION, position).apply()
}
private fun getPlaybackPosition(context: Context): Long {
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
return prefs.getLong(PREF_KEY_PLAYBACK_POSITION, 0)
}
override fun onPictureInPictureModeChanged(
isInPictureInPictureMode: Boolean,
newConfig: android.content.res.Configuration
) {
super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig)
if (isInPictureInPictureMode) {
// 进入画中画模式
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE
} else {
// 退出画中画模式
// requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
requestedOrientation = ActivityInfo.SCREEN_O RIENTATION_SENSOR_PORTRAIT
}
}
}
分析:上面实现了一个画中画的播放效果。
注意:
1. 依赖管理与版本选择
版本兼容性:ExoPlayer 的版本需要与项目的 Android Gradle 插件版本和 Compose 版本兼容,避免因版本不匹配导致的问题
2. 生命周期管理
释放资源:ExoPlayer是资源密集型组件,必须在不再使用时释放资源。可以通过DisposableEffect或onDispose 来管理生命周期,确保在onStop或onDestroy时调用player.release()。
DisposableEffect(lifecycleOwner) {
onDispose {
exoPlayer.release()
}
}
3. 媒体源设置
动态设置媒体源:在播放不同媒体时,需要动态设置 MediaItem,并调用 player.setMediaItem() 和 player.prepare()。
4. 线程与异步操作
后台线程加载:避免在主线程中进行媒体加载或解码操作,ExoPlayer会自动处理大部分异步操作,但确保网络请求或大数据处理在后台线程中完成。
异步准备:如果需要预加载媒体,可以在后台线程中调用 player.prepare(),避免阻塞UI。