前端 vue3 对接科大讯飞的语音在线合成API

news2024/9/30 13:58:14

主要的功能就是将文本转为语音,可以播放。

看了看官方提供的demo,嗯....没看懂。最后还是去网上找的。

网上提供的案例,很多都是有局限性的,我找的那个他只能读取第一段数据,剩下的不读取。

科大讯飞的接口,返回的是一个数组,因为需要合成的文本多,所以将数据切割成多份,然后返回的。

例子:

封装了个方法,直接调用方法就可以了。

import CryptoJS from 'crypto-js';
import { Base64 } from 'js-base64';
import { message } from 'ant-design-vue';

let APPID = '';
let API_SECRET = '';
let API_KEY = '';

// 正确的URL
function getWebSocketUrl(apiKey, apiSecret) {
  let url = 'wss://tts-api.xfyun.cn/v2/tts';
  const host = 'tts-api.xfyun.cn';
  const date = new Date().toGMTString();
  const algorithm = 'hmac-sha256';
  const headers = 'host date request-line';
  const signatureOrigin = `host: ${host}\ndate: ${date}\nGET /v2/tts HTTP/1.1`;
  const signatureSha = CryptoJS.HmacSHA256(signatureOrigin, apiSecret);
  const signature = CryptoJS.enc.Base64.stringify(signatureSha);
  const authorizationOrigin = `api_key="${apiKey}", algorithm="${algorithm}", headers="${headers}", signature="${signature}"`;
  const authorization = btoa(authorizationOrigin);
  url = `${url}?authorization=${authorization}&date=${date}&host=${host}`;
  return url;
}

// 文本编码
function encodeText(text, encoding) {
  switch (encoding) {
    case 'utf16le': {
      const buf = new ArrayBuffer(text.length * 4);
      const bufView = new Uint16Array(buf);
      // eslint-disable-next-line no-plusplus
      for (let i = 0, strlen = text.length; i < strlen; i++) {
        bufView[i] = text.charCodeAt(i);
      }
      return buf;
    }
    case 'buffer2Base64': {
      let binary = '';
      const bytes = new Uint8Array(text);
      const len = bytes.byteLength;
      // eslint-disable-next-line no-plusplus
      for (let i = 0; i < len; i++) {
        binary += String.fromCharCode(bytes[i]);
      }
      return window.btoa(binary);
    }
    case 'base64&utf16le': {
      return this.encodeText(this.encodeText(text, 'utf16le'), 'buffer2Base64');
    }
    default: {
      return Base64.encode(text);
    }
  }
}

// eslint-disable-next-line no-shadow
function TextToSpeechConfig(APPID, vcn, speed, volume, pitch, tte, text) {
  // 私有方法:生成参数对象
  function generateParams() {
    return {
      common: {
        app_id: APPID,
      },
      business: {
        aue: 'lame',
        auf: 'audio/L16;rate=16000',
        sfl: 1,
        vcn,
        speed,
        volume,
        pitch,
        bgs: 1,
        tte,
      },
      data: {
        status: 2,
        text: encodeText(text, tte === 'unicode' ? 'base64&utf16le' : ''), // 假设 encodeText 是一个已定义的函数
      },
    };
  }

  // 公共方法,暴露给外部调用以获取参数对象
  return generateParams();
}

export default class TTSWSS {
  static _instance; // 使用下划线表示这是一个内部使用的属性

  text = '';

  vcn = '';

  speed = '';

  volume = '';

  pitch = '';

  tte = 'UTF8';

  ttsWS = null;

  static getInstance(text, vcn, speed, volume, pitch) { // 单例模式
    // if (!TTSWSS._instance) {
    //   TTSWSS._instance = new TTSWSS(text, vcn, speed, volume, pitch);
    // }
    TTSWSS._instance = new TTSWSS(text, vcn, speed, volume, pitch);
    return TTSWSS._instance;
  }

  constructor(text, vcn, speed, volume, pitch) {
    this.text = text;
    this.vcn = vcn;
    this.speed = speed;
    this.volume = volume;
    this.pitch = pitch;
    const url = getWebSocketUrl(API_KEY, API_SECRET);
    if ('WebSocket' in window) { // 构造函数时就创建websocket对象
      this.ttsWS = new WebSocket(url);
    } else if ('MozWebSocket' in window) {
      this.ttsWS = new WebSocket(url);
    } else {
      // alert('浏览器不支持WebSocket');
      message.error('浏览器不支持WebSocket');
    }
  }

  setText(text) {
    this.text = text;
  }

  setTextVCN(vcn) {
    this.vcn = vcn;
  }

  setSpeed(speed) {
    this.speed = speed;
  }

