Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android官方Media Playback中文教程

Android官方Media Playback中文教程

編輯:關於Android編程

此文參照 android developer API Guides https://developer.android.com/guide/topics/media/mediaplayer.html   Media Playback(媒體播放)   Android 多媒體框架能夠支持多種普通媒體類型,所以我們很容易的整合音頻,視頻,圖片到我們的應用中來。這些多媒體的資源可以是本地文件系統上的,也可以是網絡上的。在android中播放音頻視頻使用的都是MediaPlayer apis。   這篇文檔會向我們展示怎樣去寫一個媒體播放應用,能使得用戶和系統之間有一個比較好的表現和用戶的良好體驗。 注意: 我們只能通過標准的輸出設備來播放在音頻數據,當前,指的是使用手機設備的揚聲器或者藍牙耳機。在語音通過過程中,我們不能播放聲音文件。   The Basics(基礎) 下面的類在Android 框架中被用來播放音視頻的。
  MediaPlayer:播放聲音和視頻的主要api。 AudioManager:用於管理音頻資源以及音頻在設備上的輸出。   Manifest中的聲明 添加兩個權限: ①需要播放網絡視頻的需要添加INTERNET權限 ②屏幕熄屏的時候,我們需要添加WAKE_LOCK權限 如果您的播放器應用程序需要防止屏幕變暗或處理器睡覺,或使用MediaPlayer.setScreenOnWhilePlaying()orMediaPlayer.setWakeMode()方法,您必須請求許可。   Using MediaPlayer 媒體框架中的一個重要的組件是MediaPlayer類。MeidaPlayer類的對象通過簡單的設置能夠獲取,解碼和播放音視頻文件。能夠支持多種不同的媒體的資源: ①本地資源 ②外部的URIs,比如你可以從ContentResolver中去獲取。 ③外部的URLs(Streaming),理解成網絡上的   MediaPlayer支持的格式: https://developer.android.com/guide/appendix/media-formats.html   demo: http://git.oschina.net/hymKing/MediaDemo   播放本地res/raw/目錄下的音頻:
