UNPKG

jz-h5-recorder-manager

Version:

一个完全兼容 uni.getRecorderManager API 的H5录音插件,支持H5、App、小程序等全平台

409 lines (347 loc) 10.3 kB
// H5录音管理插件 // 完全兼容 uni.getRecorderManager API // 检测当前平台 function isH5Platform() { // #ifdef H5 return true // #endif return false } // H5录音管理器实现 class H5RecorderManager { constructor() { this.mediaRecorder = null this.audioContext = null this.analyser = null this.audioChunks = [] this.startTime = null this.isPaused = false this.isRecording = false // 事件回调函数 this.callbacks = { onStart: [], onPause: [], onResume: [], onStop: [], onError: [], onFrameRecorded: [], onInterruptionBegin: [], onInterruptionEnd: [] } // 默认配置 this.defaultOptions = { duration: 60000, // 录音时长,单位ms,最大值600000(10分钟) sampleRate: 44100, // 采样率,有效值 8000/16000/44100 numberOfChannels: 1, // 录音通道数,有效值 1/2 encodeBitRate: 192000, // 编码码率,有效值见下表格 format: 'mp3', // 音频格式,有效值 aac/mp3/wav/PCM frameSize: null // 指定帧大小,单位 KB } } // 开始录音 start(options = {}) { if (this.isRecording) { this._triggerError('录音已在进行中') return } const config = Object.assign({}, this.defaultOptions, options) // 检查浏览器支持 if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) { this._triggerError('当前浏览器不支持录音功能') return } // 请求麦克风权限并开始录音 navigator.mediaDevices.getUserMedia({ audio: { sampleRate: config.sampleRate, channelCount: config.numberOfChannels } }) .then(stream => { this._initRecorder(stream, config) }) .catch(error => { console.error('获取麦克风权限失败:', error) this._triggerError('获取麦克风权限失败: ' + error.message) }) } // 初始化录音器 _initRecorder(stream, config) { try { // 创建AudioContext用于音频处理 this.audioContext = new (window.AudioContext || window.webkitAudioContext)() // 创建MediaRecorder const mimeType = this._getMimeType(config.format) if (!MediaRecorder.isTypeSupported(mimeType)) { console.warn(`不支持${config.format}格式,将使用默认格式`) } this.mediaRecorder = new MediaRecorder(stream, { mimeType: MediaRecorder.isTypeSupported(mimeType) ? mimeType : undefined, audioBitsPerSecond: config.encodeBitRate }) this.audioChunks = [] this.startTime = Date.now() this.isRecording = true this.isPaused = false // 设置事件监听 this.mediaRecorder.ondataavailable = (event) => { if (event.data.size > 0) { this.audioChunks.push(event.data) // 触发帧录制回调 if (config.frameSize && this.callbacks.onFrameRecorded.length > 0) { this._triggerFrameRecorded(event.data, false) } } } this.mediaRecorder.onstop = () => { this._handleRecordStop(config) } this.mediaRecorder.onerror = (event) => { this._triggerError('录音过程中出现错误: ' + event.error) } // 开始录音 if (config.frameSize) { // 如果设置了frameSize,定时收集数据 this.mediaRecorder.start(config.frameSize * 1024) // frameSize is in KB } else { this.mediaRecorder.start() } // 设置录音时长限制 if (config.duration && config.duration > 0) { setTimeout(() => { if (this.isRecording && !this.isPaused) { this.stop() } }, config.duration) } // 触发开始事件 this._triggerStart() } catch (error) { console.error('初始化录音器失败:', error) this._triggerError('初始化录音器失败: ' + error.message) } } // 获取MIME类型 _getMimeType(format) { const mimeTypes = { 'mp3': 'audio/mpeg', 'wav': 'audio/wav', 'aac': 'audio/aac', 'PCM': 'audio/wav' } return mimeTypes[format] || 'audio/webm' } // 暂停录音 pause() { if (!this.isRecording || this.isPaused) { return } if (this.mediaRecorder && this.mediaRecorder.state === 'recording') { this.mediaRecorder.pause() this.isPaused = true this._triggerPause() } } // 继续录音 resume() { if (!this.isRecording || !this.isPaused) { return } if (this.mediaRecorder && this.mediaRecorder.state === 'paused') { this.mediaRecorder.resume() this.isPaused = false this._triggerResume() } } // 停止录音 stop() { if (!this.isRecording) { return } if (this.mediaRecorder && this.mediaRecorder.state !== 'inactive') { this.mediaRecorder.stop() } // 停止所有音频轨道 if (this.mediaRecorder && this.mediaRecorder.stream) { this.mediaRecorder.stream.getTracks().forEach(track => track.stop()) } this.isRecording = false this.isPaused = false } // 处理录音停止 _handleRecordStop(config) { if (this.audioChunks.length === 0) { this._triggerError('没有录制到音频数据') return } // 创建音频文件 const audioBlob = new Blob(this.audioChunks, { type: this._getMimeType(config.format) }) // 创建临时文件URL const tempFilePath = URL.createObjectURL(audioBlob) // 计算录音时长 const duration = this.startTime ? (Date.now() - this.startTime) / 1000 : 0 // 触发停止事件 this._triggerStop({ tempFilePath: tempFilePath, duration: duration, fileSize: audioBlob.size }) // 清理资源 this._cleanup() } // 清理资源 _cleanup() { if (this.audioContext) { this.audioContext.close() this.audioContext = null } this.mediaRecorder = null this.audioChunks = [] this.startTime = null } // 事件监听方法 onStart(callback) { if (typeof callback === 'function') { this.callbacks.onStart.push(callback) } } onPause(callback) { if (typeof callback === 'function') { this.callbacks.onPause.push(callback) } } onResume(callback) { if (typeof callback === 'function') { this.callbacks.onResume.push(callback) } } onStop(callback) { if (typeof callback === 'function') { this.callbacks.onStop.push(callback) } } onError(callback) { if (typeof callback === 'function') { this.callbacks.onError.push(callback) } } onFrameRecorded(callback) { if (typeof callback === 'function') { this.callbacks.onFrameRecorded.push(callback) } } onInterruptionBegin(callback) { if (typeof callback === 'function') { this.callbacks.onInterruptionBegin.push(callback) } } onInterruptionEnd(callback) { if (typeof callback === 'function') { this.callbacks.onInterruptionEnd.push(callback) } } // 移除事件监听方法(支付宝小程序兼容) offStart(callback) { this._removeCallback('onStart', callback) } offPause(callback) { this._removeCallback('onPause', callback) } offResume(callback) { this._removeCallback('onResume', callback) } offStop(callback) { this._removeCallback('onStop', callback) } offFrameRecorded(callback) { this._removeCallback('onFrameRecorded', callback) } // 移除回调函数 _removeCallback(eventName, callback) { if (callback && this.callbacks[eventName]) { const index = this.callbacks[eventName].indexOf(callback) if (index > -1) { this.callbacks[eventName].splice(index, 1) } } } // 触发事件的私有方法 _triggerStart() { this.callbacks.onStart.forEach(callback => { try { callback() } catch (error) { console.error('onStart callback error:', error) } }) } _triggerPause() { this.callbacks.onPause.forEach(callback => { try { callback() } catch (error) { console.error('onPause callback error:', error) } }) } _triggerResume() { this.callbacks.onResume.forEach(callback => { try { callback() } catch (error) { console.error('onResume callback error:', error) } }) } _triggerStop(result) { this.callbacks.onStop.forEach(callback => { try { callback(result) } catch (error) { console.error('onStop callback error:', error) } }) } _triggerError(errMsg) { this.callbacks.onError.forEach(callback => { try { callback({ errMsg }) } catch (error) { console.error('onError callback error:', error) } }) } _triggerFrameRecorded(frameBuffer, isLastFrame = false) { this.callbacks.onFrameRecorded.forEach(callback => { try { callback({ frameBuffer, isLastFrame }) } catch (error) { console.error('onFrameRecorded callback error:', error) } }) } } // 全局实例 let recorderManagerInstance = null // 主函数:获取录音管理器 function getRecorderManager() { if (isH5Platform()) { // H5平台使用自定义实现 if (!recorderManagerInstance) { recorderManagerInstance = new H5RecorderManager() } return recorderManagerInstance } else { // 非H5平台直接使用uni.getRecorderManager return uni.getRecorderManager() } } // 导出 export default { getRecorderManager }