  setVolume(volume) {
    this.volume = volume;
  }
  // setTte(istte=false){
  //   this.tte = istte==true ? "unicode" : "UTF8"
  // }

  connectWebSocket() {
    this.ttsWS.onopen = () => {
      // console.log(TextToSpeechConfig(APPID, this.vcn, this.speed, this.volume, this.pitch, this.tte, this.text), '请求参数');
      this.ttsWS.send(JSON.stringify(TextToSpeechConfig(APPID, this.vcn, this.speed, this.volume, this.pitch, this.tte, this.text)));
    };

    this.ttsWS.onerror = () => {
      // console.error(e);
    };
    this.ttsWS.onclose = () => {
      // console.log(e);
    };
  }

  disconnectWebSocket() {
    TTSWSS._instance = null;
    this.ttsWS.close(); // 关闭 WebSocket 连接
    this.ttsWS = null; // 清空 WebSocket 对象
    // console.log('WebSocket disconnected');
  }

  send_newMessage = text => {
    const params = {
      common: {
        app_id: APPID,
      },
      business: {
        aue: 'lame',
        sfl: 1,
        auf: 'audio/L16;rate=16000',
        vcn: this.vcn,
        speed: this.speed,
        volume: this.volume,
        pitch: this.pitch,
        bgs: 1,
        tte: 'UTF8',
      },
      data: {
        status: 2,
        text: encodeText(text, this.tte === 'unicode' ? 'base64&utf16le' : ''),
      },
    };
    this.ttsWS.send(JSON.stringify(params));
  };

  getMessage() {
    const that = this.ttsWS;
    const messages = []; // 用于存储所有消息

    return new Promise((resolve, reject) => {
      that.onmessage = e => {
        const jsonData = JSON.parse(e.data);

        // 合成失败
        if (jsonData.code !== 0) {
          // eslint-disable-next-line prefer-promise-reject-errors
          reject({ message: '失败', data: jsonData });
          return; // 退出当前处理
        }

        // 存储成功的消息
        messages.push({
          message: '成功',
          type: 'base64',
          data: jsonData.data.audio,
          isLastData: jsonData.data.status === 2,
        });

        // 如果接收到最后一条数据,解析所有消息并关闭连接
        if (jsonData.data.status === 2) {
          that.close();
          resolve(messages); // 返回所有消息
        }
      };
    });
  }

  TTS_close_reset() {
    this.ttsWS?.close();
    // audioPlayer.reset();
  }

  static resetInstance() {
    TTSWSS._instance = null; // 清空实例
    // console.log('TTSWSS instance has been reset.');
  }
}
export function setConfig(params) {
  APPID = params?.APPID;
  API_SECRET = params?.APISecret;
  API_KEY = params?.APIKey;
}

 使用:

import TTWss from '@/utils/voice/index.js';

const audio_url = ref('');
const ttsinstance = ref(null); // 初始化为 null
const voiceLoading = ref(false); // 加载音频中

function playVoice() {
  voiceLoading.value = true;
  ttsinstance?.value?.disconnectWebSocket();
  ttsinstance.value = null;
  audio_url.value = null; // 清空音频 URL
  const { text } = props; // 这里是要转成语音的文字,我这个是写在组件里面的用props接收的,所以要这样写,到时候替换成自己要合成的文字就行
  // 创建 TTS 实例
  ttsinstance.value = TTWss.getInstance(text, 'xiaoyan', 50, 50, 50);
  // 连接 WebSocket
  ttsinstance.value.connectWebSocket();
  // 获取消息
  ttsinstance.value.getMessage().then(result => {
    // 这里需要特殊处理,因为返回的数据是数组,所以要先将数组中的数据拿出来,放在每项里面的data中,然将不要先拼接,而是要先解码,然后将解码后的数据在拼接起来,这样就是一整段完整的录音文件了。
    const allData = result.map(it => atob(it.data));
    const binaryString = allData.join('');
    const len = binaryString.length;
    const bytes = new Uint8Array(len);

    for (let i = 0; i < len; i++) {
      bytes[i] = binaryString.charCodeAt(i);
    }

    const blob = new Blob([bytes], { type: 'audio/mp3' }); // 根据音频格式修改MIME类型
    const url = URL.createObjectURL(blob);
    audio_url.value = url; // 将生成的 URL 赋值给 audio_url
    // 这里展开后可以直接下载
    // const aTag = document.createElement('a');
    // aTag.href = url;
    // aTag.download = 'audio_file_name.mp3'; // 设置文件名
    // aTag.style.display = 'none';
    // document.body.appendChild(aTag);
    // aTag.click();
    // document.body.removeChild(aTag);
    // 播放音频
    playItem(url);
    voiceLoading.value = false;
  }).catch(err => {
    // console.log('失败', err);
    message.error(err);
    voiceLoading.value = false;
  });
}


