Просмотр исходного кода

Merge branch 'master' of http://59.110.91.129:3000/zhangmengying/AIClass into wanzi

丸子 1 месяц назад
Родитель
Сommit
a796f4cc09
2 измененных файлов с 124 добавлено и 81 удалено
  1. 26 2
      src/api/tts/useAudioPlayer.js
  2. 98 79
      src/views/AIPage/aiGenerate/DialogContent.vue

+ 26 - 2
src/api/tts/useAudioPlayer.js

@@ -3,6 +3,7 @@ export function useAudioPlayer() {
     let audioQueue = [];
     let audioQueue = [];
     let isPlaying = false;
     let isPlaying = false;
     let currentTime = 0; // 当前播放时间(用于连续播放)
     let currentTime = 0; // 当前播放时间(用于连续播放)
+    let onPlaybackComplete = null; // 音频播放完成回调
     const SAMPLE_RATE = 16000; // 匹配后端采样率
     const SAMPLE_RATE = 16000; // 匹配后端采样率
     const CHANNELS = 1; // 单声道
     const CHANNELS = 1; // 单声道
     const BIT_DEPTH = 16; // 16位深
     const BIT_DEPTH = 16; // 16位深
@@ -34,6 +35,10 @@ export function useAudioPlayer() {
     const processAudioQueue = async () => {
     const processAudioQueue = async () => {
         if (audioQueue.length === 0) {
         if (audioQueue.length === 0) {
             isPlaying = false;
             isPlaying = false;
+            // 音频播放完成,调用回调
+            if (onPlaybackComplete) {
+                onPlaybackComplete();
+            }
             return;
             return;
         }
         }
 
 
@@ -61,6 +66,10 @@ export function useAudioPlayer() {
         } catch (error) {
         } catch (error) {
             console.error('音频处理失败:', error);
             console.error('音频处理失败:', error);
             isPlaying = false;
             isPlaying = false;
+            // 错误时也调用回调
+            if (onPlaybackComplete) {
+                onPlaybackComplete();
+            }
         }
         }
     };
     };
 
 
@@ -85,19 +94,34 @@ export function useAudioPlayer() {
     };
     };
 
 
     // 停止播放并清理
     // 停止播放并清理
-    const stopPlayback = () => {
+    const stopPlayback = (callCallback = true) => {
         if (audioContext) {
         if (audioContext) {
             audioContext.close().then(() => {
             audioContext.close().then(() => {
                 audioContext = null;
                 audioContext = null;
                 currentTime = 0; // 重置播放时间
                 currentTime = 0; // 重置播放时间
+                // 停止播放时调用回调
+                if (callCallback && onPlaybackComplete) {
+                    onPlaybackComplete();
+                }
             });
             });
+        } else {
+            // 如果没有音频上下文,直接调用回调
+            if (callCallback && onPlaybackComplete) {
+                onPlaybackComplete();
+            }
         }
         }
         audioQueue = [];
         audioQueue = [];
         isPlaying = false;
         isPlaying = false;
     };
     };
 
 
+    // 设置播放完成回调
+    const setOnPlaybackComplete = (callback) => {
+        onPlaybackComplete = callback;
+    };
+
     return {
     return {
         playAudioChunk,
         playAudioChunk,
-        stopPlayback
+        stopPlayback,
+        setOnPlaybackComplete
     };
     };
 }
 }

+ 98 - 79
src/views/AIPage/aiGenerate/DialogContent.vue

@@ -19,7 +19,7 @@
           <el-icon class="arrow-icon"><CaretLeft /></el-icon>
           <el-icon class="arrow-icon"><CaretLeft /></el-icon>
         </div>
         </div>
         <div class="arrow-icon-circle" @click="togglePlay">
         <div class="arrow-icon-circle" @click="togglePlay">
-          <span class="play-text">{{ isPlaying ? '暂停' : '播放' }}</span>
+          <span class="play-text">{{ isPlaying ? '暂停' : '自动' }}</span>
         </div>
         </div>
         <div class="arrow-icon-circle" @click="playNext" :class="{ 'disabled': currentSectionIndex === scriptData.sections.length - 1 && currentDialogueIndex === currentSection.dialogues.length - 1 }">
         <div class="arrow-icon-circle" @click="playNext" :class="{ 'disabled': currentSectionIndex === scriptData.sections.length - 1 && currentDialogueIndex === currentSection.dialogues.length - 1 }">
           <el-icon class="arrow-icon"><CaretRight /></el-icon>
           <el-icon class="arrow-icon"><CaretRight /></el-icon>
