文章目录
Android中经常用MediaPlayer控制音频/视频文件和流的播放。虽然经常用,但没怎么看其源码,今天有空记录一下,方便自己查阅。
正文
本篇涉及的源码目录
frameworks\base\media\java\android\media\MediaPlayer.java
进入正题是先看看MediaPlayer中涉及的状态。(图片来源《》)

demo
下面是播放音频demo
初始化和设置需要的监听
if (null == mMediaPlayer) { mMediaPlayer = new MediaPlayer(); mMediaPlayer.setOnCompletionListener(this);//播放完监听 mMediaPlayer.setOnErrorListener(this);//播放异常 mMediaPlayer.setOnInfoListener(this);//播放状态 mMediaPlayer.setOnPreparedListener(this);//准备好状态 mMediaPlayer.setOnSeekCompleteListener(this);//seek完成 } try { mMediaPlayer.setDataSource("/storage/udisk0/黄昏-周传雄.flac"); //mMediaPlayer.prepareAsync(); mMediaPlayer.prepare(); } catch (IOException e) { throw new RuntimeException(e); }
监听只附上一个,需要在准备好后开启播放
@Override public void onPrepared(MediaPlayer mp) { //加载完就开启播放 mMediaPlayer.start(); }
注销
if (null != mMediaPlayer) { mMediaPlayer.reset(); mMediaPlayer.release(); mMediaPlayer = null; }
MediaPlayer状态
Idle 播放器实例化或调用reset()后的状态 End 播放器release()调用之后的状态 Error 播放器出错的状态,一般情况是在调用方法和初始化错误时发生 Initialized 播放器调用setDataSource()时 preparing 播放器调用prepareAsync()或prepare()时的状态,时间较短 prepared 播放器调用prepareAsync()或prepare()时经历preparing Started 播放器调用start()方法后 Paused 播放器调用pause()方法后 Stopped 播放器调用stop()方法后 PlaybackCompleted 在播放器没有设置Loop循环模式下,播放一次后表示播放结束
播放状态内容来之《》
MediaPlayer.java
public class MediaPlayer extends PlayerBase implements SubtitleController.Listener , VolumeAutomation, AudioRouting{ //略 }
从上面知道MediaPlayer继承于PlayerBase,并实现如下接口
SubtitleController.Listener 字幕控制监听 VolumeAutomation 音量调节[字面意思,具体没深究] AudioRouting 音频控制[字面意思,具体没深究]
而PlayerBase是常用播放状态封装,具体可以自己看。
在看源码时一般会看静态代码块是否存在,然后是构造函数,再后来就是demo中调用的方法。
每个人可能不一样,勿喷!
static {}
MediaPlayer.java中有静态代码块
static { //加载libmedia_jni.so System.loadLibrary("media_jni"); native_init(); }
MediaPlayer()
public MediaPlayer() { //初始化父类 super(new AudioAttributes.Builder().build(), AudioPlaybackConfiguration.PLAYER_TYPE_JAM_MEDIAPLAYER); //初始化Looper和EventHandler Looper looper; if ((looper = Looper.myLooper()) != null) { mEventHandler = new EventHandler(this, looper); } else if ((looper = Looper.getMainLooper()) != null) { mEventHandler = new EventHandler(this, looper); } else { mEventHandler = null; } //本地方法 native_setup(new WeakReference<MediaPlayer>(this)); //父类中实现的 baseRegisterPlayer(); }
不纠结细节,下面是设置监听
mMediaPlayer.setOnCompletionListener(this);//播放完监听 mMediaPlayer.setOnErrorListener(this);//播放异常 mMediaPlayer.setOnInfoListener(this);//播放状态 mMediaPlayer.setOnPreparedListener(this);//准备好状态 mMediaPlayer.setOnSeekCompleteListener(this);//seek完成
监听的流程都一样,这里以setOnCompletionListener为例。
setOnCompletionListener
public void setOnCompletionListener(OnCompletionListener listener){ mOnCompletionListener = listener; } private OnCompletionListener mOnCompletionListener;
被调用的地方在EventHandler的handleMessage中
public void handleMessage(Message msg) { switch(msg.what) { case MEDIA_PLAYBACK_COMPLETE:{ //播放结束 mOnCompletionInternalListener.onCompletion(mMediaPlayer); OnCompletionListener onCompletionListener = mOnCompletionListener; if (onCompletionListener != null) onCompletionListener.onCompletion(mMediaPlayer); } stayAwake(false); return; case MEDIA_ERROR: boolean error_was_handled = false; OnErrorListener onErrorListener = mOnErrorListener; if (onErrorListener != null) { error_was_handled = onErrorListener.onError(mMediaPlayer, msg.arg1, msg.arg2); } { //如果播放错误没有处理,就返回播放结束 mOnCompletionInternalListener.onCompletion(mMediaPlayer); OnCompletionListener onCompletionListener = mOnCompletionListener; if (onCompletionListener != null && ! error_was_handled) { onCompletionListener.onCompletion(mMediaPlayer); } } stayAwake(false); return; } }
什么时候回调?
播放文件结束
播放文件出错,且错误没有被处理时
mEventHandler的消息最终是从postEventFromNative()来的
private static void postEventFromNative(Object mediaplayer_ref,
int what, int arg1, int arg2, Object obj){
final MediaPlayer mp = (MediaPlayer)((WeakReference)mediaplayer_ref).get();
if (mp == null) {
return;
}
if (mp.mEventHandler != null) {
Message m = mp.mEventHandler.obtainMessage(what, arg1, arg2, obj);
mp.mEventHandler.sendMessage(m);
}
}
从名字看就知道来自JNI层,暂不深入,后续分析。
setDataSource()
setDataSource重载了很多方法,这里以demo中调用的为例
public void setDataSource(String path){
setDataSource(path, null, null);
}
绕了好几个setDataSource()方法进行封装,最终调用的是
private native void _setDataSource(FileDescriptor fd, long offset, long length);
也就是有进入JNI,这里先跳过。
prepare() 或prepareAsync()
//对于文件,可以调用prepare(),它会阻塞直到MediaPlayer准备好播放。 prepare();
//对于流,应该调用prepareSync(),它会立即返回,而不是阻塞直到缓冲了足够的数据。 prepareAsync();
播放本地文件(文件可能很大),为了快速播放会优先考虑prepareAsync(),但系统IO高的时候就可能出现卡顿问题。
只能说各有千秋,看出现的情况而定。
prepare()
public void prepare() {
_prepare();
}
private native void _prepare();
prepareAsync()
public native void prepareAsync();
最终还是进入JNI了。
start()
demo中在onPrepared()回调后开启播放的
public void onPrepared(MediaPlayer mp) {
//加载完就开启播放
mMediaPlayer.start();
}
public void start() throws IllegalStateException {
final int delay = getStartDelayMs();
//是否需要延迟播放,可以通过setStartDelayMs()设置
//默认delay=0;
if (delay == 0) {
startImpl();
} else {
new Thread() {
public void run() {
try {
Thread.sleep(delay);
} catch (InterruptedException e) {
e.printStackTrace();
}
//延迟后恢复默认值0
baseSetStartDelayMs(0);
try {
startImpl();
} catch (IllegalStateException e) {
}
}
}.start();
}
}
private void startImpl() {
//更新播放状态和判断是否需要mute
baseStart();
_start();
}
private native void _start() throws IllegalStateException;
reset()
public void reset() {
stayAwake(false);
//本地方法
_reset();
//移除Handler消息
if (mEventHandler != null) {
mEventHandler.removeCallbacksAndMessages(null);
}
}
private native void _reset();
reset()就是清除上一个所有状态,此时为Idle。上下曲时,如果复用MediaPlayer,一定要reset()一下。
release()
public void release() {
//调用PlayerBase释放相关资源
baseRelease();
//监听全部置为null
mOnPreparedListener = null;
mOnBufferingUpdateListener = null;
mOnCompletionListener = null;
mOnSeekCompleteListener = null;
mOnErrorListener = null;
mOnInfoListener = null;
mOnVideoSizeChangedListener = null;
mOnTimedTextListener = null;
//本地方法
_release();
}
private native void _release();
如果是停止播放,就需要释放资源。
由于release中没有清除Handler消息,因此reset()和release()需要配合使用。
mMediaPlayer.reset(); mMediaPlayer.release();
参考文章
《
![[摘]AudioTrack简单简介之二](https://www.biumall.com/wp-content/themes/BiuX/assets/images/random/0.webp)