From 3d631dbadf98f3f77c6e955cb36219217ef73ab5 Mon Sep 17 00:00:00 2001 From: 唐浩 <15928265276@163.com> Date: 星期二, 09 九月 2025 12:56:48 +0800 Subject: [PATCH] change by tanghao --- app/src/main/java/com/ziang/driver/utils/AudioRecoderUtils.java | 364 +++++++++++++++++++++++++-------------------------- 1 files changed, 177 insertions(+), 187 deletions(-) diff --git a/app/src/main/java/com/ziang/driver/utils/AudioRecoderUtils.java b/app/src/main/java/com/ziang/driver/utils/AudioRecoderUtils.java index 3ab7b4e..677dc2e 100644 --- a/app/src/main/java/com/ziang/driver/utils/AudioRecoderUtils.java +++ b/app/src/main/java/com/ziang/driver/utils/AudioRecoderUtils.java @@ -10,10 +10,49 @@ 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; @@ -27,130 +66,13 @@ private OnAudioStatusUpdateListener audioStatusUpdateListener; private MediaPlayer mMediaPlayer; - /** - * 文件存储默认sdcard/record - */ - public AudioRecoderUtils() { - //默认保存路径为/sdcard/record/下 - this(Environment.getExternalStorageDirectory() + "/record/"); - } - - public AudioRecoderUtils(String filePath) { - - File path = new File(filePath); - if (!path.exists()) - path.mkdirs(); - - this.FolderPath = filePath; - } + // --- 新增:播放队列和状态变量 --- + private Queue<String> playQueue = new LinkedList<>(); // 存储待播放的音频文件路径 + private boolean isPlayingQueue = false; // 标记是否正在播放队列中的音频 + // --- 新增结束 --- private long startTime; private long endTime; - - - /** - * 开始录音 使用amr格式 - * 录音文件 - * - * @return - */ - public void startRecord() { - // 开始录音 - /* ①Initial:实例化MediaRecorder对象 */ - if (mMediaRecorder == null) - mMediaRecorder = new MediaRecorder(); - try { - /* ②setAudioSource/setVedioSource */ - mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);// 设置麦克风 - /* ②设置音频文件的编码:AAC/AMR_NB/AMR_MB/Default 声音的(波形)的采样 */ - mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.AAC_ADTS); - /* - * ②设置输出文件的格式:THREE_GPP/MPEG-4/RAW_AMR/Default THREE_GPP(3gp格式 - * ,H263视频/ARM音频编码)、MPEG-4、RAW_AMR(只支持音频且音频编码要求为AMR_NB) - */ - mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); - - filePath = FolderPath + TimeUtils.getCurrentTime() + ".caf"; - /* ③准备 */ - mMediaRecorder.setOutputFile(filePath); - mMediaRecorder.setMaxDuration(MAX_LENGTH); - mMediaRecorder.prepare(); - /* ④开始 */ - mMediaRecorder.start(); - // AudioRecord audioRecord. - /* 获取开始时间* */ - startTime = System.currentTimeMillis(); - updateMicStatus(); - Log.e("fan", "startTime" + startTime); - } catch (IllegalStateException e) { - Log.i(TAG, "call startAmr(File mRecAudioFile) failed!" + e.getMessage()); - } catch (IOException e) { - Log.i(TAG, "call startAmr(File mRecAudioFile) failed!" + e.getMessage()); - } - } - - /** - * 停止录音 - */ - public long stopRecord() { - if (mMediaRecorder == null) - return 0L; - endTime = System.currentTimeMillis(); - - //有一些网友反应在5.0以上在调用stop的时候会报错,翻阅了一下谷歌文档发现上面确实写的有可能会报错的情况,捕获异常清理一下就行了,感谢大家反馈! - try { - mMediaRecorder.stop(); - mMediaRecorder.reset(); - mMediaRecorder.release(); - mMediaRecorder = null; - - audioStatusUpdateListener.onStop(filePath); - filePath = ""; - - } catch (RuntimeException e) { - if (mMediaRecorder != null) { - mMediaRecorder.reset(); - mMediaRecorder.release(); - mMediaRecorder = null; - } - File file = new File(filePath); - if (file.exists()) - file.delete(); - - filePath = ""; - - } - return endTime - startTime; - } - - public String getFilePath() { - return filePath; - } - - /** - * 取消录音 - */ - public void cancelRecord() { - - try { - - mMediaRecorder.stop(); - mMediaRecorder.reset(); - mMediaRecorder.release(); - mMediaRecorder = null; - - } catch (RuntimeException e) { - mMediaRecorder.reset(); - mMediaRecorder.release(); - mMediaRecorder = null; - } - File file = new File(filePath); - if (file.exists()) - file.delete(); - - filePath = ""; - - } private final Handler mHandler = new Handler(); private Runnable mUpdateMicStatusTimer = new Runnable() { @@ -163,9 +85,7 @@ private int BASE = 1; private int SPACE = 100;// 间隔取样时间 - public void setOnAudioStatusUpdateListener(OnAudioStatusUpdateListener audioStatusUpdateListener) { - this.audioStatusUpdateListener = audioStatusUpdateListener; - } + /** * 更新麦克状态 @@ -206,80 +126,150 @@ public void onFinishPlay(); } - //开始播放录音 + public void startplayMusic(Context context, String filePath) { - if (mMediaPlayer != null) { - stopPlayMusic(); - } - mMediaPlayer = new MediaPlayer(); - try { - Log.e("mmp", "path:" + filePath); - mMediaPlayer.setDataSource(context, Uri.parse(filePath)); - mMediaPlayer.prepareAsync(); - mMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() { - @Override - public void onPrepared(MediaPlayer mp) { - // 装载完毕回调 - mMediaPlayer.start(); - if (audioStatusUpdateListener != null) - audioStatusUpdateListener.onStartPlay(); - } - }); - mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { - @Override - public void onCompletion(MediaPlayer mp) { - if (audioStatusUpdateListener != null) - audioStatusUpdateListener.onFinishPlay(); - stopPlayMusic(); -// BaseEvent baseEvent = new BaseEvent(BaseEvent.SEND_AODIO_PLAY_OVER); -// baseEvent.setMsg(filePath); -// EventBus.getDefault().post(baseEvent); - } - }); + // 将新的音频路径添加到播放队列 + playQueue.offer(filePath); // 将文件路径添加到队列尾部 + Log.e("AudioRecoderUtils", "Added to queue: " + filePath + ", Queue size: " + playQueue.size()); - } catch (Exception e) { - e.printStackTrace(); + // 如果当前没有在播放,则开始播放队列中的第一个 + if (!isPlayingQueue) { + playNextInQueue(context); } } - //结束录音播放 - public void stopPlayMusic() { - if (mMediaPlayer == null) - return; - try { - mMediaPlayer.stop(); - mMediaPlayer.release(); - if (audioStatusUpdateListener != null) + // --- 新增:播放队列中的下一个音频 --- + 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; + } } } - /** - * 获取文件长度 - * - * @param context - * @param filePath - * @return - */ - public int getMusicTime(Context context, String filePath) { - if (mMediaPlayer != null) { - stopPlayMusic(); - } - mMediaPlayer = new MediaPlayer(); - try { - mMediaPlayer.setDataSource(context, Uri.parse(filePath)); - mMediaPlayer.prepare(); - int duration = mMediaPlayer.getDuration(); - if (0 != duration) { - mMediaPlayer.release(); - return duration / 1000; - } - } catch (IOException e) { - e.printStackTrace(); - } - return 0; + // 新增设置监听器的方法 + public void setOnAudioStatusUpdateListener(OnAudioStatusUpdateListener audioStatusUpdateListener) { + this.audioStatusUpdateListener = audioStatusUpdateListener; } + } \ No newline at end of file -- Gitblit v1.7.1