dangdang 1 ay önce
ebeveyn
işleme
7a590f3757

+ 111 - 34
src/views/ChatTts.vue

@@ -17,6 +17,7 @@ const conversationId = computed(() => route.query.taskId); // 会话ID
 const round = computed(() => route.query.round); // 轮次
 const sessionId = ref("");
 const taskStatus = ref(false)
+const loading = ref(true);  //初始化进入的加载状态
 
 const currentRate = ref(0);
 const currentRound = computed(() => {
@@ -175,6 +176,7 @@ const playNextAudio = async () => {
   // 如果播放队列为空,停止播放
   if (audioQueue.length === 0) {
     isPlaying = false;  // 播放完所有音频
+    audioQueue = []; // 确保列表为空
     console.log("所有音频播放完毕.");
     return;
   }
@@ -228,7 +230,6 @@ const playNextAudio = async () => {
   }
 };
 
-
 // 获取下一个应该播放的音频(保证顺序)
 const getNextAudioToPlay = () => {
   // 检查音频队列中是否有下一个应该播放的音频
@@ -246,48 +247,82 @@ const getNextAudioToPlay = () => {
 };
 
 // 播放音频的方法
+const audioContext = new (window.AudioContext || window.webkitAudioContext)();
+
 const playAudio = (audioData) => {
   return new Promise((resolve, reject) => {
-    const audioContext = new (window.AudioContext || window.webkitAudioContext)();
     const blob = new Blob([audioData], { type: "audio/wav" });
     const url = URL.createObjectURL(blob);
-    const audioBufferSourceNode = audioContext.createBufferSource();
 
     fetch(url)
-        .then((response) => {
-          if (!response.ok) {
-            console.error("音频文件请求失败: ", response.statusText);
-            reject(new Error("音频文件请求失败"));
-            return;
-          }
-          return response.arrayBuffer();
-        })
-        .then((arrayBuffer) => audioContext.decodeAudioData(arrayBuffer))
-        .then((audioBuffer) => {
-          audioBufferSourceNode.buffer = audioBuffer;
-          audioBufferSourceNode.connect(audioContext.destination);
-
-          audioBufferSourceNode.start(0);
-
-          // 当音频播放完毕时,调用 resolve
-          audioBufferSourceNode.onended = () => {
-            URL.revokeObjectURL(url);
-            resolve();  // 播放完成后,继续播放下一个
-          };
-
-          audioBufferSourceNode.onerror = (error) => {
-            console.error("音频播放错误:", error);
-            URL.revokeObjectURL(url);
-            reject(error);  // 播放失败时,返回错误
-          };
-        })
-        .catch((error) => {
-          console.error("音频加载或解码时出错:", error);
-          reject(error);  // 如果解码或播放出错,reject
-        });
+      .then((res) => res.arrayBuffer())
+      .then((arrayBuffer) => audioContext.decodeAudioData(arrayBuffer))
+      .then((audioBuffer) => {
+        const source = audioContext.createBufferSource();
+        source.buffer = audioBuffer;
+        source.connect(audioContext.destination);
+        source.start(0);
+
+        source.onended = () => {
+          URL.revokeObjectURL(url);
+          resolve();
+        };
+
+        source.onerror = (error) => {
+          console.error("音频播放错误:", error);
+          URL.revokeObjectURL(url);
+          reject(error);
+        };
+      })
+      .catch((error) => {
+        console.error("音频加载或解码时出错:", error);
+        reject(error);
+      });
   });
 };
 
+// const playAudio = (audioData) => {
+//   return new Promise((resolve, reject) => {
+//     const audioContext = new (window.AudioContext || window.webkitAudioContext)();
+//     const blob = new Blob([audioData], { type: "audio/wav" });
+//     const url = URL.createObjectURL(blob);
+//     const audioBufferSourceNode = audioContext.createBufferSource();
+
+//     fetch(url)
+//         .then((response) => {
+//           if (!response.ok) {
+//             console.error("音频文件请求失败: ", response.statusText);
+//             reject(new Error("音频文件请求失败"));
+//             return;
+//           }
+//           return response.arrayBuffer();
+//         })
+//         .then((arrayBuffer) => audioContext.decodeAudioData(arrayBuffer))
+//         .then((audioBuffer) => {
+//           audioBufferSourceNode.buffer = audioBuffer;
+//           audioBufferSourceNode.connect(audioContext.destination);
+
+//           audioBufferSourceNode.start(0);
+
+//           // 当音频播放完毕时,调用 resolve
+//           audioBufferSourceNode.onended = () => {
+//             URL.revokeObjectURL(url);
+//             resolve();  // 播放完成后,继续播放下一个
+//           };
+
+//           audioBufferSourceNode.onerror = (error) => {
+//             console.error("音频播放错误:", error);
+//             URL.revokeObjectURL(url);
+//             reject(error);  // 播放失败时,返回错误
+//           };
+//         })
+//         .catch((error) => {
+//           console.error("音频加载或解码时出错:", error);
+//           reject(error);  // 如果解码或播放出错,reject
+//         });
+//   });
+// };
+
 // const playTTS1 = async (ttsMessage) => {
 //   try {
 //     const res = await axios.post(
@@ -491,9 +526,35 @@ const handleTaskStatus = async () => {
   }
 }
 
+async function requestMicrophonePermission() {
+  try {
+    const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
+    hasPermission.value = true;
+    // console.log("🎤 录音权限已授予");
+    return stream;
+  } catch (error) {
+    // console.warn("⛔ 录音权限被拒绝", error);
+    retryPermissionRequest();
+  }
+}
+
+async function retryPermissionRequest() {
+  const permissionStatus = await navigator.permissions.query({ name: "microphone" });
+
+  if (permissionStatus.state === "denied") {
+    Toast("您已拒绝录音权限,请前往浏览器或系统设置手动开启权限。");
+  } else {
+    requestMicrophonePermission();
+  }
+}
+
 // 在组件挂载时调用
 onMounted(() => {
   loadChatHistory();
+  requestMicrophonePermission();
+  setTimeout(() => {
+    loading.value = false;
+  }, 2000);
 });
 
 </script>
@@ -511,6 +572,10 @@ onMounted(() => {
       />
     </div>
     <BG />
+    <div class="loading" v-show="loading">
+      <van-loading color="#0094ff" size="26px" vertical>加载中...</van-loading>
+    </div>
+    
     <ChatList :chatList="chatList" />
     <BottomArea
       @startRecord="handleStartRecord"
@@ -554,4 +619,16 @@ onMounted(() => {
   box-sizing: border-box;
   padding: 0 20px;
 }
+.loading {
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  background-color: rgba(0, 0, 0, 0.5);
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  z-index: 9999;
+}
 </style>

+ 18 - 9
src/views/components/BottomArea.vue

@@ -219,12 +219,6 @@ onMounted(() => {
       );
     }
   );
-  //console.log(document.querySelector(".recwave"));
-  //const div = document.createElement("div");
-  // const div = document.querySelector(".wave");
-  // div.innerHTML =
-  //   '<div style="height:100px;width:100%;" class="recwave"></div>';
-  //document.body.prepend(div);
 });
 
 onBeforeUnmount(() => {
@@ -241,13 +235,28 @@ const startRecording = async (e) => {
   emit("startRecord");
 };
 
+// const stopRecording = async () => {
+//   isTalking.value = false;
+//   //等待500ms后,在执行以下代码
+//   await new Promise((resolve) => setTimeout(resolve, 1000));
+//   rec.stop();
+//   RealTimeSendTry([], 0, true); // 最后一次发送
+//   await asrPost("SESSION_END");
+//   voicePkgSeq = 0;
+//   emit("stopRecord", userTalk.value);
+// };
 const stopRecording = async () => {
   isTalking.value = false;
-  //等待500ms后,在执行以下代码
-  await new Promise((resolve) => setTimeout(resolve, 1000));
+  
+  // 先执行最后一次数据发送,确保数据完整
+  RealTimeSendTry([], 0, true);
+  
+  // 停止录音
   rec.stop();
-  RealTimeSendTry([], 0, true); // 最后一次发送
+
+  // 发送 SESSION_END 事件,确保后端能接收完整数据
   await asrPost("SESSION_END");
+
   voicePkgSeq = 0;
   emit("stopRecord", userTalk.value);
 };

+ 1 - 0
src/views/components/ChatList.vue

@@ -50,6 +50,7 @@ defineExpose({
   bottom: calc(65px + env(safe-area-inset-bottom));
   padding: 10px;
   overflow-y: auto;
+  overflow-x: hidden;
   box-sizing: border-box;
   mask-image: linear-gradient(to top, black 70%, transparent);
   -mask-image: linear-gradient(to top, black 70%, transparent);