Android Media Playback
原文
The Android multimedia framework includes support for playing variety of common media types, so that you can easily integrate audio, video and images into your applications. You can play audio or video from media files stored in your application's resources (raw resources), from standalone files in the filesystem, or from a data stream arriving over a network connection, all using MediaPlayer APIs.
Android多媒體框架包含對多種常見媒體類型的支持,所以你可以容易的在自己的應用中集成音頻,視頻和圖片。你可以播放應用內的資源文件,或文件系統中獨立的文件,也可以通過網絡數據流來播放,這些功能都使用MediaPlayer APIs實現。
Note: You can play back the audio data only to the standard output device. Currently, that is the mobile device speaker or a Bluetooth headset. You cannot play sound files in the conversation audio during a call.
注意:你只能通過標準輸出設備播放音頻。當前,這包括移動設備的揚聲器或者藍牙耳機。你不能在用戶打電話時播放音頻。
The Basics
在Android Framework中,下面兩個類用來播放聲音和視頻:
MediaPlayer
此類是播放聲音和視頻的主要API。
AudioManager
此類管理音頻資源和音頻在設備上的輸出。
Manifest Declarations
Before starting development on your application using MediaPlayer, make sure your manifest has the appropriate declarations to allow use of related features.
在開始使用MediaPlayer之前,確保你的清單文件中聲明了與相關特性有關的權限:
Internet Permission - 如果你使用MediaPlayer播放網絡內容,應用需要網絡訪問權限。
Wake Lock Permission - 如果你的應用需要保持屏幕不變暗或者處理器不休眠,或者使用
MediaPlayer.setScreenOnWhilePlaying()
和MediaPlayer.setWakeMode()
方法,你需要請求以下權限:
Using MediaPlayer
One of the most important components of the media framework is the MediaPlayer class. An object of this class can fetch, decode, and play both audio and video with minimal setup. It supports several different media sources such as:
媒體框架中最重要的組件之一是MediaPlayer類。該類的對象可以用最少的步驟獲取,解碼和播放音頻和視頻。它支持多種媒體源,例如:
本地資源
內部URI,例如用于Content Resolver的URI
外部URL(流)
Android支持的媒體類型,見此文檔:Android Supported Media Formats。
下面的例子顯示了如何播放本地raw資源(應用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 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播放在線媒體文件,該文件必須可以漸近下載(Progressive download)。
警告:在使用setDataSource()
時,必須捕獲或者傳遞IllegalArgumentException和IOException,因為你引用的文件可能不存在。
Asynchronous Preparation
Using MediaPlayer can be straightforward in principle. However, it's important to keep in mind that a few more things are necessary to integrate it correctly with a typical Android application. For example, the call to prepare() can take a long time to execute, because it might involve fetching and decoding media data. So, as is the case with any method that may take long to execute, you should never call it from your application's UI thread.
原則上,使用MediaPlayer是簡單直接的。然而集成在Android應用中時,有幾點需要注意。例如,調用prepare()
可能會花費很長時間,因為此方法涉及到媒體數據的獲取和解碼,因此不應該在UI線程調用。
To avoid hanging your UI thread, spawn another thread to prepare the MediaPlayer and notify the main thread when done. However, while you could write the threading logic yourself, this pattern is so common when using MediaPlayer that the framework supplies a convenient way to accomplish this task by using the prepareAsync() method. This method starts preparing the media in the background and returns immediately. When the media is done preparing, the onPrepared() method of the MediaPlayer.OnPreparedListener, configured through setOnPreparedListener() is called.
要避免掛起UI線程,使用另一個線程來準備MediaPlaer,在完成時通知主線程。然而,雖然你可以自己寫線程邏輯,此框架提供了prepareAsync
方法來簡化這一工作。此方法立刻返回,在后臺執行準備工作,完成后通過回調通知調用者。
Managing State
Another aspect of a MediaPlayer that you should keep in mind is that it's state-based. That is, the MediaPlayer has an internal state that you must always be aware of when writing your code, because certain operations are only valid when then player is in specific states. If you perform an operation while in the wrong state, the system may throw an exception or cause other undesireable behaviors.
關于MediaPlayer的另一個關注點是它是基于狀態的。也就是說,你寫代碼時必須始終意識到MediaPlayer有內部狀態,因為某些操作只在player處于特定狀態時才有效。如果你在錯誤的狀態下執行操作,系統可能會拋出異常或者引發其他不需要的行為。
MediaPlayer類文檔中展示了MediaPlayer的完整狀態圖。
Releasing the MediaPlayer
A MediaPlayer can consume valuable system resources. Therefore, you should always take extra precautions to make sure you are not hanging on to a MediaPlayer instance longer than necessary. When you are done with it, you should always call release() to make sure any system resources allocated to it are properly released. For example, if you are using a MediaPlayer and your activity receives a call to onStop(), you must release the MediaPlayer, because it makes little sense to hold on to it while your activity is not interacting with the user (unless you are playing media in the background, which is discussed in the next section). When your activity is resumed or restarted, of course, you need to create a new MediaPlayer and prepare it again before resuming playback.
MediaPlayer會消費寶貴的系統資源,所以必須注意不要保持MediaPlayer實例超過需要的時間。當你用完時,應該調用release()
方法來確保分配給它的系統資源被合適的釋放了。例如,如果你正在使用MediaPlayer,而Activity收到了onStop()
回調,則必須釋放MediaPlayer(除非你在后臺播放)。當Activity 重新獲得焦點或者重新開始時,你需要創建一個新的MediaPlayer實例,并在恢復播放前準備它。
將MediaPlayer釋放并制空:
mediaPlayer.release();
mediaPlayer = null;
As an example, consider the problems that could happen if you forgot to release the MediaPlayer when your activity is stopped, but create a new one when the activity starts again. As you may know, when the user changes the screen orientation (or changes the device configuration in another way), the system handles that by restarting the activity (by default), so you might quickly consume all of the system resources as the user rotates the device back and forth between portrait and landscape, because at each orientation change, you create a new MediaPlayer that you never release.
例如,如果你在Activity stop時忘了釋放MediaPlayer,但在Activity create時創建了新的實例,那在用戶反復旋轉屏幕時,可能會很快就耗盡所有的系統資源,因為每次方向改變,你都創建了新的MediaPlayer對象,但從來沒有釋放。
Using a Service with MediaPlayer
If you want your media to play in the background even when your application is not onscreen—that is, you want it to continue playing while the user is interacting with other applications—then you must start a Service and control the MediaPlayer instance from there. You should be careful about this setup, because the user and the system have expectations about how an application running a background service should interact with the rest of the system. If your application does not fulfil those expectations, the user may have a bad experience. This section describes the main issues that you should be aware of and offers suggestions about how to approach them.
如果想讓媒體文件在后臺播放,需要啟動一個Service,讓后再Service中控制MediaPlayer實例。用戶和系統對運行后臺服務的應用如何同系統其它部分交互有一些期望,如果你的應用無法滿足這些期望,用戶可能會有糟糕的體驗。本節描述了你需要注意的主要事項以及如何解決的建議。
Running asynchronously
First of all, like an Activity, all work in a Service is done in a single thread by default—in fact, if you're running an activity and a service from the same application, they use the same thread (the "main thread") by default. Therefore, services need to process incoming intents quickly and never perform lengthy computations when responding to them. If any heavy work or blocking calls are expected, you must do those tasks asynchronously: either from another thread you implement yourself, or using the framework's many facilities for asynchronous processing.
首先,同Activity一樣,默認情況下Service中的所有工作也是在主線程中完成的。因此,服務需要快速的處理傳入的intent,在響應意圖是不能執行長時間操作。如果有大量的工作或者阻塞式的調用,就需要異步的完成。
For instance, when using a MediaPlayer from your main thread, you should call prepareAsync() rather than prepare(), and implement a MediaPlayer.OnPreparedListener in order to be notified when the preparation is complete and you can start playing. For example:
例如,在主線程中使用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 heremMediaPlayer.setOnPreparedListener(this);mMediaPlayer.prepareAsync(); // prepare async to not block main thread}}/** Called when MediaPlayer is ready */public void onPrepared(MediaPlayer player) {player.start();}
}
Handling asynchronous errors
On synchronous operations, errors would normally be signaled with an exception or an error code, but whenever you use asynchronous resources, you should make sure your application is notified of errors appropriately. In the case of a MediaPlayer, you can accomplish this by implementing a MediaPlayer.OnErrorListener and setting it in your MediaPlayer instance:
在同步的調用中,錯誤通常是通過異常或者錯誤碼表示,但在異步調用時,你應該確保正確的接收到錯誤。對于MediaPlayer來說,你可以實現MediaPlayer.OnErrorListener
接口,設置給MediaPlayer實例:
public class MyService extends Service implements MediaPlayer.OnErrorListener {MediaPlayer mMediaPlayer;public void initMediaPlayer() {// ...initialize the MediaPlayer here...mMediaPlayer.setOnErrorListener(this);}@Overridepublic boolean onError(MediaPlayer mp, int what, int extra) {// ... react appropriately ...// The MediaPlayer has moved to the Error state, must be reset!}
}
It's important to remember that when an error occurs, the MediaPlayer moves to the Error state (see the documentation for the MediaPlayer class for the full state diagram) and you must reset it before you can use it again.
要記住當錯誤發生時,MediaPlayer轉變為Error狀態,在你使用它之前需要重置(reset)它。