Refactor microphone selection handling in voice components
- Rename `setSelectedDeviceId` to `selectDevice` in `DebugVoicePanel` and `VoiceSessionControls` for clarity and consistency. - Update `useVoicePreview` hook to implement the `selectDevice` function, enabling dynamic microphone switching during voice sessions. - Enhance device selection logic to support real-time audio track replacement without requiring session reconnection.
This commit is contained in:
@@ -1838,7 +1838,7 @@ function DebugVoicePanel({
|
||||
messages,
|
||||
audioInputs,
|
||||
selectedDeviceId,
|
||||
setSelectedDeviceId,
|
||||
selectDevice,
|
||||
sendText,
|
||||
connect,
|
||||
disconnect,
|
||||
@@ -1868,7 +1868,7 @@ function DebugVoicePanel({
|
||||
assistantId={assistantId}
|
||||
audioInputs={audioInputs}
|
||||
selectedDeviceId={selectedDeviceId}
|
||||
setSelectedDeviceId={setSelectedDeviceId}
|
||||
selectDevice={selectDevice}
|
||||
connect={connect}
|
||||
disconnect={disconnect}
|
||||
/>
|
||||
@@ -1941,9 +1941,8 @@ function DebugVoicePanel({
|
||||
<Select
|
||||
value={selectedDeviceId || "default"}
|
||||
onValueChange={(value) =>
|
||||
setSelectedDeviceId(value === "default" ? "" : value)
|
||||
selectDevice(value === "default" ? "" : value)
|
||||
}
|
||||
disabled={recording}
|
||||
>
|
||||
<SelectTrigger
|
||||
size="sm"
|
||||
@@ -2044,7 +2043,7 @@ function VoiceSessionControls({
|
||||
assistantId,
|
||||
audioInputs,
|
||||
selectedDeviceId,
|
||||
setSelectedDeviceId,
|
||||
selectDevice,
|
||||
connect,
|
||||
disconnect,
|
||||
}: {
|
||||
@@ -2054,7 +2053,7 @@ function VoiceSessionControls({
|
||||
assistantId: string | null;
|
||||
audioInputs: MediaDeviceInfo[];
|
||||
selectedDeviceId: string;
|
||||
setSelectedDeviceId: (deviceId: string) => void;
|
||||
selectDevice: (deviceId: string) => void;
|
||||
connect: () => Promise<void>;
|
||||
disconnect: () => void;
|
||||
}) {
|
||||
@@ -2096,9 +2095,8 @@ function VoiceSessionControls({
|
||||
<Select
|
||||
value={selectedDeviceId || "default"}
|
||||
onValueChange={(value) =>
|
||||
setSelectedDeviceId(value === "default" ? "" : value)
|
||||
selectDevice(value === "default" ? "" : value)
|
||||
}
|
||||
disabled={recording}
|
||||
>
|
||||
<SelectTrigger
|
||||
size="sm"
|
||||
|
||||
@@ -373,6 +373,56 @@ export function useVoicePreview(assistantId: string | null) {
|
||||
}
|
||||
}, [assistantId, fail, refreshDevices]);
|
||||
|
||||
// 选择麦克风:更新选择;若会话正在发送麦克风音频,则用 WebRTC replaceTrack
|
||||
// 热切换轨道(无需重新协商),并把波形可视化重新接到新流。
|
||||
// 未连接时仅记下选择,留待下次 connect 生效。
|
||||
const selectDevice = useCallback(
|
||||
async (deviceId: string) => {
|
||||
setSelectedDeviceId(deviceId);
|
||||
selectedDeviceIdRef.current = deviceId;
|
||||
|
||||
const pc = pcRef.current;
|
||||
if (!pc) return;
|
||||
// 只有本就在发送麦克风音频(存在 audio sender 轨道)时才热切换;
|
||||
// 仅收听模式下加麦克风需重新协商,这里不处理,留到下次连接。
|
||||
const sender = pc
|
||||
.getSenders()
|
||||
.find((s) => s.track?.kind === "audio");
|
||||
if (!sender) return;
|
||||
|
||||
try {
|
||||
const audioConstraints: MediaTrackConstraints = {
|
||||
echoCancellation: true,
|
||||
noiseSuppression: true,
|
||||
autoGainControl: true,
|
||||
};
|
||||
if (deviceId) audioConstraints.deviceId = { exact: deviceId };
|
||||
const newStream = await navigator.mediaDevices.getUserMedia({
|
||||
audio: audioConstraints,
|
||||
});
|
||||
// 切换期间可能已断开,丢弃刚拿到的流
|
||||
if (pcRef.current !== pc) {
|
||||
newStream.getTracks().forEach((t) => t.stop());
|
||||
return;
|
||||
}
|
||||
const newTrack = newStream.getAudioTracks()[0];
|
||||
if (!newTrack) {
|
||||
newStream.getTracks().forEach((t) => t.stop());
|
||||
return;
|
||||
}
|
||||
await sender.replaceTrack(newTrack);
|
||||
// 旧轨道停掉,新流替换(波形/分析器随 localStream 变化自动重连)
|
||||
localStreamRef.current?.getTracks().forEach((t) => t.stop());
|
||||
localStreamRef.current = newStream;
|
||||
setLocalStream(newStream);
|
||||
setMicWarning(null);
|
||||
} catch (mediaError) {
|
||||
setMicWarning(microphoneErrorMessage(mediaError));
|
||||
}
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
// 发送文字消息:后端先打断当前播报,再按用户输入触发新回复。
|
||||
// 成功返回 true;通道未就绪(未开始对话/连接中)返回 false。
|
||||
const sendText = useCallback((text: string): boolean => {
|
||||
@@ -396,6 +446,7 @@ export function useVoicePreview(assistantId: string | null) {
|
||||
audioInputs,
|
||||
selectedDeviceId,
|
||||
setSelectedDeviceId,
|
||||
selectDevice,
|
||||
sendText,
|
||||
connect,
|
||||
disconnect,
|
||||
|
||||
Reference in New Issue
Block a user