TL;DR: I have successfully created and coupled (via a subscription) an activity to a media browser service. This media browser service can continue running and play music in the background. I'd like to be able to refresh the content at some stage, either when the app comes to the foreground again or during a SwipeRefreshLayout event.
I have the following functionality I'd like to implement:
- Start a MediaBrowserServiceCompat service.
- From an activity, connect to and subscribe to the media browser service.
- Allow the service to continue running and playing music while the app is closed.
- At a later stage, or on a SwipeRefreshLayout event, reconnect and subscribe to the service to get fresh content.
The issue I am receiving is that within a MediaBrowserService (after a subscription has been created) you can only call sendResult() once from the onLoadChildren() method, so the next time you try to subscribe to the media browser service using the same root, you get the following exception when sendResult() is called for the second time:
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)
I call the following methods to connect to and disconnect from the media browser (again, everything runs smoothly on first connection, but on the second connection I'm not sure how to refresh the content via a subscription):
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()
}
}
I unsubscribe and disconnect in onPause() instead of onDestroy() so that the subscription is recreated even if the activity is kept on the back-stack.
Actual method used for swipe refresh, in activity and service respectively:
Activity
if (mMediaBrowser.isConnected)
mMediaController?.sendCommand(MediaService.Companion.CustomCommand.REFRESH.toString(), null, null)
Service
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)
}
}
}}
Other research:
I have referred to the Google Samples code on Github, as well as...
- https://github.com/googlesamples/android-MediaBrowserService
- https://github.com/moondroid/UniversalMusicPlayer
Neither of the above repos seem to handle the issue of refreshing content after the media browser service has been created and the activity has subscribed at least once - I'd like to avoid restarting the service so that the music can continue playing in the background.
Possible related issues:
- MediaBrowser.subscribe doesn't work after I get back to activity 1 from activity 2 (6.0.1 Android) --no effect on current issue
My issue was unrelated to the MediaBrowserServiceCompat class. The issue was coming about because I was calling
result.detach()
in order to implement some asynchronous data fetching, and the listener I was using had both theparentId
andresult
variables from the onLoadChildren method passed in and assigned finalval
rather thanvar
.I still don't fully understand why this occurs, whether it's an underlying result of using a
Player.EventListener
within another asynchronous network call listener, but the solution was to create and assign a variable (and perhaps someone else can explain this phenomenon):