@@ -105,7 +105,7 @@ import { marked } from 'marked'
 import {CreateDialogue, sendChatMessageStream} from "@/api/questions.js";
 import {CreateDialogue, sendChatMessageStream} from "@/api/questions.js";
 import {useAudioPlayer} from "@/api/tts/useAudioPlayer.js";
 import {useAudioPlayer} from "@/api/tts/useAudioPlayer.js";
 
 
-const { playAudioChunk , stopPlayback  } = useAudioPlayer();
+const { playAudioChunk , stopPlayback, setOnPlaybackComplete  } = useAudioPlayer();
 
 
 // 路由实例
 // 路由实例
 const router = useRouter()
 const router = useRouter()
@@ -135,8 +135,6 @@ const currentSectionIndex = ref(0)
 const currentDialogueIndex = ref(0)
 const currentDialogueIndex = ref(0)
 // 是否正在播放
 // 是否正在播放
 const isPlaying = ref(false)
 const isPlaying = ref(false)
-// 用户输入前是否正在播放(用于AI回答完成后继续播放)
-const wasPlayingBeforeUserInput = ref(false)
 // 用户输入内容
 // 用户输入内容
 const userInput = ref('')
 const userInput = ref('')
 // 语音录音状态
 // 语音录音状态
@@ -299,7 +297,7 @@ const playBackgroundAudio = () => {
   }
   }
 }
 }
 
 
