JLBA202505130001_关于吉林银行新建AI智能培训系统的需求_开发安卓录音按钮
This commit is contained in:
@@ -103,8 +103,25 @@
|
||||
:canAnswer="testAnswer"
|
||||
:adjustPosition="true"
|
||||
:canAnswerText="canAnswerText"
|
||||
:answerMethod="answerMethod"
|
||||
:answerMethod="answerMethod"
|
||||
:isDisabled="(lastTalkingInfo.type || 0) !== 4 || ['SN', 'MU', 'JD'].includes(lastTalkingInfo.qnsTyp)" />
|
||||
|
||||
|
||||
<touch-btn
|
||||
class="talk-btns"
|
||||
ref="touchBtnRef"
|
||||
@sendLoading="sendLoading"
|
||||
@submitVoice="submitVoice"
|
||||
@submitAnswer="submitAnswerNow"
|
||||
@startRecord="changePlayItemId('')"
|
||||
:isLoading="isUploadingVoice"
|
||||
:adjustPosition="true"
|
||||
:canAnswer="testAnswer"
|
||||
:canAnswerText="canAnswerText"
|
||||
:isDisabled="(lastTalkingInfo.type || 0) !== 4 || ['SN', 'MU', 'JD'].includes(lastTalkingInfo.qnsTyp)"
|
||||
loadingText="只能进行一条回答"
|
||||
:answerMethod="answerMethod"
|
||||
/>
|
||||
</view>
|
||||
<uv-overlay :show="showSeekModel" opacity=".6" @click="showSeekModel = false">
|
||||
<view class="overlay-box">
|
||||
@@ -165,8 +182,8 @@ import common from '@/common/common'
|
||||
import TalkingItem from '@/pages/course/components/talkingItem.vue'
|
||||
import ImagePreview from '@/components/image-preview/image-preview.vue'
|
||||
import PreviewDetail from '@/components/image-preview/preview-detail.vue'
|
||||
import TouchBtn from '@/pages/course/components/touchBtn.vue'
|
||||
import TouchBtnOnlyVoice from '@/pages/course/components/touchBtnOnlyVoice.vue'
|
||||
// import TouchBtn from '@/pages/course/components/touchBtn.vue'
|
||||
// import TouchBtnOnlyVoice from '@/pages/course/components/touchBtnOnlyVoice.vue'
|
||||
import badge from '@/components/points-and-badge/badge';
|
||||
import points from '@/components/points-and-badge/points';
|
||||
import usePointsAndBadge from '@/components/points-and-badge/usePointsAndBadge';
|
||||
@@ -885,6 +902,59 @@ const testAnswer = computed(() => {
|
||||
let _num = _arr.map(t => [1, 2, 6].indexOf(Number(t.type)) >= 0).filter(t => !!t)
|
||||
return _num.length < 5
|
||||
})
|
||||
|
||||
|
||||
|
||||
const sendLoading = () => {
|
||||
if (!testAnswer.value) {
|
||||
common.msg('每个问题最多连续发送五次答案!')
|
||||
return
|
||||
}
|
||||
isUploadingVoice.value = true
|
||||
let _data = {
|
||||
stdyId: unref(stdyId),
|
||||
qnsId: unref(lastTalkingInfo).qnsId, // 问题ID 必输项:true 类型:String
|
||||
pgrphId: unref(lastTalkingInfo).pgrphId, // 段落ID 必输项:true 类型:String
|
||||
qstLogId: unref(qstLogId), // 段落ID 必输项:true 类型:String
|
||||
crsId: crsId.value, // 课程ID 必输项:true 类型:String
|
||||
stdyBatch: stdyBatch.value, // 学习批次 必输项:true 类型:String
|
||||
ansterContent: '', // 回答文本内容 必输项:false 类型:String
|
||||
asrStartTm: unref(lastTalkingInfo).asrStartTm
|
||||
}
|
||||
let _id = new Date().getTime() + 'id'
|
||||
//#ifndef APP-PLUS
|
||||
// h5 假数据
|
||||
talkingList.value.push({
|
||||
crsId: crsId.value,
|
||||
pgrphId: unref(lastTalkingInfo).pgrphId,
|
||||
qnsId: unref(lastTalkingInfo).qnsId,
|
||||
talkingContent: '',
|
||||
talkingVoice: voicePath,
|
||||
type: 1,
|
||||
userInfo: {
|
||||
...choiceTeacherInfo.value
|
||||
},
|
||||
id: _id
|
||||
})
|
||||
setIsUploadingVoice()
|
||||
scrollToBottomNow()
|
||||
// #endif
|
||||
|
||||
}
|
||||
// 新录音组件语音提交
|
||||
const submitVoice = ({ossKey,text}) => {
|
||||
if(!ossKey) {
|
||||
|
||||
} else if (text === '') {
|
||||
|
||||
} else {
|
||||
|
||||
unref(ajaxApi)({
|
||||
processType: 'ANSER-TEXT',
|
||||
requestBody: JSON.stringify(_data)
|
||||
})
|
||||
}
|
||||
}
|
||||
const uploadRecordNow = (voicePath, voiceTime) => {
|
||||
if (!testAnswer.value) {
|
||||
common.msg('每个问题最多连续发送五次答案!')
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
<script setup>
|
||||
import { computed, unref, ref, toRefs, watch, onMounted } from "vue"
|
||||
import { onUnload } from '@dcloudio/uni-app';
|
||||
import { downloadFile } from '@/api/common.js';
|
||||
const props = defineProps({
|
||||
dataInfo: {
|
||||
default: () => ({}),
|
||||
@@ -78,7 +79,7 @@ import { onUnload } from '@dcloudio/uni-app';
|
||||
onUnload(()=>{
|
||||
pauseVoice()
|
||||
})
|
||||
watch(() => props.dataInfo.talkingVoice,(val)=>{
|
||||
watch(() => props.dataInfo.bucketKey,(val)=>{
|
||||
if(val){
|
||||
initVoice()
|
||||
}
|
||||
@@ -93,7 +94,9 @@ import { onUnload } from '@dcloudio/uni-app';
|
||||
emit('changePlay', '')
|
||||
}
|
||||
const playVoice = () =>{
|
||||
|
||||
emit('changePlay', unref(dataInfo)?.id)
|
||||
|
||||
if(talkVoiceObj.value && talkVoiceObj.value.src) {
|
||||
talkVoiceObj.value.play()
|
||||
return
|
||||
@@ -106,6 +109,7 @@ import { onUnload } from '@dcloudio/uni-app';
|
||||
isPlaying.value = true
|
||||
},100)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
const initVoice = () => {
|
||||
@@ -118,11 +122,34 @@ import { onUnload } from '@dcloudio/uni-app';
|
||||
talkVoiceObj.value.src = ''
|
||||
})
|
||||
if(unref(dataInfo).intrctWay === '02'){
|
||||
itemVoice.value = unref(dataInfo).talkingVoice
|
||||
talkVoiceObj.value.src = itemVoice.value
|
||||
setTimeout(()=>{
|
||||
setVoiceTime(0)
|
||||
},100)
|
||||
downloadFile(unref(dataInfo).bucketKey)
|
||||
.then((res) => {
|
||||
|
||||
if (res?.data) {
|
||||
common.msg(res.data.message);
|
||||
return;
|
||||
}
|
||||
voiceLoading.value = false;
|
||||
itemVoice.value = res.tempFilePath;
|
||||
talkVoiceObj.value.src = res.tempFilePath;
|
||||
if (voiceIsPlaying.value) return
|
||||
if(unref(dataInfo)?.id !== props.activeVoiceId) return
|
||||
talkVoiceObj.value.play();
|
||||
storageStore.setStorage('audio', _id, itemVoice.value);
|
||||
setTimeout(()=>{
|
||||
setVoiceTime(0)
|
||||
},100)
|
||||
})
|
||||
.catch((err) => {
|
||||
voiceLoading.value = false;
|
||||
emit('changePlay', '');
|
||||
});
|
||||
|
||||
// itemVoice.value = unref(dataInfo).talkingVoice
|
||||
// talkVoiceObj.value.src = itemVoice.value
|
||||
// setTimeout(()=>{
|
||||
// setVoiceTime(0)
|
||||
// },100)
|
||||
}
|
||||
}
|
||||
const setVoiceTime = (sum) => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<view class="talking-item">
|
||||
<questionVue v-if="talkingType === 'question'" @changePlay="changePlay" :dataInfo="talkingInfo" :isLast="isLast"
|
||||
<questionVue v-if="talkingType === 'question'" @changePlay="changePlay" :dataInfo="props.talkingInfo" :isLast="isLast"
|
||||
:activeVoiceId="activeVoiceId"></questionVue>
|
||||
<answerVue v-else-if="talkingType === 'answer'" :dataInfo="props.talkingInfo" :isLast="isLast"
|
||||
:isLoading="!!props.talkingInfo.isLoading" :isAnswering="props.isAnswering" @handleEvents="handleEvents">
|
||||
|
||||
@@ -145,7 +145,7 @@
|
||||
:adjustPosition="true"
|
||||
:isDisabled="isHistory"
|
||||
/> -->
|
||||
<touch-btn @submitVoice="submitVoice" @sendLoading="sendLoading" @submitAnswer="submitAnswerNow"
|
||||
<touch-btn @submitVoice="submitVoice" @sendLoading="sendLoading" @submitAnswer="submitAnswer"
|
||||
@startRecord="startRecordCallback" :loadingText="sendLoadingText" :isLoading="!answerIsOver"
|
||||
:adjustPosition="true" :isDisabled="isHistory" />
|
||||
|
||||
@@ -322,14 +322,16 @@
|
||||
seqNo.value = body.seqNo;
|
||||
touchBtnCallBack.value && touchBtnCallBack.value();
|
||||
touchBtnCallBack.value = null;
|
||||
console.log('body!',body);
|
||||
talkingList.value.push({
|
||||
type: 'question',
|
||||
talkingText: body.intrctContent,
|
||||
isLoading: false,
|
||||
id: _id,
|
||||
...body,
|
||||
talkingVoice: askData.value.talkingVoice
|
||||
...askData.value
|
||||
});
|
||||
console.log('talkingList!',talkingList.value);
|
||||
talkingList.value.push({
|
||||
type: 'answer',
|
||||
isLoading: true,
|
||||
@@ -701,7 +703,7 @@
|
||||
}
|
||||
|
||||
// 新发送语音
|
||||
const submitVoice = ({ossKey,text}) => {
|
||||
const submitVoice = ({ossKey, text}) => {
|
||||
console.log('新发送语音',ossKey,text);
|
||||
if(!ossKey) {
|
||||
talkingList.value.pop();
|
||||
@@ -716,9 +718,6 @@
|
||||
intrctWay: '02', //01-文本;02-语音;03-图片
|
||||
intrctContent: text,
|
||||
bucketKey: ossKey,
|
||||
// ossFileAddr: _res.body.ossFileAddr,
|
||||
// talkingVoice: voicePath,
|
||||
// voiceTime: voiceTime
|
||||
};
|
||||
sendMessage({
|
||||
commond: 'ASK',
|
||||
@@ -772,6 +771,29 @@
|
||||
});
|
||||
// #endif
|
||||
};
|
||||
|
||||
// 新录音组件文本提交
|
||||
const submitAnswer = (val, cb = null) => {
|
||||
if (!answerIsOver.value) {
|
||||
// 问题回答中
|
||||
return;
|
||||
}
|
||||
touchBtnCallBack.value = cb;
|
||||
askData.value = {
|
||||
reqBatch: reqBatch.value,
|
||||
intrctWay: '01', //01-文本;02-语音;03-图片
|
||||
intrctContent: val,
|
||||
bucketKey: ''
|
||||
};
|
||||
sendMessage({
|
||||
commond: 'ASK',
|
||||
body: askData.value
|
||||
});
|
||||
setHeight({
|
||||
height: 0
|
||||
});
|
||||
}
|
||||
|
||||
// 发送文本
|
||||
const submitAnswerNow = (val, cb = null) => {
|
||||
if (!answerIsOver.value) {
|
||||
|
||||
@@ -14,7 +14,7 @@ import AtomicBoolean from "java.util.concurrent.atomic.AtomicBoolean";
|
||||
const isRecording = new AtomicBoolean(false); // 原子布尔值,保证线程安全
|
||||
|
||||
// 1. 音频采集参数(极简配置,适配大部分设备)
|
||||
const sampleRate : Int = 48000; // 采样率
|
||||
const sampleRate : Int = 16000; // 采样率
|
||||
const channelConfig = AudioFormat.CHANNEL_IN_MONO; // 单声道
|
||||
const audioFormat = AudioFormat.ENCODING_PCM_16BIT; // 16位PCM
|
||||
const minBufferSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat);
|
||||
@@ -58,6 +58,11 @@ export function startAudioRecord(options : StartAudioRecordOptions) : void {
|
||||
if (readSize > 0) {
|
||||
const data = new ByteArray(readSize);
|
||||
java.lang.System.arraycopy(buffer, 0, data, 0, readSize);
|
||||
|
||||
// const lastVolume = calculateVolumeLight(buffer, readSize);
|
||||
|
||||
// console.log('lastVolume', lastVolume);
|
||||
|
||||
options.onFrame?.(data)
|
||||
}
|
||||
}
|
||||
@@ -100,4 +105,35 @@ export function stopAudioRecord() : void {
|
||||
}
|
||||
}
|
||||
}
|
||||
export function preRequestRecordPermission(callback ?: (granted : boolean) => void) { callback?.(true); }
|
||||
export function preRequestRecordPermission(callback ?: (granted : boolean) => void) { callback?.(true); }
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 轻量化音量计算(低资源消耗版)
|
||||
* @param buffer 音频缓冲区
|
||||
* @param readSize 实际读取的字节数
|
||||
* @returns 音量值(0-100)
|
||||
*/
|
||||
function calculateVolumeLight(buffer: ByteArray, readSize: number): number {
|
||||
try {
|
||||
let maxAmplitude = 0;
|
||||
// 1. 抽样计算:每4个采样点取1个(减少75%的计算量)
|
||||
for (let i:Int = 0; i < readSize; i += 8) { // 2字节/采样点 * 4 = 8字节步长
|
||||
if (i + 1 >= readSize) break; // 防止越界
|
||||
// 2. 简化:只取绝对值最大的振幅(替代均方根
|
||||
const shortVal = (buffer[i + 1] << 8) | (buffer[i] & 0xff);
|
||||
const absVal = Math.abs(shortVal);
|
||||
if (absVal > maxAmplitude) {
|
||||
maxAmplitude = absVal;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 直接映射到0-100(放弃分贝计算,减少Math.log10等耗时操作)
|
||||
const volume = (maxAmplitude / 32767) * 100;
|
||||
return Math.round(Math.max(0, Math.min(100, volume))); // 限制0-100
|
||||
} catch (e) {
|
||||
console.error('轻量化音量计算失败:', e);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user