当前位置: 首页 > news >正文

Android 13 Media框架(3)- MediaPlayer生命周期

上一节了解了MediaPlayer api的使用,这一节就我们将会了解MediaPlayer的生命周期与api使用细节。

1、MediaPlayer生命周期

MediaPlayer.java 一开始有对生命周期的描述,这里对这些内容进行翻译:

  1. MediaPlayer 是线程不安全的,创建以及调用应该在同一个线程中,如果需要使用Callback,那么线程必须要有一个Looper;
  2. 调用 new 或者 reset 后MediaPlayer进入 Idle 状态,release 调用后进入到 End 状态,这两个状态之间就是 MediaPlayer 对象的生命周期;
  3. 调用 new 或者 reset都会进入Idle 状态,但是它们仍然是有区别的。getDuration 等方法在Idle 状态下调用会出现error,如果是new之后调用,虽然会返回error,但是error并不是由内部播放器引擎上抛的,播放器状态仍然保持在Idle 状态;如果刚好是在reset之后调用getDuration方法,那么内部播放器引擎会上抛error事件,并且把播放器状态置为Error
  4. 如果MediaPlayer对象不再需要被使用,推荐调用release方法,让内部播放器引擎使用的资源能够被释放;一旦进入了 End 状态,MediaPlayer对象将不能再被使用,并且不能再被切换到其他状态,如果仍要使用需要重新new一个对象,进入Idle状态。
  5. 有很多原因会造成播放失败,比如不支持的audio/video格式、分辨率过高、streaming超时等,这些错误会让播放器进入Error状态并且上抛事件,如果要继续使用当前MediaPlayer对象,可以调用reset使其进入到Idle状态;
  6. setDataSource会让播放器从Idle进入Loaded状态,如果在其他状态调用则会抛出IllegalStateException错误;
  7. prepare会让播放器从Loaded状态进入到Prepared状态,如果调用的是prepareAsync,则会先进入中间状态Preparing;在除了LoadedStopped状态外调用prepare方法都是非法的,会抛出IllegalStateException错误;
  8. start方法会让播放器从Prepared状态进入到Started状态,可以用isPlaying判断播放器当前状态是否在Started状态;如果已经进入到Started状态,则再调用start不会有任何影响;
  9. pause方法会让播放器进入到Paused状态,从Started进入到Paused或者反过来的过程都是异步的;如果已经进入到Paused状态,则再调用pause不会有任何影响;在Paused状态下重新调用start将会恢复播放,进入到Started状态;
  10. stop会让播放器从StartedPausedPreparedPlaybackCompleted状态进入到Stopped状态,一旦进入到Stopped状态,那么必须要调用prepare重新进入Prepared状态才能够重新进行播放;同样的,如果已经进入到Stopped状态,则再调用stop不会有任何影响;
  11. seekTo方法是异步的,调用完成后会有callback事件onSeekComplete上抛,seekTo可以在StartedPausedPreparedPlaybackCompleted状态下调用;
  12. 当播放结束时,如果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 中还有一段关于函数在什么状态下调用是有效的,什么状态下调用是无效的的表格,这里挑出一些进行翻译。

序号方法有效状态无效状态下调用
1getAudioSessionIdany
2getCurrentPositionInitialized, Prepared, Started, Paused, Stopped, PlaybackCompletedError
3getDurationPrepared, Started, Paused, Stopped, PlaybackCompletedError
4getVideoHeightany
5isPlayingany
6pauseStarted, Paused, PlaybackCompletedError
7prepareInitialized, Stopped异常
8prepareAsyncInitialized, Stopped异常
9releaseany
10resetany
11seekToPrepared, Started, Paused, PlaybackCompletedError
12setDataSourceIdle异常
13setDisplayany
15setSurfaceany
16setVideoScalingModeInitialized, Prepared, Started, Paused, Stopped, PlaybackCompleted
17setLoopingIdle, Initialized, Stopped, Prepared, Started, Paused, PlaybackCompleted
18startPrepared, Started, Paused, PlaybackCompletedError
19stopPrepared, Started, Stopped, Paused, PlaybackCompletedError

从以上表格我们可以发现,非法状态下调用prepareprepareAsyncsetDataSource会抛异常中止程序运行;调用startpause等方法会将状态置为 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,mNativeContextmNativeSurfaceTexture等字段的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件事:

  1. 创建MediaPlayer native对象;
  2. 用传下来的弱引用对象创建Listener对象,用于Callback调用;
  3. 将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个步骤:

  1. 首先要增加MediaPlayer native对象的强引用计数;
  2. 检查mNativeContext中存储的地址是否为NULL,如果不为NULL则需要销毁存储的MediaPlayer对象;
  3. 将新的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的使用&#xff0c;这一节就我们将会了解MediaPlayer的生命周期与api使用细节。 1、MediaPlayer生命周期 MediaPlayer.java 一开始有对生命周期的描述&#xff0c;这里对这些内容进行翻译&#xff1a; MediaPlayer 是线程不安全的&#xff0c;创建…...

