MediaPlayer java层介绍

Android  源码分析  2023年7月7日 am8:09发布1年前 (2023)更新 91es.com站长
102 0 0

前言

Android中经常用MediaPlayer控制音频/视频文件和流的播放。虽然经常用,但没怎么看其源码,今天有空记录一下,方便自己查阅。

正文

本篇涉及的源码目录

frameworks\base\media\java\android\media\MediaPlayer.java

PS:只是简单的介绍

进入正题是先看看MediaPlayer中涉及的状态。(图片来源《Android音视频全面介绍与代码实践(一)》)

MediaPlayer java层介绍

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循环模式下,播放一次后表示播放结束

播放状态内容来之《 Android音视频开发系列-MediaPlayer源码解析

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;
    }
}

什么时候回调?

  1. 播放文件结束

  2. 播放文件出错,且错误没有被处理时

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();

参考文章

  1. Android源码

 历史上的今天

  1. 2021: 顾城:门前(0条评论)
  2. 2020: Handler内存泄漏之使用静态内部类并持有外部类的弱引用(0条评论)
  3. 2019: 叔本华:人生两大苦(0条评论)
版权声明 1、 本站名称: 91易搜
2、 本站网址: 91es.com3xcn.com
3、 本站内容: 部分来源于网络,仅供学习和参考,若侵权请留言
3、 本站申明: 个人流水账日记,内容并不保证有效

暂无评论

暂无评论...

随机推荐

[摘]List、Set、Map详解及区别

一、List接口List是一个继承于Collection的接口,即List是集合中的一种。List是有序的队列,List中的每一个元素都有一个索引;第一个元素的索引值是0,往后的元素的索引值依次+1。和Set不同,List中允许有重复的元素。实现List接口的集合主要有:ArrayList、Li...

[转]android NTP时间同步

推荐使用 极客导航:极客导航(http://www.91es.com/jike.html)相关文件:frameworks/base/services/java/com/android/server/SystemServer.javaframeworks/base/services/j...

[摘]强引用,软引用,弱引用等简介

前言之前也了解过用过,但还是忘了。今天有空就整(摘)理(抄)于此,方便自己查阅。好记性不如烂笔头正文从Java 1.2 开始,就引入了4中引用,强弱排序:强引用 > 软引用 > 弱引用 > 虚引用强引用、软引用、弱引用、虚引用强引用(StrongReference...

[摘]Android稳定性(二)bootup fail

前言之前我也有整理相关的,但没有摘抄的这篇文章那么详细,因此这里摘抄于此,方便自己查阅。本文摘抄,感谢作者分析。好记性不如烂笔头正文下面就简单介绍一下Android系统启动流程。BootLoader启动开机供电,硬件电路会产生一个确定的复位时序用于硬件启动顺序,直至最后CPU启动;C...

Adapter的getView方法返回了null

前言这个是很简单的问题,就是适配器(比如BaseAdapter)中的getView()返回了null,是如下: @Override public View getView(int position, View convertView, ViewGroup parent) { ...

android导出ANR日志

查看是否存在anr日志#查询ANR目录是否存在adb shell ls data/anr/#下面是ANR中的问题件binderinfomediaplayerinfomtk_traces.txtnative1.txtnative1_1.txtnative2.txtnat...