From dc2a7484bb7fce94b5f7c01052fdd853f05b95c1 Mon Sep 17 00:00:00 2001 From: Xin Wang Date: Fri, 10 Jul 2026 11:09:44 +0800 Subject: [PATCH] Enhance audio and video device selection in MobileCallPage - Refactor `CallDeviceSelect` to `CallAudioDeviceSelect`, allowing simultaneous selection of microphone and output devices. - Introduce dropdown menus for selecting audio input and output devices, improving user experience. - Update `useVoicePreview` hook to manage audio output devices, including support for selecting and applying output devices. - Modify `MobileCallPage` to integrate new device selection components, ensuring seamless audio and video call functionality. --- .../src/components/pages/MobileCallPage.tsx | 138 ++++++++++++++---- frontend/src/hooks/use-voice-preview.ts | 63 +++++++- 2 files changed, 169 insertions(+), 32 deletions(-) diff --git a/frontend/src/components/pages/MobileCallPage.tsx b/frontend/src/components/pages/MobileCallPage.tsx index 511edd0..3585edf 100644 --- a/frontend/src/components/pages/MobileCallPage.tsx +++ b/frontend/src/components/pages/MobileCallPage.tsx @@ -1,8 +1,17 @@ "use client"; import { useCallback, useEffect, useRef } from "react"; -import { Camera, Loader2, Mic, Phone, PhoneOff, Video } from "lucide-react"; +import { Camera, Headphones, Loader2, Mic, Phone, PhoneOff, Video } from "lucide-react"; import { Button } from "@/components/ui/button"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuLabel, + DropdownMenuRadioGroup, + DropdownMenuRadioItem, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; import { Select, SelectContent, @@ -13,22 +22,99 @@ import { import { useCameraPreview } from "@/hooks/use-camera-preview"; import { useVoicePreview } from "@/hooks/use-voice-preview"; -function CallDeviceSelect({ - kind, +function CallAudioDeviceSelect({ + micValue, + micDevices, + onMicSelect, + outputValue, + outputDevices, + onOutputSelect, + outputSupported, + disabled, +}: { + micValue: string; + micDevices: MediaDeviceInfo[]; + onMicSelect: (deviceId: string) => void | Promise; + outputValue: string; + outputDevices: MediaDeviceInfo[]; + onOutputSelect: (deviceId: string) => void; + outputSupported: boolean; + disabled?: boolean; +}) { + return ( + + + + + + 麦克风 + + void onMicSelect(next === "default" ? "" : next) + } + > + 默认麦克风 + {micDevices.map((device, index) => ( + + {device.label || `麦克风 ${index + 1}`} + + ))} + + {outputSupported && ( + <> + + + + 耳机 / 扬声器 + + + onOutputSelect(next === "default" ? "" : next) + } + > + + 默认输出设备 + + {outputDevices.map((device, index) => ( + + {device.label || `输出设备 ${index + 1}`} + + ))} + + + )} + + + ); +} + +function CallCameraDeviceSelect({ value, devices, onSelect, disabled, }: { - kind: "microphone" | "camera"; value: string; devices: MediaDeviceInfo[]; onSelect: (deviceId: string) => void | Promise; disabled?: boolean; }) { - const isMic = kind === "microphone"; - const label = isMic ? "选择麦克风" : "选择摄像头"; - return (