Android Kotlin中Media Buttons Receiver响应异常问题求助
安卓Kotlin音乐播放器媒体按键冲突问题:启用上下曲后暂停键无响应,反之亦然
开发的安卓Kotlin音乐播放器中,MusicService类负责显示通知并处理耳机媒体按键,但出现启用下一曲/上一曲按键后,播放暂停键无响应,反之亦然的问题。
处理媒体按键的相关代码
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { val playbackSpeed = if (MusicInterface.isPlaying) 1F else 0F mediaSession.setMetadata( MediaMetadataCompat.Builder() .putLong( MediaMetadataCompat.METADATA_KEY_DURATION, mediaPlayer!!.duration.toLong() ) .build() ) val playBackState = PlaybackStateCompat.Builder() .setState( PlaybackStateCompat.STATE_PLAYING, mediaPlayer!!.currentPosition.toLong(), playbackSpeed ) .setActions( PlaybackStateCompat.ACTION_SEEK_TO ) .build() mediaSession.setPlaybackState(playBackState) mediaSession.isActive = true mediaSession.setCallback(object : MediaSessionCompat.Callback() { override fun onMediaButtonEvent(mediaButtonEvent: Intent?): Boolean { val intentAction: String = mediaButtonEvent!!.action!! if (Intent.ACTION_MEDIA_BUTTON == intentAction) { val event: KeyEvent = mediaButtonEvent.getParcelableExtra(Intent.EXTRA_KEY_EVENT)!! val keyCode: Int = event.keyCode if (keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE) { if (MusicInterface.isPlaying) { //pause music MusicInterface.binding.interfacePlay.setImageResource(R.drawable.play) MusicInterface.isPlaying = false mediaPlayer!!.pause() showNotification(R.drawable.play_notification) NowPlaying.binding.fragmentButton.setImageResource(R.drawable.play_now) } else { //play music MusicInterface.binding.interfacePlay.setImageResource(R.drawable.pause) MusicInterface.isPlaying = true mediaPlayer!!.start() showNotification(R.drawable.pause_notification) NowPlaying.binding.fragmentButton.setImageResource(R.drawable.pause_now) } return true } if (keyCode == KeyEvent.KEYCODE_MEDIA_NEXT) { broadcastReceiver.prevNextMusic(increment = true, baseContext) return true } if (keyCode == KeyEvent.KEYCODE_MEDIA_PREVIOUS) { broadcastReceiver.prevNextMusic(increment = false, baseContext) return true } } return false } override fun onSeekTo(pos: Long) { super.onSeekTo(pos) mediaPlayer!!.seekTo(pos.toInt()) val playBackStateNew = PlaybackStateCompat.Builder() .setState( PlaybackStateCompat.STATE_PLAYING, mediaPlayer!!.currentPosition.toLong(), playbackSpeed ) .setActions(PlaybackStateCompat.ACTION_SEEK_TO) .build() mediaSession.setPlaybackState(playBackStateNew) } }) }
完整的MusicService类代码
package com.example.music import android.annotation.SuppressLint import android.app.PendingIntent import android.app.Service import android.content.Intent import android.graphics.BitmapFactory import android.media.AudioManager import android.media.MediaPlayer import android.os.* import android.support.v4.media.MediaMetadataCompat import android.support.v4.media.session.MediaSessionCompat import android.support.v4.media.session.PlaybackStateCompat import android.view.KeyEvent import androidx.core.app.NotificationCompat class MusicService : Service(), AudioManager.OnAudioFocusChangeListener { private var myBinder = MyBinder() var mediaPlayer: MediaPlayer? = null private lateinit var mediaSession: MediaSessionCompat private lateinit var runnable: Runnable lateinit var audioManager: AudioManager val broadcastReceiver: BroadcastReceiver = BroadcastReceiver() companion object { lateinit var playPendingIntent: PendingIntent } override fun onBind(intent: Intent?): IBinder { mediaSession = MediaSessionCompat(baseContext, "Music") return myBinder } inner class MyBinder : Binder() { fun currentService(): MusicService { return this@MusicService } } @SuppressLint("UnspecifiedImmutableFlag") fun showNotification(playPauseButton: Int) { val intent = Intent(baseContext, MusicInterface::class.java) intent.putExtra("index", MusicInterface.songPosition) intent.putExtra("class", "Now Playing Notification") val flag = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { PendingIntent.FLAG_IMMUTABLE } else { PendingIntent.FLAG_UPDATE_CURRENT } val contentIntent = PendingIntent.getActivity(this, 0, intent, flag) val prevIntent = Intent(baseContext, BroadcastReceiver::class.java).setAction(ApplicationClass.PREVIOUS) val prevPendingIntent = PendingIntent.getBroadcast( baseContext, 3, prevIntent, PendingIntent.FLAG_UPDATE_CURRENT ) val playIntent = Intent(baseContext, BroadcastReceiver::class.java).setAction(ApplicationClass.PLAY) playPendingIntent = PendingIntent.getBroadcast( baseContext, 3, playIntent, PendingIntent.FLAG_UPDATE_CURRENT ) val nextIntent = Intent(baseContext, BroadcastReceiver::class.java).setAction(ApplicationClass.NEXT) val nextPendingIntent = PendingIntent.getBroadcast( baseContext, 3, nextIntent, PendingIntent.FLAG_UPDATE_CURRENT ) val exitIntent = Intent(baseContext, BroadcastReceiver::class.java).setAction(ApplicationClass.EXIT) val exitPendingIntent = PendingIntent.getBroadcast( baseContext, 3, exitIntent, PendingIntent.FLAG_UPDATE_CURRENT ) val imageArt = getImageArt(MusicInterface.musicList[MusicInterface.songPosition].path) val image = if (imageArt != null) { BitmapFactory.decodeByteArray(imageArt, 0, imageArt.size) } else { BitmapFactory.decodeResource(resources, R.drawable.image_as_cover) } val notification = NotificationCompat.Builder(baseContext, ApplicationClass.CHANNEL_ID) .setContentTitle(MusicInterface.musicList[MusicInterface.songPosition].title) .setContentText(MusicInterface.musicList[MusicInterface.songPosition].artist) .setSubText(MusicInterface.musicList[MusicInterface.songPosition].album) .setSmallIcon(R.drawable.music_note) .setLargeIcon(image) .setStyle( androidx.media.app.NotificationCompat.MediaStyle() .setMediaSession(mediaSession.sessionToken) .setShowActionsInCompactView(0, 1, 2) ).setPriority(NotificationCompat.PRIORITY_HIGH) .setSilent(true) .setContentIntent(contentIntent) .setVisibility(NotificationCompat.VISIBILITY_PUBLIC).setOnlyAlertOnce(true) .addAction(R.drawable.navigate_before_notification, "Previous", prevPendingIntent) .addAction(playPauseButton, "PlayPause", playPendingIntent) .addAction(R.drawable.navigate_next_notification, "Next", nextPendingIntent) .addAction(R.drawable.close_notification, "Exit", exitPendingIntent).build() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { val playbackSpeed = if (MusicInterface.isPlaying) 1F else 0F mediaSession.setMetadata( MediaMetadataCompat.Builder() .putLong( MediaMetadataCompat.METADATA_KEY_DURATION, mediaPlayer!!.duration.toLong() ) .build() ) val playBackState = PlaybackStateCompat.Builder() .setState( PlaybackStateCompat.STATE_PLAYING, mediaPlayer!!.currentPosition.toLong(), playbackSpeed ) .setActions( PlaybackStateCompat.ACTION_SEEK_TO ) .build() mediaSession.setPlaybackState(playBackState) mediaSession.isActive = true mediaSession.setCallback(object : MediaSessionCompat.Callback() { override fun onMediaButtonEvent(mediaButtonEvent: Intent?): Boolean { val intentAction: String = mediaButtonEvent!!.action!! if (Intent.ACTION_MEDIA_BUTTON == intentAction) { val event: KeyEvent = mediaButtonEvent.getParcelableExtra(Intent.EXTRA_KEY_EVENT)!! val keyCode: Int = event.keyCode if (keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE) { if (MusicInterface.isPlaying) { //pause music MusicInterface.binding.interfacePlay.setImageResource(R.drawable.play) MusicInterface.isPlaying = false mediaPlayer!!.pause() showNotification(R.drawable.play_notification) NowPlaying.binding.fragmentButton.setImageResource(R.drawable.play_now) } else { //play music MusicInterface.binding.interfacePlay.setImageResource(R.drawable.pause) MusicInterface.isPlaying = true mediaPlayer!!.start() showNotification(R.drawable.pause_notification) NowPlaying.binding.fragmentButton.setImageResource(R.drawable.pause_now) } return true } if (keyCode == KeyEvent.KEYCODE_MEDIA_NEXT) { broadcastReceiver.prevNextMusic(increment = true, baseContext) return true } if (keyCode == KeyEvent.KEYCODE_MEDIA_PREVIOUS) { broadcastReceiver.prevNextMusic(increment = false, baseContext) return true } } return false } override fun onSeekTo(pos: Long) { super.onSeekTo(pos) mediaPlayer!!.seekTo(pos.toInt()) val playBackStateNew = PlaybackStateCompat.Builder() .setState( PlaybackStateCompat.STATE_PLAYING, mediaPlayer!!.currentPosition.toLong(), playbackSpeed ) .setActions(PlaybackStateCompat.ACTION_SEEK_TO) .build() mediaSession.setPlaybackState(playBackStateNew) } }) } startForeground(3, notification) } fun initSong() { try { if (mediaPlayer == null) mediaPlayer = MediaPlayer() MusicInterface.musicService!!.mediaPlayer!!.reset() MusicInterface.musicService!!.mediaPlayer!!.setDataSource(MusicInterface.musicList[MusicInterface.songPosition].path) MusicInterface.musicService!!.mediaPlayer!!.prepare() MusicInterface.musicService!!.showNotification(R.drawable.pause_notification) MusicInterface.binding.interfacePlay.setImageResource((R.drawable.pause)) MusicInterface.binding.interfaceSeekStart.text = formatDuration(mediaPlayer!!.currentPosition.toLong()) MusicInterface.binding.interfaceSeekEnd.text = formatDuration(mediaPlayer!!.duration.toLong()) MusicInterface.binding.seekbar.progress = 0 MusicInterface.binding.seekbar.max = mediaPlayer!!.duration } catch (e: Exception) { return } } fun seekBarHandler() { runnable = Runnable { MusicInterface.binding.interfaceSeekStart.text = formatDuration(mediaPlayer!!.currentPosition.toLong()) MusicInterface.binding.seekbar.progress = mediaPlayer!!.currentPosition Handler(Looper.getMainLooper()).postDelayed(runnable, 200) } Handler(Looper.getMainLooper()).postDelayed(runnable, 0) } override fun onAudioFocusChange(focusChange: Int) { if (focusChange <= 0) { //pause music MusicInterface.binding.interfacePlay.setImageResource(R.drawable.play) MusicInterface.isPlaying = false NowPlaying.binding.fragmentButton.setImageResource(R.drawable.play_now) mediaPlayer!!.pause() showNotification(R.drawable.play_notification) } else { //play music MusicInterface.binding.interfacePlay.setImageResource(R.drawable.pause) MusicInterface.isPlaying = true mediaPlayer!!.start() NowPlaying.binding.fragmentButton.setImageResource(R.drawable.pause_now) showNotification(R.drawable.pause_notification) } } }
问题根源分析
- PlaybackState未声明支持的媒体动作:当前代码仅添加了
ACTION_SEEK_TO,未包含ACTION_PLAY_PAUSE、ACTION_SKIP_TO_NEXT、ACTION_SKIP_TO_PREVIOUS,系统会根据声明的动作决定是否分发对应按键事件,导致未声明的按键无法触发。 - PlaybackState状态与实际播放状态不一致:无论当前是播放还是暂停,都强制设置为
STATE_PLAYING,系统无法正确识别播放状态,进而影响按键事件的处理逻辑。 - 重复创建MediaSession Callback:每次调用
showNotification都会重新设置Callback,覆盖之前的实例,导致按键处理逻辑混乱。 - MediaSession生命周期管理不当:MediaSession在
onBind中初始化,但播放状态配置却在showNotification中重复执行,不符合规范。
修复方案
1. 重构MediaSession初始化逻辑
将MediaSession的初始化、Callback设置移至onCreate,避免重复创建:
override fun onCreate() { super.onCreate() mediaSession = MediaSessionCompat(baseContext, "Music") mediaSession.isActive = true // 初始化统一的MediaSession Callback mediaSession.setCallback(object : MediaSessionCompat.Callback() { override fun onMediaButtonEvent(mediaButtonEvent: Intent?): Boolean { val intentAction = mediaButtonEvent?.action ?: return false if (Intent.ACTION_MEDIA_BUTTON == intentAction) { val event = mediaButtonEvent.getParcelableExtra<KeyEvent>(Intent.EXTRA_KEY_EVENT) ?: return false val keyCode = event.keyCode when (keyCode) { KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE -> { togglePlayPause() return true } KeyEvent.KEYCODE_MEDIA_NEXT -> { broadcastReceiver.prevNextMusic(increment = true, baseContext) return true } KeyEvent.KEYCODE_MEDIA_PREVIOUS -> { broadcastReceiver.prevNextMusic(increment = false, baseContext) return true } } } return false } override fun onSeekTo(pos: Long) { mediaPlayer?.seekTo(pos.toInt()) updatePlaybackState() } }) } // 统一播放暂停切换逻辑 private fun togglePlayPause() { if (MusicInterface.isPlaying) { MusicInterface.binding.interfacePlay.setImageResource(R.drawable.play) MusicInterface.isPlaying = false mediaPlayer?.pause() showNotification(R.drawable.play_notification) NowPlaying.binding.fragmentButton.setImageResource(R.drawable.play_now) } else { MusicInterface.binding.interfacePlay.setImageResource(R.drawable.pause) MusicInterface.isPlaying = true mediaPlayer?.start() showNotification(R.drawable.pause_notification) NowPlaying.binding.fragmentButton.setImageResource(R.drawable.pause_now) } updatePlaybackState() } // 统一更新PlaybackState private fun updatePlaybackState() { val playbackSpeed = if (MusicInterface.isPlaying) 1F else 0F val playbackState = PlaybackStateCompat.Builder() .setState( if (MusicInterface.isPlaying) PlaybackStateCompat.STATE_PLAYING else PlaybackStateCompat.STATE_PAUSED, mediaPlayer?.currentPosition?.toLong() ?: 0L, playbackSpeed ) .setActions( PlaybackStateCompat.ACTION_SEEK_TO or PlaybackStateCompat.ACTION_PLAY_PAUSE or PlaybackStateCompat.ACTION_SKIP_TO_NEXT or PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS ) .build() mediaSession.setPlaybackState(playbackState) }
2. 修改showNotification方法
移除重复的MediaSession配置,仅保留必要的元数据更新和通知显示:
@SuppressLint("UnspecifiedImmutableFlag") fun showNotification(playPauseButton: Int) { // 保留原有的PendingIntent和Notification构建逻辑... if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { mediaSession.setMetadata( MediaMetadataCompat.Builder() .putLong( MediaMetadataCompat.METADATA_KEY_DURATION, mediaPlayer?.duration?.toLong() ?: 0L ) .build() ) updatePlaybackState() } startForeground(3, notification) }
3. 调整onBind方法
移除onBind中的MediaSession初始化:
override fun onBind(intent: Intent?): IBinder { return myBinder }
4. 同步音频焦点变化时
相关产品推荐
相关产品推荐

