/** * 语音对讲工具类 * 支持录音、播放、权限管理等功能 * 适用于uniapp项目 */ class VoiceIntercom { constructor() { this.recorderManager = null; this.innerAudioContext = null; this.isRecording = false; this.isPlaying = false; this.recordPath = ''; this.audioList = []; // 存储录音文件列表 this.maxRecordTime = 60000; // 最大录音时长60秒 this.recordOptions = { duration: 60000, sampleRate: 16000, numberOfChannels: 1, encodeBitRate: 96000, format: 'mp3' }; } /** * 初始化录音管理器 */ initRecorder() { if (!this.recorderManager) { this.recorderManager = uni.getRecorderManager(); this.setupRecorderEvents(); } return this.recorderManager; } /** * 初始化音频播放器 */ initPlayer() { if (!this.innerAudioContext) { this.innerAudioContext = uni.createInnerAudioContext(); this.setupPlayerEvents(); } return this.innerAudioContext; } /** * 设置录音事件监听 */ setupRecorderEvents() { this.recorderManager.onStart(() => { console.log('录音开始'); this.isRecording = true; this.onRecordStart && this.onRecordStart(); }); this.recorderManager.onStop((res) => { console.log('录音结束', res); this.isRecording = false; this.recordPath = res.tempFilePath; this.audioList.push({ path: res.tempFilePath, duration: res.duration, fileSize: res.fileSize, timestamp: Date.now() }); this.onRecordStop && this.onRecordStop(res); }); this.recorderManager.onError((err) => { console.error('录音错误', err); this.isRecording = false; this.onRecordError && this.onRecordError(err); }); this.recorderManager.onFrameRecorded((res) => { // 实时录音数据,可用于实时传输 this.onFrameRecorded && this.onFrameRecorded(res); }); } /** * 设置播放器事件监听 */ setupPlayerEvents() { this.innerAudioContext.onPlay(() => { console.log('开始播放'); this.isPlaying = true; this.onPlayStart && this.onPlayStart(); }); this.innerAudioContext.onEnded(() => { console.log('播放结束'); this.isPlaying = false; this.onPlayEnd && this.onPlayEnd(); }); this.innerAudioContext.onError((err) => { console.error('播放错误', err); this.isPlaying = false; this.onPlayError && this.onPlayError(err); }); this.innerAudioContext.onTimeUpdate(() => { this.onTimeUpdate && this.onTimeUpdate({ currentTime: this.innerAudioContext.currentTime, duration: this.innerAudioContext.duration }); }); } /** * 检查录音权限 */ async checkRecordPermission() { return new Promise((resolve, reject) => { // #ifdef H5 this.checkH5RecordPermission().then(resolve).catch(reject) // #endif // #ifdef MP-WEIXIN || MP-ALIPAY || MP-BAIDU || MP-TOUTIAO this.checkMiniProgramRecordPermission().then(resolve).catch(reject) // #endif // #ifdef APP-PLUS this.checkAppRecordPermission().then(resolve).catch(reject) // #endif }) } /** * 申请录音权限 */ async requestRecordPermission() { return new Promise((resolve, reject) => { // #ifdef H5 this.requestH5RecordPermission().then(resolve).catch(reject) // #endif // #ifdef MP-WEIXIN || MP-ALIPAY || MP-BAIDU || MP-TOUTIAO this.requestMiniProgramRecordPermission().then(resolve).catch(reject) // #endif // #ifdef APP-PLUS this.requestAppRecordPermission().then(resolve).catch(reject) // #endif }) } /** * H5平台检查录音权限 */ async checkH5RecordPermission() { return new Promise((resolve, reject) => { // #ifdef H5 if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) { navigator.mediaDevices.getUserMedia({ audio: true }) .then(() => { resolve(true) }) .catch((error) => { console.error('H5录音权限检查失败:', error) reject(error) }) } else { reject(new Error('浏览器不支持录音功能')) } // #endif // #ifndef H5 resolve(true) // #endif }) } /** * H5平台申请录音权限 */ async requestH5RecordPermission() { return this.checkH5RecordPermission() } /** * 小程序平台检查录音权限 */ async checkMiniProgramRecordPermission() { return new Promise((resolve, reject) => { // #ifdef MP-WEIXIN || MP-ALIPAY || MP-BAIDU || MP-TOUTIAO uni.getSetting({ success: (res) => { if (res.authSetting['scope.record'] === true) { resolve(true) } else if (res.authSetting['scope.record'] === false) { reject(new Error('用户拒绝了录音权限')) } else { // 未询问过权限 resolve(false) } }, fail: (err) => { reject(err) } }) // #endif // #ifndef MP-WEIXIN && !MP-ALIPAY && !MP-BAIDU && !MP-TOUTIAO resolve(true) // #endif }) } /** * 小程序平台申请录音权限 */ async requestMiniProgramRecordPermission() { return new Promise((resolve, reject) => { // #ifdef MP-WEIXIN || MP-ALIPAY || MP-BAIDU || MP-TOUTIAO uni.authorize({ scope: 'scope.record', success: () => { resolve(true) }, fail: (err) => { if (err.errMsg.includes('auth deny')) { // 用户拒绝,引导到设置页面 this.showPermissionModal('录音权限', '需要录音权限才能使用语音对讲功能') .then(() => { uni.openSetting({ success: (res) => { if (res.authSetting['scope.record']) { resolve(true) } else { reject(new Error('用户拒绝录音权限')) } }, fail: reject }) }) .catch(reject) } else { reject(err) } } }) // #endif // #ifndef MP-WEIXIN && !MP-ALIPAY && !MP-BAIDU && !MP-TOUTIAO resolve(true) // #endif }) } /** * App平台检查录音权限 */ async checkAppRecordPermission() { return new Promise((resolve, reject) => { // #ifdef APP-PLUS plus.android.requestPermissions( ['android.permission.RECORD_AUDIO'], (result) => { const granted = result.granted && result.granted.length > 0 resolve(granted) }, (error) => { reject(error) } ) // #endif // #ifndef APP-PLUS resolve(true) // #endif }) } /** * App平台申请录音权限 */ async requestAppRecordPermission() { return this.checkAppRecordPermission() } /** * 显示权限申请弹窗 */ showPermissionModal(permissionName, description) { return new Promise((resolve, reject) => { uni.showModal({ title: '权限申请', content: `需要${permissionName}才能使用相关功能。${description}`, confirmText: '去设置', cancelText: '取消', success: (res) => { if (res.confirm) { resolve() } else { reject(new Error('用户取消权限申请')) } } }) }) } /** * 开始录音 */ async startRecord(options = {}) { try { // 检查权限 const hasPermission = await this.checkRecordPermission(); // 如果没有权限,尝试申请 if (!hasPermission) { await this.requestRecordPermission(); } // 初始化录音管理器 this.initRecorder(); // 合并录音配置 const recordOptions = {...this.recordOptions, ...options }; // 开始录音 this.recorderManager.start(recordOptions); return true; } catch (error) { console.error('开始录音失败', error); throw error; } } /** * 停止录音 */ stopRecord() { if (this.recorderManager && this.isRecording) { this.recorderManager.stop(); return true; } return false; } /** * 播放录音 */ async playRecord(audioPath = null) { try { const path = audioPath || this.recordPath; if (!path) { throw new Error('没有可播放的录音文件'); } // 初始化播放器 this.initPlayer(); // 停止当前播放 if (this.isPlaying) { this.stopPlay(); } // 设置音频源并播放 this.innerAudioContext.src = path; this.innerAudioContext.play(); return true; } catch (error) { console.error('播放录音失败', error); throw error; } } /** * 停止播放 */ stopPlay() { if (this.innerAudioContext && this.isPlaying) { this.innerAudioContext.stop(); this.isPlaying = false; return true; } return false; } /** * 暂停播放 */ pausePlay() { if (this.innerAudioContext && this.isPlaying) { this.innerAudioContext.pause(); return true; } return false; } /** * 恢复播放 */ resumePlay() { if (this.innerAudioContext && !this.isPlaying) { this.innerAudioContext.play(); return true; } return false; } /** * 设置播放进度 */ seekPlay(position) { if (this.innerAudioContext) { this.innerAudioContext.seek(position); return true; } return false; } /** * 获取录音列表 */ getAudioList() { return this.audioList; } /** * 删除录音文件 */ deleteAudio(index) { if (index >= 0 && index < this.audioList.length) { const audio = this.audioList[index]; // 删除文件 uni.getFileSystemManager().unlink({ filePath: audio.path, success: () => { console.log('文件删除成功'); }, fail: (err) => { console.error('文件删除失败', err); } }); // 从列表中移除 this.audioList.splice(index, 1); return true; } return false; } /** * 清空所有录音 */ clearAllAudio() { this.audioList.forEach(audio => { uni.getFileSystemManager().unlink({ filePath: audio.path, fail: (err) => { console.error('文件删除失败', err); } }); }); this.audioList = []; this.recordPath = ''; } /** * 上传录音文件 */ async uploadAudio(audioPath, uploadUrl, formData = {}) { return new Promise((resolve, reject) => { uni.uploadFile({ url: uploadUrl, filePath: audioPath, name: 'audio', formData: formData, success: (res) => { resolve(res); }, fail: (err) => { reject(err); } }); }); } /** * 获取录音状态 */ getRecordStatus() { return { isRecording: this.isRecording, isPlaying: this.isPlaying, recordPath: this.recordPath, audioCount: this.audioList.length }; } /** * 销毁实例 */ destroy() { if (this.recorderManager) { this.recorderManager.stop(); this.recorderManager = null; } if (this.innerAudioContext) { this.innerAudioContext.destroy(); this.innerAudioContext = null; } this.isRecording = false; this.isPlaying = false; } } // 创建单例实例 const voiceIntercom = new VoiceIntercom(); export default voiceIntercom;