首页 > 代码库 > MediaPlayer 状态机 API 详解 示例

MediaPlayer 状态机 API 详解 示例

简介

  1. public class android.media.MediaPlayer extends Object implements VolumeAutomation
可能需要的权限:
One may need to declare a corresponding(相应) WAKE_LOCK permission <uses-permission> element.
  1. <uses-permission android:name="android.permission.WAKE_LOCK" />
  2. <uses-permission android:name="android.permission.INTERNET" />
  3. <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 one rrorListener.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 one rrorListener.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 one rrorListener.onError() method if an one rrorListener 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 one rrorListener 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 NameValid SatesInvalid StatesComments
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
setVolumeSuccessful invoke of this method does not change the state.
setAudioAttributesSuccessful 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.
setAudioSessionIdThis 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.
attachAuxEffectThis 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.
prepareAsyncSuccessful 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.
startSuccessful 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.
stopSuccessful 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.
getTrackInfoSuccessful 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.
setAuxEffectSendLevelThis 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
相关源码
  1. public void setDataSource(String path) throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
  2. setDataSource(path, null, null);
  3. }
  4. public void setDataSource(String path, Map<String, String> headers)
  5. throws IOException, IllegalArgumentException, SecurityException, IllegalStateException{
  6. String[] keys = null;
  7. String[] values = null;
  8. if (headers != null) {
  9. keys = new String[headers.size()];
  10. values = new String[headers.size()];
  11. int i = 0;
  12. for (Map.Entry<String, String> entry: headers.entrySet()) {
  13. keys[i] = entry.getKey();
  14. values[i] = entry.getValue();
  15. ++i;
  16. }
  17. }
  18. setDataSource(path, keys, values);
  19. }
  20. private void setDataSource(String path, String[] keys, String[] values)
  21. throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
  22. final Uri uri = Uri.parse(path);
  23. final String scheme = uri.getScheme();
  24. if ("file".equals(scheme)) path = uri.getPath();// 本地文件
  25. else if (scheme != null) {
  26. // handle non-file sources。对于http文件,就是通过这种方式将网上的文件搞下来后设置给MediaPlayer
  27. nativeSetDataSource(MediaHTTPService.createHttpServiceBinderIfNecessary(path), path, keys, values);
  28. return;
  29. }
  30. final File file = new File(path);
  31. if (file.exists()) {
  32. FileInputStream is = new FileInputStream(file);
  33. FileDescriptor fd = is.getFD();
  34. setDataSource(fd);// 可见,最终也是通过FileDescriptor设置的
  35. is.close();
  36. } else throw new IOException("setDataSource failed.");
  37. }
  38. private native void nativeSetDataSource(IBinder httpServiceBinder, String path, String[] keys, String[] values)
  39. 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.
相关源码
  1. public void setDataSource(FileDescriptor fd) throws IOException, IllegalArgumentException, IllegalStateException {
  2. setDataSource(fd, 0, 0x7ffffffffffffffL);// intentionally故意地 less than LONG_MAX
  3. }
  4. public void setDataSource(FileDescriptor fd, long offset, long length) throws IOException, IllegalArgumentException, IllegalStateException {
  5. _setDataSource(fd, offset, length);
  6. }
  7. public void setDataSource(@NonNull AssetFileDescriptor afd) throws IOException, IllegalArgumentException, IllegalStateException {
  8. Preconditions.checkNotNull(afd);
  9. // Note: using getDeclaredLength so that our behavior is the same as previous versions when the content provider is returning a full file.
  10. if (afd.getDeclaredLength() < 0) setDataSource(afd.getFileDescriptor());
  11. else setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getDeclaredLength());
  12. }
  13. 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