-const playDialogueAudio = () => {
+const playDialogueAudio = (isAutoPlay = false) => {
   // 停止之前的对话语音
   // 停止之前的对话语音
   if (dialogueAudio.value) {
   if (dialogueAudio.value) {
     dialogueAudio.value.pause()
     dialogueAudio.value.pause()
@@ -308,41 +306,49 @@ const playDialogueAudio = () => {
   
   
   // 播放当前对话的语音
   // 播放当前对话的语音
   if (currentDialogue.value?.voiceoverUrl) {
   if (currentDialogue.value?.voiceoverUrl) {
-    dialogueAudio.value = new Audio(currentDialogue.value.voiceoverUrl)
-    return new Promise((resolve) => {
-      // 最短3秒跳转
-      const minDelay = 3000
-      let audioEnded = false
-      
-      // 音频结束事件
-      dialogueAudio.value.onended = () => {
-        audioEnded = true
-        resolve()
+    const audio = new Audio(currentDialogue.value.voiceoverUrl)
+    dialogueAudio.value = audio
+    
+    // 音频结束事件
+    audio.onended = () => {
+      // 如果是自动播放状态,继续播放下一条
+      if (isAutoPlay && isPlaying.value) {
+        setTimeout(() => {
+          if (!playNext(true)) {
+            // 播放完毕
+            isPlaying.value = false
+            stopAllAudio()
+          }
+        }, 100)
       }
       }
-      
-      // 播放音频
-      dialogueAudio.value.play().catch(e => {
-        console.error('对话语音播放失败:', e)
-        // 播放失败时,2秒后跳转
-        setTimeout(resolve, minDelay)
-      })
-      
-      // 检查音频时长
-      dialogueAudio.value.addEventListener('loadedmetadata', () => {
-        const duration = dialogueAudio.value.duration
-        // 如果音频时长小于2秒,2秒后跳转
-        if (duration < 2) {
-          setTimeout(() => {
-            if (!audioEnded) {
-              resolve()
-            }
-          }, minDelay)
-        }
-      })
+    }
+    
+    // 播放音频
+    audio.play().catch(e => {
+      console.error('对话语音播放失败:', e)
+      // 播放失败时,2秒后跳转(暂时注销,因为会导致user类型数字人回复播报跳转后语音播报报错,直接2秒跳转)
+      // if (isAutoPlay && isPlaying.value) {
+      //   setTimeout(() => {
+      //     if (!playNext(true)) {
+      //       // 播放完毕
+      //       isPlaying.value = false
+      //       stopAllAudio()
+      //     }
+      //   }, 2000)
+      // }
     })
     })
+  } else {
+    // 如果没有语音文件,2秒后跳转
+    if (isAutoPlay && isPlaying.value && currentDialogue.value?.type !== 'user') {
+      setTimeout(() => {
+        if (!playNext(true)) {
+          // 播放完毕
+          isPlaying.value = false
+          stopAllAudio()
+        }
+      }, 2000)
+    }
   }
   }
-  // 如果没有语音文件,2秒后跳转
-  return new Promise(resolve => setTimeout(resolve, 2000))
 }
 }
 
 
 const togglePlay = () => {
 const togglePlay = () => {
@@ -358,27 +364,15 @@ const togglePlay = () => {
   }
   }
 }
 }
 
 
-const playSequence = async () => {
+// 自动播放序列
+const playSequence = () => {
   if (!isPlaying.value) return
   if (!isPlaying.value) return
 
 
   // 如果当前是用户输入卡片,暂停播放等待用户输入
   // 如果当前是用户输入卡片,暂停播放等待用户输入
-  if (currentDialogue.value?.type === 'user') {
-    wasPlayingBeforeUserInput.value = true
-    return
-  }
-
-  // 播放当前对话语音
-  await playDialogueAudio()
+  if (currentDialogue.value?.type === 'user') return
 
 
-  // 播放下一个对话
-  if (!playNext()) {
-    // 播放完毕
-    isPlaying.value = false
-    // 停止所有音频,包括背景音
-    stopAllAudio()
-  } else {
-    playSequence()
-  }
+  // 播放当前对话语音,传递isAutoPlay参数
+  playDialogueAudio(true)
 }
 }
 
 
 const playPrevious = () => {
 const playPrevious = () => {
@@ -387,7 +381,7 @@ const playPrevious = () => {
 
 
   // 如果正在进行数字人对话,调用stopStream清理
   // 如果正在进行数字人对话,调用stopStream清理
   recoverQuestDialogue()
   recoverQuestDialogue()
-  stopPlayback()
+  stopPlayback(false) // 不触发回调
   if (conversationInProgress.value) {
   if (conversationInProgress.value) {
     stopStream()
     stopStream()
   }
   }
@@ -403,10 +397,14 @@ const playPrevious = () => {
   // 播放背景音
   // 播放背景音
   playBackgroundAudio()
   playBackgroundAudio()
   // 播放当前对话语音
   // 播放当前对话语音
-  playDialogueAudio()
+  if (isPlaying.value) {
+    playDialogueAudio(true)
+  } else {
+    playDialogueAudio()
+  }
 }
 }
 
 
-const playNext = () => {
+const playNext = (isAutoPlay = false) => {
   // 停止当前对话语音
   // 停止当前对话语音
   if (dialogueAudio.value) {
   if (dialogueAudio.value) {
     dialogueAudio.value.pause()
     dialogueAudio.value.pause()
@@ -415,23 +413,31 @@ const playNext = () => {
   
   
   // 如果正在进行数字人对话,调用stopStream清理
   // 如果正在进行数字人对话,调用stopStream清理
   recoverQuestDialogue()
   recoverQuestDialogue()
-  stopPlayback()
+  stopPlayback(false)
   if (conversationInProgress.value) {
   if (conversationInProgress.value) {
     stopStream()
     stopStream()
   }
   }
 
 
   if (currentSection.value && currentDialogueIndex.value < currentSection.value.dialogues.length - 1) {
   if (currentSection.value && currentDialogueIndex.value < currentSection.value.dialogues.length - 1) {
     currentDialogueIndex.value++
     currentDialogueIndex.value++
-    // 播放当前对话语音
-    playDialogueAudio()
+    // 根据是否为自动播放状态决定如何播放语音
+    if (isPlaying.value) {
+      playDialogueAudio(true)
+    } else {
+      playDialogueAudio()
+    }
     return true
     return true
   } else if (currentSectionIndex.value < props.scriptData.sections.length - 1) {
   } else if (currentSectionIndex.value < props.scriptData.sections.length - 1) {
     currentSectionIndex.value++
     currentSectionIndex.value++
     currentDialogueIndex.value = 0
     currentDialogueIndex.value = 0
     // 播放背景音
     // 播放背景音
     playBackgroundAudio()
     playBackgroundAudio()
-    // 播放当前对话语音
-    playDialogueAudio()
+    // 根据是否为自动播放状态决定如何播放语音
+    if (isPlaying.value) {
+      playDialogueAudio(true)
+    } else {
+      playDialogueAudio()
+    }
     return true
     return true
   }
   }
   return false
   return false
@@ -515,13 +521,12 @@ const doSendMessage = async () => {
   })
   })
   // 清空输入框
   // 清空输入框
   userInput.value = ''
   userInput.value = ''
-  recoverQuestDialogue()
 }
 }
 
 
 /** 显示问题回答对话 */
 /** 显示问题回答对话 */
 const showQuestAnswerDialogue = () => {
 const showQuestAnswerDialogue = () => {
   // 缓存当前对话
   // 缓存当前对话
-  currentDialogueCache.value = currentDialogue.value
+  currentDialogueCache.value = JSON.parse(JSON.stringify(currentDialogue.value))
 
 
   // 将当前对话类型设置为数字人对话
   // 将当前对话类型设置为数字人对话
   currentDialogue.value.type = "digital"
   currentDialogue.value.type = "digital"
@@ -529,11 +534,21 @@ const showQuestAnswerDialogue = () => {
   currentDialogue.value.content = "让我思考一下..."
   currentDialogue.value.content = "让我思考一下..."
 
 
 }
 }
+
+
+
 /** 回复对话 */
 /** 回复对话 */
 const recoverQuestDialogue = () => {
 const recoverQuestDialogue = () => {
-  // 缓存当前对话
-  currentDialogue.value = currentDialogueCache.value
-  currentDialogueCache.value = null
+  // 如果有缓存的对话
+  if (currentDialogueCache.value){
+    // 恢复当前对话
+    const currentSection = props.scriptData.sections[currentSectionIndex.value]
+    if (currentSection) {
+      currentSection.dialogues[currentDialogueIndex.value] = JSON.parse(JSON.stringify(currentDialogueCache.value))
+    }
+    // 清空缓存
+    currentDialogueCache.value = null
+  }
 }
 }
 
 
 /** 真正执行【发送】消息操作 */
 /** 真正执行【发送】消息操作 */
@@ -561,7 +576,6 @@ const doSendMessageStream = async userMessage => {
           const { code, data, msg } = JSON.parse(res.data)
           const { code, data, msg } = JSON.parse(res.data)
           if (code !== 0) {
           if (code !== 0) {
             console.log(`对话异常! ${msg}`)
             console.log(`对话异常! ${msg}`)
-            recoverQuestDialogue();
             stopStream();
             stopStream();
             return
             return
           }
           }
@@ -590,20 +604,17 @@ const doSendMessageStream = async userMessage => {
         },
         },
         error => {
         error => {
           console.log(`对话异常! ${error}`)
           console.log(`对话异常! ${error}`)
-          recoverQuestDialogue();
           stopStream()
           stopStream()
           // 需要抛出异常,禁止重试
           // 需要抛出异常,禁止重试
           throw error
           throw error
         },
         },
         () => {
         () => {
           console.log(`结束对话! `)
           console.log(`结束对话! `)
-          recoverQuestDialogue();
           stopStream()
           stopStream()
         }
         }
     )
     )
   } catch (error) {
   } catch (error) {
     console.error('发送消息失败:', error)
     console.error('发送消息失败:', error)
-    recoverQuestDialogue();
     stopStream()
     stopStream()
   }
   }
 }
 }
