翻譯自MediaPlayer overviewhtml
Android多媒體框架支持播放各類常見媒體類型,所以您能夠輕鬆地將音頻、視頻和圖像集成到應用程序中。您可使用MediaPlayer api從存儲在應用程序資源(原始資源)中的媒體文件、文件系統中的獨立文件或經過網絡鏈接到達的數據流中播放音頻或視頻。java
本文向您展現瞭如何編寫與用戶和系統交互的媒體播放應用程序,以得到良好的性能和愉快的用戶體驗。react
注意:您只能將音頻數據回放到標準輸出設備。目前,這是移動設備揚聲器或藍牙耳機。您不能在通話期間播放通話音頻中的聲音文件。android
在Android框架中使用如下類播放聲音和視頻:git
MediaPlayergithub
這個類是播放聲音和視頻的主要API。
複製代碼
AudioManagerapi
該類管理設備上的音頻源和音頻輸出。
複製代碼
在使用MediaPlayer對應用程序進行開發以前,請確保清單中有適當的聲明,容許使用相關特性。數組
<uses-permission android:name="android.permission.INTERNET" />
複製代碼
<uses-permission android:name="android.permission.WAKE_LOCK" />
複製代碼
媒體框架最重要的組件之一是MediaPlayer類。這個類的對象可使用最少的設置獲取、解碼和播放音頻和視頻。它支持幾種不一樣的媒體來源,如:bash
下面是如何播放本地音頻資源(保存在您的應用程序的res/raw/目錄中):服務器
MediaPlayer mediaPlayer = MediaPlayer.create(context, R.raw.sound_file_1);
mediaPlayer.start(); // no need to call prepare(); create() does that for you
複製代碼
在本例中,「raw」資源是系統不嘗試以任何特定方式解析的文件。然而,這個資源的內容不該該是原始音頻。它應該是一個以支持的格式之一適當編碼和格式化的媒體文件。
下面是您如何從系統中本地可用的URI(例如,您經過內容解析器得到的URI)進行播放:
Uri myUri = ....; // initialize Uri here
MediaPlayer mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.setDataSource(getApplicationContext(), myUri);
mediaPlayer.prepare();
mediaPlayer.start();
複製代碼
經過HTTP流媒體從遠程URL播放以下:
String url = "http://........"; // your URL here
MediaPlayer mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.setDataSource(url);
mediaPlayer.prepare(); // might take long! (for buffering, etc)
mediaPlayer.start();
複製代碼
注意:若是要經過一個URL來傳輸流媒體在線文件,該文件必須可以逐步下載。
注意:在使用setDataSource()時,您必須捕獲或傳遞IllegalArgumentException和IOException,由於您引用的文件可能不存在。
使用MediaPlayer原則上很簡單。可是,須要記住的是,要將它正確地集成到典型的Android應用程序中,還須要作一些其餘的事情。例如,prepare()的調用可能須要很長時間執行,由於它可能涉及到獲取和解碼媒體數據。所以,就像任何須要很長時間才能執行的方法同樣,永遠不要從應用程序的UI線程調用它。這樣作會致使UI掛起,直到方法返回,這是一種很是糟糕的用戶體驗,並可能致使ANR(應用程序沒有響應)錯誤。即便您但願您的資源可以快速加載,也要記住,在UI中任何須要超過十分之一秒才能響應的內容都會引發明顯的暫停,並給用戶留下您的應用程序很慢的印象。
爲了不掛起UI線程,生成另外一個線程來準備MediaPlayer,並在完成時通知主線程。然而,雖然您能夠本身編寫線程邏輯,可是在使用MediaPlayer時,這種模式很是常見,所以框架提供了一種方便的方法來經過使用prepareAsync()方法來完成此任務。該方法開始在後臺準備媒體並當即返回。當媒體完成準備工做時,將調用 經過setOnPreparedListener()配置的MediaPlayer.OnPreparedListener()的onPrepared()方法。
您應該記住的MediaPlayer的另外一個方面是,它是基於狀態的。也就是說,MediaPlayer有一個內部狀態,在編寫代碼時必須始終注意到,由於只有當player處於特定狀態時,某些操做纔有效。若是在錯誤的狀態下執行操做,系統可能會拋出異常或引起其餘不但願看到的行爲。
MediaPlayer類中的文檔顯示了一個完整的狀態機,它闡明瞭哪些方法將MediaPlayer從一個狀態移動到另外一個狀態。例如,當您建立一個新的MediaPlayer時,它處於空閒狀態。這時,您應該經過調用setDataSource()來初始化它,使它處於初始化狀態。以後,您必須使用prepare()或prepareAsync()方法來準備它。當MediaPlayer完成準備工做時,它進入準備狀態,這意味着您能夠調用start()來讓它播放媒體。此時,您能夠經過調用start()、pause()和seekTo()等方法在start、pause()和PlaybackCompleted狀態之間切換。可是,當您調用stop()時,請注意,在從新準備MediaPlayer以前,您不能再次調用start()。
在編寫與MediaPlayer對象交互的代碼時,必定要記住狀態圖,由於從錯誤的狀態調用其方法是致使錯誤的常見緣由。
MediaPlayer可能會消耗有價值的系統資源。所以,您應該始終採起額外的預防措施,以確保您沒有過多地依賴MediaPlayer實例。處理完它以後,應該始終調用release(),以確保分配給它的任何系統資源都被正確釋放。例如,若是您使用的是一個媒體播放器和活動接收onStop()調用,您必須釋放媒體播放器,由於當你的活動不與用戶進行交互,繼續持有實例毫無心義(除非你是在後臺播放媒體,這是在下一節中討論)。當您的活動恢復或從新啓動時,固然,您須要建立一個新的MediaPlayer,並在恢復回放以前從新準備。
下面是您應該如何釋放並取消MediaPlayer:
mediaPlayer.release();
mediaPlayer = null;
複製代碼
做爲一個例子,考慮一下若是您在活動中止時忘記釋放MediaPlayer,而在活動從新開始時建立一個新的,可能會發生的問題。正如你可能知道的,當用戶更改屏幕的方向(或更改設備配置以另外一種方式),系統處理,經過從新啓動活動(默認狀況下),因此你可能會很快消耗掉全部系統資源的用戶旋轉設備之間來回的肖像和風景,由於在每個方向變化,您建立一個新的媒體播放器,你永遠不會釋放。(有關運行時從新啓動的更多信息,請參見處理運行時更改。)
您可能想知道,在用戶離開您的活動時若是您想繼續播放「背景媒體」,會發生什麼,這與內置音樂應用程序的表現很是相似。在這種狀況下,您須要的是一個由服務控制的MediaPlayer,下一節將對此進行討論.
若是您想要您的媒體在後臺播放,即便您的應用程序不是在屏幕上——也就是說,您想要它在用戶與其餘應用程序交互時繼續播放——那麼您必須啓動一個服務並從那裏控制MediaPlayer實例。您須要將MediaPlayer嵌入到MediaBrowserServiceCompat服務中,並讓它與另外一個活動中的MediaBrowserCompat交互。
要當心這個client/server設置。人們對在後臺服務中運行的播放器如何與系統的其餘部分進行交互抱有指望。若是您的應用程序沒有知足這些指望,用戶可能會有一個糟糕的體驗。閱讀創建一個音頻應用程序的完整細節。
本節描述了在服務中實現MediaPlayer時管理它的特殊說明。
首先,與活動同樣,服務中的全部工做在默認狀況下都是在單個線程中完成的——事實上,若是您從同一個應用程序運行活動和服務,默認狀況下它們使用相同的線程(「主線程」)。所以,服務須要快速處理傳入意圖,而且在響應它們時從不執行冗長的計算。若是預期有任何繁重的工做或阻塞調用,您必須異步執行這些任務:要麼從另外一個您本身實現的線程執行,要麼使用框架的許多異步處理工具。
例如,在使用主線程中的MediaPlayer時,應該調用prepareAsync()而不是prepare(),並實現MediaPlayer.OnPreparedListener目的是在準備完成後開始播放時獲得通知。例如:
public class MyService extends Service implements MediaPlayer.OnPreparedListener {
private static final String ACTION_PLAY = "com.example.action.PLAY";
MediaPlayer mMediaPlayer = null;
public int 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 */
public void onPrepared(MediaPlayer player) {
player.start();
}
}
複製代碼
在同步操做中,錯誤一般會以異常或錯誤代碼發出信號,但不管什麼時候使用異步資源,都應該確保將錯誤通知給應用程序。對於MediaPlayer,您能夠經過實現MediaPlayer.OnErrorListener並將其設置到MediaPlayer實例中來解決該問題。
public class MyService extends Service implements MediaPlayer.OnErrorListener {
MediaPlayer mMediaPlayer;
public void initMediaPlayer() {
// ...initialize the MediaPlayer here...
mMediaPlayer.setOnErrorListener(this);
}
@Override
public boolean onError(MediaPlayer mp, int what, int extra) {
// ... react appropriately ...
// The MediaPlayer has moved to the Error state, must be reset!
}
}
複製代碼
要記住,當發生錯誤時,MediaPlayer將切換到錯誤狀態,您必須在再次使用它以前重置它。
當設計在後臺播放媒體的應用程序時,設備可能會在服務運行時休眠。因爲Android系統試圖在設備處於休眠狀態時節省電池,因此係統試圖關閉手機的任何沒必要要的功能,包括CPU和WiFi硬件。然而,若是您的服務正在播放或流媒體音樂,您但願防止系統干擾您的播放。
爲了確保您的服務在這些條件下繼續運行,您必須使用「喚醒鎖」。喚醒鎖是一種向系統發出信號的方式,即您的應用程序正在使用某些特性,即便手機處於空閒狀態,這些特性也應該保持可用。
注意:你應該儘可能少用喚醒鎖,而且只在必要的時候使用它們,由於它們會大大減小設備的電池壽命。
要確保在MediaPlayer播放時CPU繼續運行,在初始化MediaPlayer時調用setWakeMode()方法。一旦你這樣作了,MediaPlayer會在播放時持有指定的鎖,並在暫停或中止時釋放鎖:
mMediaPlayer = new MediaPlayer();
// ... other initialization here ...
mMediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);
複製代碼
可是,在本例中得到的喚醒鎖只保證CPU保持清醒。若是你是經過網絡流媒體,使用的是Wi-Fi,你可能也想要一個WifiLock,你必須手動獲取和釋放它。所以,當您開始使用遠程URL準備MediaPlayer時,您應該建立並得到Wi-Fi鎖。例如:
WifiLock wifiLock = ((WifiManager) getSystemService(Context.WIFI_SERVICE))
.createWifiLock(WifiManager.WIFI_MODE_FULL, "mylock");
wifiLock.acquire();
複製代碼
當你暫停或中止你的媒體,或當你再也不須要網絡,你應該釋放鎖:
wifiLock.release();
複製代碼
如前所述,MediaPlayer對象可能會消耗大量的系統資源,因此您應該只在須要的時候使用它,而且在使用以後調用release()。顯式調用這種清理方法而不是依賴於系統垃圾收集是很重要的,由於垃圾收集器從新聲明MediaPlayer可能須要一些時間,由於它只對內存需求敏感,而不缺少其餘與媒體相關的資源。所以,在使用服務時,您應該老是重寫onDestroy()方法,以確保釋放MediaPlayer:
public class MyService extends Service {
MediaPlayer mMediaPlayer;
// ...
@Override
public void onDestroy() {
super.onDestroy()
if (mMediaPlayer != null) mMediaPlayer.release();
}
}
複製代碼
從Android 8.0 (API級別26)開始,MediaPlayer就包含了支持drm保護材料回放的API。它們相似於MediaDrm提供的低級API,可是它們在更高級別上操做,而且不公開底層提取器、drm和加密對象。
儘管MediaPlayer DRM API沒有提供MediaDrm的所有功能,但它支持最多見的用例。當前實現能夠處理如下內容類型:
下面的代碼片斷演示瞭如何在簡單的同步實現中使用新的DRM MediaPlayer方法。
要管理drm控制的媒體,您須要在一般的MediaPlayer調用流以外包括新的方法,以下所示:
setDataSource();
setOnDrmConfigHelper(); // optional, for custom configuration
prepare();
if (getDrmInfo() != null) {
prepareDrm();
getKeyRequest();
provideKeyResponse();
}
// MediaPlayer is now ready to use
start();
// ...play/pause/resume...
stop();
releaseDrm();
複製代碼
像往常同樣,初始化MediaPlayer對象並使用setDataSource()設置其源。而後,要使用DRM,執行如下步驟:
若是MediaPlayer.DrmInfo存在:
默認狀況下,prepareDrm()同步運行,阻塞直到準備工做完成。可是,在新設備上進行的第一次DRM準備也可能須要進行準備,準備工做由prepareDrm()內部處理,因爲涉及網絡操做,可能須要一些時間才能完成。經過定義和設置MediaPlayer.OnDrmPreparedListener,能夠避免在prepareDrm()上阻塞。
當您設置OnDrmPreparedListener時,prepareDrm()在後臺執行請求(若是須要)和準備。當drm準備就緒時,將調用偵聽器。您不該該對調用序列或偵聽器運行的線程作任何假設(除非偵聽器註冊到handler thread)。偵聽器在prepareDrm()返回以前或以後能被調用。
您能夠異步地初始化DRM,經過建立和註冊MediaPlayer.OnDrmInfoListener用於DRM準備和MediaPlayer.OnDrmPreparedListener去啓動播放器。它們與prepareAsync()協同工做,以下所示:
setOnPreparedListener();
setOnDrmInfoListener();
setDataSource();
prepareAsync();
// ...
// If the data source content is protected you receive a call to the onDrmInfo() callback.
onDrmInfo() {
prepareDrm();
getKeyRequest();
provideKeyResponse();
}
// When prepareAsync() finishes, you receive a call to the onPrepared() callback.
// If there is a DRM, onDrmInfo() sets it up before executing this callback,
// so you can start the player.
onPrepared() {
start();
}
複製代碼
從Android 8.0 (API級別26)開始,MediaPlayer還能夠爲H.264和AAC的基本流類型解密通用加密方案(CENC)和HLS採樣級加密媒體(METHOD=SAMPLE-AES)。之前支持全段加密媒體(METHOD=AES-128)。
在媒體播放器應用程序中可能有用的另外一個特性是檢索本地音樂。你能夠經過查詢外部媒體的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.
} else if (!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 = new MediaPlayer();
mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mMediaPlayer.setDataSource(getApplicationContext(), contentUri);
// ...prepare and start...
複製代碼
SimpleMediaPlayer代碼示例展現瞭如何構建獨立播放器。android-BasicMediaDecoder和android-DeviceOwner示例進一步演示了本頁所述api的使用。
這些頁面涵蓋了有關錄音、存儲和回放音頻和視頻的主題。