MediaPlayer 状态机 API 详解 示例
简介
public class android.media.MediaPlayer extends Object implements VolumeAutomation
<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
状态机示意图官方解释
- 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状态了。
- 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异常,以避免编程错误。
- 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异常。
- 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方法,调整音频/视频的属性,如音量,播放时是否一直亮屏,循环播放等。
- 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()方法没有影响。
- 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()方法没有影响。
- 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()方法没有影响。
- 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()方法得到,它可以帮助如音乐播放器的应用程序不断更新播放进度
- 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
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】系列方法源码分析
- 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;
- 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;
- 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);
}
}
- 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】等方法源码分析
public void prepare() throws IOException, IllegalStateException {
_prepare();
scanInternalSubtitleTracks();
}
private native void _prepare() throws IOException, IllegalStateException;
public native void prepareAsync() throws IllegalStateException;
public void start() throws IllegalStateException {
baseStart();
stayAwake(true);
_start();
}
private native void _start() throws IllegalStateException;
public void pause() throws IllegalStateException {
stayAwake(false);
_pause();
}
private native void _pause() throws IllegalStateException;
public void stop() throws IllegalStateException {
stayAwake(false);
_stop();
}
private native void _stop() throws IllegalStateException;
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();
public native void seekTo(int msec) throws IllegalStateException;
【release】方法源码分析
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
- setOnPreparedListener(OnPreparedListener)
- setOnCompletionListener(OnCompletionListener)
- setOnErrorListener(OnErrorListener)
- setOnSeekCompleteListener(OnSeekCompleteListener)
- setOnVideoSizeChangedListener(OnVideoSizeChangedListener)
- setOnBufferingUpdateListener(OnBufferingUpdateListener)
- setOnInfoListener(OnInfoListener)
测试示例
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);
}
}
MediaPlayer 状态机 API 详解 示例的更多相关文章
- 百度地图API详解之事件机制,function“闭包”解决for循环和监听器冲突的问题:
原文:百度地图API详解之事件机制,function"闭包"解决for循环和监听器冲突的问题: 百度地图API详解之事件机制 2011年07月26日 星期二 下午 04:06 和D ...
- 【Unity编程】Unity中关于四元数的API详解
本文为博主原创文章,欢迎转载,请保留出处:http://blog.csdn.net/andrewfan Unity中关于四元数的API详解 Quaternion类 Quaternion(四元数)用于计 ...
- SDN 网络系统之 Mininet 与 API 详解
SDN 网络系统之 Mininet 与 API 详解 Mininet 是轻量级的软件定义网络系统平台,同时提供了对 OpenFlow 协议的支持.本文主要介绍了 Mininet 的相关概念与特性,并列 ...
- 【HANA系列】SAP HANA XS的JavaScript API详解
公众号:SAP Technical 本文作者:matinal 原文出处:http://www.cnblogs.com/SAPmatinal/ 原文链接:[HANA系列]SAP HANA XS的Java ...
- Java8学习笔记(五)--Stream API详解[转]
为什么需要 Stream Stream 作为 Java 8 的一大亮点,它与 java.io 包里的 InputStream 和 OutputStream 是完全不同的概念.它也不同于 StAX 对 ...
- DOM API详解
来源于:http://zxc0328.github.io/2016/01/23/learning-dom-part1/ https://zxc0328.github.io/2016/01/26/lea ...
- Lucene系列六:Lucene搜索详解(Lucene搜索流程详解、搜索核心API详解、基本查询详解、QueryParser详解)
一.搜索流程详解 1. 先看一下Lucene的架构图 由图可知搜索的过程如下: 用户输入搜索的关键字.对关键字进行分词.根据分词结果去索引库里面找到对应的文章id.根据文章id找到对应的文章 2. L ...
- [转]百度地图API详解之地图坐标系统
博客原文地址:http://www.jiazhengblog.com/blog/2011/07/02/289/ 我们都知道地球是圆的,电脑显示器是平的,要想让位于球面的形状显示在平面的显示器上就必然需 ...
- 【HANA系列】【第五篇】SAP HANA XS的JavaScript API详解
公众号:SAP Technical 本文作者:matinal 原文出处:http://www.cnblogs.com/SAPmatinal/ 原文链接:[HANA系列][第五篇]SAP HANA XS ...
随机推荐
- Node.js CVE-2017-14849复现(详细步骤)
0x00 前言 早上看Sec-news安全文摘的时候,发现腾讯安全应急响应中心发表了一篇文章,Node.js CVE-2017-14849 漏洞分析(https://security.tencent. ...
- JS 常用库汇总收集
本文不定期更新, 用于汇总记录一些看着 ok 的 JS 库. 库名 简介 项目地址 macy.js 仅 4 kb的 原生 流布局插件 http://macyjs.com/ Driver.js 仅 4 ...
- JavaSE基础之JDBC
JavaSE基础之JDBC 1.JDBC 的步骤: ①加载数据库驱动: a.MySQL:com.mysql.jdbc.Driver: b.SQLServer:com.microsoft.jdbc.sq ...
- C++11中的raw string literals
作为一名C++书看得少得可怜的新手,我一直没有勇气去系统地学习一下C++ 11添加的新特性.不过,平日里逛论坛,阅读大犇们的博客,倒是了解了一些.比如,这个帖子: 如何绕过g++ 4.8.1那个不能在 ...
- hdu 3061 最大权闭合子图
属于模板题吧... #include <cstdio> #include <cstring> #include <vector> #define min(a,b) ...
- 5、Redis中对Set类型的操作命令
写在前面的话:读书破万卷,编码如有神 -------------------------------------------------------------------- ------------ ...
- BZOJ 1191: [HNOI2006]超级英雄Hero 匈牙利算法
1191: [HNOI2006]超级英雄Hero Time Limit: 2 Sec Memory Limit: 256 MBSubmit: xxx Solved: 2xx 题目连接 http:/ ...
- opencv hog算子
梯度直方图特征(HOG) 是一种对图像局部重叠区域的密集型描述符, 它通过计算局部区域的梯度方向直方图来构成特征.Hog特征结合SVM分类器已经被广泛应用于图像识别中,尤其在行人检测中获得了极大的成功 ...
- Android实现两次按下返回键退出
@Override public boolean onKeyDown(int keyCode, KeyEvent event) { if(keyCode == KeyEvent.KEYCODE_BAC ...
- CSerialPort串口类最新修正版(解决关闭死锁问题)
这是一份优秀的类文件,好多的地方值得我们学习,具体在多线程,事件,自定义消息,类的封装方面等等.Remon提供的串口类网址为:http://codeguru.earthweb.com/network/ ...