自学内容网 自学内容网

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

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

看了看官方提供的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"
            />


原文地址:https://blog.csdn.net/motoudi/article/details/142653230

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!