|
|
- /**
- * 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;
|