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 && }
);
}