我想实现如下的场景,判断当前Android手机上是否正在播放音乐,如果是,通过某个特定的手势,
或者点击某个按键,将当前我正在听的音乐共享出去。
第一步,就是判断当前是否有音乐正在播放。
最开始我想得有点复杂,以为要深入framework或更下层去做手脚才行,找了一下资料,发现AudioManager对外暴露了接口。
[java] view plain copy print ?
-
- isMusicActive()
通过这个接口就可以判断当前系统是否有音乐在播放了。
还有一个问题,如果我想在音乐一开始就已经播放的时候,就知道这个事件,以便进行特殊的处理。
再进一步看一下 AudioManager 的源码,发现其中有如下方法:
[java] view plain copy print ?
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- public int requestAudioFocus(OnAudioFocusChangeListener l, int streamType, int durationHint)
从字面意思来看:请求音频焦点,再看这个函数的返回值:
[java] view plain copy print ?
-
-
-
- public static final int AUDIOFOCUS_REQUEST_FAILED = 0;
-
-
-
-
-
- public static final int AUDIOFOCUS_REQUEST_GRANTED = 1;
这个函数可能对我有帮助,进一步查一下Google官方的帮助:http://developer.android.com/training/managing-audio/audio-focus.html
Managing Audio Focus
With multiple apps potentially playing audio it's important to think about how they should interact. To avoid every music app playing at the same time, Android uses audio focus to moderate audio playback—only apps that hold the audio focus should play audio.
Before your app starts playing audio it should request—and receive—the audio focus. Likewise, it should know how to listen for a loss of audio focus and respond appropriately when that happens.
简单地翻译一下:
管理音频焦点
多个应用都在播放音频的可能性,所以考虑应用间如何交互非常重要。为避免每个音乐应用同时播放,Android使用音频焦点来协调音频的播放----只有获取到音频焦点的应用可以播放音频。
在你的应用开始播放音频之前,它应该先请求--并接收音频焦点。同样,它也应该知道当监听到失去音频焦点后如何合理地进行响应。
沿着这个路应该是对的,写了下面的测试代码进行验证。这个主要是Service的实现,你还需要实现一个Activity去启动Service、结束Service:
[java] view plain copy print ?
- package com.example.servicetest;
-
- import android.app.Service;
- import android.content.Context;
- import android.content.Intent;
- import android.media.AudioManager;
- import android.media.AudioManager.OnAudioFocusChangeListener;
- import android.media.MediaPlayer;
- import android.os.IBinder;
- import android.util.Log;
- import android.widget.Toast;
-
- public class MainService extends Service
- {
- private static final String TAG = "MainService";
-
- private MediaPlayer player;
-
- private AudioManager mAm;
-
- private MyOnAudioFocusChangeListener mListener;
-
-
- @Override
- public void onCreate()
- {
- Log.i(TAG, "onCreate");
-
- player = MediaPlayer.create(this, R.raw.test);
- player.setLooping(false);
-
- mAm = (AudioManager) getApplicationContext().getSystemService(Context.AUDIO_SERVICE);
- mListener = new MyOnAudioFocusChangeListener();
- }
-
-
- @Override
- public IBinder onBind(Intent intent)
- {
- return null;
- }
-
-
- @Override
- public void onStart(Intent intent, int startid)
- {
- Toast.makeText(this, "My Service Start", Toast.LENGTH_LONG).show();
- Log.i(TAG, "onStart");
-
-
- int result = mAm.requestAudioFocus(mListener,
- AudioManager.STREAM_MUSIC,
- AudioManager.AUDIOFOCUS_GAIN);
-
- if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED)
- {
- Log.i(TAG, "requestAudioFocus successfully.");
-
-
- player.start();
- }
- else
- {
- Log.e(TAG, "requestAudioFocus failed.");
- }
- }
-
-
- @Override
- public void onDestroy()
- {
- Toast.makeText(this, "My Service Stoped", Toast.LENGTH_LONG).show();
- Log.i(TAG, "onDestroy");
- player.stop();
-
- mAm.abandonAudioFocus(mListener);
- }
-
-
- private class MyOnAudioFocusChangeListener implements
- OnAudioFocusChangeListener
- {
- @Override
- public void onAudioFocusChange(int focusChange)
- {
- Log.i(TAG, "focusChange=" + focusChange);
- }
- }
- }
和 天天动听 结合起来测试,先打开天天动听播放音乐,再启动这个Service,发现天天动听自动暂停,再停止这个Service,天天动听又开始播放了。
反过来,我先启动这个Service,再播放、暂停天天动听,“Log.i(TAG, "focusChange=" + focusChange);” 这个确实有输出日志。
主流的音乐播放器,都遵循此规则的,所以通过使用Android的这个机制,我们就可以监控音乐的播放了。
还有一个问题,如何知道当前播放的音乐信息呢?两个思路:
1、通过在后台自动截取音频流的输出,通过服务器进行听歌识曲;
2、通过在SystemUI中拦截主流音乐播放器的通知;
第1个思路,从原理上是可行的,但是实现起来难度比较大,而且严重依赖网络;
还是先来分析一下第2个思路。
先找主流的Android音乐播放器来做个简单地测试,比如:天天动听、QQ音乐、酷狗音乐、酷我音乐、百度音乐等,在播放过程中,都会向状态栏中发一个Notification消息,其中已经包含歌曲信息。那我只需要做一个特殊的拦截并进行包名匹配,就可以获取正在播放的音乐了。
具体思路如下:
1、实现一个服务,这个服务在Android手机启动时,自动运行起来,通过 AudioManager.requestAudioFocus() 获取音频焦点,但什么事都不干,只为有其它音乐播放器开始运行时,得到一个通知消息;
2、修改SystemUI,当主流音乐播放器发Notification到状态栏时,从中获取到音乐信息;
3、步骤1的Listener就可以集成到SystemUI中,这样当音乐焦点被其它音乐播放器抢走后,再结合最近收到的Notification通知,这样更准确一些;
这样,基本上就可以实现我们想要的场景了。