Sfoglia il codice sorgente

[feat][ai聊天][语音合成]

hizhangling 4 giorni fa
parent
commit
ce8a7cbb6e
2 ha cambiato i file con 140 aggiunte e 4 eliminazioni
  1. 1 0
      package.json
  2. 139 4
      src/views/xjc-integratedmachine/common/ai/chat.vue

+ 1 - 0
package.json

@@ -28,6 +28,7 @@
     "file-saver": "2.0.5",
     "fuse.js": "6.6.2",
     "html2canvas": "^1.4.1",
+    "js-base64": "^3.7.8",
     "js-beautify": "1.14.11",
     "js-cookie": "3.0.5",
     "jsencrypt": "3.3.2",

+ 139 - 4
src/views/xjc-integratedmachine/common/ai/chat.vue

@@ -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)