简介: 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.setLooping调用流程与实战。
在多媒体应用中,循环播放(Looping)是一个极其常见的需求,例如游戏的背景音乐、冥想类应用的白噪音或是手机铃声。MediaPlayer.setLooping 允许开发者通过一个简单的布尔值开关,控制媒体流在到达末尾时是立即停止并触发完成回调,还是自动跳转回起始点重新开始播放。
🌻2. 用法与应用场景
MediaPlayer.setLooping 方法用于设置播放器是否进入循环模式。
-
用法说明 :该方法可以在
MediaPlayer的任何状态下调用,但其逻辑生效是在播放到达媒体结束点(End of Stream)时。 -
运行结果:
-
true:播放结束后自动 Seek 到 0 并继续播放,不触发OnCompletionListener。 -
false:播放结束后停止,并触发OnCompletionListener。 -
应用场景:
- 游戏背景音乐:确保 BGM 在游戏运行期间不间断循环。
- 展厅引导语音:在公共场所循环播放特定的通知或导览信息。
- 短视频/GIF 播放:实现类似 TikTok 的短视频无限自动重播效果。
🌻3. 调用流程剖析
3.1 核心步骤
- Java 层参数透传 :
MediaPlayer.java接收boolean参数,并调用native_setLooping进入 JNI 层。 - Native 状态同步 :JNI 将指令分发给
mediaplayer.cpp,最终通过 Binder 封送到MediaServer进程中的播放引擎对象。 - 引擎标志位设定 :在
NuPlayer引擎中,该标志位被存储在Source或Player的配置参数中。 - 边界检测(End of Stream) :当解码器读取到媒体文件的最后一个 Buffer 时,引擎会检查
mLooping标志。 - 自动跳转指令 :如果循环开启,引擎不会向上层发送
MEDIA_PLAYBACK_COMPLETE消息,而是内部执行一个seekTo(0)的原子操作,并刷新解码器缓冲区,直接开始新一轮的渲染。
3.2 涉及核心时序图
Decoder/Source NuPlayer Engine MediaPlayer Native MediaPlayer Java 应用代码层 Decoder/Source NuPlayer Engine MediaPlayer Native MediaPlayer Java 应用代码层 播放进行中... alt [Looping == true] [Looping == false] 调用 setLooping(true) 调用 native_setLooping 更新引擎循环标志位 发送流结束信号 (EOS) 检查 Looping 标志位 执行内部 SeekTo(0) 重新启动数据流读取 发送完成通知 触发 OnCompletionListener
🌻4. 实战应用案例
本案例展示了如何正确地在初始化阶段开启循环播放,并提供了一个动态切换循环模式的安全封装。
java
public class LoopPlaybackManager {
private MediaPlayer mediaPlayer;
public void startBgm(Context context, Uri uri) {
if (mediaPlayer == null) {
mediaPlayer = new MediaPlayer();
}
try {
mediaPlayer.setDataSource(context, uri);
// 1. 在准备前或准备后均可设置,这里演示在准备前设置
mediaPlayer.setLooping(true);
mediaPlayer.setOnPreparedListener(mp -> {
System.out.println("BGM 开始无限循环播放");
mp.start();
});
mediaPlayer.prepareAsync();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 根据用户交互动态切换循环状态
* @param shouldLoop 是否循环
*/
public void toggleLooping(boolean shouldLoop) {
if (mediaPlayer != null) {
try {
// 实时生效:如果在播放末尾切换,会立即改变下一次的行为
mediaPlayer.setLooping(shouldLoop);
boolean isNowLooping = mediaPlayer.isLooping();
System.out.println("当前循环状态已更新为: " + isNowLooping);
} catch (IllegalStateException e) {
System.err.println("播放器处于非法状态,无法设置循环");
}
}
}
public void stopAndRelease() {
if (mediaPlayer != null) {
mediaPlayer.stop();
mediaPlayer.release();
mediaPlayer = null;
}
}
}
🌻5. 用法总结
| 调用层级 | 核心职责 | 关键特性/影响 |
|---|---|---|
| 应用框架层 | 提供布尔值接口与状态获取 | isLooping() 可随时查询当前模式 |
| 系统服务层 | 跨进程维护播放配置属性 | 确保参数在 MediaServer 侧实时同步 |
| 引擎处理层 | 执行边界逻辑判断与自动重置 | 屏蔽 onCompletion 事件,实现自动续播 |
| 解码驱动层 | 响应 flush 与重新定位指令 |
循环时需要重新初始化解码上下文 |
| 用户体验层 | 提供无缝的听感体验 | 循环间隔受文件开头/结尾静音段影响 |