MediaPlayer java层介绍

Android  源码分析  2023年7月7日 am8:09发布1年前 (2023)更新 91es.com站长
112 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.com
3、 本站内容: 部分来源于网络,仅供学习和参考,若侵权请留言
3、 本站申明: 个人流水账日记,内容并不保证有效

暂无评论

暂无评论...

随机推荐

JNI之函数介绍一

前言虽然jni.h中定义了很多函数,但也不是每个都需要用,这个主要是看需求。今天介绍一下常用jni函数,方便自己后续查阅。正文每个个函数可通过JNIEnv指针以固定偏移量进行访问。JNIEnv指针可指向存储全部JNI函数指针的结构。 如果要看全部的函数定义,可以看《NDK中jni.h头文件...

[摘]修改cmd编码格式

前言命令行显示中文乱码,大多是由于字符编码不匹配导致。有时候需要改变cmd的字符编码。本文摘抄,记录于此。好记性不如烂笔头。正文查看cmd的字符编码,在cmd中输入chcpchcp活动代码页: 936活动代码页936,指的就是GBK如果需要改变字符编码。# 设置编码格式为U...

许立志:我咽下一枚铁做的月亮

我咽下一枚铁做的月亮他们把它叫做螺丝 我咽下这工业的废水,失业的订单那些低于机台的青春早早夭亡 我咽下奔波,咽下流离失所咽下人行天桥,咽下长满水锈的生活 我再咽不下了所有我曾经咽下的现在都从喉咙汹涌而出在祖国的领土上铺成一首耻辱的诗 ​

JNI学习手册

前言之前初略的学习了一下JNI的使用,也做了对应的笔记。为了方便自己复现,这里就把所有文章整理在一起,方便自己查询。正文JNI之数据类型Java中调到Native方法传递的参数是Java类型,这些参数需要通过Dalvik虚拟机转换为JNI类型。具体请看《JNI之类型介绍》基本数据类型...

纪伯伦:岸边一捧沙

爱情的忧愁歌唱着,知识的忧愁谈论着,欲望的忧愁悄语着,贫穷的忧愁号哭着。但是,还有一种忧愁,比爱情更深沉,比知识更高贵,比欲望更有力,比贫穷更苦涩。不过,它哑然无声,眼睛像星星一样闪闪发亮。当你遇遭不幸,向邻居诉说时,你正将自己心灵的一部分托付给他。倘若他胸怀宽阔,他会感谢你;倘若他气量狭小,他会...

个人常用的ListView方法简介

前言项目中ListView还是比较常用的,ListView有些方法或者配置属性都是比较常用也比较容易忘记的。因此,今天抽空整(抄)理(袭)一下,以便查阅。PS: 现在RecyclerView比较多了好记性不如烂笔头正文停止滚动 private void stopListView...