帮你快速理解、总结文档立即下载

设备及网络(Web)

最近更新时间:2026-06-09 11:59:22
我的收藏
本文档指导您使用无 UI 集成方案中的 useDeviceState 管理 Web 端会议的摄像头、麦克风、扬声器和本地网络状态。阅读本文后,您可以完成会前设备检测、会中设备控制、PC 与 H5 设备设置面板、设备权限会控以及网络质量展示等功能。

前提条件

用户已经通过 useLoginState 完成登录鉴权,请参考 接入概览 文档。
用户已经通过 useRoomState 进入标准会议房间,请参考 房间管理 文档。
浏览器需要运行在安全环境中,例如 https://、localhost 或 file://。如果页面部署在 HTTP 协议下,浏览器会禁止访问摄像头和麦克风。
若在 iframe 中集成,需要在标签中声明麦克风、摄像头、屏幕分享等权限。
<iframe allow="microphone; camera; display-capture; fullscreen;"></iframe>

设备管理能力说明

不同终端和浏览器对媒体设备的支持能力存在差异,设计设备设置 UI 前建议先明确能力边界。
能力
PC Web
移动端 H5
推荐处理方式
打开/关闭摄像头
支持
支持
使用 openLocalCamera、closeLocalCamera。
切换摄像头设备
支持切换设备列表中的摄像头
支持切换前后置摄像头
PC 使用 setCurrentCamera,H5 使用 switchCamera 切换前后置摄像头。
打开/关闭麦克风
支持
支持
会中推荐入会后先 muteMicrophone 再 openLocalMicrophone 预采集,避免漏音;开麦时调用 unmuteMicrophone,闭麦时调用 muteMicrophone。
切换麦克风设备
支持
不建议展示
H5 建议隐藏麦克风设备选择入口。
切换扬声器设备
支持
不建议展示
H5 建议隐藏扬声器设备选择入口。
设置采集音量
支持
不建议展示
使用 setCaptureVolume。
设置播放音量
支持
不建议展示
使用 setOutputVolume。
麦克风音量检测
支持
支持
使用 startMicrophoneTest 及 currentMicVolume。
切换视频分辨率
支持
支持
使用 updateVideoQuality。
本地镜像设置
支持
支持
使用 switchMirror。
网络质量监控
支持
支持
进房后读取 networkInfo。

场景一:会前设备检测与预览

适用场景

会前检测用于在用户入会前确认摄像头、麦克风和扬声器是否可用,常见于会议预览页、入会设置页和设备检测页。
注意:
会前设备检测用于确认设备是否可用,不代表用户已经在房间中发布音视频。用户入会后,仍需调用对应的设备控制接口,摄像头或麦克风才会被打开并在房间中发布音视频。

检查麦克风

麦克风检测主要用于确认用户是否具备音频采集能力,以及在预览页展示麦克风输入音量。
目标
使用接口/状态
处理建议
获取麦克风列表
getMicrophoneList、microphoneList、currentMicrophone
PC 端可展示设备列表供用户选择;H5 不建议展示设备列表。
切换麦克风
setCurrentMicrophone
仅建议在 PC 端提供。切换失败时保留原设备并提示用户重试。
检测输入音量
startMicrophoneTest、testingMicVolume、stopMicrophoneTest
预览页进入时开启检测,离开预览页时停止检测。
处理采集异常
microphoneLastError
根据 DeviceError 提示无权限、无设备、设备占用或浏览器不支持。
Vue3
React
<script setup lang="ts">
import { onBeforeUnmount, onMounted } from 'vue';
import { useDeviceState } from 'tuikit-atomicx-vue3/room';

const {
microphoneList,
currentMicrophone,
testingMicVolume,
getMicrophoneList,
setCurrentMicrophone,
startMicrophoneTest,
stopMicrophoneTest,
} = useDeviceState();

onMounted(async () => {
await getMicrophoneList();
await startMicrophoneTest({ interval: 200 });
});

async function switchMicrophone(deviceId: string) {
await setCurrentMicrophone({ deviceId });
}

onBeforeUnmount(async () => {
await stopMicrophoneTest();
});
</script
import { useEffect } from 'react';
import { useDeviceState } from 'tuikit-atomicx-react/room';

function MicrophoneCheck() {
const {
microphoneList,
currentMicrophone,
testingMicVolume,
getMicrophoneList,
setCurrentMicrophone,
startMicrophoneTest,
stopMicrophoneTest,
} = useDeviceState();

useEffect(() => {
const init = async () => {
await getMicrophoneList();
await startMicrophoneTest({ interval: 200 });
};
init();
return () => {
stopMicrophoneTest();
};
}, []);

async function switchMicrophone(deviceId: string) {
await setCurrentMicrophone({ deviceId });
}
}

检查摄像头

摄像头检测主要用于确认用户是否具备视频采集能力,并在预览页展示本地画面。
目标
使用接口/状态
处理建议
获取摄像头列表
getCameraList、cameraList、currentCamera
PC 端可展示设备列表;H5 建议只提供前后置切换。
切换摄像头
setCurrentCamera、switchCamera
PC 端用于切换具体摄像头设备。切换失败时保留原设备并提示用户重试。
预览摄像头
startCameraTest、stopCameraTest
进入预览页时传入预览容器,离开预览页时停止测试。
处理采集异常
cameraLastError
根据 DeviceError 提示无权限、无设备、设备占用或浏览器不支持。
Vue3
React
<template>
<div id="camera-preview" class="camera-preview"></div>
</template>

