2013年9月4日 星期三

單元一: Android 的影音播放 --- MediaPlayer

隨著入門課程告一段落, 我們接著講解的各個單元會比較沒怎麼彼此相關性.
大家可以挑自己有興趣, 有需要的部分學習就好~~
我就以 MediaPlayer 當做這個基礎單元的起頭吧!

認識 MediaPlayer

要妥善處理我們裝置的影音播放, 有兩個元件要認識
一.  MediaPlayer
MediaPlayer 是播放影音主要的 API (Application Programming Interface, 應用程序接口), 我們透個這個 API 管理影音的播放或停止等動作.
二. AudioManager
AudioManager 是用來管理手機影音資源的, 包括聲音的輸出, 以及目前應該由哪個程式取得播放權..等等.

概述

MediaPlayer 的處理是 state_based 的, 也就是跟它目前所處的狀態有關.
先看一下底下這張圖表. 藍色就表示所處的狀態(state), 箭頭旁邊則是可以呼叫的方法(method).


當我們的新產生一個 MediaPlayer 時, 會先處於 idle 的狀態,
接著執行 setDataResource() 指定資料來源後, 就會跑到 initialize 的狀態,
執行 prepare() 時, 它(MediaPlayer)會開始去截取資料並解碼, 等解碼完畢就會進入 prepared.

這邊需要注意的是, 截取資料並解碼是一個比較耗時的過程, 應該要另外開啟副線程(Thread)來執行. 或者是使用 prepareAsync() 這個 Android 幫我們準備好的方法.
* prepareAsync() 本身即是開啓副線程來處理資料.

等到 MediaPlayer 進入 prepared 的狀態後, 即可執行 start() 開始播放,
接著播放中亦或播放結束會視情況不同進入 Paused, Stopped, PlaybackCompleted 的狀態.

最重要的是必須避免在不適當的狀態呼叫不適當的method. 這樣會造成出錯.
(例如還沒 setDataSource() 就 prepare().)

MediaPlayer 的範例 --- 以 RandomMediaPlayer 為例 (原始碼連結)

RandomMediaPlayer 是 Android Developer 上的一個範例.
它包含了幾個部分:
1. 截取手機上可以播放的音樂資料
2. 設置 service, 讓音樂可以在背景播放
3. 設置 Remote Control, 讓音樂播放時在 Screen Lock 的情況下也能操控.
4. 處理 AudioFocus 的問題
這個範例相當精彩, 我們就將上面各部分切割講解.

截取手機上可以播放的音樂資料

利用 MusicRetriever 來取得手機內可以播放的資源.

首先, 取得 SD 卡上面音樂資料的位置
MusicRetriever.class
 Uri uri = android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;  

接著利用這個位置取得可以 query 資料用的 cursor
MusicRetriever.class
 Cursor cur = mContentResolver.query(uri, null,  
         MediaStore.Audio.Media.IS_MUSIC + " = 1", null, null);  

這樣就可以取得手機上的音樂資料了,
範例裡還定義了 Item 這個物件, 並且足一取出 cursor 的資料塞到 List<Item> 裡,
方便在 service 呼叫時使用.

設置 service, 讓音樂可以在背景播放

在 service 的入門課程有提到過, 我們是利用 intent 來開啓 service 的.
這個範例裡也是一漾, 先設置了播放, 停止, 回播, 快轉等按鈕,
當我們點擊按鈕時, 主程式就會傳 intent 開啟 service.
MainActivity.class
 public void onClick(View target) {  
     // Send the correct intent to the MusicService, according to the button that was clicked  
     if (target == mPlayButton)  
       startService(new Intent(MusicService.ACTION_PLAY));  
     else if (target == mPauseButton)  
       startService(new Intent(MusicService.ACTION_PAUSE));  
     else if (target == mSkipButton)  
       startService(new Intent(MusicService.ACTION_SKIP));  
     else if (target == mRewindButton)  
       startService(new Intent(MusicService.ACTION_REWIND));  
     else if (target == mStopButton)  
       startService(new Intent(MusicService.ACTION_STOP));  
     else if (target == mEjectButton) {  
       showUrlDialog();  
     }  
   }  


