
在 Vue 应用中用 VueUseuseDevicesList响应式枚举音视频设备【免费下载链接】airi Self hosted, you-owned Grok Companion, a container of souls of waifu, cyber livings to bring them into our worlds, wishing to achieve Neuro-samas altitude. Capable of realtime voice chat, Minecraft, Factorio playing. Web / macOS / Windows supported.项目地址: https://gitcode.com/GitHub_Trending/ai/airiuseDevicesList是 VueUse 提供的响应式封装它将浏览器原生MediaDevices.enumerateDevicesAPI 转化为可响应式更新的设备列表自动划分摄像头、麦克风与扬声器并提供统一的权限申请能力。在本仓库的 AI 桌面与 Web 应用中它被用于麦克风选择、语音流启动和音频设备异常处理本文将以官方参考文档为主体结合仓库源码讲解其用法、配置项与真实落地模式。功能定位Sensors 类别下的设备枚举 composable在 .agents/skills/vueuse-functions/SKILL.md 中useDevicesList被归类为Sensors传感器类别功能描述为 ReactiveenumerateDeviceslisting available input/output devices即对MediaDevices.enumerateDevices()的响应式封装用于列出可用的输入/输出设备调用规则为AUTO适用时自动使用。该 composable 的核心价值在于把以下繁琐工作自动化监听devicechange事件并在设备插拔时自动刷新列表将扁平化的MediaDeviceInfo[]按kind自动划分为摄像头videoinput、麦克风audioinput、扬声器audiooutput三类通过ensurePermissions统一处理getUserMedia权限申请流程暴露isSupported用于检测浏览器是否支持该 API保证 SSR 兼容。基础用法一行代码拿到三类设备最基本的用法是从vueuse/core导入并在组件或 composable 中调用import { useDevicesList } from vueuse/core const { devices, videoInputs: cameras, audioInputs: microphones, audioOutputs: speakers, } useDevicesList()返回值说明返回属性类型含义devicesShallowRefMediaDeviceInfo[]全部设备的完整列表videoInputsComputedRefMediaDeviceInfo[]摄像头kind videoinputaudioInputsComputedRefMediaDeviceInfo[]麦克风kind audioinputaudioOutputsComputedRefMediaDeviceInfo[]扬声器kind audiooutputpermissionGrantedShallowRefboolean媒体权限是否已授予ensurePermissions() Promiseboolean申请媒体权限返回是否成功isSupportedRefboolean当前环境是否支持enumerateDevices解构时可以直接重命名为语义更清晰的变量名如cameras、microphones、speakers这在模板和业务逻辑中都非常直观。请求权限ensurePermissions与权限时序问题浏览器在用户未授权前enumerateDevices返回的设备对象的label和deviceId通常为空字符串无法用于精确的设备选择。因此 VueUse 提供了ensurePermissions方法主动触发权限申请import { useDevicesList } from vueuse/core const { ensurePermissions, permissionGranted, } useDevicesList() await ensurePermissions() console.log(permissionGranted.value)ensurePermissions()内部会调用getUserMedia请求媒体权限返回一个Promiseboolean表示权限是否授予成功。调用后应再读取permissionGranted.value确认状态。仓库源码 packages/stage-ui/src/composables/audio/audio-device.ts 中的askPermission函数给出了一个完整的权限申请封装并指出了 VueUse 的一个时序细节async function askPermission() { try { const granted await ensurePermissions() if (granted) { // NOTICE: // VueUse starts its post-permission device refresh without awaiting it, so callers can // otherwise observe the anonymous pre-permission list after askPermission() resolves. devices.value await navigator.mediaDevices.enumerateDevices() } selectAvailableAudioInput() } catch (error) { // ...错误处理与埋点 } }源码注释明确说明VueUse 在权限授予后会异步不 await刷新设备列表因此ensurePermissions()resolve 后调用方可能仍读到权限授予前的匿名设备列表。仓库的做法是手动再调用一次原生navigator.mediaDevices.enumerateDevices()刷新devices.value这一细节对实现授权后立即拿到完整设备信息的交互至关重要。组件形式UseDevicesList除 composable 外VueUse 还提供了同名组件形式适合在模板中直接消费通过v-slot暴露设备分组template UseDevicesList v-slot{ videoInputs, audioInputs, audioOutputs } Cameras: {{ videoInputs }} Microphones: {{ audioInputs }} Speakers: {{ audioOutputs }} /UseDevicesList /template在需要把设备列表交给子组件或直接渲染下拉选项的场景下组件形式可以省去在script中手动桥接的样板代码。配置项详解UseDevicesListOptions根据参考文档的类型声明useDevicesList接受一个可选配置对象export interface UseDevicesListOptions extends ConfigurableNavigator { onUpdated?: (devices: MediaDeviceInfo[]) void /** * Request for permissions immediately if its not granted, * otherwise label and deviceIds could be empty * * default false */ requestPermissions?: boolean /** * Request for types of media permissions * * default { audio: true, video: true } */ constraints?: MediaStreamConstraints }配置项类型默认值说明onUpdated(devices: MediaDeviceInfo[]) void无设备列表每次更新后的回调requestPermissionsbooleanfalse若设为truecomposable 初始化时若权限未授予则立即申请否则label和deviceId可能为空constraintsMediaStreamConstraints{ audio: true, video: true }权限申请时请求的媒体类型可只请求音频或视频继承ConfigurableNavigator—window.navigator可传入自定义navigator对象SSR / 测试场景constraints的取舍直接影响用户体验默认{ audio: true, video: true }会在申请权限时同时请求麦克风和摄像头授权若应用只需要语音输入用户可能会对为什么请求摄像头权限产生疑虑仓库的 packages/stage-ui/src/composables/audio/audio-device.ts 与 apps/stage-web/src/composables/audio-input.ts 都传入了constraints: { audio: true }即只申请音频权限避免不必要的摄像头授权弹窗。返回值详解UseDevicesListReturn参考文档的完整返回类型声明如下export interface UseDevicesListReturn extends Supportable { /** * All devices */ devices: ShallowRefMediaDeviceInfo[] videoInputs: ComputedRefMediaDeviceInfo[] audioInputs: ComputedRefMediaDeviceInfo[] audioOutputs: ComputedRefMediaDeviceInfo[] permissionGranted: ShallowRefboolean ensurePermissions: () Promiseboolean }继承的Supportable提供了isSupported属性用于在调用enumerateDevices前检测浏览器支持度避免在不支持的 WebView 或老版本浏览器中报错。这在多端项目中尤为重要——例如仓库的 stage-web / stage-pocket 等应用同时面向浏览器与 Capacitor WebView运行环境差异明显。仓库实战从设备枚举到语音流的完整链路仓库中最完整的实践案例位于 packages/stage-ui/src/composables/audio/audio-device.ts它展示了useDevicesList与useUserMedia的组合使用模式1. 默认设备偏好解析function resolvePreferredAudioInput(audioInputs: MediaDeviceInfo[]) { return audioInputs.find(device device.deviceId default)?.deviceId || audioInputs[0]?.deviceId || }优先选择deviceId default的系统默认麦克风否则回退到列表第一个输入设备。2. 响应式设备选项const audioInputOptions computed(() audioInputs.value .filter(device device.deviceId) .map(device ({ label: device.label || device.deviceId, value: device.deviceId, })))将audioInputs映射为可直接供下拉选择组件消费的{ label, value }列表未授权时label为空则回退展示deviceId。3. 设备热插拔时自动纠正选择watch(audioInputs, () { selectAvailableAudioInput() })监听audioInputs变化拔掉当前选中的麦克风时触发若所选设备不再存在则自动切换到默认设备保证语音流不会因设备移除而失效。4. 精确设备约束 流启动失败回退const deviceConstraints computedMediaStreamConstraints(() ({ audio: selectedAudioInput.value ? { deviceId: { exact: selectedAudioInput.value }, autoGainControl: true, echoCancellation: true, noiseSuppression: true, } : { autoGainControl: true, echoCancellation: true, noiseSuppression: true, }, }))这里展示了从设备枚举结果到getUserMedia精确约束的完整衔接选定设备后用deviceId: { exact }锁定设备同时开启自动增益、回声消除与降噪启动失败时还会按NotFoundError/OverconstrainedError/ Requested device not found 判断设备缺失场景并逐级回退。apps/stage-web与apps/stage-pocket下的 audio-input.ts 则提供了另一种更简洁的组织方式用watch同时监听permissionGranted、audioInputs与selectedAudioInputId在权限就绪且设备存在时自动完成设备选择随后才允许media.start()启动语音流。测试验证如何 mockuseDevicesList仓库的单元测试 packages/stage-ui/src/composables/audio/audio-device.test.ts 展示了在 Vitest 中 mockvueuse/core的标准做法vi.mock(vueuse/core, async () { const { ref } await import(vue) audioDeviceMock.audioInputsRef ref([]) return { useDevicesList: () ({ audioInputs: audioDeviceMock.audioInputsRef, permissionGranted: ref(false), ensurePermissions: audioDeviceMock.ensurePermissions, }), useUserMedia: () ({ ... }), } })测试覆盖了两个关键行为askPermission()在权限被拒绝DOMExceptionname NotAllowedError时会以低基数的埋点事件上报permission_denied且不暴露浏览器原始错误文本设备列表为空时调用askPermission()不会产生产品埋点事件。通过 mockensurePermissions的 resolve/reject 分支可以在无真实硬件环境下稳定验证权限链路与埋点逻辑这对 CI 环境的可重复性至关重要。使用注意与最佳实践小结SSR 与不支持环境先检查isSupported再调用ensurePermissions避免在无navigator.mediaDevices的环境抛出异常权限时序ensurePermissions()resolve 后设备列表的刷新可能尚未完成必要时手动await navigator.mediaDevices.enumerateDevices()刷新见 audio-device.ts 的 NOTICE 注释最小权限原则仅需语音时传入constraints: { audio: true }不要把摄像头授权一并请求设备热插拔通过watch(audioInputs, ...)监听设备列表变化在所选设备消失时自动回退到默认设备错误分类区分NotAllowedError权限拒绝与NotFoundError/OverconstrainedError设备缺失前者引导用户去浏览器设置授权后者做设备回退或降级提示。useDevicesList虽然只是一个轻量的枚举封装但配合ensurePermissions、useUserMedia与响应式watch足以支撑从设备枚举、权限申请、精确选麦到语音流启停的完整媒体输入链路这也是它在当前仓库多端应用中被反复复用的根本原因。【免费下载链接】airi Self hosted, you-owned Grok Companion, a container of souls of waifu, cyber livings to bring them into our worlds, wishing to achieve Neuro-samas altitude. Capable of realtime voice chat, Minecraft, Factorio playing. Web / macOS / Windows supported.项目地址: https://gitcode.com/GitHub_Trending/ai/airi创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考