Android開發之MdiaPlayer詳解
MediaPlayer類可用於控制音頻/視頻文件或流的播放,我曾在《Android開發之基於Service的音樂播放器》一文中介紹過它的使用。下面讓我們看一下MediaPlayer類的詳細介紹。
一、類結構:
java.lang.Object |
|
? |
android.media.MediaPlayer |
二、構造方法和公有方法
構造方法:
Public Constructors |
|
MediaPlayer() 默認構造方法。 |
公有方法:
Public Methods |
|
static MediaPlayer |
create(Context context, Uri uri, SurfaceHolder holder) 指定從資源ID對應的資源文件中來裝載音樂文件,同時指定了SurfaceHolder對象並返回MediaPlyaer對象。 |
static MediaPlayer |
create(Context context, int resid) 指定從資源ID對應的資源文件中來裝載音樂文件,並返回新創建的MediaPlyaer對象。 |
static MediaPlayer |
create(Context context, Uri uri) 從指定Uri裝在音頻文件,並返回新創建的MediaPlayer對象。 |
int |
getCurrentPosition() 獲取當前播放的位置。 |
int |
getDuration() 獲取音頻的時長。 |
int |
getVideoHeight() 獲取視頻的高度。 |
int |
getVideoWidth() 獲取視頻的寬度。 |
boolean |
isLooping() 判斷MediaPlayer是否正在循環播放。 |
boolean |
isPlaying() 判斷MediaPlayer是否正在播放。 |
void |
pause() 暫停播放。 |
void |
prepare() 准備播放(裝載音頻),調用此方法會使MediaPlayer進入Prepared狀態。 |
void |
prepareAsync() 准備播放異步音頻。 |
void |
release() 釋放媒體資源。 |
void |
reset() 重置MediaPlayer進入未初始化狀態。 |
void |
seekTo(int msec) 尋找指定的時間位置。 |
void |
setAudioStreamType(int streamtype) 設置音頻流的類型。 |
void |
setDataSource(String path) 指定裝載path路徑所代表的文件。 |
void |
setDataSource(Context context, Uri uri, Map<String, String headers) 指定裝載uri所代表的文件。 |
void |
setDataSource(Context context, Uri uri) 指定裝載uri所代表的文件。 |
void |
setDataSource(FileDescriptor fd, long offset, long length) 指定裝載fd所代表的文件中從offset開始長度為length的文件內容。 |
void |
setDataSource(FileDescriptor fd) 指定裝載fd所代表的文件。 |
void |
setDisplay(SurfaceHolder sh) 設置顯示方式。 |
void |
setLooping(boolean looping) 設置是否循環播放。 |
void |
setNextMediaPlayer(MediaPlayer next) 設置當前流媒體播放完畢,下一個播放的MediaPlayer。 |
void |
setOnBufferingUpdateListener(MediaPlayer.OnBufferingUpdateListener listener) 注冊一個回調函數,在網絡視頻流緩沖變化時調用。 |
void |
setOnCompletionListener(MediaPlayer.OnCompletionListener listener) 為Media Player的播放完成事件綁定事件監聽器。 |
void |
setOnErrorListener(MediaPlayer.OnErrorListener listener) 為MediaPlayer的播放錯誤事件綁定事件監聽器。 |
void |
setOnPreparedListener(MediaPlayer.OnPreparedListener listener) 當MediaPlayer調用prepare()方法時觸發該監聽器。 |
void |
setOnSeekCompleteListener(MediaPlayer.OnSeekCompleteListener listener) 當MediaPlayer調用seek()方法時觸發該監聽器。 |
void |
setOnVideoSizeChangedListener(MediaPlayer.OnVideoSizeChangedListener listener) 注冊一個用於監聽視頻大小改變的監聽器。 |
void |
setScreenOnWhilePlaying(boolean screenOn) 置是否使用SurfaceHolder來顯示。 |
void |
setSurface(Surface surface) 設置Surface。 |
void |
setVideoScalingMode(int mode) 設置視頻縮放的模式。 |
void |
setVolume(float leftVolume, float rightVolume) 設置播放器的音量。 |
void |
setWakeMode(Context context, int mode) 為MediaPlayer設置低級電源管理行為。. |
void |
start() 開始或恢復播放。 |
void |
stop() 停止播放。 |
三、常用方法分析:
1.使用進度條:
進度條SeekBar可以用來顯示播放進度,用戶也可以利用SeekBar的滑塊來控制音樂的播放。
SeekBar需要使用的一些方法:
setProgress(int value):設置滑塊的位置方法為。
setMax(int value):設置進度條的最大長度。
setOnSeekBarChangeListener(OnSeekBarChangeListener l):設置SeekBar的進度改變事件。
MusicPlayer需要使用的一些方法:
getDuration():獲得音樂長度為。
getCurrentPosition():獲得現在播放的位置。
seekTo(int msec):調用seekTo()方法可以調整播放的位置。
seekTo(int)方法是異步執行的,所以它可以馬上返回,但是實際的定位播放操作可能需要一段時間才能完成,尤其是播放流形式的音頻/視頻。當實際的定位播放操作完成之后,內部的播放引擎會調用客戶端程序員提供的OnSeekComplete.onSeekComplete()回調方法。可以通過setOnSeekCompleteListener(OnSeekCompleteListener)方法注冊。
seekTo(int)方法也可以在其它狀態下調用,比如Prepared,Paused和PlaybackCompleted狀態。此外,目前的播放位置,實際可以調用getCurrentPosition()方法得到,它可以幫助如音樂播放器的應用程序不斷更新播放進度。
創建並使用進度條的步驟:
第一步:創建一個進度條
//進度條 static SeekBarskbMusic; skbMusic=(SeekBar)findViewById(R.id.skbMusic); |
第二步:為進度條的改變事件注冊並添加監聽器
skbMusic.setOnSeekBarChangeListener(sChangeListener); /** * SeekBar進度改變事件 */ OnSeekBarChangeListenersChangeListener=new OnSeekBarChangeListener() { @Override public void onStopTrackingTouch(SeekBar seekBar) { // TODO Auto-generated method stub //當拖動停止后,控制mediaPlayer播放指定位置的音樂 MusicService.mediaPlayer.seekTo(seekBar.getProgress()); MusicService.isChanging=false; } @Override public void onStartTrackingTouch(SeekBar seekBar) { // TODO Auto-generated method stub MusicService.isChanging=true; } @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { // TODO Auto-generated method stub } }; |
第三步:設置進度條的最大長度:
//getDuration()方法要在prepare()方法之后,否則會出現Attempt to call getDuration without a valid mediaplayer異常 MusicBox.skbMusic.setMax(mediaPlayer.getDuration());//設置SeekBar的長度 |
第四步:更新進度條
//----------定時器記錄播放進度---------// mTimer =new Timer(); mTimerTask =new TimerTask() { @Override publicvoid run() { isTimerRunning=true; if(isChanging==true)//當用戶正在拖動進度進度條時不處理進度條的的進度 return; MusicBox.skbMusic.setProgress(mediaPlayer.getCurrentPosition()); } }; //每隔10毫秒檢測一下播放進度 mTimer.schedule(mTimerTask, 0, 10); |
2.裝載音頻文件:
為了讓MediaPlayer來裝載指定音頻文件,MediaPlayer提供了如下簡單的靜態方法。
static MediaPlayer create(Context context, Uri uri):從指定Uri來裝載音頻文件,並返回新創建的MediaPlayer對象。
static MediaPlayer create(Context context, int resid):從 resid資源 ID對應的資源文件中裝載音頻文件,並返回新創建的MediaPlayer對象。
提示:上而這兩個方法用起來非常方便,但這兩個方法每次都會返回新創建的MediaPlayer對象,如來程序需要使用MediaPlayer循環播放多個音頻文件,使用MediaPlayer的靜態create方法就不太合適了,此時可通過MediaPlayer的setDataSource()方法來裝載指定的音頻文件。MediaPlayer提供了如下方法來指定裝載相應的音頻文件。
setDataSource(String path):指定裝載path路徑所代表的文件。
setDataSource(FileDescriptor fd, long offset,long length):指定裝載fd所代表的文件中從offset開始長度為length的文件內容。
setDataSource(FileDescriptor fd):指定裝載fd所代表的文件。
setDataSource(Context context, Uri uri):指定裝載uri所代表的文件。
提示:執行上面所示的setDataSource()方法之后,MediaPlayer並未真正去裝載那些音頻文件,還需要調用MediaPlayer的prepare()方法去准備音頻,所謂“准備”,就是讓MediaPlayer真正去裝載音頻文件。
使用已有的MediaPlayer對象裝載“一首”歌曲的代碼模板為:
mPlayer.reset(); //裝戰下一竹歌曲 mPlayer.setDataSource(M/mnt/sdcard/next.mp3); //准備聲音 mPlayer.prepare(); "播放 mPlayer.start(); } catch (IOException e) e.printStackTrace(); } |
3.與MediaPlayer有關的事件監聽器:
MediaPlayer提供了一些綁定事件監聽器的方法,用於監聽MediaPlayer播放過程中所發生的特定事件,綁定事件監聽器的方法如下。
setOnCompletionListener(MediaPlayer.OnCompletionListener listener):為 Media Player的播放完成事件綁定事件監聽器。
setOnErrorListener(MediaPlayer.OnErrorListener listener):為MediaPlayer的播放錯誤事件綁定事件監聽器。
setOnPreparedListener(MediaPlayer.OnPreparedListener listener):當 MediaPlayer調用prepare()方法時觸發該監聽器。
setOnSeekCompleteListener(MediaPlayer.OnSeekCompleteListener listener):當MediaPlayer調用seek()方法時觸發該監聽器。
因此可以在創建一個MediaPlayer對象之后,通過為該MediaPlayer綁定監聽器來監聽相應的事件,例如如下代碼:
//為MediaPlayer的播放完成事件綁定事件監聽器 mPlayer.setOnErrorListener(new OnErrorListener() { @Override publicboolean onError(MediaPlayer mp,int what,int extra) { // TODO Auto-generated method stub //針對錯誤進行相應的處理 // ... ... } }); //為MediaPlayer的播放完成講件綁定市件監聽器 mPlayer.setOnCompletionListener(new OnCompletionListener() { @Override publicvoid onCompletion(MediaPlayer mp) { // TODO Auto-generated method stub current++; prepareAndPlay(current); } }); |
四、MediaPlayer播放不同來源的音頻文件:
1.播放應用的資源文件
播放應用的資源文件需要兩步即:
1) 調用MediaPlayer的create(Context context,int resid)方法加指定資源文件。
2) 調用 MediaPlayer的 start()、pause()、stop()等方法控制播放即可。
例如如下代碼:
MediaPlayer mPlayer=new MediaPlayer(); mPlayer.create(this, R.raw.music); |
2. 播放應用的原始資源文件
播放應用的資源文件按如下步驟執行。
1) 調用 Context的 getAssets()方法獲取應用的 AssetManager。
2) 調用AssetManager對象的openFd(String name)方法打開指定的原生資源,該方法返回一個AssetFileDescriptor對象。
3) 調用 AssetFileDescriptor的 getFileDescriptor()、getStartOffset()和 getLength()方法來獲取音頻文件的FileDescriptor、開始位置、長度等。
4) 創建MediaPlayer對象(或利用已有的MediaPlayer對象),並調用MediaPlayer對象的setDataSource(FileDescriptor fd,long offset, long length)方法來裝載音頻資源。
5) 調用MediaPlayer對象的prepare()方法准備音頻。
6) 調用MediaPlayer的start()、pause()、stop()等方法控制播放即可。
例如如下代碼片段:
//獲取assets目錄下指定文件的AssetFileDescriptor對象 AssetFileDescriptor assetFileDescriptor=assetManager.openFd(musics[current]); mediaPlayer.reset();//初始化mediaPlayer對象 mediaPlayer.setDataSource(assetFileDescriptor.getFileDescriptor(), assetFileDescriptor.getStartOffset(), assetFileDescriptor.getLength()); //准備播放音樂 mediaPlayer.prepare(); //播放音樂 mediaPlayer.start(); |
3. 播放外部存儲器上音頻文件
播放外部存儲器上音頻文件按如下步驟執行。
1) 創建MediaPlayer對象(或利用已有的MediaPlayer對象),並調用MediaPlayer對象的setDateSource(String path)方法裝載指定的音頻文件。
2) 調用MediaPlayer對象的prepare()方法准備音頻。
3) 調用MediaPlayer的start()、pause()、stop()等方法控制播放即可。
例如如下代碼:
//加載SD卡上的指定資源音頻文件 mPlayer.setDataSource("/mnt/You Are The One.mp3"); mPlayer.prepare();//准備因音頻 mPlayer.start();//播放音頻 |
4.播放來自網絡的音頻文件
播放來自網絡的音頻文件?兩種方式:1.直接使用MediaPlayer的靜態create(Context context, Uri uri)方法;2.調用 MediaPlayer的setDataSource(Context context,Uri uri)裝載指定Uri對應的音頻文件。
以第二種方式播放來自網絡的音頻文件的步驟如下。
1. 根據網絡上的音頻文件所在的位置創建Uri對象。
2. 創建MediaPlayer對象(或利用己有的MediaPlayer對象),並調用MediaPlayer對象的 setDateSource(Context context,Uri uri)方法裝載Uri對應的音頻文件。
3. 調用MediaPlayer對象的prepare()方法准備音頻。
4. 調用MediaPlayer的start()、pause()、stop()等方法控制播放即可。
例如如下代碼片段:
Uri uri = Uri.parse("http://play.baidu.com/heihei.mp3"); MediaPlayer mPlayer = new MediaPlayer(); //使用MediaPlayer根據Uri來加載指定的聲音文件 mPlayer.setDataSource(this, uri); mPlayer.prepare();//准備因音頻 mPlayer.start();//播放音頻 |
MediaPlayer除了調用prepare()方法來准備聲音之外,還以調用prepareAsync()來准備聲音,prepareAsync()與普通prepare()方法的區別在於,prepareAsync()是異步的,它不會阻塞當前的UI線程。
五、解析MdiaPlayer的狀態圖
上面這張狀態轉換圖清晰的描述了MediaPlayer的各個狀態,也列舉了主要的方法的調用時序,每種方法只能在一些特定的狀態下使用,如果使用時MediaPlayer的狀態不正確則會引發IllegalStateException異常。
Idle狀態:當使用new()方法創建一個MediaPlayer對象或者調用了其reset()方法時,該MediaPlayer對象處於idle狀態。在處於Idle狀態時,調用getCurrentPosition(), getDuration(), getVideoHeight(),getVideoWidth(),setAudioStreamType(int), setLooping(boolean), setVolume(float, float), pause(), start(), stop(), seekTo(int), prepare()或者 prepareAsync()方法都會出現相應錯誤。這兩種方法的一個重要差別就是:當一個MediaPlayer對象剛被構建的時候,內部的播放引擎和對象的狀態都沒有改變,在這個時候調用以上的那些方法,框架將無法回調客戶端程序注冊的OnErrorListener.onError()方法,所以不會進入Error狀態;但若這個MediaPlayer對象調用了reset()方法之后,再調用以上的那些方法,內部的播放引擎就會回調客戶端程序注冊的OnErrorListener.onError()方法,這時MediaPlayer會進入Error狀態。
提示:使用new操作符創建的MediaPlayer對象處於Idle狀態,而那些通過重載的create()便利方法創建的MediaPlayer對象卻不是處於Idle狀態。事實上,如果成功調用了重載的create()方法,那么這些對象已經是Prepare狀態了。
End狀態:通過release()方法可以進入End狀態,只要MediaPlayer對象不再被使用,就應當盡快將其通過release()方法釋放掉,以釋放相關的軟硬件組件資源,這其中有些資源是只有一份的(相當於臨界資源)。如果MediaPlayer對象進入了End狀態,則不會再進入任何其他狀態了。
Initialized狀態:這個狀態比較簡單,MediaPlayer調用setDataSource()方法就進入Initialized狀態,表示此時要播放的文件已經設置好了。
提示:若當此MediaPlayer處於其它的狀態下,調用setDataSource()方法,會拋出IllegalStateException異常。
Prepared狀態:初始化完成之后還需要通過調用prepare()或prepareAsync()方法,這兩個方法一個是同步的一個是異步的,只有進入Prepared狀態,才表明MediaPlayer到目前為止都沒有錯誤,可以進行文件播放。
提示:在不合適的狀態下調用prepare()和prepareAsync()方法會拋出IllegalStateException異常。當MediaPlayer對象處於Prepared狀態的時候,可以調整音頻/視頻的屬性,如音量,播放時是否一直亮屏,循環播放等。
Preparing狀態:這個狀態比較好理解,主要是和prepareAsync()配合,如果異步准備完成,會觸發OnPreparedListener.onPrepared(),進而進入Prepared狀態。
Started狀態:顯然,MediaPlayer一旦准備好,就可以調用start()方法,這樣MediaPlayer就處於Started狀態,這表明MediaPlayer正在播放文件過程中。可以使用isPlaying()測試MediaPlayer是否處於了Started狀態。如果播放完畢,而又設置了循環播放,則MediaPlayer仍然會處於Started狀態,類似的,如果在該狀態下MediaPlayer調用了seekTo()或者start()方法均可以讓MediaPlayer停留在Started狀態。
Paused狀態:Started狀態下MediaPlayer調用pause()方法可以暫停MediaPlayer,從而進入Paused狀態,MediaPlayer暫停后再次調用start()則可以繼續MediaPlayer的播放,轉到Started狀態,暫停狀態時可以調用seekTo()方法,這是不會改變狀態的。
Stop狀態:Started或者Paused狀態下均可調用stop()停止MediaPlayer,而處於Stop狀態的MediaPlayer要想重新播放,需要通過prepareAsync()和prepare()回到先前的Prepared狀態重新開始才可以。
PlaybackCompleted狀態:文件正常播放完畢,而又沒有設置循環播放的話就進入該狀態,並會觸發OnCompletionListener的onCompletion()方法。此時可以調用start()方法重新從頭播放文件,也可以stop()停止MediaPlayer,或者也可以seekTo()來重新定位播放位置。
Error狀態:在一般情況下,由於種種原因一些播放控制操作可能會失敗,如不支持的音頻/視頻格式,缺少隔行掃描的音頻/視頻,分辨率太高,流超時等原因,等等會觸發會觸發OnErrorListener.onError()事件,此時MediaPlayer會進入Error狀態,及時捕捉並妥善處理這些錯誤是很重要的,可以幫助我們及時釋放相關的軟硬件資源,也可以改善用戶體驗。
開發者可以通過setOnErrorListener(android.media.MediaPlayer.OnErrorListener設置監聽器來監聽MediaPlayer是否進入Error狀態。如果MediaPlayer進入了Error狀態,可以通過調用reset()來恢復,使得MediaPlayer重新返回到Idle狀態。