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

Media3播放中动态更新通知标题及自定义通知相关问题

Media3 动态更新媒体通知标题相关问题解答

问题1:使用Media3默认通知时,如何实时更新标题?

针对API版本差异,分两种场景处理,核心是动态维护标题源并触发通知刷新:

实现步骤:

  1. 维护动态标题变量,根据播放进度实时更新:
    在Player.Listener中监听播放位置变化,计算生成包含进度信息的动态标题。
  2. API33及以上版本:
    系统通知从MediaSession元数据读取内容,需更新Session元数据,避免调用player.setMediaItem()打断播放。
  3. 触发通知刷新:
    调用playerNotificationManager.invalidate(),让MediaDescriptionAdapter重新获取最新标题。

代码修改示例:

class PlaybackService : MediaSessionService() {
    private var currentDynamicTitle = "My Title" // 新增动态标题变量
    
    override fun onCreate() {
        super.onCreate()
        val player = ExoPlayer.Builder(this)
            .setMediaSourceFactory(...)
            .build()
            .apply {
                addListener(object : Player.Listener {
                    override fun onPositionChanged(player: Player, position: Long) {
                        super.onPositionChanged(player, position)
                        // 格式化时间,生成动态标题
                        val progress = formatTime(position)
                        val total = formatTime(player.duration)
                        currentDynamicTitle = "播放中:$progress/$total"
                        
                        // API33+ 更新MediaSession元数据
                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
                            val metadata = Metadata.Builder()
                                .add(TextMetadataEntry(Metadata.TITLE, currentDynamicTitle))
                                .build()
                            mediaSession?.setMetadata(metadata)
                        }
                        
                        // 触发通知刷新
                        playerNotificationManager.invalidate()
                    }
                })
                playWhenReady = true
            }
        
        // ... 其他初始化代码
        
        // 修改MediaDescriptionAdapter的getCurrentContentTitle
        playerNotificationManager = PlayerNotificationManager.Builder(this, NOTIFICATION_ID, NOTIFICATION_CHANNEL)
            .setMediaDescriptionAdapter(object : PlayerNotificationManager.MediaDescriptionAdapter {
                override fun getCurrentContentTitle(player: Player): CharSequence {
                    return currentDynamicTitle // 返回动态标题
                }
                // ... 其他方法保持不变
            })
            .build()
    }
    
    // 新增时间格式化工具方法
    private fun formatTime(millis: Long): String {
        val minutes = (millis / 1000) / 60
        val seconds = (millis / 1000) % 60
        return String.format("%02d:%02d", minutes, seconds)
    }
    
    // ... 其他代码
}

问题2:若只能自定义通知,如何禁用默认MediaStyle通知(当前出现两个通知)?

双通知出现的原因是:API33+系统会通过MediaSession自动生成通知,同时PlayerNotificationManager也会创建一个通知。解决方案是禁用系统默认通知,完全自定义并手动管理通知:

实现步骤:

  1. 移除PlayerNotificationManager的初始化代码,不再依赖它生成默认通知。
  2. 在MediaSession.Builder中设置setNotificationProvider(null),禁用系统自动生成的通知。
  3. 手动创建自定义通知,通过startForeground()发布和更新。

代码修改示例:

class PlaybackService : MediaSessionService() {
    private var mediaSession: MediaSession? = null
    private lateinit var notificationManager: NotificationManagerCompat
    private var currentDynamicTitle = "My Title"
    
    override fun onCreate() {
        super.onCreate()
        val player = ExoPlayer.Builder(this)
            .setMediaSourceFactory(...)
            .build()
            .apply {
                addListener(object : Player.Listener {
                    override fun onPositionChanged(player: Player, position: Long) {
                        super.onPositionChanged(player, position)
                        // 更新动态标题
                        val progress = formatTime(position)
                        val total = formatTime(player.duration)
                        currentDynamicTitle = "播放中:$progress/$total"
                        // 更新通知
                        updateNotification()
                    }
                    
                    override fun onPlaybackStateChanged(playbackState: Int) {
                        super.onPlaybackStateChanged(playbackState)
                        // 播放状态变化时更新通知
                        updateNotification()
                    }
                })
                playWhenReady = true
            }
        
        // 禁用系统默认通知
        mediaSession = MediaSession.Builder(this, player)
            .setNotificationProvider(null)
            .build()
        
        notificationManager = NotificationManagerCompat.from(this)
        createChannel()
    }
    
    // 新增自定义通知更新方法
    private fun updateNotification() {
        // 构建自定义通知逻辑(见问题3示例)
        // ...
        val notification = NotificationCompat.Builder(this, NOTIFICATION_CHANNEL)
            // ... 自定义配置
            .build()
        startForeground(NOTIFICATION_ID, notification)
    }
    
    // ... 其他代码
}

问题3:如何创建自定义布局并在其中显示/更新数据?

自定义通知需使用RemoteViews加载布局,因为通知属于系统进程,无法直接操作控件。

实现步骤:

  1. 创建自定义通知布局XML文件;
  2. 使用RemoteViews加载布局,设置控件内容和点击事件;
  3. 构建Notification并通过startForeground()发布,数据变化时重新调用更新。

示例代码:

1. 自定义布局文件(res/layout/custom_media_notification.xml)

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="64dp"
    android:orientation="horizontal"
    android:padding="8dp"
    android:gravity="center_vertical"
    android:background="@android:color/black">

    <ImageView
        android:id="@+id/iv_play_pause"
        android:layout_width="48dp"
        android:layout_height="48dp"
        android:src="@drawable/ic_pause"/>

    <LinearLayout
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:orientation="vertical"
        android:marginStart="16dp">

        <TextView
            android:id="@+id/tv_title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="16sp"
            android:textColor="@android:color/white"/>

        <TextView
            android:id="@+id/tv_subtitle"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="12sp"
            android:textColor="@android:color/darker_gray"
            android:layout_marginTop="4dp"/>
    </LinearLayout>

    <ImageView
        android:id="@+id/iv_next"
        android:layout_width="48dp"
        android:layout_height="48dp"
        android:src="@drawable/ic_next"/>
</LinearLayout>

2. Service中更新自定义通知的方法

private fun updateNotification() {
    val remoteViews = RemoteViews(packageName, R.layout.custom_media_notification)
    
    // 设置动态标题
    remoteViews.setTextViewText(R.id.tv_title, currentDynamicTitle)
    remoteViews.setTextViewText(R.id.tv_subtitle, "示例艺术家")
    
    // 设置播放/暂停按钮点击事件
    val playPauseIntent = Intent(this, PlaybackService::class.java).apply {
        action = "ACTION_PLAY_PAUSE"
    }
    val playPausePendingIntent = PendingIntent.getService(
        this,
        0,
        playPauseIntent,
        PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
    )
    remoteViews.setOnClickPendingIntent(R.id.iv_play_pause, playPausePendingIntent)
    
    // 根据播放状态切换图标
    val player = mediaSession?.player
    if (player?.isPlaying == true) {
        remoteViews.setImageViewResource(R.id.iv_play_pause, R.drawable.ic_pause)
    } else {
        remoteViews.setImageViewResource(R.id.iv_play_pause, R.drawable.ic_play)
    }
    
    // 构建通知
    val notification = NotificationCompat.Builder(this, NOTIFICATION_CHANNEL)
        .setSmallIcon(R.drawable.ic_notification_small) // 必须设置小图标
        .setCustomContentView(remoteViews)
        .setStyle(NotificationCompat.DecoratedCustomViewStyle()) // 适配系统通知栏样式
        .setOngoing(true) // 常驻通知,防止手动取消
        .build()
    
    startForeground(NOTIFICATION_ID, notification)
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 15:04:52