获取应用播放音频的详细信息

Get details of app playing audio

我正在开发一款需要在设备重启后恢复音频播放的应用。

我发现仅仅发送媒体播放按钮可能无法启动用户正在使用的同一个应用程序。 (我有 spotify 运行,但在重新启动后发送播放按钮会打开 Google 音乐)

有没有办法获取有关播放媒体的应用程序的详细信息(重启前)?

我需要类似 app name/activity name/package name 等的东西。 (所以我可以在发送播放按钮之前启动相同的应用程序)

更新 2:这使用 queryBroadcastReceivers API 可能受制于 package visibility filtering on Android 11 and higher. This means you will have to declare QUERY_ALL_PACKAGES permission and will have to justify its use if/when publishing to Play Store. Android T seems to deprecate this API in favor of a slightly different one,但当前调用应该仍然有效。

更新:以下方法要求您实施 AccessibilityService,Google Play 现在有严格的要求和单独的审核流程,应用必须证明使用它。如果您执行以下操作,您的应用可能会因滥用 API.

而被 Play Store 拒绝

虽然不完美,但我找到了一种识别当前正在播放音乐的应用程序的方法。

我的方法是利用音乐播放器必须post一个'ongoing'通知来防止被系统杀死,他们通常有一个Broadcast Receiver来处理媒体按钮。

从技术上讲,遵循 android 惯例的音乐应用程序必须有一个 intent-filter,以便 CATEGORY_MUSIC_APP (or the deprecated MediaStore.INTENT_ACTION_MUSIC_PLAYER) for atleast one of its activities, but I noticed that the current version of iHeartRadio app 决定不打扰。

这是我的方法

  • 已注册 Accessibility Service 以监控 android 通知
  • (音乐播放器必须post一个'ongoing'通知,以防止在CPU或内存压力下被android杀死。

无障碍服务必须至少有 android:accessibilityEventTypes="typeNotificationStateChanged" 由用户明确注册和启用。

服务必须按如下方式处理通知事件

@Override
public void onAccessibilityEvent(AccessibilityEvent accessibilityEvent) {
        if (accessibilityEvent.getEventType() ==
           AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED) {
            String packageName = accessibilityEvent.getPackageName().toString();

            AudioManager am = (AudioManager) getSystemService(AUDIO_SERVICE);
            boolean musicPlaying = am.isMusicActive();
            
            if (!musicPlaying) {
                Log.i("NOTIFICATION", "no one is playing music. (anymore...)");
               return; // clear saved package
            }
            
            PackageManager pm = getPackageManager();    
            Intent i = new Intent(Intent.ACTION_MEDIA_BUTTON);                
            i.setPackage(packageName);    

            List<ResolveInfo> a = pm.queryBroadcastReceivers(i, PackageManager.MATCH_ALL);

            if (a.size() > 0)
                Log.i("NOTIFICATION", "Music is played by: " + packageName);
                // save packageName to somewhere
            else
                Log.i("NOTIFICATION", "irrelevant notification from package: " + packageName);
        }
    }

稍后当设备重新启动时,启动默认 activity 并按播放按钮。

Intent i = new Intent(Intent.ACTION_VIEW);
i.addCategory(Intent.CATEGORY_DEFAULT); 
i.setPackage(savedPackage);
startActivity(i);

AudioManager am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);

 am.dispatchMediaKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PLAY));
 am.dispatchMediaKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_PLAY));

 // may be fire another intent for home/launcher. so user doesn't have to

Assumptions/Caveats

  • 音乐应用程序直接转到准备播放的 activity(如果您尚未在该设备上登录他们的服务,iHeartRadio 会要求登录)
  • 音乐应用保存上次播放状态。如果不是这种情况,它可能会从 track/playlist 开始播放,而不是从用户停止的地方开始播放。
  • 准确性可能会有所不同
  • Spotify post在用户离开 activity 或屏幕关闭时发送通知,并在播放状态更改时更新相同的通知(我们不会收到有关现有通知更改的通知。在 spotify activity 暂停之前,您不会收到播放开始的通知。)
  • Google 音乐 post 启动后立即通知,可能没有播放音乐(另一个应用可能正在播放音乐。可能会导致误报。)
  • iHeartRadio 导致连续多个通知更改。

您还可以从事件中获取 Notification,并列出 actions。如果任何 Actiontitle"Play",那么该应用程序不会播放音乐,只是 post 发送通知,就像上面解释的 Google 音乐一样。

(有启动 'Play' 的选项意味着它现在没有播放。)此外,字符串 'Play' 不是标准字符串,可能因应用 and/or 语言而异。假设大多数应用程序的字符串相同或相似以支持可访问性等。并确保检查 actions 是否为空。如果通知使用自定义布局(例如:Spotify),则为空。 (也可能意味着盲人用户无法使用 Spotify,因为辅助功能服务无法使用通知操作来控制它。)


更新: 如果您不想将它构建到您自己的应用程序中,您也可以使用像 Automate 这样的应用程序来处理它。

需要许可

<uses-permission android:name="android.permission.READ_LOGS" />
new Thread(new Runnable() {
    @Override
    public void run() {
        Process mLogcatProc = null;
        BufferedReader reader = null;
        try {
            //获取logcat日志信息
            mLogcatProc = Runtime.getRuntime().exec(new String[] { "logcat","MediaFocusControl:I *:S" });
            reader = new BufferedReader(new InputStreamReader(mLogcatProc.getInputStream()));

            String line;

            while ((line = reader.readLine()) != null) {

                if (line.contains("MediaFocusControl: requestAudioFocus()")) {
                    String[] strArr = line.split(" ");
                    for (String str:
                         strArr) {
                        if (str.contains("callingPack")){
                            System.out.println(str);
                            Test.packageName = str.split("=")[1];
                        }
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}).start();