刷新MediaBrowserService订阅内容

时间:2017-10-04 14:51:53

标签: android kotlin android-mediasession mediabrowserservice mediabrowserservicecompat

  

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

我有以下功能:

  1. 启动MediaBrowserServiceCompat服务。
  2. 从活动中,连接并订阅媒体浏览器服务。
  3. 在应用关闭时,允许服务继续运行并播放音乐。
  4. 在稍后阶段或SwipeRefreshLayout事件中,重新连接并订阅该服务以获取新内容。
  5. 我收到的问题是在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)
    

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

    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()
            }
    }
    

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

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

    活动

    if (mMediaBrowser.isConnected)
            mMediaController?.sendCommand(MediaService.Companion.CustomCommand.REFRESH.toString(), null, null)
    

    服务

    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)
                }
            }
        }}
    

    其他研究:

    我在Github上提到了Google Samples代码,以及......

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

    可能的相关问题:

3 个答案:

答案 0 :(得分:0)

我的问题与MediaBrowserServiceCompat类无关。问题出现了,因为我正在调用result.detach()以实现一些异步数据获取,而我使用的监听器同时传入了来自onLoadChildren方法的parentIdresult变量并指定了最终val而不是var

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

// 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
                            }
                        }
                        ...
                     }
                }
       }

    }

答案 1 :(得分:0)

调用音乐服务实现notifyChildrenChanged(String parentId)将触发onLoadChildren,在其中,您可以使用result.sendResult()发送不同的结果。

我所做的是在音乐服务中添加了一个BroadcastReceiver,在其中,我刚好叫notifyChildrenChanged(String parentId)。在我的“活动”中,当我更改音乐列表时,我发送了一个广播。

答案 2 :(得分:0)

可选(不推荐)快速修复

音乐服务 ->

companion object {
    var musicServiceInstance:MusicService?=null
}

override fun onCreate() {
    super.onCreate()
    musicServiceInstance=this
}

//api call
fun fetchSongs(params:Int){
    serviceScope.launch {
        firebaseMusicSource.fetchMediaData(params)

        //Edit Data or Change Data
         notifyChildrenChanged(MEDIA_ROOT_ID)
    }
}

视图模型 ->

fun fetchSongs(){
    MusicService.musicServiceInstance?.let{
      it.fetchSongs(params)
     }
}

可选(推荐)

MusicPlaybackPreparer

class MusicPlaybackPreparer (
private val firebaseMusicSource: FirebaseMusicSource,
private val serviceScope: CoroutineScope,
private val exoPlayer: SimpleExoPlayer,
private val playerPrepared: (MediaMetadataCompat?) -> Unit

) : MediaSessionConnector.PlaybackPreparer {

override fun onCommand(player: Player, controlDispatcher: ControlDispatcher, command: String, extras: Bundle?, cb: ResultReceiver?
): Boolean {
    when(command){
         //edit data or fetch more data from api
        "Add Songs"->{
            serviceScope.launch {
                firebaseMusicSource.fetchMediaData()
            }
         }
       
    }
    return false
}


override fun getSupportedPrepareActions(): Long {
    return PlaybackStateCompat.ACTION_PREPARE_FROM_MEDIA_ID or
            PlaybackStateCompat.ACTION_PLAY_FROM_MEDIA_ID
}

override fun onPrepare(playWhenReady: Boolean) = Unit

override fun onPrepareFromMediaId(mediaId: String, playWhenReady: Boolean, extras: Bundle?) {
    firebaseMusicSource.whenReady {
        val itemToPlay = firebaseMusicSource.songs.find { mediaId == it.description.mediaId }
        playerPrepared(itemToPlay)
    }
}

override fun onPrepareFromSearch(query: String, playWhenReady: Boolean, extras: Bundle?) = Unit

override fun onPrepareFromUri(uri: Uri, playWhenReady: Boolean, extras: Bundle?) = Unit

}

音乐服务连接

fun sendCommand(command: String, parameters: Bundle?) =
    sendCommand(command, parameters) { _, _ -> }

private fun sendCommand(
    command: String,
    parameters: Bundle?,
    resultCallback: ((Int, Bundle?) -> Unit)
) = if (mediaBrowser.isConnected) {
    mediaController.sendCommand(command, parameters, object : ResultReceiver(Handler()) {
        override fun onReceiveResult(resultCode: Int, resultData: Bundle?) {
            resultCallback(resultCode, resultData)
        }
    })
    true
} else {
    false
}

视图模型

 fun fetchSongs(){
    val args = Bundle()
    args.putInt("nRecNo", 2)
    musicServiceConnection.sendCommand("Add Songs", args )
}

音乐服务 ->

 override fun onLoadChildren(
    parentId: String,
    result: Result<MutableList<MediaBrowserCompat.MediaItem>>
) {
    when(parentId) {
        MEDIA_ROOT_ID -> {
            val resultsSent = firebaseMusicSource.whenReady { isInitialized ->
                if(isInitialized) {
                    try {
                        result.sendResult(firebaseMusicSource.asMediaItems())
                        if(!isPlayerInitialized && firebaseMusicSource.songs.isNotEmpty()) {
                            preparePlayer(firebaseMusicSource.songs, firebaseMusicSource.songs[0], true)
                            isPlayerInitialized = true
                        }
                    }
                   catch (exception: Exception){
                       // not recommend to notify here , instead notify when you 
                       // change existing list in MusicPlaybackPreparer onCommand()
                       notifyChildrenChanged(MEDIA_ROOT_ID)
                   }
                } else {
                    result.sendResult(null)
                }
            }
            if(!resultsSent) {
                result.detach()
            }
        }
    }
}