浏览代码

feat: 音频改造

zhangjie 7 月之前
父节点
当前提交
fa4635387e

+ 1 - 1
src/components/mathjax-editor/menu-data/formula/sequence.js

@@ -12,7 +12,7 @@ const data = {
       source: "sequence_2",
     },
     {
-      output: "S_{n}=na_{1}+\\frac{n \\eft( n-1 \\right)}{{2}}d ",
+      output: "S_{n}=na_{1}+\\frac{n \\left( n-1 \\right)}{{2}}d ",
       source: "sequence_3",
     },
     {

+ 88 - 40
src/components/vEditor/components/TtsDialog.vue

@@ -8,26 +8,41 @@
     :close-on-click-modal="false"
     :close-on-press-escape="false"
     append-to-body
-    @opened="initData"
+    @opened="opened"
     @closed="closed"
   >
     <el-input
+      v-model="text"
       type="textarea"
       :rows="8"
-      v-model="text"
       maxlength="2000"
       show-word-limit
+      :disabled="loading"
     ></el-input>
     <div class="btns">
-      <el-button
-        :type="status === 'PLAY' ? 'danger' : 'primary'"
-        @click="action"
-        :disabled="!text"
-        >{{ statusStr }}</el-button
-      >
+      <div>
+        <el-button
+          type="primary"
+          icon="el-icon-help"
+          :loading="loading"
+          @click="toBuild"
+          >开始合成</el-button
+        >
+      </div>
+      <div>
+        <el-button
+          v-if="dataFilled"
+          :type="playing ? 'danger' : 'primary'"
+          :icon="playing ? 'el-icon-video-pause' : 'el-icon-video-play'"
+          @click="toPlay"
+          >{{ playing ? "暂停播放" : "开始播放" }}</el-button
+        >
+      </div>
     </div>
+    <div v-if="errorMsg" class="tips danger">{{ errorMsg }}</div>
+    <div v-if="done" class="tips success">合成成功!</div>
     <div slot="footer">
-      <el-button type="primary" :disabled="loading || !blob" @click="submit"
+      <el-button type="primary" :disabled="loading" @click="submit"
         >保存音频</el-button
       >
       <el-button type="danger" plain @click="cancel">取消</el-button>
@@ -43,63 +58,96 @@ export default {
   props: {},
   data() {
     return {
-      loading: false,
       modalIsShow: false,
       ttsVoice: null,
       text: ``,
-      status: "UNDEFINED",
-      statusStr: "立即合成",
-      blob: null,
     };
   },
-  created() {
-    this.ttsVoice = new TtsVoice({
-      appId: "5cc0340f",
-      apiSecret: "fb1702602169809fef3dc60a16d60493",
-      apiKey: "e5b67c040d614b7bfb9aa852731947eb",
-      cb: this.watchStatus,
-      blobDoneCb: this.getBlob,
-    });
+  computed: {
+    loading() {
+      return this.ttsVoice && this.ttsVoice.running;
+    },
+    playing() {
+      return this.ttsVoice && this.ttsVoice.playing;
+    },
+    dataFilled() {
+      return this.ttsVoice && this.ttsVoice.dataFilled;
+    },
+    done() {
+      return this.ttsVoice && this.ttsVoice.done;
+    },
+    errorMsg() {
+      return this.ttsVoice?.error ? this.ttsVoice.errorMsg : "";
+    },
   },
   beforeDestroy() {
-    this.ttsVoice.ttsWS?.close();
+    this.ttsVoice?.reset();
+    this.ttsVoice = null;
   },
   methods: {
-    action() {
-      this.ttsVoice?.action(this.text);
+    initTts() {
+      this.ttsVoice = new TtsVoice({
+        appId: "5cc0340f",
+        apiSecret: "fb1702602169809fef3dc60a16d60493",
+        apiKey: "e5b67c040d614b7bfb9aa852731947eb",
+      });
     },
-    watchStatus(obj) {
-      this.status = obj.status;
-      this.statusStr = obj.statusStr;
+    toBuild() {
+      if (!this.text) {
+        this.$message.error("请输入内容!");
+        return;
+      }
+      this.ttsVoice.run(this.text);
     },
-    initData() {
-      this.text = ``;
-      this.status = "UNDEFINED";
-      this.statusStr = "立即合成";
-      this.blob = null;
+    toPlay() {
+      if (this.playing) {
+        this.ttsVoice.stop();
+      } else {
+        this.ttsVoice.play();
+      }
+    },
+    opened() {
+      this.initTts();
     },
     closed() {
+      this.text = "";
       this.ttsVoice?.reset();
+      this.ttsVoice = null;
     },
     cancel() {
-      this.ttsVoice?.reset();
       this.modalIsShow = false;
     },
     open() {
       this.modalIsShow = true;
     },
-    getBlob(blob) {
-      this.blob = blob;
-      console.log("done blob", blob);
-    },
-    async submit() {
-      this.$emit("confirm", this.blob);
+    submit() {
+      const blob = this.ttsVoice.getBlob();
+      if (!blob) {
+        this.$message.error("文件生成失败,请重新尝试!");
+        return;
+      }
+
+      this.$emit("confirm", blob);
     },
   },
 };
 </script>
 <style lang="scss" scoped>
 .btns {
-  padding-top: 10px;
+  margin-top: 10px;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  min-height: 32px;
+}
+.tips {
+  min-height: 20px;
+  line-height: 20px;
+}
+.tips.danger {
+  color: #f56c6c;
+}
+.tips.success {
+  color: #1fb46f;
 }
 </style>

+ 1 - 1
src/components/vEditor/components/VMenu.vue

@@ -155,7 +155,7 @@ export default {
       const file = new File([blob], `${new Date().getTime()}.wav`, {
         type: "audio/wav",
       });
-      console.log("file", file);
+      // console.log("file", file);
       let result = true;
       await uploadAudioHandle
         .bind(this.$parent)(file)

+ 80 - 68
src/plugins/tts/index.js

@@ -8,49 +8,67 @@ export default class TtsVoice {
   apiSecret = "";
   apiKey = "";
   ttsWS = null;
-  status = "UNDEFINED";
-  statusStr = "";
   audioPlayer = null;
   text = "";
-  blob = "";
-  cb = () => {};
-  blobDoneCb = () => {};
+  playing = false;
+  running = false;
+  done = false;
+  error = false;
+  errorMsg = "";
+  dataFilled = false;
+
   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.playing = true;
     };
-    this.audioPlayer.onStop = (audioDatas) => {
-      console.log(audioDatas);
-      this.status === "PLAY" && this.changeBtnStatus("STOP");
+    this.audioPlayer.onStop = () => {
+      // console.log(audioDatas);
+      this.playing = false;
     };
   }
+
   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 });
+
+  run(text) {
+    if (!text) return;
+
+    this.text = "";
+    this.playing = false;
+    this.done = false;
+    this.error = false;
+    this.errorMsg = "";
+    this.dataFilled = false;
+
+    this.text = text;
+    this.running = true;
+    this.connect();
   }
+
+  stopRun() {
+    this.ttsWS?.close();
+    this.audioPlayer.reset();
+    this.running = false;
+    this.playing = false;
+    this.done = false;
+  }
+
+  play() {
+    if (!this.playing) this.audioPlayer.play();
+  }
+  stop() {
+    if (this.playing) this.audioPlayer.stop();
+  }
+
+  getBlob() {
+    return this.audioPlayer.getAudioDataBlob("wav");
+  }
+
   encodeText(text, type) {
     if (type === "unicode") {
       let buf = new ArrayBuffer(text.length * 4);
@@ -85,39 +103,9 @@ export default class TtsVoice {
     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 text = this.text;
     const appId = this.appId;
     const apiKey = this.apiKey;
     const apiSecret = this.apiSecret;
@@ -130,7 +118,7 @@ export default class TtsVoice {
       alert("浏览器不支持WebSocket");
       return;
     }
-    this.changeBtnStatus("CONNECTING");
+
     const _this = this;
     this.ttsWS.onopen = () => {
       _this.audioPlayer.start({
@@ -138,7 +126,6 @@ export default class TtsVoice {
         sampleRate: 16000,
         resumePlayDuration: 1000,
       });
-      _this.changeBtnStatus("PLAY");
       var tte = "UTF8";
       var params = {
         common: {
@@ -161,14 +148,20 @@ export default class TtsVoice {
       };
       _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");
+        this.stopConnect();
+        this.error = true;
+        this.errorMsg = jsonData.message;
         return;
       }
+      // console.log(e);
+
+      _this.dataFilled = true;
       _this.audioPlayer.postMessage({
         type: "base64",
         data: jsonData.data.audio,
@@ -177,18 +170,30 @@ export default class TtsVoice {
       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.running = false;
+          this.done = true;
         }, 300);
       }
     };
     _this.ttsWS.onerror = (e) => {
       console.error(e);
+      this.stopConnect();
+      this.error = true;
+      this.errorMsg = e.message || "tts error";
     };
     _this.ttsWS.onclose = (e) => {
       console.log(e);
     };
   }
+
+  stopConnect() {
+    this.running = false;
+    this.playing = false;
+    this.ttsWS?.close();
+    this.audioPlayer.reset();
+    this.dataFilled = false;
+  }
+
   download() {
     const blob = this.audioPlayer.getAudioDataBlob("wav");
     if (!blob) {
@@ -201,11 +206,18 @@ export default class TtsVoice {
     node.click();
     node.remove();
   }
+
   reset() {
-    this.ttsWS?.close();
     this.text = "";
-    this.blob = "";
-    this.changeBtnStatus("UNDEFINED");
+    this.playing = false;
+    this.running = false;
+    this.done = false;
+    this.error = false;
+    this.errorMsg = "";
+    this.dataFilled = false;
+    this.ttsWS?.close();
     this.audioPlayer?.reset();
+    this.ttsWS = null;
+    this.audioPlayer = null;
   }
 }