当前位置: 首页 > news >正文

讯飞语音合成(流式版)语音专业版高质量的分析

一、引言

在现代的 Web 应用开发中,语音合成技术为用户提供了更加便捷和人性化的交互体验。讯飞语音合成(流式版)以其高效、稳定的性能,成为了众多开发者的首选。本文将详细介绍在 Home.vue 文件中实现讯飞语音合成(流式版)的开发逻辑、涉及的代码以及相关的环境配置,帮助大家更好地掌握这一技术。
在这里插入图片描述

二、开发环境配置

在这里插入图片描述

在 Home.vue 文件中,我们需要引入并配置讯飞语音合成的相关参数。在代码中,我们可以看到如下配置:
2-1, sparkCOnfig.js

// 讯飞星火大模型WebSocket配置
const getSparkConfig = () => {
  const config = {
    APPID: '6acb09d5',
    APISecret: 'MmNhN2VkY2JkMjQyODYyNzBhZDVhYjgz',
    APIKey: '36fb21a7095db0bb6ff2ac928e14a8e7',
    host: 'spark-api.xf-yun.com',
    path: '/v3.1/chat',
  };

  // 验证配置参数的有效性
  const validateConfig = () => {
    const requiredFields = ['APPID', 'APISecret', 'APIKey', 'host', 'path'];
    for (const field of requiredFields) {
      if (!config[field]) {
        throw new Error(`缺少必要的配置参数: ${field}`);
      }
    }
  };

  // 获取完整的WebSocket URL
  const getWebSocketUrl = () => {
    validateConfig();
    return `wss://${config.host}${config.path}`;
  };

  return {
    ...config,
    getWebSocketUrl,
  };
};

export default getSparkConfig;

2-2, Home.vue

import TTSRecorder from '../utils/voice/onlineTTS'
const ttsConfig = {
  app_id: '6acb09d5',
  api_secret: 'MmNhN2VkY2Jk*',
  api_key: '36fb21a7095*'
};
TTSRecorder.init(ttsConfig);

这里的 app_id 、 api_secret 和 api_key 是我们在讯飞开放平台申请的密钥,用于身份验证和访问控制。通过调用 TTSRecorder.init(ttsConfig) 方法,我们将这些配置信息传递给 TTSRecorder 实例,以便后续使用。

三、开发逻辑分析

3.1 初始化阶段

在 Home.vue 的 onMounted 生命周期钩子中,我们首先调用 fetchWelcomeMessage 方法获取欢迎消息。在获取到消息后,我们初始化 3D 场景,并连接 WebSocket 服务。同时,我们还调用 ttsplaybtn 方法对欢迎消息进行语音播报:

onMounted(() => {
  fetchWelcomeMessage().then(() => {
    initGlbScene(container.value, objpath.value, objpath_create_time.value);
    animate();
    window.addEventListener('resize', handleResize);
    connectWebSocket();
    nextTick(() => {
      setTimeout(() => {
        ttsplaybtn(response.data.data.welcome, 0);
      }, 3000);
    });
  });
  scrollToBottom();
});

### 3.2 语音播放功能实现
ttsplaybtn 方法是实现语音播放的核心函数。在这个方法中,我们根据当前的播放状态进行不同的处理:

