Android 13 Media框架(3)- MediaPlayer生命周期
上一节了解了MediaPlayer api的使用,这一节就我们将会了解MediaPlayer的生命周期与api使用细节。
1、MediaPlayer生命周期
MediaPlayer.java 一开始有对生命周期的描述,这里对这些内容进行翻译:
- MediaPlayer 是线程不安全的,创建以及调用应该在同一个线程中,如果需要使用Callback,那么线程必须要有一个Looper;
- 调用 new 或者 reset 后MediaPlayer进入
Idle
状态,release 调用后进入到End
状态,这两个状态之间就是 MediaPlayer 对象的生命周期; - 调用 new 或者 reset都会进入
Idle
状态,但是它们仍然是有区别的。getDuration 等方法在Idle
状态下调用会出现error,如果是new之后调用,虽然会返回error,但是error并不是由内部播放器引擎上抛的,播放器状态仍然保持在Idle
状态;如果刚好是在reset之后调用getDuration方法,那么内部播放器引擎会上抛error事件,并且把播放器状态置为Error
; - 如果MediaPlayer对象不再需要被使用,推荐调用release方法,让内部播放器引擎使用的资源能够被释放;一旦进入了
End
状态,MediaPlayer对象将不能再被使用,并且不能再被切换到其他状态,如果仍要使用需要重新new一个对象,进入Idle
状态。 - 有很多原因会造成播放失败,比如不支持的audio/video格式、分辨率过高、streaming超时等,这些错误会让播放器进入
Error
状态并且上抛事件,如果要继续使用当前MediaPlayer对象,可以调用reset使其进入到Idle
状态; - setDataSource会让播放器从
Idle
进入Loaded
状态,如果在其他状态调用则会抛出IllegalStateException
错误; - prepare会让播放器从
Loaded
状态进入到Prepared
状态,如果调用的是prepareAsync,则会先进入中间状态Preparing
;在除了Loaded
和Stopped
状态外调用prepare方法都是非法的,会抛出IllegalStateException
错误; - start方法会让播放器从
Prepared
状态进入到Started
状态,可以用isPlaying判断播放器当前状态是否在Started
状态;如果已经进入到Started
状态,则再调用start不会有任何影响; - pause方法会让播放器进入到
Paused
状态,从Started
进入到Paused
或者反过来的过程都是异步的;如果已经进入到Paused
状态,则再调用pause不会有任何影响;在Paused
状态下重新调用start将会恢复播放,进入到Started
状态; - stop会让播放器从
Started
、Paused
、Prepared
、PlaybackCompleted
状态进入到Stopped
状态,一旦进入到Stopped
状态,那么必须要调用prepare重新进入Prepared
状态才能够重新进行播放;同样的,如果已经进入到Stopped
状态,则再调用stop不会有任何影响; - seekTo方法是异步的,调用完成后会有callback事件onSeekComplete上抛,seekTo可以在
Started
、Paused
、Prepared
、PlaybackCompleted
状态下调用; - 当播放结束时,如果setLooping设置为true,那么播放器将会保持在
Started
状态,如果setLooping设置为false,那么播放器将会进入到PlaybackCompleted
状态;在PlaybackCompleted
状态下,调用start方法将会从头开始重新播放,并且进入到Started
状态。
2、异常处理
从上面我们可以了解到,MediaPlayer维护了一套状态机,并且调用MediaPlayer方法时会检查当前调用是否是非法的,这套状态机机制在 mediaplayer.cpp 中。 MediaPlayer 定义了如下状态:
enum media_player_states {MEDIA_PLAYER_STATE_ERROR = 0,MEDIA_PLAYER_IDLE = 1 << 0,MEDIA_PLAYER_INITIALIZED = 1 << 1,MEDIA_PLAYER_PREPARING = 1 << 2,MEDIA_PLAYER_PREPARED = 1 << 3,MEDIA_PLAYER_STARTED = 1 << 4,MEDIA_PLAYER_PAUSED = 1 << 5,MEDIA_PLAYER_STOPPED = 1 << 6,MEDIA_PLAYER_PLAYBACK_COMPLETE = 1 << 7
};
要注意的是,End状态表示native MediaPlayer对象已经销毁了,所以它并没有真正的 End 状态。
MediaPlayer.java 中还有一段关于函数在什么状态下调用是有效的,什么状态下调用是无效的的表格,这里挑出一些进行翻译。
序号 | 方法 | 有效状态 | 无效状态下调用 |
---|---|---|---|
1 | getAudioSessionId | any | |
2 | getCurrentPosition | Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted | Error |
3 | getDuration | Prepared, Started, Paused, Stopped, PlaybackCompleted | Error |
4 | getVideoHeight | any | |
5 | isPlaying | any | |
6 | pause | Started, Paused, PlaybackCompleted | Error |
7 | prepare | Initialized, Stopped | 异常 |
8 | prepareAsync | Initialized, Stopped | 异常 |
9 | release | any | |
10 | reset | any | |
11 | seekTo | Prepared, Started, Paused, PlaybackCompleted | Error |
12 | setDataSource | Idle | 异常 |
13 | setDisplay | any | |
15 | setSurface | any | |
16 | setVideoScalingMode | Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted | |
17 | setLooping | Idle, Initialized, Stopped, Prepared, Started, Paused, PlaybackCompleted | |
18 | start | Prepared, Started, Paused, PlaybackCompleted | Error |
19 | stop | Prepared, Started, Stopped, Paused, PlaybackCompleted | Error |
从以上表格我们可以发现,非法状态下调用prepare
、prepareAsync
、setDataSource
会抛异常中止程序运行;调用start
、pause
等方法会将状态置为 Error
,具体程序如何反应需要看我们的OnErrorListener是如何处理的:
case MEDIA_ERROR:Log.e(TAG, "Error (" + msg.arg1 + "," + msg.arg2 + ")");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;
3、对new、release、reset的一些理解
相关代码路径:
- MediaPlayer.java
- android_media_MediaPlayer.cpp
- mediaplayer.cpp
3.1、new
在看正式的 new MediaPlayer 流程前,我们先要注意MediaPlayer.java中的如下代码段:
static {System.loadLibrary("media_jni");native_init();}
它会加载media_jni.so,并执行native函数 android_media_MediaPlayer_native_init 。native_init会获取java类中的postEventFromNative
方法ID,mNativeContext
、mNativeSurfaceTexture
等字段的ID,获取到的ID会存储在静态变量fields_t
中,后期可以通过这些ID找到Java对象中对应的成员,获取其存储的值或者向其中存储值。
static fields_t fields;fields.context = env->GetFieldID(clazz, "mNativeContext", "J");
fields.post_event = env->GetStaticMethodID(clazz, "postEventFromNative", "(Ljava/lang/Object;IIILjava/lang/Object;)V");
fields.surface_texture = env->GetFieldID(clazz, "mNativeSurfaceTexture", "J");
接下来看new MediaPlayer创建一个对象会做哪些事情:
private MediaPlayer(int sessionId) {...try (ScopedParcelState attributionSourceState = attributionSource.asScopedParcelState()) {native_setup(new WeakReference<MediaPlayer>(this), attributionSourceState.getParcel());}
}
核心是调用native_setup方法,需要将自身的弱引用对象作为参数向下传递:
static void
android_media_MediaPlayer_native_setup(JNIEnv *env, jobject thiz, jobject weak_this,jobject jAttributionSource)
{Parcel* parcel = parcelForJavaObject(env, jAttributionSource);android::content::AttributionSourceState attributionSource;attributionSource.readFromParcel(parcel);// 创建MediaPlayer native对象sp<MediaPlayer> mp = sp<MediaPlayer>::make(attributionSource);// 创建并注册Callback对象sp<JNIMediaPlayerListener> listener = new JNIMediaPlayerListener(env, thiz, weak_this);mp->setListener(listener);// 将MediaPlayer对象存储到Java对象中setMediaPlayer(env, thiz, mp);
}
native_setup干了3件事:
- 创建MediaPlayer native对象;
- 用传下来的弱引用对象创建Listener对象,用于Callback调用;
- 将MediaPlayer native对象指针到Java对象中;
来看看如何存储MediaPlayer native指针的:
static sp<MediaPlayer> setMediaPlayer(JNIEnv* env, jobject thiz, const sp<MediaPlayer>& player)
{Mutex::Autolock l(sLock);sp<MediaPlayer> old = (MediaPlayer*)env->GetLongField(thiz, fields.context);// 手动增加强引用计数if (player.get()) {player->incStrong((void*)setMediaPlayer);}// 检查mNativeContext中存储的MediaPlayer对象if (old != 0) {old->decStrong((void*)setMediaPlayer);}// 将MediaPlayer的地址存储到mNativeContextenv->SetLongField(thiz, fields.context, (jlong)player.get());return old;
}
这里有3个步骤:
- 首先要增加MediaPlayer native对象的强引用计数;
- 检查
mNativeContext
中存储的地址是否为NULL,如果不为NULL则需要销毁存储的MediaPlayer对象; - 将新的MediaPlayer对象存储到
mNativeContext
中。
虽然我们会把MediaPlayer对象存储到Java字段中,但是其引用计数在setMediaPlayer中仍为1,如不增加引用计数,出了当前作用域native对象将自动销毁。
3.2、reset
reset的作用是重置当前MediaPlayer对象,使其恢复到new的状态。我们来看下reset实现:
status_t MediaPlayer::reset_l()
{mLoop = false;if (mCurrentState == MEDIA_PLAYER_IDLE) return NO_ERROR;// 1.阻止 MEDIA_PREPARED 事件上抛mPrepareSync = false;if (mPlayer != 0) {// 2.调用内部实现的resetstatus_t ret = mPlayer->reset();if (ret != NO_ERROR) {ALOGE("reset() failed with return code (%d)", ret);mCurrentState = MEDIA_PLAYER_STATE_ERROR;} else {// 2.调用内部实现的disconnectmPlayer->disconnect();mCurrentState = MEDIA_PLAYER_IDLE;}// 3.销毁内部实现mPlayer = 0;return ret;}// 4.清除设置clear_l();return NO_ERROR;
}
reset主要会做3件事情:
- 如果当前状态是
Preparing
,那么会将 mPrepareSync 置为fasle,阻止 MEDIA_PREPARED 事件上抛,具体内部如何操作后续再研究; - 调用 MediaPlayer 内部实例的 disconnect 方法断开连接,如果处在播放状态将会停止播放(这点后续再研究);
- 销毁 MediaPlayer 内部实例;
3.3、release
release会直接销毁掉MediaPlayer native对象,一旦调用release,当前MediaPlayer java对象将不能够再被使用:
MediaPlayer::~MediaPlayer()
{ALOGV("destructor");if (mAudioAttributesParcel != NULL) {delete mAudioAttributesParcel;mAudioAttributesParcel = NULL;}AudioSystem::releaseAudioSessionId(mAudioSessionId, (pid_t)-1);disconnect();IPCThreadState::self()->flushCommands();
}
MediaPlayer 的析构函数同样也会调用内部实例的 disconnect 方法断开连接,之后会销毁 MediaPlayer 内部实例,这两点和reset是相同的。我们在想要结束activity,可以先调用reset,再调用release,也可以直接调用release来释放资源。
相关文章:
Android 13 Media框架(3)- MediaPlayer生命周期
上一节了解了MediaPlayer api的使用,这一节就我们将会了解MediaPlayer的生命周期与api使用细节。 1、MediaPlayer生命周期 MediaPlayer.java 一开始有对生命周期的描述,这里对这些内容进行翻译: MediaPlayer 是线程不安全的,创建…...

