我想實現如下的場景,判斷當前Android手機上是否正在播放音樂,如果是,通過某個特定的手勢,
或者點擊某個按鍵,將當前我正在聽的音樂共享出去。
第一步,就是判斷當前是否有音樂正在播放。
最開始我想得有點復雜,以為要深入framework或更下層去做手腳才行,找了一下資料,發現AudioManager對外暴露了接口。
isMusicActive()
通過這個接口就可以判斷當前系統是否有音樂在播放了。
還有一個問題,如果我想在音樂一開始就已經播放的時候,就知道這個事件,以便進行特殊的處理。
再進一步看一下 AudioManager 的源碼,發現其中有如下方法:
public int requestAudioFocus(OnAudioFocusChangeListener l, int streamType, int durationHint)
從字面意思來看:請求音頻焦點,再看這個函數的返回值:
/** * A failed focus change request. */ public static final int AUDIOFOCUS_REQUEST_FAILED = 0; /** * A successful focus change request. */ public static final int AUDIOFOCUS_REQUEST_GRANTED = 1;
Managing Audio Focus
管理音頻焦點
多個應用都在播放音頻的可能性,所以考慮應用間如何交互非常重要。為避免每個音樂應用同時播放,Android使用音頻焦點來協調音頻的播放----只有獲取到音頻焦點的應用可以播放音頻。
在你的應用開始播放音頻之前,它應該先請求--並接收音頻焦點。同樣,它也應該知道當監聽到失去音頻焦點后如何合理地進行響應。
沿着這個路應該是對的,寫了下面的測試代碼進行驗證。這個主要是Service的實現,你還需要實現一個Activity去啟動Service、結束Service:
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); // 在res目錄下新建raw目錄,復制一個test.mp3文件到此目錄下。 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"); // Request audio focus for playback int result = mAm.requestAudioFocus(mListener, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN); if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { Log.i(TAG, "requestAudioFocus successfully."); // Start playback. 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通知,這樣更准確一些;
這樣,基本上就可以實現我們想要的場景了。