package com.ziang.driver.utils;
|
|
import android.content.Context;
|
import android.media.MediaPlayer;
|
import android.media.MediaRecorder;
|
import android.net.Uri;
|
import android.os.Environment;
|
import android.os.Handler;
|
import android.util.Log;
|
|
import java.io.File;
|
import java.io.IOException;
|
import java.util.LinkedList;
|
import java.util.Queue;
|
|
import cn.sinata.xldutils.utils.TimeUtils;
|
|
public class AudioRecoderUtils {
|
// --- 单例实例 ---
|
private static AudioRecoderUtils instance; // 静态变量,持有唯一实例
|
|
// 私有化构造函数,防止外部直接创建新实例
|
private AudioRecoderUtils() {
|
// 默认保存路径为/sdcard/record/下
|
this(Environment.getExternalStorageDirectory() + "/record/");
|
}
|
|
// 私有化带 filePath 的构造函数
|
private AudioRecoderUtils(String filePath) {
|
File path = new File(filePath);
|
if (!path.exists())
|
path.mkdirs();
|
this.FolderPath = filePath;
|
}
|
|
// --- 获取单例的公共静态方法 ---
|
// 使用 synchronized 确保线程安全,防止多线程环境下创建多个实例
|
public static synchronized AudioRecoderUtils getInstance() {
|
if (instance == null) {
|
instance = new AudioRecoderUtils();
|
}
|
return instance;
|
}
|
|
// 如果需要指定文件路径,可以提供另一个获取实例的方法
|
public static synchronized AudioRecoderUtils getInstance(String filePath) {
|
if (instance == null) {
|
instance = new AudioRecoderUtils(filePath);
|
}
|
// 如果实例已存在但路径不同,这里通常不修改路径。
|
// 若要支持动态路径,需要更复杂的逻辑,或者在外部处理路径逻辑。
|
return instance;
|
}
|
// --- 单例修改结束 ---
|
|
|
//文件路径
|
private String filePath;
|
//文件夹路径
|
private String FolderPath;
|
|
private MediaRecorder mMediaRecorder;
|
private final String TAG = "fan";
|
public static final int MAX_LENGTH = 1000 * 60;// 最大录音时长1000*60*10;
|
|
private OnAudioStatusUpdateListener audioStatusUpdateListener;
|
private MediaPlayer mMediaPlayer;
|
|
// --- 新增:播放队列和状态变量 ---
|
private Queue<String> playQueue = new LinkedList<>(); // 存储待播放的音频文件路径
|
private boolean isPlayingQueue = false; // 标记是否正在播放队列中的音频
|
// --- 新增结束 ---
|
|
private long startTime;
|
private long endTime;
|
|
private final Handler mHandler = new Handler();
|
private Runnable mUpdateMicStatusTimer = new Runnable() {
|
public void run() {
|
updateMicStatus();
|
}
|
};
|
|
|
private int BASE = 1;
|
private int SPACE = 100;// 间隔取样时间
|
|
|
|
/**
|
* 更新麦克状态
|
*/
|
private void updateMicStatus() {
|
|
if (mMediaRecorder != null) {
|
double ratio = (double) mMediaRecorder.getMaxAmplitude() / BASE;
|
double db = 0;// 分贝
|
if (ratio > 1) {
|
db = 20 * Math.log10(ratio);
|
if (null != audioStatusUpdateListener) {
|
audioStatusUpdateListener.onUpdate(db, System.currentTimeMillis() - startTime);
|
}
|
}
|
mHandler.postDelayed(mUpdateMicStatusTimer, SPACE);
|
}
|
}
|
|
public interface OnAudioStatusUpdateListener {
|
/**
|
* 录音中...
|
*
|
* @param db 当前声音分贝
|
* @param time 录音时长
|
*/
|
public void onUpdate(double db, long time);
|
|
/**
|
* 停止录音
|
*
|
* @param filePath 保存路径
|
*/
|
public void onStop(String filePath);
|
|
public void onStartPlay();
|
|
public void onFinishPlay();
|
}
|
|
|
public void startplayMusic(Context context, String filePath) {
|
// 将新的音频路径添加到播放队列
|
playQueue.offer(filePath); // 将文件路径添加到队列尾部
|
Log.e("AudioRecoderUtils", "Added to queue: " + filePath + ", Queue size: " + playQueue.size());
|
|
// 如果当前没有在播放,则开始播放队列中的第一个
|
if (!isPlayingQueue) {
|
playNextInQueue(context);
|
}
|
}
|
|
// --- 新增:播放队列中的下一个音频 ---
|
private void playNextInQueue(Context context) {
|
if (playQueue.isEmpty()) {
|
isPlayingQueue = false;
|
Log.d(TAG, "playNextInQueue: Play queue finished.");
|
if (audioStatusUpdateListener != null) {
|
audioStatusUpdateListener.onFinishPlay();
|
}
|
return;
|
}
|
|
String currentFilePath = playQueue.poll();
|
Log.d(TAG, "playNextInQueue: Now attempting to play from queue: " + currentFilePath);
|
|
// 确保每次播放前,旧的 MediaPlayer 都已经被彻底释放
|
// 这行虽然在前面已经有,但此处是核心逻辑,再次强调其重要性
|
if (mMediaPlayer != null) {
|
stopPlayMusic(); // 确保旧的播放器已完全释放
|
}
|
|
mMediaPlayer = new MediaPlayer();
|
isPlayingQueue = true; // 标记正在播放队列中的音频
|
|
try {
|
mMediaPlayer.setDataSource(context, Uri.parse(currentFilePath));
|
Log.d(TAG, "playNextInQueue: Set data source for: " + currentFilePath + ", preparing async.");
|
mMediaPlayer.prepareAsync();
|
|
mMediaPlayer.setOnPreparedListener(mp -> {
|
Log.d(TAG, "onPrepared: MediaPlayer prepared for " + currentFilePath + ", starting playback.");
|
mp.start(); // 这里使用 mp,它是 MediaPlayer 实例本身
|
if (audioStatusUpdateListener != null)
|
audioStatusUpdateListener.onStartPlay();
|
});
|
|
mMediaPlayer.setOnCompletionListener(mp -> {
|
Log.d(TAG, "onCompletion: Playback finished for " + currentFilePath + ", moving to next.");
|
// 注意:onFinishPlay() 应该在整个队列播放完毕时才触发,而不是单个文件
|
stopPlayMusic(); // 播放完成后释放当前播放器
|
playNextInQueue(context); // 递归调用,播放队列中的下一个
|
});
|
|
mMediaPlayer.setOnErrorListener((mp, what, extra) -> {
|
Log.e(TAG, "onError: MediaPlayer error occurred: what=" + what + ", extra=" + extra + " for " + currentFilePath);
|
// 错误发生时,也要通知UI(如果需要),并尝试播放下一个
|
if (audioStatusUpdateListener != null)
|
audioStatusUpdateListener.onFinishPlay(); // 通知当前文件播放失败
|
stopPlayMusic(); // 释放当前播放器
|
playNextInQueue(context); // 继续播放队列中的下一个
|
return true; // 表示已处理错误
|
});
|
|
} catch (IOException e) {
|
// IOException 通常是文件不存在、无法读取或URL无效
|
Log.e(TAG, "IOException during MediaPlayer setup for " + currentFilePath, e);
|
// 捕获异常后,也要尝试播放下一个
|
if (audioStatusUpdateListener != null)
|
audioStatusUpdateListener.onFinishPlay(); // 通知播放失败
|
stopPlayMusic(); // 释放当前播放器
|
playNextInQueue(context); // 继续播放队列中的下一个
|
} catch (IllegalArgumentException e) {
|
// IllegalArgumentException 可能是URI格式不正确等
|
Log.e(TAG, "IllegalArgumentException during MediaPlayer setup for " + currentFilePath, e);
|
if (audioStatusUpdateListener != null)
|
audioStatusUpdateListener.onFinishPlay(); // 通知播放失败
|
stopPlayMusic();
|
playNextInQueue(context);
|
} catch (SecurityException e) {
|
// SecurityException 可能是权限问题
|
Log.e(TAG, "SecurityException during MediaPlayer setup for " + currentFilePath, e);
|
if (audioStatusUpdateListener != null)
|
audioStatusUpdateListener.onFinishPlay(); // 通知播放失败
|
stopPlayMusic();
|
playNextInQueue(context);
|
} catch (IllegalStateException e) {
|
// IllegalStateException 可能是 MediaPlayer 状态不正确
|
Log.e(TAG, "IllegalStateException during MediaPlayer setup for " + currentFilePath, e);
|
if (audioStatusUpdateListener != null)
|
audioStatusUpdateListener.onFinishPlay(); // 通知播放失败
|
stopPlayMusic();
|
playNextInQueue(context);
|
} catch (Exception e) { // 最后捕获所有其他未知异常
|
Log.e(TAG, "Generic Exception during MediaPlayer setup for " + currentFilePath, e);
|
e.printStackTrace();
|
if (audioStatusUpdateListener != null)
|
audioStatusUpdateListener.onFinishPlay(); // 通知播放失败
|
stopPlayMusic();
|
playNextInQueue(context);
|
}
|
}
|
// --- 新增结束 ---
|
|
|
public void stopPlayMusic() {
|
// playQueue.clear();
|
isPlayingQueue = false;
|
if (mMediaPlayer == null) {
|
Log.d(TAG, "stopPlayMusic: MediaPlayer is null, nothing to stop.");
|
return;
|
}
|
try {
|
// 检查 MediaPlayer 是否处于可停止的状态
|
// 如果正在准备或处于错误状态,直接 release 可能更安全
|
if (mMediaPlayer.isPlaying()) {
|
mMediaPlayer.stop(); // 停止当前播放
|
Log.d(TAG, "stopPlayMusic: Stopped active MediaPlayer.");
|
}
|
mMediaPlayer.release(); // 释放资源
|
mMediaPlayer = null; // 置为 null,防止内存泄漏和引用旧对象
|
Log.d(TAG, "stopPlayMusic: MediaPlayer released and set to null.");
|
} catch (IllegalStateException e) {
|
// 捕获 MediaPlayer 状态异常,例如在 prepareAsync() 期间调用 stop()
|
Log.e(TAG, "stopPlayMusic: IllegalStateException during stop/release.", e);
|
// 即使异常,也要尝试释放并置null,防止后续问题
|
if (mMediaPlayer != null) {
|
mMediaPlayer.release();
|
mMediaPlayer = null;
|
}
|
} catch (Exception e) {
|
// 捕获其他未知异常
|
Log.e(TAG, "stopPlayMusic: General Exception during stop/release.", e);
|
if (mMediaPlayer != null) {
|
mMediaPlayer.release();
|
mMediaPlayer = null;
|
}
|
}
|
}
|
|
// 新增设置监听器的方法
|
public void setOnAudioStatusUpdateListener(OnAudioStatusUpdateListener audioStatusUpdateListener) {
|
this.audioStatusUpdateListener = audioStatusUpdateListener;
|
}
|
|
|
}
|