1. 程式人生 > >Android如何判斷當前手機是否正在播放音樂,並獲取到正在播放的音樂的資訊

Android如何判斷當前手機是否正在播放音樂,並獲取到正在播放的音樂的資訊

我想實現如下的場景,判斷當前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;

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:

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通知,這樣更準確一些;

這樣,基本上就可以實現我們想要的場景了。