相关源码
  1. public void setDataSource(@NonNull Context context, @NonNull Uri uri)
  2. throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
  3. setDataSource(context, uri, null);
  4. }
  5. public void setDataSource(@NonNull Context context, @NonNull Uri uri, @Nullable Map<String, String> headers)
  6. throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
  7. final ContentResolver resolver = context.getContentResolver();
  8. final String scheme = uri.getScheme();
  9. if (ContentResolver.SCHEME_FILE.equals(scheme)) {
  10. setDataSource(uri.getPath());// 文件
  11. return;
  12. } else if (ContentResolver.SCHEME_CONTENT.equals(scheme) && Settings.AUTHORITY.equals(uri.getAuthority())) {
  13. // Try cached ringtone first since the actual provider may not be encryption aware, or it may be stored on CE media storage
  14. // 先尝试缓存铃声,因为实际的提供者可能没有被加密,或者可能存储在CE媒体存储上
  15. final int type = RingtoneManager.getDefaultType(uri);
  16. final Uri cacheUri = RingtoneManager.getCacheForType(type);
  17. final Uri actualUri = RingtoneManager.getActualDefaultRingtoneUri(context, type);
  18. if (attemptDataSource(resolver, cacheUri)) return;
  19. else if (attemptDataSource(resolver, actualUri)) return;
  20. else setDataSource(uri.toString(), headers);
  21. } else {
  22. // Try requested Uri locally first, or fallback to media server。先尝试本地请求Uri,或者回退到媒体服务器
  23. if (attemptDataSource(resolver, uri)) return;
  24. else setDataSource(uri.toString(), headers);
  25. }
  26. }

4、提供 MediaDataSource
  • void setDataSource(MediaDataSource dataSource)    Sets the data source (MediaDataSource) to use.
    • MediaDataSource: the MediaDataSource for the media you want to play
相关源码
  1. public void setDataSource(MediaDataSource dataSource) throws IllegalArgumentException, IllegalStateException {
  2. _setDataSource(dataSource);
  3. }
  4. 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
  1. public void prepare() throws IOException, IllegalStateException {
  2. _prepare();
  3. scanInternalSubtitleTracks();
  4. }
  5. private native void _prepare() throws IOException, IllegalStateException;
  6. 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
  1. public void start() throws IllegalStateException {
  2. baseStart();
  3. stayAwake(true);
  4. _start();
  5. }
  6. 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.
  1. public void pause() throws IllegalStateException {
  2. stayAwake(false);
  3. _pause();
  4. }
  5. 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.
  1. public void stop() throws IllegalStateException {
  2. stayAwake(false);
  3. _stop();
  4. }
  5. 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()来重新初始化它。
  1. public void reset() {
  2. mSelectedSubtitleTrackIndex = -1;
  3. synchronized(mOpenSubtitleSources) {
  4. for (final InputStream is: mOpenSubtitleSources) {
  5. try {
  6. is.close();
  7. } catch (IOException e) {
  8. }
  9. }
  10. mOpenSubtitleSources.clear();
  11. }
  12. if (mSubtitleController != null) mSubtitleController.reset();
  13. if (mTimeProvider != null) {
  14. mTimeProvider.close();
  15. mTimeProvider = null;
  16. }
  17. stayAwake(false);
  18. _reset();
  19. // make sure none of the listeners get called anymore
  20. if (mEventHandler != null) mEventHandler.removeCallbacksAndMessages(null);
  21. synchronized (mIndexTrackPairs) {
  22. mIndexTrackPairs.clear();
  23. mInbandTrackIndices.clear();
  24. };
  25. }
  26. 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. 
  1. 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的任何方法。
  1. public void release() {
  2. stayAwake(false);
  3. updateSurfaceScreenOn();
  4. mOnPreparedListener = null;
  5. mOnBufferingUpdateListener = null;
  6. mOnCompletionListener = null;
  7. mOnSeekCompleteListener = null;
  8. mOnErrorListener = null;
  9. mOnInfoListener = null;
  10. mOnVideoSizeChangedListener = null;
  11. mOnTimedTextListener = null;
  12. if (mTimeProvider != null) {
  13. mTimeProvider.close();
  14. mTimeProvider = null;
  15. }
  16. mOnSubtitleDataListener = null;
  17. _release();
  18. }
  19. 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在运行)。

