Android监听MediaNotification/PiP按键事件的实现方法
你之前尝试的所有方案失效,核心原因是没有卡准媒体通知、PiP控件的事件分发入口,靠全局回调、播放器状态回调捞事件本来就无法区分来源,直接按下面的方式实现即可,不需要改动现有播放核心逻辑:
媒体通知(MediaNotification)按钮点击追踪
MediaControllerCompat.Callback、MediaSessionConnector.setMediaButtonEventHandler 这类方案只能监听到走MediaSession分发的硬件媒体按键事件(比如线控、蓝牙耳机按键),ExoPlayer自带的PlayerNotificationManager实现的通知栏按钮,是直接通过绑定的PendingIntent把事件转发给播放器,根本不会经过MediaSession的分发链路,所以你收不到回调。
直接做一层接收器代理即可:
- 自定义类继承
PlayerNotificationManager.MediaButtonReceiver,重写onReceive方法 - 在方法内先从Intent中解析出对应的按键事件,匹配播放/暂停/上一首/下一首/快进/快退操作,直接上报来源为媒体通知的对应埋点
- 最后调用
super.onReceive()把事件原样转发,不影响原有播放逻辑
核心代码示例:
class TrackedNotificationMediaReceiver : PlayerNotificationManager.MediaButtonReceiver() { override fun onReceive(context: Context?, intent: Intent?) { // 埋点逻辑先走 intent?.getParcelableExtra<KeyEvent>(Intent.EXTRA_KEY_EVENT)?.takeIf { it.action == KeyEvent.ACTION_UP }?.let { keyEvent -> val trackAction = when(keyEvent.keyCode) { KeyEvent.KEYCODE_MEDIA_PLAY -> "play" KeyEvent.KEYCODE_MEDIA_PAUSE -> "pause" KeyEvent.KEYCODE_MEDIA_NEXT -> "next_track" KeyEvent.KEYCODE_MEDIA_PREVIOUS -> "prev_track" KeyEvent.KEYCODE_MEDIA_FAST_FORWARD -> "seek_forward" KeyEvent.KEYCODE_MEDIA_REWIND -> "seek_backward" else -> return@let } // 上报埋点:触发来源=媒体通知栏,操作=trackAction trackEvent(source = "media_notification", action = trackAction) } // 原有播放逻辑正常执行 super.onReceive(context, intent) } }
最后把AndroidManifest里原来注册的MediaButtonReceiver替换成这个自定义类,初始化PlayerNotificationManager时传入对应接收器class即可。
PiP画中画按钮点击追踪
PiP控件的事件完全不走媒体按钮分发链路,所有按钮点击都是通过你初始化PiP参数时绑定的PendingIntent直接回调,只要在构建PendingIntent时加一层埋点代理就行:
- 不要直接把控制播放器的逻辑绑到PiP按钮的PendingIntent上
- 所有PiP按钮的PendingIntent都指向你自定义的广播接收器,接收器收到对应Action后,先上报来源为PiP的操作埋点,再调用播放器的对应控制方法
示例代码(构建暂停按钮):
// 定义PiP操作广播Action const val ACTION_PIP_PLAY = "tracked_pip_play" const val ACTION_PIP_PAUSE = "tracked_pip_pause" const val ACTION_PIP_NEXT = "tracked_pip_next" const val ACTION_PIP_PREV = "tracked_pip_prev" // 构建暂停按钮对应的PendingIntent val pausePi = PendingIntent.getBroadcast( context, 0, Intent(context, TrackedPipReceiver::class.java).apply { action = ACTION_PIP_PAUSE }, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE ) // 把pausePi作为操作Intent传入RemoteAction,绑定到PiP参数中 val pauseRemoteAction = RemoteAction( Icon.createWithResource(context, R.drawable.ic_pause), "暂停", "暂停播放", pausePi )
在TrackedPipReceiver的onReceive里匹配对应Action上报埋点后,直接调用player.play()/player.pause()/player.seekTo()等对应方法即可。
避坑说明
不要尝试在ExoPlayer的onEvents回调里做来源判断,这个回调是播放器状态变更后的全局通知,不管是应用内操作、通知点击、PiP点击、线控触发的状态变化都会走到这里,回调参数里没有任何字段可以标记事件来源,硬加临时状态位判断很容易出现埋点错报漏报,后续维护成本极高。
你之前自定义MediaButtonReceiver不生效,本质是没有替换掉PlayerNotificationManager默认注册的接收器,系统会优先把媒体按钮广播分发给默认接收器,你自定义的接收器根本拿不到事件。
内容的提问来源于stack exchange,提问作者Rastimir Orlic