<script setup lang="ts">
import { onBeforeUnmount, onMounted } from 'vue';
import { useDeviceState } from 'tuikit-atomicx-vue3/room';

const {
cameraList,
currentCamera,
getCameraList,
setCurrentCamera,
startCameraTest,
stopCameraTest,
} = useDeviceState();

onMounted(async () => {
await getCameraList();
await startCameraTest({ view: 'camera-preview' });
});

async function switchCamera(deviceId: string) {
await setCurrentCamera({ deviceId });
}

onBeforeUnmount(async () => {
await stopCameraTest();
});
</script>
import { useEffect } from 'react';
import { useDeviceState } from 'tuikit-atomicx-react/room';

function CameraCheck() {
const {
cameraList,
currentCamera,
getCameraList,
setCurrentCamera,
startCameraTest,
stopCameraTest,
} = useDeviceState();

useEffect(() => {
const init = async () => {
await getCameraList();
await startCameraTest({ view: 'camera-preview' });
};
init();
return () => {
stopCameraTest();
};
}, []);

async function switchCamera(deviceId: string) {
await setCurrentCamera({ deviceId });
}

return <div id="camera-preview" className="camera-preview"></div>;
}

检查扬声器

扬声器检测主要用于确认用户是否可以听到房间内其他成员的声音。
目标
使用接口/状态
处理建议
获取扬声器列表
getSpeakerList、speakerList、currentSpeaker
仅建议在 PC 端展示。部分浏览器可能无法返回扬声器列表。
切换扬声器
setCurrentSpeaker
切换能力依赖浏览器,失败时提示用户使用系统声音设置。
播放测试音频
startSpeakerTest、stopSpeakerTest
用户点击“测试扬声器”时播放测试音频,离开页面时停止播放。
Vue3
React
<script setup lang="ts">
import { onBeforeUnmount, onMounted } from 'vue';
import { useDeviceState } from 'tuikit-atomicx-vue3/room';

const {
speakerList,
currentSpeaker,
getSpeakerList,
setCurrentSpeaker,
startSpeakerTest,
stopSpeakerTest,
} = useDeviceState();

onMounted(async () => {
await getSpeakerList();
});

async function switchSpeaker(deviceId: string) {
await setCurrentSpeaker({ deviceId });
}

async function testSpeaker() {
await startSpeakerTest({ filePath: '/audio/test.mp3' });
}

onBeforeUnmount(async () => {
await stopSpeakerTest();
});
</script>
import { useEffect } from 'react';
import { useDeviceState } from 'tuikit-atomicx-react/room';

function SpeakerCheck() {
const {
speakerList,
currentSpeaker,
getSpeakerList,
setCurrentSpeaker,
startSpeakerTest,
stopSpeakerTest,
} = useDeviceState();

useEffect(() => {
getSpeakerList();
return () => {
stopSpeakerTest();
};
}, []);

async function switchSpeaker(deviceId: string) {
await setCurrentSpeaker({ deviceId });
}

async function testSpeaker() {
await startSpeakerTest({ filePath: '/audio/test.mp3' });
}
}

场景二:会中控制摄像头和麦克风

用户进入房间后,需要通过自定义按钮控制本地摄像头和麦克风的开启或关闭。典型场景包括会议底部工具栏、悬浮控制条、移动端底部操作栏。

开启/关闭摄像头

通过调用 openLocalCamera 和 closeLocalCamera 控制本地摄像头。
Vue3
React
<script setup lang="ts">
import { DeviceError, DeviceStatus, useDeviceState, useRoomState } from 'tuikit-atomicx-vue3/room';

const { currentRoom } = useRoomState();
const {
cameraStatus,
cameraLastError,
openLocalCamera,
closeLocalCamera,
} = useDeviceState();

async function toggleCamera() {
try {
if (cameraStatus.value === DeviceStatus.On) {
await closeLocalCamera();
} else {
await openLocalCamera();
}
} catch (error) {
console.error('摄像头操作失败:', error);
handleCameraError(cameraLastError.value);
}
}

function handleCameraError(error: DeviceError) {
switch (error) {
case DeviceError.NoSystemPermission:
console.error('摄像头权限被拒绝,请在浏览器或系统设置中开启摄像头权限');
break;
case DeviceError.NoDeviceDetected:
console.error('未检测到摄像头设备');
break;
case DeviceError.OccupiedError:
console.error('摄像头被其他应用占用');
break;
case DeviceError.NotSupportCapture:
console.error('当前浏览器不支持摄像头采集');
break;
default:
console.error('摄像头操作失败,请检查设备状态');
break;
}
}
</script>
import { DeviceError, DeviceStatus, useDeviceState, useRoomState } from 'tuikit-atomicx-react/room';

function CameraControl() {
const { currentRoom } = useRoomState();
const {
cameraStatus,
cameraLastError,
openLocalCamera,
closeLocalCamera,
} = useDeviceState();

async function toggleCamera() {
try {
if (cameraStatus === DeviceStatus.On) {
await closeLocalCamera();
} else {
await openLocalCamera();
}
} catch (error) {
console.error('摄像头操作失败:', error);
handleCameraError(cameraLastError);
}
}

function handleCameraError(error: DeviceError) {
switch (error) {
case DeviceError.NoSystemPermission:
console.error('摄像头权限被拒绝,请在浏览器或系统设置中开启摄像头权限');
break;
case DeviceError.NoDeviceDetected:
console.error('未检测到摄像头设备');
break;
case DeviceError.OccupiedError:
console.error('摄像头被其他应用占用');
break;
case DeviceError.NotSupportCapture:
console.error('当前浏览器不支持摄像头采集');
break;
default:
console.error('摄像头操作失败,请检查设备状态');
break;
}
}
}

