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

Media3播放通知下一曲按钮不显示问题求助

解决Media3播放通知不显示下一曲按钮的问题

我基于Media3实现了播放功能,已在SimpleMediaNotificationManager中设置notificationManager.setUseNextAction(true),但播放通知仍未显示下一曲按钮,相关代码如下:

相关代码

SimpleMediaNotificationManager类

private const val NOTIFICATION_ID = 200
private const val NOTIFICATION_CHANNEL_ID = "notification channel id 1"

class SimpleMediaNotificationManager(val context: Context, val player: Player) {

    @RequiresApi(Build.VERSION_CODES.O)
    fun startNotificationService(
        mediaSessionService: MediaSessionService,
        mediaSession: MediaSession
    ) {
        buildNotification(mediaSession)
        startForegroundNotification(mediaSessionService)
    }

    private fun buildNotification(mediaSession: MediaSession) {
        val notificationManager =
            PlayerNotificationManager.Builder(context, 111, NOTIFICATION_CHANNEL_ID)
                .setChannelImportance(NotificationUtil.IMPORTANCE_LOW)
                .setSmallIconResourceId(R.drawable.music)
                .setChannelDescriptionResourceId(R.string.app_name)
                .setChannelNameResourceId(R.string.app_name)
                .setMediaDescriptionAdapter(audioDescriptor)
                .setNotificationListener(notificationListener)
                .build()


        notificationManager.setPriority(NotificationCompat.PRIORITY_MIN)
        notificationManager.setUseRewindAction(false)
        notificationManager.setUseFastForwardAction(false)
        notificationManager.setUsePreviousAction(true)
        notificationManager.setUsePlayPauseActions(true)
        notificationManager.setUseNextAction(true)
        notificationManager.setPlayer(player)
    }

    private val notificationListener = object : PlayerNotificationManager.NotificationListener {
        override fun onNotificationCancelled(notificationId: Int, dismissedByUser: Boolean) {
            super.onNotificationCancelled(notificationId, dismissedByUser)
            if (player?.isPlaying!!) {
                player?.stop()
                player?.release()
            }
        }

        override fun onNotificationPosted(
            notificationId: Int,
            notification: Notification,
            ongoing: Boolean
        ) {
            super.onNotificationPosted(notificationId, notification, ongoing)
        }
    }

    @RequiresApi(Build.VERSION_CODES.O)
    private fun startForegroundNotification(mediaSessionService: MediaSessionService) {
        val notification = Notification.Builder(context, NOTIFICATION_CHANNEL_ID)
            .setCategory(Notification.CATEGORY_SERVICE)
            .build()
        mediaSessionService.startForeground(NOTIFICATION_ID, notification)
    }

    private val audioDescriptor = object : PlayerNotificationManager.MediaDescriptionAdapter {
        override fun getCurrentContentTitle(player: Player): CharSequence {
            return player.currentMediaItem?.mediaMetadata?.albumTitle!!
        }

        override fun createCurrentContentIntent(player: Player): PendingIntent? {
            return pendingIntent()
        }

        override fun getCurrentContentText(player: Player): CharSequence? {
            return ""
        }

        override fun getCurrentLargeIcon(
            player: Player,
            callback: PlayerNotificationManager.BitmapCallback
        ): Bitmap? {
            val bitmapDrawable: BitmapDrawable =
                ContextCompat.getDrawable(
                    context,
                    R.drawable.cma_logo_render
                ) as BitmapDrawable
            return bitmapDrawable.bitmap
        }
    }

    private fun pendingIntent(): PendingIntent? {
        val intent = Intent(context, MusicPlayerActivity::class.java)
        return PendingIntent.getActivity(
            context,
            0,
            intent,
            PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
        )
    }
}

PlayService类(MediaSessionService实现)

class PlayService : MediaSessionService() {

    private var mediaSession: MediaSession? = null
    lateinit var notificationManager: SimpleMediaNotificationManager

    override fun onCreate() {
        super.onCreate()
        val player = ExoPlayer.Builder(this).build()
        notificationManager = SimpleMediaNotificationManager(this, player)
        mediaSession =
            MediaSession.Builder(this, player).setSessionActivity(pendingIntent()!!)
                .setId(Random(5).toString())
                .build()
    }

    @RequiresApi(Build.VERSION_CODES.O)
    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        notificationManager.startNotificationService(
            mediaSessionService = this,
            mediaSession = mediaSession!!
        )
        return super.onStartCommand(intent, flags, startId)
    }

    override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaSession? =
        mediaSession

    override fun onDestroy() {
        mediaSession?.run {
            player.release()
            release()
            mediaSession = null
        }
        super.onDestroy()
    }

    private fun pendingIntent(): PendingIntent? {
        val intent = Intent(applicationContext, MusicPlayerActivity::class.java)
        return PendingIntent.getActivity(
            applicationContext,
            0,
            intent,
            PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
        )
    }
}

Activity类相关代码

