Files
qiming/qiming-mobile/uni_modules/jz-h5-recorder-manager/js/index.js

444 lines
11 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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.touchEndCancelFlag = false;
// 事件回调函数
this.callbacks = {
onStart: [],
onPause: [],
onResume: [],
onStop: [],
onError: [],
onFrameRecorded: [],
onInterruptionBegin: [],
onInterruptionEnd: [],
};
// 默认配置
this.defaultOptions = {
duration: 60000, // 录音时长单位ms最大值60000010分钟
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 (
typeof navigator.mediaDevices === 'undefined' ||
!navigator.mediaDevices.getUserMedia
) {
// 这里做一个消息原因增加说明
const isHttpsProtocol =
(typeof window !== 'undefined' &&
window.location.protocol === 'https:') ||
false;
const msg = !isHttpsProtocol
? '需要在HTTPS模式下且同意浏览器录音授权时支持语音'
: '当前浏览器不支持录音功能';
this._triggerError(msg);
return;
}
// 请求麦克风权限并开始录音
navigator.mediaDevices
.getUserMedia({
audio: {
sampleRate: config.sampleRate,
channelCount: config.numberOfChannels,
},
})
.then(stream => {
console.log('获取麦克风权限成功: stream', stream);
if (this.touchEndCancelFlag) {
this.touchEndCancelFlag = false;
return;
}
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)) {
uni.showToast({
title: `不支持${config.format}格式,将使用默认格式`,
icon: 'none',
});
}
this.mediaRecorder = new MediaRecorder(stream, {
mimeType: MediaRecorder.isTypeSupported(mimeType)
? mimeType
: undefined,
audioBitsPerSecond: config.encodeBitRate,
});
console.log(stream, this.mediaRecorder.mimeType);
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;
this.touchEndCancelFlag = false;
}
// 事件监听方法
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.touchEndCancelFlag = false;
this.callbacks.onResume.forEach(callback => {
try {
callback();
} catch (error) {
console.error('onResume callback error:', error);
}
});
}
_triggerStop(result) {
// 重置触摸结束取消录音标志
this.touchEndCancelFlag = false;
this.callbacks.onStop.forEach(callback => {
try {
callback(result);
} catch (error) {
console.error('onStop callback error:', error);
}
});
}
_triggerError(errMsg) {
// 重置触摸结束取消录音标志
this.touchEndCancelFlag = false;
this.callbacks.onError.forEach(callback => {
try {
callback({ errMsg });
} catch (error) {
console.error('onError callback error:', error);
}
});
}
_triggerFrameRecorded(frameBuffer, isLastFrame = false) {
// 重置触摸结束取消录音标志
this.touchEndCancelFlag = 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,
};