开启/关闭麦克风

会议中推荐使用“进房后预采集 + 默认静音”:先调用 muteMicrophone,再调用 openLocalMicrophone 打开采集,避免采集开启但尚未静音导致漏音。用户开麦时调用 unmuteMicrophone,闭麦时调用 muteMicrophone。
说明:
1. 麦克风控制推荐采用“进房后预采集 + 默认静音”的方式有两个好处:
用户点击打开麦克风时,不需要重新处理物理设备采集,响应速度更快。
业务层可以持续检测本地麦克风音量。当用户正在说话但音频未上行时,可以提示“您正在说话,是否打开麦克风?”。
2. closeLocalMicrophone 会关闭麦克风设备采集,适合用户退出房间、结束会议或业务明确需要释放麦克风设备的场景。
Vue3
React
<script setup lang="ts">
import { ref } from 'vue';
import {
DeviceError,
useDeviceState,
useRoomParticipantState,
useRoomState,
} from 'tuikit-atomicx-vue3/room';

const { currentRoom } = useRoomState();
const {
currentMicVolume,
microphoneLastError,
openLocalMicrophone,
} = useDeviceState();
const { muteMicrophone, unmuteMicrophone } = useRoomParticipantState();

const isMicrophoneMuted = ref(true);

async function prepareMicrophoneAfterJoinRoom() {
try {
await muteMicrophone();
await openLocalMicrophone();
isMicrophoneMuted.value = true;
} catch (error) {
console.error('麦克风预采集失败:', error);
handleMicrophoneError(microphoneLastError.value);
}
}

async function openMicrophoneForSpeaking() {
await unmuteMicrophone();
isMicrophoneMuted.value = false;
}

async function closeMicrophoneForSpeaking() {
await muteMicrophone();
isMicrophoneMuted.value = true;
}

function checkSpeakingWhileMuted() {
if (isMicrophoneMuted.value && currentMicVolume.value > 30) {
console.warn('检测到您正在说话,是否打开麦克风?');
}
}

function handleMicrophoneError(error: DeviceError) {
switch (error) {
case DeviceError.NoSystemPermission:
console.error('麦克风权限被拒绝,请在浏览器或系统设置中开启麦克风权限');
break;
case DeviceError.NoDeviceDetected:
console.error('未检测到麦克风设备');
break;
case DeviceError.OccupiedError:
console.error('麦克风被其他应用占用');
break;
case DeviceError.NotSupportCapture:
console.error('当前浏览器不支持麦克风采集');
break;
default:
console.error('麦克风操作失败,请检查设备状态');
break;
}
}
</script>
import { useState } from 'react';
import {
DeviceError,
useDeviceState,
useRoomParticipantState,
useRoomState,
} from 'tuikit-atomicx-react/room';

function MicrophoneControl() {
const { currentRoom } = useRoomState();
const {
currentMicVolume,
microphoneLastError,
openLocalMicrophone,
} = useDeviceState();
const { muteMicrophone, unmuteMicrophone } = useRoomParticipantState();

const [isMicrophoneMuted, setIsMicrophoneMuted] = useState(true);

async function prepareMicrophoneAfterJoinRoom() {
try {
await muteMicrophone();
await openLocalMicrophone();
setIsMicrophoneMuted(true);
} catch (error) {
console.error('麦克风预采集失败:', error);
handleMicrophoneError(microphoneLastError);
}
}

async function openMicrophoneForSpeaking() {
await unmuteMicrophone();
setIsMicrophoneMuted(false);
}

async function closeMicrophoneForSpeaking() {
await muteMicrophone();
setIsMicrophoneMuted(true);
}

function checkSpeakingWhileMuted() {
if (isMicrophoneMuted && currentMicVolume > 30) {
console.warn('检测到您正在说话,是否打开麦克风?');
}
}

function handleMicrophoneError(error: DeviceError) {
switch (error) {
case DeviceError.NoSystemPermission:
console.error('麦克风权限被拒绝,请在浏览器或系统设置中开启麦克风权限');
break;
case DeviceError.NoDeviceDetected:
console.error('未检测到麦克风设备');
break;
case DeviceError.OccupiedError:
console.error('麦克风被其他应用占用');
break;
case DeviceError.NotSupportCapture:
console.error('当前浏览器不支持麦克风采集');
break;
default:
console.error('麦克风操作失败,请检查设备状态');
break;
}
}
}

场景三:构建设备设置面板

用户需要在会议前或会议中切换设备、调整音量、设置视频分辨率。PC 端和移动端 H5 的设备能力差异较大,建议按终端分别设计设置面板。

PC 端设备设置面板

PC 端通常可以提供完整的音频设置和视频设置,包括设备列表、设备切换、音量调节、分辨率切换和摄像头预览。

音频设置

