import React, { RefObject, useRef } from 'react'; import { useSetAtom } from 'jotai'; import { Badge, Box, Button, color, Header, Icon, Icons, Scroll, Text, toRem } from 'folds'; import { useCallEmbed, useCallJoined, useCallEmbedPlacementSync, useCallLoadError, } from '../../hooks/useCallEmbed'; import { callEmbedAtom } from '../../state/callEmbed'; import { ContainerColor } from '../../styles/ContainerColor.css'; import { PrescreenControls } from './PrescreenControls'; import { usePowerLevelsContext } from '../../hooks/usePowerLevels'; import { useRoom } from '../../hooks/useRoom'; import { useRoomCreators } from '../../hooks/useRoomCreators'; import { useRoomPermissions } from '../../hooks/useRoomPermissions'; import { useMatrixClient } from '../../hooks/useMatrixClient'; import { StateEvent } from '../../../types/matrix/room'; import { useCallMembers, useCallSession } from '../../hooks/useCall'; import { useStateEvent } from '../../hooks/useStateEvent'; import { VoiceLimitContent } from '../common-settings/general/RoomVoiceLimit'; import { CallMemberRenderer } from './CallMemberCard'; import * as css from './styles.css'; import { CallControls } from './CallControls'; import { useLivekitSupport } from '../../hooks/useLivekitSupport'; import { webRTCSupported } from '../../utils/rtc'; function LivekitServerMissingMessage() { return ( Your homeserver does not support calling. ); } function WebRTCMissingError() { return ( Your browser does not support WebRTC, which is required for calling. ); } function JoinMessage({ hasParticipant, livekitSupported, rtcSupported, }: { hasParticipant?: boolean; livekitSupported?: boolean; rtcSupported?: boolean; }) { if (rtcSupported === false) { return ; } if (livekitSupported === false) { return ; } if (hasParticipant) return null; return ( Voice chat’s empty — Be the first to hop in! ); } function NoPermissionMessage() { return ( You don't have permission to join! ); } function AlreadyInCallMessage() { return ( Already in another call — End the current call to join! ); } function ChannelFullMessage({ current, max }: { current: number; max: number }) { return ( Channel Full ({current}/{max}) — Wait for someone to leave before joining. ); } function CallPrescreen() { const mx = useMatrixClient(); const room = useRoom(); const livekitSupported = useLivekitSupport(); const rtcSupported = webRTCSupported(); const powerLevels = usePowerLevelsContext(); const creators = useRoomCreators(room); const permissions = useRoomPermissions(creators, powerLevels); const hasPermission = permissions.stateEvent( StateEvent.GroupCallMemberPrefix, mx.getSafeUserId(), ); const callSession = useCallSession(room); const callMembers = useCallMembers(callSession); const hasParticipant = callMembers.length > 0; const callEmbed = useCallEmbed(); const inOtherCall = callEmbed && callEmbed.roomId !== room.roomId; // Voice channel user limit (io.lotus.voice_limit). 0 / absent means no limit. const limitEvent = useStateEvent(room, StateEvent.LotusVoiceLimit); const maxUsers = limitEvent?.getContent().max_users ?? 0; // A user already counted in the session is rejoining and should not be blocked. const alreadyMember = callMembers.some((m) => m.sender === mx.getSafeUserId()); const channelFull = maxUsers > 0 && !alreadyMember && callMembers.length >= maxUsers; const canJoin = hasPermission && livekitSupported && rtcSupported && !channelFull; return ( {hasParticipant && (
Participant {callMembers.length} Live
)} {!inOtherCall && !hasPermission && } {!inOtherCall && hasPermission && channelFull && ( )} {!inOtherCall && hasPermission && !channelFull && ( )} {inOtherCall && }
); } function CallLoadErrorMessage() { const setCallEmbed = useSetAtom(callEmbedAtom); // Disposing the embed tears down the hung iframe and returns the user to the // prescreen, from which they can join again ("Retry") or simply walk away. const dismiss = () => setCallEmbed(undefined); return ( The call failed to load. Check your connection and try again. ); } type CallJoinedProps = { containerRef: RefObject; joined: boolean; }; function CallJoined({ joined, containerRef }: CallJoinedProps) { const callEmbed = useCallEmbed(); return ( {callEmbed && joined && } ); } export function CallView() { const room = useRoom(); const callContainerRef = useRef(null) as React.RefObject; useCallEmbedPlacementSync(callContainerRef); const callEmbed = useCallEmbed(); const callJoined = useCallJoined(callEmbed); const loadError = useCallLoadError(callEmbed); const isCurrentRoom = callEmbed?.roomId === room.roomId; const currentJoined = isCurrentRoom && callJoined; // Show the recovery UI when this room's embed failed to load and we never // made it into the call (a hung iframe / blank spinner otherwise). const showLoadError = isCurrentRoom && !currentJoined && Boolean(loadError); return ( {showLoadError && } {!currentJoined && !showLoadError && } {!showLoadError && } ); }