let currentAudio = null;
function playItem(url) {
  // 如果当前有音频在播放,则停止它
  if (currentAudio) {
    currentAudio.pause();
    currentAudio.currentTime = 0; // 可选:重置播放时间
  }
  currentAudio = new Audio(url);

  currentAudio.play().then(() => {
    // console.log('音频播放开始');
  }).catch(error => {
    // console.error('音频播放失败', error);
    message.error(error);
  });

  // 释放对象URL(可选)
  currentAudio.addEventListener('ended', () => {
    URL.revokeObjectURL(url);
    currentAudio = null; // 音频结束后清空实例
  });
}

HTML:

<img
              :src="PlayVoice"
              alt=""
              class="icon-img"
              @click.stop="playVoice"
            />

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

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

相关文章

中国一定赞!北京华联BHGMall献礼祖国75周年华诞,创新践行促消费体验再升级

北京华联BHGMall [华联股份(000882)] 作为零售行业核心力量&#xff0c;以广大消费者为核心&#xff0c;不断提升自身竞争力、优化服务、以实惠的价格优质的品牌组合创新的营销活动&#xff0c;带来全新的购物消费体验。 让利于民&#xff0c;以缤纷活动点燃国庆热烈氛围 金秋…

OpenAI创始成员Andrej Karpathy:这才是技术之美

来源 | 机器之心 技术应该是什么样子&#xff1f; 我们知道乔布斯有「为改变混乱繁杂而生的现代简约主义」的设计理念。所以苹果提供的科技产品都是简洁的。可斯人已逝&#xff0c;如今我们身边的科技产品似乎又进入了复杂与不实用的怪圈之中。 近日&#xff0c;知名 AI 领域学…

【教程】57帧! Mac电脑流畅运行黑神话悟空

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhagn.cn] 如果本文帮助到了你&#xff0c;欢迎[点赞、收藏、关注]哦~ 1、先安装CrossOver。网上有许多和谐版&#xff0c;可自行搜索。&#xff08;pd虚拟机里运行黑神话估计够呛的&#xff09; 2、运行CrossOver&#xf…

VMware ESXi 8.0U3b macOS Unlocker OEM BIOS 2.7 Dell HPE 定制版 9 月更新发布

VMware ESXi 8.0U3b macOS Unlocker & OEM BIOS 2.7 Dell HPE 定制版 9 月更新发布 VMware ESXi 8.0U3b macOS Unlocker & OEM BIOS 2.7 标准版和厂商定制版 ESXi 8.0U3 标准版&#xff0c;Dell (戴尔)、HPE (慧与)、Lenovo (联想)、IEIT SYSTEMS (浪潮信息)、Cisco …

在使用表格识别工具时,如何确保识别的准确性?

在使用表格识别工具时&#xff0c;确保识别准确性的关键在于以下几个方面&#xff1a; 1.图像质量&#xff1a;确保扫描或拍摄的图像清晰&#xff0c;无遮挡、无反光、无阴影&#xff0c;并且文字清晰可辨 。 2.预处理图像&#xff1a;在图像送入OCR识别之前&#xff0c;进行…

Linux —— Socket编程(三)

一、本章重点 1. tcp服务器实现思路&#xff0c;进一步了解和总结相关的接口 2. 了解日志和守护进程 二、tcp服务器核心思路 tcp版的服务器与udp的不同在于&#xff0c;udp是面向数据报传输数据&#xff0c;在数据传输中不需要建立与客户端的链接&#xff0c;直接用recvfrom…

GEE数据集:1996 年到 2020 年全球红树林观测数据集(JAXA)(更新)

目录 简介 数据集说明 数据集 代码 代码链接 结果 引用 许可 网址推荐 0代码在线构建地图应用 机器学习 简介 全球红树林观测 这项研究使用了日本宇宙航空研究开发机构&#xff08;JAXA&#xff09;提供的 L 波段合成孔径雷达&#xff08;SAR&#xff09;全球mask…

银河麒麟服务器:更新软件源

银河麒麟服务器&#xff1a;更新软件源 1、使用场景2、操作步骤3、注意事项 &#x1f490;The Begin&#x1f490;点点关注&#xff0c;收藏不迷路&#x1f490; 1、使用场景 当需要安装最新软件或修改软件源配置后&#xff0c;需更新软件源以获取最新软件包信息。 2、操作步…

