四零语境后端代码仓库
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

429 lines
10 KiB

/**
* UniApp TTS服务类
* 封装文字转语音功能,提供简单易用的API
*/
import { API_CONFIG, TTS_CONFIG, UTILS, ERROR_CODES, ERROR_MESSAGES } from './uniapp-tts-config.js';
class TTSService {
constructor() {
this.audioContext = null;
this.isPlaying = false;
this.isConverting = false;
this.voiceList = [];
this.currentAudioUrl = '';
}
/**
* 初始化TTS服务
* @param {Object} options 配置选项
*/
async init(options = {}) {
try {
// 加载音色列表
await this.loadVoiceList();
// 初始化音频上下文
this.initAudioContext();
console.log('TTS服务初始化成功');
return { success: true };
} catch (error) {
console.error('TTS服务初始化失败:', error);
return { success: false, error: error.message };
}
}
/**
* 加载音色列表
*/
async loadVoiceList() {
try {
// 先尝试从缓存获取
const cached = this.getCachedVoiceList();
if (cached) {
this.voiceList = cached;
return cached;
}
// 从服务器获取
const response = await this.request({
url: API_CONFIG.ENDPOINTS.VOICE_LIST,
method: 'GET'
});
if (response.success && response.result) {
this.voiceList = response.result;
this.cacheVoiceList(this.voiceList);
return this.voiceList;
} else {
throw new Error(response.message || '获取音色列表失败');
}
} catch (error) {
console.error('加载音色列表失败:', error);
throw error;
}
}
/**
* 文字转语音
* @param {Object} params 转换参数
* @param {string} params.text 文本内容
* @param {number} params.speed 语速
* @param {number} params.voiceType 音色ID
* @param {number} params.volume 音量
* @param {string} params.codec 音频格式
* @param {string} params.userId 用户ID
*/
async textToVoice(params) {
if (this.isConverting) {
throw new Error('正在转换中,请稍候...');
}
// 参数验证
const validation = this.validateParams(params);
if (!validation.valid) {
throw new Error(validation.message);
}
this.isConverting = true;
const startTime = Date.now();
try {
// 构建请求参数
const requestParams = {
text: params.text,
speed: params.speed || TTS_CONFIG.SPEED.DEFAULT,
voiceType: params.voiceType || 0,
volume: params.volume || TTS_CONFIG.VOLUME.DEFAULT,
codec: params.codec || TTS_CONFIG.CODEC.DEFAULT,
userId: params.userId || this.generateUserId()
};
// 发起请求
const audioData = await this.requestBinary({
url: API_CONFIG.ENDPOINTS.TEXT_TO_VOICE,
method: 'GET',
data: requestParams
});
if (!audioData || audioData.byteLength === 0) {
throw new Error('转换失败,未返回音频数据');
}
// 创建音频文件
const audioUrl = await this.createAudioFile(audioData, requestParams.codec);
// 计算转换耗时
const convertTime = ((Date.now() - startTime) / 1000).toFixed(2);
// 更新当前音频URL
this.currentAudioUrl = audioUrl;
return {
success: true,
audioUrl: audioUrl,
audioSize: UTILS.formatFileSize(audioData.byteLength),
convertTime: convertTime,
params: requestParams
};
} catch (error) {
console.error('文字转语音失败:', error);
throw error;
} finally {
this.isConverting = false;
}
}
/**
* 播放音频
* @param {string} audioUrl 音频文件路径(可选,默认使用最后转换的音频)
*/
async playAudio(audioUrl) {
const targetUrl = audioUrl || this.currentAudioUrl;
if (!targetUrl) {
throw new Error('没有可播放的音频文件');
}
if (this.isPlaying) {
this.stopAudio();
}
return new Promise((resolve, reject) => {
try {
this.initAudioContext();
this.audioContext.src = targetUrl;
this.isPlaying = true;
this.audioContext.onPlay(() => {
console.log('音频开始播放');
resolve({ success: true, action: 'play_started' });
});
this.audioContext.onEnded(() => {
console.log('音频播放结束');
this.isPlaying = false;
});
this.audioContext.onError((error) => {
console.error('音频播放失败:', error);
this.isPlaying = false;
reject(new Error('音频播放失败'));
});
this.audioContext.play();
} catch (error) {
this.isPlaying = false;
reject(error);
}
});
}
/**
* 停止音频播放
*/
stopAudio() {
if (this.audioContext && this.isPlaying) {
this.audioContext.stop();
this.isPlaying = false;
console.log('音频播放已停止');
}
}
/**
* 暂停音频播放
*/
pauseAudio() {
if (this.audioContext && this.isPlaying) {
this.audioContext.pause();
console.log('音频播放已暂停');
}
}
/**
* 获取音色列表
*/
getVoiceList() {
return this.voiceList;
}
/**
* 根据ID获取音色信息
* @param {number} voiceId 音色ID
*/
getVoiceById(voiceId) {
return this.voiceList.find(voice => voice.id === voiceId);
}
/**
* 获取当前播放状态
*/
getPlayStatus() {
return {
isPlaying: this.isPlaying,
isConverting: this.isConverting,
currentAudioUrl: this.currentAudioUrl
};
}
/**
* 清理资源
*/
destroy() {
if (this.audioContext) {
this.audioContext.destroy();
this.audioContext = null;
}
this.isPlaying = false;
this.isConverting = false;
this.currentAudioUrl = '';
console.log('TTS服务已销毁');
}
// ==================== 私有方法 ====================
/**
* 初始化音频上下文
*/
initAudioContext() {
if (this.audioContext) {
this.audioContext.destroy();
}
// #ifdef MP-WEIXIN
this.audioContext = wx.createInnerAudioContext();
// #endif
// #ifdef H5
this.audioContext = uni.createInnerAudioContext();
// #endif
}
/**
* 参数验证
*/
validateParams(params) {
if (!params || typeof params !== 'object') {
return { valid: false, message: '参数格式错误' };
}
// 验证文本
const textValidation = UTILS.validateText(params.text);
if (!textValidation.valid) {
return textValidation;
}
// 验证语速
if (params.speed !== undefined) {
const speedValidation = UTILS.validateSpeed(params.speed);
if (!speedValidation.valid) {
return speedValidation;
}
}
// 验证音量
if (params.volume !== undefined) {
const volumeValidation = UTILS.validateVolume(params.volume);
if (!volumeValidation.valid) {
return volumeValidation;
}
}
return { valid: true };
}
/**
* 创建音频文件
*/
async createAudioFile(arrayBuffer, codec) {
return new Promise((resolve, reject) => {
const fileName = `tts_${Date.now()}.${codec}`;
// #ifdef MP-WEIXIN
const filePath = `${wx.env.USER_DATA_PATH}/${fileName}`;
wx.getFileSystemManager().writeFile({
filePath: filePath,
data: arrayBuffer,
success: () => resolve(filePath),
fail: (error) => reject(new Error('创建音频文件失败: ' + error.errMsg))
});
// #endif
// #ifdef H5
// H5环境下创建Blob URL
const blob = new Blob([arrayBuffer], { type: `audio/${codec}` });
const url = URL.createObjectURL(blob);
resolve(url);
// #endif
});
}
/**
* 生成用户ID
*/
generateUserId() {
// 尝试从存储获取用户ID
let userId = uni.getStorageSync('tts_user_id');
if (!userId) {
userId = 'user_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
uni.setStorageSync('tts_user_id', userId);
}
return userId;
}
/**
* 缓存音色列表
*/
cacheVoiceList(voiceList) {
const cacheData = {
data: voiceList,
timestamp: Date.now()
};
uni.setStorageSync('tts_voice_cache', cacheData);
}
/**
* 获取缓存的音色列表
*/
getCachedVoiceList() {
try {
const cached = uni.getStorageSync('tts_voice_cache');
if (cached && cached.data) {
// 检查缓存是否过期(24小时)
const expireTime = 24 * 60 * 60 * 1000;
if (Date.now() - cached.timestamp < expireTime) {
return cached.data;
}
}
} catch (error) {
console.error('获取缓存失败:', error);
}
return null;
}
/**
* 通用请求方法
*/
request(options) {
return new Promise((resolve, reject) => {
uni.request({
url: UTILS.buildApiUrl(options.url),
method: options.method || 'GET',
data: options.data || {},
timeout: API_CONFIG.TIMEOUT,
header: {
'Content-Type': 'application/json',
// 如果需要token认证,在这里添加
// 'Authorization': 'Bearer ' + uni.getStorageSync('token')
},
success: (res) => {
if (res.statusCode === 200) {
resolve(res.data);
} else {
reject(new Error(`HTTP ${res.statusCode}: ${res.data?.message || '请求失败'}`));
}
},
fail: (error) => {
reject(new Error(ERROR_MESSAGES[ERROR_CODES.NETWORK_ERROR] || error.errMsg));
}
});
});
}
/**
* 二进制数据请求方法
*/
requestBinary(options) {
return new Promise((resolve, reject) => {
uni.request({
url: UTILS.buildApiUrl(options.url),
method: options.method || 'GET',
data: options.data || {},
responseType: 'arraybuffer',
timeout: API_CONFIG.TIMEOUT,
header: {
// 如果需要token认证,在这里添加
// 'Authorization': 'Bearer ' + uni.getStorageSync('token')
},
success: (res) => {
if (res.statusCode === 200) {
resolve(res.data);
} else {
reject(new Error(`HTTP ${res.statusCode}: 请求失败`));
}
},
fail: (error) => {
reject(new Error(ERROR_MESSAGES[ERROR_CODES.NETWORK_ERROR] || error.errMsg));
}
});
});
}
}
// 创建单例实例
const ttsService = new TTSService();
// 导出服务实例和类
export { TTSService, ttsService };
export default ttsService;