/** * Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. * SPDX-license-identifier: BSD-3-Clause */ import { useEffect, useState, useRef } from 'react'; import { useSelector, useDispatch } from 'react-redux'; import VERTC, { MediaType } from '@volcengine/rtc'; import { Modal } from '@arco-design/web-react'; 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'; export const ABORT_VISIBILITY_CHANGE = 'abortVisibilityChange'; export interface FormProps { username: string; roomId: string; publishAudio: boolean; } export const useScene = () => { const { scene, sceneConfigMap } = useSelector((state: RootState) => state.room); return sceneConfigMap[scene] || {}; } export const useRTC = () => { const { scene, rtcConfigMap } = useSelector((state: RootState) => state.room); return rtcConfigMap[scene] || {}; } 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 isScreenPublished = localUser.publishScreen; 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 = async (controlPublish = true) => { if (controlPublish) { await (!isAudioPublished ? RtcClient.publishStream(MediaType.AUDIO) : RtcClient.unpublishStream(MediaType.AUDIO)); } queryDevices(MediaType.AUDIO); await (!isAudioPublished ? RtcClient.startAudioCapture() : RtcClient.stopAudioCapture()); dispatch( updateLocalUser({ publishAudio: !isAudioPublished, }) ); }; const switchCamera = async (controlPublish = true) => { if (controlPublish) { await (!isVideoPublished ? RtcClient.publishStream(MediaType.VIDEO) : RtcClient.unpublishStream(MediaType.VIDEO)); } queryDevices(MediaType.VIDEO); await (!isVideoPublished ? RtcClient.startVideoCapture() : RtcClient.stopVideoCapture()); dispatch( updateLocalUser({ publishVideo: !isVideoPublished, }) ); }; const switchScreenCapture = async (controlPublish = true) => { try { !isScreenPublished ? sessionStorage.setItem(ABORT_VISIBILITY_CHANGE, 'true') : sessionStorage.removeItem(ABORT_VISIBILITY_CHANGE); if (controlPublish) { await (!isScreenPublished ? RtcClient.publishScreenStream(MediaType.VIDEO) : RtcClient.unpublishScreenStream(MediaType.VIDEO)); } await (!isScreenPublished ? RtcClient.startScreenCapture() : RtcClient.stopScreenCapture()); dispatch( updateLocalUser({ publishScreen: !isScreenPublished, }) ); } catch { console.warn('Not Authorized.'); } sessionStorage.removeItem(ABORT_VISIBILITY_CHANGE); return false; }; return { isAudioPublished, isVideoPublished, isScreenPublished, switchMic, switchCamera, switchScreenCapture, }; }; 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, () => Promise ] => { const devicePermissions = useSelector((state: RootState) => state.device.devicePermissions); const room = useSelector((state: RootState) => state.room); const dispatch = useDispatch(); const { id } = useScene(); const { switchMic } = useDeviceState(); const [joining, setJoining] = useState(false); const listeners = useRtcListeners(); const handleAIGCModeStart = async () => { if (room.isAIGCEnable) { await RtcClient.stopAgent(id); dispatch(clearCurrentMsg()); await RtcClient.startAgent(id); } else { await RtcClient.startAgent(id); } dispatch(updateAIGCState({ isAIGCEnable: true })); }; async function disPatchJoin(): Promise { if (joining) { return; } const isSupported = await VERTC.isSupported(); if (!isSupported) { Modal.error({ title: '不支持 RTC', content: '您的浏览器可能不支持 RTC 功能,请尝试更换浏览器或升级浏览器后再重试。', }); return; } setJoining(true); /** 1. Create RTC Engine */ await RtcClient.createEngine(); /** 2.1 Set events callbacks */ RtcClient.addEventListeners(listeners); /** 2.2 RTC starting to join room */ await RtcClient.joinRoom(); /** 3. Set users' devices info */ const mediaDevices = await RtcClient.getDevices({ audio: true, video: false, }); dispatch( localJoinRoom({ roomId: RtcClient.basicInfo.room_id, user: { username: RtcClient.basicInfo.user_id, userId: RtcClient.basicInfo.user_id, }, }) ); dispatch( updateSelectedDevice({ selectedMicrophone: mediaDevices.audioInputs[0]?.deviceId, selectedCamera: mediaDevices.videoInputs[0]?.deviceId, }) ); dispatch(updateMediaInputs(mediaDevices)); setJoining(false); if (devicePermissions.audio) { try { await switchMic(); } catch (e) { logger.debug('No permission for mic'); } } handleAIGCModeStart(); } return [joining, disPatchJoin]; }; export const useLeave = () => { const dispatch = useDispatch(); const { id } = useScene(); const idRef = useRef(id); idRef.current = id; return async function () { await Promise.all([ RtcClient.stopAudioCapture, RtcClient.stopScreenCapture, RtcClient.stopVideoCapture, ]); await RtcClient.stopAgent(idRef.current); await RtcClient.leaveRoom(); dispatch(clearHistoryMsg()); dispatch(clearCurrentMsg()); dispatch(localLeaveRoom()); dispatch(updateAIGCState({ isAIGCEnable: false })); }; };