chore: initialize qiming workspace repository

This commit is contained in:
Codex
2026-05-29 14:22:48 +08:00
commit bfd67a0f2c
10750 changed files with 1885711 additions and 0 deletions

View 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最大值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,
};