MediaPlayer mediaPlayer =MediaPlayer.create(context, R.raw.sound_file_1);
mediaPlayer.start();// no need to call prepare(); create() does that for you
播放URIs 中的音頻:
Uri myUri =....;// initialize Uri here
MediaPlayer mediaPlayer =newMediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.setDataSource(getApplicationContext(), myUri);
mediaPlayer.prepare();
mediaPlayer.start();
播放遠程網絡上的音頻:
String url ="http://........";// your URL here
MediaPlayer mediaPlayer =newMediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.setDataSource(url);
mediaPlayer.prepare();// might take long! (for buffering, etc)
mediaPlayer.start();
提醒: 播放網絡上的視頻,可能用出現或遇到這兩種異常IllegalArgumentException and IOException ,在當你使用setDataSource()方法的時候。因為有可能網絡上的視頻根本就不存在。   Asynchronous Preparation(異步准備) 因為視頻准備過程(包括緩沖區的准備)是一個相對耗時的工作。所以異步的方式去准備視頻是有必要的。所以android media框架中也提供了 prepareAsyc()用來做異步視頻的准備工作。設置setOnPreparedListener方法,可以在准備完成的時候,回調onPrepared方法。也就標示了視頻已經准備完成。可以去調用相應的生命周期的方法,比如start()方法去播放視頻。   Managing state (管理生命周期的狀態,這部分內容在基礎篇生命周期狀態圖中已經有說明) google 的api guide 中再次提醒我們在編寫media應用的時候,要在腦海中有media 生命周期的狀態圖。避免編寫一些非法狀態下的代碼。以避免一些沒有必要的異常的出現。   Releasing the MediaPlayer(釋放mediaPlayer組件) MediaPlayer能消耗Android系統比較寶貴的資源。因此我們要特別注意不要在沒有必要的情況下,持有MediaPlayer實例很長時間。下面是釋放資源的處理方法:
mediaPlayer.release();
mediaPlayer =null;
在音樂播放器這種場景下或一些類似的場景下,即使用戶已經離開了當前的activity,但是還需要播放多媒體。這時候,需要在Service中去控制MeidaPlayer。   Using a Service with MediaPlayer(使用帶有多媒體的服務) 當你希望用戶離開使用了MediaPlayer功能的application。或者用戶切換到了別的application,但是仍然要保證應用的MediaPlayer的播放等。這就是需要使用Android提供的四大組件之一Service。讓Service來控制Mediaplayer。用戶和系統對運行後台服務的應用如何同系統其它部分交互有一些期望,如果你的應用無法滿足這些期望,用戶可能會有糟糕的體驗。本節描述了你需要注意的主要事項以及如何解決的建議。   Running asychronously(異步運行) 首先,同Activity一樣,默認情況下Service中的所有工作也是在主線程中完成的。因此,服務需要快速的處理傳入的intent,在響應意圖是不能執行長時間操作。如果有大量的工作或者阻塞式的調用,就需要異步的完成。   舉個例子,當你在主線程中使用MediaPlayer,你應該使用prepareAsyc()而不使用prepare(),可以通過實現MediaPlayer.OnPreparedListener這個接口來接受准備完成的回調。這個時候,你可以啟用start方法去播放多媒體。   下面是一個在service中執行的例子:
publicclassMyServiceextendsServiceimplementsMediaPlayer.OnPreparedListener{
  privatestaticfinalString ACTION_PLAY ="com.example.action.PLAY";
  MediaPlayer mMediaPlayer =null;

  publicint onStartCommand(Intent intent,int flags,int startId){
    ...
    if(intent.getAction().equals(ACTION_PLAY)){
      mMediaPlayer =...// initialize it here
      mMediaPlayer.setOnPreparedListener(this);
      mMediaPlayer.prepareAsync();// prepare async to not block main thread
    }
  }

  /** Called when MediaPlayer is ready */
  publicvoid onPrepared(MediaPlayer player){
    player.start();
  }
}
Handling asychronous errors(處理異步的錯誤) 同步操作中,錯誤一般會錯誤一般會出現在異常或者錯誤代碼信息中。當你使用異步資源的時候,你需求確保你的應用程序在出現錯誤的時候,需要有錯誤提示。可以通過實現MediaPlayer.OnErrorListener這個接口來處理。 舉例:
publicclassMyServiceextendsServiceimplementsMediaPlayer.OnErrorListener{
  MediaPlayer mMediaPlayer;

  publicvoid initMediaPlayer(){
    // ...initialize the MediaPlayer here...

    mMediaPlayer.setOnErrorListener(this);
  }

  @Override
  publicboolean onError(MediaPlayer mp,int what,int extra){
    // ... react appropriately ...
    // The MediaPlayer has moved to the Error state, must be reset!
  }
}
尤其要記住的是:當錯誤發生的時候,MediaPlayer的進入到生命周期狀態圖中的Error State的狀態。在重新使用它之前,必須進行重新reset().   Using wake locks(使用喚醒鎖) 當我們設計一個應用在後台播放媒體的時候,我們的android設備在服務運行過程中,有可能會進入到休眠狀態。由於android系統為了在設備休眠狀態下節省電量。系統會試圖去關閉和釋放相關資源。比如cpu,wifi硬件等等。但是如果我們應用程序的服務在運行著多媒體的播放。你希望防止系統熄屏對媒體播放的影響。 為了確保您的服務在這些條件下,能夠繼續運行,你需要添加“wake locks”,喚醒鎖是一種信號系統,它發出的信號顯示:應用程序正在使用或者可用的功能,後手機閒置。   注意:你應該盡量避免過多的使用喚醒鎖,只有在必要的時候才去使用它。它會使設備的電池壽命大大的降低。   為了確保媒體的在播放的時候,cpu能夠持續的運行。需要在初始化MeidaPlayer的時候調用setWakeMode()方法。一旦這樣做了,媒體在播放的時候將持有一個指定鎖 ,當媒體暫停和停止的時候會釋放鎖。
mMediaPlayer =newMediaPlayer();
// ... other initialization here ...
mMediaPlayer.setWakeMode(getApplicationContext(),PowerManager.PARTIAL_WAKE_LOCK);
然而,在這個例子中喚醒鎖是指保證手機系統的cpu在喚醒狀態。當你在通過網絡獲取媒體和您正在使用Wifi時,你可能有希望有個wifiLock,可以手動獲取並釋放。當你通過遠程的Url准備mediaPlayer的時候,你應該創建並獲得Wifi鎖。代碼如下:
WifiLock wifiLock =((WifiManager) getSystemService(Context.WIFI_SERVICE))
  .createWifiLock(WifiManager.WIFI_MODE_FULL,"mylock");

wifiLock.acquire();
當你暫停或者停止媒體的時候,你不在需要網絡的時候,需要釋放這個鎖:
wifiLock.release();
Running as foreground service(作為前台服務運行) 服務通常用於執行後台任務,例如獲取電子郵件,同步數據,下載內容,或其他。在這些情況下,用戶不會意識到這個服務的執行,甚至可能不會注意到這些服務被打斷,後來重新啟動。毫無疑問,後台播放音樂是一個服務,用戶能意識到,任何中斷都會嚴重影響到用戶體驗。此外,用戶可能會希望在這個服務執行期間作用於它。這種情況,服務應該運行一個“前景服務”。前台服務在系統中持有一個更高水平的重要性,系統幾乎從未將服務扼殺,因為它對用戶有著直接的重要性。當應用在前台運行,該服務還必須提供一個狀態欄來通知用戶意識有服務正在運行同時允許他們打開一個活動,可以與服務進行交互。   為了把普通的服務轉換成一個前台服務,你必須創建一個狀態欄通知,然後通過service(指的就是notification,或者理解成Notification是android中實現前台服務的方式) 調用startForground()方法。例子如下
String songName;
// assign the song name to songName
PendingIntent pi =PendingIntent.getActivity(getApplicationContext(),0,
        newIntent(getApplicationContext(),MainActivity.class),
        PendingIntent.FLAG_UPDATE_CURRENT);
