You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
295 lines
8.0 KiB
295 lines
8.0 KiB
<template>
|
|
<div class="voice-record">
|
|
<div class="record-btn" :ref="refNode">
|
|
<i></i>
|
|
<span>{{ text }}</span>
|
|
<div class="mask" :ref="refNode" @touchstart.prevent.stop="handlerTouchstart" @touchmove="handlerTouchmove"
|
|
@touchend="handlerTouchend">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, watch, getCurrentInstance, onBeforeUnmount } from 'vue'
|
|
import { showToast, showLoadingToast, closeToast } from 'vant'
|
|
import { openWebSocket } from '@/utils/websocket.js'
|
|
import { recorderInit, recorderStart, recorderStop, recorderClose, mergeWav } from '@/utils/record.js'
|
|
import '@/assets/images/fill-microphone.png'
|
|
|
|
const instance = getCurrentInstance()
|
|
const store = instance?.appContext?.config?.globalProperties?.$store
|
|
|
|
const props = defineProps({
|
|
content: { type: String, default: '' },
|
|
refNode: { type: String, default: '' },
|
|
isEdit: { type: Boolean, default: true },
|
|
asrData: { type: Object, default: () => ({}) }
|
|
})
|
|
|
|
const emit = defineEmits(['handleUpdateContent', 'isClose', 'isRecorderStart', 'clickFinish'])
|
|
|
|
const talkList = ref([])
|
|
const isRecording = ref(false)
|
|
const connectTimes = ref(0)
|
|
const connectStatus = ref(null)
|
|
const trainWebsocket = ref(null)
|
|
const activeCloseFlag = ref(false)
|
|
const identifyingTimer = ref(null)
|
|
const lastContent = ref('')
|
|
const voiceContent = ref('')
|
|
const timeOutEvent = ref('')
|
|
const mouse = ref({ x: 0, y: 0 })
|
|
const text = ref('长按开始录音')
|
|
const isShowClose = ref(true)
|
|
const recorderBlob = ref(null)
|
|
const asrMsg = ref('')
|
|
const longTimeNoSay = ref(false)
|
|
const touchTimeStart = ref(0)
|
|
const touchTimeEnd = ref(0)
|
|
const recordRight = ref(true)
|
|
const recorderEnded = ref(true)
|
|
const oneMinLoading = ref(null)
|
|
const oneMinLimit = ref(null)
|
|
const sendMsgTimeout = ref(null)
|
|
const asrLoading = ref(null)
|
|
const asrLoadingState = ref(false)
|
|
const asrTransLoading = ref(null)
|
|
const sceneType = ref('123')
|
|
const practiceId = ref('')
|
|
const reconnectFlag = ref(false)
|
|
|
|
const userId = ref(localStorage.getItem('userId') || '')
|
|
const manualService = ref('1')
|
|
|
|
watch(talkList, (newVal) => {
|
|
if (newVal) {
|
|
const length = newVal.length
|
|
if (newVal[length - 1] !== undefined) {
|
|
asrMsg.value += newVal[length - 1]
|
|
}
|
|
}
|
|
}, { deep: true })
|
|
|
|
watch(connectStatus, (newVal) => {
|
|
if (newVal === 'close') {
|
|
emit('isClose', false)
|
|
}
|
|
})
|
|
|
|
function handleClick() {
|
|
if (!props.isEdit) {
|
|
showToast('不可编辑,禁止语音识别!')
|
|
return
|
|
}
|
|
const tts = store?.getters?.tts
|
|
if (tts && tts._uid === instance?.uid) {
|
|
recorderClose.call(instance?.proxy || {})
|
|
tts.isShowClose = true
|
|
trainWebsocket.value?.onclose?.()
|
|
store?.commit?.('set_tts', '')
|
|
return
|
|
}
|
|
if (tts) {
|
|
if (tts._uid !== instance?.uid) {
|
|
tts.isShowClose = false
|
|
}
|
|
recorderClose.call(instance?.proxy || {})
|
|
tts.trainWebsocket.onclose()
|
|
}
|
|
lastContent.value = props.content
|
|
openWebSocket.call(instance?.proxy || {}, props.asrData, connectTimes.value, sceneType.value)
|
|
asrLoading.value = showLoadingToast({ message: '语音连接中...', duration: 0, loadingType: 'spinner', forbidClick: true })
|
|
asrLoadingState.value = true
|
|
setTimeout(() => {
|
|
if (trainWebsocket.value?.readyState !== 1 && asrLoadingState.value) {
|
|
closeToast()
|
|
asrLoadingState.value = false
|
|
recorderClose.call(instance?.proxy || {})
|
|
isShowClose.value = true
|
|
trainWebsocket.value?.close?.()
|
|
store?.commit?.('set_tts', '')
|
|
showToast('语音连接失败,请重试')
|
|
}
|
|
}, 10000)
|
|
const time = setTimeout(() => {
|
|
recorderInit.call(instance?.proxy || {})
|
|
const isRecorderStart = recorderStart.call(instance?.proxy || {})
|
|
store?.commit?.('set_tts', instance?.proxy || {})
|
|
emit('isRecorderStart', isRecorderStart)
|
|
clearTimeout(time)
|
|
}, 500)
|
|
}
|
|
|
|
function handlerTouchstart(event) {
|
|
asrMsg.value = ''
|
|
recorderEnded.value = false
|
|
longTimeNoSay.value = false
|
|
recordRight.value = true
|
|
touchTimeStart.value = new Date().getTime()
|
|
talkList.value = []
|
|
|
|
if (!props.isEdit) {
|
|
showToast('不可编辑,禁止语音转文字!')
|
|
return
|
|
}
|
|
const clientX = event.targetTouches[0].clientX
|
|
const clientY = event.targetTouches[0].clientY
|
|
mouse.value.x = clientX
|
|
mouse.value.y = clientY
|
|
activeCloseFlag.value = false
|
|
|
|
if (!trainWebsocket.value) {
|
|
openWebSocket.call(instance?.proxy || {}, props.asrData, connectTimes.value, sceneType.value)
|
|
} else {
|
|
trainWebsocket.value.onclose()
|
|
}
|
|
|
|
asrLoading.value = showLoadingToast({ message: '语音连接中...', duration: 0, loadingType: 'spinner', forbidClick: true })
|
|
asrLoadingState.value = true
|
|
setTimeout(() => {
|
|
if (trainWebsocket.value?.readyState !== 1 && asrLoadingState.value) {
|
|
closeToast()
|
|
asrLoadingState.value = false
|
|
recorderClose.call(instance?.proxy || {})
|
|
isShowClose.value = true
|
|
trainWebsocket.value?.close?.()
|
|
store?.commit?.('set_tts', '')
|
|
showToast('语音连接失败,请重试')
|
|
}
|
|
}, 10000)
|
|
|
|
const time = setTimeout(() => {
|
|
recorderInit.call(instance?.proxy || {})
|
|
recorderStart.call(instance?.proxy || {})
|
|
store?.commit?.('set_tts', instance?.proxy || {})
|
|
clearTimeout(time)
|
|
}, 500)
|
|
}
|
|
|
|
function handlerTouchmove(event) {
|
|
if (!props.isEdit) return
|
|
const clientX = event.targetTouches[0].clientX
|
|
const clientY = event.targetTouches[0].clientY
|
|
let x = Math.abs(mouse.value.x - clientX)
|
|
let y = Math.abs(mouse.value.y - clientY)
|
|
if (x > 10 || y > 10) {
|
|
cancelTimeoutEvent()
|
|
}
|
|
}
|
|
|
|
function handlerTouchend() {
|
|
text.value = '长按开始录音'
|
|
if (recorderEnded.value) return
|
|
touchTimeEnd.value = new Date().getTime()
|
|
recorderEnded.value = true
|
|
mergeWav.call(instance?.proxy || {})
|
|
if (touchTimeEnd.value - touchTimeStart.value < 500) {
|
|
trainWebsocket.value?.close?.()
|
|
recorderClose.call(instance?.proxy || {})
|
|
showToast('录音时间过短,请长按')
|
|
recordRight.value = false
|
|
return
|
|
}
|
|
if (!props.isEdit) return
|
|
clearTimeout(oneMinLimit.value)
|
|
oneMinLoading.value?.close?.()
|
|
cancelTimeoutEvent()
|
|
if (!longTimeNoSay.value) {
|
|
asrTransLoading.value = showLoadingToast({ message: '语音识别中...', duration: 0, loadingType: 'spinner', forbidClick: true })
|
|
}
|
|
const sec = longTimeNoSay.value ? 10 : 8000
|
|
sendMsgTimeout.value = setTimeout(() => {
|
|
closeAndSendMsg()
|
|
}, sec)
|
|
emit('clickFinish', 2)
|
|
}
|
|
|
|
function cancelTimeoutEvent() {
|
|
if (timeOutEvent.value) {
|
|
clearTimeout(timeOutEvent.value)
|
|
timeOutEvent.value = null
|
|
}
|
|
}
|
|
|
|
function stop() {
|
|
recorderClose.call(instance?.proxy || {})
|
|
}
|
|
|
|
function closeAndSendMsg() {
|
|
trainWebsocket.value?.close?.()
|
|
recorderStop.call(instance?.proxy || {})
|
|
stop()
|
|
asrTransLoading.value?.close?.()
|
|
if (!asrMsg.value) return
|
|
|
|
let voiceNo = new Date().getTime()
|
|
let voiceContent = { voiceData: recorderBlob.value, id: voiceNo }
|
|
store?.commit?.('set_VoiceData', voiceContent)
|
|
|
|
let sendParmas = {
|
|
reqAndResType: '5',
|
|
msgText: asrMsg.value,
|
|
voiceNo: voiceNo,
|
|
msgSource: manualService.value,
|
|
voiceData: recorderBlob.value,
|
|
}
|
|
store?.dispatch?.('addUpLoadVoice', sendParmas).then(() => {
|
|
store?.commit?.('set_isHumanChat', true)
|
|
}).catch(err => console.log(err))
|
|
}
|
|
|
|
onBeforeUnmount(() => {
|
|
if (trainWebsocket.value) {
|
|
recorderClose.call(instance?.proxy || {})
|
|
isShowClose.value = true
|
|
trainWebsocket.value.onclose?.()
|
|
store?.commit?.('set_tts', '')
|
|
}
|
|
})
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
.voice-record {
|
|
.record {
|
|
border-radius: 8px;
|
|
padding: 12px;
|
|
background: #f7f8fa;
|
|
}
|
|
|
|
:deep(.van-field__word-limit) {
|
|
line-height: 22px;
|
|
font-size: 12px;
|
|
}
|
|
|
|
.record-btn {
|
|
position: relative;
|
|
display: flex;
|
|
align-items: center;
|
|
font-size: 12px;
|
|
color: #7e849f;
|
|
|
|
i {
|
|
width: 22px;
|
|
height: 22px;
|
|
border-radius: 50%;
|
|
background-size: 100%;
|
|
margin-right: 8px;
|
|
}
|
|
|
|
span {
|
|
font-size: 18px;
|
|
}
|
|
|
|
.mask {
|
|
width: 174px;
|
|
height: 44px;
|
|
position: absolute;
|
|
left: 0;
|
|
top: 0;
|
|
z-index: 1;
|
|
background-color: transparent;
|
|
}
|
|
}
|
|
}
|
|
</style>
|
|
|