设置项
使用接口/状态
处理建议
麦克风列表
getMicrophoneList、microphoneList、currentMicrophone
进入设置面板时获取,设备热插拔后响应式更新 UI。
切换麦克风
setCurrentMicrophone
用户选择设备后调用。若麦克风已经开启,无 UI SDK 会使用新设备重新采集。
扬声器列表
getSpeakerList、speakerList、currentSpeaker
浏览器不支持时可能为空,需要给出兼容性提示。
切换扬声器
setCurrentSpeaker
切换失败时提示用户到系统声音设置中切换。
采集音量
captureVolume、setCaptureVolume
范围 0-100,可对应音量滑块。
播放音量
outputVolume、setOutputVolume
范围 0-100,可对应音量滑块。
实时麦克风音量
currentMicVolume
会中可用于音量条,也可用于闭麦说话提醒。
Vue3
React
<script setup lang="ts">
import { onMounted } from 'vue';
import { useDeviceState } from 'tuikit-atomicx-vue3/room';

const {
getMicrophoneList,
getSpeakerList,
setCurrentMicrophone,
setCurrentSpeaker,
setCaptureVolume,
setOutputVolume,
} = useDeviceState();

onMounted(async () => {
await getMicrophoneList();
await getSpeakerList();
});

async function switchMicrophone(deviceId: string) {
await setCurrentMicrophone({ deviceId });
}

async function switchSpeaker(deviceId: string) {
await setCurrentSpeaker({ deviceId });
}

async function updateCaptureVolume(volume: number) {
await setCaptureVolume(volume);
}

async function updateOutputVolume(volume: number) {
await setOutputVolume(volume);
}
</script>
import { useEffect } from 'react';
import { useDeviceState } from 'tuikit-atomicx-react/room';

function AudioSettings() {
const {
getMicrophoneList,
getSpeakerList,
setCurrentMicrophone,
setCurrentSpeaker,
setCaptureVolume,
setOutputVolume,
} = useDeviceState();

useEffect(() => {
const init = async () => {
await getMicrophoneList();
await getSpeakerList();
};
init();
}, []);

async function switchMicrophone(deviceId: string) {
await setCurrentMicrophone({ deviceId });
}

async function switchSpeaker(deviceId: string) {
await setCurrentSpeaker({ deviceId });
}

async function updateCaptureVolume(volume: number) {
await setCaptureVolume(volume);
}

async function updateOutputVolume(volume: number) {
await setOutputVolume(volume);
}
}
说明:
扬声器设备切换依赖浏览器能力。部分浏览器可能无法获取扬声器列表或无法切换播放设备,建议在列表为空或切换失败时提示用户使用系统声音设置切换输出设备。

视频设置

设置项
使用接口/状态
处理建议
摄像头列表
getCameraList、cameraList、currentCamera
进入设置面板时获取,PC 端可展示完整设备列表。
切换摄像头
setCurrentCamera
用户选择设备后调用。若摄像头已经开启,会切换到新设备采集。
视频分辨率
localVideoQuality、updateVideoQuality、VideoQuality
建议设置默认值为 VideoQuality.Quality720P。
本地镜像
localMirrorType、switchMirror、MirrorType
可提供自动、开启、关闭三个选项。
摄像头预览
startCameraTest、stopCameraTest
仅在会前或设置面板预览场景使用,离开面板时停止测试。
Vue3
React
<script setup lang="ts">
import { onMounted } from 'vue';
import {
MirrorType,
VideoQuality,
useDeviceState,
} from 'tuikit-atomicx-vue3/room';

const {
getCameraList,
setCurrentCamera,
updateVideoQuality,
switchMirror,
} = useDeviceState();

onMounted(async () => {
await getCameraList();
});

async function switchCamera(deviceId: string) {
await setCurrentCamera({ deviceId });
}

async function setVideoQuality(quality: VideoQuality) {
await updateVideoQuality({ quality });
}

async function setMirror(mirror: MirrorType) {
await switchMirror({ mirror });
}
</script>
import { useEffect } from 'react';
import {
MirrorType,
VideoQuality,
useDeviceState,
} from 'tuikit-atomicx-react/room';

function VideoSettings() {
const {
getCameraList,
setCurrentCamera,
updateVideoQuality,
switchMirror,
} = useDeviceState();

useEffect(() => {
getCameraList();
}, []);

async function switchCamera(deviceId: string) {
await setCurrentCamera({ deviceId });
}

async function setVideoQuality(quality: VideoQuality) {
await updateVideoQuality({ quality });
}

async function setMirror(mirror: MirrorType) {
await switchMirror({ mirror });
}
}

H5 设备设置面板

移动端 H5 设备能力受系统和浏览器限制较多,建议设计更轻量的设置入口。

H5 音频设置

H5 建议只保留麦克风开关和必要的错误提示,不展示麦克风设备列表、扬声器设备列表或扬声器切换入口。会中开麦/闭麦仍建议沿用“预采集 + 静音”的方式。
Vue3
React
<script setup lang="ts">
import { ref } from 'vue';
import { useDeviceState, useRoomParticipantState } from 'tuikit-atomicx-vue3/room';

const { openLocalMicrophone } = useDeviceState();
const { muteMicrophone, unmuteMicrophone } = useRoomParticipantState();

const isMicrophoneMuted = ref(true);

async function prepareH5Microphone() {
await muteMicrophone();
await openLocalMicrophone();
isMicrophoneMuted.value = true;
}

async function toggleH5MicrophoneForSpeaking() {
if (isMicrophoneMuted.value) {
await unmuteMicrophone();
isMicrophoneMuted.value = false;
} else {
await muteMicrophone();
isMicrophoneMuted.value = true;
}
}
</script
import { useState } from 'react';
import { useDeviceState, useRoomParticipantState } from 'tuikit-atomicx-react/room';