[oneAPI] BERT

[oneAPI] BERT BERT训练过程Masked Language Model&#xff08;MLM&#xff09;Next Sentence Prediction&#xff08;NSP&#xff09;微调 总结基于oneAPI代码 比赛&#xff1a;https://marketing.csdn.net/p/f3e44fbfe46c465f4d9d6c23e38e0517 Intel DevCloud for oneAPI&…...

F1-score解析

报错&#xff1a;valueError: Target is multiclass but average‘binary’. Please choose another average setting, one of 原因&#xff1a;使用from sklearn.metrics import f1_score多类别计算F1-score时报错&#xff0c;改函数的参数即可&#xff0c;如&#xff1a;f1_s…...

windows11下配置vscode中c/c++环境

本文默认已经下载且安装好vscode&#xff0c;主要是解决环境变量配置以及编译task、launch文件的问题。 自己尝试过许多博客&#xff0c;最后还是通过这种方法配置成功了。 Linux(ubuntu 20.04)配置vscode可以直接跳转到配置task、launch文件&#xff0c;不需要下载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. 构造函数注入&#xff08;Constructor Injection&#xff09;3.2. Setter方法注入&#xff08;Setter Injection&#xff09;3.3. 字段注入&#xff08;Field Injection&#xff09; 4…...

C#字符串占位符替换

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

ChatGPT等人工智能编写文章的内容今后将成为常态

BuzzFeed股价上涨200%可能标志着“转向人工智能”媒体趋势的开始。 周四&#xff0c;一份内部备忘录被华尔街日报透露BuzzFeed正计划使用ChatGPT聊天机器人-风格文本合成技术来自OpenAI&#xff0c;用于创建个性化盘问和将来可能的其他内容。消息传出后&#xff0c;BuzzFeed的…...

【Sklearn】基于梯度提升树算法的数据分类预测(Excel可直接替换数据)

