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

相关推荐
杉氧几秒前
React 灵魂:深入剖析 Hooks 与副作用(Side Effects)陷阱
android·前端·react native
Json____4 分钟前
java-宿舍安全卫生检查系统项目源码
java·前端·javascript·课程设计·it学习·wwwoop.com
lichenyang45315 分钟前
鸿蒙ArkUI 键盘避让终极踩坑指南:RESIZE 无效、Column 布局遮挡、折叠屏适配全解
前端
爱勇宝25 分钟前
公司没给活干,却因为员工看手机把人开了:法院判赔11万元
前端·后端·程序员
Patrick_Wilson26 分钟前
iOS Safari 按钮点击无响应问题修复指南
前端·ios·safari
Csvn34 分钟前
模块化与工程化:CJS / ESM / 打包器,到底怎么协作?
前端
hunterandroid1 小时前
[鸿蒙从零到一] ArkTS Sendable 数据模型与跨线程共享实战
前端
又见情义1 小时前
RK3568 + RTL8211F 网络唤醒(WOL)功能适配全记录
android·网络·驱动开发
AICoder码农王1 小时前
Vitest 实战:从一个真实项目看懂测试套件
前端·前端框架
用户0934077735141 小时前
HarmonyOS WPS Open SDK 实践:registerApp 鉴权与就绪门禁
android·typescript·harmonyos