官方文档: Media3 ExoPlayer
1. 添加 Gradle 依赖
gradle
dependencies {
implementation "androidx.media3:media3-exoplayer:1.7.1"
implementation "androidx.media3:media3-exoplayer-dash:1.7.1" // DASH 支持
implementation "androidx.media3:media3-ui:1.7.1" // UI 组件
}
2. 布局文件
xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".LoginRegisterActivity">
<SurfaceView
android:id="@+id/surface_view"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_width="0dp"
android:layout_height="0dp"/>
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:background="@android:color/transparent"
app:contentInsetStartWithNavigation="0dp"
android:elevation="0dp"
android:layout_width="match_parent"
android:layout_height="@dimen/design_toolbar_height">
</androidx.appcompat.widget.Toolbar>
</androidx.constraintlayout.widget.ConstraintLayout>
2. Activity 代码
kotlin
class ExoPlayerActivity : BaseActivity(), SurfaceHolder.Callback by noOpDelegate() {
private val binding by lazy {
ActivityExoPlayerBinding.inflate(layoutInflater)
}
private var exoPlayer: ExoPlayer? = null
private var videoSurface: Surface? = null
// 播放器状态监听
private val playerListener = object : Player.Listener by noOpDelegate() {
override fun onPlayerError(error: PlaybackException) {
finish()
}
}
@SuppressLint("ClickableViewAccessibility")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContentView(binding.root)
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(systemBars.left, 0, systemBars.right, systemBars.bottom)
insets
}
initActionBar()
binding.surfaceView.holder.addCallback(this)
// 阻止任何触摸事件(纯播放)
binding.surfaceView.setOnTouchListener { _, _ -> true }
initializePlayer(ConstConfig.getVideoUri())
}
/**
* 初始化ActionBar
*/
private fun initActionBar() {
binding.toolbar.title = ""
setSupportActionBar(binding.toolbar)
binding.toolbar.elevation = 0f
binding.toolbar.setNavigationIcon(com.hongtu.widget.R.drawable.icon_back_white)
binding.toolbar.setNavigationOnClickListener {
finish()
}
}
override fun onResume() {
super.onResume()
exoPlayer?.play()
}
override fun onPause() {
super.onPause()
exoPlayer?.pause()
}
override fun onDestroy() {
super.onDestroy()
countDownTimer?.cancel()
exoPlayer?.release()
exoPlayer = null
}
private fun initializePlayer(videoUri: Uri) {
// 创建ExoPlayer实例
exoPlayer = ExoPlayer.Builder(this).build().apply {
// 设置媒体
setMediaItem(MediaItem.fromUri(videoUri))
// 监听状态
addListener(playerListener)
// 循环播放
repeatMode = Player.REPEAT_MODE_ONE
// 准备播放
prepare()
}
}
override fun surfaceCreated(holder: SurfaceHolder) {
videoSurface = holder.surface
exoPlayer?.setVideoSurface(videoSurface)
}
override fun surfaceDestroyed(holder: SurfaceHolder) {
videoSurface = null
exoPlayer?.clearVideoSurface()
}
}
4. Activity中使用的 noOpDelegate() 方法
kotlin
import java.lang.reflect.InvocationHandler
import java.lang.reflect.Proxy
/**
* 使用Kotlin委托和Java动态代理实现移除接口需要强制实现的方法
*/
inline fun <reified T : Any> noOpDelegate(): T {
val javaClass = T::class.java
return Proxy.newProxyInstance(
javaClass.classLoader, arrayOf(javaClass), noOpHandler
) as T
}
val noOpHandler = InvocationHandler { _, _, _ ->
// no op
}