[oneAPI] BERT
[oneAPI] BERT BERT训练过程Masked Language Model(MLM)Next Sentence Prediction(NSP)微调 总结基于oneAPI代码 比赛:https://marketing.csdn.net/p/f3e44fbfe46c465f4d9d6c23e38e0517 Intel DevCloud for oneAPI&…...
F1-score解析
报错:valueError: Target is multiclass but average‘binary’. Please choose another average setting, one of 原因:使用from sklearn.metrics import f1_score多类别计算F1-score时报错,改函数的参数即可,如:f1_s…...

windows11下配置vscode中c/c++环境
本文默认已经下载且安装好vscode,主要是解决环境变量配置以及编译task、launch文件的问题。 自己尝试过许多博客,最后还是通过这种方法配置成功了。 Linux(ubuntu 20.04)配置vscode可以直接跳转到配置task、launch文件,不需要下载mingw与配…...
Max Sum
一、题目 Given a sequence a[1],a[2],a[3]…a[n], your job is to calculate the max sum of a sub-sequence. For example, given (6,-1,5,4,-7), the max sum in this sequence is 6 (-1) 5 4 14. Input The first line of the input contains an integer T(1<T<…...

Field injection is not recommended
文章目录 1. 引言2. 不推荐使用Autowired的原因3. Spring提供了三种主要的依赖注入方式3.1. 构造函数注入(Constructor Injection)3.2. Setter方法注入(Setter Injection)3.3. 字段注入(Field Injection) 4…...

C#字符串占位符替换
using System;namespace myprog {class test{static void Main(string[] args){string str1 string.Format("{0}今年{1}岁,身高{2}cm,月收入{3}元;", "小李", 23, 177, 5000);Console.WriteLine(str1);Console.ReadKey(…...

ChatGPT等人工智能编写文章的内容今后将成为常态
BuzzFeed股价上涨200%可能标志着“转向人工智能”媒体趋势的开始。 周四,一份内部备忘录被华尔街日报透露BuzzFeed正计划使用ChatGPT聊天机器人-风格文本合成技术来自OpenAI,用于创建个性化盘问和将来可能的其他内容。消息传出后,BuzzFeed的…...
【Sklearn】基于梯度提升树算法的数据分类预测(Excel可直接替换数据)
【Sklearn】基于梯度提升树算法的数据分类预测(Excel可直接替换数据) 1.模型原理2.模型参数3.文件结构4.Excel数据5.下载地址6.完整代码7.运行结果1.模型原理 梯度提升树(Gradient Boosting Trees)是一种集成学习方法,用于解决分类和回归问题。它通过将多个弱学习器(通常…...
什么叫做云计算?
相信大多数人对云计算或者是云服务的认识还停留在仅仅听过这个名词,但是对其真正的定义或者意义还不甚了解的层面。甚至有些技术人员,如果日常的业务不涉及到云服务,可能对其也只是一知半解的程度。首先云计算准确的讲只是云服务中的一部分&a…...
深度学习Batch Normalization
批标准化(Batch Normalization,简称BN)是一种用于深度神经网络的技术,它的主要目的是解决深度学习模型训练过程中的内部协变量偏移问题。简单来说,当我们在训练深度神经网络时,每一层的输入分布都可能会随着…...

el-table实现懒加载(el-table-infinite-scroll)
2023.8.15今天我学习了用el-table对大量的数据进行懒加载。 效果如下: 1.首先安装: npm install --save el-table-infinite-scroll2 2.全局引入: import ElTableInfiniteScroll from "el-table-infinite-scroll";// 懒加载 V…...
vueRouter回顾
关于vueRouter的两种路由模式 “history” 模式使用正常的 URL 格式,例如 https://example.com/path。“hash” 模式将路由信息添加到 URL 的哈希部分(#)后面,例如 https://example.com/#/path。 1、history模式:没有…...

大规模无人机集群算法flocking(蜂群)
matlab2016b正常运行...

【第三阶段】kotlin语言的split
const val INFO"kotlin,java,c,c#" fun main() {//list自动类型推断成listList<String>val listINFO.split(",")//直接输出list集合,不解构println("直接输出list的集合元素:$list")//类比c有解构,ktoli…...

机器学习笔记值优化算法(十四)梯度下降法在凸函数上的收敛性
机器学习笔记之优化算法——梯度下降法在凸函数上的收敛性 引言回顾:收敛速度:次线性收敛二次上界引理 梯度下降法在凸函数上的收敛性收敛性定理介绍证明过程 引言 本节将介绍梯度下降法在凸函数上的收敛性。 回顾: 收敛速度:次…...

iphone拷贝照片中间带E自动去重软件,以及java程序如何打包成jar和exe
文章目录 一、前提二、问题描述三、原始处理方式四、程序处理4.1 java程序如何打包exe4.1.1 首先打包jar4.1.2 开始生成exe4.1.3 软件使用方式 4.2 更换图标4.2.1 更换swing的打包jar图标4.2.2 更换exe图标 4.3 如何使生成的exe在没有java环境的电脑上运行4.3.1 Inno Setup打包…...
不同分类器对数据的处理
"""基于鸢尾花的不同分类器的效果比对:step1:准备数据;提取数据的特征向量X,Y将Y数据采用LabelEncoder转化为数值型数据;step2:将提取的特征向量X,Y进行拆分(训练集与测试集)step3:构建不同分类器并设置参数,例如:…...
十面骰子、
十面骰子(一): v 有一个十面的骰子,每一面分别为1-10,不断投掷骰子,投10000次,统计每一面1-10出现的次数或概率. v 提示:可用rand()产生1-10之间的随机数,再统计1-10出现的机会,存放于数组里,…...

IDE的下载和使用
IDE 文章目录 IDEJETBRAIN JETBRAIN 官网下载对应的ide 激活方式 dxm的电脑已经把这个脚本下载下来了,脚本是macjihuo 以后就不用买了...
云原生核心技术 (7/12): K8s 核心概念白话解读(上):Pod 和 Deployment 究竟是什么?
大家好,欢迎来到《云原生核心技术》系列的第七篇! 在上一篇,我们成功地使用 Minikube 或 kind 在自己的电脑上搭建起了一个迷你但功能完备的 Kubernetes 集群。现在,我们就像一个拥有了一块崭新数字土地的农场主,是时…...
rknn优化教程(二)
文章目录 1. 前述2. 三方库的封装2.1 xrepo中的库2.2 xrepo之外的库2.2.1 opencv2.2.2 rknnrt2.2.3 spdlog 3. rknn_engine库 1. 前述 OK,开始写第二篇的内容了。这篇博客主要能写一下: 如何给一些三方库按照xmake方式进行封装,供调用如何按…...

理解 MCP 工作流:使用 Ollama 和 LangChain 构建本地 MCP 客户端
🌟 什么是 MCP? 模型控制协议 (MCP) 是一种创新的协议,旨在无缝连接 AI 模型与应用程序。 MCP 是一个开源协议,它标准化了我们的 LLM 应用程序连接所需工具和数据源并与之协作的方式。 可以把它想象成你的 AI 模型 和想要使用它…...
Spring AI 入门:Java 开发者的生成式 AI 实践之路
一、Spring AI 简介 在人工智能技术快速迭代的今天,Spring AI 作为 Spring 生态系统的新生力量,正在成为 Java 开发者拥抱生成式 AI 的最佳选择。该框架通过模块化设计实现了与主流 AI 服务(如 OpenAI、Anthropic)的无缝对接&…...

如何在网页里填写 PDF 表格?
有时候,你可能希望用户能在你的网站上填写 PDF 表单。然而,这件事并不简单,因为 PDF 并不是一种原生的网页格式。虽然浏览器可以显示 PDF 文件,但原生并不支持编辑或填写它们。更糟的是,如果你想收集表单数据ÿ…...

Reasoning over Uncertain Text by Generative Large Language Models
https://ojs.aaai.org/index.php/AAAI/article/view/34674/36829https://ojs.aaai.org/index.php/AAAI/article/view/34674/36829 1. 概述 文本中的不确定性在许多语境中传达,从日常对话到特定领域的文档(例如医学文档)(Heritage 2013;Landmark、Gulbrandsen 和 Svenevei…...

网站指纹识别
网站指纹识别 网站的最基本组成:服务器(操作系统)、中间件(web容器)、脚本语言、数据厍 为什么要了解这些?举个例子:发现了一个文件读取漏洞,我们需要读/etc/passwd,如…...

短视频矩阵系统文案创作功能开发实践,定制化开发
在短视频行业迅猛发展的当下,企业和个人创作者为了扩大影响力、提升传播效果,纷纷采用短视频矩阵运营策略,同时管理多个平台、多个账号的内容发布。然而,频繁的文案创作需求让运营者疲于应对,如何高效产出高质量文案成…...

浪潮交换机配置track检测实现高速公路收费网络主备切换NQA
浪潮交换机track配置 项目背景高速网络拓扑网络情况分析通信线路收费网络路由 收费汇聚交换机相应配置收费汇聚track配置 项目背景 在实施省内一条高速公路时遇到的需求,本次涉及的主要是收费汇聚交换机的配置,浪潮网络设备在高速项目很少,通…...

Mysql中select查询语句的执行过程
目录 1、介绍 1.1、组件介绍 1.2、Sql执行顺序 2、执行流程 2.1. 连接与认证 2.2. 查询缓存 2.3. 语法解析(Parser) 2.4、执行sql 1. 预处理(Preprocessor) 2. 查询优化器(Optimizer) 3. 执行器…...