如何通过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
相关产品推荐
相关产品推荐

