chore: initialize qiming workspace repository
This commit is contained in:
443
qiming-mobile/uni_modules/jz-h5-recorder-manager/js/index.js
Normal file
443
qiming-mobile/uni_modules/jz-h5-recorder-manager/js/index.js
Normal file
@@ -0,0 +1,443 @@
|
||||
// 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,最大值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 (
|
||||
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,
|
||||
};
|
||||
Reference in New Issue
Block a user