【问题标题】:Refreshing MediaBrowserService subcription content刷新 MediaBrowserService 订阅内容
【发布时间】:2017-10-04 14:51:52
【问题描述】:

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)

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

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 示例代码,以及...

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

可能的相关问题:

【问题讨论】:

  • 那么最后,你能告诉我们在拉刷新后你是如何刷新 MediaBrowserService 的吗?
  • @RicardoBelchior 我已将用于刷卡刷新的特定方法添加到我的问题中,因为它也不是我正在寻找答案的具体方法,但确实添加了更多上下文。希望对您有所帮助!
  • @YodaScholtz 谢谢。调用 notifyChildrenChanged() 解决了我的问题。

标签: android kotlin android-mediasession mediabrowserservice mediabrowserservicecompat


【解决方案1】:

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

我所做的是在我的音乐服务中添加了一个BroadcastReceiver,并在其中调用了notifyChildrenChanged(String parentId)。在我的 Activity 中,我在更改音乐列表时发送了一个广播。

【讨论】:

    【解决方案2】:

    可选(不推荐)快速修复

    音乐服务 ->

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

    ViewModel ->

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

    【讨论】:

      【解决方案3】:

      我的问题与 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
                                  }
                              }
                              ...
                           }
                      }
             }
      
          }
      

      【讨论】:

      • 嗨,我遇到了同样的问题,你能看到我的代码覆盖 fun onLoadChildren(parentMediaId: String, result: Result>) { result.detach() channelLists.clear() Log .d("children", parentMediaId) channelLists.addAll(StreamGenerator.generateSongs(this@AutoTechnoService)) result.sendResult(ChannelHelperNew.createChildrenListing(channelLists, this@AutoTechnoService)) }
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2010-09-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多