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 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; } }