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

如何通过RecyclerView控制MediaBrowserServiceCompat的播放/暂停状态?

实现方案

1. 建立UI与MediaBrowserService的通信连接

在包含RecyclerView的Activity/Fragment中,通过MediaBrowserCompat绑定到你的MediaBrowserServiceCompat实现类,建立通信通道,后续通过MediaControllerCompat发送控制命令并监听播放状态。

private lateinit var mediaBrowser: MediaBrowserCompat

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    mediaBrowser = MediaBrowserCompat(
        this,
        ComponentName(this, YourMediaService::class.java),
        connectionCallback,
        null
    )
}

private val connectionCallback = object : MediaBrowserCompat.ConnectionCallback() {
    override fun onConnected() {
        val sessionToken = mediaBrowser.sessionToken
        val mediaController = MediaControllerCompat(this@YourActivity, sessionToken)
        MediaControllerCompat.setMediaController(this@YourActivity, mediaController)
        // 注册回调监听播放状态变化
        mediaController.registerCallback(controllerCallback)
        // 这里可以请求加载媒体列表,刷新RecyclerView
        mediaBrowser.subscribe("root", subscriptionCallback)
    }

    override fun onConnectionSuspended() {
        // 处理连接中断逻辑
    }

    override fun onConnectionFailed() {
        // 处理连接失败逻辑
    }
}

private val subscriptionCallback = object : MediaBrowserCompat.SubscriptionCallback() {
    override fun onChildrenLoaded(parentId: String, children: List<MediaBrowserCompat.MediaItem>) {
        // 将加载到的媒体项传给RecyclerView Adapter
        mediaAdapter.updateData(children)
    }
}

override fun onStart() {
    super.onStart()
    mediaBrowser.connect()
}

override fun onStop() {
    super.onStop()
    MediaControllerCompat.getMediaController(this)?.unregisterCallback(controllerCallback)
    mediaBrowser.disconnect()
}

2. RecyclerView列表项点击逻辑实现

在Adapter中保存当前播放的媒体ID和播放状态,点击时根据当前状态判断执行播放/暂停/切换操作:

Adapter代码

class MediaAdapter : RecyclerView.Adapter<MediaAdapter.MediaViewHolder>() {
    private var mediaItems = emptyList<MediaBrowserCompat.MediaItem>()
    var currentPlayingMediaId: String? = null
    var isPlaying: Boolean = false
    private var onClickListener: ((MediaBrowserCompat.MediaItem) -> Unit)? = null

    inner class MediaViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        fun bind(mediaItem: MediaBrowserCompat.MediaItem) {
            itemView.tv_title.text = mediaItem.description.title
            // 根据播放状态切换图标
            itemView.iv_play_pause.setImageResource(
                if (currentPlayingMediaId == mediaItem.mediaId && isPlaying) 
                    R.drawable.ic_pause else R.drawable.ic_play
            )
            itemView.setOnClickListener { onClickListener?.invoke(mediaItem) }
        }
    }

    fun updateData(newItems: List<MediaBrowserCompat.MediaItem>) {
        mediaItems = newItems
        notifyDataSetChanged()
    }

    fun setOnClickListener(listener: (MediaBrowserCompat.MediaItem) -> Unit) {
        onClickListener = listener
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MediaViewHolder {
        val view = LayoutInflater.from(parent.context).inflate(R.layout.item_media, parent, false)
        return MediaViewHolder(view)
    }

    override fun onBindViewHolder(holder: MediaViewHolder, position: Int) {
        holder.bind(mediaItems[position])
    }

    override fun getItemCount() = mediaItems.size
}

Activity/Fragment中处理点击事件

mediaAdapter.setOnClickListener { mediaItem ->
    val mediaController = MediaControllerCompat.getMediaController(this) ?: return@setOnClickListener
    val currentPlayingId = mediaController.metadata?.description?.mediaId
    val playbackState = mediaController.playbackState

    when {
        // 当前播放项为点击项且处于播放状态 -> 暂停
        currentPlayingId == mediaItem.mediaId && playbackState?.state == PlaybackStateCompat.STATE_PLAYING -> {
            mediaController.transportControls.pause()
        }
        // 当前播放项为点击项且处于暂停状态 -> 继续播放
        currentPlayingId == mediaItem.mediaId && playbackState?.state == PlaybackStateCompat.STATE_PAUSED -> {
            mediaController.transportControls.play()
        }
        // 点击非当前播放项 -> 切换播放
        else -> {
            mediaController.transportControls.playFromMediaId(mediaItem.mediaId, null)
        }
    }
}

3. 同步播放状态到UI

