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. 示例视频

相关推荐
小猪努力学前端9 分钟前
基于PixiJS的试玩广告开发-续篇
前端·javascript·游戏
bluceli20 分钟前
前端构建工具深度解析:从Webpack到Vite的演进之路
前端
wuhen_n22 分钟前
v-model 的进阶用法:搞定复杂的父子组件数据通信
前端·javascript·vue.js
wuhen_n24 分钟前
TypeScript 深度加持:让你的组合式函数拥有“钢筋铁骨”
前端·javascript·vue.js
滕青山36 分钟前
基于 ZXing 的 Vue 在线二维码扫描器实现
前端·javascript·vue.js
Kayshen1 小时前
我在设计工具里实现了一个 Agent Team:多智能体协作生成 UI 的实战经验
前端·aigc·agent
swipe1 小时前
深入理解 JavaScript 中的 this 绑定机制:从原理到实战
前端·javascript·面试
Json_Lee1 小时前
2026 年了,多 Agent 编码该怎么选?agent-team vs Claude Agent Teams vs Claude Squad vs Met
前端·后端·vibecoding
Novlan11 小时前
Stepper 小数输入精度丢失 Bug 修复
前端
陈随易1 小时前
刚上市就断货?如此火爆的编程显示器到底有什么魔力
前端·后端·程序员