<<迷雾>> 第5章 从逻辑学到逻辑电路(2)--非门 示例电路

一个应用非门的例子 info::操作说明 鼠标单击开关切换开合状态 primary::在线交互操作链接 https://cc.xiaogd.net/?startCircuitLinkhttps://book.xiaogd.net/cyjsjdmw-examples/assets/circuit/cyjsjdmw-ch05-05-not-gate-sample.txt 原图 一个自带电源的常闭触点继电器属于…

基于定制开发与2+1链动模式的商城小程序搭建策略

摘要&#xff1a;本文探讨商城小程序的搭建策略&#xff0c;对比自主组建团队和第三方开发两种方式&#xff0c;强调以第三方开发模式为主的优势。阐述在第三方开发模式下&#xff0c;结合定制开发和21链动模式&#xff0c;如何搭建一款有助于企业商业模式创新与智能商业升级的…

化工企业大文件传输软件该怎么选?

化工行业里&#xff0c;数据的迅速、安全传递对于企业的研发、生产和供应链管理是至关重要的。随着数据量的不断增长和网络环境的日益复杂&#xff0c;传统的文件传输方法已经无法满足化工企业的需求。接下来&#xff0c;我将带领大家一起探讨化工企业在进行大文件传输时所面临…

linux驱动编程——标准、混杂、中断

一、优化——自动申请设备号、自动创建节点 设备号类型&#xff1a;①主设备号 ②子设备号 类型&#xff1a;unsigned int <>dev_t 12 major &#xff08;主设备号&#xff09; 20 minor &#xff08;子设备号&#xff09;<区…

【课程总结】day29:大模型之深入了解Retrievers解析器

前言 在上一章【课程总结】day28:大模型之深入探索RAG流程中,我们对RAG流程中 文档读取(LOAD) -> 文档切分(SPLIT) -> 向量化(EMBED) -> 存储(STORE) 进行了深入了解,本章将接着深入了解 解析(Retrieval) 的使用 解析器简介 简介:在 RAG(Retrieval-Augmented G…

墙绘产品交易平台:SpringBoot技术实现

4 系统设计 墙绘产品展示交易平台的设计方案比如功能框架的设计&#xff0c;比如数据库的设计的好坏也就决定了该系统在开发层面是否高效&#xff0c;以及在系统维护层面是否容易维护和升级&#xff0c;因为在系统实现阶段是需要考虑用户的所有需求&#xff0c;要是在设计阶段没…

矩阵奇异值

一、ATA 任给一个矩阵A&#xff0c;都有&#xff1a; ATA 为一个对称矩阵 例子&#xff1a;A为一个mn的矩阵&#xff0c;A的转置为一个nm的矩阵 对称矩阵的重要性质如下&#xff1a; ① 对称矩阵的特征值全为实数&#xff08;实数特征根&#xff09; ② 任意一个n阶对称矩阵…

思科dhcp的配置

以路由器为例 让pc3 自动获取ip地址并获取的网段为172.16.4.100-172.16.4.200 配置如下&#xff1a; R1(config)#interface GigabitEthernet0/2 R1(config)#ip address 172.16.4.254 255.255.255.0 R1(config)# no shutdown R1(config)#ip dhcp pool 4_pool //创建dhcp地址池…

实际有库存却提示可用量不足保存不了杂发单

财务要统计研发费用&#xff0c;成本的金额。研发人员没有足够的意识配合。开立请购单时兴之所致&#xff0c;任性自由。想弄一个项目号就弄一个。不开心就没有项目号啦。哪管他人死活。 U9的逻辑&#xff0c;请购单如果带入项目号&#xff08;客制化的功能&#xff09;&#x…

c语言200例 067

大家好&#xff0c;欢迎来到无限大的频道 今天给大家带来的是c语言200例 题目要求&#xff1a; 设计一个共用体类型&#xff0c;使其成员包含多种数据类型&#xff0c;根据不同的数据类型&#xff0c;输出不同的结果 要设计一个共用体&#xff08;union&#xff09;类型&…

如何判断主机字节序

测试代码: #include <stdio.h> void byteorder() {union{short value;char union_bytes[sizeof(short)];//union_bytes数组}test;test.value 0x0102;if((test.union_bytes[0] 1) && (test.union_bytes[1]2)){printf("big endian\n");}else if((test…

初识Java反序列化漏洞

目录 为什么需要序列化&#xff1f; 序列化与反序列化基础案例 Serializable 接口 序列化对象 反序列化对象 Java 反序列化漏洞 readObject() 序列化&#xff1a;将对象的状态信息转换为可以存储或传输的形式的过程&#xff0c;即将对象转换为字节序列。反序列化&#x…