Commit afe81980 authored by ali's avatar ali

feat: tts 语音播放结束后,视频不不暂停问题;回答状态对应处理。

parent 83f308a7
...@@ -21,17 +21,16 @@ const iconMicrophone = new URL('/images/microphone-input.svg', import.meta.url). ...@@ -21,17 +21,16 @@ const iconMicrophone = new URL('/images/microphone-input.svg', import.meta.url).
const recordVolume = ref(0) const recordVolume = ref(0)
const url = route.query.url as string const url = route.query.url as string
const role = useVideo.list.find((i) => i.url === url) const role = useVideo.list.find((i) => i.url === url)
const microphoneState = ref<'waitInput' | 'input' | 'loading' | 'disabled'>('waitInput') const microphoneState = ref<'waitInput' | 'input' | 'loading' | 'disabled' | 'reply'>('waitInput')
const videoElement = ref<HTMLVideoElement | null>(null) const videoElement = ref<HTMLVideoElement | null>(null)
const videoElement2 = ref<HTMLVideoElement | null>(null)
const videos = [videoElement, videoElement2];
onMounted(() => { onMounted(() => {
// init(); // init();
}) })
async function init() {
const videoEle = videoElement.value
}
router.beforeEach((g) => { router.beforeEach((g) => {
if (!g.query.url) return router.push('/error') if (!g.query.url) return router.push('/error')
}) })
...@@ -184,7 +183,13 @@ async function startVoskWsAudioInput() { ...@@ -184,7 +183,13 @@ async function startVoskWsAudioInput() {
source.connect(processor) source.connect(processor)
processor.connect(audioContext.destination) processor.connect(audioContext.destination)
processor.onaudioprocess = (audioDataChunk) => postAudio(audioDataChunk) processor.onaudioprocess = (audioDataChunk) => {
if (microphoneState.value === 'loading' || microphoneState.value === 'disabled' || microphoneState.value === 'reply') {
return;
}
postAudio(audioDataChunk);
}
await analyzeMicrophoneVolume(mediaStream, (val) => { await analyzeMicrophoneVolume(mediaStream, (val) => {
recordVolume.value = val recordVolume.value = val
...@@ -249,29 +254,41 @@ function endAudioInput() { ...@@ -249,29 +254,41 @@ function endAudioInput() {
} }
} }
function setVideoUrl(url: string) { const canplay = () => {
const videoEle = videoElement.value as HTMLVideoElement videos[1].value!.style.opacity = '1';
if (!videoEle) return videos[0].value!.style.opacity = '0';
videos[0].value!.pause();
videos[1].value!.play();
videos[1].value!.removeEventListener('canplay', canplay);
videos.unshift(videos.pop()!);
}
videoEle.src = url function loadVideo(url: string) {
videoEle.load() videos[1].value!.src = url
videoEle.play() videos[1].value!.style.opacity = '0';
videos[1].value!.addEventListener('canplay', canplay);
} }
async function onAsr(question: string) { async function onAsr(question: string) {
endAudioInput()
console.log('---------------->', question) console.log('---------------->', question)
if (!role) return if (!role) return;
microphoneState.value = 'loading';
question = question.replace(/\s/g, '') question = question.replace(/\s/g, '')
for (let i = 0; i < role.qa.length; i++) { for (let i = 0; i < role.qa.length; i++) {
const { q, url } = role.qa[i] const { q, url } = role.qa[i]
console.log(question + ' : ' + q) console.log(question + ' : ' + q)
if (q.includes(question)) { if (q.includes(question)) {
const videoEle = videoElement.value as HTMLVideoElement loadVideo(url)
videoEle && (videoEle.loop = false) microphoneState.value = 'reply';
videoEle && (videoEle.muted = false) const videoEle = videos[1].value
setVideoUrl(url) videoEle!.loop = false
videoEle!.muted = false
videoEle!.onended = () => {
videoEle!.onended = null;
microphoneState.value = 'input';
// 是否需要初始化
}
return return
} }
} }
...@@ -358,6 +375,7 @@ async function runTTSTask(tasks: string[]) { ...@@ -358,6 +375,7 @@ async function runTTSTask(tasks: string[]) {
runAudioPlay() runAudioPlay()
} }
} catch (error) { } catch (error) {
microphoneState.value = 'input'
console.error(error) console.error(error)
} }
...@@ -371,20 +389,21 @@ async function runAudioPlay() { ...@@ -371,20 +389,21 @@ async function runAudioPlay() {
isPlayRunning = true isPlayRunning = true
const audio = ttsAudios.shift() const audio = ttsAudios.shift()
const videoEle = videoElement.value as HTMLVideoElement;
if (!audio) { if (!audio) {
isPlayRunning = false; isPlayRunning = false;
videoEle.pause(); videos[0].value!.pause();
microphoneState.value = 'input';
return return
} }
audio.onended = () => { audio.onended = () => {
isPlayRunning = false isPlayRunning = false
videoEle && (videoEle.loop = true) loadVideo(new URL('/libai/10.mp4', import.meta.url).href)
videoEle && (videoEle.muted = true) videos[1].value!.loop = true
setVideoUrl(new URL('/libai/10.mp4', import.meta.url).href) videos[1].value!.muted = true
runAudioPlay() runAudioPlay()
} }
await audio.play() await audio.play();
microphoneState.value = 'reply';
} }
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
...@@ -400,6 +419,7 @@ async function xfTTS(text: string) { ...@@ -400,6 +419,7 @@ async function xfTTS(text: string) {
}) })
console.log('----------------> tts:', res) console.log('----------------> tts:', res)
} }
</script> </script>
<template> <template>
...@@ -408,7 +428,8 @@ async function xfTTS(text: string) { ...@@ -408,7 +428,8 @@ async function xfTTS(text: string) {
class="d-flex justify-center align-center" class="d-flex justify-center align-center"
:style="{ background: '#000' }" :style="{ background: '#000' }"
> >
<video id="videoElement" ref="videoElement" :src="url" class="video-ele"></video> <video id="videoElement" ref="videoElement" :src="url" class="video-ele active"></video>
<video id="videoElement2" ref="videoElement2" class="video-ele2"></video>
</div> </div>
<div class="voice"> <div class="voice">
...@@ -417,12 +438,13 @@ async function xfTTS(text: string) { ...@@ -417,12 +438,13 @@ async function xfTTS(text: string) {
color="#fff" color="#fff"
variant="elevated" variant="elevated"
size="x-large" size="x-large"
:disabled="microphoneState === 'loading' || microphoneState === 'disabled'" :disabled="microphoneState === 'loading' || microphoneState === 'disabled' || microphoneState === 'reply'"
@pointerdown="startVoskWsAudioInput" @pointerdown="startVoskWsAudioInput"
> >
<v-icon v-if="microphoneState === 'waitInput'" icon="mdi-microphone"></v-icon> <v-icon v-if="microphoneState === 'waitInput'" icon="mdi-microphone"></v-icon>
<v-icon v-if="microphoneState === 'loading'" icon="mdi-microphone-settings"></v-icon> <v-icon v-if="microphoneState === 'loading'" icon="mdi-microphone-settings"></v-icon>
<v-icon v-if="microphoneState === 'disabled'" icon="mdi-microphone-off"></v-icon> <v-icon v-if="microphoneState === 'disabled'" icon="mdi-microphone-off"></v-icon>
<v-icon v-if="microphoneState === 'reply'" icon="mdi-message-reply-text-outline"></v-icon>
<template v-if="microphoneState === 'input'"> <template v-if="microphoneState === 'input'">
<img width="30" height="30" :src="iconMicrophone" alt="" srcset="" /> <img width="30" height="30" :src="iconMicrophone" alt="" srcset="" />
...@@ -483,8 +505,14 @@ async function xfTTS(text: string) { ...@@ -483,8 +505,14 @@ async function xfTTS(text: string) {
border-radius: 36%; border-radius: 36%;
} }
.video-ele { .video-ele, .video-ele2 {
position: absolute; position: absolute;
width: 100%;
height: 100%;
opacity: 0;
}
.video-ele.active, .video-ele2.active {
opacity: 1;
} }
.q-list { .q-list {
......
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
type IVideo = { type IVideo = {
list: { url: string; name: string; qa: { url: string; q: string; a: string }[] }[] list: { url: string; poster: string; name: string; qa: { url: string; q: string; a: string }[] }[]
} }
const useVideoStore = defineStore('video', { const useVideoStore = defineStore('video', {
...@@ -11,6 +11,7 @@ const useVideoStore = defineStore('video', { ...@@ -11,6 +11,7 @@ const useVideoStore = defineStore('video', {
list: [ list: [
{ {
url: new URL('/libai/wait.mp4', import.meta.url).href, url: new URL('/libai/wait.mp4', import.meta.url).href,
poster: new URL('/libai/poster.jpg', import.meta.url).href,
name: '李白', name: '李白',
qa: [ qa: [
{ {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment