Android Media3 — 在后台播放视频

目前主流的音视频App中,除了当用户正在使用时播放视频或音频,应用回到后台后音视频仍能继续播放的功能也很常见。本文简单介绍下如何使用Media3库实现在后台播放视频。

官方文档

添加依赖

在app module下的build.gradle中添加代码,如下:

scss 复制代码
dependencies {
    implementation("androidx.media3:media3-ui:1.1.0")
    implementation("androidx.media3:media3-session:1.1.0")
    implementation("androidx.media3:media3-exoplayer:1.1.0")
}

添加相关权限

AndroidManifest中添加前台服务权限,具体如下:

xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
    
    <!--targetSdk34开始需要添加此权限-->
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />

    ...
</manifest>

前台服务权限按理来说需要动态申请,但是在测试过程中,发现没有动态申请也能实现在后台播放视频的功能。

实现后台播放

自定义MediaSessionService

自定义ExamplePlaybackService继承MediaSessionService,代码如下:

kotlin 复制代码
class ExamplePlaybackService : MediaSessionService() {

    private var exoPlayer: ExoPlayer? = null
    private var mediaSession: MediaSession? = null

    override fun onCreate() {
        super.onCreate()
        // 创建ExoPlayer
        exoPlayer = ExoPlayer.Builder(this).build()
        // 基于已创建的ExoPlayer创建MediaSession
        exoPlayer?.let { mediaSession = MediaSession.Builder(this, it).build() }
    }

    override fun onDestroy() {
        // 释放相关实例
        exoPlayer?.stop()
        exoPlayer?.release()
        exoPlayer = null
        mediaSession?.release()
        mediaSession = null
        super.onDestroy()
    }

    override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaSession? {
        return mediaSession
    }
}

在Manifest中注册MediaSessionService

AndroidManifest中添加ExamplePlaybackService,代码如下:

xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

    ...

    <application
        ... >

        <!--android 10开始,需要指定foregroundServiceType-->
        <service
            android:name=".androidapi.media3.ExamplePlaybackService"
            android:exported="true"
            android:foregroundServiceType="mediaPlayback">
            <!--如果需要兼容低版本,需要添加此intent-filter-->
            <intent-filter>
                <action android:name="androidx.media3.session.MediaSessionService" />
            </intent-filter>
        </service>
    </application>
</manifest>

播放视频

kotlin 复制代码
class Media3ExampleActivity : AppCompatActivity() {

    private lateinit var binding: LayoutMedia3ExampleActivityBinding

    private lateinit var controllerFuture: ListenableFuture<MediaController>
    private val mediaController: MediaController?
        get() = if (controllerFuture.isDone) controllerFuture.get() else null

    private val playerListener = object : Player.Listener {
        override fun onIsPlayingChanged(isPlaying: Boolean) {
            super.onIsPlayingChanged(isPlaying)
            // 播放状态变化回调
        }

        override fun onPlaybackStateChanged(playbackState: Int) {
            super.onPlaybackStateChanged(playbackState)
            when (playbackState) {
                Player.STATE_IDLE -> {
                    //播放器停止时的状态
                }

                Player.STATE_BUFFERING -> {
                    // 正在缓冲数据
                }

                Player.STATE_READY -> {
                    // 可以开始播放
                }

                Player.STATE_ENDED -> {
                    // 播放结束
                }
            }

        }

        override fun onPlayerError(error: PlaybackException) {
            super.onPlayerError(error)
            // 获取播放错误信息
        }
    }

    @UnstableApi
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = LayoutMedia3ExampleActivityBinding.inflate(layoutInflater)
        setContentView(binding.root)
        binding.includeTitle.tvTitle.text = "Media3 Example"

        binding.btnPlaySingleVideo.setOnClickListener {
            binding.playView.player?.run {
                // 停止之前播放的视频
                stop()
                //设置单个资源
                setMediaItem(MediaItem.fromUri("https://minigame.vip/Uploads/images/2021/09/18/1631951892_page_img.mp4"))
                // 开始缓冲
                prepare()
            }
        }
        binding.btnPlayMultiVideo.setOnClickListener {
            binding.playView.player?.run {
                // 停止之前播放的视频
                stop()
                // 设置多个资源,当一个视频播完后自动播放下一个
                setMediaItems(arrayListOf(
                    MediaItem.fromUri("https://minigame.vip/Uploads/images/2021/09/18/1631951892_page_img.mp4"),
                    MediaItem.fromUri("https://storage.googleapis.com/exoplayer-test-media-1/mp4/dizzy-with-tx3g.mp4")
                ))
                // 开始缓冲
                prepare()
            }
        }
    }

    private fun initController() {
        controllerFuture = MediaController.Builder(this, SessionToken(this, ComponentName(this, ExamplePlaybackService::class.java)))
            .buildAsync()
        controllerFuture.addListener({
            binding.root.post {
                mediaController?.let {
                    binding.playView.player = it.apply {
                        // 设置播放监听
                        addListener(playerListener)
                        // 设置重复模式
                        // Player.REPEAT_MODE_ALL 无限重复
                        // Player.REPEAT_MODE_ONE 重复一次
                        // Player.REPEAT_MODE_OFF 不重复
                        repeatMode = Player.REPEAT_MODE_ALL
                        // 设置当缓冲完毕后直接播放视频
                        playWhenReady = true
                    }
                }
            }
        }, ContextCompat.getMainExecutor(this))
    }

    override fun onStart() {
        super.onStart()
        initController()
    }

    override fun onStop() {
        super.onStop()
        binding.playView.player = null
        MediaController.releaseFuture(controllerFuture)
    }

    override fun onDestroy() {
        super.onDestroy()
        // 释放播放器资源
        binding.playView.player?.release()
        binding.playView.player = null
    }
}

效果如图:

可以看到,Media3库内部实现了用于播放的前台服务和通知,不用做额外的操作就可以简单的实现相关功能。

示例

演示代码已在示例Demo中添加。

ExampleDemo github

ExampleDemo gitee

相关推荐
tangweiguo0305198727 分钟前
Android应用完全重启指南:从任务重置到进程重生
android
2501_9160074735 分钟前
uni-app iOS 文件调试常见问题与解决方案:结合 itools、克魔、iMazing 的实战经验
android·ios·小程序·https·uni-app·iphone·webview
ljt27249606611 小时前
Compose笔记(四十九)--SwipeToDismiss
android·笔记·android jetpack
fatiaozhang95271 小时前
山西移动九联UNT413HS-海思MV320-2+8G-原机全量备份包
android·电脑·电视盒子·刷机固件·机顶盒刷机
某空m1 小时前
【Android】ViewPager2结合Fragment实现多页面滑动切换
android
旺小仔.1 小时前
Linux--命名管道
android·java·linux
2501_915918418 小时前
uni-app 项目 iOS 上架踩坑经验总结 从证书到审核的避坑指南
android·ios·小程序·https·uni-app·iphone·webview
游戏开发爱好者88 小时前
iOS 上架 uni-app 流程全解析,从打包到发布的完整实践
android·ios·小程序·https·uni-app·iphone·webview
雨白12 小时前
实现双向滑动的 ScalableImageView(上)
android
Y40900113 小时前
数据库基础知识——聚合函数、分组查询
android·数据库