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