测试示例

  1. public class MediaPlayerActivity extends ListActivity {
  2. private MediaPlayer mediaPlayer;
  3. private static final int STATE_CONTINUE = 1;//继续播放
  4. private static final int STATE_PAUSE = 2;//暂停播放
  5. private boolean b = false;
  6. protected void onCreate(Bundle savedInstanceState) {
  7. super.onCreate(savedInstanceState);
  8. String[] array = {"播放SD卡或网络URL中的音乐(注意要申请权限)",
  9. "以FileDescriptor形式播放assent或raw中的音乐。可以播放文件中指定的某一部分",
  10. "暂停播放",
  11. "停止播放",
  12. "重新播放",};
  13. setListAdapter(new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, new ArrayList<>(Arrays.asList(array))));
  14. //不能在onCreate(甚至onResume)中获取本ListView中的item,因为可能还没有创建呢
  15. getListView().getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
  16. @Override
  17. public void onGlobalLayout() {
  18. getListView().getViewTreeObserver().removeOnGlobalLayoutListener(this);//使用完之后必须立刻撤销监听
  19. setPlayState(STATE_PAUSE);
  20. }
  21. });
  22. initMediaPlayer();
  23. }
  24. private void initMediaPlayer() {
  25. mediaPlayer = new MediaPlayer();
  26. mediaPlayer.setOnCompletionListener(mp -> {
  27. Toast.makeText(MediaPlayerActivity.this, "【onCompletion】", Toast.LENGTH_SHORT).show();
  28. mp.reset();//MediaPlayer同时只能播放一个音乐文件,若要播另一个音乐文件,需先设置为初始状态
  29. setPlayEnable(true);
  30. });
  31. mediaPlayer.setOnPreparedListener(mp -> {
  32. Log.i("bqt", "【onPrepared】");
  33. mp.start();//只有准备好以后才能播放
  34. });
  35. mediaPlayer.setOnErrorListener((mp, what, extra) -> {
  36. Toast.makeText(this, "【onError】" + what + " " + extra, Toast.LENGTH_SHORT).show();
  37. return false;
  38. });
  39. mediaPlayer.setOnSeekCompleteListener(mp -> Log.i("bqt", "【onSeekComplete】"));
  40. }
  41. @Override
  42. protected void onDestroy() {
  43. super.onDestroy();
  44. if (mediaPlayer != null) {
  45. mediaPlayer.stop();
  46. mediaPlayer.release();//释放播放器资源
  47. mediaPlayer = null;
  48. }
  49. }
  50. @Override
  51. protected void onListItemClick(ListView l, View v, int position, long id) {
  52. switch (position) {
  53. case 0:
  54. playMusicFromSDCardOrUrl();
  55. break;
  56. case 1:
  57. playMusicFromAssentOrRawByFD();
  58. break;
  59. case 2:
  60. pause();
  61. break;
  62. case 3:
  63. stopPlaying();
  64. case 4:
  65. replay();
  66. break;
  67. }
  68. }
  69. //******************************************************播放不同来源的音乐**********************************************
  70. /**
  71. * 播放SD卡或网络URL中的音乐
  72. */
  73. private void playMusicFromSDCardOrUrl() {
  74. b = !b;
  75. stopPlaying();
  76. String path;
  77. if (b) path = Environment.getExternalStorageDirectory() + File.separator + "voice/caravan.mp3";
  78. else path = "http://www.baiqiantao.xyz/s10_bgm.ogg";
  79. try {
  80. mediaPlayer.setDataSource(path);//设置播放的数据源。参数可以是本地或网络路径
  81. mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);//设置音频流的类型,不是必须的
  82. mediaPlayer.prepareAsync();//For streams, you should call prepareAsync(), which returns immediately
  83. //mediaPlayer.prepare();//For files, it is OK to call prepare(), which blocks until MediaPlayer is ready for playback.
  84. setPlayEnable(false);//播放时将“播放”按钮设置为不可点击
  85. } catch (Exception e) {
  86. e.printStackTrace();
  87. Toast.makeText(this, "播放失败!", Toast.LENGTH_SHORT).show();
  88. }
  89. }
  90. /**
  91. * 以FileDescriptor形式播放assent或raw中的音乐。可以播放文件中指定的某一部分
  92. */
  93. private void playMusicFromAssentOrRawByFD() {
  94. b = !b;
  95. stopPlaying();
  96. AssetFileDescriptor afd;
  97. try {
  98. if (b) afd = getAssets().openFd("voice/caravan_15s_59kb.mp3");
  99. else afd = getResources().openRawResourceFd(R.raw.hellow_tomorrow);//一个10M+的超大文件
  100. long offset = afd.getStartOffset(), length = afd.getDeclaredLength();
  101. mediaPlayer.setDataSource(afd.getFileDescriptor(), offset + length / 2, length / 2);
  102. mediaPlayer.prepareAsync();
  103. setPlayEnable(false);
  104. } catch (IOException e) {
  105. e.printStackTrace();
  106. }
  107. }
  108. //**************************************************暂停、停止、重播***************************************************
  109. /**
  110. * 暂停
  111. */
  112. private void pause() {
  113. if (mediaPlayer == null) return;
  114. if (mediaPlayer.isPlaying()) {//只有播放器已初始化并且正在播放才可暂停
  115. mediaPlayer.pause();
  116. setPlayState(STATE_CONTINUE);
  117. } else {
  118. mediaPlayer.start();
  119. setPlayState(STATE_PAUSE);
  120. }
  121. }
  122. /**
  123. * 停止
  124. */
  125. private void stopPlaying() {
  126. if (mediaPlayer == null) return;
  127. if (mediaPlayer.isPlaying()) mediaPlayer.stop();
  128. mediaPlayer.reset();
  129. setPlayEnable(true);//播放时将“播放”按钮设置为不可点击
  130. setPlayState(STATE_PAUSE);
  131. }
  132. /**
  133. * 重播
  134. */
  135. private void replay() {
  136. if (mediaPlayer == null) return;
  137. mediaPlayer.start();
  138. mediaPlayer.seekTo(0);//重头开始播放本音乐
  139. setPlayState(STATE_PAUSE);
  140. }
  141. //******************************************************其他方法*******************************************************
  142. /**
  143. * 设置是否能点击播放
  144. *
  145. * @param enable setEnabled的值
  146. */
  147. private void setPlayEnable(boolean enable) {
  148. getListView().getChildAt(0).setEnabled(enable);
  149. getListView().getChildAt(1).setEnabled(enable);
  150. }
  151. /**
  152. * 设置播放按钮的播放状态,进而控制显示文案
  153. *
  154. * @param state 暂停或播放
  155. */
  156. private void setPlayState(int state) {
  157. SpannableStringBuilder mSSBuilder = new SpannableStringBuilder("");
  158. if (state == STATE_CONTINUE) {
  159. SpannableString mSString = new SpannableString("继续播放");
  160. ForegroundColorSpan colorSpan = new ForegroundColorSpan(Color.RED);
  161. mSString.setSpan(colorSpan, 0, mSString.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
  162. mSSBuilder.append(mSString);
  163. } else if (state == STATE_PAUSE) {
  164. SpannableString mSString = new SpannableString("暂停播放");
  165. ForegroundColorSpan colorSpan = new ForegroundColorSpan(Color.BLUE);
  166. mSString.setSpan(colorSpan, 0, mSString.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
  167. mSSBuilder.append(mSString);
  168. }
  169. ((TextView) getListView().getChildAt(2)).setText(mSSBuilder);
  170. }
  171. }
2017-7-12


null


MediaPlayer 状态机 API 详解 示例