Android Auto媒体应用中PlaybackState的ACTION_STOP无法正常工作
问题:Android Auto媒体应用无法暂停/停止,控制按钮显示异常
开发的Android Auto媒体应用已实现MediaItem创建、元数据展示及基础播放功能,但存在以下问题:
- 仅能播放媒体,无法执行暂停或停止操作
- 播放启动后,界面显示停止按钮而非预期的暂停按钮
- 点击停止按钮无任何响应
完整MusicService代码
// imports here class MusicService : MediaBrowserServiceCompat() { private lateinit var mediaSession: MediaSessionCompat private lateinit var mediaController: MediaControllerCompat private var playbackStateBuilder = PlaybackStateCompat.Builder() override fun onCreate() { super.onCreate() mediaSession = MediaSessionCompat(baseContext, "MusicService").apply { setCallback(MyMediaSessionCallback()) setPlaybackState( playbackStateBuilder.apply { setState(PlaybackStateCompat.STATE_NONE, 0L, 1f) addActionsForPlayback(this) // addCustomActionsForPlayback(this) }.build() ) // setFlags( // MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS or // MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS ) isActive = true } sessionToken = mediaSession.sessionToken mediaController = MediaControllerCompat(baseContext, mediaSession.sessionToken).apply { registerCallback(MyMediaControllerCallback()) } } override fun onGetRoot( clientPackageName: String, clientUid: Int, rootHints: Bundle? ): BrowserRoot { val extras = Bundle() extras.putBoolean( MediaConstants.BROWSER_SERVICE_EXTRAS_KEY_SEARCH_SUPPORTED, true ) extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM ) extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM ) return BrowserRoot("/", extras) } override fun onLoadChildren( parentId: String, result: Result<MutableList<MediaItem>> ) { val treeResult = BrowseTree(baseContext)[parentId] if (treeResult.isNullOrEmpty()) { result.detach() } else { result.sendResult(treeResult) } } private fun addActionsForPlayback(playbackStateCompatBuilder: PlaybackStateCompat.Builder) { playbackStateCompatBuilder.apply { setActions(PlaybackStateCompat.ACTION_PLAY) setActions(PlaybackStateCompat.ACTION_PAUSE) // setActions(PlaybackStateCompat.ACTION_PLAY_PAUSE) setActions(PlaybackStateCompat.ACTION_STOP) setActions(PlaybackStateCompat.ACTION_PLAY_FROM_MEDIA_ID) setActions(PlaybackStateCompat.ACTION_PLAY_FROM_SEARCH) setActions(PlaybackStateCompat.ACTION_SEEK_TO) } } private fun addCustomActionsForPlayback(playbackStateCompatBuilder: PlaybackStateCompat.Builder) { // custom actions defined here } inner class MyMediaSessionCallback : MediaSessionCompat.Callback() { private val musicSource = MusicSource(this@MusicService) private var seekedPos: Long? = null override fun onPrepare() { println("Preparing") super.onPrepare() } override fun onPrepareFromMediaId(mediaId: String?, extras: Bundle?) { println("Preparing from media id") super.onPrepareFromMediaId(mediaId, extras) } override fun onPlay() { println("Playing") mediaSession.setPlaybackState( playbackStateBuilder.apply { setState(PlaybackStateCompat.STATE_PLAYING, seekedPos ?: 0L, 1f) setActions(PlaybackStateCompat.ACTION_PAUSE) setActions(PlaybackStateCompat.ACTION_PLAY_PAUSE) setActions(PlaybackStateCompat.ACTION_STOP) setActions(PlaybackStateCompat.ACTION_SEEK_TO) setActions(PlaybackStateCompat.ACTION_PLAY) }.build() ) super.onPlay() } override fun onPause() { println("Paused") super.onPause() } override fun onStop() { println("Stopped") mediaSession.release() super.onStop() } override fun onSeekTo(pos: Long) { println("Seeked to: $pos") seekedPos = pos if (mediaController.playbackState.state == 3) { mediaSession.setPlaybackState( playbackStateBuilder.apply { setState(PlaybackStateCompat.STATE_PLAYING, pos, 1f) addActionsForPlayback(this) }.build() ) } else { mediaSession.setPlaybackState( playbackStateBuilder.apply { setState(PlaybackStateCompat.STATE_PAUSED, pos, 1f) addActionsForPlayback(this) }.build() ) } super.onSeekTo(pos) } override fun onRewind() { println("Rewind command") super.onRewind() } override fun onPlayFromMediaId(mediaId: String?, extras: Bundle) { seekedPos = null val defaultArt = BitmapFactory.decodeResource( this@MusicService.resources, R.drawable.music_library_icon_2 ) val albumCover = musicSource.getAlbumCover(extras.getString("PATH", "")) mediaSession.apply { setPlaybackState(playbackStateBuilder.apply { setState(PlaybackStateCompat.STATE_STOPPED, 0L, 1f) setBufferedPosition(extras.getLong(MediaMetadataCompat.METADATA_KEY_DURATION)) setActions(PlaybackStateCompat.ACTION_PLAY) setActions(PlaybackStateCompat.ACTION_PLAY_PAUSE) setActions(PlaybackStateCompat.ACTION_STOP) setActions(PlaybackStateCompat.ACTION_SEEK_TO) setActions(PlaybackStateCompat.ACTION_PAUSE) val playbackStateExtras = Bundle().apply { putString( MediaConstants.PLAYBACK_STATE_EXTRAS_KEY_MEDIA_ID, extras.getString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID) ) } setExtras(playbackStateExtras) addActionsForPlayback(this) }.build()) setMetadata( MediaMetadataCompat.Builder().apply { putString( MediaMetadataCompat.METADATA_KEY_MEDIA_ID, extras.getString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID) ) putString( MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, extras.getString(MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE) ) putString( MediaMetadataCompat.METADATA_KEY_ARTIST, extras.getString(MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE) ) putString( MediaMetadataCompat.METADATA_KEY_ALBUM_ARTIST, extras.getString(MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE) ) putString( MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE, extras.getString(MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE) ) putLong( MediaMetadataCompat.METADATA_KEY_DURATION, extras.getLong(MediaMetadataCompat.METADATA_KEY_DURATION) ) putString( MediaMetadataCompat.METADATA_KEY_MEDIA_URI, extras.getString(MediaMetadataCompat.METADATA_KEY_MEDIA_URI) ) if (albumCover != null) { putBitmap( MediaMetadataCompat.METADATA_KEY_ART, albumCover ) } else { putBitmap( MediaMetadataCompat.METADATA_KEY_ART, defaultArt ) } putLong( MediaConstants.METADATA_KEY_IS_EXPLICIT, extras.getLong(MediaConstants.METADATA_KEY_IS_EXPLICIT) ) }.build() ) } println("Playing from Media Id") super.onPlayFromMediaId(mediaId, extras) } override fun onPlayFromSearch(query: String?, extras: Bundle?) { println("Playing from Search") super.onPlayFromSearch(query, extras) } override fun onPlayFromUri(uri: Uri?, extras: Bundle?) { println("Playing from Uri") super.onPlayFromUri(uri, extras) } override fun onCommand(command: String?, extras: Bundle?, cb: ResultReceiver?) { println("Command: $command") super.onCommand(command, extras, cb) } override fun onCustomAction(action: String?, extras: Bundle?) { println("Action: $action") super.onCustomAction(action, extras) } override fun onMediaButtonEvent(mediaButtonEvent: Intent?): Boolean { println("Media button event") return super.onMediaButtonEvent(mediaButtonEvent) } } inner class MyMediaControllerCallback : MediaControllerCompat.Callback() { override fun onMetadataChanged(metadata: MediaMetadataCompat?) { println("Controller media id: ${metadata?.description?.mediaId}") super.onMetadataChanged(metadata) } override fun onPlaybackStateChanged(state: PlaybackStateCompat?) { println("Controller playback state : ${state?.state}") super.onPlaybackStateChanged(state) } override fun onSessionReady() { println("Controller session is ready") super.onSessionReady() } } }
尝试过的无效操作
多次调用setActions设置单个操作,未解决问题:
setActions(PlaybackStateCompat.ACTION_PLAY) setActions(PlaybackStateCompat.ACTION_PAUSE) setActions(PlaybackStateCompat.ACTION_STOP) setActions(PlaybackStateCompat.ACTION_PLAY_FROM_MEDIA_ID) setActions(PlaybackStateCompat.ACTION_PLAY_FROM_SEARCH) setActions(PlaybackStateCompat.ACTION_SEEK_TO)
问题根源及解决方案
1. setActions使用错误(核心问题)
PlaybackStateCompat.Builder.setActions()方法接收的是位掩码,多次调用会覆盖之前的设置,最终只有最后一次调用的action生效。必须通过位或(or)将所有支持的操作合并:
修改addActionsForPlayback方法:
private fun addActionsForPlayback(playbackStateCompatBuilder: PlaybackStateCompat.Builder) { playbackStateCompatBuilder.setActions( PlaybackStateCompat.ACTION_PLAY or PlaybackStateCompat.ACTION_PAUSE or PlaybackStateCompat.ACTION_STOP or PlaybackStateCompat.ACTION_PLAY_FROM_MEDIA_ID or PlaybackStateCompat.ACTION_PLAY_FROM_SEARCH or PlaybackStateCompat.ACTION_SEEK_TO or PlaybackStateCompat.ACTION_PLAY_PAUSE ) }
同时,onPlay和onPlayFromMediaId中不要重复调用单个setActions,直接调用上述方法即可。
2. 未开启MediaSession的核心标志
注释掉的setFlags必须启用,否则MediaSession无法处理传输控制命令(暂停、停止等):
在MediaSession初始化时恢复并设置flags:
mediaSession = MediaSessionCompat(baseContext, "MusicService").apply { setCallback(MyMediaSessionCallback()) setPlaybackState( playbackStateBuilder.apply { setState(PlaybackStateCompat.STATE_NONE, 0L, 1f) addActionsForPlayback(this) }.build() ) setFlags( MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS or MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS ) isActive = true }
3. onPause/onStop未更新PlaybackState状态
暂停或停止后,必须更新PlaybackState的状态,否则UI无法感知状态变化,按钮不会更新:
修改MyMediaSessionCallback中的onPause和onStop:
override fun onPause() { println("Paused") // 更新播放状态为暂停,并设置支持的操作 mediaSession.setPlaybackState( playbackStateBuilder.apply { setState(PlaybackStateCompat.STATE_PAUSED, mediaController.playbackState.position, 1f) addActionsForPlayback(this) }.build() ) super.onPause() } override fun onStop() { println("Stopped") // 更新播放状态为停止,再释放session mediaSession.setPlaybackState( playbackStateBuilder.apply { setState(PlaybackStateCompat.STATE_STOPPED, 0L, 1f) addActionsForPlayback(this) }.build() ) mediaSession.release() super.onStop() }
4. 避免复用全局PlaybackStateBuilder(可选优化)
全局的playbackStateBuilder会保留之前的设置,可能导致状态混乱,建议每次创建新的Builder:
// 移除全局变量,每次创建新的Builder // private var playbackStateBuilder = PlaybackStateCompat.Builder() // 使用时创建新实例 mediaSession.setPlaybackState( PlaybackStateCompat.Builder().apply { setState(PlaybackStateCompat.STATE_PLAYING, seekedPos ?: 0L, 1f) addActionsForPlayback(this) }.build() )
内容的提问来源于stack exchange,提问作者ChaTho
相关产品推荐
相关产品推荐