Notification notification =newNotification();
notification.tickerText = text;
notification.icon = R.drawable.play0;
notification.flags |=Notification.FLAG_ONGOING_EVENT;
notification.setLatestEventInfo(getApplicationContext(),"MusicPlayerSample",
        "Playing: "+ songName, pi);
startForeground(NOTIFICATION_ID, notification);
當你的服務運行在前台的時候,你配置的通知在設備的通知區域可見的。如果用戶選擇了這個通知,系統會調起配置的PendingIntent。像上面的demo,系統打開了一個MainActivity。   在需要停止前台服務的場景下,調用stopForeground(true).   更多的信息,可以參見service 和Status Bar Notification的官方文檔。   Handing audio focus(處理音頻焦點) 在任何給定的時間,android只能有一個activity在運行,但是android系統是一個多任務的環境。這對應用程序使用音頻造成了一個特別大的難度。由於只有一個音頻輸出,可能會有好幾個媒體爭奪使用它。在android2.2之前,android系統並沒有一個內置的機制,來解決這個問題。所以可能導致在一些場景下,給用戶一個比較壞的體驗。比如用戶正在聽音樂,另一個應用需要通知用戶非常重要的事情。由於播放的音樂用戶可能聽不到通知的聲音。在android系統2.2之後,android系統平台微應用提供了一個方法來轉讓設備音頻輸出的使用。這個機制被稱作音頻焦點。   當你的應用需要輸出音頻的時候比如音樂通知,你應該總是要請求音頻焦點。一旦有了焦點,你就能自由的使用聲音,當也應該總是監聽著焦點的改變。如果被通知失去了音頻的焦點,需要立即殺死音頻或者將當前音頻的聲音降低到一個較低的級別。(被稱為“閃避”-有一個標識flag證明哪一個音頻服務的占用),另外在重新獲得焦點的時候,才恢復播放。   音頻焦點能夠非常自然的協作,也就是應用希望(高度建議)遵守音頻焦點的指導線。但是這個規則並不是被系統強制的。如果一個應用想要播放大聲音的音樂,即時在失去了焦點以後,系統也不會阻止它。然而,用戶可能就得到一個會的體驗,繼而非常有可能會卸載掉應用。   請求音頻焦點,必須調用AudioManager的requestAudioFocus(),正如下面這個例子演示:
AudioManager audioManager =(AudioManager) getSystemService(Context.AUDIO_SERVICE);
int result = audioManager.requestAudioFocus(this,AudioManager.STREAM_MUSIC,
  AudioManager.AUDIOFOCUS_GAIN);

