简介: CSDN博客专家、《Android系统多媒体进阶实战》作者
博主新书推荐:《Android系统多媒体进阶实战》🚀
Android Audio工程师专栏地址:Audio工程师进阶系列【原创干货持续更新中...... 】🚀
Android多媒体专栏地址:多媒体系统工程师系列【原创干货持续更新中...... 】🚀
专题一 二:AAOS车载系统+AOSP14系统攻城狮入门视频实战课 🚀
专题三:Android14 Binder之HIDL与AIDL通信实战课 🚀
专题四:Android15快速自定义与集成音效实战课 🚀
专题五:Android15音频策略实战课 🚀
专题六:Android15音频性能实战课(无声/杂音/断音/爆音实战案例) 🚀
人生格言: 人生从来没有捷径,只有行动才是治疗恐惧和懒惰的唯一良药.
更多原创,欢迎关注:Android系统攻城狮

🍉🍉🍉文章目录🍉🍉🍉
-
-
- [🌻1. 前言](#🌻1. 前言)
- [🌻2. 用法与应用场景](#🌻2. 用法与应用场景)
- [🌻3. 调用流程剖析](#🌻3. 调用流程剖析)
-
- [3.1 核心步骤](#3.1 核心步骤)
- [3.2 涉及核心时序图](#3.2 涉及核心时序图)
- [🌻4. 实战应用案例](#🌻4. 实战应用案例)
- [🌻5. 用法总结](#🌻5. 用法总结)
-
🌻1. 前言
本篇目的:Android16音频深度解析之MediaPlayer.getDuration调用流程与实战。
在 Android 多媒体开发中,获取媒体文件的总时长(Duration)是构建播放器 UI 的基础。无论是显示总时长文本,还是初始化进度条(SeekBar)的最大值,都需要通过 MediaPlayer.getDuration 接口。虽然其调用看似简单,但其背后的元数据(Metadata)解析与状态机约束却直接影响着接口返回的准确性。
🌻2. 用法与应用场景
MediaPlayer.getDuration 方法用于获取媒体文件的总时长,单位为毫秒(ms)。
- 用法说明 :该方法必须在播放器进入
Prepared状态之后调用。如果在Idle或Initialized状态下调用,会抛出IllegalStateException或返回非法值(如 -1)。 - 运行结果 :返回一个
int类型的总毫秒数。对于直播流(Live Stream),该值通常返回 -1 或 0。 - 应用场景:
- UI 界面初始化 :在准备完成后,设置
TextView显示总时长(如 04:30)。 - 进度条配置 :将
SeekBar.setMax(duration),使进度条的跨度与媒体时长对齐。 - 播放逻辑校验 :用于判断
seekTo的目标值是否超过了文件末尾。
🌻3. 调用流程剖析
3.1 核心步骤
- 状态合法性检查 :
MediaPlayer.java在接收到调用后,会首先检查 Native 层的实例是否存在。虽然 Java 层不做严格状态限制,但若底层未就绪,JNI 调用将返回错误。 - JNI 层透传 :调用
android_media_MediaPlayer_getDuration,随后通过 Binder 机制将请求发送至MediaServer进程中的MediaPlayerService。 - 元数据提取 :
NuPlayer引擎在执行prepare阶段时,通过解封装器(GenericSource/Extractor)已经解析了媒体容器的头部信息(如 MP4 的moov箱体或 MP3 的帧头),并将时长信息存入元数据池。 - 结果转换 :引擎从
IMediaSource获取时长(通常以微秒 为单位),并将其转换为毫秒返回给应用层。 - 异步更新:对于某些索引不全的变码率(VBR)文件,时长可能会在播放过程中随着索引的完善而发生修正。
3.2 涉及核心时序图
MediaExtractor / Source NuPlayer Engine MediaPlayer Native MediaPlayer Java 应用代码层 MediaExtractor / Source NuPlayer Engine MediaPlayer Native MediaPlayer Java 应用代码层 在 Prepare 阶段已解析完毕 调用 getDuration() 调用 native_getDuration 通过 Binder 请求获取 Duration 读取元数据 (Metadata) 返回文件时长 (单位: us) 转换为 ms 并返回 返回执行结果 获取到总时长 (ms)
🌻4. 实战应用案例
本案例展示了如何在 OnPreparedListener 回调中安全地获取总时长,并将其格式化为标准的"分:秒"格式。
java
public class AudioDurationManager {
private MediaPlayer mediaPlayer;
public void initPlayer(Context context, Uri uri) {
mediaPlayer = new MediaPlayer();
try {
mediaPlayer.setDataSource(context, uri);
// 必须在 Prepared 之后获取时长
mediaPlayer.setOnPreparedListener(mp -> {
int durationMs = mp.getDuration();
String formattedTime = formatTime(durationMs);
System.out.println("媒体文件准备就绪,总时长: " + formattedTime);
// 示例:设置进度条最大值
// seekBar.setMax(durationMs);
});
mediaPlayer.prepareAsync();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 将毫秒转换为 00:00 格式
*/
private String formatTime(int ms) {
int seconds = (ms / 1000) % 60;
int minutes = (ms / (1000 * 60)) % 60;
return String.format("%02d:%02d", minutes, seconds);
}
public void safeGetDuration() {
if (mediaPlayer != null) {
try {
// 增加状态校验,避免在错误状态下崩溃
int duration = mediaPlayer.getDuration();
System.out.println("当前获取的时长: " + duration);
} catch (IllegalStateException e) {
System.err.println("播放器尚未准备好或已释放");
}
}
}
}
🌻5. 用法总结
| 调用层级 | 核心职责 | 关键特性/影响 |
|---|---|---|
| 应用框架层 | 状态检查与 JNI 接口提供 | 强依赖 Prepared 状态 |
| 系统服务层 | 跨进程同步时长信息 | 毫秒级精度返回 |
| 引擎处理层 | 管理 NuPlayer 中的元数据缓存 |
在 prepare 阶段完成时长提取 |
| 数据解析层 | 负责解封装(Demux)与头信息扫描 | 决定了变码率文件的时长准确性 |
| 文件输入层 | 提供物理二进制数据 | 网络流(HTTP/RTSP)可能导致时长未知 |