|
@@ -51,7 +51,7 @@
|
|
|
{{expandIndexList.includes(index)?'折叠':'展开'}}
|
|
|
</el-button>
|
|
|
</div>
|
|
|
- <el-button>播放</el-button>
|
|
|
+ <el-button @click="ttsStartPlay(item.content)">播放</el-button>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
@@ -99,6 +99,7 @@
|
|
|
import CryptoJS from 'crypto-js';
|
|
|
import * as RecorderManager from "/public/ai/iat/dist/index.umd.js"
|
|
|
// 语音播放
|
|
|
+ import {Base64} from 'js-base64'
|
|
|
import * as AudioPlayer from "/public/ai/tts/dist/index.umd.js"
|
|
|
|
|
|
const loadingHistoryRecord = ref(false)
|
|
@@ -521,11 +522,16 @@
|
|
|
/* 语音识别 ↑*/
|
|
|
|
|
|
/*语音合成 ↓*/
|
|
|
-
|
|
|
+ // 合成对象
|
|
|
+ let audioPlayer = null
|
|
|
+ // 按钮状态:"UNDEFINED" "CONNECTING" "PLAY" "STOP"
|
|
|
+ let btnStatus = "UNDEFINED";
|
|
|
+ //
|
|
|
+ let ttsWS = null;
|
|
|
|
|
|
function initSpeechSynthesis(){
|
|
|
// 初始化语音播放
|
|
|
- const audioPlayer = new AudioPlayer("/ai/tts/dist");
|
|
|
+ audioPlayer = new window.AudioPlayer("/ai/tts/dist");
|
|
|
// 开始播放
|
|
|
audioPlayer.onPlay = () => {
|
|
|
changeBtnStatus("PLAY");
|
|
@@ -536,7 +542,6 @@
|
|
|
};
|
|
|
}
|
|
|
|
|
|
- let btnStatus = "UNDEFINED"; // "UNDEFINED" "CONNECTING" "PLAY" "STOP"
|
|
|
function changeBtnStatus(status) {
|
|
|
btnStatus = status;
|
|
|
if (status === "UNDEFINED") {
|
|
@@ -550,6 +555,136 @@
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ function getWebSocketUrl() {
|
|
|
+ const { APIKey, APISecret } = xfIatKeys;
|
|
|
+ if (!APIKey) {
|
|
|
+ message.error('语音合成配置未生效');
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ var url = "wss://tts-api.xfyun.cn/v2/tts";
|
|
|
+ var host = location.host;
|
|
|
+ const apiKey = APIKey;
|
|
|
+ const apiSecret = APISecret;
|
|
|
+ var date = new Date().toGMTString();
|
|
|
+ var algorithm = "hmac-sha256";
|
|
|
+ var headers = "host date request-line";
|
|
|
+ var signatureOrigin = `host: ${host}\ndate: ${date}\nGET /v2/tts HTTP/1.1`;
|
|
|
+ var signatureSha = CryptoJS.HmacSHA256(signatureOrigin, apiSecret);
|
|
|
+ var signature = CryptoJS.enc.Base64.stringify(signatureSha);
|
|
|
+ var authorizationOrigin = `api_key="${apiKey}", algorithm="${algorithm}", headers="${headers}", signature="${signature}"`;
|
|
|
+ var authorization = btoa(authorizationOrigin);
|
|
|
+ url = `${url}?authorization=${authorization}&date=${date}&host=${host}`;
|
|
|
+ return url;
|
|
|
+ }
|
|
|
+
|
|
|
+ function encodeText(text, type) {
|
|
|
+ if (type === "unicode") {
|
|
|
+ let buf = new ArrayBuffer(text.length * 4);
|
|
|
+ let bufView = new Uint16Array(buf);
|
|
|
+ for (let i = 0, strlen = text.length; i < strlen; i++) {
|
|
|
+ bufView[i] = text.charCodeAt(i);
|
|
|
+ }
|
|
|
+ let binary = "";
|
|
|
+ let bytes = new Uint8Array(buf);
|
|
|
+ let len = bytes.byteLength;
|
|
|
+ for (let i = 0; i < len; i++) {
|
|
|
+ binary += String.fromCharCode(bytes[i]);
|
|
|
+ }
|
|
|
+ return window.btoa(binary);
|
|
|
+ } else {
|
|
|
+ return Base64.encode(text);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ function ttsStartPlay(text) {
|
|
|
+ if (btnStatus === "UNDEFINED") {
|
|
|
+ // 开始合成
|
|
|
+ connectWebSocket(text.replace(/<\/?.+?\/?>/g,''));
|
|
|
+ } else if (btnStatus === "CONNECTING") {
|
|
|
+ // 停止合成
|
|
|
+ changeBtnStatus("UNDEFINED");
|
|
|
+ ttsWS?.close();
|
|
|
+ audioPlayer.reset();
|
|
|
+ return;
|
|
|
+ } else if (btnStatus === "PLAY") {
|
|
|
+ audioPlayer.stop();
|
|
|
+ } else if (btnStatus === "STOP") {
|
|
|
+ audioPlayer.play();
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ function connectWebSocket(text) {
|
|
|
+ const url = getWebSocketUrl();
|
|
|
+ if ("WebSocket" in window) {
|
|
|
+ ttsWS = new WebSocket(url);
|
|
|
+ } else if ("MozWebSocket" in window) {
|
|
|
+ ttsWS = new MozWebSocket(url);
|
|
|
+ } else {
|
|
|
+ alert("浏览器不支持WebSocket");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ changeBtnStatus("CONNECTING");
|
|
|
+
|
|
|
+ ttsWS.onopen = (e) => {
|
|
|
+ audioPlayer.start({
|
|
|
+ autoPlay: true,
|
|
|
+ sampleRate: 16000,
|
|
|
+ resumePlayDuration: 1000
|
|
|
+ });
|
|
|
+ changeBtnStatus("PLAY");
|
|
|
+ var tte = "UTF8";
|
|
|
+ var params = {
|
|
|
+ common: {
|
|
|
+ app_id: xfIatKeys.APPID,
|
|
|
+ },
|
|
|
+ business: {
|
|
|
+ aue: "raw",
|
|
|
+ auf: "audio/L16;rate=16000",
|
|
|
+ vcn: "xiaoyan",
|
|
|
+ // vcn: "aisbabyxu",
|
|
|
+ // vcn: "aisjinger",
|
|
|
+ // vcn: "aisxping",
|
|
|
+ // vcn: "aisjinger",
|
|
|
+ // vcn: "x4_lingxiaoyao_em",
|
|
|
+ // vcn: "x4_lingxiaoyao_en",
|
|
|
+ speed: +50,
|
|
|
+ volume: +50,
|
|
|
+ pitch: +50,
|
|
|
+ bgs: 1,
|
|
|
+ tte,
|
|
|
+ },
|
|
|
+ data: {
|
|
|
+ status: 2,
|
|
|
+ text: encodeText(text, tte),
|
|
|
+ },
|
|
|
+ };
|
|
|
+ ttsWS.send(JSON.stringify(params));
|
|
|
+ };
|
|
|
+ ttsWS.onmessage = (e) => {
|
|
|
+ let jsonData = JSON.parse(e.data);
|
|
|
+ // 合成失败
|
|
|
+ if (jsonData.code !== 0) {
|
|
|
+ console.error(jsonData);
|
|
|
+ changeBtnStatus("UNDEFINED");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ audioPlayer.postMessage({
|
|
|
+ type: "base64",
|
|
|
+ data: jsonData.data.audio,
|
|
|
+ isLastData: jsonData.data.status === 2,
|
|
|
+ });
|
|
|
+ if (jsonData.code === 0 && jsonData.data.status === 2) {
|
|
|
+ ttsWS.close();
|
|
|
+ }
|
|
|
+ };
|
|
|
+ ttsWS.onerror = (e) => {
|
|
|
+ console.error(e);
|
|
|
+ };
|
|
|
+ ttsWS.onclose = (e) => {
|
|
|
+ // console.log(e);
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
/*语音合成 ↑*/
|
|
|
|
|
|
let chatContainerRef = ref(null)
|