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

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

    }
}

问题根源分析

  1. PlaybackState未声明支持的媒体动作:当前代码仅添加了ACTION_SEEK_TO,未包含ACTION_PLAY_PAUSE、ACTION_SKIP_TO_NEXT、ACTION_SKIP_TO_PREVIOUS,系统会根据声明的动作决定是否分发对应按键事件,导致未声明的按键无法触发。
  2. PlaybackState状态与实际播放状态不一致:无论当前是播放还是暂停,都强制设置为STATE_PLAYING,系统无法正确识别播放状态,进而影响按键事件的处理逻辑。
  3. 重复创建MediaSession Callback:每次调用showNotification都会重新设置Callback,覆盖之前的实例,导致按键处理逻辑混乱。
  4. 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. 同步音频焦点变化时

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 14:05:13