function H5AudioSettings() {
const { openLocalMicrophone } = useDeviceState();
const { muteMicrophone, unmuteMicrophone } = useRoomParticipantState();

const [isMicrophoneMuted, setIsMicrophoneMuted] = useState(true);

async function prepareH5Microphone() {
await muteMicrophone();
await openLocalMicrophone();
setIsMicrophoneMuted(true);
}

async function toggleH5MicrophoneForSpeaking() {
if (isMicrophoneMuted) {
await unmuteMicrophone();
setIsMicrophoneMuted(false);
} else {
await muteMicrophone();
setIsMicrophoneMuted(true);
}
}
}

H5 视频设置

H5 视频设置建议保留摄像头开关、前后置摄像头切换和少量分辨率选项。
Vue3
React
<script setup lang="ts">
import {
DeviceStatus,
VideoQuality,
useDeviceState,
} from 'tuikit-atomicx-vue3/room';

const {
cameraStatus,
isFrontCamera,
openLocalCamera,
closeLocalCamera,
switchCamera,
updateVideoQuality,
} = useDeviceState();

async function toggleH5Camera() {
if (cameraStatus.value === DeviceStatus.On) {
await closeLocalCamera();
} else {
await openLocalCamera();
}
}

async function switchH5Camera() {
await switchCamera({ isFrontCamera: !isFrontCamera.value });
}

async function setSmoothQuality() {
await updateVideoQuality({ quality: VideoQuality.Quality360P });
}

async function setHighQuality() {
await updateVideoQuality({ quality: VideoQuality.Quality720P });
}
</script>
import {
DeviceStatus,
VideoQuality,
useDeviceState,
} from 'tuikit-atomicx-react/room';

function H5VideoSettings() {
const {
cameraStatus,
isFrontCamera,
openLocalCamera,
closeLocalCamera,
switchCamera,
updateVideoQuality,
} = useDeviceState();

async function toggleH5Camera() {
if (cameraStatus === DeviceStatus.On) {
await closeLocalCamera();
} else {
await openLocalCamera();
}
}

async function switchH5Camera() {
await switchCamera({ isFrontCamera: !isFrontCamera });
}

async function setSmoothQuality() {
await updateVideoQuality({ quality: VideoQuality.Quality360P });
}

async function setHighQuality() {
await updateVideoQuality({ quality: VideoQuality.Quality720P });
}
}

场景四:结合会控实现设备权限管理

在会议、在线课堂、医疗会诊等场景中,房主或管理员可能需要禁用全员设备、处理开麦/开摄像头申请,或邀请成员开启设备。该能力需要结合 useRoomParticipantState 和 useDeviceState 使用,前者负责权限控制、申请、邀请和事件监听,后者负责真正打开或关闭本地设备。

房主/管理员禁用全体设备

房主或管理员可以通过 disableAllDevices 禁用或启用全体成员的麦克风、摄像头或屏幕分享。禁用全体设备后,普通成员不能自行开启对应设备,房主和管理员不受该限制。
Vue3
React
import {
DeviceType,
useRoomParticipantState,
} from 'tuikit-atomicx-vue3/room';

const { disableAllDevices } = useRoomParticipantState();

async function disableAllMicrophones() {
await disableAllDevices({
deviceType: DeviceType.Microphone,
disable: true,
});
}

async function enableAllMicrophones() {
await disableAllDevices({
deviceType: DeviceType.Microphone,
disable: false,
});
}

async function disableAllCameras() {
await disableAllDevices({
deviceType: DeviceType.Camera,
disable: true,
});
}
import {
DeviceType,
useRoomParticipantState,
} from 'tuikit-atomicx-react/room';

function AdminDeviceControl() {
const { disableAllDevices } = useRoomParticipantState();

async function disableAllMicrophones() {
await disableAllDevices({
deviceType: DeviceType.Microphone,
disable: true,
});
}

async function enableAllMicrophones() {
await disableAllDevices({
deviceType: DeviceType.Microphone,
disable: false,
});
}

async function disableAllCameras() {
await disableAllDevices({
deviceType: DeviceType.Camera,
disable: true,
});
}
}

普通成员申请开启设备

当房间处于全员禁麦或禁摄像头状态时,普通成员点击开麦或开摄像头,应先向房主和管理员申请。
Vue3
React
<script setup lang="ts">
import { onBeforeUnmount, onMounted } from 'vue';
import {
DeviceType,
RoomParticipantRole,
RoomParticipantEvent,
useDeviceState,
useRoomParticipantState,
useRoomState,
} from 'tuikit-atomicx-vue3/room';

const { currentRoom } = useRoomState();
const { openLocalMicrophone } = useDeviceState();
const {
localParticipant,
unmuteMicrophone,
requestToOpenDevice,
subscribeEvent,
unsubscribeEvent,
} = useRoomParticipantState();

async function handleOpenMicrophone() {
if (currentRoom.value?.isAllMicrophoneDisabled && localParticipant.value.role === RoomParticipantRole.GeneralUser) {
await requestToOpenDevice({
device: DeviceType.Microphone,
timeout: 60,
});
console.log('已向管理员发送开启麦克风申请');
return;
}
}

async function onDeviceRequestApproved({ request }: { request: { deviceType: DeviceType } }) {
if (request.deviceType === DeviceType.Microphone) {
await openLocalMicrophone();
await unmuteMicrophone();
} else if (request.deviceType === DeviceType.Camera) {
await openLocalCamera();
}
}

onMounted(() => {
subscribeEvent(RoomParticipantEvent.onDeviceRequestApproved, onDeviceRequestApproved);
});

