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

相关推荐
踏雪羽翼4 小时前
android TextView实现文字字符不同方向显示
android·自定义view·textview方向·文字方向·textview文字显示方向·文字旋转·textview文字旋转
lxysbly4 小时前
安卓玩MRP冒泡游戏:模拟器下载与使用方法
android·游戏
夏沫琅琊7 小时前
Android 各类日志全面解析(含特点、分析方法、实战案例)
android
程序员JerrySUN7 小时前
OP-TEE + YOLOv8:从“加密权重”到“内存中解密并推理”的完整实战记录
android·java·开发语言·redis·yolo·架构
TeleostNaCl8 小时前
Android | 启用 TextView 跑马灯效果的方法
android·经验分享·android runtime
TheNextByte19 小时前
Android USB文件传输无法使用?5种解决方法
android
quanyechacsdn10 小时前
Android Studio创建库文件用jitpack构建后使用implementation方式引用
android·ide·kotlin·android studio·implementation·android 库文件·使用jitpack
程序员陆业聪11 小时前
聊聊2026年Android开发会是什么样
android
编程大师哥11 小时前
Android分层
android
极客小云13 小时前
【深入理解 Android 中的 build.gradle 文件】
android·安卓·安全架构·安全性测试