Ionic 3从可暂停恢复的录制文件获取Base64音频字符串异常问题
兄弟,我之前在Android上用cordova-plugin-media做录音功能时,也踩过几乎一模一样的坑!结合我当时排查的经验,给你理清楚问题出在哪,以及怎么解决:
关于3GP格式无回调的问题
cordova-plugin-media在Android上录制3GP时,暂停再恢复后,文件的内部索引指针会出现异常状态,导致cordova-plugin-file调用readAsDataURL时,底层卡在文件读取的逻辑里,既不触发成功回调也不触发错误回调。本质是Android原生MediaRecorder在暂停恢复后,文件写入的状态没有被插件正确处理,留下了隐蔽的bug。
关于MP3/WAV只保留第一部分的问题
这俩格式本身就不支持暂停-恢复式的分段录制追加。Android原生MediaRecorder对MP3/WAV格式,暂停后重新启动录制时,会直接覆盖之前的录制内容或者重新创建文件,而cordova-plugin-media并没有做分段录制内容的合并处理,所以最终只能拿到第一次录制的那段音频。
方案1:自定义分段录制+原生合并(推荐)
既然插件本身的暂停恢复逻辑不靠谱,我们可以手动实现分段录制,最后合并成完整音频:
- 每次点击“暂停”时,停止当前录制并保存临时音频文件;
- 点击“恢复”时,创建新的录制实例生成新的临时文件;
- 录制完成后,用Android原生的
MediaMuxer工具合并所有临时文件; - 最后读取合并后的文件转为Base64用于播放。
给你个Ionic/Angular的代码示例参考:
import { Media, MediaObject } from '@ionic-native/media/ngx'; import { File } from '@ionic-native/file/ngx'; // 存储临时录制文件的路径 private tempAudioFiles: string[] = []; private currentRecorder: MediaObject | null = null; // 启动/恢复录制 startOrResumeRecording() { // 生成唯一的临时文件名 const tempFileName = `temp_rec_${Date.now()}.3gp`; const savePath = this.file.externalDataDirectory + tempFileName; // 创建新的录制实例 this.currentRecorder = this.media.create(savePath); this.currentRecorder.startRecord(); // 单个片段录制完成(暂停时触发stopRecord会走这里) this.currentRecorder.onSuccess.subscribe(() => { this.tempAudioFiles.push(savePath); this.currentRecorder = null; }); this.currentRecorder.onError.subscribe(err => { console.error('单段录制失败:', err); this.currentRecorder = null; }); } // 暂停录制 pauseRecording() { if (this.currentRecorder) { // 停止当前片段录制,触发onSuccess保存临时文件 this.currentRecorder.stopRecord(); } } // 完成录制并合并 async finishRecording() { // 确保最后一段录制被保存 this.pauseRecording(); // 这里需要自己写一个Cordova自定义插件,封装Android的MediaMuxer合并逻辑 // 假设我们有个插件叫AudioMerger,提供mergeAudioFiles方法 const finalFilePath = await this.audioMerger.mergeAudioFiles( this.tempAudioFiles, this.file.externalDataDirectory + 'final_voice.3gp' ); // 读取合并后的文件为Base64 this.file.readAsDataURL(this.file.externalDataDirectory, 'final_voice.3gp') .then(base64Audio => { // 用HTML audio标签播放 const audioEl = document.createElement('audio'); audioEl.src = base64Audio; audioEl.play(); // 清理临时文件 this.tempAudioFiles.forEach(file => this.file.removeFile(file)); this.tempAudioFiles = []; }) .catch(readErr => console.error('读取音频文件失败:', readErr)); }
方案2:规避暂停恢复逻辑(简单场景适用)
如果你的业务场景可以接受“分段手动录制”的体验(比如用户点击多次“录制”按钮,每次录一段,最后合并),那可以直接放弃插件的暂停功能,改用多次录制后合并的方式,完全避开插件的bug。
方案3:切换到Capacitor的音频插件
如果项目允许迁移到Capacitor,推荐用@capacitor-community/audio-recorder插件,它对Android的暂停恢复录制支持更完善,底层处理了原生MediaRecorder的状态问题,还能直接获取录制文件的Base64,省去cordova-plugin-file的读取步骤,省心很多。
- 记得在AndroidManifest中申请
RECORD_AUDIO和WRITE_EXTERNAL_STORAGE权限,并且在Ionic中正确处理权限请求; - 尽量用真实设备测试,模拟器的音频录制兼容性问题很多;
- 优先用3GP格式,它是Android原生支持最好的格式,文件体积也更小,合并起来更稳定。
内容的提问来源于stack exchange,提问作者Jack

