当前位置: 首页 > article >正文

Android-Mediasession-播放状态监控

Android 监控 MediaSession 播放状态并打印包名的 Java 实现下面是一个完整的 Java 示例展示如何系统级监控所有应用的 MediaSession 播放状态并打印当前正在播放的应用包名。 一、核心原理通过 MediaSessionManager 获取所有活跃的 MediaSession监听其状态变化并提取对应的包名。️ 二、完整实现代码权限配置 (AndroidManifest.xml)?xml version1.0 encodingutf-8?!-- 必须权限监听媒体按键和会话 -- uses-permission android:nameandroid.permission.MEDIA_CONTENT_CONTROL / uses-permission android:nameandroid.permission.ACCESS_NOTIFICATION_POLICY / !-- ⚠️ 仅系统应用可获得 MEDIA_CONTENT_CONTROL 权限 -- !-- 普通应用需用户手动授权或使用无障碍服务 -- application service android:name.GlobalMediaMonitorService android:enabledtrue android:exportedtrue android:permissionandroid.permission.BIND_NOTIFICATION_LISTENER_SERVICE intent-filter action android:nameandroid.service.notification.NotificationListenerService / /intent-filter /service /application全局媒体监控服务 (GlobalMediaMonitorService.java)import android.app.Service;import android.content.Context;import android.media.MediaController;import android.media.MediaSessionManager;import android.media.session.MediaController.Callback;import android.media.session.MediaSession;import android.media.session.PlaybackState;import android.os.Binder;import android.os.IBinder;import android.util.Log;import java.util.ArrayList;import java.util.HashSet;import java.util.List;import java.util.Set;public class GlobalMediaMonitorService extends Service {private static final String TAG “MediaMonitor”;private MediaSessionManager mMediaSessionManager; private SetMediaController mActiveControllers new HashSet(); private final ListMediaController.Callback mControllerCallbacks new ArrayList(); // 记录最后一次播放状态避免重复打印 private String mLastPlayingPackage ; private int mLastKnownState PlaybackState.STATE_NONE; Override public void onCreate() { super.onCreate(); Log.d(TAG, 全局媒体监控服务启动); mMediaSessionManager (MediaSessionManager) getSystemService(Context.MEDIA_SESSION_SERVICE); startMonitoring(); } /** * 核心开始监控所有活跃会话 */ private void startMonitoring() { try { // 获取所有活跃的 MediaSession ListMediaController controllers mMediaSessionManager.getActiveSessions(null); for (MediaController controller : controllers) { if (controller ! null) { registerControllerCallback(controller); mActiveControllers.add(controller); // 初始检查 checkInitialState(controller); } } // 监听会话变化Android 5.0 mMediaSessionManager.addOnActiveSessionsChangedListener(this::onActiveSessionsChanged, null); } catch (SecurityException e) { Log.e(TAG, 权限不足无法访问 MediaSession: e.getMessage()); } } /** * 活跃会话列表变化回调 */ private void onActiveSessionsChanged(ListMediaController controllers) { Log.d(TAG, 活跃会话数量变化: controllers.size()); // 清理旧的 for (MediaController oldController : mActiveControllers) { if (!controllers.contains(oldController)) { unregisterControllerCallback(oldController); } } // 添加新的 for (MediaController newController : controllers) { if (!mActiveControllers.contains(newController)) { registerControllerCallback(newController); mActiveControllers.add(newController); } } } /** * 为单个控制器注册回调 */ private void registerControllerCallback(MediaController controller) { String pkg controller.getPackageName(); Log.d(TAG, 开始监控应用: pkg); MediaController.Callback callback new MediaController.Callback() { Override public void onPlaybackStateChanged(PlaybackState state) { super.onPlaybackStateChanged(state); handlePlaybackStateChange(pkg, state); } Override public void onSessionDestroyed() { super.onSessionDestroyed(); Log.d(TAG, 会话销毁: pkg); mActiveControllers.remove(controller); } }; controller.registerCallback(callback); mControllerCallbacks.add(callback); } /** * 取消回调注册 */ private void unregisterControllerCallback(MediaController controller) { controller.unregisterCallback(mControllerCallbacks.get(mActiveControllers.indexOf(controller))); } /** * 处理播放状态变化 */ private void handlePlaybackStateChange(String packageName, PlaybackState state) { if (state null) return; int stateCode state.getState(); String stateName getStateName(stateCode); // 过滤只有状态变化或包名变化时才打印 boolean shouldPrint !packageName.equals(mLastPlayingPackage) || stateCode ! mLastKnownState; if (shouldPrint) { Log.i(TAG, String.format( [%s] %s - %s (位置: %d), packageName, getAppNameFromPackage(packageName), stateName, state.getPosition() )); // 记录最后状态 mLastPlayingPackage packageName; mLastKnownState stateCode; } // 特别关注正在播放的应用 if (stateCode PlaybackState.STATE_PLAYING) { onAppStartedPlaying(packageName); } else if (stateCode PlaybackState.STATE_STOPPED || stateCode PlaybackState.STATE_NONE) { onAppStoppedPlaying(packageName); } } /** * 初始状态检查 */ private void checkInitialState(MediaController controller) { PlaybackState state controller.getPlaybackState(); if (state ! null state.getState() PlaybackState.STATE_PLAYING) { String pkg controller.getPackageName(); Log.d(TAG, 发现正在播放的应用: pkg); onAppStartedPlaying(pkg); } } /** * 应用开始播放的处理 */ private void onAppStartedPlaying(String packageName) { // 这里可以加入你的业务逻辑 Log.w(TAG, ⚠️ 检测到播放开始: packageName); // 示例限制某些应用播放 if (isRestrictedApp(packageName)) { Log.e(TAG, 受限应用正在播放: packageName); } } /** * 应用停止播放的处理 */ private void onAppStoppedPlaying(String packageName) { Log.w(TAG, ⏹️ 检测到播放停止: packageName); } /** * 获取状态名称 */ private String getStateName(int state) { switch (state) { case PlaybackState.STATE_NONE: return 无状态; case PlaybackState.STATE_STOPPED: return 已停止; case PlaybackState.STATE_PAUSED: return 已暂停; case PlaybackState.STATE_PLAYING: return 正在播放; case PlaybackState.STATE_FAST_FORWARDING: return 快进中; case PlaybackState.STATE_REWINDING: return 快退中; case PlaybackState.STATE_BUFFERING: return 缓冲中; case PlaybackState.STATE_ERROR: return 错误; case PlaybackState.STATE_CONNECTING: return 连接中; case PlaybackState.STATE_SKIPPING_TO_PREVIOUS: return 切上一首; case PlaybackState.STATE_SKIPPING_TO_NEXT: return 切下一首; default: return 未知状态( state ); } } /** * 根据包名获取应用名称 */ private String getAppNameFromPackage(String packageName) { try { return getPackageManager().getApplicationLabel( getPackageManager().getApplicationInfo(packageName, 0) ).toString(); } catch (Exception e) { return packageName; } } /** * 判断是否为受限应用示例 */ private boolean isRestrictedApp(String packageName) { // 示例黑名单 String[] restrictedApps {com.example.restricted1, com.example.restricted2}; for (String restricted : restrictedApps) { if (restricted.equals(packageName)) return true; } return false; } Override public IBinder onBind(Intent intent) { return new MonitorBinder(); } public class MonitorBinder extends Binder { public GlobalMediaMonitorService getService() { return GlobalMediaMonitorService.this; } } Override public void onDestroy() { super.onDestroy(); Log.d(TAG, 监控服务销毁); // 清理资源 for (MediaController controller : mActiveControllers) { unregisterControllerCallback(controller); } mActiveControllers.clear(); mControllerCallbacks.clear(); }}启动监控的 Activity (MonitorActivity.java)import android.content.ComponentName;import android.content.Intent;import android.os.Bundle;import androidx.appcompat.app.AppCompatActivity;public class MonitorActivity extends AppCompatActivity {private static final String TAG “MonitorActivity”;Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_monitor); // 启动监控服务 startMonitorService(); // 检查权限 checkPermissions(); } private void startMonitorService() { try { Intent serviceIntent new Intent(this, GlobalMediaMonitorService.class); startService(serviceIntent); Log.d(TAG, 媒体监控服务已启动); } catch (SecurityException e) { Log.e(TAG, 启动服务失败可能需要系统权限: e.getMessage()); } } private void checkPermissions() { // 检查必要的权限 if (!hasMediaControlPermission()) { Log.w(TAG, 缺少 MEDIA_CONTENT_CONTROL 权限监控可能受限); // 引导用户到设置页面 openSettingsForPermission(); } } private boolean hasMediaControlPermission() { // 检查权限的逻辑 return checkSelfPermission(android.permission.MEDIA_CONTENT_CONTROL) PackageManager.PERMISSION_GRANTED; } private void openSettingsForPermission() { // 打开系统设置页面 Intent intent new Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS); startActivity(intent); } Override protected void onDestroy() { super.onDestroy(); // 可选停止服务 // stopService(new Intent(this, GlobalMediaMonitorService.class)); }}简化的无障碍服务方案 (普通应用备用方案)如果无法获取 MEDIA_CONTENT_CONTROL 权限可以使用无障碍服务间接监控public class MediaAccessibilityService extends AccessibilityService {Overridepublic void onAccessibilityEvent(AccessibilityEvent event) {if (event.getEventType() AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {// 通过窗口变化推断播放状态String packageName event.getPackageName() ! null? event.getPackageName().toString() : “”;// 检查是否有媒体通知 if (hasMediaNotification(packageName)) { Log.d(AccessibilityMedia, 检测到媒体应用: packageName); } } } private boolean hasMediaNotification(String packageName) { // 这里实现通知扫描逻辑 return false; // 简化实现 } Override public void onInterrupt() {}} 三、运行效果示例当不同应用播放时Logcat 会输出I/MediaMonitor: [com.spotify.music] Spotify - 正在播放 (位置: 125000)I/MediaMonitor: [com.google.android.youtube] YouTube - 已暂停 (位置: 45000)I/MediaMonitor: [com.netflix.ninja] Netflix - 正在播放 (位置: 89000)⚠️ 四、重要注意事项事项 说明权限限制 MEDIA_CONTENT_CONTROL 是系统级权限普通应用难以获取系统应用 此方案最适合系统内置应用或拥有平台签名的应用备用方案 普通应用可考虑通过通知监听或无障碍服务间接实现Android 版本 API 21 (Android 5.0 Lollipop) 及以上支持性能考量 监控大量会话时注意资源消耗 五、调试命令查看当前活跃的 MediaSessionadb shell dumpsys media_session强制杀死某个播放器测试状态变化adb shell am force-stop com.spotify.music模拟按键事件adb shell input keyevent KEYCODE_MEDIA_PLAY这个方案提供了一个系统级的 MediaSession 监控框架能够准确捕捉各个应用的播放状态变化并输出包名信息。

相关文章:

Android-Mediasession-播放状态监控

Android 监控 MediaSession 播放状态并打印包名的 Java 实现 下面是一个完整的 Java 示例,展示如何系统级监控所有应用的 MediaSession 播放状态,并打印当前正在播放的应用包名。 📦 一、核心原理 通过 MediaSessionManager 获取所有活跃的 M…...

基于粒子群算法的配电网分布式电源与储能选址定容规划,以最低总成本为目标,综合考虑年运行成本、设...

配电网分布式电源和储能选址定容 以配电网总成本最低为目标函数,其中包括年运行成本,设备维护折损成本、环境成本;以系统潮流运行为约束条件,采用粒子群算法求解,实现光伏、风电、储能设备的规划。 这是一个使用粒子群…...

QMC音频解密终极指南:快速解锁QQ音乐加密文件,实现音乐自由播放

QMC音频解密终极指南:快速解锁QQ音乐加密文件,实现音乐自由播放 【免费下载链接】qmc-decoder Fastest & best convert qmc 2 mp3 | flac tools 项目地址: https://gitcode.com/gh_mirrors/qm/qmc-decoder 还在为QQ音乐下载的音频文件只能在特…...

从运维人员的角度解析《我是谁:没有绝对安全的系统》

1. 核心观点:系统最薄弱的环节永远是“人”电影反复强调的“没有绝对安全的系统”,在运维眼中正是日常工作的痛点:防火墙再强、IDS/IPS再精密、日志审计再完善,只要一个员工点开钓鱼邮件、一个管理员复用密码、一个客服被“假高管…...

告别单调!手机动态壁纸设置与自制全攻略,让你的屏幕“动”起来

你是否已经厌倦了手机上那张一成不变的静态壁纸?想不想每次点亮屏幕,都能看到一段生动的视频、一个流光溢彩的动画,让手机瞬间充满个性和活力?其实,设置和制作动态壁纸远比你想象的要简单!今天,…...

STM32智能循迹小车(1)多路TCRT5000传感器与PWM调速融合实践

1. 多路TCRT5000传感器布局策略 在搭建STM32智能循迹小车时,传感器的布局直接影响循迹效果。我建议采用前三角布局法:将三个TCRT5000模块呈等腰三角形排列,中间传感器位于车头正中,左右两侧传感器对称分布,间距建议控制…...

【重启日记】第三周复盘:从冷启动到运维榜第 2 名,这波真的稳了

三周时间,从 0 开始重启 CSDN,坚持每日两篇,内容三条线并行,终于跑出稳定结果。 一、三周数据总览 第一周:阅读 2176,原力 50,排名 2.9w第二周:阅读 7131,原力 155&…...

双目深度相机:模拟人眼视觉,解锁三维感知新可能

在三维感知技术快速迭代的现在,深度相机作为机器获取空间信息的重要“眼睛”,已渗透到工业自动化、机器人导航、AR/VR、智能家居等多个领域。其中,双目深度相机凭借其被动式工作、成本可控、强光适应性强等独特优势,成为中远距离三…...

Lychee Rerank MM可部署:支持私有云/本地IDC的多模态语义匹配解决方案

Lychee Rerank MM可部署:支持私有云/本地IDC的多模态语义匹配解决方案 你是否遇到过这样的场景?在一个电商平台里,用户上传了一张红色连衣裙的图片,想找类似款式,但搜索引擎却返回了一堆毫不相关的商品。或者&#xf…...

Java SSM Vue 基于Web的家教服务平台

这里写目录标题系统实现截图技术栈介绍Spring Boot与Vue结合使用的优势Spring Boot的优点Vue的优点Spring Boot 框架结构解析Vue介绍系统执行流程Java语言介绍系统测试目的可行性分析核心代码详细视频演示源码获取所需该项目可以在最下面查看联系方式,为防止迷路可以…...

2025年最新Docker镜像加速器实测与配置指南

1. 为什么你需要Docker镜像加速器? 如果你经常使用Docker拉取镜像,肯定遇到过下载速度慢到让人抓狂的情况。这就像在高峰时段挤地铁,明明目的地就在那里,但就是动弹不得。我最近在部署一个微服务项目时,光是拉取基础镜…...

2026八大数据采集与数据服务工具深度测评:分级分类全解析

在数据驱动的时代,选择合适的数据工具如同挑选趁手的工具。为了帮你快速定位,本文将八款主流产品按 “数据采集工具(自助型)”和“数据服务商(成品/标注型)” 两大类别,再依据用户能力、团队规模…...

5分钟部署Qwen3-Embedding-4B:支持100+语言的文本嵌入

5分钟部署Qwen3-Embedding-4B:支持100语言的文本嵌入 1. Qwen3-Embedding-4B简介 Qwen3-Embedding-4B是通义千问团队最新推出的文本嵌入模型,专为语义搜索、知识检索等任务优化。作为Qwen3系列的一员,它继承了基础模型强大的多语言能力和长…...

MT-PXle【多路复用器】1线-单端信号类型,高负载能力,高密度通道

...

2007-2020年税调与上市公司匹配结果

结合税收调查数据与上市公司数据的变量,得到2007~2020 年税调与上市公司匹配结果。 匹配方法: 首先根据企业名称进行匹配。为便于两个数据集的连接,我在税调数据中生成了 sdid 变量以在匹配过程中识别每个观测值。使用精确匹配&…...

DAMO-YOLO 5分钟零基础部署:小白也能玩转赛博朋克视觉探测

DAMO-YOLO 5分钟零基础部署:小白也能玩转赛博朋克视觉探测 1. 引言:未来已来,视觉探测触手可及 想象一下,你刚看完一部赛博朋克电影,被那些炫酷的视觉特效和智能识别系统深深吸引。现在,我要告诉你一个好…...

STM32 芯片报错 Invalid ROM Table 解决方法

在使用 Keil 下载程序时,弹出错误窗口: Error: Flash Download failed - Target DLL has been cancelled 点击 Debug Settings 查看,发现能识别到芯片,但状态显示 Invalid ROM Table,无法正常下载程序。 无论怎么重插…...

如何避免过拟合?深度学习训练中Epoch数量的选择技巧

深度学习训练中Epoch选择的艺术:从理论到实践的防过拟合指南 当你在深夜盯着屏幕上不断跳动的损失函数曲线时,是否曾为"到底该训练多少个epoch"这个问题辗转反侧?这个问题看似简单,实则暗藏玄机。就像烘焙蛋糕时火候的掌…...

为什么Nuxt本地开发会收到Chrome DevTools的请求?深入解析与两种解决方案

为什么Nuxt本地开发会收到Chrome DevTools的请求?深入解析与两种解决方案 在Nuxt.js本地开发过程中,不少开发者都遇到过这样一个现象:控制台频繁出现/.well-known/appspecific/com.chrome.devtools.json路径的404警告。这看似无害的提示背后&…...

PWM与脉冲信号的区别及电机驱动方式

PWM信号和脉冲信号是电子控制和电机驱动领域两个核心概念,它们既有联系又有本质区别。理解其差异,并掌握驱动电机的不同信号方式,是进行嵌入式系统设计的基础。 一、PWM信号与脉冲信号的核心区别 尽管PWM(Pulse Width Modulatio…...

手把手教你用MFRC522射频模块实现门禁系统(附完整代码)

手把手教你用MFRC522射频模块实现门禁系统(附完整代码) 在智能家居和安防领域,射频识别(RFID)技术因其非接触式交互特性而广受欢迎。MFRC522作为一款高性价比的13.56MHz射频模块,配合Arduino或STM32等开发板…...

《工程伦理2.0》核心要义与实践路径解析

1. 工程伦理2.0的演进背景与核心特征 十年前我刚入行做智能硬件开发时,工程师们讨论的还只是"电路板布线要不要留安全间距"这类基础伦理问题。如今团队开会,话题已经变成"人脸识别系统的种族偏见修正"和"用户行为数据的使用边界…...

AI 拟人化新规落地:情感陪伴有边界,行业告别野蛮生长

4 月 10 日,五部门联合发布《人工智能拟人化互动服务管理暂行办法》,7 月 15 日起正式施行。在我看来,这次新规不是简单约束,而是给当下越来越泛滥的 AI 陪伴、虚拟伴侣、情感交互划清了底线。过去几年 AI 拟人化一路狂奔&#xf…...

三相UVW的时间分配

七段式输出为例,在第一扇区中顺序为000 100 110 111,最先启动的是u相,最后启动的是w相,中间启动的是v相,v相必须等待100这个状态执行完才可以启动,所以v相要等待这段时间T1。在第二扇区中顺序为…...

每日一题day1(Leetcode 76最小覆盖子串)

1.题目解析 1.该题“讲人话”就是在一个字符串s中找到一个最短的能够涵盖子串所有字符的子串 2.解法 解法1&#xff08;暴力枚举hash表&#xff09; class Solution { public:string minWindow(string s, string t) {int m s.size();int n t.size();if (m < n)return &quo…...

从零部署RKNN模型:在Ubuntu22.04上搭建Python3.8虚拟环境与RKNN Toolkit2-1.5.2开发环境

1. 环境准备与Python 3.8安装 刚拿到一台全新的Ubuntu 22.04系统时&#xff0c;你会发现默认安装的Python版本可能是3.10。但RKNN Toolkit2-1.5.2对Python 3.8的支持最稳定&#xff0c;这是我踩过几次坑后得出的经验。先别急着创建虚拟环境&#xff0c;我们需要确保系统基础环境…...

魔兽争霸3现代难题终结者:WarcraftHelper一站式解决方案

魔兽争霸3现代难题终结者&#xff1a;WarcraftHelper一站式解决方案 【免费下载链接】WarcraftHelper Warcraft III Helper , support 1.20e, 1.24e, 1.26a, 1.27a, 1.27b 项目地址: https://gitcode.com/gh_mirrors/wa/WarcraftHelper 还在为魔兽争霸3在现代电脑上的各…...

全网通用版|2026 年财务培训机构优缺点分析与选择指南(附选型标准)

2026 年财务行业向复合型、数字化、合规化转型&#xff0c;会计培训市场 2025 年规模已突破320 亿元&#xff08;来源&#xff1a;《2025 中国财经教育行业发展报告》&#xff09;&#xff0c;预计 2030 年超900 亿元。财务从业者与企业选型培训机构&#xff0c;直接决定职业晋…...

DotNetPy:现代.NET 与 Python 互操作 实战指南撼

我为什么会发出这个疑问呢&#xff1f;是因为我研究Web开发中的一个问题时&#xff0c;HTTP请求体在 Filter&#xff08;过滤器&#xff09;处被读取了之后&#xff0c;在 Controller&#xff08;控制层&#xff09;就读不到值了&#xff0c;使用 RequestBody 的时候。 无论是字…...

014、搭建你的第一个神经网络(使用Keras/TensorFlow)

昨天有个刚转行的同事跑来找我,屏幕上是个经典的MNIST手写数字识别模型,训练集准确率冲到99%,验证集死活卡在87%。“明明没报错,为什么泛化这么差?”他指着损失曲线里那条越拉越大的缝隙问我。我扫了眼代码,发现他在model.fit()里没切验证集,自己手动拆的数据又忘了做归…...