onBeforeUnmount(() => {
unsubscribeEvent(RoomParticipantEvent.onDeviceRequestApproved, onDeviceRequestApproved);
});
</script>
import { useEffect } from 'react';
import {
DeviceType,
RoomParticipantRole,
RoomParticipantEvent,
useDeviceState,
useRoomParticipantState,
useRoomState,
} from 'tuikit-atomicx-react/room';

function MemberDeviceRequest() {
const { currentRoom } = useRoomState();
const { openLocalMicrophone } = useDeviceState();
const {
localParticipant,
unmuteMicrophone,
requestToOpenDevice,
subscribeEvent,
unsubscribeEvent,
} = useRoomParticipantState();

async function handleOpenMicrophone() {
if (currentRoom?.isAllMicrophoneDisabled && localParticipant.role === RoomParticipantRole.GeneralUser) {
await requestToOpenDevice({
device: DeviceType.Microphone,
timeout: 60,
});
console.log('已向管理员发送开启麦克风申请');
return;
}
}

async function onDeviceRequestApproved({ request }: { request: { deviceType: DeviceType } }) {
if (request.deviceType === DeviceType.Microphone) {
await openLocalMicrophone();
await unmuteMicrophone();
} else if (request.deviceType === DeviceType.Camera) {
await openLocalCamera();
}
}

useEffect(() => {
subscribeEvent(RoomParticipantEvent.onDeviceRequestApproved, onDeviceRequestApproved);
return () => {
unsubscribeEvent(RoomParticipantEvent.onDeviceRequestApproved, onDeviceRequestApproved);
};
}, []);
}

管理员处理设备开启申请

管理员可以监听 RoomParticipantEvent.onDeviceRequestReceived,收到普通成员的设备开启申请后,调用 approveOpenDeviceRequest 或 rejectOpenDeviceRequest 处理。
Vue3
React
<script setup lang="ts">
import { onMounted, onUnmounted } from 'vue';
import {
DeviceType,
RoomParticipantEvent,
useRoomParticipantState,
} from 'tuikit-atomicx-vue3/room';
import { TUIMessageBox } from '@tencentcloud/uikit-base-component-vue3';

type DeviceRequest = {
deviceType: DeviceType;
sender: {
userId: string;
userName?: string;
};
};

const {
subscribeEvent,
unsubscribeEvent,
approveOpenDeviceRequest,
rejectOpenDeviceRequest,
} = useRoomParticipantState();

function getDeviceName(device: DeviceType) {
if (device === DeviceType.Microphone) return '麦克风';
if (device === DeviceType.Camera) return '摄像头';
return '屏幕分享';
}

function onDeviceRequestReceived({ request }: { request: DeviceRequest }) {
TUIMessageBox.confirm({
title: '设备开启申请',
content: `${request.sender.userName || request.sender.userId} 申请开启${getDeviceName(request.deviceType)}`,
callback: async (action) => {
if (action === 'confirm') {
await approveOpenDeviceRequest({
userId: request.sender.userId,
device: request.deviceType,
});
} else {
await rejectOpenDeviceRequest({
userId: request.sender.userId,
device: request.deviceType,
});
}
},
});
}

onMounted(() => {
subscribeEvent(RoomParticipantEvent.onDeviceRequestReceived, onDeviceRequestReceived);
});

onUnmounted(() => {
unsubscribeEvent(RoomParticipantEvent.onDeviceRequestReceived, onDeviceRequestReceived);
});
</script>
import { useEffect } from 'react';
import {
DeviceType,
RoomParticipantEvent,
useRoomParticipantState,
} from 'tuikit-atomicx-react/room';
import { TUIMessageBox } from '@tencentcloud/uikit-base-component-react';

type DeviceRequest = {
deviceType: DeviceType;
sender: {
userId: string;
userName?: string;
};
};

function AdminDeviceRequestHandler() {
const {
subscribeEvent,
unsubscribeEvent,
approveOpenDeviceRequest,
rejectOpenDeviceRequest,
} = useRoomParticipantState();

function getDeviceName(device: DeviceType) {
if (device === DeviceType.Microphone) return '麦克风';
if (device === DeviceType.Camera) return '摄像头';
return '屏幕分享';
}

function onDeviceRequestReceived({ request }: { request: DeviceRequest }) {
TUIMessageBox.confirm({
title: '设备开启申请',
content: `${request.sender.userName || request.sender.userId} 申请开启${getDeviceName(request.deviceType)}`,
callback: async (action) => {
if (action === 'confirm') {
await approveOpenDeviceRequest({
userId: request.sender.userId,
device: request.deviceType,
});
} else {
await rejectOpenDeviceRequest({
userId: request.sender.userId,
device: request.deviceType,
});
}
},
});
}

useEffect(() => {
subscribeEvent(RoomParticipantEvent.onDeviceRequestReceived, onDeviceRequestReceived);
return () => {
unsubscribeEvent(RoomParticipantEvent.onDeviceRequestReceived, onDeviceRequestReceived);
};
}, []);
}

管理员邀请成员开启设备

管理员也可以邀请指定成员开启麦克风、摄像头或屏幕分享。邀请不是强制开启,成员接受后才会打开本地设备。
说明:
邀请开启设备需要尊重用户授权。即使用户接受邀请,浏览器仍可能弹出系统权限确认;如果用户拒绝授权,设备仍无法打开。
Vue3
React
import { onMounted, onUnmounted } from 'vue';
import {
DeviceType,
RoomParticipantEvent,
useDeviceState,
useRoomParticipantState,
} from 'tuikit-atomicx-vue3/room';

