/** * Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. * SPDX-license-identifier: BSD-3-Clause */ import { useEffect, useState } from 'react'; import { useSelector, useDispatch } from 'react-redux'; import VERTC, { MediaType } from '@volcengine/rtc'; import { Modal } from '@arco-design/web-react'; import Utils from '@/utils/utils'; import RtcClient from '@/lib/RtcClient'; import { clearCurrentMsg, clearHistoryMsg, localJoinRoom, localLeaveRoom, updateAIGCState, updateLocalUser, } from '@/store/slices/room'; import useRtcListeners from '@/lib/listenerHooks'; import { RootState } from '@/store'; import { updateMediaInputs, updateSelectedDevice, setDevicePermissions, } from '@/store/slices/device'; import logger from '@/utils/logger'; import aigcConfig, { AI_MODEL } from '@/config'; export interface FormProps { username: string; roomId: string; publishAudio: boolean; } export const useVisionMode = () => { const room = useSelector((state: RootState) => state.room); return [AI_MODEL.VISION].includes(room.aiConfig?.Config?.LLMConfig.ModelName); }; export const useGetDevicePermission = () => { const [permission, setPermission] = useState<{ audio: boolean; }>(); const dispatch = useDispatch(); useEffect(() => { (async () => { const permission = await RtcClient.checkPermission(); dispatch(setDevicePermissions(permission)); setPermission(permission); })(); }, [dispatch]); return permission; }; export const useJoin = (): [ boolean, (formValues: FormProps, fromRefresh: boolean) => Promise ] => { const devicePermissions = useSelector((state: RootState) => state.device.devicePermissions); const room = useSelector((state: RootState) => state.room); const dispatch = useDispatch(); const [joining, setJoining] = useState(false); const listeners = useRtcListeners(); const handleAIGCModeStart = async () => { if (room.isAIGCEnable) { await RtcClient.stopAudioBot(); dispatch(clearCurrentMsg()); await RtcClient.startAudioBot(); } else { await RtcClient.startAudioBot(); } dispatch(updateAIGCState({ isAIGCEnable: true })); }; async function disPatchJoin(formValues: FormProps): Promise { if (joining) { return; } const isSupported = await VERTC.isSupported(); if (!isSupported) { Modal.error({ title: '不支持 RTC', content: '您的浏览器可能不支持 RTC 功能,请尝试更换浏览器或升级浏览器后再重试。', }); return; } setJoining(true); const { username, roomId } = formValues; const isVisionMode = aigcConfig.Model === AI_MODEL.VISION; const token = aigcConfig.BaseConfig.Token; /** 1. Create RTC Engine */ await RtcClient.createEngine({ appId: aigcConfig.BaseConfig.AppId, roomId, uid: username, } as any); /** 2.1 Set events callbacks */ RtcClient.addEventListeners(listeners); /** 2.2 RTC starting to join room */ await RtcClient.joinRoom(token!, username); console.log(' ------ userJoinRoom\n', `roomId: ${roomId}\n`, `uid: ${username}`); /** 3. Set users' devices info */ const mediaDevices = await RtcClient.getDevices({ audio: true, video: isVisionMode, }); if (devicePermissions.audio) { try { await RtcClient.startAudioCapture(); // RtcClient.setAudioVolume(30); } catch (e) { logger.debug('No permission for mic'); } } if (devicePermissions.video && isVisionMode) { try { await RtcClient.startVideoCapture(); } catch (e) { logger.debug('No permission for camera'); } } dispatch( localJoinRoom({ roomId, user: { username, userId: username, publishAudio: true, publishVideo: devicePermissions.video && isVisionMode, }, }) ); dispatch( updateSelectedDevice({ selectedMicrophone: mediaDevices.audioInputs[0]?.deviceId, selectedCamera: mediaDevices.videoInputs[0]?.deviceId, }) ); dispatch(updateMediaInputs(mediaDevices)); setJoining(false); Utils.setSessionInfo({ username, roomId, publishAudio: true, }); handleAIGCModeStart(); } return [joining, disPatchJoin]; }; export const useLeave = () => { const dispatch = useDispatch(); return async function () { dispatch(localLeaveRoom()); dispatch(updateAIGCState({ isAIGCEnable: false })); await Promise.all([RtcClient.stopAudioCapture]); RtcClient.leaveRoom(); dispatch(clearHistoryMsg()); dispatch(clearCurrentMsg()); }; }; export const useDeviceState = () => { const dispatch = useDispatch(); const room = useSelector((state: RootState) => state.room); const localUser = room.localUser; const isAudioPublished = localUser.publishAudio; const isVideoPublished = localUser.publishVideo; const queryDevices = async (type: MediaType) => { const mediaDevices = await RtcClient.getDevices({ audio: type === MediaType.AUDIO, video: type === MediaType.VIDEO, }); if (type === MediaType.AUDIO) { dispatch( updateMediaInputs({ audioInputs: mediaDevices.audioInputs, }) ); dispatch( updateSelectedDevice({ selectedMicrophone: mediaDevices.audioInputs[0]?.deviceId, }) ); } else { dispatch( updateMediaInputs({ videoInputs: mediaDevices.videoInputs, }) ); dispatch( updateSelectedDevice({ selectedCamera: mediaDevices.videoInputs[0]?.deviceId, }) ); } return mediaDevices; }; const switchMic = (publish = true) => { if (publish) { !isAudioPublished ? RtcClient.publishStream(MediaType.AUDIO) : RtcClient.unpublishStream(MediaType.AUDIO); } queryDevices(MediaType.AUDIO); !isAudioPublished ? RtcClient.startAudioCapture() : RtcClient.stopAudioCapture(); dispatch( updateLocalUser({ publishAudio: !localUser.publishAudio, }) ); }; const switchCamera = (publish = true) => { if (publish) { !isVideoPublished ? RtcClient.publishStream(MediaType.VIDEO) : RtcClient.unpublishStream(MediaType.VIDEO); } queryDevices(MediaType.VIDEO); !localUser.publishVideo ? RtcClient.startVideoCapture() : RtcClient.stopVideoCapture(); dispatch( updateLocalUser({ publishVideo: !localUser.publishVideo, }) ); }; return { isAudioPublished, isVideoPublished, switchMic, switchCamera, }; };