MusicService 收到後會依據 Action 的不同執行不同的動作
MusicService.class
 if (action.equals(ACTION_TOGGLE_PLAYBACK)) processTogglePlaybackRequest();  
     else if (action.equals(ACTION_PLAY)) processPlayRequest();  
     else if (action.equals(ACTION_PAUSE)) processPauseRequest();  
     else if (action.equals(ACTION_SKIP)) processSkipRequest();  
     else if (action.equals(ACTION_STOP)) processStopRequest();  
     else if (action.equals(ACTION_REWIND)) processRewindRequest();  
     else if (action.equals(ACTION_URL)) processAddRequest(intent);  

如果是 action_play 即執行 processPlayRequest(),
這時便要透過 MusicRetriever 取得 Item, 並設置 MediaPlayer 的 Source, 並播放.
MusicService.class
 450 playingItem = mRetriever.getRandomItem();  
 462 mPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);  
 463 mPlayer.setDataSource(getApplicationContext(), playingItem.getURI());  
 517 mPlayer.prepareAsync();  
 400 mPlayer.start();  

關鍵的程式順序是這幾句, 它們分別被封裝在不同的 Method 裡,
我將它們出現的行數寫在左邊.

設置 Remote Control

這個音樂的 Remote Control 是Android 4.0 以上才有的功能.
這部分不實作對程式不會有什麼影響, 但是還滿有趣的, 我們也說明一下.



在 Screen Lock 的情況下出現的 Button 叫 MediaButton, 它是 Android 幫我們實作的.
我們要透過 MediaButtonHelper 註冊.
MusicService.class
 MediaButtonHelper.registerMediaButtonEventReceiverCompat(  
           mAudioManager, mMediaButtonReceiverComponent);  

接著, 取得 Media 的 RemoteControlComponent,
然後利用 RemoteControlHelper 註冊來讓手機知道.
MusicService.class
 Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON);  
 intent.setComponent(mMediaButtonReceiverComponent);  
 mRemoteControlClientCompat = new RemoteControlClientCompat(  
             PendingIntent.getBroadcast(this, 0 , intent , 0));  
 RemoteControlHelper.registerRemoteControlClient(mAudioManager,  
             mRemoteControlClientCompat);  

處理 AudioFocus 的問題

在處理影音的時候,要注意何時應該取得 AudioFocus, 何時放棄 AudioFocus,
只有取得 AudioFocus 才能讓喇叭發出聲音,
所以我們在播放前, 要先取得 AudioFocus,
如果播放中有電話打來, 則要放棄 AudioFocus.

舉我們在執行 porcessPlayRequest() 的時候為例, 需要先要求 AudioFocus,
MusicService.class
 void processPlayRequest() {  
     ...  
     tryToGetAudioFocus();  
     ...  
 }  
MusicService.class
 void tryToGetAudioFocus() {  
     if (mAudioFocus != AudioFocus.Focused && mAudioFocusHelper != null  
             && mAudioFocusHelper.requestFocus())  
       mAudioFocus = AudioFocus.Focused;  
 }  

真正幫助我們取得 Focus 的是 AudioFocusHelper 這個元件,
這邊的 AudioFocus 是我們自定義的 enum, 是依據 Android 的 Focus 形態而定的,
Duck  的意思是可以小聲播放,
MusicService.class
 enum AudioFocus {  
     NoFocusNoDuck,  // we don't have audio focus, and can't duck  
     NoFocusCanDuck,  // we don't have focus, but can play at a low volume ("ducking")  
     Focused      // we have full audio focus  
 }  

其他的 processStopRequest(), processSkipRequest(), ...等也都要注意 AudioFocus的問題,
我們就不細述了.

範例圖片(原始碼連結)







沒有留言:

張貼留言