if(result !=AudioManager.AUDIOFOCUS_REQUEST_GRANTED){
  // could not get audio focus.
}
requestAudioRequest()的第一個參數 AudioManager.OnAudioFocusChangeListener。當音頻焦點改變的時候,會回調這個方法。因此你需要在你的service或者activities中去實現這個接口,舉個例子:
classMyServiceextendsService
        implementsAudioManager.OnAudioFocusChangeListener{
  // ....
  publicvoid onAudioFocusChange(int focusChange){
    // Do something based on focus change...
  }
}
onAudioFocusChange(int focusChange),focusChange 參數告訴我們音頻焦點是如何改變的。參數的值如下(這些值都是在AudioManager中定義的) AUDIOFOCUS_GAIN:獲得焦點 AUDIOFOCUS_LOSS:你可能長時間失去了這個音頻焦點,你一定停止所有的音頻的播放,因為你應該不希望在後台聚焦太長時間。這是一個比較的時機或者地方去讓你竟可能的釋放資源。比如你需要release() AUDIOFOCUS_LOSS_TRANSIENT:你暫時失去了音頻焦點,但是應該馬上就會回到焦點上。你必須停止所有的音頻播放, AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:你暫時失去了音頻焦點,但是你被允許繼續播放音頻(低音量)來替代完全的殺死音頻。   下面是一個實現:
publicvoid onAudioFocusChange(int focusChange){
  switch(focusChange){
    caseAudioManager.AUDIOFOCUS_GAIN:
      // resume playback
      if(mMediaPlayer ==null) initMediaPlayer();
      elseif(!mMediaPlayer.isPlaying()) mMediaPlayer.start();
      mMediaPlayer.setVolume(1.0f,1.0f);
      break;

    caseAudioManager.AUDIOFOCUS_LOSS:
      // Lost focus for an unbounded amount of time: stop playback and release media player
      if(mMediaPlayer.isPlaying()) mMediaPlayer.stop();
      mMediaPlayer.release();
      mMediaPlayer =null;
      break;

    caseAudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
      // Lost focus for a short time, but we have to stop
      // playback. We don't release the media player because playback
      // is likely to resume
      if(mMediaPlayer.isPlaying()) mMediaPlayer.pause();
      break;

    caseAudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
      // Lost focus for a short time, but it's ok to keep playing
      // at an attenuated level
      if(mMediaPlayer.isPlaying()) mMediaPlayer.setVolume(0.1f,0.1f);
      break;
  }
}
腦海中要很清晰音頻焦點的apis是在api8(android2.2系統)或以上(當然現在已經幾乎沒有4.0系統以下的機型了,所以目前開發基本不用考慮這個問題),所以關於後續的官方文檔的這部分內容就直接不用理會就可以了。   Performing cleanup(執行清理操作) 如前所說,MediaPlayer對象能夠消耗大量的系統資源。所以,只是在有必要的時候讓應用持有這個對象,使用完成之後要調用release()方法,進行清理。相比較於依賴於系統的垃圾回收機制,主動顯示調用MediaPlayer的清理方法顯得非常重要。因為系統可能花費一些時間在垃圾器回收利用MediaPlayer之前。只是因為android系統敏感的內存需求而不是媒體相關的資源的稀缺。(總之一句話,要在適當的時機去釋放內存),所以當我們使用service(activity)的時候,應該覆蓋onDestroy()來確保MediaPlayer被釋放。
publicclassMyServiceextendsService{
 MediaPlayer mMediaPlayer;
 // ...

 @Override
 publicvoid onDestroy(){
   if(mMediaPlayer !=null) mMediaPlayer.release();
 }
}
你也應該盡可能的尋找一些其它的釋放MediaPlayer資源的機會,而不僅僅在關閉的時候去釋放。比如,如果你並不希望在某一段時間去播放Media(比如失去音頻焦點之後)。你當然應該釋放它然後在需要的時候,再去重新創建。另一方面,如果僅僅是暫停一下,就沒有必要去頻繁的釋放和創建了,這樣可以避免再一次的去准備。   Handling the AUDIO_BECOMING_NOISY Intent(處理音頻變得吵鬧的意圖) 許多良好的AP會在發生一些導致音頻變得混雜的時候自動停止後台播放的,例如,當用戶在用耳機聽歌的時候若是發生突然失去連接的情況會產生音頻混雜。然而,這個行為不是自動發生的。如果你沒有實現這個功能,則不會產生你需要的效果。 你可以通過處理 ACTION_AUDIO_BECOMING_NOISY 的Intent來確保你的AP停止播放音樂在那種情況下,那個Intent需要在manifest文件中注冊一個receiver。
 
   
 
為這個意圖注冊一個廣播接受器,如下:
publicclassMusicIntentReceiverextends android.content.BroadcastReceiver{
 @Override
 publicvoid onReceive(Context ctx,Intent intent){
   if(intent.getAction().equals(
          android.media.AudioManager.ACTION_AUDIO_BECOMING_NOISY)){
     // signal your service to stop playback
     // (via an Intent, for instance)
   }
 }
}
接受到這個廣播以後,需要通知service停止MediaPlayer。 (當然上面提到的是比較良好的應用,會做如上的處理,很顯然大多應用忽視了這一塊)   Retrieving Media from a Content Resolver(通過內容解釋器獲取媒體資源) 在媒體應用中另外一個比較有用的功能是能夠獲取設備上已經存在的資源。你可以通過ContentResolver來查詢外部媒體。
ContentResolver contentResolver = getContentResolver();
Uri uri = android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
Cursor cursor = contentResolver.query(uri,null,null,null,null);
if(cursor ==null){
  // query failed, handle error.
}elseif(!cursor.moveToFirst()){
  // no media on the device
}else{
  int titleColumn = cursor.getColumnIndex(android.provider.MediaStore.Audio.Media.TITLE);
  int idColumn = cursor.getColumnIndex(android.provider.MediaStore.Audio.Media._ID);
  do{
   long thisId = cursor.getLong(idColumn);
   String thisTitle = cursor.getString(titleColumn);
   // ...process entry...
  }while(cursor.moveToNext());
}
和MediaPlayer使用,你可以寫如下代碼:
long id =/* retrieve it from somewhere */;
Uri contentUri =ContentUris.withAppendedId(
    android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, id);

mMediaPlayer =newMediaPlayer();
mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mMediaPlayer.setDataSource(getApplicationContext(), contentUri);

// ...prepare and start...
 
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved