Media3播放中动态更新通知标题及自定义通知相关问题
Media3 动态更新媒体通知标题相关问题解答
问题1:使用Media3默认通知时,如何实时更新标题?
针对API版本差异,分两种场景处理,核心是动态维护标题源并触发通知刷新:
实现步骤:
- 维护动态标题变量,根据播放进度实时更新:
在Player.Listener中监听播放位置变化,计算生成包含进度信息的动态标题。 - API33及以上版本:
系统通知从MediaSession元数据读取内容,需更新Session元数据,避免调用player.setMediaItem()打断播放。 - 触发通知刷新:
调用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也会创建一个通知。解决方案是禁用系统默认通知,完全自定义并手动管理通知:
实现步骤:
- 移除
PlayerNotificationManager的初始化代码,不再依赖它生成默认通知。 - 在
MediaSession.Builder中设置setNotificationProvider(null),禁用系统自动生成的通知。 - 手动创建自定义通知,通过
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加载布局,因为通知属于系统进程,无法直接操作控件。
实现步骤:
- 创建自定义通知布局XML文件;
- 使用
RemoteViews加载布局,设置控件内容和点击事件; - 构建
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
相关产品推荐
相关产品推荐

