一、功能介绍
设备预览是保障音视频房间顺利运行的重要环节。Atomicx 专为进房前场景提供了一套独立的测试接口,其核心能力包括:
·视频画面预览:在不推流的情况下启动摄像头,验证画面清晰度与光线。
·麦克风检测:通过实时音量反馈,确认麦克风采集是否正常。
·扬声器校准:通过播放测试音,确认输出设备及其音量设置。
二、前提条件
·用户已经通过 useLoginState 完成登录鉴权,请参考 接入概览。
三、步骤1:摄像头测试
使用 startCameraTest 方法启动摄像头测试。该方法仅用于本地预览,不会产生网络流量。
代码示例:
Vue3React<template> <div class="preview-container"> <!– 摄像头预览区域 –> <div id="camera-preview" class="camera-preview"> <!– 加载状态 –> <div v-if="isCameraTestLoading" class="loading-overlay"> <div class="loading-spinner" /> <span>正在启动摄像头…</span> </div> </div> <!– 摄像头选择 –> <div class="camera-selector"> <label>选择摄像头</label> <select :value="currentCamera?.deviceId" @change="handleCameraChange" :disabled="isCameraTestLoading" > <option v-for="camera in cameraList" :key="camera.deviceId" :value="camera.deviceId" > {{ camera.deviceName }} </option> </select> </div> <!– 控制按钮 –> <div class="control-buttons"> <button @click="getCameraList">获取摄像头列表</button> <button @click="toggleCameraTest" :disabled="isCameraTestLoading" > {{ isCameraTesting ? '停止预览' : '开始预览' }} </button> </div> </div></template><script setup lang="ts">import { ref, computed, watch, onMounted, onUnmounted } from 'vue';import { IconWarning } from '@tencentcloud/uikit-base-component-vue3';import { useLoginState, useDeviceState, DeviceStatus, DeviceError,} from 'tuikit-atomicx-vue3/room';const { isCameraTesting, isCameraTestLoading, cameraList, currentCamera, startCameraTest, stopCameraTest, getCameraList, setCurrentCamera} = useDeviceState();onUnmounted(() => { if (isCameraTesting.value) { stopCameraTest(); }});const toggleCameraTest = async () => { if (isCameraTesting.value) { await stopCameraTest(); } else { const previewElement = document.getElementById('camera-preview'); if (previewElement) { await startCameraTest({ view: previewElement }); } }};const handleCameraChange = async (event: Event) => { const deviceId = (event.target as HTMLSelectElement).value; try { await setCurrentCamera({ deviceId }); } catch (error) { console.error('切换摄像头失败:', error); }};</script><style scoped>.preview-container { position: relative; width: 100%; max-width: 640px; margin: 0 auto;}.camera-preview { width: 100%; position: relative; aspect-ratio: 16 / 9; background-color: #000; border-radius: 8px; overflow: hidden;}.loading-overlay { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 12px; background-color: rgba(0, 0, 0, 0.7); color: white; border-radius: 8px;}.loading-spinner { width: 40px; height: 40px; border: 4px solid rgba(255, 255, 255, 0.3); border-top-color: white; border-radius: 50%; animation: spin 0.8s linear infinite;}@keyframes spin { to { transform: rotate(360deg); }}.error-message { display: flex; align-items: center; gap: 8px; padding: 12px; margin-top: 12px; background-color: #ffebee; color: #c62828; border-radius: 4px; font-size: 14px;}.camera-selector { margin-top: 16px; display: flex; align-items: center; gap: 12px;}.camera-selector label { font-weight: 500; min-width: 80px;}.camera-selector select { flex: 1; padding: 8px 12px; border: 1px solid #e0e0e0; border-radius: 4px; font-size: 14px;}.control-buttons { margin-top: 16px; display: flex; gap: 12px;}.control-buttons button { flex: 1; padding: 12px 24px; background-color: #1976d2; color: white; border: none; border-radius: 4px; font-size: 14px; font-weight: 500; cursor: pointer; transition: background-color 0.3s;}.control-buttons button:hover:not(:disabled) { background-color: #1565c0;}.control-buttons button:disabled { background-color: #bdbdbd; cursor: not-allowed;}</style>import React, { useEffect } from 'react';import { useDeviceState, DeviceStatus, DeviceError,} from 'tuikit-atomicx-react/room';const CameraTest: React.FC = () => { const { isCameraTesting, isCameraTestLoading, cameraList, currentCamera, startCameraTest, stopCameraTest, getCameraList, setCurrentCamera } = useDeviceState(); useEffect(() => { return () => { if (isCameraTesting) { stopCameraTest(); } }; }, []); const toggleCameraTest = async () => { if (isCameraTesting) { await stopCameraTest(); } else { const previewElement = document.getElementById('camera-preview'); if (previewElement) { await startCameraTest({ view: previewElement }); } } }; const handleCameraChange = async (event: React.ChangeEvent<HTMLSelectElement>) => { const deviceId = event.target.value; try { await setCurrentCamera({ deviceId }); } catch (error) { console.error('切换摄像头失败:', error); } }; return ( <div className="preview-container"> {/* 摄像头预览区域 */} <div id="camera-preview" className="camera-preview"> {/* 加载状态 */} {isCameraTestLoading && ( <div className="loading-overlay"> <div className="loading-spinner" /> <span>正在启动摄像头…</span> </div> )} </div> {/* 摄像头选择 */} <div className="camera-selector"> <label>选择摄像头</label> <select value={currentCamera?.deviceId} onChange={handleCameraChange} disabled={isCameraTestLoading} > {cameraList.map(camera => ( <option key={camera.deviceId} value={camera.deviceId}> {camera.deviceName} </option> ))} </select> </div> {/* 控制按钮 */} <div className="control-buttons"> <button onClick={getCameraList}>获取摄像头列表</button> <button onClick={toggleCameraTest} disabled={isCameraTestLoading} > {isCameraTesting ? '停止预览' : '开始预览'} </button> </div> </div> );};export default CameraTest;
四、步骤2:麦克风测试
通过 startMicrophoneTest 启动麦克风测试,并利用 testingMicVolume 实现 UI 音量条的实时波动。
代码示例:
Vue3React<template> <div class="microphone-test"> <!– 音量显示 –> <div class="volume-display"> <div class="volume-label">麦克风音量</div> <div class="volume-bar-container"> <div class="volume-bar" :style="{ width: `${testingMicVolume}%` }" :class="volumeClass" /> </div> <div class="volume-value">{{ testingMicVolume }}</div> </div> <!– 麦克风选择 –> <div class="microphone-selector"> <label>选择麦克风</label> <select :value="currentMicrophone?.deviceId" @change="handleMicrophoneChange" :disabled="isMicrophoneTesting" > <option v-for="mic in microphoneList" :key="mic.deviceId" :value="mic.deviceId" > {{ mic.deviceName }} </option> </select> </div> <!– 控制按钮 –> <div class="control-buttons"> <button @click="getMicrophoneList" > 获取设备 </button> <button @click="toggleMicrophoneTest" > {{ isMicrophoneTesting ? '停止测试' : '开始测试' }} </button> </div> <!– 测试提示 –> <div v-if="isMicrophoneTesting" class="test-tip"> <span>请对着麦克风说话,查看音量变化</span> </div> </div></template><script setup lang="ts">import { ref, computed, watch, onMounted, onUnmounted } from 'vue';import { useDeviceState, DeviceError,} from 'tuikit-atomicx-vue3/room';const { isMicrophoneTesting, testingMicVolume, microphoneList, currentMicrophone, startMicrophoneTest, stopMicrophoneTest, getMicrophoneList, setCurrentMicrophone} = useDeviceState();// 音量条样式类const volumeClass = computed(() => { const volume = testingMicVolume.value; if (volume === 0) return 'volume-zero'; if (volume < 30) return 'volume-low'; if (volume < 70) return 'volume-medium'; return 'volume-high';});// 清理:停止测试onUnmounted(() => { if (isMicrophoneTesting.value) { stopMicrophoneTest(); }});// 切换麦克风测试const toggleMicrophoneTest = async () => { if (isMicrophoneTesting.value) { await stopMicrophoneTest(); } else { try { await startMicrophoneTest({ interval: 200 }); } catch (error) { console.error('启动麦克风测试失败:', error); } }};// 切换麦克风设备const handleMicrophoneChange = async (event: Event) => { const deviceId = (event.target as HTMLSelectElement).value; try { await setCurrentMicrophone({ deviceId }); } catch (error) { console.error('切换麦克风失败:', error); }};</script><style scoped>.microphone-test { width: 100%; max-width: 640px; margin: 0 auto;}.volume-display { padding: 24px; background-color: #f5f5f5; border-radius: 8px; margin-bottom: 16px;}.volume-label { font-size: 14px; font-weight: 500; color: #666; margin-bottom: 12px;}.volume-bar-container { width: 100%; height: 24px; background-color: #e0e0e0; border-radius: 12px; overflow: hidden; margin-bottom: 8px;}.volume-bar { height: 100%; transition: width 0.1s ease; border-radius: 12px;}.volume-bar.volume-zero { background-color: #9e9e9e;}.volume-bar.volume-low { background-color: #ff9800;}.volume-bar.volume-medium { background-color: #4caf50;}.volume-bar.volume-high { background-color: #2196f3;}.volume-value { text-align: center; font-size: 18px; font-weight: 600; color: #333;}.microphone-selector { display: flex; align-items: center; gap: 12px; margin-bottom: 16px;}.microphone-selector label { font-weight: 500; min-width: 80px;}.microphone-selector select { flex: 1; padding: 8px 12px; border: 1px solid #e0e0e0; border-radius: 4px; font-size: 14px;}.error-message { display: flex; align-items: center; gap: 8px; padding: 12px; margin-bottom: 16px; background-color: #ffebee; color: #c62828; border-radius: 4px; font-size: 14px;}.control-buttons { margin-bottom: 12px; display: flex; gap: 8px; }.control-buttons button { flex: 1; width: 100%; padding: 12px 24px; background-color: #1976d2; color: white; border: none; border-radius: 4px; font-size: 14px; font-weight: 500; cursor: pointer; transition: background-color 0.3s;}.control-buttons button:hover:not(:disabled) { background-color: #1565c0;}.control-buttons button:disabled { background-color: #bdbdbd; cursor: not-allowed;}.test-tip { display: flex; align-items: center; gap: 8px; padding: 12px; background-color: #e3f2fd; color: #1565c0; border-radius: 4px; font-size: 13px;}</style>import React, { useMemo, useEffect } from 'react';import { useDeviceState, DeviceError,} from 'tuikit-atomicx-react/room';const MicrophoneTest: React.FC = () => { const { isMicrophoneTesting, testingMicVolume, microphoneList, currentMicrophone, startMicrophoneTest, stopMicrophoneTest, getMicrophoneList, setCurrentMicrophone } = useDeviceState(); // Volume bar style class const volumeClass = useMemo(() => { const volume = testingMicVolume; if (volume === 0) return 'volume-zero'; if (volume < 30) return 'volume-low'; if (volume < 70) return 'volume-medium'; return 'volume-high'; }, [testingMicVolume]); // Cleanup: stop test useEffect(() => { return () => { if (isMicrophoneTesting) { stopMicrophoneTest(); } }; }, []); // Toggle microphone test const toggleMicrophoneTest = async () => { if (isMicrophoneTesting) { await stopMicrophoneTest(); } else { try { await startMicrophoneTest({ interval: 200 }); } catch (error) { console.error('启动麦克风测试失败:', error); } } }; // Switch microphone device const handleMicrophoneChange = async (event: React.ChangeEvent<HTMLSelectElement>) => { const deviceId = event.target.value; try { await setCurrentMicrophone({ deviceId }); } catch (error) { console.error('切换麦克风失败:', error); } }; return ( <div className="microphone-test"> {/* 音量显示 */} <div className="volume-display"> <div className="volume-label">麦克风音量</div> <div className="volume-bar-container"> <div className={`volume-bar ${volumeClass}`} style={{ width: `${testingMicVolume}%` }} /> </div> <div className="volume-value">{testingMicVolume}</div> </div> {/* 麦克风选择 */} <div className="microphone-selector"> <label>选择麦克风</label> <select value={currentMicrophone?.deviceId} onChange={handleMicrophoneChange} disabled={isMicrophoneTesting} > {microphoneList.map(mic => ( <option key={mic.deviceId} value={mic.deviceId}> {mic.deviceName} </option> ))} </select> </div> {/* 控制按钮 */} <div className="control-buttons"> <button onClick={getMicrophoneList}> 获取设备 </button> <button onClick={toggleMicrophoneTest}> {isMicrophoneTesting ? '停止测试' : '开始测试'} </button> </div> {/* 测试提示 */} {isMicrophoneTesting && ( <div className="test-tip"> <span>请对着麦克风说话,查看音量变化</span> </div> )} </div> );};export default MicrophoneTest;
五、步骤3:扬声器测试
使用 startSpeakerTest 方法播放一段指定的音频文件来确认扬声器是否工作正常。
代码示例:
Vue3React<template> <div class="speaker-test"> <!– 扬声器选择 –> <div class="speaker-selector"> <label>选择扬声器</label> <select :value="currentSpeaker?.deviceId" @change="handleSpeakerChange" :disabled="isSpeakerTesting" > <option v-for="speaker in speakerList" :key="speaker.deviceId" :value="speaker.deviceId" > {{ speaker.deviceName }} </option> </select> </div> <!– 控制按钮 –> <div class="control-buttons"> <button @click="getSpeakerList">获取扬声器列表</button> <button @click="toggleSpeakerTest" :disabled="isSpeakerTesting" > {{ isSpeakerTesting ? '正在播放…' : '播放测试音频' }} </button> <button v-if="isSpeakerTesting" @click="stopSpeakerTest" > 停止播放 </button> </div> <!– 测试提示 –> <div v-if="isSpeakerTesting" class="test-tip"> <span>正在播放测试音频,请确认是否能听到声音</span> </div> </div></template><script setup lang="ts">import { ref, onMounted, onUnmounted } from 'vue';import { useDeviceState } from 'tuikit-atomicx-vue3/room';const { isSpeakerTesting, speakerList, currentSpeaker, startSpeakerTest, stopSpeakerTest, getSpeakerList, setCurrentSpeaker,} = useDeviceState();// 测试音频文件路径// 注意:此 URL 仅供开发测试使用,生产环境请使用你自己的音频文件,建议使用 3-5 秒的 MP3 格式音频文件const testAudioPath = 'https://web.sdk.qcloud.com/trtc/electron/download/resources/media/TestSpeaker.mp3';// 清理:停止测试onUnmounted(() => { if (isSpeakerTesting.value) { stopSpeakerTest(); }});// 切换扬声器测试const toggleSpeakerTest = async () => { if (isSpeakerTesting.value) { await stopSpeakerTest(); } else { try { await startSpeakerTest({ filePath: testAudioPath }); } catch (error) { console.error('启动扬声器测试失败:', error); } }};// 切换扬声器设备const handleSpeakerChange = async (event: Event) => { const deviceId = (event.target as HTMLSelectElement).value; try { await setCurrentSpeaker({ deviceId }); } catch (error) { console.error('切换扬声器失败:', error); }};</script><style scoped>.speaker-test { width: 100%; max-width: 640px; margin: 0 auto;}.speaker-selector { display: flex; align-items: center; gap: 12px; margin-bottom: 24px;}.speaker-selector label { font-weight: 500; min-width: 80px;}.speaker-selector select { flex: 1; padding: 8px 12px; border: 1px solid #e0e0e0; border-radius: 4px; font-size: 14px;}.volume-slider-container { display: flex; align-items: center; gap: 12px;}.volume-slider { flex: 1; height: 6px; border-radius: 3px; background: #e0e0e0; outline: none; -webkit-appearance: none;}.volume-slider::-webkit-slider-thumb { -webkit-appearance: none; width: 18px; height: 18px; border-radius: 50%; background: #1976d2; cursor: pointer;}.volume-slider::-moz-range-thumb { width: 18px; height: 18px; border-radius: 50%; background: #1976d2; cursor: pointer; border: none;}.volume-value { min-width: 50px; text-align: right; font-weight: 500; color: #333;}.control-buttons { display: flex; gap: 12px; margin-bottom: 12px;}button { flex: 1; padding: 12px 24px; background-color: #1976d2; color: white; border: none; border-radius: 4px; font-size: 14px; font-weight: 500; cursor: pointer; transition: background-color 0.3s;}.control-buttons button:hover:not(:disabled) { background-color: #1565c0;}.control-buttons button:disabled { background-color: #bdbdbd; cursor: not-allowed;}.test-tip { display: flex; align-items: center; gap: 8px; padding: 12px; background-color: #e3f2fd; color: #1565c0; border-radius: 4px; font-size: 13px;}</style>import React, { useEffect } from 'react';import { useDeviceState } from 'tuikit-atomicx-react/room';const SpeakerTest: React.FC = () => { const { isSpeakerTesting, speakerList, currentSpeaker, startSpeakerTest, stopSpeakerTest, getSpeakerList, setCurrentSpeaker, } = useDeviceState(); // Test audio file path // Note: This URL is for development testing only. In production, use your own audio file. Recommended: 3-5 second MP3 format. const testAudioPath = 'https://web.sdk.qcloud.com/trtc/electron/download/resources/media/TestSpeaker.mp3'; // Cleanup: stop test useEffect(() => { return () => { if (isSpeakerTesting) { stopSpeakerTest(); } }; }, []); // Toggle speaker test const toggleSpeakerTest = async () => { if (isSpeakerTesting) { await stopSpeakerTest(); } else { try { await startSpeakerTest({ filePath: testAudioPath }); } catch (error) { console.error('启动扬声器测试失败:', error); } } }; // Switch speaker device const handleSpeakerChange = async (event: React.ChangeEvent<HTMLSelectElement>) => { const deviceId = event.target.value; try { await setCurrentSpeaker({ deviceId }); } catch (error) { console.error('切换扬声器失败:', error); } }; return ( <div className="speaker-test"> {/* 扬声器选择 */} <div className="speaker-selector"> <label>选择扬声器</label> <select value={currentSpeaker?.deviceId} onChange={handleSpeakerChange} disabled={isSpeakerTesting} > {speakerList.map(speaker => ( <option key={speaker.deviceId} value={speaker.deviceId}> {speaker.deviceName} </option> ))} </select> </div> {/* 控制按钮 */} <div className="control-buttons"> <button onClick={getSpeakerList}>获取扬声器列表</button> <button onClick={toggleSpeakerTest} disabled={isSpeakerTesting} > {isSpeakerTesting ? '正在播放…' : '播放测试音频'} </button> {isSpeakerTesting && ( <button onClick={stopSpeakerTest}> 停止播放 </button> )} </div> {/* 测试提示 */} {isSpeakerTesting && ( <div className="test-tip"> <span>正在播放测试音频,请确认是否能听到声音</span> </div> )} </div> );};export default SpeakerTest;
六、API 文档
| State/Component | 功能描述 | API 文档 |
| useDeviceState | 包含音视频设备状态,音视频设备列表及操作接口。 | API 文档 |
七、为什么摄像头预览没有画面?
可能的原因包括:
·权限被拒绝:检查浏览器是否允许摄像头权限。
·设备被占用:关闭其他使用摄像头的应用。
·设备未连接:检查摄像头是否正确连接。
·浏览器不支持:使用 Chrome、Edge 等现代浏览器。
八、麦克风测试时音量一直为 0?
可能的原因包括:
·麦克风权限未授予:检查浏览器权限设置。
·麦克风被静音:检查系统麦克风设置。
·设备选择错误:切换到正确的麦克风设备。