@@ -620,20 +631,26 @@ const stopStream = async () => {
   conversationInProgress.value = false
   conversationInProgress.value = false
 
 
   console.log(`结束对话!更改状态: `,conversationInProgress.value)
   console.log(`结束对话!更改状态: `,conversationInProgress.value)
+}
 
 
+// 处理音频播放完成
+const handleAudioPlaybackComplete = () => {
+  console.log('智能问答音频播放完成');
   // AI回答完成后,如果之前是播放状态,继续播放
   // AI回答完成后,如果之前是播放状态,继续播放
-  if (wasPlayingBeforeUserInput.value) {
-    wasPlayingBeforeUserInput.value = false
-    // 跳到下一个对话并继续播放
+
+  stopAllAudio();
+  // 如果处于自动播放状态,继续播放下一条对话
+  if (isPlaying.value) {
     if (playNext()) {
     if (playNext()) {
-      playSequence()
+      // 继续自动播放序列
+      playSequence();
     } else {
     } else {
       // 播放完毕
       // 播放完毕
-      isPlaying.value = false
-      stopAllAudio()
+      isPlaying.value = false;
+      stopAllAudio();
     }
     }
   }
   }
-}
+};
 
 
 // 组件挂载时添加键盘事件监听
 // 组件挂载时添加键盘事件监听
 onMounted(() => {
 onMounted(() => {
@@ -642,13 +659,15 @@ onMounted(() => {
   playBackgroundAudio()
   playBackgroundAudio()
   // 播放当前对话语音
   // 播放当前对话语音
   playDialogueAudio()
   playDialogueAudio()
+  // 设置音频播放完成回调
+  setOnPlaybackComplete(handleAudioPlaybackComplete)
 })
 })
 
 
 // 组件卸载时移除键盘事件监听和停止音频
 // 组件卸载时移除键盘事件监听和停止音频
 onUnmounted(() => {
 onUnmounted(() => {
   window.removeEventListener('keydown', handleKeydown)
   window.removeEventListener('keydown', handleKeydown)
   stopAllAudio()
   stopAllAudio()
-  stopPlayback()
+  stopPlayback(false)
 })
 })
 </script>
 </script>