|
@@ -71,6 +71,10 @@ const scriptProcessor = ref(null) // 脚本处理器
|
|
|
const audioDataBuffer = ref([]) // 音频数据缓冲区
|
|
const audioDataBuffer = ref([]) // 音频数据缓冲区
|
|
|
const MAX_BUFFER_SIZE = 1024 * 10 // 10KB
|
|
const MAX_BUFFER_SIZE = 1024 * 10 // 10KB
|
|
|
let batchSendTimer = null // 批量发送定时器
|
|
let batchSendTimer = null // 批量发送定时器
|
|
|
|
|
+// 声音检测相关变量
|
|
|
|
|
+let silenceTimer = null // 静音检测定时器
|
|
|
|
|
+const SILENCE_THRESHOLD = 0.01 // 静音阈值
|
|
|
|
|
+const SILENCE_DURATION = 1000 // 静音持续时间(毫秒)
|
|
|
|
|
|
|
|
// 提取资源释放函数
|
|
// 提取资源释放函数
|
|
|
const releaseResources = () => {
|
|
const releaseResources = () => {
|
|
@@ -104,6 +108,11 @@ const resetRecordingState = () => {
|
|
|
countdown.value = 0
|
|
countdown.value = 0
|
|
|
emit('recordingStatusChanged', false)
|
|
emit('recordingStatusChanged', false)
|
|
|
releaseResources()
|
|
releaseResources()
|
|
|
|
|
+ // 清除静音检测定时器
|
|
|
|
|
+ if (silenceTimer) {
|
|
|
|
|
+ clearTimeout(silenceTimer)
|
|
|
|
|
+ silenceTimer = null
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 增强错误处理
|
|
// 增强错误处理
|
|
@@ -285,12 +294,60 @@ const sendAudioData = async (arrayBuffer) => {
|
|
|
audioDataBuffer.value.push(data[i])
|
|
audioDataBuffer.value.push(data[i])
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ // 检测声音
|
|
|
|
|
+ const hasSound = detectSound(data)
|
|
|
|
|
+
|
|
|
|
|
+ // 处理声音检测结果
|
|
|
|
|
+ if (hasSound) {
|
|
|
|
|
+ // 检测到声音,重置静音定时器
|
|
|
|
|
+ resetSilenceTimer()
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 检测到静音,启动静音定时器
|
|
|
|
|
+ startSilenceTimer()
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
// 如果缓冲区达到阈值,立即发送
|
|
// 如果缓冲区达到阈值,立即发送
|
|
|
if (audioDataBuffer.value.length >= MAX_BUFFER_SIZE) {
|
|
if (audioDataBuffer.value.length >= MAX_BUFFER_SIZE) {
|
|
|
await sendAudioDataBatch()
|
|
await sendAudioDataBatch()
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+// 检测声音
|
|
|
|
|
+const detectSound = (data) => {
|
|
|
|
|
+ // 计算音频数据的平均幅度
|
|
|
|
|
+ let sum = 0
|
|
|
|
|
+ for (let i = 0; i < data.length; i++) {
|
|
|
|
|
+ sum += Math.abs(data[i])
|
|
|
|
|
+ }
|
|
|
|
|
+ const average = sum / data.length
|
|
|
|
|
+
|
|
|
|
|
+ // 与阈值比较,判断是否有声音
|
|
|
|
|
+ return average > SILENCE_THRESHOLD * 32768
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 重置静音定时器
|
|
|
|
|
+const resetSilenceTimer = () => {
|
|
|
|
|
+ if (silenceTimer) {
|
|
|
|
|
+ clearTimeout(silenceTimer)
|
|
|
|
|
+ silenceTimer = null
|
|
|
|
|
+ }
|
|
|
|
|
+ // 重新启动静音定时器
|
|
|
|
|
+ startSilenceTimer()
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 启动静音定时器
|
|
|
|
|
+const startSilenceTimer = () => {
|
|
|
|
|
+ if (!silenceTimer && isRecording.value) {
|
|
|
|
|
+ silenceTimer = setTimeout(() => {
|
|
|
|
|
+ // 静音时间超过阈值,自动停止录音
|
|
|
|
|
+ if (isRecording.value) {
|
|
|
|
|
+ resetRecordingState()
|
|
|
|
|
+ stopBackendRecognition()
|
|
|
|
|
+ }
|
|
|
|
|
+ }, SILENCE_DURATION)
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
// 动态调整轮询频率
|
|
// 动态调整轮询频率
|
|
|
const adjustPollingInterval = (baseInterval = 300) => {
|
|
const adjustPollingInterval = (baseInterval = 300) => {
|
|
|
if (navigator.connection) {
|
|
if (navigator.connection) {
|
|
@@ -447,6 +504,11 @@ const toggleSpeechInput = async () => {
|
|
|
clearInterval(countdownTimer.value)
|
|
clearInterval(countdownTimer.value)
|
|
|
countdownTimer.value = null
|
|
countdownTimer.value = null
|
|
|
stopResultPolling()
|
|
stopResultPolling()
|
|
|
|
|
+ // 清除静音检测定时器
|
|
|
|
|
+ if (silenceTimer) {
|
|
|
|
|
+ clearTimeout(silenceTimer)
|
|
|
|
|
+ silenceTimer = null
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
if (isRecording.value) {
|
|
if (isRecording.value) {
|
|
|
// 手动停止时立即重置状态,确保在所有浏览器中波纹都能立即关闭
|
|
// 手动停止时立即重置状态,确保在所有浏览器中波纹都能立即关闭
|
|
@@ -598,9 +660,21 @@ onUnmounted(() => {
|
|
|
stopResultPolling()
|
|
stopResultPolling()
|
|
|
releaseResources()
|
|
releaseResources()
|
|
|
|
|
|
|
|
|
|
+ // 清除静音检测定时器
|
|
|
|
|
+ if (silenceTimer) {
|
|
|
|
|
+ clearTimeout(silenceTimer)
|
|
|
|
|
+ silenceTimer = null
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
// 通知后端停止识别
|
|
// 通知后端停止识别
|
|
|
stopBackendRecognition()
|
|
stopBackendRecognition()
|
|
|
})
|
|
})
|
|
|
|
|
+
|
|
|
|
|
+// 暴露方法给父组件
|
|
|
|
|
+defineExpose({
|
|
|
|
|
+ toggleSpeechInput,
|
|
|
|
|
+ isRecording
|
|
|
|
|
+})
|
|
|
</script>
|
|
</script>
|
|
|
|
|
|
|
|
<style scoped lang="scss">
|
|
<style scoped lang="scss">
|