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

相关推荐
子兮曰1 天前
jev-ultrafast 深度解析:7 秒订机票的浏览器 Agent 是如何炼成的
前端·后端·agent
子兮曰1 天前
Jev 爆发一周:7 秒 Agent 背后的 System One 生态与三场争议
前端·后端·ai编程
前端小万1 天前
写公众号赚了 3000 块后,我做了一款叫 "一键成稿" 的软件
前端·微信小程序
爱勇宝1 天前
ZCode 开源 24 小时:一份没有历史的账本,回答不了"有没有偷代码"
前端·后端·chatglm (智谱)
千里马学框架1 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台1 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
三十而立洋1 天前
Cookie 详解:从产生到安全,一次讲透
前端·javascript
AFinalStone1 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
卡布鲁1 天前
把一个 Vite + Vue3 应用塞进 qiankun (React + Umi3) 主站:十个坑的复盘
前端·javascript·react.js
致远ccc1 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试