You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在MediaBrowserServiceCompat中访问SimpleExoPlayer实例

ExoPlayer + MediaBrowserServiceCompat 实现方案

核心逻辑不需要序列化传递播放器实例:ExoPlayer 实例全局唯一托管在 MediaBrowserServiceCompat 中,Activity 通过绑定服务直接获取同进程下的播放器对象引用,再绑定给 PlayerView 即可,完全符合官方架构要求。


步骤1:迁移播放器初始化逻辑到Service

移除 Activity 中创建 SimpleExoPlayer 的代码,把所有播放器初始化、MediaSession 关联逻辑放到你的 MediaBrowserServiceCompat 实现类中:

class PodcastPlayService : MediaBrowserServiceCompat() {
    lateinit var exoPlayer: SimpleExoPlayer
        private set
    private lateinit var mediaSession: MediaSessionCompat
    private lateinit var notificationManager: PlayerNotificationManager
    private val NOTIFICATION_ID = 1001

    inner class LocalBinder : Binder() {
        fun getService() = this@PodcastPlayService
    }

    override fun onBind(intent: Intent?): IBinder {
        return LocalBinder()
    }

    override fun onCreate() {
        super.onCreate()
        // 初始化MediaSession,作为系统媒体控件、通知栏的交互枢纽
        mediaSession = MediaSessionCompat(this, "PodcastPlay").apply {
            isActive = true
            setFlags(
                MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS 
                or MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS
            )
        }
        sessionToken = mediaSession.sessionToken

        // 原Activity中的ExoPlayer初始化逻辑挪到这里
        val renderersFactory = DefaultRenderersFactory(this)
        val trackSelectionFactory = AdaptiveTrackSelection.Factory()
        val trackSelector = DefaultTrackSelector(trackSelectionFactory)
        val loadControl = DefaultLoadControl()
        exoPlayer = ExoPlayerFactory.newSimpleInstance(this, renderersFactory, trackSelector, loadControl)

        // 用ExoPlayer官方提供的MediaSessionConnector自动同步播放状态到MediaSession
        // 不需要自己写播放状态、进度、媒体按钮事件的转发逻辑
        MediaSessionConnector(mediaSession).setPlayer(exoPlayer)

        // 初始化通知栏管理器,自动生成锁屏、通知栏播放控件
        notificationManager = PlayerNotificationManager.createWithNotificationChannel(
            this,
            "podcast_play_channel",
            R.string.play_channel_name,
            NOTIFICATION_ID,
            object : PlayerNotificationManager.MediaDescriptionAdapter {
                override fun getCurrentContentTitle(player: Player): String {
                    return player.currentMediaItem?.mediaMetadata?.title?.toString() ?: "正在播放"
                }
                override fun getCurrentContentText(player: Player): String? {
                    return player.currentMediaItem?.mediaMetadata?.artist?.toString()
                }
                override fun getCurrentLargeIcon(player: Player, callback: PlayerNotificationManager.BitmapCallback): Bitmap? {
                    // 加载播客封面,异步加载完成后调用callback.onBitmap(bitmap)即可
                    return null
                }
                override fun createCurrentContentIntent(player: Player): PendingIntent? {
                    // 配置点击通知跳转回MainActivity的逻辑
                    return PendingIntent.getActivity(
                        this@PodcastPlayService,
                        0,
                        Intent(this@PodcastPlayService, MainActivity::class.java),
                        PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
                    )
                }
            }
        ).apply {
            setPlayer(exoPlayer)
            setMediaSessionToken(mediaSession.sessionToken)
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        exoPlayer.release()
        mediaSession.release()
        notificationManager.setPlayer(null)
    }

    // 按业务需求实现以下两个方法即可,基础播放功能不需要额外逻辑
    override fun onGetRoot(clientPkg: String, uid: Int, hints: Bundle?): BrowserRoot {
        return BrowserRoot("root", null)
    }
    override fun onLoadChildren(parentId: String, result: Result<MutableList<MediaBrowserCompat.MediaItem>>) {
        result.sendResult(mutableListOf())
    }
}

步骤2:Activity绑定Service获取播放器实例

同进程下通过 ServiceConnection 可以直接拿到 Service 的公开属性,不需要序列化传递对象:

class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding
    private var playService: PodcastPlayService? = null
    private var mediaBrowser: MediaBrowserCompat? = null

    private val serviceConnection = object : ServiceConnection {
        override fun onServiceConnected(name: ComponentName, binder: IBinder) {
            playService = (binder as PodcastPlayService.LocalBinder).getService()
            // 拿到全局唯一的ExoPlayer实例,直接绑定给PlayerView即可
            binding.bottomSheetParent.playerView.player = playService?.exoPlayer
        }
        override fun onServiceDisconnected(name: ComponentName) {
            playService = null
            binding.bottomSheetParent.playerView.player = null
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
    }

    override fun onStart() {
        super.onStart()
        // 连接MediaBrowserService获取MediaSession令牌
        mediaBrowser = MediaBrowserCompat(
            this,
            ComponentName(this, PodcastPlayService::class.java),
            object : MediaBrowserCompat.ConnectionCallback() {
                override fun onConnected() {
                    val token = mediaBrowser?.sessionToken ?: return
                    MediaControllerCompat.setMediaController(this@MainActivity, MediaControllerCompat(this@MainActivity, token))
                    // 绑定服务获取播放器实例
                    bindService(
                        Intent(this@MainActivity, PodcastPlayService::class.java),
                        serviceConnection,
                        Context.BIND_AUTO_CREATE
                    )
                }
            },
            null
        )
        mediaBrowser?.connect()
    }

    override fun onStop() {
        super.onStop()
        playService?.let {
            unbindService(serviceConnection)
            playService = null
        }
        mediaBrowser?.disconnect()
    }
}

注意事项

  • PlayerView 仅负责UI渲染和交互转发,只要绑定的是Service中托管的全局播放器实例,所有播放操作、状态同步、通知栏/锁屏控件更新都会自动生效,不需要额外做跨组件通信。
  • 播放器生命周期完全和Service绑定,Activity销毁、退到后台都不会中断播放,符合后台音频播放的要求。
  • 记得在 AndroidManifest.xml 中注册Service,添加前台服务权限、通知权限,Android 12及以上版本需要额外添加蓝牙媒体控制相关权限。
  • 不需要照搬UAMP中基于MediaPlayer的实现逻辑,ExoPlayer提供的MediaSessionConnector、PlayerNotificationManager已经封装了所有状态同步、媒体事件转发的逻辑,代码量比原生MediaPlayer实现少很多。

内容的提问来源于stack exchange,提问作者krupa parekh

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 17:09:14