当调用 paused()
时,MediaPlayer 瞬间 从Started变为Prepared状态。
但是在播放器内部,这个过程是异步的。
如果在Paused状态下调用 start()
, playback()
会恢复之前暂停时的位置,接着开始播放,这时候又变成了 Started状态
当然了,如果 已经是Paused状态,调用paused() 是没有任何用处的。
当调用stop()
时,MediaPlayer无论处于哪种状态都会进入Stopped状态
如果已经处于Stopped状态时,调用 stop()是没有任何用处的
可以通过 getCurrentPosition()
获取播放的位置
当MediaPlayer播放结束时:
- 如果设置了setLooping(true)
MediaPlayer依然处于Started状态
- 如果设置了setLooping(false),并且事先重写过
setOnCompletionListener
播放器会回调上面那个接口的 onCompletion()
,然后进入 PlaybackCompleted(播放完成)状态。当处于这个状态时,调用start()
将重启播放器从头开始播放数据
========================================================================================
可以从时序图中看出一下步骤:
-
通过
getService()
从 ServiceManager获取对应的MediaPlayerService -
调用
native_setup()
创建播放器 -
调用
setDataSource()
把URL地址传入低层 -
通过
setDisplay()
传入 SurfaceHolder,以便将解码出的数据放到SurfaceHolder中的Surface -
显示在SurfaceView上
当应用层调用下面代码时
MediaPlayer.create(this, Uri.parse(" url") );
create会这样做:
//create源码
public static MediaPlayer create(Context context, Uri uri, SurfaceHolder holder,
AudioAttributes audioAttributes, int audioSessionId) {
try {
MediaPlayer mp = new MediaPlayer();
//声音处理,如果为空就new一个
final AudioAttributes aa = audioAttributes != null ? audioAttributes :
new AudioAttributes.Builder().build();
//设置音频属性
mp.setAudioAttributes(aa);
//设置声音会话Id,视频和音频是分开渲染的
mp.setAudioSessionId(audioSessionId);
//从这里setDataSource,传入uri统一资源标志符
mp.setDataSource(context, uri);
//判断SurfaceHolder是否为空,这是一个控制器,用来操作Surface,处理它在Canvas上作画的效果和动画,控制表面、大小、像素
if (holder != null) {
//给Surface设置一个控制器
mp.setDisplay(holder);
}
//开始准备
mp.prepare();
return mp;
} catch (IOException ex) {
Log.d(TAG, “create failed:”, ex);
// fall through
} catch (IllegalArgumentException ex) {
Log.d(TAG, “create failed:”, ex);
// fall through
} catch (SecurityException ex) {
Log.d(TAG, “create failed:”, ex);
// fall through
}
return null;
}
create(context,uri)总结
通过MediaPlayer.create(Context,uri)
,内部会new出一个 MediaPlayer对象,并为它设置一些配置,这个时候已经经历了IDLE->Initalized状态
并setDataSource,做好prepare的动作,只需要调用start()
,就能开始播放数据了。
MediaPlayer可以通过new的方式创建(要手动调用setDataSource()),也可以通过create方式创建(不用手动调用了),我们来看看MediaPlayer在构造的时候做了什么:
public MediaPlayer() {
super(new AudioAttributes.Builder().build(),
AudioPlaybackConfiguration.PLAYER_TYPE_JAM_MEDIAPLAYER);
//定义一个Looper
Looper looper;
if ((looper = Looper.myLooper()) != null) {
//如果myLooper不为空就赋值给myLooper,并实例化一个EventHandler对象
mEventHandler = new EventHandler(this, looper);
} else if ((looper = Looper.getMainLooper()) != null) {
//如果主线程Looper不为空,就赋值给looper,并实例化一个EventHandler对象
mEventHandler = new EventHandler(this, looper);
} else {
mEventHandler = null;
}
//时间数据容器,一般provider都是和数据联系起来的,如ContentProvider、VideoProvider
mTimeProvider = new TimeProvider(this);
mOpenSubtitleSources = new Vector();
//通过Binder机制获取到原生的OPS服务
IBinder b = ServiceManager.getService(Context.APP_OPS_SERVICE);
mAppOps = IAppOpsService.Stub.asInterface(b);
//通过之后获取到的服务,就能使用native_setup方法开始创建MediaPlayer了,而且还是软引用,
//接下来就是放在C++层去创建MediaPlayer了
native_setup(new WeakReference(this));
}
MediaPlayer构造函数总结
-
定义Looper
-
初始化一个TimeProvider
-
通过Binder获取原生
ops服务
-
进入c++层创建一个弱引用的MediaPlayer
所以接下来我们就进入C++层,去分析Native层如何创建MediaPlayer的了。
在分析native_setup
之前,请注意 .so文件
一般都是在静态代码块中加载的。在MediaPlayer中有一段静态代码块,用于加载和链接库文件media_jni.so
:
static {
System.loadLibrary(“media_jni”);
native_init();
}
所以我们要去查看 "media_jni"这个c代码,Android Studio是看不到JNI文件的
我们要去Android源码大全这上面去找,代码是通过 loadLibrary(xxx_jni)
加载的,那么我的搜索关键字就是 libxxx_jni
。
然后我们就找到目录/frameworks/base/media/jni下的android_media_MediaPlayer.cpp文件,从它来分析,因为它的第一个函数 android_media_MediaPlayer_init就是从Java静态代码快调过来的nativie_init
:
//android_media_MediaPlayer.cpp
static void
android_media_MediaPlayer_native_init(JNIEnv *env) {
//类的句柄
jclass clazz;
//这里通过Native层调用Java层,获取MediaPlayer对象
clazz = env -> FindClass(“android/media/MediaPlayer”);
if (clazz == NULL) {
//判空
return;
}
//获取成员变量mNativeContext,它是一个long型,实际对应的是一个内存地址
fields.context = env -> GetFieldID(clazz, “mNativeContext”, “J”);
if (fields.context == NULL) {
return;
}
fields.post_event = env -> GetStaticMethodID(clazz, “postEventFromNative”,
“(Ljava/lang/Object;IIILjava/lang/Object;)V”);
if (fields.post_event == NULL) {
return;
}
…
}
init方法总结:
-
被通知init后,从Java层获取一个MediaPlayer
-
从MediaPlayer获取一些变量
-
从MediaPlayer获取
postEventFromNative()
并执行
这个方法顾名思义:Native通知Java层,我初始化好了,你可以开始做一些事情了。
这个时候我们就要去Java层中看 postEventFromNative()
这个方法做什么了。
private static void postEventFromNative(Object mediaplayer_ref,
int what, int arg1, int arg2, Object obj)
{
//得到弱引用的MediaPlayer
final MediaPlayer mp = (MediaPlayer)((WeakReference)mediaplayer_ref).get();
if (mp == null) {
return;
}
…
//如果handler不为空,则发送一条message
if (mp.mEventHandler != null) {
Message m = mp.mEventHandler.obtainMessage(what, arg1, arg2, obj);
mp.mEventHandler.sendMessage(m);
}
}
postEventFromNative总结
总的来说,就是拿到Native层创建好的MediaPlayer,并且向Handler发送一条message,至于后面做了啥之后再讲。
我们之前在create()的时候,创建MediaPlayer之后,会走native_setup方法,我们来看看这个方法做了什么:
//android_media_MediaPlayer.cpp
static void
android_media_MediaPlayer_native_setup(JNIEnv *env, jobject thiz, jobject weak_this)
{
ALOGV(“native_setup”);
sp mp = new MediaPlayer();
…
// 给MediaPlayer创建一个Listener,便于我们在Java层设置的 setPrepareListener、setOnCompleteListener能产生回调
sp listener = new JNIMediaPlayerListener(env, thiz, weak_this);
mp->setListener(listener);
// 对于Java层来说,C++中的MediaPlayer是不透明的,也无需关心其对应的逻辑,各司其职就行了
setMediaPlayer(env, thiz, mp);
}
native_setup总结
-
设置监听器
-
C++层自己处理这个MediaPlayer
上面是一个MediaPlayer的构造过程,在构造完,着实的获取到MediaPlayer这个对象之后,我们就会去 setDataSource(),我们来看看它的源码(注:这一段代码比较无聊,可以大致浏览一下并直接看总结):
(注:下面一段是传入的uri为文件资源形式的代码,就比如我们传了一个本地的媒体文件
private void setDataSource(String path, Map<String, String> headers, List cookies)
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;
//把HTPP/RTSP中包含的key、value分别装到两个数组中
for (Map.Entry<String, String> entry: headers.entrySet()) {
keys[i] = entry.getKey();
values[i] = entry.getValue();
++i;
}
}
setDataSource(path, keys, values, cookies);
}
private void setDataSource(String path, String[] keys, String[] values,
List cookies)
throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
//解析path
final Uri uri = Uri.parse(path);
final String scheme = uri.getScheme();
if (“file”.equals(scheme)) {
path = uri.getPath();
} else if (scheme != null) {
// 处理非资源文件
nativeSetDataSource(
MediaHTTPService.createHttpServiceBinderIfNecessary(path, cookies),
path,
keys,
values);
return;
}
//处理文件类型
final File file = new File(path);
if (file.exists()) {
FileInputStream is = new FileInputStream(file);
//得到文件标识符
FileDescriptor fd = is.getFD();
//传入文件标志符
setDataSource(fd);
is.close();
} else {
throw new IOException(“setDataSource failed.”);
}
}
public void setDataSource(FileDescriptor fd)
throws IOException, IllegalArgumentException, IllegalStateException {
setDataSource(fd, 0, 0x7ffffffffffffffL);
}
public void setDataSource(FileDescriptor fd, long offset, long length)
throws IOException, IllegalArgumentException, IllegalStateException {
//进入native层
_setDataSource(fd, offset, length);
}
setDataSource()总结
解析uri,如果得到的路径是文件,则把该文件资源的资源标识符丢给native层处理。
native层并没有setDataSource函数,但是有一个函数名映射函数说明,这是JNI中常用的动态注册方法:
所以我们找到 setDataSourceFD()
:
static void
android_media_MediaPlayer_setDataSourceFD(JNIEnv *env, jobject thiz, jobject fileDescriptor, jlong offset, jlong length)
{
//获得MediaPlayer
sp mp = getMediaPlayer(env, thiz);
…
//在JNI中获取 java.io.FileDescription 这里开始调用JNIEnv*中的 GetIntField函数获取对应的变量
int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
ALOGV(“setDataSourceFD: fd %d”, fd);
//进行 setDataSource
process_media_player_call( env, thiz, mp->setDataSource(fd, offset, length), “java/io/IOException”, “setDataSourceFD failed.” );
}
static void process_media_player_call(JNIEnv env, jobject thiz, status_t opStatus, const char exception, const char *message)
{
if (exception == NULL) {
//如果没有异常
if (opStatus != (status_t) OK) {
//如果在 setDataSource过程中opStatus不Ok
sp mp = getMediaPlayer(env, thiz);
//则通知MEDIA_ERROR
if (mp != 0) mp->notify(MEDIA_ERROR, opStatus, 0);
}
} else { // Throw exception!
…
}
}
setDataSourceFD()总结
拿到标识符(uri的内容)后,用mp->setDataSource(fd, offset, length)
得到的结果,进行异常处理,如果有异常就抛出。
在setDataSource的时候我们是以本地文件的形式来走的,如果我们当时走的是网络请求,即我们uri内容是 HTTP/RTSP,那么就会nativeSetDataSource()
方法,在jni层,对应的是setDataSourceAndHeader()
:
static void
android_media_MediaPlayer_setDataSourceAndHeaders(
JNIEnv *env, jobject thiz, jobject httpServiceBinderObj, jstring path,
jobjectArray keys, jobjectArray values) {
sp mp = getMediaPlayer(env, thiz);
…//解析网络路径、判空、判内存溢出
//下面是通过Binder机制,将httpServiceBinderObj传给IPC返回给binder,然后强制转换成IMediaHTTPService
sp httpService;
if (httpServiceBinderObj != NULL) {
sp binder = ibinderForJavaObject(env, httpServiceBinderObj);
httpService = interface_cast(binder);
}
//开始判断状态,和上面的文件操作是一样的
status_t opStatus =
mp->setDataSource(
httpService,
pathStr,
headersVector.size() > 0? &headersVector : NULL);
process_media_player_call(
env, thiz, opStatus, “java/io/IOException”,
“setDataSource failed.” );
}
至此,setDataSource()的过程就完成了。
这里最后总结一下setDataSource做了什么:
拿到uri,判断uri的内容是否为文件资源 (用大白话讲:就是你用的是本地文件,还是一个指定的网络url)
-
如果是文件资源,就去检查它的 文件描述符(判断类型啥的),没问题的话,就
mp-> setDataSource()
-
如果是HTTP/RTSP(网络请求),则通过Binder,去一次做IPC(这里暂且推断为:网络请求),然后就
mp-> setDataSource()
这样通过JNI, JAVA和C++相互调用,有这么几个好处:
- 安全
封装在native层的代码是.so形式的,破坏风险小。
- 效率高
在运行速度上C++更高效,所以对于复杂的渲染,放在native层做最好
- 连通性强
因为正向调用就传值,反向调用就把处理后的值通知回去,其实就是一条路
接下来看看在create()阶段里,setDataSource后 的setDisplay(holder)
做了什么
首先我们都知道 Holder是容器、管家的意思,RecyclerView有ViewHolder来控制每个item,那SurfaceHolder同样的,也是控制着每一个Surface。
public void setDisplay(SurfaceHolder sh) {
//给Surface设置一个控制器
mSurfaceHolder = sh;
Surface surface;
if (sh != null) {
surface = sh.getSurface();
} else {
surface = null;
}
//到native层,给视频设置Surface
_setVideoSurface(surface);
//更新Surface到屏幕上
updateSurfaceScreenOn();
}
在定义完SurfaceHolder后,就要去native层了,我们在同样的cpp文件中找到了对应的函数:
static void
setVideoSurface(JNIEnv *env, jobject thiz, jobject jsurface, jboolean mediaPlayerMustBeAlive) {
…
decVideoSurfaceRef(env, thiz);
sp new_st;
if (jsurface) {
//得到Java层的Surface
sp surface (android_view_Surface_getSurface(env, jsurface));
if (surface != NULL) {
//如果Surface不为空,则获取 IGraphicBufferProducer
new_st = surface -> getIGraphicBufferProducer();
…
//调用 incStrong
new_st -> incStrong(( void*)decVideoSurfaceRef);
} else {
…
}
}
env -> SetLongField(thiz, fields.surface_texture, (jlong) new_st.get());
//如果MediaPlayer还没有被初始化,setDataSource将会失败,但setDataSource之前就setDisplay了
//在prepare/prepareAsync中调用setVideoSurfaceTexture 可以覆盖该cas
mp -> setVideoSurfaceTexture(new_st);
}
static void
decVideoSurfaceRef(JNIEnv *env, jobject thiz)
{
…
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级安卓工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
这里我就分享一份资料,希望可以帮助到大家提升进阶。
内容包含:Android学习PDF+架构视频+面试文档+源码笔记,高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料 这几块的内容。分享给大家,非常适合近期有面试和想在技术道路上继续精进的朋友。
如果你有需要的话,可以点击Android学习PDF+架构视频+面试文档+源码笔记获取免费领取方式
喜欢本文的话,不妨给我点个小赞、评论区留言或者转发支持一下呗~
tVideoSurfaceTexture(new_st);
}
static void
decVideoSurfaceRef(JNIEnv *env, jobject thiz)
{
…
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级安卓工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-Wl8SzaSu-1710574583773)]
[外链图片转存中…(img-VTs7yT0l-1710574583774)]
[外链图片转存中…(img-ANTeIkCO-1710574583774)]
[外链图片转存中…(img-1bYp5OOw-1710574583774)]
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
[外链图片转存中…(img-9wIZbI33-1710574583775)]
这里我就分享一份资料,希望可以帮助到大家提升进阶。
内容包含:Android学习PDF+架构视频+面试文档+源码笔记,高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料 这几块的内容。分享给大家,非常适合近期有面试和想在技术道路上继续精进的朋友。
如果你有需要的话,可以点击Android学习PDF+架构视频+面试文档+源码笔记获取免费领取方式
喜欢本文的话,不妨给我点个小赞、评论区留言或者转发支持一下呗~