通过MediaControllerCompat.Callback监听Service的播放状态和元数据变化,实时更新Adapter状态并刷新列表:

private val controllerCallback = object : MediaControllerCompat.Callback() {
    override fun onPlaybackStateChanged(state: PlaybackStateCompat?) {
        mediaAdapter.isPlaying = state?.state == PlaybackStateCompat.STATE_PLAYING
        mediaAdapter.notifyDataSetChanged()
    }

    override fun onMetadataChanged(metadata: MediaMetadataCompat?) {
        mediaAdapter.currentPlayingMediaId = metadata?.description?.mediaId
        mediaAdapter.notifyDataSetChanged()
    }
}

4. Service层处理播放命令

在MediaBrowserServiceCompat实现类中,通过MediaSessionCompat.Callback接收并处理播放控制命令:

class YourMediaService : MediaBrowserServiceCompat() {
    private lateinit var mediaSession: MediaSessionCompat
    private lateinit var mediaPlayer: MediaPlayer

    override fun onCreate() {
        super.onCreate()
        mediaSession = MediaSessionCompat(this, "YourMediaService")
        mediaSession.setCallback(sessionCallback)
        mediaSession.isActive = true
        sessionToken = mediaSession.sessionToken

        mediaPlayer = MediaPlayer().apply {
            setOnCompletionListener {
                updatePlaybackState(PlaybackStateCompat.STATE_PAUSED)
            }
        }
    }

    private val sessionCallback = object : MediaSessionCompat.Callback() {
        override fun onPlayFromMediaId(mediaId: String?, extras: Bundle?) {
            mediaId ?: return
            val mediaUri = getMediaUriById(mediaId)
            mediaPlayer.reset()
            mediaPlayer.setDataSource(applicationContext, mediaUri)
            mediaPlayer.prepareAsync()
            mediaPlayer.setOnPreparedListener { player ->
                player.start()
                updatePlaybackState(PlaybackStateCompat.STATE_PLAYING)
                updateMediaMetadata(mediaId)
            }
        }

        override fun onPause() {
            mediaPlayer.pause()
            updatePlaybackState(PlaybackStateCompat.STATE_PAUSED)
        }

        override fun onPlay() {
            mediaPlayer.start()
            updatePlaybackState(PlaybackStateCompat.STATE_PLAYING)
        }
    }

    private fun updatePlaybackState(state: Int) {
        val playbackState = PlaybackStateCompat.Builder()
            .setState(state, mediaPlayer.currentPosition, 1f)
            .setActions(PlaybackStateCompat.ACTION_PLAY or PlaybackStateCompat.ACTION_PAUSE)
            .build()
        mediaSession.setPlaybackState(playbackState)
    }

    private fun updateMediaMetadata(mediaId: String) {
        val metadata = MediaMetadataCompat.Builder()
            .putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, mediaId)
            .putString(MediaMetadataCompat.METADATA_KEY_TITLE, getMediaTitleById(mediaId))
            .build()
        mediaSession.setMetadata(metadata)
    }

    // 根据mediaId获取媒体Uri的自定义逻辑
    private fun getMediaUriById(mediaId: String): Uri {
        return Uri.parse("your_media_source/$mediaId")
    }

    // 根据mediaId获取媒体标题的自定义逻辑
    private fun getMediaTitleById(mediaId: String): String {
        return "Media $mediaId"
    }

    override fun onGetRoot(clientPackageName: String, clientUid: Int, rootHints: Bundle?): BrowserRoot? {
        return BrowserRoot("root", null)
    }

    override fun onLoadChildren(parentId: String, result: Result<List<MediaBrowserCompat.MediaItem>>) {
        // 加载媒体列表的逻辑,比如从本地数据库或网络获取
        val mediaItems = mutableListOf<MediaBrowserCompat.MediaItem>()
        // 填充媒体项
        result.sendResult(mediaItems)
    }

    override fun onDestroy() {
        super.onDestroy()
        mediaPlayer.release()
        mediaSession.release()
    }
}

核心逻辑说明

  • 通信链路:UI层通过MediaBrowser连接Service,获取MediaController后,通过TransportControls发送控制命令;Service通过MediaSession的回调接收命令并处理播放逻辑。
  • 状态同步:UI层通过MediaController的回调监听Service的播放状态和元数据变化,实时更新RecyclerView列表项的显示状态。
  • 点击判断:每次点击列表项时,先对比当前播放项ID和点击项ID,结合播放状态决定发送播放、暂停还是切换播放命令。

内容的提问来源于stack exchange,提问作者David Feldman

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 02:45:44