Android监听第三方播放获取音乐信息及包名

文章目录

NotificationListenerService

NotificationListenerService 是 Android 系统提供的一个特殊服务,允许应用监听和获取其他应用发出的通知。通过这个服务,开发者可以读取通知的内容、发送时间、来源应用等信息,还可以对通知进行操作(如删除通知)。

核心功能。

  • 监听通知:获取系统中所有应用发出的通知。
  • 读取通知内容:包括标题、文本、图标等。
  • 通知操作:删除通知或执行通知中的操作。

在 Android 中使用 NotificationListenerService 监听媒体播放器状态是可行的,因为大多数媒体应用(如音乐播放器、视频播放器)会在发出的通知中包含当前播放状态(播放 / 暂停)、曲目信息(标题、艺术家、专辑)等内容。下面介绍如何实现这一功能。

注意:有一些应用NotificationListenerService是监听不到播放状态的,比如其不发通知,或者是在后台播放情况下没有通知,这种可以考虑不使用NotificationListenerService实现,而是用一个定时器去轮询MediaSessionManager的getActiveSessions()函数获取音乐信息,这里不贴代码了。

代码获取播放音乐

步骤 1:声明权限和服务

在AndroidManifest.xml中添加以下权限和服务:

xml 复制代码
<uses-permission android:name="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE" />

<service
    android:name=".MediaSessionListenerService"
    android:label="Notification Listener"
    android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
    <intent-filter>
        <action android:name="android.service.notification.NotificationListenerService" />
    </intent-filter>
</service>

步骤 2:实现NotificationListenerService

创建MediaSessionListenerService类继承NotificationListenerService:

java 复制代码
import android.app.Notification;
import android.media.session.MediaController;
import android.media.MediaMetadata;
import android.media.session.MediaSession;
import android.media.session.PlaybackState;
import android.os.Bundle;
import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
import android.util.Log;


public class MediaSessionListenerService extends NotificationListenerService {

    private static final String TAG = "MusicProgressListener";
    private MediaController mMediaController;
    private String currentPlayingPackage;

    public void onMusicStateChanged(PlaybackState state) {
        if (state != null) {
            long currentPosition = state.getPosition();
            Log.d(TAG, "[" + currentPlayingPackage + "] 当前进度: " + currentPosition + " ms");
        }
    }

    public void onMusicMetadataChanged(MediaMetadata metadata) {
        if (metadata != null) {
            long duration = metadata.getLong(MediaMetadata.METADATA_KEY_DURATION);
            String title = metadata.getString(MediaMetadata.METADATA_KEY_TITLE);
            String artist = metadata.getString(MediaMetadata.METADATA_KEY_ARTIST);

            Log.d(TAG, "[" + currentPlayingPackage + "] 歌曲: " + title + " - " + artist);
            Log.d(TAG, "[" + currentPlayingPackage + "] 总时长: " + duration + " ms");
        }
    }

    private final MediaController.Callback mControllerCallback = new MediaController.Callback() {

        @Override
        public void onPlaybackStateChanged(PlaybackState state) {
            onMusicStateChanged(state);
        }

        @Override
        public void onMetadataChanged(MediaMetadata metadata) {
            onMusicMetadataChanged(metadata);
        }
    };

    @Override
    public void onListenerConnected() {
        super.onListenerConnected();
        Log.d(TAG, "通知监听服务已连接");
        // 服务连接后,立即检查当前是否有活跃的媒体会话
        checkActiveMediaSessions();
    }

    @Override
    public void onNotificationPosted(StatusBarNotification sbn) {
        super.onNotificationPosted(sbn);
        // 当有新通知发出时,检查是否是媒体通知
        handleMediaNotification(sbn);
    }

    @Override
    public void onNotificationRemoved(StatusBarNotification sbn) {
        super.onNotificationRemoved(sbn);
        // 当媒体通知被移除时(如音乐停止),清理资源
        if (isMediaNotification(sbn)) {
            // 确保只清理当前正在播放的会话
            if (mMediaController != null && sbn.getPackageName().equals(currentPlayingPackage)) {
                mMediaController.unregisterCallback(mControllerCallback);
                mMediaController = null;
                currentPlayingPackage = null; // 重置包名
                Log.d(TAG, "媒体会话已断开: " + sbn.getPackageName());
            }
        }
    }

    private void checkActiveMediaSessions() {
        StatusBarNotification[] activeMediaSessions = getActiveNotifications();
        if (activeMediaSessions != null && activeMediaSessions.length > 0) {
            // 通常我们只关心第一个活跃的媒体会话
            handleMediaNotification(activeMediaSessions[0]);
        }
    }

