Media3 ExoPlayer 快速实现背景视频播放(干货)

官方文档: 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
}

5. 示例视频

相关推荐
JosieBook6 分钟前
【SpringBoot】21-Spring Boot中Web页面抽取公共页面的完整实践
前端·spring boot·python
吃饭睡觉打豆豆嘛1 小时前
深入剖析 Promise 实现:从原理到手写完整实现
前端·javascript
前端端1 小时前
claude code 原理分析
前端
GalaxyMeteor1 小时前
Elpis 开发框架搭建第二期 - Webpack5 实现工程化建设
前端
Spider_Man1 小时前
从 “不会迭代” 到 “面试加分”:JS 迭代器现场教学
前端·javascript·面试
我的写法有点潮1 小时前
最全Scss语法,赶紧收藏起来吧
前端·css
小高0071 小时前
🧙‍♂️ 老司机私藏清单:从“记事本”到“旗舰 IDE”,我只装了这 12 个插件
前端·javascript·vue.js
Mo_jon1 小时前
css 遮盖滚动条,鼠标移上显示
前端·css
g_i_a_o_giao1 小时前
Android8 binder源码学习分析笔记(一)
android·java·笔记·学习·binder·安卓源码分析
翻滚丷大头鱼1 小时前
android 四大组件—BroadcastReceiver
android