uniapp微信小程序-长按按钮百度语音识别回显文字
流程图:
话不多说,上代码:
<template><view class="content"><view class="speech-chat" @longpress="startSpeech" @touchend="endSpeech"><view class="animate-block" v-if="voiceState"><view class="dot-ripple"></view><view class="dot-ripple"></view></view><image class="speech-icon" src="/static/aichat/icon-speech.png" mode="aspectFit"></image></view></view>
</template><script lang="ts" setup>
const recorderManager = ref(uni.getRecorderManager()) // 获取全局唯一的录音管理器
// 这俩有啥用?
const innerAudioContext = uni.createInnerAudioContext() // 创建并返回内部audio上下文对象
innerAudioContext.autoplay = true // 开启自动播放设置const voicePath = ref<string>('') // 语音文件路径
const result = ref<string>('') // 语音识别结果
const voiceState = ref<boolean>(false) // 语音输入状态
const authState = ref<boolean>(false) // 录音授权状态
const bdData = reactive({pid: '你的pid',cuid: '你的cuid',token: '你的token',voiceUrl: 'https://vop.baidu.com/server_api',
})const emit = defineEmits(['speech-result'])// 初始化录音配置
const initRecorder = () => {recorderManager.value.onStart(() => {// console.log('录音开始')})recorderManager.value.onStop((res) => {readFile(res.tempFilePath) // 文件读取事件voicePath.value = res.tempFilePath})
}
/*** @param {*} voiceFilePath 文件流路径* @desc 文件流或文件路径获取*/
const readFile = (voiceFilePath) => {// 通过全局唯一的<文件管理器>读取文件信息uni.getFileSystemManager().readFile({filePath: voiceFilePath,success: (res) => {// console.log('文件编码成功', res)getRecognizeResult(res.data) // 调用百度语言识别API},fail: (err) => {// console.log('文件编码失败', err)return err},})
}
/*** @param {*} speechResult 文件流路径* @desc 百度语音识别获取*/
const getRecognizeResult = (speechResult) => {const questUrl = `${bdData.voiceUrl}?cuid=${bdData.cuid}&token=${bdData.token}&dev_pid=${bdData.pid}`uni.request({url: questUrl,method: 'POST',header: {'Content-Type': 'audio/pcm;rate=16000', // 注意这个header用于设置音频文件格式和码率},data: speechResult,success: (res) => {console.log('请求成功', res)result.value = res.data.result[0]emit('speech-result', res.data.result[0])},fail: (err) => {console.log('请求失败', err)uni.showToast({title: '语言识别异常,稍后重试',icon: 'error',duration: 2000,})},complete: () => {voiceState.value = false},})
}
const startRecord = () => {// 开始录音并设置格式recorderManager.value.start({format: 'PCM',})
}
/*** @desc 长按触发录音*/
const startSpeech = (e) => {// 震动提示用户--不一定生效uni.vibrateLong({success: function () {// console.log('success')},})// 已经授权 则开始录音if (authState.value) {voiceState.value = truestartRecord()return}// 未授权--主要用于检查用户是否已授权某些敏感权限(如录音、摄像头、位置等)uni.getSetting({success(settingRes) {// 未授权if (!settingRes.authSetting['scope.record']) {// 只保留一种授权方式,避免重复弹框uni.authorize({scope: 'scope.record',success() {authState.value = truevoiceState.value = truestartRecord()},fail() {// 用户拒绝授权后,引导进入设置页面开启授权uni.showModal({title: '提示',content: '需要录音权限才能使用语音识别功能',confirmText: '去设置',success(res) {if (res.confirm) {uni.openSetting({success(res) {if (res.authSetting['scope.record']) {authState.value = truevoiceState.value = truestartRecord()}},})}},})},})} else {authState.value = truevoiceState.value = truestartRecord()}},})
}
/*** @desc 检查录音授权状态*/
const checkRecordAuth = () => {// 获取应用的权限设置uni.getSetting({success(res) {// 如果已经授权录音权限,更新状态if (res.authSetting['scope.record']) {authState.value = true}},})
}
const endSpeech = (e) => {// 只有在录音状态时才停止录音if (voiceState.value) {voiceState.value = false// 结束录音recorderManager.value.stop()}
}onMounted(() => {// 初始化录音管理器initRecorder()// 初始化检查录音权限状态checkRecordAuth()
})
</script><style lang="scss" scoped>
.speech-chat {position: relative;bottom: 0;left: 50%;box-sizing: border-box;width: 140rpx;height: 140rpx;border-radius: 30rpx;transform: translateX(-50%);image {box-sizing: border-box;width: 140rpx;height: 140rpx;border-radius: 50%;}
}
.speech-icon {position: absolute;z-index: 2;
}
/* 波纹动画容器 */
.dot-ripple {position: absolute;top: 40%; /* 定位到父容器垂直中点 *//* 新增居中代码 */left: 50%; /* 定位到父容器水平中点 */z-index: 1;width: 20rpx;height: 20rpx;background-color: #ff4200;border-radius: 50%;box-shadow: 0 0 20rpx rgba(0, 0, 0, 0.3) inset;transform: translate(-50%, -50%); /* 自身尺寸反向位移50% */animation: ripple 1s ease infinite;
}
/* 动画定义 */
@keyframes ripple {0% {width: 0;height: 0;opacity: 0.75;transform: translate(-50%, -50%) scale(0); /* 保持居中缩放 */}100% {width: 280rpx;height: 280rpx;opacity: 0;transform: translate(-50%, -50%) scale(1); /* 保持居中放大 */}
}
/* 小程序兼容 */
/* #ifdef MP-WEIXIN */
.dot-ripple {transform: translateZ(0); /* 触发硬件加速 */
}
/* #endif */
.dot-ripple:nth-child(2) {animation-delay: 0.2s;
}
</style>