【Sklearn】基于梯度提升树算法的数据分类预测(Excel可直接替换数据) 1.模型原理2.模型参数3.文件结构4.Excel数据5.下载地址6.完整代码7.运行结果1.模型原理 梯度提升树(Gradient Boosting Trees)是一种集成学习方法,用于解决分类和回归问题。它通过将多个弱学习器(通常…...

什么叫做云计算?

相信大多数人对云计算或者是云服务的认识还停留在仅仅听过这个名词&#xff0c;但是对其真正的定义或者意义还不甚了解的层面。甚至有些技术人员&#xff0c;如果日常的业务不涉及到云服务&#xff0c;可能对其也只是一知半解的程度。首先云计算准确的讲只是云服务中的一部分&a…...

深度学习Batch Normalization

批标准化&#xff08;Batch Normalization&#xff0c;简称BN&#xff09;是一种用于深度神经网络的技术&#xff0c;它的主要目的是解决深度学习模型训练过程中的内部协变量偏移问题。简单来说&#xff0c;当我们在训练深度神经网络时&#xff0c;每一层的输入分布都可能会随着…...

el-table实现懒加载(el-table-infinite-scroll)

2023.8.15今天我学习了用el-table对大量的数据进行懒加载。 效果如下&#xff1a; 1.首先安装&#xff1a; npm install --save el-table-infinite-scroll2 2.全局引入&#xff1a; import ElTableInfiniteScroll from "el-table-infinite-scroll";// 懒加载 V…...

vueRouter回顾

关于vueRouter的两种路由模式 “history” 模式使用正常的 URL 格式&#xff0c;例如 https://example.com/path。“hash” 模式将路由信息添加到 URL 的哈希部分&#xff08;#&#xff09;后面&#xff0c;例如 https://example.com/#/path。 1、history模式&#xff1a;没有…...

大规模无人机集群算法flocking(蜂群)

matlab2016b正常运行...

【第三阶段】kotlin语言的split

const val INFO"kotlin,java,c,c#" fun main() {//list自动类型推断成listList<String>val listINFO.split(",")//直接输出list集合&#xff0c;不解构println("直接输出list的集合元素&#xff1a;$list")//类比c有解构&#xff0c;ktoli…...

机器学习笔记值优化算法(十四)梯度下降法在凸函数上的收敛性

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

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&#xff1a;准备数据&#xff1b;提取数据的特征向量X,Y将Y数据采用LabelEncoder转化为数值型数据;step2:将提取的特征向量X,Y进行拆分(训练集与测试集)step3:构建不同分类器并设置参数&#xff0c;例如&#xff1a;…...

十面骰子、

十面骰子(一): v 有一个十面的骰子&#xff0c;每一面分别为1-10&#xff0c;不断投掷骰子&#xff0c;投10000次&#xff0c;统计每一面1-10出现的次数或概率. v 提示&#xff1a;可用rand()产生1-10之间的随机数&#xff0c;再统计1-10出现的机会&#xff0c;存放于数组里,…...

IDE的下载和使用

IDE 文章目录 IDEJETBRAIN JETBRAIN 官网下载对应的ide 激活方式 dxm的电脑已经把这个脚本下载下来了&#xff0c;脚本是macjihuo 以后就不用买了...

挑战杯推荐项目

“人工智能”创意赛 - 智能艺术创作助手&#xff1a;借助大模型技术&#xff0c;开发能根据用户输入的主题、风格等要求&#xff0c;生成绘画、音乐、文学作品等多种形式艺术创作灵感或初稿的应用&#xff0c;帮助艺术家和创意爱好者激发创意、提高创作效率。 ​ - 个性化梦境…...

【解密LSTM、GRU如何解决传统RNN梯度消失问题】

解密LSTM与GRU&#xff1a;如何让RNN变得更聪明&#xff1f; 在深度学习的世界里&#xff0c;循环神经网络&#xff08;RNN&#xff09;以其卓越的序列数据处理能力广泛应用于自然语言处理、时间序列预测等领域。然而&#xff0c;传统RNN存在的一个严重问题——梯度消失&#…...

数据链路层的主要功能是什么

数据链路层&#xff08;OSI模型第2层&#xff09;的核心功能是在相邻网络节点&#xff08;如交换机、主机&#xff09;间提供可靠的数据帧传输服务&#xff0c;主要职责包括&#xff1a; &#x1f511; 核心功能详解&#xff1a; 帧封装与解封装 封装&#xff1a; 将网络层下发…...

令牌桶 滑动窗口->限流 分布式信号量->限并发的原理 lua脚本分析介绍

文章目录 前言限流限制并发的实际理解限流令牌桶代码实现结果分析令牌桶lua的模拟实现原理总结&#xff1a; 滑动窗口代码实现结果分析lua脚本原理解析 限并发分布式信号量代码实现结果分析lua脚本实现原理 双注解去实现限流 并发结果分析&#xff1a; 实际业务去理解体会统一注…...

大模型多显卡多服务器并行计算方法与实践指南

一、分布式训练概述 大规模语言模型的训练通常需要分布式计算技术,以解决单机资源不足的问题。分布式训练主要分为两种模式: 数据并行:将数据分片到不同设备,每个设备拥有完整的模型副本 模型并行:将模型分割到不同设备,每个设备处理部分模型计算 现代大模型训练通常结合…...

使用 Streamlit 构建支持主流大模型与 Ollama 的轻量级统一平台

🎯 使用 Streamlit 构建支持主流大模型与 Ollama 的轻量级统一平台 📌 项目背景 随着大语言模型(LLM)的广泛应用,开发者常面临多个挑战: 各大模型(OpenAI、Claude、Gemini、Ollama)接口风格不统一;缺乏一个统一平台进行模型调用与测试;本地模型 Ollama 的集成与前…...

JS设计模式(4):观察者模式

JS设计模式(4):观察者模式 一、引入 在开发中&#xff0c;我们经常会遇到这样的场景&#xff1a;一个对象的状态变化需要自动通知其他对象&#xff0c;比如&#xff1a; 电商平台中&#xff0c;商品库存变化时需要通知所有订阅该商品的用户&#xff1b;新闻网站中&#xff0…...

【VLNs篇】07:NavRL—在动态环境中学习安全飞行

项目内容论文标题NavRL: 在动态环境中学习安全飞行 (NavRL: Learning Safe Flight in Dynamic Environments)核心问题解决无人机在包含静态和动态障碍物的复杂环境中进行安全、高效自主导航的挑战&#xff0c;克服传统方法和现有强化学习方法的局限性。核心算法基于近端策略优化…...

C#中的CLR属性、依赖属性与附加属性

CLR属性的主要特征 封装性&#xff1a; 隐藏字段的实现细节 提供对字段的受控访问 访问控制&#xff1a; 可单独设置get/set访问器的可见性 可创建只读或只写属性 计算属性&#xff1a; 可以在getter中执行计算逻辑 不需要直接对应一个字段 验证逻辑&#xff1a; 可以…...

三分算法与DeepSeek辅助证明是单峰函数

前置 单峰函数有唯一的最大值&#xff0c;最大值左侧的数值严格单调递增&#xff0c;最大值右侧的数值严格单调递减。 单谷函数有唯一的最小值&#xff0c;最小值左侧的数值严格单调递减&#xff0c;最小值右侧的数值严格单调递增。 三分的本质 三分和二分一样都是通过不断缩…...