我想實現如下的場景,判斷當前Android手機上是否正在播放音樂,如果是,通過某個特定的手勢,
或者點擊某個按鍵,將當前我正在聽的音樂共享出去。
第一步,就是判斷當前是否有音樂正在播放。
最開始我想得有點複雜,以爲要深入framework或更下層去做手腳才行,找了一下資料,發現AudioManager對外暴露了接口。
- /** Checks whether any music is active. */
- isMusicActive()
通過這個接口就可以判斷當前系統是否有音樂在播放了。
還有一個問題,如果我想在音樂一開始就已經播放的時候,就知道這個事件,以便進行特殊的處理。
再進一步看一下 AudioManager 的源碼,發現其中有如下方法:
- /**
- * Request audio focus.
- * Send a request to obtain the audio focus
- * @param l the listener to be notified of audio focus changes
- * @param streamType the main audio stream type affected by the focus request
- * @param durationHint use {@link #AUDIOFOCUS_GAIN_TRANSIENT} to indicate this focus request
- * is temporary, and focus will be abandonned shortly. Examples of transient requests are
- * for the playback of driving directions, or notifications sounds.
- * Use {@link #AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK} to indicate also that it's ok for
- * the previous focus owner to keep playing if it ducks its audio output.
- * Use {@link #AUDIOFOCUS_GAIN} for a focus request of unknown duration such
- * as the playback of a song or a video.
- * @return {@link #AUDIOFOCUS_REQUEST_FAILED} or {@link #AUDIOFOCUS_REQUEST_GRANTED}
- */
- 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;
這個函數可能對我有幫助,進一步查一下Google官方的幫助:http://developer.android.com/training/managing-audio/audio-focus.html
Managing Audio Focus
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:
- 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); // 在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通知,這樣更準確一些;
這樣,基本上就可以實現我們想要的場景了。