<template>
|
<Teleport to=".content-container">
|
<div class="shoutingMask">
|
<van-icon name="cross" class="closeDlg" @click="closeDlgFun" />
|
<div v-if="startState === '2'" style="text-align: center">{{nowPlaying}}</div>
|
<div class="functionHead">
|
<div v-if="startState === '0'">
|
<img :src="startSpeakImg" alt="" @click="startRecording" />
|
<div>点击录音</div>
|
</div>
|
<div v-if="startState === '1'">
|
<img :src="videoStopImg" alt="" @click="stopRecord" />
|
<div>点击完成</div>
|
</div>
|
|
<template v-if="startState === '2'">
|
<div>
|
<img :src="tryListrenImg" alt="" @click="trialListening" />
|
<div>试听</div>
|
</div>
|
<div>
|
<img v-if="speakerInfo?.system_state === 2" :src="videoStopImg" alt="" @click="suspendDrone" />
|
<div v-else-if="speakerInfo?.system_state === 1" class="intermediateState" @click="suspendDrone">
|
<img :src="videoPlayImg" alt="" />
|
<van-loading color="#0E0E0EFF" />
|
</div>
|
<img v-else :src="videoPlayImg" alt="" @click="pushToDrone" />
|
<div>{{ systemState?.[speakerInfo?.system_state] }}</div>
|
</div>
|
<div>
|
<img :src="repertSpeakImg" alt="" @click="resetRecording" />
|
<div>重录</div>
|
</div>
|
</template>
|
</div>
|
|
<div v-if="startState === '2'" class="saveBlock">
|
<el-input
|
size="small"
|
v-model="soundVideoName"
|
:suffix-icon="null"
|
placeholder="请输入音频名称名称"
|
v-focusDisableKeys
|
/>
|
<div class="whiteBtn" @click="saveSound">保存音频</div>
|
</div>
|
|
<VolumeAdjustment />
|
</div>
|
</Teleport>
|
</template>
|
|
<script setup>
|
import startSpeakImg from '@/assets/images/taskManagement/taskIntermediateContent/start-speak.svg'
|
import videoStopImg from '@/assets/images/taskManagement/taskIntermediateContent/video-stop.svg'
|
import tryListrenImg from '@/assets/images/taskManagement/taskIntermediateContent/try-listren.svg'
|
import videoPlayImg from '@/assets/images/taskManagement/taskIntermediateContent/video-play.svg'
|
import repertSpeakImg from '@/assets/images/taskManagement/taskIntermediateContent/repret-speak.svg'
|
import Recorder from 'js-audio-recorder'
|
import _ from 'lodash'
|
import dayjs from 'dayjs'
|
import { setVoiceVolumn, startVoice, stopVoiceApi, uploadSpeak, voiceMode } from '@/api/payload'
|
import { showToast } from 'vant'
|
import VolumeAdjustment from '@/components/CurrentTaskDetails/AppConsoleRight/VolumeAdjustment.vue'
|
|
const showShouting = defineModel('show')
|
const soundVideoName = ref(`${dayjs().format('YYYYMMDDHHmmss')}实时喊话`) // 喊话名称
|
|
const fileName = ref('')
|
const accept = '.FLAC,.ALAC,.WAV,.MP3,.AAC,.OGG,.wav'
|
const list = ref([])
|
let audioPlayer = null
|
let recordingTime = ref('00:00')
|
const showAdd = ref(false)
|
// 0未录制 1 录制中 2 录制完成
|
const startState = ref('0')
|
const droneHornState = ref('0')
|
const activeAudio = ref('')
|
const searchText = ref('')
|
const showSearch = ref(false)
|
const editItem = ref({})
|
let ctx = null
|
let oCanvas = null
|
let drawRecordId = null
|
const dockSn = inject('dockSn')
|
const speakerInfo = inject('speakerInfo')
|
const delayedRecepAuto = inject('delayedRecepAuto')
|
|
const playMode = { 0: '单次播放', 1: '循环播放' }
|
const systemState = { 0: '空闲中', 1: '传输中', 2: '播放中', 3: '异常', 4: '文本转换中', 99: '下载中' }
|
|
|
const pushed = ref(false)
|
// 正在播放
|
const nowPlaying = computed(()=>{
|
return speakerInfo.value.system_state === 2 && !pushed.value ? '正在播放上一次音频' : ''
|
})
|
|
const wayLineJobInfoId = ref(null)
|
const updateKey = ref(0)
|
const uNotifyRef = ref(null)
|
|
const route = useRoute()
|
|
function timeConversion(secondsCount) {
|
const minutes = _.padStart(Math.floor(secondsCount / 60), 2, '0')
|
const secs = _.padStart(Math.floor(secondsCount % 60), 2, '0')
|
return minutes + ':' + secs
|
}
|
|
function startRecording() {
|
startState.value = '1'
|
audioPlayer = new Recorder({
|
sampleBits: 16, // 采样位数,支持8或16,默认16
|
sampleRate: 16000, // 采样率,支持11025、16000、22050、24000、44100、48000
|
numChannels: 1, // 声道,支持1或2,默认1
|
})
|
nextTick(() => {
|
audioPlayer.start().then(() => {
|
audioPlayer.onprocess = duration => {}
|
})
|
})
|
}
|
|
// 完成录音
|
function stopRecord() {
|
startState.value = '2'
|
audioPlayer.stop()
|
soundVideoName.value = `${dayjs().format('YYYYMMDDHHmmss')}实时喊话`
|
}
|
|
// 试听
|
function trialListening() {
|
audioPlayer?.play()
|
}
|
|
async function pushToDrone() {
|
let formData = new FormData()
|
const wavBlob = audioPlayer.getWAVBlob()
|
const wavform = new File([wavBlob], new Date().getTime() + '.wav')
|
formData.append('file', wavform)
|
formData.append('sn', dockSn.value)
|
formData.append('psdk_index', 2)
|
formData.append('name', soundVideoName.value + '.wav')
|
formData.append('volumn', 100)
|
await voiceMode({ sn: dockSn.value }, { psdk_index: 2, play_mode: 0 })
|
await stopVoiceApi({ sn: dockSn.value, psdk_index: 2 })
|
startVoice(formData)
|
.then( res => {
|
pushed.value = true
|
showToast('推送成功')
|
})
|
}
|
|
function suspendDrone() {
|
stopVoiceApi({ sn: dockSn.value, psdk_index: 2 }).then(res => {
|
showToast('停止播放成功')
|
})
|
}
|
|
function resetRecording() {
|
startState.value = '0'
|
audioPlayer?.destroy().then(() => {
|
audioPlayer = null // 释放资源
|
})
|
}
|
|
function closeDlgFun() {
|
resetRecording()
|
showShouting.value = false
|
}
|
|
function saveSound() {
|
const wavFile = new File([audioPlayer.getWAVBlob()], '.wav')
|
let formData = new FormData()
|
formData.append('file', wavFile)
|
formData.append('fileName', soundVideoName.value + '.wav')
|
formData.append('sn', dockSn.value)
|
uploadSpeak(formData).then(res => {
|
showToast('保存成功')
|
})
|
}
|
|
watch(
|
() => speakerInfo.value,
|
(newValue, oldValue) => {
|
const new_system_state = newValue?.system_state
|
const old_system_state = oldValue?.system_state
|
if (new_system_state !== old_system_state) {
|
if (new_system_state === 2) {
|
trialListening()
|
} else {
|
audioPlayer?.pause?.()
|
}
|
}
|
},
|
{ immediate: true, deep: true }
|
)
|
</script>
|
|
<style scoped lang="scss">
|
.shoutingMask {
|
position: absolute;
|
width: half(313);
|
height: 100%;
|
top: half(0);
|
right: half(0);
|
z-index: 1;
|
background: rgba(0, 0, 0, 0.4);
|
backdrop-filter: blur(5px);
|
font-family: Source Han Sans CN, Source Han Sans CN;
|
font-size: half(11);
|
color: #ffffff;
|
display: flex;
|
flex-direction: column;
|
justify-content: center;
|
gap: half(30) 0;
|
|
.closeDlg {
|
font-size: half(20);
|
position: absolute;
|
right: half(5);
|
top: half(5);
|
color: #ffffff;
|
}
|
|
.functionHead {
|
display: flex;
|
justify-content: center;
|
align-items: center;
|
text-align: center;
|
gap: half(36);
|
|
img {
|
width: half(42);
|
height: half(42);
|
}
|
>div{
|
display: flex;
|
flex-direction: column;
|
align-items: center;
|
gap: half(5);
|
}
|
|
|
.intermediateState{
|
position: relative;
|
width: half(42);
|
height: half(42);
|
> img {
|
width: half(42);
|
height: half(42);
|
opacity: 0.5;
|
}
|
.van-loading {
|
position: absolute;
|
left: 50%;
|
top: 50%;
|
transform: translate(-50%, -50%);
|
width: half(35);
|
height: half(35);
|
}
|
}
|
}
|
|
.saveBlock {
|
display: flex;
|
height: half(38);
|
padding: half(0) half(30);
|
gap: 0 half(6);
|
|
.el-input {
|
height: 100%;
|
font-size: half(15);
|
}
|
}
|
}
|
</style>
|