const { openLocalMicrophone, openLocalCamera } = useDeviceState();
const {
inviteToOpenDevice,
acceptOpenDeviceInvitation,
declineOpenDeviceInvitation,
subscribeEvent,
unsubscribeEvent,
} = useRoomParticipantState();

async function inviteUserToOpenMicrophone(userId: string) {
await inviteToOpenDevice({
userId,
device: DeviceType.Microphone,
timeout: 60,
});
}

async function onDeviceInvitationReceived({
invitation,
}: {
invitation: {
sender: { userId: string };
deviceType: DeviceType;
};
}) {
const shouldAccept = window.confirm('管理员邀请您开启设备,是否同意?');
if (!shouldAccept) {
await declineOpenDeviceInvitation({
userId: invitation.sender.userId,
device: invitation.deviceType,
});
return;
}

await acceptOpenDeviceInvitation({
userId: invitation.sender.userId,
device: invitation.deviceType,
});

if (invitation.deviceType === DeviceType.Microphone) {
await openLocalMicrophone();
}
if (invitation.deviceType === DeviceType.Camera) {
await openLocalCamera();
}
}

onMounted(() => {
subscribeEvent(RoomParticipantEvent.onDeviceInvitationReceived, onDeviceInvitationReceived);
});

onUnmounted(() => {
unsubscribeEvent(RoomParticipantEvent.onDeviceInvitationReceived, onDeviceInvitationReceived);
});
import { useEffect } from 'react';
import {
DeviceType,
RoomParticipantEvent,
useDeviceState,
useRoomParticipantState,
} from 'tuikit-atomicx-react/room';

function DeviceInvitationHandler() {
const { openLocalMicrophone, openLocalCamera } = useDeviceState();
const {
inviteToOpenDevice,
acceptOpenDeviceInvitation,
declineOpenDeviceInvitation,
subscribeEvent,
unsubscribeEvent,
} = useRoomParticipantState();

async function inviteUserToOpenMicrophone(userId: string) {
await inviteToOpenDevice({
userId,
device: DeviceType.Microphone,
timeout: 60,
});
}

async function onDeviceInvitationReceived({
invitation,
}: {
invitation: {
sender: { userId: string };
deviceType: DeviceType;
};
}) {
const shouldAccept = window.confirm('管理员邀请您开启设备,是否同意?');
if (!shouldAccept) {
await declineOpenDeviceInvitation({
userId: invitation.sender.userId,
device: invitation.deviceType,
});
return;
}

await acceptOpenDeviceInvitation({
userId: invitation.sender.userId,
device: invitation.deviceType,
});

if (invitation.deviceType === DeviceType.Microphone) {
await openLocalMicrophone();
}
if (invitation.deviceType === DeviceType.Camera) {
await openLocalCamera();
}
}

useEffect(() => {
subscribeEvent(RoomParticipantEvent.onDeviceInvitationReceived, onDeviceInvitationReceived);
return () => {
unsubscribeEvent(RoomParticipantEvent.onDeviceInvitationReceived, onDeviceInvitationReceived);
};
}, []);
}

场景五:网络质量监控

会议中需要向用户展示本地网络质量,例如“网络良好”“网络较差”“连接异常”,并在需要时展示延迟和丢包率等详细信息。

实现思路

使用 useDeviceState 中的 networkInfo 获取本地用户网络状态。
说明:
仅当本地用户通过 joinRoom 或 createAndJoinRoom 加入房间,并且打开麦克风或摄像头后,networkInfo 中才可以拿到有效数据。未入会或未开启音视频采集时,建议展示“未知”或不展示网络状态。
字段
含义
展示建议
quality
网络质量等级。
可映射为“优秀、良好、一般、差、很差、断开”。
delay
网络延迟,单位 ms。
可在详情面板中展示。
upLoss
上行丢包率。
可在详情面板中展示。
downLoss
下行丢包率。
可在详情面板中展示。
Vue3
React
<script setup lang="ts">
import { computed } from 'vue';
import { NetworkQuality, useDeviceState } from 'tuikit-atomicx-vue3/room';

const { networkInfo } = useDeviceState();

const networkText = computed(() => {
switch (networkInfo.value?.quality) {
case NetworkQuality.Excellent:
return '优秀';
case NetworkQuality.Good:
return '良好';
case NetworkQuality.Poor:
return '一般';
case NetworkQuality.Bad:
return '差';
case NetworkQuality.VeryBad:
return '很差';
case NetworkQuality.Down:
return '断开';
default:
return '未知';
}
});
</script>
import { useMemo } from 'react';
import { NetworkQuality, useDeviceState } from 'tuikit-atomicx-react/room';

function NetworkStatus() {
const { networkInfo } = useDeviceState();

const networkText = useMemo(() => {
switch (networkInfo?.quality) {
case NetworkQuality.Excellent:
return '优秀';
case NetworkQuality.Good:
return '良好';
case NetworkQuality.Poor:
return '一般';
case NetworkQuality.Bad:
return '差';
case NetworkQuality.VeryBad:
return '很差';
case NetworkQuality.Down:
return '断开';
default:
return '未知';
}
}, [networkInfo]);
}

错误码与异常处理

设备操作失败时,您可以通过 cameraLastError 和 microphoneLastError 获取最近一次摄像头或麦克风错误。

错误码枚举