    private void handleMediaNotification(StatusBarNotification sbn) {
        if (!isMediaNotification(sbn)) {
            return;
        }

        // 如果已经是当前正在处理的包,无需重复操作
        if (sbn.getPackageName().equals(currentPlayingPackage)) {
            return;
        }

        try {
            Bundle extras = sbn.getNotification().extras;
            MediaSession.Token token = extras.getParcelable(Notification.EXTRA_MEDIA_SESSION);

            if (token != null) {
                // 如果已经有一个控制器,先注销它
                if (mMediaController != null) {
                    mMediaController.unregisterCallback(mControllerCallback);
                }

                // 创建新的 MediaController
                mMediaController = new MediaController(this, token);
                mMediaController.registerCallback(mControllerCallback);

                // 从 StatusBarNotification 获取包名并存储
                currentPlayingPackage = sbn.getPackageName();
                Log.d(TAG, "已连接到媒体会话: " + currentPlayingPackage);

                // 立即获取一次当前状态,防止错过回调
                PlaybackState state = mMediaController.getPlaybackState();
                MediaMetadata metadata = mMediaController.getMetadata();
                if (state != null) {
                    onMusicStateChanged(state);
                }
                if (metadata != null) {
                    onMusicMetadataChanged(metadata);
                }
            }
        } catch (Exception e) {
            Log.e(TAG, "处理媒体通知时出错", e);
        }
    }

    private boolean isMediaNotification(StatusBarNotification sbn) {
        // 判断一个通知是否为媒体通知
        Notification notification = sbn.getNotification();
        return notification != null && notification.extras != null
                && notification.extras.containsKey(Notification.EXTRA_MEDIA_SESSION);
    }
}

步骤 3:请求用户启用通知监听权限

在Activity中检查并请求权限,系统不会自动授予此权限。你需要引导用户去设置页面手动开启:

MainActivity.java

java 复制代码
private void checkNotificationListenerPermission() {
    if (!isNotificationServiceEnabled()) {
        startActivity(new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS"));
    }
}

private boolean isNotificationServiceEnabled() {
    String packageName = getPackageName();
    String flat = Settings.Secure.getString(getContentResolver(), "enabled_notification_listeners");
    if (flat != null) {
        String[] names = flat.split(":");
        for (String name : names) {
            ComponentName cn = ComponentName.unflattenFromString(name);
            if (cn != null && cn.getPackageName().equals(packageName)) {
                return true;
            }
        }
    }
    return false;
}

然后MainActivity.java中调用

java 复制代码
checkNotificationListenerPermission();

用户必须点击进入设置,并手动开启你的 App 的"通知监听"开关。

启动服务

通常不需要显式启动 NotificationListenerService,只要用户开启了权限,系统就会自动绑定并运行服务。

但你可以调用以下代码来"尝试"触发绑定(实际是否运行仍由系统控制),

java 复制代码
// 可以尝试启动服务(非必需)
Intent intent = new Intent(this, MediaSessionListenerService.class);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    startForegroundService(intent);
} else {
    startService(intent);
}

运行正常的话onListenerConnected()会被自动调用

java 复制代码
@Override
public void onListenerConnected() {
    super.onListenerConnected();
    Log.d(TAG, "通知监听服务已连接"); // 这行会被执行
    checkActiveMediaSessions();
}

你可以在 Logcat 中搜索 "通知监听服务已连接" 来确认是否成功触发。

注意事项

  1. 用户授权:必须引导用户到设置中启用应用的通知监听权限。
  2. 媒体会话限制:并非所有应用都使用MediaSession,因此可能无法检测到所有播放情况。
  3. 多应用播放:可能存在多个应用同时播放的情况,需根据业务逻辑处理。

如果报以下错误,

java.lang.SecurityException: Missing permission to control media.

需要加上这个权限

获取播放信息如下图:

注意上面有总时长为-1的,这个是不对的需要特殊处理。

作者:帅得不敢出门

相关推荐
萤丰信息3 小时前
从超级大脑到智能毛细血管:四大技术重构智慧园区生态版图
java·人工智能·科技·重构·架构·智慧园区
qq_266348733 小时前
系统白名单接口添加自定义验证(模仿oauth2.0),防安全扫描不通过
java·安全
2503_928411563 小时前
10.9 鸿蒙创建和运行项目
android·华为·harmonyos
努力努力再努力wz3 小时前
【C++进阶系列】:万字详解特殊类以及设计模式
java·linux·运维·开发语言·数据结构·c++·设计模式
青云交3 小时前
Java 大视界 -- Java 大数据在智慧交通自动驾驶仿真与测试数据处理中的应用
java·大数据·自动驾驶·数据存储·算法优化·智慧交通·测试数据处理
reasonsummer3 小时前
【办公类-115-05】20250920职称资料上传04——PDF和PDF合并PDF、图片和PDF合并PDF(十三五PDF+十四五图片)
java·python·pdf
Mcband3 小时前
Apache Commons IO:文件流处理利器,让Java IO操作更简单
java·开发语言·apache
缺点内向3 小时前
Java:将 Word 文档转换为密码保护的 PDF 文件
java·pdf·word
骑士雄师4 小时前
Java 泛型中级面试题及答案
java·开发语言·面试