MediaPlayer java层介绍

Android  源码分析  2023年7月7日 am8:09发布1年前 (2023)更新 城堡大人
137 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、 本站网址: https://www.91es.com/
3、 本站部分文章来源于网络,仅供学习与参考,如有侵权,请留言

暂无评论

暂无评论...

随机推荐

Android 换肤setFactory2时出现No field mFactorySet in class问题解决

前言Android换肤插件开发时,需要依赖LayoutInflater.Factory类,在Android P和 之前版本都测试ok,但放在Android Q(10)上测试就失效了。本文记录一下失效的原因和解决的方法。正文在Android Q之前版本,通过如下代码可以设置的 try {...

王小波:用一生来学习艺术

我念过文科,也念过理科。在课堂上听老师提到艺术这个词,还是理科的老师次数更多:化学老师说,做实验有实验艺术;计算机老师说,编程序有编程艺术。老师们说,怎么做对是科学,怎么做好则是艺术;前者有判断真伪的法则,后者则没有;艺术的真谛就是要叫人感到好,甚至是完美无缺;传授科学知识就是告诉你这些法则,而艺术...

季羡林:论坏人

积将近90年的经验,我深知世界上确实是有坏人的。乍看上去,这个看法的智商只能达到小学一年级的水平。这就等于说“每个人都必须吃饭”那样既真实又平庸。可是事实上我顿悟到这个真理,是经过了长时间的观察与思考的。我从来就不是性善说的信徒,毋宁说我是倾向性恶说的。古书上说“天命之谓性”,“性”就是我们现在...

rc文件规则简介

前言上一篇大致的走了一下init.cpp的流程,其中很重要的一环就是解析.rc文件,在介绍init.rc之前先学习一下rc文件的语法。内容主要来源参考文正文什么是rc文件在进入init.rc之前,我们需要先了解一下.rc文件。rc文件,是用Android Init Language...

Android系统切换语言后,Activity中的文本没有改变

前言切换语言后,项目中的APP中的文本没有根据系统的语言改变而改变。这个是个小问题,但还是记录一下,方便自己查阅。正文隐藏内容!付费阅读后才能查看!¥1 ¥3多个隐藏块只需支付一次付费阅读参考文章《[摘]切换多国语言导致Fragment被回收,出现切换错乱》

Google Nexus 7 adb devices时出现 ???????????? no permissions

我在使用Nexus 7平板时,在使用adb devices后出现如下:List of devices attached???????????? no permissions同时在DDMS中显示设备名也显示????????????,也无法显示进程名,无法查看log。解决方法如下:...