MediaPlayer 狀態機 API 詳解 示例

簡介

   
   
   
   
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.
一、當一個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).
二、 在通常狀況下,因爲種種緣由一些播放控制操做可能會失敗,如不支持的音頻/視頻格式,缺乏隔行掃描的音頻/視頻,分辨率過高,流超時等緣由,等等。所以,錯誤報告和恢復在這種狀況下是很是重要的。有時,因爲編程錯誤,在處於無效狀態的狀況下調用了一個播放控制操做也可能發生(錯誤)。在全部這些錯誤條件下,內部的播放引擎會調用一個由客戶端程序員提供的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.
三、 調用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.
四、在開始播放以前,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.
五、要開始播放,必須調用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.
六、播放能夠被暫停、中止,而且能夠調整當前播放位置。能夠經過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.
七、調用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).
八、調用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.
九、當播放到流的末尾時,播放就完成了。
  • 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】系列方法源碼分析

一、提供 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
相關源碼
     
     
     
     
  1. // 本地文件
  2. // 可見,最終也是經過FileDescriptor設置的
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); 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;

二、提供 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;

三、提供 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
相關源碼
   
   
   
   
  1. // 文件
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); }}

四、提供 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】等方法源碼分析

一、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;

二、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;

三、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;

四、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;

五、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()來從新初始化它。
    
    
    
    
  1. synchronized (mIndexTrackPairs) {
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); mIndexTrackPairs.clear(); mInbandTrackIndices.clear(); };}private native void _reset();

六、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


相關文章
相關標籤/搜索