Initial Commit

This commit is contained in:
Rajuahamedkst 2025-09-12 16:08:17 +08:00
commit 1d71a02738
237 changed files with 64293 additions and 0 deletions

View file

@ -0,0 +1,751 @@
// 语音消息管理器 - 微信小程序专用
// 处理语音录制、播放、转换、上传等功能
const apiClient = require('./api-client.js');
const performanceMonitor = require('./performance-monitor.js');
/**
* 语音消息管理器
* 功能
* 1. 语音录制和停止
* 2. 语音播放和暂停
* 3. 语音文件管理
* 4. 语音质量控制
* 5. 语音时长限制
* 6. 语音格式转换
*/
class VoiceMessageManager {
constructor() {
this.isInitialized = false;
// 录音配置
this.recordConfig = {
duration: 60000, // 最大录音时长 60秒
sampleRate: 16000, // 采样率
numberOfChannels: 1, // 声道数
encodeBitRate: 48000, // 编码码率
format: 'mp3', // 录音格式
frameSize: 50, // 帧大小
minDuration: 1000, // 最小录音时长 1秒
maxDuration: 60000 // 最大录音时长 60秒
};
// 播放配置
this.playConfig = {
autoplay: false,
loop: false,
volume: 1.0,
playbackRate: 1.0
};
// 录音器实例
this.recorderManager = null;
this.innerAudioContext = null;
// 录音状态
this.recordingState = {
isRecording: false,
isPaused: false,
startTime: null,
duration: 0,
tempFilePath: null,
fileSize: 0
};
// 播放状态
this.playingState = {
isPlaying: false,
isPaused: false,
currentTime: 0,
duration: 0,
currentVoiceId: null,
playingMessageId: null
};
// 语音文件缓存
this.voiceCache = new Map();
// 事件监听器
this.eventListeners = new Map();
// 权限状态
this.permissionGranted = false;
this.init();
}
// 初始化语音消息管理器
async init() {
if (this.isInitialized) return;
console.log('🎤 初始化语音消息管理器...');
try {
// 初始化录音管理器
this.initRecorderManager();
// 初始化音频播放器
this.initAudioPlayer();
// 检查录音权限
await this.checkRecordPermission();
this.isInitialized = true;
console.log('✅ 语音消息管理器初始化完成');
} catch (error) {
console.error('❌ 语音消息管理器初始化失败:', error);
}
}
// 初始化录音管理器
initRecorderManager() {
this.recorderManager = wx.getRecorderManager();
// 录音开始事件
this.recorderManager.onStart(() => {
console.log('🎤 录音开始');
this.recordingState.isRecording = true;
this.recordingState.startTime = Date.now();
this.triggerEvent('recordStart');
});
// 录音暂停事件
this.recorderManager.onPause(() => {
console.log('⏸️ 录音暂停');
this.recordingState.isPaused = true;
this.triggerEvent('recordPause');
});
// 录音恢复事件
this.recorderManager.onResume(() => {
console.log('▶️ 录音恢复');
this.recordingState.isPaused = false;
this.triggerEvent('recordResume');
});
// 录音停止事件
this.recorderManager.onStop((res) => {
console.log('⏹️ 录音停止:', res);
this.recordingState.isRecording = false;
this.recordingState.isPaused = false;
this.recordingState.duration = res.duration;
this.recordingState.tempFilePath = res.tempFilePath;
this.recordingState.fileSize = res.fileSize;
this.triggerEvent('recordStop', {
duration: res.duration,
tempFilePath: res.tempFilePath,
fileSize: res.fileSize
});
});
// 录音错误事件
this.recorderManager.onError((error) => {
console.error('❌ 录音错误:', error);
this.recordingState.isRecording = false;
this.recordingState.isPaused = false;
this.triggerEvent('recordError', error);
});
// 录音帧数据事件(用于实时波形显示)
this.recorderManager.onFrameRecorded((res) => {
this.triggerEvent('recordFrame', {
frameBuffer: res.frameBuffer,
isLastFrame: res.isLastFrame
});
});
}
// 初始化音频播放器
initAudioPlayer() {
this.innerAudioContext = wx.createInnerAudioContext();
// 播放开始事件
this.innerAudioContext.onPlay(() => {
console.log('🔊 语音播放开始');
this.playingState.isPlaying = true;
this.playingState.isPaused = false;
this.triggerEvent('playStart');
});
// 播放暂停事件
this.innerAudioContext.onPause(() => {
console.log('⏸️ 语音播放暂停');
this.playingState.isPaused = true;
this.triggerEvent('playPause');
});
// 播放结束事件
this.innerAudioContext.onEnded(() => {
console.log('⏹️ 语音播放结束');
this.playingState.isPlaying = false;
this.playingState.isPaused = false;
this.playingState.currentTime = 0;
this.playingState.currentVoiceId = null;
this.playingState.playingMessageId = null;
this.triggerEvent('playEnd');
});
// 播放错误事件
this.innerAudioContext.onError((error) => {
console.error('❌ 语音播放错误:', error);
this.playingState.isPlaying = false;
this.playingState.isPaused = false;
this.triggerEvent('playError', error);
});
// 播放进度更新事件
this.innerAudioContext.onTimeUpdate(() => {
this.playingState.currentTime = this.innerAudioContext.currentTime;
this.playingState.duration = this.innerAudioContext.duration;
this.triggerEvent('playTimeUpdate', {
currentTime: this.playingState.currentTime,
duration: this.playingState.duration
});
});
// 音频加载完成事件
this.innerAudioContext.onCanplay(() => {
console.log('🎵 语音加载完成');
this.triggerEvent('playCanplay');
});
}
// 🎤 ===== 录音功能 =====
// 开始录音
async startRecording(options = {}) {
if (!this.isInitialized) {
throw new Error('语音消息管理器未初始化');
}
if (this.recordingState.isRecording) {
throw new Error('正在录音中');
}
// 检查录音权限
if (!this.permissionGranted) {
const granted = await this.requestRecordPermission();
if (!granted) {
throw new Error('录音权限被拒绝');
}
}
try {
const recordOptions = {
...this.recordConfig,
...options
};
console.log('🎤 开始录音,配置:', recordOptions);
// 重置录音状态
this.recordingState = {
isRecording: false,
isPaused: false,
startTime: null,
duration: 0,
tempFilePath: null,
fileSize: 0
};
this.recorderManager.start(recordOptions);
} catch (error) {
console.error('❌ 开始录音失败:', error);
throw error;
}
}
// 停止录音
stopRecording() {
if (!this.recordingState.isRecording) {
throw new Error('当前没有在录音');
}
try {
console.log('⏹️ 停止录音');
this.recorderManager.stop();
} catch (error) {
console.error('❌ 停止录音失败:', error);
throw error;
}
}
// 暂停录音
pauseRecording() {
if (!this.recordingState.isRecording || this.recordingState.isPaused) {
throw new Error('当前状态无法暂停录音');
}
try {
console.log('⏸️ 暂停录音');
this.recorderManager.pause();
} catch (error) {
console.error('❌ 暂停录音失败:', error);
throw error;
}
}
// 恢复录音
resumeRecording() {
if (!this.recordingState.isRecording || !this.recordingState.isPaused) {
throw new Error('当前状态无法恢复录音');
}
try {
console.log('▶️ 恢复录音');
this.recorderManager.resume();
} catch (error) {
console.error('❌ 恢复录音失败:', error);
throw error;
}
}
// 取消录音
cancelRecording() {
if (!this.recordingState.isRecording) {
return;
}
try {
console.log('❌ 取消录音');
this.recorderManager.stop();
// 重置录音状态
this.recordingState = {
isRecording: false,
isPaused: false,
startTime: null,
duration: 0,
tempFilePath: null,
fileSize: 0
};
this.triggerEvent('recordCancel');
} catch (error) {
console.error('❌ 取消录音失败:', error);
}
}
// 🔊 ===== 播放功能 =====
// 播放语音消息
async playVoiceMessage(voiceUrl, messageId = null, options = {}) {
if (!this.isInitialized) {
throw new Error('语音消息管理器未初始化');
}
try {
// 如果正在播放其他语音,先停止
if (this.playingState.isPlaying) {
this.stopPlaying();
}
console.log('🔊 播放语音消息:', voiceUrl);
// 设置播放配置
const playOptions = {
...this.playConfig,
...options
};
this.innerAudioContext.src = voiceUrl;
this.innerAudioContext.autoplay = playOptions.autoplay;
this.innerAudioContext.loop = playOptions.loop;
this.innerAudioContext.volume = playOptions.volume;
this.innerAudioContext.playbackRate = playOptions.playbackRate;
// 更新播放状态
this.playingState.currentVoiceId = voiceUrl;
this.playingState.playingMessageId = messageId;
// 开始播放
this.innerAudioContext.play();
} catch (error) {
console.error('❌ 播放语音消息失败:', error);
throw error;
}
}
// 暂停播放
pausePlaying() {
if (!this.playingState.isPlaying || this.playingState.isPaused) {
return;
}
try {
console.log('⏸️ 暂停播放');
this.innerAudioContext.pause();
} catch (error) {
console.error('❌ 暂停播放失败:', error);
}
}
// 恢复播放
resumePlaying() {
if (!this.playingState.isPlaying || !this.playingState.isPaused) {
return;
}
try {
console.log('▶️ 恢复播放');
this.innerAudioContext.play();
} catch (error) {
console.error('❌ 恢复播放失败:', error);
}
}
// 停止播放
stopPlaying() {
if (!this.playingState.isPlaying) {
return;
}
try {
console.log('⏹️ 停止播放');
this.innerAudioContext.stop();
// 重置播放状态
this.playingState.isPlaying = false;
this.playingState.isPaused = false;
this.playingState.currentTime = 0;
this.playingState.currentVoiceId = null;
this.playingState.playingMessageId = null;
} catch (error) {
console.error('❌ 停止播放失败:', error);
}
}
// 设置播放进度
seekTo(time) {
if (!this.playingState.isPlaying) {
return;
}
try {
this.innerAudioContext.seek(time);
this.playingState.currentTime = time;
} catch (error) {
console.error('❌ 设置播放进度失败:', error);
}
}
// 📁 ===== 文件管理 =====
// 上传语音文件
async uploadVoiceFile(tempFilePath, duration) {
try {
console.log('📤 上传语音文件:', tempFilePath);
// 使用微信小程序的上传文件API
const uploadResult = await new Promise((resolve, reject) => {
wx.uploadFile({
url: `${apiClient.baseUrl}/api/v1/file/upload`,
filePath: tempFilePath,
name: 'file',
formData: {
file_type: 'audio',
usage_type: 'message',
duration: duration.toString()
},
header: {
'Authorization': `Bearer ${apiClient.getToken()}`
},
success: (res) => {
try {
const data = JSON.parse(res.data);
resolve({
success: data.success || res.statusCode === 200,
data: data.data || data,
message: data.message
});
} catch (error) {
resolve({
success: res.statusCode === 200,
data: { url: res.data },
message: '上传成功'
});
}
},
fail: reject
});
});
if (uploadResult.success) {
const fileUrl = uploadResult.data.url || uploadResult.data.file_url || uploadResult.data.fileUrl;
console.log('✅ 语音文件上传成功:', fileUrl);
return {
success: true,
url: fileUrl,
duration: duration,
size: this.recordingState.fileSize
};
} else {
throw new Error(uploadResult.message || '上传失败');
}
} catch (error) {
console.error('❌ 上传语音文件失败:', error);
throw error;
}
}
// 下载语音文件到本地
async downloadVoiceFile(voiceUrl) {
try {
// 检查缓存
if (this.voiceCache.has(voiceUrl)) {
const cached = this.voiceCache.get(voiceUrl);
if (this.isFileExists(cached.localPath)) {
return cached.localPath;
} else {
this.voiceCache.delete(voiceUrl);
}
}
console.log('📥 下载语音文件:', voiceUrl);
const downloadResult = await new Promise((resolve, reject) => {
wx.downloadFile({
url: voiceUrl,
success: resolve,
fail: reject
});
});
if (downloadResult.statusCode === 200) {
// 缓存文件路径
this.voiceCache.set(voiceUrl, {
localPath: downloadResult.tempFilePath,
downloadTime: Date.now()
});
console.log('✅ 语音文件下载成功:', downloadResult.tempFilePath);
return downloadResult.tempFilePath;
} else {
throw new Error(`下载失败,状态码: ${downloadResult.statusCode}`);
}
} catch (error) {
console.error('❌ 下载语音文件失败:', error);
throw error;
}
}
// 检查文件是否存在
isFileExists(filePath) {
try {
const fileManager = wx.getFileSystemManager();
const stats = fileManager.statSync(filePath);
return stats.isFile();
} catch (error) {
return false;
}
}
// 🔐 ===== 权限管理 =====
// 检查录音权限
async checkRecordPermission() {
try {
const setting = await new Promise((resolve, reject) => {
wx.getSetting({
success: resolve,
fail: reject
});
});
this.permissionGranted = setting.authSetting['scope.record'] === true;
console.log('🔐 录音权限状态:', this.permissionGranted);
return this.permissionGranted;
} catch (error) {
console.error('❌ 检查录音权限失败:', error);
return false;
}
}
// 请求录音权限
async requestRecordPermission() {
try {
await new Promise((resolve, reject) => {
wx.authorize({
scope: 'scope.record',
success: resolve,
fail: reject
});
});
this.permissionGranted = true;
console.log('✅ 录音权限获取成功');
return true;
} catch (error) {
console.error('❌ 录音权限获取失败:', error);
this.permissionGranted = false;
// 引导用户到设置页面
this.showPermissionGuide();
return false;
}
}
// 显示权限引导
showPermissionGuide() {
wx.showModal({
title: '需要录音权限',
content: '使用语音消息功能需要录音权限,请在设置中开启',
confirmText: '去设置',
cancelText: '取消',
success: (res) => {
if (res.confirm) {
wx.openSetting({
success: (settingRes) => {
if (settingRes.authSetting['scope.record']) {
this.permissionGranted = true;
console.log('✅ 用户已开启录音权限');
}
}
});
}
}
});
}
// 📊 ===== 状态管理 =====
// 获取录音状态
getRecordingState() {
return { ...this.recordingState };
}
// 获取播放状态
getPlayingState() {
return { ...this.playingState };
}
// 是否正在录音
isRecording() {
return this.recordingState.isRecording;
}
// 是否正在播放
isPlaying() {
return this.playingState.isPlaying;
}
// 获取当前播放的消息ID
getCurrentPlayingMessageId() {
return this.playingState.playingMessageId;
}
// 🎧 ===== 事件管理 =====
// 注册事件监听器
on(event, callback) {
if (!this.eventListeners.has(event)) {
this.eventListeners.set(event, []);
}
this.eventListeners.get(event).push(callback);
}
// 移除事件监听器
off(event, callback) {
if (this.eventListeners.has(event)) {
const listeners = this.eventListeners.get(event);
const index = listeners.indexOf(callback);
if (index > -1) {
listeners.splice(index, 1);
}
}
}
// 触发事件
triggerEvent(event, data = null) {
if (this.eventListeners.has(event)) {
const listeners = this.eventListeners.get(event);
listeners.forEach(callback => {
try {
callback(data);
} catch (error) {
console.error(`❌ 事件处理器错误 [${event}]:`, error);
}
});
}
}
// 🔧 ===== 工具方法 =====
// 格式化时长
formatDuration(duration) {
const seconds = Math.floor(duration / 1000);
const minutes = Math.floor(seconds / 60);
const remainingSeconds = seconds % 60;
if (minutes > 0) {
return `${minutes}:${remainingSeconds.toString().padStart(2, '0')}`;
} else {
return `${remainingSeconds}"`;
}
}
// 获取语音文件大小描述
getFileSizeDescription(fileSize) {
if (fileSize < 1024) {
return `${fileSize}B`;
} else if (fileSize < 1024 * 1024) {
return `${(fileSize / 1024).toFixed(1)}KB`;
} else {
return `${(fileSize / (1024 * 1024)).toFixed(1)}MB`;
}
}
// 清理缓存
clearCache() {
this.voiceCache.clear();
console.log('🧹 语音文件缓存已清理');
}
// 销毁管理器
destroy() {
// 停止录音和播放
if (this.recordingState.isRecording) {
this.cancelRecording();
}
if (this.playingState.isPlaying) {
this.stopPlaying();
}
// 销毁音频上下文
if (this.innerAudioContext) {
this.innerAudioContext.destroy();
this.innerAudioContext = null;
}
// 清理缓存和事件监听器
this.clearCache();
this.eventListeners.clear();
this.isInitialized = false;
console.log('🎤 语音消息管理器已销毁');
}
}
// 创建全局实例
const voiceMessageManager = new VoiceMessageManager();
module.exports = voiceMessageManager;