语音射别修复
This commit is contained in:
@@ -65,6 +65,26 @@ const centerIndex = ref(0);
|
||||
// 动画帧ID
|
||||
let animationId = null;
|
||||
|
||||
// 为小程序环境提供requestAnimationFrame兼容
|
||||
const requestAnimationFramePolyfill = (callback) => {
|
||||
// #ifdef MP-WEIXIN
|
||||
return setTimeout(callback, 16); // 约60fps
|
||||
// #endif
|
||||
// #ifdef H5
|
||||
return requestAnimationFrame(callback);
|
||||
// #endif
|
||||
};
|
||||
|
||||
// 为小程序环境提供cancelAnimationFrame兼容
|
||||
const cancelAnimationFramePolyfill = (id) => {
|
||||
// #ifdef MP-WEIXIN
|
||||
clearTimeout(id);
|
||||
// #endif
|
||||
// #ifdef H5
|
||||
cancelAnimationFrame(id);
|
||||
// #endif
|
||||
};
|
||||
|
||||
// 格式化显示时间
|
||||
const formattedTime = computed(() => {
|
||||
const mins = Math.floor(props.recordingTime / 60)
|
||||
@@ -125,7 +145,7 @@ const updateWaveform = () => {
|
||||
}
|
||||
}
|
||||
|
||||
animationId = requestAnimationFrame(updateWaveform);
|
||||
animationId = requestAnimationFramePolyfill(updateWaveform);
|
||||
};
|
||||
|
||||
// 更新单个波形条
|
||||
@@ -157,14 +177,14 @@ const updateWaveBar = (index, value) => {
|
||||
// 开始动画
|
||||
const startAnimation = () => {
|
||||
if (!animationId) {
|
||||
animationId = requestAnimationFrame(updateWaveform);
|
||||
animationId = requestAnimationFramePolyfill(updateWaveform);
|
||||
}
|
||||
};
|
||||
|
||||
// 停止动画
|
||||
const stopAnimation = () => {
|
||||
if (animationId) {
|
||||
cancelAnimationFrame(animationId);
|
||||
cancelAnimationFramePolyfill(animationId);
|
||||
animationId = null;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -133,6 +133,20 @@
|
||||
<view class="chat-item self" v-if="isRecording">
|
||||
<view class="message">{{ recognizedText }} {{ lastFinalText }}</view>
|
||||
</view>
|
||||
<!-- 语音正在识别提示 -->
|
||||
<!-- <view>{{isRecognizing}}</view> -->
|
||||
<view class="chat-item self" v-if="isRecognizing">
|
||||
<view class="message msg-loading">
|
||||
<view class="loading-content">
|
||||
<view class="ai-loading">
|
||||
<view></view>
|
||||
<view></view>
|
||||
<view></view>
|
||||
</view>
|
||||
<text class="loading-text">正在识别语音...</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="isTyping" class="self">
|
||||
<view class="message msg-loading">
|
||||
<view class="loading-content">
|
||||
@@ -175,9 +189,6 @@
|
||||
@touchmove="handleTouchMove"
|
||||
@touchend="handleTouchEnd"
|
||||
@touchcancel="handleTouchCancel"
|
||||
:catchtouchstart="true"
|
||||
:catchtouchmove="true"
|
||||
:catchtouchend="true"
|
||||
v-show="isVoice"
|
||||
type="default"
|
||||
>
|
||||
@@ -294,11 +305,11 @@ import FileIcon from './fileIcon.vue';
|
||||
import FileText from './fileText.vue';
|
||||
import { useAudioRecorder } from '@/hook/useRealtimeRecorder.js';
|
||||
import { useTTSPlayer } from '@/hook/useTTSPlayer.js';
|
||||
import successIcon from '@/static/icon/success.png';
|
||||
// 全局
|
||||
const { $api, navTo, throttle, config } = inject('globalFunction');
|
||||
const emit = defineEmits(['onConfirm']);
|
||||
const { messages, isTyping, textInput, chatSessionID } = storeToRefs(useChatGroupDBStore());
|
||||
import successIcon from '@/static/icon/success.png';
|
||||
// hook
|
||||
const {
|
||||
isRecording,
|
||||
@@ -309,8 +320,32 @@ const {
|
||||
volumeLevel,
|
||||
recognizedText,
|
||||
lastFinalText,
|
||||
recordingDuration,
|
||||
isRecognizing,
|
||||
reset
|
||||
} = useAudioRecorder();
|
||||
|
||||
// 监听语音识别结果变化,自动发送消息
|
||||
watch(
|
||||
() => recognizedText.value,
|
||||
(newVal) => {
|
||||
if (newVal && newVal.trim()) {
|
||||
console.log('监听到语音识别结果变化,自动发送消息:', newVal);
|
||||
sendMessage(newVal);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// 监听isRecognizing状态,显示提示
|
||||
watch(
|
||||
() => isRecognizing.value,
|
||||
(newVal) => {
|
||||
if (newVal) {
|
||||
$api.msg('正在识别语音...');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const { speak, pause, resume, isSpeaking, isPaused, cancelAudio } = useTTSPlayer(config.speechSynthesis);
|
||||
|
||||
// 获取组件实例(用于小程序 SelectorQuery)
|
||||
@@ -362,6 +397,7 @@ onMounted(async () => {
|
||||
changeQueries();
|
||||
scrollToBottom();
|
||||
isAudioPermission.value = await requestMicPermission();
|
||||
reset(); // 重置语音识别状态
|
||||
});
|
||||
|
||||
const requestMicPermission = async () => {
|
||||
@@ -684,17 +720,22 @@ const handleTouchEnd = () => {
|
||||
if (status.value === 'cancel') {
|
||||
console.log('取消发送');
|
||||
cancelRecording();
|
||||
status.value = 'idle';
|
||||
} else {
|
||||
stopRecording();
|
||||
if (isAudioPermission.value) {
|
||||
if (recognizedText.value) {
|
||||
sendMessage(recognizedText.value);
|
||||
} else {
|
||||
// 主要根据录音时长判断,而不是完全依赖识别结果
|
||||
// 由于setInterval是异步的,这里需要考虑计时延迟
|
||||
const actualDuration = recordingDuration.value > 0 ? recordingDuration.value : (isRecording.value ? 0.5 : 0);
|
||||
if (actualDuration < 1) {
|
||||
$api.msg('说话时长太短');
|
||||
status.value = 'idle';
|
||||
} else {
|
||||
// 状态管理由useAudioRecorder hook内部处理
|
||||
status.value = 'idle';
|
||||
}
|
||||
}
|
||||
}
|
||||
status.value = 'idle';
|
||||
};
|
||||
|
||||
const handleTouchCancel = () => {
|
||||
@@ -1118,6 +1159,11 @@ image-margin-top = 40rpx
|
||||
-moz-user-select:none;
|
||||
-ms-user-select:none;
|
||||
touch-action: none; /* 禁用默认滚动 */
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 160rpx; /* 为底部导航栏留出空间 */
|
||||
z-index: 9999; /* 确保高于其他元素 */
|
||||
.record-tip
|
||||
font-weight: 400;
|
||||
color: #909090;
|
||||
|
||||
Reference in New Issue
Block a user