override fun onStart() {
    super.onStart()
    val sessionToken = SessionToken(this, ComponentName(this, PlayService::class.java))
    val controllerFuture = MediaController.Builder(this, sessionToken).buildAsync()
    controllerFuture.addListener(
        {
            player = controllerFuture.get()
            player?.seekTo(0, 0)
            val mediaMetaData = MediaMetadata.Builder()
                .setAlbumTitle(MusicPlayerData.audioTitle)
                .build()
            val mediaItem =
                MediaItem.Builder().setMediaMetadata(mediaMetaData)
                    .setUri(MusicPlayerData.audioUri)
                    .build()
            player?.setMediaItem(mediaItem)
            player?.prepare()
            player?.play()
            setPlayerControls()
        },
        MoreExecutors.directExecutor()
    )
}

问题原因与解决方案

核心原因

Media3的PlayerNotificationManager会根据播放器的hasNext()返回值决定是否显示下一曲按钮。如果播放队列中只有单个媒体项,hasNext()返回false,按钮会被自动隐藏;另外,MediaSession未明确启用下一曲命令、通知渠道优先级过低也可能导致按钮不显示。

解决方案1:添加多个媒体项到播放队列

确保播放器的播放队列中有至少两个媒体项,hasNext()会返回true,按钮会自动显示。修改Activity中的代码:

override fun onStart() {
    super.onStart()
    val sessionToken = SessionToken(this, ComponentName(this, PlayService::class.java))
    val controllerFuture = MediaController.Builder(this, sessionToken).buildAsync()
    controllerFuture.addListener(
        {
            player = controllerFuture.get()
            player?.seekTo(0, 0)
            
            // 创建多个媒体项组成播放列表
            val mediaItems = listOf(
                MediaItem.Builder()
                    .setMediaMetadata(MediaMetadata.Builder().setAlbumTitle(MusicPlayerData.audioTitle).build())
                    .setUri(MusicPlayerData.audioUri)
                    .build(),
                // 添加第二个媒体项
                MediaItem.Builder()
                    .setMediaMetadata(MediaMetadata.Builder().setAlbumTitle("第二首").build())
                    .setUri("第二首音频的URI")
                    .build()
            )
            
            player?.setMediaItems(mediaItems)
            player?.prepare()
            player?.play()
            setPlayerControls()
        },
        MoreExecutors.directExecutor()
    )
}

解决方案2:强制显示下一曲按钮(无播放列表时)

如果需要在只有单个媒体项时也显示下一曲按钮(比如自定义跳转逻辑),可以通过自定义ActionProvider强制启用按钮,并确保MediaSession允许下一曲命令:

修改SimpleMediaNotificationManager的buildNotification方法

private fun buildNotification(mediaSession: MediaSession) {
    val notificationManager =
        PlayerNotificationManager.Builder(context, 111, NOTIFICATION_CHANNEL_ID)
            .setChannelImportance(NotificationUtil.IMPORTANCE_DEFAULT) // 提高渠道优先级,避免通知被压缩
            .setSmallIconResourceId(R.drawable.music)
            .setChannelDescriptionResourceId(R.string.app_name)
            .setChannelNameResourceId(R.string.app_name)
            .setMediaDescriptionAdapter(audioDescriptor)
            .setNotificationListener(notificationListener)
            // 添加自定义下一曲ActionProvider
            .setCustomActionProviders(listOf(
                object : PlayerNotificationManager.CustomActionProvider {
                    override fun getCustomAction(player: Player): PlayerNotificationManager.CustomAction? {
                        return PlayerNotificationManager.CustomAction(
                            android.R.drawable.ic_media_next,
                            "下一曲",
                            NotificationCompat.Action.NEXT_MEDIA_ACTION
                        )
                    }
                }
            ))
            .build()

    notificationManager.setPriority(NotificationCompat.PRIORITY_DEFAULT)
    notificationManager.setUseRewindAction(false)
    notificationManager.setUseFastForwardAction(false)
    notificationManager.setUsePreviousAction(true)
    notificationManager.setUsePlayPauseActions(true)
    notificationManager.setUseNextAction(true)
    notificationManager.setUseNextActionInCompactView(true) // 确保在紧凑通知中也显示按钮
    notificationManager.setPlayer(player)
}

修改PlayService的MediaSession配置

override fun onCreate() {
    super.onCreate()
    val player = ExoPlayer.Builder(this).build()
    notificationManager = SimpleMediaNotificationManager(this, player)
    mediaSession =
        MediaSession.Builder(this, player)
            .setSessionActivity(pendingIntent()!!)
            .setId(Random(5).toString())
            // 明确启用下一曲命令
            .setAvailableCommands(
                SessionCommands.Builder()
                    .addAll(Player.DEFAULT_SESSION_COMMANDS)
                    .add(SessionCommand(SessionCommand.COMMAND_CODE_NEXT_MEDIA_ITEM, null))
                    .build()
            )
            .build()
}

解决方案3:检查播放器状态与通知渠道

  1. 验证player.hasNext()的返回值,可以添加播放器监听器打印日志:
player?.addListener(object : Player.Listener {
    override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) {
        Log.d("PlayerDebug", "hasNext: ${player?.hasNext()}")
    }
})
  1. 提高通知渠道的优先级,避免通知被系统压缩导致按钮隐藏;
  2. 确保使用最新版本的Media3库,旧版本可能存在UI显示bug。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 12:57:01