```vue
const ttsplaybtn = async (content, index) => {
  try {
    console.log('准备播放文本,当前状态:', { content, index, isPlaying: isPlaying.value, currentPlayingIndex: currentPlayingIndex.value });
    if (isPlaying.value && currentPlayingIndex.value === index) {
      ttsRecorder.value.stop();
      stopAnimations();
      isPlaying.value = false;
      currentPlayingIndex.value = null;
      return;
    }
    if (ttsRecorder.value && isPlaying.value) {
      ttsRecorder.value.stop();
      stopAnimations();
      isPlaying.value = false;
      currentPlayingIndex.value = null;
    }
    ttsRecorder.value = new TTSRecorder({
      voiceName: 'xiaoyan',
      tte: 'UTF8',
      text: content,
      onEnd: () => {
        console.log('音频播放结束');
        isPlaying.value = false;
        currentPlayingIndex.value = null;
        stopAnimations();
      }
    });
    currentPlayingIndex.value = index;
    isPlaying.value = true;
    console.log('开始播放文本:', content);
    startAnimations();
    await ttsRecorder.value.start();
  } catch (error) {
    console.error('语音播放出错:', error);
    isPlaying.value = false;
    currentPlayingIndex.value = null;
    stopAnimations();
    ElMessage.error('语音播放失败: ' + error.message);
  }
};

当用户点击播放按钮时,我们首先检查当前是否有其他消息正在播放。如果有,则停止当前播放的消息。然后,我们创建一个新的 TTSRecorder 实例,并传入要播放的文本和相关配置。最后,我们调用 start 方法开始语音播放。

sparkChat.js

// 讯飞星火大模型WebSocket通信模块
import axios from ‘axios’
import getSparkConfig from ‘…/sparkConfig’

class SparkChatService {
constructor(callbacks) {
this.websocket = null
this.isReconnecting = false
this.reconnectAttempts = 0
this.MAX_RECONNECT_ATTEMPTS = 3
this.RECONNECT_INTERVAL = 2000

    // 获取配置
    const sparkConfig = getSparkConfig()
    this.APPID = sparkConfig.APPID
    this.APISecret = sparkConfig.APISecret
    this.APIKey = sparkConfig.APIKey
    this.host = sparkConfig.host
    this.path = sparkConfig.path
    this.sparkBaseUrl = sparkConfig.getWebSocketUrl()
    
    // 回调函数
    this.callbacks = callbacks || {}
}

// 生成鉴权URL所需的日期
getAuthorizationDate() {
    return new Date().toUTCString()
}

// 生成鉴权URL
async getAuthUrl() {
    const date = this.getAuthorizationDate()
    const tmp = `host: ${this.host}\ndate: ${date}\nGET ${this.path} HTTP/1.1`
    
    const encoder = new TextEncoder()
    const key = await window.crypto.subtle.importKey(
        'raw',
        encoder.encode(this.APISecret),
        { name: 'HMAC', hash: 'SHA-256' },
        false,
        ['sign']
    )
    
    const signature = await window.crypto.subtle.sign(
        'HMAC',
        key,
        encoder.encode(tmp)
    )
    
    const signatureBase64 = btoa(String.fromCharCode(...new Uint8Array(signature)))
    const authorization_origin = `api_key="${this.APIKey}", algorithm="hmac-sha256", headers="host date request-line", signature="${signatureBase64}"`
    const authorization = btoa(authorization_origin)
    
    return `${this.sparkBaseUrl}?authorization=${encodeURIComponent(authorization)}&date=${encodeURIComponent(date)}&host=${encodeURIComponent(this.host)}`
}

// 检查WebSocket连接状态
checkWebSocketConnection() {
    return this.websocket && this.websocket.readyState === WebSocket.OPEN
}

// 重连WebSocket
async reconnectWebSocket() {
    if (this.isReconnecting || this.reconnectAttempts >= this.MAX_RECONNECT_ATTEMPTS) return
    
    this.isReconnecting = true
    this.reconnectAttempts++
    
    console.log(`尝试重新连接WebSocket (第${this.reconnectAttempts}次)...`)
    
    try {
        await this.connect()
        this.isReconnecting = false
        this.reconnectAttempts = 0
        console.log('WebSocket重连成功')
    } catch (error) {
        console.error('WebSocket重连失败:', error)
        this.isReconnecting = false
        
        if (this.reconnectAttempts < this.MAX_RECONNECT_ATTEMPTS) {
            setTimeout(() => this.reconnectWebSocket(), this.RECONNECT_INTERVAL)
        } else {
            console.error('WebSocket重连次数达到上限')
            this.callbacks.onError?.('网络连接异常,请刷新页面重试')
        }
    }
}

// 建立WebSocket连接
async connect() {
    try {
        const url = await this.getAuthUrl()
        this.websocket = new WebSocket(url)
        
        this.websocket.onopen = () => {
            console.log('WebSocket连接已建立')
            this.isReconnecting = false
            this.reconnectAttempts = 0
            this.callbacks.onOpen?.()
        }
        
        this.websocket.onmessage = (event) => {
            const response = JSON.parse(event.data)
            
            if (response.header.code === 0) {
                if (response.payload.choices.text[0].content) {
                    const content = response.payload.choices.text[0].content.replace(/\r?\n/g, '')
                    this.callbacks.onMessage?.(content)
                }
                
                if (response.header.status === 2) {
                    this.callbacks.onComplete?.()
                }
            } else {
                this.callbacks.onError?.(`抱歉,发生错误:${response.header.message}`)
            }
        }
        
        this.websocket.onerror = (error) => {
            console.error('WebSocket错误:', error)
            if (!this.isReconnecting) {
                this.reconnectWebSocket()
            }
            this.callbacks.onError?.(error)
        }
        
        this.websocket.onclose = () => {
            console.log('WebSocket连接已关闭')
            if (!this.isReconnecting) {
                this.reconnectWebSocket()
            }
            this.callbacks.onClose?.()
        }
    } catch (error) {
        console.error('连接WebSocket失败:', error)
        throw error
    }
}

// 发送消息
async sendMessage(message) {
    if (!this.checkWebSocketConnection()) {
        try {
            await this.reconnectWebSocket()
        } catch (error) {
            console.error('重连失败,无法发送消息')
            throw new Error('网络连接异常,请稍后重试')
        }
    }

    const requestData = {
        header: {
            app_id: this.APPID,
            uid: 'user1'
        },
        parameter: {
            chat: {
                domain: 'generalv3',
                temperature: 0.5,
                max_tokens: 4096
            }
        },
        payload: {
            message: {
                text: [{ role: 'user', content: message }]
            }
        }
    }

    try {
        this.websocket.send(JSON.stringify(requestData))
    } catch (error) {
        console.error('发送消息失败:', error)
        throw new Error('发送消息失败,请重试')
    }
}

// 关闭连接
close() {
    if (this.websocket) {
        this.websocket.close()
    }
}

}

export default SparkChatService
···

3.3 播放状态管理

为了实现播放和暂停功能的切换,我们使用 isPlaying 和 currentPlayingIndex 两个响应式变量来管理播放状态。在 ttsplaybtn 方法中,我们根据这两个变量的值来判断当前的播放状态,并进行相应的处理。同时,在模板中,我们根据 isPlaying 的值来显示不同的图标:

<svg class="input-icon tts-btn" viewBox="0 0 24 24" aria-label="播报语音图标" @click="ttsplaybtn(message.content, index)">
  <path v-if="currentPlayingIndex === index && isPlaying" d="M6 4h4v16H6zM14 4h4v16h-4z" fill="currentColor"/>
  <path v-else d="M12 3c-4.97 0-9 4.03-9 9v7c0 1.66 1.34 3 3 3h3v-8H5v-2c0-3.87 3.13-7 7-7s7 3.13 7 7v2h-4v8h3c1.66 0 3-1.34 3-3v-7c0-4.97-4.03-9-9-9z" fill="currentColor"/>
</svg>

## 四、文件结构和代码分析
### 4.1 文件结构
在项目中,与讯飞语音合成相关的文件主要包括:

- Home.vue :主页面文件,包含语音播放按钮和相关逻辑。
- TTSRecorder.js :封装了讯飞语音合成的核心功能,如初始化、开始播放、停止播放等。
- sparkConfig.js :配置文件,包含讯飞语音合成的 API 地址和密钥信息。
### 4.2 代码分析
- Home.vue :在这个文件中,我们主要处理用户的交互事件,如点击播放按钮、停止播放等。同时,我们还管理播放状态,并根据状态更新界面。
- TTSRecorder.js :这个文件封装了与讯飞语音合成服务的交互逻辑。它接收配置信息,并提供了 start 和 stop 方法来控制语音播放。
- sparkConfig.js :该文件存储了讯飞语音合成的 API 地址和密钥信息,确保我们能够正确地与服务进行通信。

相关文章:

  • vscode和cursor对ubuntu22.04的remote ssh和X-Windows的无密码登录
  • 【深度学习】通过colab将本地的数据集上传到drive
  • Linux_4
  • HTML5 Web Workers 学习笔记
  • DAY 37 leetcode 454--哈希表.四数相加
  • 快速搭建gateway并接入nacos,并使用nacos配置文件
  • 用于解决个人使用的公网ip动态变化问题的解决方案
  • leetcode111 二叉树的最小深度
  • 解决报错:node:internal/errors:496 ErrorCaptureStackTrace(err);
  • Python中将脚本打包成独立的 ​EXE
  • 生成式人工智能认证的理性思考:人工智能(AI)将深度改造行业?
  • 【网络安全】安全的网络设计
  • 蓝桥云客--团队赛
  • kotlin函数类型
  • Higress项目解析(一):Higress核心组件和原理、Wasm插件实现原理
  • 力扣热题100刷题day61|234.回文链表(两种方法)
  • 用Grok 3分析案例并提供一些助理或助手的整理工作
  • 宏碁笔记本电脑擎7PRO搭载的 NVIDIA RTX 5080 显卡安装pytorch
  • Talib库安装教程
  • 通过AOP切面,切点,反射填充公共字段