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

相关推荐
张拭心7 分钟前
为什么说 AI 视频模型不能用来做教育?Sora-2 Veo-3 来了也不行
前端·人工智能
lvchaoq38 分钟前
页面停留时间过长导致token过期问题
前端
elangyipi12342 分钟前
深入理解前端项目中的 package.json 和 package-lock.json
前端·json
LYFlied1 小时前
【算法解题模板】-【回溯】----“试错式”问题解决利器
前端·数据结构·算法·leetcode·面试·职场和发展
composurext1 小时前
录音切片上传
前端·javascript·css
程序员小寒1 小时前
前端高频面试题:深拷贝和浅拷贝的区别?
前端·javascript·面试
狮子座的男孩1 小时前
html+css基础:07、css2的复合选择器_伪类选择器(概念、动态伪类、结构伪类(核心)、否定伪类、UI伪类、目标伪类、语言伪类)及伪元素选择器
前端·css·经验分享·html·伪类选择器·伪元素选择器·结构伪类
zhougl9961 小时前
Vue 中的 `render` 函数
前端·javascript·vue.js
听风吟丶1 小时前
Spring Boot 自动配置深度解析:原理、实战与源码追踪
前端·bootstrap·html
跟着珅聪学java1 小时前
HTML中设置<select>下拉框默认值的详细教程
开发语言·前端·javascript