| | |
| | | |
| | | 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 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() { |
| | |
| | | private int BASE = 1; |
| | | private int SPACE = 100;// 间隔取样时间 |
| | | |
| | | public void setOnAudioStatusUpdateListener(OnAudioStatusUpdateListener audioStatusUpdateListener) { |
| | | this.audioStatusUpdateListener = audioStatusUpdateListener; |
| | | } |
| | | |
| | | |
| | | /** |
| | | * 更新麦克状态 |
| | |
| | | public void onFinishPlay(); |
| | | } |
| | | |
| | | //开始播放录音 |
| | | |
| | | public void startplayMusic(Context context, String filePath) { |
| | | if (mMediaPlayer != null) { |
| | | stopPlayMusic(); |
| | | // 将新的音频路径添加到播放队列 |
| | | 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 { |
| | | Log.e("mmp", "path:" + filePath); |
| | | mMediaPlayer.setDataSource(context, Uri.parse(filePath)); |
| | | mMediaPlayer.setDataSource(context, Uri.parse(currentFilePath)); |
| | | Log.d(TAG, "playNextInQueue: Set data source for: " + currentFilePath + ", preparing async."); |
| | | mMediaPlayer.prepareAsync(); |
| | | mMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() { |
| | | @Override |
| | | public void onPrepared(MediaPlayer mp) { |
| | | // 装载完毕回调 |
| | | mMediaPlayer.start(); |
| | | |
| | | mMediaPlayer.setOnPreparedListener(mp -> { |
| | | Log.d(TAG, "onPrepared: MediaPlayer prepared for " + currentFilePath + ", starting playback."); |
| | | mp.start(); // 这里使用 mp,它是 MediaPlayer 实例本身 |
| | | 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); |
| | | } |
| | | }); |
| | | |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | } |
| | | } |
| | | mMediaPlayer.setOnCompletionListener(mp -> { |
| | | Log.d(TAG, "onCompletion: Playback finished for " + currentFilePath + ", moving to next."); |
| | | // 注意:onFinishPlay() 应该在整个队列播放完毕时才触发,而不是单个文件 |
| | | stopPlayMusic(); // 播放完成后释放当前播放器 |
| | | playNextInQueue(context); // 递归调用,播放队列中的下一个 |
| | | }); |
| | | |
| | | //结束录音播放 |
| | | public void stopPlayMusic() { |
| | | if (mMediaPlayer == null) |
| | | return; |
| | | try { |
| | | mMediaPlayer.stop(); |
| | | mMediaPlayer.release(); |
| | | mMediaPlayer.setOnErrorListener((mp, what, extra) -> { |
| | | Log.e(TAG, "onError: MediaPlayer error occurred: what=" + what + ", extra=" + extra + " for " + currentFilePath); |
| | | // 错误发生时,也要通知UI(如果需要),并尝试播放下一个 |
| | | if (audioStatusUpdateListener != null) |
| | | audioStatusUpdateListener.onFinishPlay(); |
| | | } catch (IllegalStateException e) { |
| | | e.printStackTrace(); |
| | | } |
| | | } |
| | | audioStatusUpdateListener.onFinishPlay(); // 通知当前文件播放失败 |
| | | stopPlayMusic(); // 释放当前播放器 |
| | | playNextInQueue(context); // 继续播放队列中的下一个 |
| | | return true; // 表示已处理错误 |
| | | }); |
| | | |
| | | /** |
| | | * 获取文件长度 |
| | | * |
| | | * @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) { |
| | | // 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); |
| | | } |
| | | return 0; |
| | | } |
| | | // --- 新增结束 --- |
| | | |
| | | |
| | | 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; |
| | | } |
| | | |
| | | |
| | | } |