refactor: 录音换成微信同声传译插件
替换整个录音+ASR流程: - 移除 uni.getRecorderManager() + 后端上传 ASR 方案 - 接入 WechatSI 插件 getRecordRecognitionManager() - start() 一次性完成录音+实时流式识别 - onRecognize 实时回填 inputText (用户可见) - onStop 自动调用 sendAnswer(),无需手动点击发送 - 移除 asrLoading/skipAsr/recorderStarted/pendingStop 状态 效果: 8步→3步, 延时2-10s→~500ms, 不再有录音格式兼容问题 注意: 需在 mp.weixin.qq.com 后台添加同声传译插件(插件ID: wx069ba97219f66d99)
This commit is contained in:
@@ -17,6 +17,12 @@
|
|||||||
"__usePrivacyCheck__": true
|
"__usePrivacyCheck__": true
|
||||||
},
|
},
|
||||||
"usingComponents": true,
|
"usingComponents": true,
|
||||||
|
"plugins": {
|
||||||
|
"WechatSI": {
|
||||||
|
"version": "0.3.6",
|
||||||
|
"provider": "wx069ba97219f66d99"
|
||||||
|
}
|
||||||
|
},
|
||||||
"permission": {
|
"permission": {
|
||||||
"scope.record": {
|
"scope.record": {
|
||||||
"desc": "用于语音输入回答面试问题"
|
"desc": "用于语音输入回答面试问题"
|
||||||
|
|||||||
@@ -65,7 +65,7 @@
|
|||||||
<text class="mic-label">{{ isRecording ? recordingDuration + 's' : '按住' }}</text>
|
<text class="mic-label">{{ isRecording ? recordingDuration + 's' : '按住' }}</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="input-box">
|
<view class="input-box">
|
||||||
<textarea class="input-area" v-model="inputText" :placeholder="asrLoading ? '语音识别中...' : '输入你的回答...'" :auto-height="true" :maxlength="2000" :disabled="aiLoading || isRecording || asrLoading" @confirm="sendAnswer" />
|
<textarea class="input-area" v-model="inputText" placeholder="按住🎤说话或输入回答..." :auto-height="true" :maxlength="2000" :disabled="aiLoading || isRecording" @confirm="sendAnswer" />
|
||||||
</view>
|
</view>
|
||||||
<view class="send-btn" :class="{ disabled: (!inputText.trim() && !isRecording) || aiLoading }" @click="sendAnswer">
|
<view class="send-btn" :class="{ disabled: (!inputText.trim() && !isRecording) || aiLoading }" @click="sendAnswer">
|
||||||
<text class="send-icon">{{ isRecording ? '◉' : '➤' }}</text>
|
<text class="send-icon">{{ isRecording ? '◉' : '➤' }}</text>
|
||||||
@@ -153,76 +153,49 @@ const isSpeaking = ref(false)
|
|||||||
const dhRef = ref(null)
|
const dhRef = ref(null)
|
||||||
const isRecording = ref(false)
|
const isRecording = ref(false)
|
||||||
const recordingDuration = ref(0)
|
const recordingDuration = ref(0)
|
||||||
const asrLoading = ref(false)
|
let manager = null
|
||||||
let recorder = null
|
|
||||||
let recordTimer = null
|
let recordTimer = null
|
||||||
let skipAsr = false
|
let suppressAutoSend = false
|
||||||
let recorderStarted = false
|
|
||||||
let pendingStop = false
|
|
||||||
|
|
||||||
function initRecorder() {
|
function initRecorder() {
|
||||||
// #ifdef MP-WEIXIN
|
// #ifdef MP-WEIXIN
|
||||||
if (recorder) return
|
if (manager) return
|
||||||
recorder = uni.getRecorderManager()
|
try {
|
||||||
recorder.onStart(() => {
|
const plugin = requirePlugin('WechatSI')
|
||||||
console.log('[Recorder] start recording')
|
manager = plugin.getRecordRecognitionManager()
|
||||||
recorderStarted = true
|
manager.onStart = () => {
|
||||||
recordingDuration.value = 0
|
console.log('[WechatSI] start')
|
||||||
recordTimer = setInterval(() => { recordingDuration.value++ }, 1000)
|
isRecording.value = true
|
||||||
if (pendingStop) {
|
recordingDuration.value = 0
|
||||||
pendingStop = false
|
recordTimer = setInterval(() => { recordingDuration.value++ }, 1000)
|
||||||
recorder.stop()
|
|
||||||
}
|
}
|
||||||
})
|
manager.onRecognize = (res) => {
|
||||||
recorder.onError((err) => {
|
if (res?.result?.trim()) inputText.value = res.result.trim()
|
||||||
console.error('[Recorder] error:', JSON.stringify(err))
|
|
||||||
isRecording.value = false
|
|
||||||
recorderStarted = false
|
|
||||||
pendingStop = false
|
|
||||||
skipAsr = false
|
|
||||||
recordingDuration.value = 0
|
|
||||||
asrLoading.value = false
|
|
||||||
if (recordTimer) { clearInterval(recordTimer); recordTimer = null }
|
|
||||||
const msg = err?.errMsg?.includes('permission') ? '请允许录音权限' : (err?.errMsg || '录音失败,请重试')
|
|
||||||
uni.showToast({ title: msg, icon: 'none' })
|
|
||||||
recorder = null
|
|
||||||
})
|
|
||||||
recorder.onStop(async (res) => {
|
|
||||||
isRecording.value = false
|
|
||||||
recorderStarted = false
|
|
||||||
recordingDuration.value = 0
|
|
||||||
if (recordTimer) { clearInterval(recordTimer); recordTimer = null }
|
|
||||||
if (skipAsr) { skipAsr = false; return }
|
|
||||||
if (!res.tempFilePath) {
|
|
||||||
uni.showToast({ title: '录音文件为空', icon: 'none' })
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
asrLoading.value = true
|
manager.onStop = (res) => {
|
||||||
try {
|
isRecording.value = false
|
||||||
const uploadRes = await uni.uploadFile({
|
recordingDuration.value = 0
|
||||||
url: api(API_ENDPOINTS.TTS.ASR),
|
if (recordTimer) { clearInterval(recordTimer); recordTimer = null }
|
||||||
filePath: res.tempFilePath,
|
if (suppressAutoSend) { suppressAutoSend = false; return }
|
||||||
name: 'audio',
|
const text = res?.result?.trim()
|
||||||
header: { 'Authorization': `Bearer ${token()}` },
|
if (text && text.length >= 2) {
|
||||||
timeout: 30000,
|
inputText.value = text
|
||||||
})
|
sendAnswer()
|
||||||
if (uploadRes.statusCode === 200 && uploadRes.data) {
|
|
||||||
const data = typeof uploadRes.data === 'string' ? JSON.parse(uploadRes.data) : uploadRes.data
|
|
||||||
if (data.text) {
|
|
||||||
inputText.value = data.text
|
|
||||||
uni.vibrateShort({ type: 'light' })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else if (checkAuth(uploadRes)) {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
|
||||||
console.error('[ASR] upload error:', e?.message || e)
|
|
||||||
} finally {
|
|
||||||
asrLoading.value = false
|
|
||||||
}
|
}
|
||||||
uni.showToast({ title: '语音识别失败,请手动输入', icon: 'none' })
|
manager.onError = (res) => {
|
||||||
})
|
console.error('[WechatSI] error:', JSON.stringify(res))
|
||||||
|
isRecording.value = false
|
||||||
|
recordingDuration.value = 0
|
||||||
|
if (recordTimer) { clearInterval(recordTimer); recordTimer = null }
|
||||||
|
const msg = res?.errMsg?.includes('permission') ? '请允许录音权限' : '语音识别失败,请重试'
|
||||||
|
uni.showToast({ title: msg, icon: 'none' })
|
||||||
|
manager = null
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('WechatSI plugin init failed:', e)
|
||||||
|
manager = null
|
||||||
|
}
|
||||||
// #endif
|
// #endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -438,22 +411,15 @@ const confirmExit = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function startRecord() {
|
function startRecord() {
|
||||||
if (aiLoading.value || isComplete.value || asrLoading.value) return
|
if (aiLoading.value || isComplete.value) return
|
||||||
// #ifdef MP-WEIXIN
|
// #ifdef MP-WEIXIN
|
||||||
if (isRecording.value) return
|
if (isRecording.value) return
|
||||||
isRecording.value = true
|
if (!manager) initRecorder()
|
||||||
if (!recorder) initRecorder()
|
if (!manager) {
|
||||||
if (!recorder) {
|
uni.showToast({ title: '语音功能初始化失败', icon: 'none' })
|
||||||
isRecording.value = false
|
|
||||||
uni.showToast({ title: '录音初始化失败', icon: 'none' })
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
recorder.start({
|
manager.start({ lang: 'zh_CN', duration: 60000 })
|
||||||
format: 'aac',
|
|
||||||
sampleRate: 22050,
|
|
||||||
numberOfChannels: 1,
|
|
||||||
encodeBitRate: 16000,
|
|
||||||
})
|
|
||||||
uni.vibrateShort({ type: 'medium' })
|
uni.vibrateShort({ type: 'medium' })
|
||||||
// #endif
|
// #endif
|
||||||
// #ifndef MP-WEIXIN
|
// #ifndef MP-WEIXIN
|
||||||
@@ -463,18 +429,13 @@ function startRecord() {
|
|||||||
|
|
||||||
function stopRecord() {
|
function stopRecord() {
|
||||||
if (!isRecording.value) return
|
if (!isRecording.value) return
|
||||||
isRecording.value = false
|
if (!manager) { isRecording.value = false; return }
|
||||||
if (!recorder) return
|
|
||||||
if (recordingDuration.value < 1) {
|
if (recordingDuration.value < 1) {
|
||||||
skipAsr = true
|
suppressAutoSend = true
|
||||||
if (recordTimer) { clearInterval(recordTimer); recordTimer = null }
|
if (recordTimer) { clearInterval(recordTimer); recordTimer = null }
|
||||||
recordingDuration.value = 0
|
recordingDuration.value = 0
|
||||||
}
|
}
|
||||||
if (!recorderStarted) {
|
manager.stop()
|
||||||
pendingStop = true
|
|
||||||
return
|
|
||||||
}
|
|
||||||
recorder.stop()
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user