// import Base64 from "crypto-js/enc-base64"; import CryptoJS from "crypto-js"; import AudioPlayer from "./audioPlayer.esm"; import Base64 from "./base64"; export default class TtsVoice { appId = ""; apiSecret = ""; apiKey = ""; ttsWS = null; status = "UNDEFINED"; statusStr = ""; audioPlayer = null; text = ""; blob = ""; cb = () => {}; blobDoneCb = () => {}; constructor(params) { this.appId = params.appId; this.apiSecret = params.apiSecret; this.apiKey = params.apiKey; if (params.cb) { this.cb = params.cb; } if (params.blobDoneCb) { this.blobDoneCb = params.blobDoneCb; } this.audioPlayer = new AudioPlayer(); this.audioPlayer.onPlay = () => { this.changeBtnStatus("PLAY"); }; this.audioPlayer.onStop = (audioDatas) => { console.log(audioDatas); this.status === "PLAY" && this.changeBtnStatus("STOP"); }; } setText(text) { this.text = text; } changeBtnStatus(status) { this.status = status; if (status === "UNDEFINED") { this.statusStr = "立即合成"; } else if (status === "CONNECTING") { this.statusStr = "正在合成"; } else if (status === "PLAY") { this.statusStr = "停止播放"; } else if (status === "STOP") { this.statusStr = "重新播放"; } const _this = this; this.cb({ status, statusStr: _this.statusStr }); } 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); } } getWebSocketUrl() { let apiKey = this.apiKey; let apiSecret = this.apiSecret; var url = "wss://tts-api.xfyun.cn/v2/tts"; var host = location.host; 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; } stopConnect() { this.changeBtnStatus("UNDEFINED"); this.ttsWS?.close(); this.audioPlayer.reset(); } action(text) { if (text && typeof text === "string") { if (text !== this.text) { this.changeBtnStatus("UNDEFINED"); } this.setText(text); } if (this.status === "UNDEFINED") { // 开始合成 this.connect(); } else if (this.status === "CONNECTING") { // 停止合成 this.changeBtnStatus("UNDEFINED"); this.ttsWS?.close(); this.audioPlayer.reset(); return; } else if (this.status === "PLAY") { this.audioPlayer.stop(); } else if (this.status === "STOP") { this.audioPlayer.play(); } } connect() { this.ttsWS?.close(); this.blob = ""; this.blobDoneCb && this.blobDoneCb(this.blob); const text = this.text || "请输入文字!"; const appId = this.appId; const apiKey = this.apiKey; const apiSecret = this.apiSecret; const url = this.getWebSocketUrl(apiKey, apiSecret); if ("WebSocket" in window) { this.ttsWS = new WebSocket(url); } else if ("MozWebSocket" in window) { this.ttsWS = new window.MozWebSocket(url); } else { alert("浏览器不支持WebSocket"); return; } this.changeBtnStatus("CONNECTING"); const _this = this; this.ttsWS.onopen = () => { _this.audioPlayer.start({ autoPlay: true, sampleRate: 16000, resumePlayDuration: 1000, }); _this.changeBtnStatus("PLAY"); var tte = "UTF8"; var params = { common: { app_id: appId, }, business: { aue: "raw", auf: "audio/L16;rate=16000", vcn: "50", speed: 50, volume: 50, pitch: 50, bgs: 1, tte, }, data: { status: 2, text: _this.encodeText(text, tte), }, }; _this.ttsWS.send(JSON.stringify(params)); }; this.ttsWS.onmessage = (e) => { let jsonData = JSON.parse(e.data); // 合成失败 if (jsonData.code !== 0) { console.error(jsonData); _this.changeBtnStatus("UNDEFINED"); return; } _this.audioPlayer.postMessage({ type: "base64", data: jsonData.data.audio, isLastData: jsonData.data.status === 2, }); if (jsonData.code === 0 && jsonData.data.status === 2) { _this.ttsWS.close(); setTimeout(() => { _this.blob = this.audioPlayer.getAudioDataBlob("wav"); _this.blobDoneCb && _this.blobDoneCb(_this.blob); }); } }; _this.ttsWS.onerror = (e) => { console.error(e); }; _this.ttsWS.onclose = (e) => { console.log(e); }; } download() { const blob = this.audioPlayer.getAudioDataBlob("wav"); if (!blob) { return; } let defaultName = new Date().getTime(); let node = document.createElement("a"); node.href = window.URL.createObjectURL(blob); node.download = `${defaultName}.wav`; node.click(); node.remove(); } reset() { this.ttsWS?.close(); this.text = ""; this.blob = ""; this.changeBtnStatus("UNDEFINED"); } }