刷新 MediaBrowserService 订阅内容

Yod*_*ltz 5 android kotlin android-mediasession mediabrowserservice mediabrowserservicecompat

TL;DR:我已经成功地创建和耦合(通过订阅)一个活动到媒体浏览器服务。此媒体浏览器服务可以继续运行并在后台播放音乐。我希望能够在某个阶段刷新内容,无论是在应用程序再次出现在前台时还是在 SwipeRefreshLayout 事件期间。

我想实现以下功能:

  1. 启动 MediaBrowserServiceCompat 服务。
  2. 从活动中,连接并订阅媒体浏览器服务。
  3. 允许服务在应用程序关闭时继续运行和播放音乐。
  4. 在稍后阶段,或在 SwipeRefreshLayout 事件上,重新连接并订阅服务以获取新鲜内容。

我收到的问题是在 MediaBrowserService 中(在创建订阅后)您只能从 onLoadChildren() 方法调用 sendResult() 一次,因此下次尝试使用相同的根订阅媒体浏览器服务时,第二次调用 sendResult() 时会出现以下异常:

E/UncaughtException: java.lang.IllegalStateException: sendResult() called when either sendResult() or sendError() had already been called for: MEDIA_ID_ROOT
                                                    at android.support.v4.media.MediaBrowserServiceCompat$Result.sendResult(MediaBrowserServiceCompat.java:602)
                                                    at com.roostermornings.android.service.MediaService.loadChildrenImpl(MediaService.kt:422)
                                                    at com.roostermornings.android.service.MediaService.access$loadChildrenImpl(MediaService.kt:50)
                                                    at com.roostermornings.android.service.MediaService$onLoadChildren$1$onSyncFinished$playerEventListener$1.onPlayerStateChanged(MediaService.kt:376)
                                                    at com.google.android.exoplayer2.ExoPlayerImpl.handleEvent(ExoPlayerImpl.java:422)
                                                    at com.google.android.exoplayer2.ExoPlayerImpl$1.handleMessage(ExoPlayerImpl.java:103)
                                                    at android.os.Handler.dispatchMessage(Handler.java:102)
                                                    at android.os.Looper.loop(Looper.java:150)
                                                    at android.app.ActivityThread.main(ActivityThread.java:5665)
                                                    at java.lang.reflect.Method.invoke(Native Method)
                                                    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:822)
                                                    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:712)
Run Code Online (Sandbox Code Playgroud)

我调用以下方法来连接和断开媒体浏览器(同样,第一次连接时一切运行顺利,但在第二次连接时,我不确定如何通过订阅刷新内容):

override fun onStart() {
        super.onStart()

        mMediaBrowser = MediaBrowserCompat(this, ComponentName(this, MediaService::class.java), connectionCallback, null)

        if (!mMediaBrowser.isConnected)
            mMediaBrowser.connect()
}

override fun onPause() {
        super.onPause()

        //Unsubscribe and unregister MediaControllerCompat callbacks
        MediaControllerCompat.getMediaController(this@DiscoverFragmentActivity)?.unregisterCallback(mediaControllerCallback)
        if (mMediaBrowser.isConnected) {
            mMediaBrowser.unsubscribe(mMediaBrowser.root, subscriptionCallback)
            mMediaBrowser.disconnect()
        }
}
Run Code Online (Sandbox Code Playgroud)

我取消订阅并断开 onPause() 而不是 onDestroy() 以便即使活动保留在后台堆栈中也会重新创建订阅。

用于滑动刷新的实际方法,分别在活动和服务中:

活动

if (mMediaBrowser.isConnected)
        mMediaController?.sendCommand(MediaService.Companion.CustomCommand.REFRESH.toString(), null, null)
Run Code Online (Sandbox Code Playgroud)

服务

inner class MediaPlaybackPreparer : MediaSessionConnector.PlaybackPreparer {

    ...

    override fun onCommand(command: String?, extras: Bundle?, cb: ResultReceiver?) {
        when(command) {
            // Refresh media browser content and send result to subscribers
            CustomCommand.REFRESH.toString() -> {
                notifyChildrenChanged(MEDIA_ID_ROOT)
            }
        }
    }}
Run Code Online (Sandbox Code Playgroud)

其他研究:

我参考了 Github 上的 Google Samples 代码,以及...

在创建媒体浏览器服务并且至少订阅了一次 Activity 之后,上述存储库似乎都没有处理刷新内容的问题- 我想避免重新启动服务,以便音乐可以继续在后台播放。

可能的相关问题:

Yod*_*ltz 0

我的问题与 MediaBrowserServiceCompat 类无关。出现这个问题是因为我调用是result.detach()为了实现一些异步数据获取,而我使用的侦听器将onLoadChildren 方法中的parentId和变量传入并分配为 Final而不是。resultvalvar

我仍然不完全理解为什么会发生这种情况,是否是Player.EventListener在另一个异步网络调用侦听器中使用 a 的潜在结果,但解决方案是创建并分配一个变量(也许其他人可以解释这种现象):

// Create variable
var currentResult: Result<List<MediaBrowserCompat.MediaItem>>? = null

override fun onLoadChildren(parentId: String, result: MediaBrowserServiceCompat.Result<List<MediaBrowserCompat.MediaItem>>) {
    // Use result.detach to allow calling result.sendResult from another thread
    result.detach()
    // Assign returned result to temporary variable
    currentResult = result
    currentParentId = parentId

    // Create listener for network call
    ChannelManager.onFlagChannelManagerDataListener = object : ChannelManager.Companion.OnFlagChannelManagerDataListener {
       override fun onSyncFinished() {
            // Create a listener to determine when player is prepared
            val playerEventListener = object : Player.EventListener {

                override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) {
                     when(playbackState) {
                        Player.STATE_READY -> {
                            if(mPlayerPreparing) {
                                // Prepare content to send to subscribed content
                                loadChildrenImpl(currentParentId, currentResult as MediaBrowserServiceCompat.Result<List<MediaBrowserCompat.MediaItem>>)
                                mPlayerPreparing = false
                            }
                        }
                        ...
                     }
                }
       }

    }
Run Code Online (Sandbox Code Playgroud)