feat: 增加扬声器音量控制
This commit is contained in:
parent
974b4ed736
commit
a3ca31a1e0
@ -8,6 +8,7 @@ import {
|
||||
createAGVGrpcClient,
|
||||
createARMGrpcClient,
|
||||
createMicrophoneGrpcClient,
|
||||
createSpeakerGrpcClient,
|
||||
__dirname
|
||||
} from './grpcClient.js';
|
||||
import { attachAudioWebSocket } from './audioWebSocket.js';
|
||||
@ -83,6 +84,72 @@ app.post('/api/audio/microphone/volume/set', (req, res) => {
|
||||
})
|
||||
})
|
||||
|
||||
// 获取机器人扬声器音量(0-100)。
|
||||
app.post('/api/audio/speaker/volume/get', (req, res) => {
|
||||
const { ip, deviceId } = req.body
|
||||
if (!ip || !deviceId) {
|
||||
res.status(400).json({ code: 400, message: '机器人地址和扬声器设备 ID 不能为空' })
|
||||
return
|
||||
}
|
||||
|
||||
const grpcClient = createSpeakerGrpcClient(ip)
|
||||
grpcClient.getVolume(
|
||||
{ header: { device_id: deviceId } },
|
||||
{ deadline: Date.now() + 5000 },
|
||||
(error, feedback) => {
|
||||
grpcClient.close()
|
||||
if (error) {
|
||||
console.error('[audio] get speaker volume failed:', error)
|
||||
res.status(502).json({ code: 502, message: error.details || error.message || '获取扬声器音量失败' })
|
||||
return
|
||||
}
|
||||
if (feedback?.header?.success === false) {
|
||||
res.status(502).json({
|
||||
code: 502,
|
||||
message: feedback.header.error_message || '机器人拒绝获取扬声器音量'
|
||||
})
|
||||
return
|
||||
}
|
||||
res.json({ code: 200, data: { volume: feedback?.volume ?? 0 } })
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
// 设置机器人扬声器音量(0-100)。
|
||||
app.post('/api/audio/speaker/volume/set', (req, res) => {
|
||||
const { ip, deviceId } = req.body
|
||||
const volume = Number(req.body.volume)
|
||||
if (!ip || !deviceId) {
|
||||
res.status(400).json({ code: 400, message: '机器人地址和扬声器设备 ID 不能为空' })
|
||||
return
|
||||
}
|
||||
if (!Number.isInteger(volume) || volume < 0 || volume > 100) {
|
||||
res.status(400).json({ code: 400, message: '音量必须是 0 到 100 的整数' })
|
||||
return
|
||||
}
|
||||
|
||||
const grpcClient = createSpeakerGrpcClient(ip)
|
||||
grpcClient.setVolume({
|
||||
header: { device_id: deviceId },
|
||||
volume
|
||||
}, { deadline: Date.now() + 5000 }, (error, feedback) => {
|
||||
grpcClient.close()
|
||||
if (error) {
|
||||
console.error('[audio] set speaker volume failed:', error)
|
||||
res.status(502).json({ code: 502, message: error.details || error.message || '设置扬声器音量失败' })
|
||||
return
|
||||
}
|
||||
if (feedback?.header?.success === false) {
|
||||
res.status(502).json({
|
||||
code: 502,
|
||||
message: feedback.header.error_message || '机器人拒绝设置扬声器音量'
|
||||
})
|
||||
return
|
||||
}
|
||||
res.json({ code: 200, data: { volume } })
|
||||
})
|
||||
})
|
||||
|
||||
// 获取agv运行时状态
|
||||
app.post('/api/agv/getRuntimeState', async (req, res) => {
|
||||
const request = {
|
||||
|
||||
@ -7,3 +7,11 @@ export function getMicrophoneVolume(params) {
|
||||
export function setMicrophoneVolume(params) {
|
||||
return request('/api/audio/microphone/volume/set', params, 'POST')
|
||||
}
|
||||
|
||||
export function getSpeakerVolume(params) {
|
||||
return request('/api/audio/speaker/volume/get', params, 'POST')
|
||||
}
|
||||
|
||||
export function setSpeakerVolume(params) {
|
||||
return request('/api/audio/speaker/volume/set', params, 'POST')
|
||||
}
|
||||
|
||||
@ -55,15 +55,29 @@
|
||||
<div class="robot-volume">
|
||||
<span class="robot-volume__label">机器人麦克风音量</span>
|
||||
<el-slider
|
||||
v-model="robotVolume"
|
||||
v-model="microphoneVolume"
|
||||
class="robot-volume__slider"
|
||||
:min="0"
|
||||
:max="100"
|
||||
:disabled="isVolumeLoading"
|
||||
:disabled="isMicrophoneVolumeLoading"
|
||||
:show-tooltip="true"
|
||||
@change="updateRobotVolume"
|
||||
@change="updateMicrophoneVolume"
|
||||
/>
|
||||
<span class="robot-volume__value">{{ robotVolume }}%</span>
|
||||
<span class="robot-volume__value">{{ microphoneVolume }}%</span>
|
||||
</div>
|
||||
|
||||
<div class="robot-volume">
|
||||
<span class="robot-volume__label">机器人扬声器音量</span>
|
||||
<el-slider
|
||||
v-model="speakerVolume"
|
||||
class="robot-volume__slider"
|
||||
:min="0"
|
||||
:max="100"
|
||||
:disabled="isSpeakerVolumeLoading"
|
||||
:show-tooltip="true"
|
||||
@change="updateSpeakerVolume"
|
||||
/>
|
||||
<span class="robot-volume__value">{{ speakerVolume }}%</span>
|
||||
</div>
|
||||
|
||||
<section class="status-card">
|
||||
@ -87,7 +101,12 @@
|
||||
import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
|
||||
import { ElMessage, ElNotification } from 'element-plus'
|
||||
import { useRobotStore } from '@/stores/robot'
|
||||
import { getMicrophoneVolume, setMicrophoneVolume } from '@/api/audio'
|
||||
import {
|
||||
getMicrophoneVolume,
|
||||
getSpeakerVolume,
|
||||
setMicrophoneVolume,
|
||||
setSpeakerVolume
|
||||
} from '@/api/audio'
|
||||
|
||||
// 机器人实际音频流为 48kHz,因此浏览器上传也统一为 48kHz。
|
||||
const TARGET_SAMPLE_RATE = 48000
|
||||
@ -101,9 +120,12 @@ const isStarting = ref(false)
|
||||
const isMuted = ref(false)
|
||||
const connectionStatus = ref('idle')
|
||||
const errorMessage = ref('')
|
||||
const robotVolume = ref(50)
|
||||
const isVolumeLoading = ref(false)
|
||||
let confirmedRobotVolume = 50
|
||||
const microphoneVolume = ref(50)
|
||||
const speakerVolume = ref(50)
|
||||
const isMicrophoneVolumeLoading = ref(false)
|
||||
const isSpeakerVolumeLoading = ref(false)
|
||||
let confirmedMicrophoneVolume = 50
|
||||
let confirmedSpeakerVolume = 50
|
||||
|
||||
// 本次通话占用的浏览器资源。
|
||||
const localStream = ref(null)
|
||||
@ -128,46 +150,89 @@ const statusMeta = computed(() => {
|
||||
return statusMap[connectionStatus.value] || statusMap.idle
|
||||
})
|
||||
|
||||
function volumeRequestParams() {
|
||||
function microphoneVolumeRequestParams() {
|
||||
return {
|
||||
ip: robotStore.ip,
|
||||
deviceId: robotStore.micDeviceId
|
||||
}
|
||||
}
|
||||
|
||||
async function loadRobotVolume({ silent = false } = {}) {
|
||||
async function loadMicrophoneVolume({ silent = false } = {}) {
|
||||
if (!robotStore.ip || !robotStore.micDeviceId) return
|
||||
isVolumeLoading.value = true
|
||||
isMicrophoneVolumeLoading.value = true
|
||||
try {
|
||||
const response = await getMicrophoneVolume(volumeRequestParams())
|
||||
const response = await getMicrophoneVolume(microphoneVolumeRequestParams())
|
||||
if (response.code !== 200) throw new Error(response.message || '获取机器人音量失败')
|
||||
const volume = Math.max(0, Math.min(100, Number(response.data?.volume) || 0))
|
||||
robotVolume.value = volume
|
||||
confirmedRobotVolume = volume
|
||||
microphoneVolume.value = volume
|
||||
confirmedMicrophoneVolume = volume
|
||||
} catch (error) {
|
||||
if (!silent) ElMessage.error(error.message || '获取机器人音量失败')
|
||||
} finally {
|
||||
isVolumeLoading.value = false
|
||||
isMicrophoneVolumeLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function updateRobotVolume(volume) {
|
||||
const previousVolume = confirmedRobotVolume
|
||||
isVolumeLoading.value = true
|
||||
async function updateMicrophoneVolume(volume) {
|
||||
const previousVolume = confirmedMicrophoneVolume
|
||||
isMicrophoneVolumeLoading.value = true
|
||||
try {
|
||||
const response = await setMicrophoneVolume({
|
||||
...volumeRequestParams(),
|
||||
...microphoneVolumeRequestParams(),
|
||||
volume: Math.round(volume)
|
||||
})
|
||||
if (response.code !== 200) throw new Error(response.message || '设置机器人音量失败')
|
||||
robotVolume.value = response.data.volume
|
||||
confirmedRobotVolume = response.data.volume
|
||||
microphoneVolume.value = response.data.volume
|
||||
confirmedMicrophoneVolume = response.data.volume
|
||||
ElMessage.success(`机器人麦克风音量已设置为 ${response.data.volume}%`)
|
||||
} catch (error) {
|
||||
robotVolume.value = previousVolume
|
||||
microphoneVolume.value = previousVolume
|
||||
ElMessage.error(error.message || '设置机器人音量失败')
|
||||
} finally {
|
||||
isVolumeLoading.value = false
|
||||
isMicrophoneVolumeLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function speakerVolumeRequestParams() {
|
||||
return {
|
||||
ip: robotStore.ip,
|
||||
deviceId: robotStore.spkDeviceId
|
||||
}
|
||||
}
|
||||
|
||||
async function loadSpeakerVolume({ silent = false } = {}) {
|
||||
if (!robotStore.ip || !robotStore.spkDeviceId) return
|
||||
isSpeakerVolumeLoading.value = true
|
||||
try {
|
||||
const response = await getSpeakerVolume(speakerVolumeRequestParams())
|
||||
if (response.code !== 200) throw new Error(response.message || '获取扬声器音量失败')
|
||||
const volume = Math.max(0, Math.min(100, Number(response.data?.volume) || 0))
|
||||
speakerVolume.value = volume
|
||||
confirmedSpeakerVolume = volume
|
||||
} catch (error) {
|
||||
if (!silent) ElMessage.error(error.message || '获取扬声器音量失败')
|
||||
} finally {
|
||||
isSpeakerVolumeLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function updateSpeakerVolume(volume) {
|
||||
const previousVolume = confirmedSpeakerVolume
|
||||
isSpeakerVolumeLoading.value = true
|
||||
try {
|
||||
const response = await setSpeakerVolume({
|
||||
...speakerVolumeRequestParams(),
|
||||
volume: Math.round(volume)
|
||||
})
|
||||
if (response.code !== 200) throw new Error(response.message || '设置扬声器音量失败')
|
||||
speakerVolume.value = response.data.volume
|
||||
confirmedSpeakerVolume = response.data.volume
|
||||
ElMessage.success(`机器人扬声器音量已设置为 ${response.data.volume}%`)
|
||||
} catch (error) {
|
||||
speakerVolume.value = previousVolume
|
||||
ElMessage.error(error.message || '设置扬声器音量失败')
|
||||
} finally {
|
||||
isSpeakerVolumeLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
@ -472,7 +537,10 @@ function endCall() {
|
||||
}
|
||||
|
||||
// 离开页面时释放麦克风、AudioWorklet 和 WebSocket。
|
||||
onMounted(() => loadRobotVolume())
|
||||
onMounted(() => {
|
||||
loadMicrophoneVolume()
|
||||
loadSpeakerVolume()
|
||||
})
|
||||
onBeforeUnmount(cleanupResources)
|
||||
</script>
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user