错误枚举
含义
常见触发场景
建议处理方式
DeviceError.NoError
无错误。
设备操作成功。
无需处理。
DeviceError.NoSystemPermission
系统或浏览器权限未开启。
用户拒绝摄像头/麦克风权限,或系统权限关闭。
提示用户在浏览器或系统设置中开启权限。
DeviceError.NoDeviceDetected
未检测到设备。
电脑未连接摄像头或麦克风,设备被系统禁用。
提示用户检查设备连接或系统设备设置。
DeviceError.OccupiedError
设备被占用。
摄像头或麦克风正在被其他应用使用。
提示用户关闭其他占用设备的应用后重试。
DeviceError.NotSupportCapture
当前环境不支持采集。
浏览器不支持 WebRTC 采集能力,或当前环境受限。
提示用户更换浏览器或设备环境。
DeviceError.UnknownError
未知错误。
非预期设备错误。
提示用户重试,并收集日志排查。

统一错误处理示例

Vue3
React
import { DeviceError } from 'tuikit-atomicx-vue3/room';

function getDeviceErrorMessage(error: DeviceError, deviceName: '摄像头' | '麦克风') {
switch (error) {
case DeviceError.NoSystemPermission:
return `${deviceName}权限被拒绝,请在浏览器或系统设置中开启权限`;
case DeviceError.NoDeviceDetected:
return `未检测到${deviceName}设备,请检查设备连接`;
case DeviceError.OccupiedError:
return `${deviceName}被其他应用占用,请关闭相关应用后重试`;
case DeviceError.NotSupportCapture:
return `当前浏览器不支持${deviceName}采集,请更换浏览器`;
case DeviceError.UnknownError:
return `${deviceName}操作失败,请稍后重试`;
default:
return '';
}
}
import { DeviceError } from 'tuikit-atomicx-react/room';

function getDeviceErrorMessage(error: DeviceError, deviceName: '摄像头' | '麦克风') {
switch (error) {
case DeviceError.NoSystemPermission:
return `${deviceName}权限被拒绝,请在浏览器或系统设置中开启权限`;
case DeviceError.NoDeviceDetected:
return `未检测到${deviceName}设备,请检查设备连接`;
case DeviceError.OccupiedError:
return `${deviceName}被其他应用占用,请关闭相关应用后重试`;
case DeviceError.NotSupportCapture:
return `当前浏览器不支持${deviceName}采集,请更换浏览器`;
case DeviceError.UnknownError:
return `${deviceName}操作失败,请稍后重试`;
default:
return '';
}
}

API 文档

State/Component
功能描述
API 文档
useDeviceState
包含音视频设备状态、设备列表、设备操作接口和本地网络质量数据。
useRoomParticipantState
包含成员角色、成员状态、会控能力、设备申请和设备邀请能力。
useRoomState
包含当前房间状态,可用于判断用户是否已经入会以及房间会控状态。

常见问题

本地开发时音视频功能正常,但部署到线上环境后无法采集摄像头或麦克风?

浏览器出于安全和隐私保护的考虑,对音视频设备采集有严格限制。只有在安全环境下,采集操作才会被允许。安全环境协议包括 https://、localhost、file:// 等。HTTP 协议被视为不安全,浏览器会默认禁止其访问媒体设备。
如果本地测试正常,但部署后出现采集失败,请检查页面是否部署在 HTTP 协议上。必须使用 HTTPS 协议部署页面,并确保具备有效的安全证书。更多关于 URL 域名及协议的限制详情,请参见 URL 域名及协议限制说明。

为什么移动端 H5 不能切换麦克风或扬声器?

移动端浏览器通常不开放完整的音频输入和输出设备选择能力,因此 H5 不建议提供麦克风设备列表和扬声器设备列表。建议移动端只保留麦克风开关、摄像头开关、前后置摄像头切换等核心入口。

为什么扬声器列表为空或切换失败?

扬声器设备枚举和切换依赖浏览器能力。部分浏览器可能不支持播放设备选择,或者需要用户授权后才能返回完整设备信息。当扬声器列表为空或切换失败时,建议提示用户通过系统声音设置切换输出设备。

为什么 networkInfo 没有网络质量数据?

networkInfo 仅在用户已经加入房间,并且打开摄像头或麦克风后才会返回有效数据。未入会、未开启音视频采集或网络质量尚未上报时,建议展示“未知”或不展示网络状态。

如果用户的电脑无麦克风、扬声器或摄像头,是否可以进入音视频房间?

可以。用户即使缺少麦克风、扬声器或摄像头,也可以调用 joinRoom 进入房间,但会受到以下限制:
无法开启媒体采集:调用 openLocalMicrophone 或 openLocalCamera 会触发异步错误,microphoneLastError 或 cameraLastError 返回 DeviceError.NoDeviceDetected。
采集端受限:无法通过本端设备采集并传输声音或画面。
播放端受限:如果缺少扬声器设备,用户将无法听到房间内其他成员的音频。
如果业务支持仅观看模式,用户仍可以观看远端视频流或屏幕共享画面。

如何实现设备热插拔检测?

无 UI SDK 已经内置设备热插拔检测能力。当媒体设备插拔时,设备列表和当前设备数据会自动更新。业务层只需要基于 cameraList、microphoneList、speakerList、currentCamera、currentMicrophone、currentSpeaker 等响应式数据更新 UI。

是否支持使用 iframe 集成?

支持。使用 iframe 集成无 UI SDK 时,需要在 iframe 标签中配置 allow 属性以授予必要的浏览器权限。
<iframe allow="microphone; camera; display-capture; fullscreen;"></iframe>