簡介
public class android.media.MediaPlayer extends Object implements VolumeAutomation
可能需要的權限:
One may need to declare a
corresponding(相應) WAKE_LOCK permission <uses-permission> element.
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
狀態機示意圖 State Diagram
Playback control of audio/video files and streams is managed as a state machine. The following diagram shows the life cycle and the states of a MediaPlayer object driven by the supported playback control operations.
對播放音頻/視頻文件和流的控制是通過一個狀態機來管理的。
下圖顯示了由所支持的播放控制操作驅動的,MediaPlayer對象的生命周期和狀態。
The ovals represent the states a MediaPlayer object may reside in. The arcs represent the playback control operations that drive the object state transition. There are two types of arcs. The arcs with a single arrow head represent synchronous method calls, while those with a double arrow head represent asynchronous method calls.
橢圓代表MediaPlayer對象可能駐留的狀態,弧線表示驅動MediaPlayer在各個狀態之間
轉換
的播放控制操作。
這里有兩種類型的弧線:
具有單個箭頭的弧表示同步方法調用,而帶有雙箭頭的弧表示異步方法調用。

狀態機示意圖官方解釋
From this state diagram, one can see that a MediaPlayer object has the following states:
通過這張狀態圖,我們可以知道一個MediaPlayer對象有以下的狀態:
When a MediaPlayer object is just created using new or after reset() is called, it is in the Idle state; and after release() is called, it is in the End state. Between these two states is the life cycle of the MediaPlayer object.
1、當一個MediaPlayer對象被剛剛用new創建或是調用了reset()方法后,它就處於【Idle】狀態。當調用了release()方法后,它就處於【End】狀態。這兩種狀態之間是MediaPlayer對象的生命周期。
- There is a subtle but important difference between a newly constructed MediaPlayer object and the MediaPlayer object after reset() is called. It is a programming error to invoke methods such asgetCurrentPosition(), getDuration(), getVideoHeight(), getVideoWidth(), setAudioAttributes(AudioAttributes), setLooping(boolean), setVolume(float, float), pause(), start(), stop(), seekTo(long, int), prepare() or prepareAsync() in the Idle state for both cases.
If any of these methods is called right after a MediaPlayer object is constructed, the user supplied callback method OnErrorListener.onError() won't be called by the internal player engine and the object state remains unchanged; but if these methods are called right after reset(), the user supplied callback method OnErrorListener.onError() will be invoked by the internal player engine and the object will be transfered to the Error state. - 1.1) 在一個新構建的MediaPlayer對象和一個調用了reset()方法的MediaPlayer對象之間有一個微小的但是十分重要的差別。在處於Idle狀態時,調用***方法都是編程錯誤。
當一個MediaPlayer對象剛被構建的時候,內部的播放引擎和對象的狀態都沒有改變,在這個時候調用以上的那些方法,框架將無法回調客戶端程序注冊的OnErrorListener.onError()方法;但如果那些方法是在調用了reset()方法之后調用的,內部的播放引擎就會回調客戶端程序注冊的OnErrorListener.onError()方法了,並將錯誤的狀態傳入。 - It is also recommended that once a MediaPlayer object is no longer being used, call release() immediately so that resources used by the internal player engine associated with the MediaPlayer object can be released immediately. Resource may include singleton resources such as hardware acceleration components and failure to call release() may cause subsequent instances of MediaPlayer objects to fallback to software implementations or fail altogether. Once the MediaPlayer object is in the End state, it can no longer be used and there is no way to bring it back to any other state.
- 1.2) 建議,一旦一個MediaPlayer對象不再被使用,應立即調用release()方法來釋放在內部的播放引擎中與這個MediaPlayer對象關聯的資源。資源可能包括如硬件加速組件的單態組件,若沒有調用release()方法可能會導致,之后的MediaPlayer對象實例無法使用這種單態硬件資源,從而退回到軟件實現或運行失敗。一旦MediaPlayer對象進入了End狀態,它不能再被使用,也沒有辦法再遷移到其它狀態。
- Furthermore, the MediaPlayer objects created using new is in the Idle state, while those created with one of the overloaded convenient create methods are NOT in the Idle state. In fact, the objects are in the Prepared state if the creation using create method is successful.
- 1.3) 此外,使用new操作符創建的MediaPlayer對象處於Idle狀態,而那些通過重載的create()便利方法創建的MediaPlayer對象卻不是處於Idle狀態。事實上,如果成功調用了重載的create()方法,那么這些對象已經是Prepare狀態了。
In general, some playback control operation may fail due to various reasons, such as unsupported audio/video format, poorly interleaved audio/video, resolution too high, streaming timeout, and the like. Thus, error reporting and recovery is an important concern under these circumstances. Sometimes, due to programming errors, invoking a playback control operation in an invalid state may also occur. Under all these error conditions, the internal player engine invokes a user supplied OnErrorListener.onError() method if an OnErrorListener has been registered beforehand via setOnErrorListener(android.media.MediaPlayer.OnErrorListener).
2、 在一般情況下,由於種種原因一些播放控制操作可能會失敗,如不支持的音頻/視頻格式,缺少隔行掃描的音頻/視頻,分辨率太高,流超時等原因,等等。因此,錯誤報告和恢復在這種情況下是非常重要的。有時,由於編程錯誤,在處於無效狀態的情況下調用了一個播放控制操作也可能發生(錯誤)。在所有這些錯誤條件下,內部的播放引擎會調用一個由客戶端程序員提供的OnErrorListener.onError()方法,如果已經通過setOnErrorListener(android.media.MediaPlayer.OnErrorListener)方法來注冊OnErrorListener.
- It is important to note that once an error occurs, the MediaPlayer object enters the Error state (except as noted above), even if an error listener has not been registered by the application.
- In order to reuse a MediaPlayer object that is in the Error state and recover from the error, reset() can be called to restore the object to its Idle state.
- It is good programming practice to have your application register a OnErrorListener to look out for error notifications from the internal player engine.
- IllegalStateException is thrown to prevent programming errors such as calling prepare(), prepareAsync(), or one of the overloaded setDataSource methods in an invalid state.
- 2.1) 需要注意的是,一旦發生錯誤,MediaPlayer對象會進入到Error狀態(除了如上所述),即使應用程序尚未注冊錯誤監聽器。
- 2.2) 為了重用一個處於Error狀態的MediaPlayer對象,可以調用reset()方法來把這個對象恢復成Idle狀態。
- 2.3) 注冊一個OnErrorListener來獲知內部播放引擎發生的錯誤是好的編程習慣。
- 2.4) 在不合法的狀態下調用一些方法,如prepare(),prepareAsync()和setDataSource()方法會拋出IllegalStateException異常,以避免編程錯誤。
Calling setDataSource(FileDescriptor), or setDataSource(String), or setDataSource(Context, Uri), or setDataSource(FileDescriptor, long, long), or setDataSource(MediaDataSource) transfers a MediaPlayer object in the Idle state to the Initialized state.
3、 調用setDataSource(***)方法方法會使處於Idle狀態的對象遷移到Initialized狀態。
- An IllegalStateException is thrown if setDataSource() is called in any other state.
- It is good programming practice to always look out for IllegalArgumentException and IOException that may be thrown from the overloaded setDataSource methods.
- 3.1) 若MediaPlayer處於其它的狀態下,調用setDataSource()方法會拋出IllegalStateException異常。
- 3.2) 好的編程習慣是不要疏忽了調用setDataSource()方法的時候可能會拋出的IllegalArgumentException異常和IOException異常。
A MediaPlayer object must first enter the Prepared state before playback can be started.
4、在開始播放之前,MediaPlayer對象必須要(一定會)
先
進入Prepared狀態。
- There are two ways (synchronous vs. asynchronous) that the Prepared state can be reached: either a call to prepare() (synchronous) which transfers the object to the Prepared state once the method call returns, or a call to prepareAsync() (asynchronous) which first transfers the object to the Preparing state after the call returns (which occurs almost right way) while the internal player engine continues working on the rest of preparation work until the preparation work completes.
When the preparation completes or when prepare() call returns, the internal player engine then calls a user supplied callback method, onPrepared() of the OnPreparedListener interface, if an OnPreparedListener is registered beforehand via setOnPreparedListener(android.media.MediaPlayer.OnPreparedListener). - 4.1) 有兩種方法(同步和異步)可以使MediaPlayer對象進入Prepared狀態:要么調用prepare()方法,此方法返回就表示該MediaPlayer對象已經進入了Prepared狀態;要么調用prepareAsync()方法,此方法會首先使此MediaPlayer對象進入Preparing狀態並返回(這幾乎時立即發生的),而內部的播放引擎會繼續未完成的准備工作,直到准備工作完成。
當准備工作完全完成時,如果之前已經通過調用setOnPreparedListener()方法注冊過OnPreparedListener,內部的播放引擎就會調用客戶端提供的OnPreparedListener.onPrepared()監聽方法。 - It is important to note that the Preparing state is a transient state, and the behavior of calling any method with side effect while a MediaPlayer object is in the Preparing state is undefined.
- 4.2) 需要注意的是, Preparing是一個中間狀態,在此狀態下調用任何具有副作用的方法的結果都是未知的!
- An IllegalStateException is thrown if prepare() or prepareAsync() is called in any other state.
- 4.3) 在除此之外的其他狀態下調用prepare()和prepareAsync()方法會拋出IllegalStateException異常。
- While in the Prepared state, properties such as audio/sound volume, screenOnWhilePlaying, looping can be adjusted by invoking the corresponding set methods.
- 4.4) 當MediaPlayer對象處於Prepared狀態的時候,可以通過調用相應的set方法,調整音頻/視頻的屬性,如音量,播放時是否一直亮屏,循環播放等。
To start the playback, start() must be called. After start() returns successfully, the MediaPlayer object is in the Started state. isPlaying() can be called to test whether the MediaPlayer object is in the Started state.
5、要開始播放,必須調用start()方法。當此方法成功返回時,MediaPlayer的對象處於Started狀態。isPlaying()方法可以被調用來測試某個MediaPlayer對象是否在Started狀態。
- While in the Started state, the internal player engine calls a user supplied OnBufferingUpdateListener.onBufferingUpdate() callback method if a OnBufferingUpdateListener has been registered beforehand via setOnBufferingUpdateListener(OnBufferingUpdateListener). This callback allows applications to keep track of the buffering status while streaming audio/video.
- 5.1) 當處於Started狀態時,如果***,內部播放引擎會調用客戶端提供的**回調方法。此回調允許應用程序在流式傳輸音頻/視頻時跟蹤緩沖狀態。
- Calling start() has not effect on a MediaPlayer object that is already in the Started state.
- 5.2) 對一個已經處於Started 狀態的MediaPlayer對象,調用start()方法沒有影響。
Playback can be paused and stopped, and the current playback position can be adjusted. Playback can be paused via pause(). When the call to pause() returns, the MediaPlayer object enters the Paused state. Note that the transition from the Started state to the Paused state and vice versa(
反之亦然
) happens asynchronously in the player engine. It may take some time before the state is updated in calls to isPlaying(), and it can be a number of seconds in the case of streamed content.
6、播放可以被暫停、停止,並且可以調整當前播放位置。可以通過pause()暫停
播放
。當調用pause()方法並返回時,會使MediaPlayer對象進入Paused狀態。注意,Started與Paused狀態間的相互轉換,在
MediaPlayer
內部的播放引擎中是異步的。
在調用isPlaying()后,在
狀態更新
之前可能需要一些時間,並且在流式傳輸內容的情況下可能需要幾秒鍾。
- Calling start() to resume playback for a paused MediaPlayer object, and the resumed playback position is the same as where it was paused. When the call to start() returns, the paused MediaPlayer object goes back to the Started state.
- 6.1) 調用start()方法會讓一個處於Paused狀態的MediaPlayer對象從之前暫停時的地方恢復播放。當調用start()方法返回的時候,暫停的MediaPlayer對象會變成Started狀態。
- Calling pause() has no effect on a MediaPlayer object that is already in the Paused state.
- 6.2) 對一個已經處於Paused狀態的MediaPlayer對象,調用pause()方法沒有影響。
Calling stop() stops playback and causes a MediaPlayer in the Started, Paused, Prepared or PlaybackCompleted state to enter the Stopped state.
7、調用stop()方法會停止播放,並且還會讓一個處於Started、Paused、Prepared、PlaybackCompleted狀態的MediaPlayer進入Stopped狀態。
- Once in the Stopped state, playback cannot be started until prepare() or prepareAsync() are called to set the MediaPlayer object to the Prepared state again.
- 7.1) 一旦處於停止狀態,播放將不能開始,直到調用prepare()或prepareAsync()將MediaPlayer對象重新設置為Prepared狀態。
- Calling stop() has no effect on a MediaPlayer object that is already in the Stopped state.
- 7.2) 對一個已經處於Stopped狀態的MediaPlayer對象stop()方法沒有影響。
The playback position can be adjusted with a call to seekTo(long, int).
8、調用seekTo()方法可以調整播放的位置。
- Although the asynchronuous seekTo(long, int) call returns right away, the actual seek operation may take a while to finish, especially for audio/video being streamed. When the actual seek operation completes, the internal player engine calls a user supplied OnSeekComplete.onSeekComplete() if an OnSeekCompleteListener has been registered beforehand via setOnSeekCompleteListener(OnSeekCompleteListener).
- 8.1) 雖然異步的seekTo(long,int)在調用后立即返回,但實際的定位操作可能需要一段時間才能完成,特別是播放流形式的音頻/視頻。當實際的定位播放操作完成之后,如果***,內部的播放引擎會調用客戶端提供的OnSeekComplete.onSeekComplete()回調方法。
- Please note that seekTo(long, int) can also be called in the other states, such as Prepared, Paused and PlaybackCompleted state. When seekTo(long, int) is called in those states, one video frame will be displayed if the stream has video and the requested position is valid.
- 8.2) 注意,seekTo()方法也可以在其它狀態下調用,比如Prepared、Paused、PlaybackCompleted狀態。當在這些狀態中調用seekTo()時,如果流具有視頻並且請求的位置有效,則將顯示一個視頻幀。
- Furthermore, the actual current playback position can be retrieved with a call to getCurrentPosition(), which is helpful for applications such as a Music player that need to keep track of the playback progress.
- 8.3) 此外,目前的播放位置,實際可以調用getCurrentPosition()方法得到,它可以幫助如音樂播放器的應用程序不斷更新播放進度
When the playback reaches the end of stream, the playback completes.
9、當播放到流的末尾時,播放就完成了。
- If the looping mode was being set to true with setLooping(boolean), the MediaPlayer object shall(應) remain in the Started state.
- 9.1) 如果通過調用setLooping(true)方法設置為循環模式,這個MediaPlayer對象會保持在Started狀態。
- If the looping mode was set to false , the player engine calls a user supplied callback method, OnCompletion.onCompletion(), if a OnCompletionListener is registered beforehand via setOnCompletionListener(OnCompletionListener). The invoke of the callback signals(表面) that the object is now in the PlaybackCompleted state.
- 9.2) 如果循環模式設置為false,如果***,那么內部的播放引擎會調用客戶端提供的OnCompletion.onCompletion()回調方法。調用回調后說明這個MediaPlayer對象進入了PlaybackCompleted狀態。
- While in the PlaybackCompleted state, calling start() can restart the playback from the beginning of the audio/video source.
- 9.3) 當處於PlaybackCompleted狀態的時候,可以再調用start()方法來讓這個MediaPlayer對象再進入Started狀態。
有效的狀態 Valid and invalid states
Valid and invalid states
Method Name | Valid Sates | Invalid States | Comments |
getCurrentPosition | {Idle, Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted} |
{Error} | Successful invoke of this method in a valid state does not change the state. Calling this method in an invalid state transfers the object to the Error state. |
getVideoHeight | |||
getVideoWidth | |||
setLooping | |||
isPlaying | |||
setVolume | Successful invoke of this method does not change the state. | ||
setAudioAttributes | Successful invoke of this method does not change the state. In order for the target audio 【attributes/stream】 type to become effective, this method must be called before prepare() or prepareAsync(). | ||
setAudioStreamType (deprecated) |
|||
setDataSource | {Idle} | {Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted, Error} |
Successful invoke of this method in a valid state transfers the object to the Initialized state. Calling this method in an invalid state throws an IllegalStateException. |
setAudioSessionId | This method must be called in idle state as the audio session ID must be known before calling setDataSource. Calling it does not change the object state. | ||
setPlaybackParams | {Initialized, Prepared, Started, Paused, PlaybackCompleted, Error} |
{Idle, Stopped} | This method will change state in some cases, depending on when it's called. |
setVideoScalingMode | {Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted} |
{Idle, Error} | Successful invoke of this method does not change the state. |
attachAuxEffect | This method must be called after setDataSource. Calling it does not change the object state. | ||
prepare | {Initialized, Stopped} | {Idle, Prepared, Started, Paused, PlaybackCompleted, Error} |
Successful invoke of this method in a valid state transfers the object to the Prepared state. Calling this method in an invalid state throws an IllegalStateException. |
prepareAsync | Successful invoke of this method in a valid state transfers the object to the Preparing state. Calling this method in an invalid state throws an IllegalStateException. | ||
seekTo | {Prepared, Started, Paused, PlaybackCompleted} |
{Idle, Initialized, Stopped, Error} |
Successful invoke of this method in a valid state does not change the state. Calling this method in an invalid state transfers the object to the Error state. |
start | Successful invoke of this method in a valid state transfers the object to the Started state. Calling this method in an invalid state transfers the object to theError state. | ||
getDuration | {Prepared, Started, Paused, Stopped, PlaybackCompleted} |
{Idle, Initialized, Error} | Successful invoke of this method in a valid state does not change the state. Calling this method in an invalid state transfers the object to the Error state. |
stop | Successful invoke of this method in a valid state transfers the object to the Stopped state. Calling this method in an invalid state transfers the object to theError state. | ||
getTrackInfo | Successful invoke of this method does not change the state. | ||
addTimedTextSource | |||
selectTrack | |||
deselectTrack | |||
pause | {Started, Paused, PlaybackCompleted} | {Idle, Initialized, Prepared, Stopped, Error} |
Successful invoke of this method in a valid state transfers the object to the Paused state. Calling this method in an invalid state transfers the object to theError state. |
reset | {Idle, Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted, Error} |
不存在 | After reset(), the object is like being just created. |
release | 任何狀態 | After release(), the object is no longer available. | |
setAuxEffectSendLevel | This method can be called in any state and calling it does not change the object state. | ||
getAudioSessionId | |||
setDisplay | |||
setSurface | |||
isLooping | |||
setOnBufferingUpdateListener | |||
setOnCompletionListener | |||
setOnErrorListener | |||
setOnPreparedListener | |||
setOnSeekCompleteListener | |||
setScreenOnWhilePlaying | |||
setWakeMode |
【setDataSource】系列方法源碼分析
1、提供 String
- void setDataSource(String path) Sets the data source (file-path or http/rtsp URL) to use.
- When path refers to a local file, the file may actually be opened by a process other than the calling application. This implies that the pathname should be an absolute path (as any other process runs with unspecified current working directory), and that the pathname should reference a world-readable file. As an alternative, the application could first open the file for reading, and then use the file descriptor form setDataSource(FileDescriptor).
- 當path指的是本地文件時,該文件實際上可能是由一個進程而非調用的應用程序所打開。 這意味着路徑名應該是絕對路徑(任何其他進程都使用未指定的當前工作目錄運行),並且此路徑名所指向的文件是世界可讀的。 作為替代方案,應用程序可以首先打開文件進行閱讀,然后使用 setDataSource(FileDescriptor) 的文件描述符。
- void setDataSource(String path, Map<String, String> headers)
- Map headers:the headers associated with the http request for the stream you want to play
相關源碼
public void setDataSource(String path) throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
setDataSource(path, null, null);
}
public void setDataSource(String path, Map<String, String> headers)
throws IOException, IllegalArgumentException, SecurityException, IllegalStateException{
String[] keys = null;
String[] values = null;
if (headers != null) {
keys = new String[headers.size()];
values = new String[headers.size()];
int i = 0;
for (Map.Entry<String, String> entry: headers.entrySet()) {
keys[i] = entry.getKey();
values[i] = entry.getValue();
++i;
}
}
setDataSource(path, keys, values);
}
private void setDataSource(String path, String[] keys, String[] values)
throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
final Uri uri = Uri.parse(path);
final String scheme = uri.getScheme();
if ("file".equals(scheme)) path = uri.getPath();
// 本地文件else if (scheme != null) {
// handle non-file sources。對於http文件,就是通過這種方式將網上的文件搞下來后設置給MediaPlayer
nativeSetDataSource(MediaHTTPService.createHttpServiceBinderIfNecessary(path), path, keys, values);
return;
}
final File file = new File(path);
if (file.exists()) {
FileInputStream is = new FileInputStream(file);
FileDescriptor fd = is.getFD();
setDataSource(fd);
// 可見,最終也是通過FileDescriptor設置的is.close();
} else throw new IOException("setDataSource failed.");
}
private native void nativeSetDataSource(IBinder httpServiceBinder, String path, String[] keys, String[] values)
throws IOException, IllegalArgumentException, SecurityException, IllegalStateException;
2、提供 FileDescriptor 或 AssetFileDescriptor
Tips:It is the caller's responsibility to close the file descriptor. It is safe to do so as soon as this call returns.
- void setDataSource(FileDescriptor fd) Sets the data source (FileDescriptor) to use.
- void setDataSource(FileDescriptor fd, long offset, long length) Sets the data source (FileDescriptor) to use.
- The FileDescriptor must be seekable (N.B. a LocalSocket is not seekable).
- void setDataSource(AssetFileDescriptor afd) Sets the data source (AssetFileDescriptor) to use.
- It is the caller's responsibility to close the file descriptor. It is safe to do so as soon as this call returns.
相關源碼
public void setDataSource(FileDescriptor fd) throws IOException, IllegalArgumentException, IllegalStateException {
setDataSource(fd, 0, 0x7ffffffffffffffL);// intentionally故意地 less than LONG_MAX
}
public void setDataSource(FileDescriptor fd, long offset, long length) throws IOException, IllegalArgumentException, IllegalStateException {
_setDataSource(fd, offset, length);
}
public void setDataSource(@NonNull AssetFileDescriptor afd) throws IOException, IllegalArgumentException, IllegalStateException {
Preconditions.checkNotNull(afd);
// Note: using getDeclaredLength so that our behavior is the same as previous versions when the content provider is returning a full file.
if (afd.getDeclaredLength() < 0) setDataSource(afd.getFileDescriptor());
else setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getDeclaredLength());
}
private native void _setDataSource(FileDescriptor fd, long offset, long length) throws IOException, IllegalArgumentException, IllegalStateException;
3、提供 Uri
- void setDataSource(Context context, Uri uri) Sets the data source as a content Uri.
- Uri: the Content URI of the data you want to play。This value must never be null.
- void setDataSource(Context context, Uri uri, Map<String, String> headers) Sets the data source as a content Uri.
- Map headers: the headers to be sent together with the request for the data。This value may be null.
- Note that the cross domain redirection is allowed by default, but that can be changed with key/value pairs through the headers parameter with "android-allow-cross-domain-redirect" as the key and "0" or "1" as the value to disallow or allow cross domain redirection.
- 請注意,默認情況下允許跨域重定向,但是可以通過headers參數使用鍵/值對更改,“android-allow-cross-domain-redirect”作為key,將“0”或“1”替換為value,禁止或允許跨域重定向。
- void setDataSource(Context context, Uri uri, Map<String, String> headers, List<HttpCookie> cookies) Sets the data source as a content Uri.
- List cookies: the cookies to be sent together with the request。This value may be null.
- Android O Developer Preview
相關源碼
public void setDataSource(@NonNull Context context, @NonNull Uri uri)
throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
setDataSource(context, uri, null);
}
public void setDataSource(@NonNull Context context, @NonNull Uri uri, @Nullable Map<String, String> headers)
throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
final ContentResolver resolver = context.getContentResolver();
final String scheme = uri.getScheme();
if (ContentResolver.SCHEME_FILE.equals(scheme)) {
setDataSource(uri.getPath());
// 文件return;
} else if (ContentResolver.SCHEME_CONTENT.equals(scheme) && Settings.AUTHORITY.equals(uri.getAuthority())) {
// Try cached ringtone first since the actual provider may not be encryption aware, or it may be stored on CE media storage
// 先嘗試緩存鈴聲,因為實際的提供者可能沒有被加密,或者可能存儲在CE媒體存儲上
final int type = RingtoneManager.getDefaultType(uri);
final Uri cacheUri = RingtoneManager.getCacheForType(type);
final Uri actualUri = RingtoneManager.getActualDefaultRingtoneUri(context, type);
if (attemptDataSource(resolver, cacheUri)) return;
else if (attemptDataSource(resolver, actualUri)) return;
else setDataSource(uri.toString(), headers);
} else {
// Try requested Uri locally first, or fallback to media server。先嘗試本地請求Uri,或者回退到媒體服務器
if (attemptDataSource(resolver, uri)) return;
else setDataSource(uri.toString(), headers);
}
}
4、提供 MediaDataSource
- void setDataSource(MediaDataSource dataSource) Sets the data source (MediaDataSource) to use.
- MediaDataSource: the MediaDataSource for the media you want to play
相關源碼
public void setDataSource(MediaDataSource dataSource) throws IllegalArgumentException, IllegalStateException {
_setDataSource(dataSource);
}
private native void _setDataSource(MediaDataSource dataSource) throws IllegalArgumentException, IllegalStateException;
【prepare、start、pause、stop、reset、seekTo】等方法源碼分析
1、prepare和prepareAsync 准備
After setting the datasource and the display surface, you need to either call prepare() or prepareAsync().
For files, it is OK to call prepare(), which blocks until MediaPlayer is ready for playback.
For streams, you should call prepareAsync(), which returns immediately, rather than blocking until enough data has been buffered.
設置datasource
和用來顯示的surface
后,您需要調用prepare
()
或prepareAsync
()
。
對於文件,可以調用prepare
()
,它阻塞,直到MediaPlayer准備好了進行播放。
對於流,您應該調用prepareAsync(),調用之后它會立即返回,而不是阻塞直到足夠的數據被緩沖后。
throws IllegalStateException if it is called in an invalid state
public void prepare() throws IOException, IllegalStateException {
_prepare();
scanInternalSubtitleTracks();
}
private native void _prepare() throws IOException, IllegalStateException;
public native void prepareAsync() throws IllegalStateException;
2、start
開始或恢復播放
Starts or resumes playback. If playback had previously been paused, playback will continue from where it was paused. If playback had been stopped, or never started before, playback will start at the beginning.
開始或恢復播放。 如果播放以前已暫停,播放將從暫停播放繼續。 如果播放已經停止,或者從未開始播放,則播放將從頭開始。
throws IllegalStateException if it is called in an invalid state
public void start() throws IllegalStateException {
baseStart();
stayAwake(true);
_start();
}
private native void _start() throws IllegalStateException;
3、pause
暫停播放
Pauses playback. Call start() to resume.
throws IllegalStateException if the internal player engine has not been initialized.
public void pause() throws IllegalStateException {
stayAwake(false);
_pause();
}
private native void _pause() throws IllegalStateException;
4、stop
停止播放
Stops playback after playback has been stopped or paused.
throws IllegalStateException if the internal player engine has not been initialized.
public void stop() throws IllegalStateException {
stayAwake(false);
_stop();
}
private native void _stop() throws IllegalStateException;
5、reset
重置狀態
Resets the MediaPlayer to its
uninitialized state. After calling this method, you will have to initialize it again by setting the data source and calling prepare().
將媒體播放器重置為未初始化狀態。 調用此方法后,您必須通過設置數據源並調用prepare()來重新初始化它。
public void reset() {
mSelectedSubtitleTrackIndex = -1;
synchronized(mOpenSubtitleSources) {
for (final InputStream is: mOpenSubtitleSources) {
try {
is.close();
} catch (IOException e) {
}
}
mOpenSubtitleSources.clear();
}
if (mSubtitleController != null) mSubtitleController.reset();
if (mTimeProvider != null) {
mTimeProvider.close();
mTimeProvider = null;
}
stayAwake(false);
_reset();
// make sure none of the listeners get called anymore
if (mEventHandler != null) mEventHandler.removeCallbacksAndMessages(null);
- synchronized (mIndexTrackPairs) {
mIndexTrackPairs.clear();
mInbandTrackIndices.clear();
};
}
private native void _reset();
6、seekTo 轉到指定位置
Seeks to specified time position. Same as seekTo(long, int) with mode =
SEEK_PREVIOUS_SYNC.
When seeking to the given time position, there is
no guarantee(不能保證)
that the data source has a
frame
located at the position. When this happens, a frame nearby will be
rendered(渲染)
. If msec is
negative
, time position zero will be used. If msec is larger than duration, duration will be used.
throws IllegalStateException if the internal player engine has not been initialized
參數 int msec:the offset in milliseconds from the start to seek to.
public native void seekTo(int msec) throws IllegalStateException;
【release】方法源碼分析
Releases resources
associated with this MediaPlayer object. It is considered good
practice to call this method when you're done using the MediaPlayer.
In particular, whenever an Activity of an application is paused (its onPause() method is called), or stopped (its onStop() method is called), this method should
be invoked to release the MediaPlayer object, unless the application has a special need to keep the object around.
釋放與此MediaPlayer對象關聯的資源。 在完成使用MediaPlayer之后,調用此方法是很好的做法。 特別是,當應用程序的Activity被暫停(其onPause()方法被調用)或停止(其onStop()方法被調用)時,應該調用此方法來釋放MediaPlayer對象,除非應用程序具有特殊的需要保留對象。
In addition to unnecessary resources (such as memory and instances of codecs) being held, failure to call this method immediately if a MediaPlayer object is no longer needed may also lead to continuous battery consumption for mobile devices, and playback failure for other applications if no multiple instances of the same codec are supported on a device. Even if multiple instances of the same codec are supported, some performance degradation(惡化、墮落) may be expected when unnecessary multiple instances are used at the same time.
除了不必要的資源(如內存和編解碼器實例)之外,如果一個MediaPlayer對象
不再被需要
,則無法立即調用此方法,也同樣可能會導致移動設備持續耗電;並且如果沒有"相同編解碼器的多個實例"在設備上得到支持,對於其他應用程序會播放失敗。 即使支持相同編解碼器的多個實例,當同時使用不必要的多個實例時,也可能會出現一些性能下降。
注意:調用release方法相當於將MediaPlayer所有設置、監聽、狀態等全部置空了,所以基本上不能再調用
MediaPlayer的任何方法。
public void release() {
stayAwake(false);
updateSurfaceScreenOn();
mOnPreparedListener = null;
mOnBufferingUpdateListener = null;
mOnCompletionListener = null;
mOnSeekCompleteListener = null;
mOnErrorListener = null;
mOnInfoListener = null;
mOnVideoSizeChangedListener = null;
mOnTimedTextListener = null;
if (mTimeProvider != null) {
mTimeProvider.close();
mTimeProvider = null;
}
mOnSubtitleDataListener = null;
_release();
}
private native void _release();
可能需要注冊的回調監聽 Callbacks
Applications may want to register for informational and error events
in order to be informed of some internal state update
and possible runtime errors
during playback or streaming. Registration for these events is done by
properly setting the appropriate listeners.
應用程序可能希望注冊信息和錯誤事件,以便在播放或流式傳輸期間通知一些內部狀態更新和可能的運行時錯誤。 這些事件的注冊是通過正確設置適當的監聽來完成的
如:
- setOnPreparedListener(OnPreparedListener)
- setOnCompletionListener(OnCompletionListener)
- setOnErrorListener(OnErrorListener)
- setOnSeekCompleteListener(OnSeekCompleteListener)
- setOnVideoSizeChangedListener(OnVideoSizeChangedListener)
- setOnBufferingUpdateListener(OnBufferingUpdateListener)
- setOnInfoListener(OnInfoListener)
In order to receive the
respective callback
associated with these listeners, applications are required to create MediaPlayer objects on a thread with its own Looper running (main UI thread by default has a Looper running).
為了接收與這些監聽器相關聯的相應回調,應用程序需要在其自己的Looper運行的線程上創建MediaPlayer對象(主UI線程默認情況下有一個Looper在運行)。
測試示例
public class MediaPlayerActivity extends ListActivity {
private MediaPlayer mediaPlayer;
private static final int STATE_CONTINUE = 1;//繼續播放
private static final int STATE_PAUSE = 2;//暫停播放
private boolean b = false;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String[] array = {"播放SD卡或網絡URL中的音樂(注意要申請權限)",
"以FileDescriptor形式播放assent或raw中的音樂。可以播放文件中指定的某一部分",
"暫停播放",
"停止播放",
"重新播放",};
setListAdapter(new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, new ArrayList<>(Arrays.asList(array))));
//不能在onCreate(甚至onResume)中獲取本ListView中的item,因為可能還沒有創建呢
getListView().getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
getListView().getViewTreeObserver().removeOnGlobalLayoutListener(this);//使用完之后必須立刻撤銷監聽
setPlayState(STATE_PAUSE);
}
});
initMediaPlayer();
}
private void initMediaPlayer() {
mediaPlayer = new MediaPlayer();
mediaPlayer.setOnCompletionListener(mp -> {
Toast.makeText(MediaPlayerActivity.this, "【onCompletion】", Toast.LENGTH_SHORT).show();
mp.reset();//MediaPlayer同時只能播放一個音樂文件,若要播另一個音樂文件,需先設置為初始狀態
setPlayEnable(true);
});
mediaPlayer.setOnPreparedListener(mp -> {
Log.i("bqt", "【onPrepared】");
mp.start();//只有准備好以后才能播放
});
mediaPlayer.setOnErrorListener((mp, what, extra) -> {
Toast.makeText(this, "【onError】" + what + " " + extra, Toast.LENGTH_SHORT).show();
return false;
});
mediaPlayer.setOnSeekCompleteListener(mp -> Log.i("bqt", "【onSeekComplete】"));
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mediaPlayer != null) {
mediaPlayer.stop();
mediaPlayer.release();//釋放播放器資源
mediaPlayer = null;
}
}
@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
switch (position) {
case 0:
playMusicFromSDCardOrUrl();
break;
case 1:
playMusicFromAssentOrRawByFD();
break;
case 2:
pause();
break;
case 3:
stopPlaying();
case 4:
replay();
break;
}
}
//******************************************************播放不同來源的音樂**********************************************
/**
* 播放SD卡或網絡URL中的音樂
*/
private void playMusicFromSDCardOrUrl() {
b = !b;
stopPlaying();
String path;
if (b) path = Environment.getExternalStorageDirectory() + File.separator + "voice/caravan.mp3";
else path = "http://www.baiqiantao.xyz/s10_bgm.ogg";
try {
mediaPlayer.setDataSource(path);//設置播放的數據源。參數可以是本地或網絡路徑
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);//設置音頻流的類型,不是必須的
mediaPlayer.prepareAsync();//For streams, you should call prepareAsync(), which returns immediately
//mediaPlayer.prepare();//For files, it is OK to call prepare(), which blocks until MediaPlayer is ready for playback.
setPlayEnable(false);//播放時將“播放”按鈕設置為不可點擊
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(this, "播放失敗!", Toast.LENGTH_SHORT).show();
}
}
/**
* 以FileDescriptor形式播放assent或raw中的音樂。可以播放文件中指定的某一部分
*/
private void playMusicFromAssentOrRawByFD() {
b = !b;
stopPlaying();
AssetFileDescriptor afd;
try {
if (b) afd = getAssets().openFd("voice/caravan_15s_59kb.mp3");
else afd = getResources().openRawResourceFd(R.raw.hellow_tomorrow);//一個10M+的超大文件
long offset = afd.getStartOffset(), length = afd.getDeclaredLength();
mediaPlayer.setDataSource(afd.getFileDescriptor(), offset + length / 2, length / 2);
mediaPlayer.prepareAsync();
setPlayEnable(false);
} catch (IOException e) {
e.printStackTrace();
}
}
//**************************************************暫停、停止、重播***************************************************
/**
* 暫停
*/
private void pause() {
if (mediaPlayer == null) return;
if (mediaPlayer.isPlaying()) {//只有播放器已初始化並且正在播放才可暫停
mediaPlayer.pause();
setPlayState(STATE_CONTINUE);
} else {
mediaPlayer.start();
setPlayState(STATE_PAUSE);
}
}
/**
* 停止
*/
private void stopPlaying() {
if (mediaPlayer == null) return;
if (mediaPlayer.isPlaying()) mediaPlayer.stop();
mediaPlayer.reset();
setPlayEnable(true);//播放時將“播放”按鈕設置為不可點擊
setPlayState(STATE_PAUSE);
}
/**
* 重播
*/
private void replay() {
if (mediaPlayer == null) return;
mediaPlayer.start();
mediaPlayer.seekTo(0);//重頭開始播放本音樂
setPlayState(STATE_PAUSE);
}
//******************************************************其他方法*******************************************************
/**
* 設置是否能點擊播放
*
* @param enable setEnabled的值
*/
private void setPlayEnable(boolean enable) {
getListView().getChildAt(0).setEnabled(enable);
getListView().getChildAt(1).setEnabled(enable);
}
/**
* 設置播放按鈕的播放狀態,進而控制顯示文案
*
* @param state 暫停或播放
*/
private void setPlayState(int state) {
SpannableStringBuilder mSSBuilder = new SpannableStringBuilder("");
if (state == STATE_CONTINUE) {
SpannableString mSString = new SpannableString("繼續播放");
ForegroundColorSpan colorSpan = new ForegroundColorSpan(Color.RED);
mSString.setSpan(colorSpan, 0, mSString.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
mSSBuilder.append(mSString);
} else if (state == STATE_PAUSE) {
SpannableString mSString = new SpannableString("暫停播放");
ForegroundColorSpan colorSpan = new ForegroundColorSpan(Color.BLUE);
mSString.setSpan(colorSpan, 0, mSString.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
mSSBuilder.append(mSString);
}
((TextView) getListView().getChildAt(2)).setText(mSSBuilder);
}
}
2017-7-12