This commit is contained in:
李志鹏
2025-10-21 13:42:51 +08:00
19 changed files with 1902 additions and 197 deletions

231
src/utils/audioUtils.ts Normal file
View File

@@ -0,0 +1,231 @@
/**
* 音频处理工具函数
* 为后续集成第三方TTS方案做准备
*/
// 音频文件类型定义
export interface AudioFile {
blob: Blob
name: string
size: number
type: string
duration?: number
}
// 音频格式转换选项
export interface AudioConvertOptions {
format: 'webm' | 'mp3' | 'wav' | 'ogg'
quality?: number
bitrate?: number
}
/**
* 将Blob转换为指定格式的音频文件
* @param audioBlob 原始音频Blob
* @param options 转换选项
* @returns Promise<Blob> 转换后的音频Blob
*/
export const convertAudioFormat = async (
audioBlob: Blob,
options: AudioConvertOptions
): Promise<Blob> => {
return new Promise((resolve, reject) => {
try {
const audio = new Audio()
const url = URL.createObjectURL(audioBlob)
audio.onloadedmetadata = () => {
// 这里可以添加格式转换逻辑
// 目前直接返回原始Blob实际项目中可以使用Web Audio API或第三方库
URL.revokeObjectURL(url)
resolve(audioBlob)
}
audio.onerror = () => {
URL.revokeObjectURL(url)
reject(new Error('音频加载失败'))
}
audio.src = url
} catch (error) {
reject(error)
}
})
}
/**
* 获取音频文件信息
* @param audioBlob 音频Blob
* @returns Promise<AudioFile> 音频文件信息
*/
export const getAudioFileInfo = async (audioBlob: Blob): Promise<AudioFile> => {
return new Promise((resolve, reject) => {
const audio = new Audio()
const url = URL.createObjectURL(audioBlob)
audio.onloadedmetadata = () => {
const audioFile: AudioFile = {
blob: audioBlob,
name: `audio-${Date.now()}.webm`,
size: audioBlob.size,
type: audioBlob.type,
duration: audio.duration
}
URL.revokeObjectURL(url)
resolve(audioFile)
}
audio.onerror = () => {
URL.revokeObjectURL(url)
reject(new Error('无法获取音频信息'))
}
audio.src = url
})
}
/**
* 压缩音频文件
* @param audioBlob 原始音频Blob
* @param quality 压缩质量 (0-1)
* @returns Promise<Blob> 压缩后的音频Blob
*/
export const compressAudio = async (
audioBlob: Blob,
quality: number = 0.8
): Promise<Blob> => {
// 这里可以实现音频压缩逻辑
// 目前直接返回原始Blob实际项目中可以使用Web Audio API
return audioBlob
}
/**
* 上传音频文件到服务器
* @param audioFile 音频文件
* @param uploadUrl 上传地址
* @returns Promise<string> 服务器返回的文件ID或URL
*/
export const uploadAudioFile = async (
audioFile: AudioFile,
uploadUrl: string
): Promise<string> => {
const formData = new FormData()
formData.append('audio', audioFile.blob, audioFile.name)
formData.append('duration', audioFile.duration?.toString() || '0')
try {
const response = await fetch(uploadUrl, {
method: 'POST',
body: formData
})
if (!response.ok) {
throw new Error(`上传失败: ${response.statusText}`)
}
const result = await response.json()
return result.fileId || result.url
} catch (error) {
console.error('音频上传失败:', error)
throw error
}
}
/**
* 为第三方TTS服务准备音频数据
* @param audioBlob 音频Blob
* @param serviceType TTS服务类型
* @returns Promise<any> 准备好的音频数据
*/
export const prepareAudioForTTS = async (
audioBlob: Blob,
serviceType: 'openai' | 'azure' | 'aws' | 'google'
): Promise<any> => {
const audioFile = await getAudioFileInfo(audioBlob)
switch (serviceType) {
case 'openai':
return {
file: audioFile.blob,
model: 'whisper-1',
language: 'zh',
response_format: 'json'
}
case 'azure':
return {
audio: audioFile.blob,
language: 'zh-CN',
format: 'json'
}
case 'aws':
return {
Audio: audioFile.blob,
LanguageCode: 'zh-CN',
MediaFormat: 'webm'
}
case 'google':
return {
audio: {
content: await blobToBase64(audioFile.blob)
},
config: {
encoding: 'WEBM_OPUS',
languageCode: 'zh-CN',
sampleRateHertz: 16000
}
}
default:
throw new Error(`不支持的TTS服务类型: ${serviceType}`)
}
}
/**
* 将Blob转换为Base64字符串
* @param blob Blob对象
* @returns Promise<string> Base64字符串
*/
export const blobToBase64 = (blob: Blob): Promise<string> => {
return new Promise((resolve, reject) => {
const reader = new FileReader()
reader.onload = () => resolve(reader.result as string)
reader.onerror = reject
reader.readAsDataURL(blob)
})
}
/**
* 验证音频文件格式
* @param blob 音频Blob
* @returns boolean 是否为有效的音频格式
*/
export const validateAudioFormat = (blob: Blob): boolean => {
const validTypes = [
'audio/webm',
'audio/mp3',
'audio/wav',
'audio/ogg',
'audio/mpeg'
]
return validTypes.includes(blob.type)
}
/**
* 获取音频录制权限
* @returns Promise<boolean> 是否获得权限
*/
export const requestAudioPermission = async (): Promise<boolean> => {
try {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true })
stream.getTracks().forEach(track => track.stop())
return true
} catch (error) {
console.error('获取音频权限失败:', error)
return false
}
}