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

一、引言

在现代的 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 URLconst 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.APPIDthis.APISecret = sparkConfig.APISecretthis.APIKey = sparkConfig.APIKeythis.host = sparkConfig.hostthis.path = sparkConfig.paththis.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) returnthis.isReconnecting = truethis.reconnectAttempts++console.log(`尝试重新连接WebSocket (第${this.reconnectAttempts}次)...`)try {await this.connect()this.isReconnecting = falsethis.reconnectAttempts = 0console.log('WebSocket重连成功')} catch (error) {console.error('WebSocket重连失败:', error)this.isReconnecting = falseif (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 = falsethis.reconnectAttempts = 0this.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 地址和密钥信息,确保我们能够正确地与服务进行通信。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/74655.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

走进未来的交互世界:下一代HMI设计趋势解析

在科技日新月异的今天&#xff0c;人机交互界面&#xff08;HMI&#xff09;设计正以前所未有的速度发展&#xff0c;不断引领着未来的交互世界。从简单的按钮和图标&#xff0c;到如今的智能助手和虚拟现实&#xff0c;HMI设计不仅改变了我们的生活方式&#xff0c;还深刻影响…

洛谷题单3-P1217 [USACO1.5] 回文质数 Prime Palindromes-python-流程图重构

题目描述 因为 151 151 151 既是一个质数又是一个回文数&#xff08;从左到右和从右到左是看一样的&#xff09;&#xff0c;所以 151 151 151 是回文质数。 写一个程序来找出范围 [ a , b ] ( 5 ≤ a < b ≤ 100 , 000 , 000 ) [a,b] (5 \le a < b \le 100,000,000…

学习笔记,DbContext context 对象是保存了所有用户对象吗

DbContext 并不会将所有用户对象保存在内存中&#xff1a; DbContext 是 Entity Framework Core (EF Core) 的数据库上下文&#xff0c;它是一个数据库访问的抽象层它实际上是与数据库的一个连接会话&#xff0c;而不是数据的内存缓存当您通过 _context.Users 查询数据时&…

本地命令行启动服务并连接MySQL8

启动服务命令 net start mysql8 关闭服务命令 net stop mysql8 本地连接MySQL数据库mysql -u [用户名] -p[密码] 这里&#xff0c;我遇到了个问题 —— 启动、关闭服务时&#xff0c;显示 “发生系统错误 5。拒绝访问。 ” 解法1&#xff1a;在 Windows 上以管理员身份打开…

数据蒸馏:Dataset Distillation by Matching Training Trajectories 论文翻译和理解

一、TL&#xff1b;DR 数据集蒸馏的任务是合成一个较小的数据集&#xff0c;使得在该合成数据集上训练的模型能够达到在完整数据集上训练的模型相同的测试准确率&#xff0c;号称优于coreset的选择方法本文中&#xff0c;对于给定的网络&#xff0c;我们在蒸馏数据上对其进行几…

【spring cloud Netflix】Ribbon组件

1.基本概念 SpringCloud Ribbon是基于Netflix Ribbon 实现的一套客户端负载均衡的工具。简单的说&#xff0c;Ribbon 是 Netflix 发布的开源项目&#xff0c;主要功能是提供客户端的软件负载均衡算法&#xff0c;将 Netflix 的中间层服务连接在一 起。Ribbon 的客户端组件提供…

P1036 [NOIP 2002 普及组] 选数(DFS)

题目描述 已知 n 个整数 x1​,x2​,⋯,xn​&#xff0c;以及 1 个整数 k&#xff08;k<n&#xff09;。从 n 个整数中任选 k 个整数相加&#xff0c;可分别得到一系列的和。例如当 n4&#xff0c;k3&#xff0c;4 个整数分别为 3,7,12,19 时&#xff0c;可得全部的组合与它…

在响应式网页的开发中使用固定布局、流式布局、弹性布局哪种更好

一、首先看下固定布局与流体布局的区别 &#xff08;一&#xff09;固定布局 固定布局的网页有一个固定宽度的容器&#xff0c;内部组件宽度可以是固定像素值或百分比。其容器元素不会移动&#xff0c;无论访客屏幕分辨率如何&#xff0c;看到的网页宽度都相同。现代网页设计…

二分查找与二叉树中序遍历——面试算法

目录 二分查找与分治 循环方式 递归方式 元素中有重复的二分查找 基于二分查找的拓展问题 山脉数组的顶峰索引——局部有序 旋转数字中的最小数字 找缺失数字 优化平方根 中序与搜索树 二叉搜索树中搜索特定值 验证二叉搜索树 有序数组转化为二叉搜索树 寻找两个…

字符串——面试考察高频算法题

目录 转换成小写字母 字符串转化为整数 反转相关的问题 反转字符串 k个一组反转 仅仅反转字母 反转字符串里的单词 验证回文串 判断是否互为字符重排 最长公共前缀 字符串压缩问题 转换成小写字母 给你一个字符串 s &#xff0c;将该字符串中的大写字母转换成相同的…

现代复古电影海报品牌徽标设计衬线英文字体安装包 Thick – Retro Vintage Cinematic Font

Thick 是一种大胆的复古字体&#xff0c;专为有影响力的标题和怀旧的视觉效果而设计。其厚实的字体、复古魅力和电影风格使其成为电影海报、产品标签、活动品牌和编辑设计的理想选择。无论您是在引导电影的黄金时代&#xff0c;还是在现代布局中注入复古活力&#xff0c;Thick …

[C++面试] new、delete相关面试点

一、入门 1、说说new与malloc的基本用途 int* p1 (int*)malloc(sizeof(int)); // C风格 int* p2 new int(10); // C风格&#xff0c;初始化为10 new 是 C 中的运算符&#xff0c;用于在堆上动态分配内存并调用对象的构造函数&#xff0c;会自动计算所需内存…

Unity URP管线与HDRP管线对比

1. 渲染架构与底层技术 URP 渲染路径&#xff1a; 前向渲染&#xff08;Forward&#xff09;&#xff1a;默认单Pass前向&#xff0c;支持少量实时光源&#xff08;通常4-8个逐物体&#xff09;。 延迟渲染&#xff08;Deferred&#xff09;&#xff1a;可选但功能简化&#…

JDK8卸载与安装教程(超详细)

JDK8卸载与安装教程&#xff08;超详细&#xff09; 最近学习一个项目&#xff0c;需要使用更高级的JDK&#xff0c;这里记录一下卸载旧版本与安装新版本JDK的过程。 JDK8卸载 以windows10操作系统为例&#xff0c;使用快捷键winR输入cmd&#xff0c;打开控制台窗口&#xf…

python爬虫:DrissionPage实战教程

如果本文章看不懂可以看看上一篇文章&#xff0c;加强自己的基础&#xff1a;爬虫自动化工具&#xff1a;DrissionPage-CSDN博客 案例解析&#xff1a; 前提&#xff1a;我们以ChromiumPage为主&#xff0c;写代码工具使用Pycharm&#xff08;python环境3.9-3.10&#xff09; …

07-01-自考数据结构(20331)- 排序-内部排序知识点

内部排序算法是数据结构核心内容,主要包括插入类(直接插入、希尔)、交换类(冒泡、快速)、选择类(简单选择、堆)、归并和基数五大类排序方法。 知识拓扑 知识点介绍 直接插入排序 定义:将每个待排序元素插入到已排序序列的适当位置 算法步骤: 从第二个元素开始遍历…

Go语言-初学者日记(八):构建、部署与 Docker 化

&#x1f9f1; 一、go build&#xff1a;最基础的构建方式 Go 的构建工具链是出了名的轻量、简洁&#xff0c;直接用 go build 就能把项目编译成二进制文件。 ✅ 构建当前项目 go build -o myapp-o myapp 指定输出文件名默认会构建当前目录下的 main.go 或 package main &a…

教程:如何使用 JSON 合并脚本

目录 1. 介绍 2. 使用方法 3. 注意事项 4. 示例 5.完整代码 1. 介绍 该脚本用于将多个 COCO 格式的 JSON 标注文件合并为一个 JSON 文件。COCO 格式常用于目标检测和图像分割任务&#xff0c;包含以下三个主要部分&#xff1a; "images"&#xff1a;图像信息&a…

Java学习总结-缓冲流性能分析

测试用例&#xff1a; 分别使用原始的字节流&#xff0c;以及字节缓冲流复制一个很大的视频。 测试步骤&#xff1a; 在这个分析性能需要一个记录时间的工具&#xff1a;这个是记录1970-1-1 00&#xff1a;00&#xff1a;00到现在的总毫秒值。 long start System.currentT…

流影---开源网络流量分析平台(五)(成果展示)

目录 前沿 攻击过程 前沿 前四章我们已经成功安装了流影的各个功能&#xff0c;那么接下来我们就看看这个开源工具的实力&#xff0c;本实验将进行多个攻击手段&#xff08;ip扫描&#xff0c;端口扫描&#xff0c;sql注入&#xff09;攻击靶机&#xff0c;来看看流影的态感效…