import React, { useCallback, useEffect, useState } from 'react'; import { Box, Button, color, Icon, Icons, Spinner, Text } from 'folds'; import { SequenceCard } from '../../components/sequence-card'; import * as css from './styles.css'; import { ChatButton, ControlDivider, MicrophoneButton, SoundButton, VideoButton } from './Controls'; import { useIsDirectRoom, useRoom } from '../../hooks/useRoom'; import { useCallEmbed, useCallJoined, useCallStart } from '../../hooks/useCallEmbed'; import { useCallPreferences } from '../../state/hooks/callPreferences'; import { useSetting } from '../../state/hooks/settings'; import { settingsAtom } from '../../state/settings'; type MediaPermState = 'granted' | 'denied' | 'prompt' | 'unknown'; function useMediaPermissions(): MediaPermState { const [state, setState] = useState('unknown'); useEffect(() => { if (!navigator.permissions) { setState('unknown'); return undefined; } let cancelled = false; let permStatus: PermissionStatus | undefined; navigator.permissions .query({ name: 'microphone' as unknown as PermissionDescriptor['name'] }) .then((result) => { if (cancelled) return; permStatus = result; setState(result.state as MediaPermState); result.onchange = () => { if (!cancelled) setState(result.state as MediaPermState); }; }) .catch(() => { if (!cancelled) setState('unknown'); }); // Detach the onchange handler on unmount so it can't setState afterward (and // so the PermissionStatus doesn't retain the callback). return () => { cancelled = true; if (permStatus) permStatus.onchange = null; }; }, []); return state; } type PrescreenControlsProps = { canJoin?: boolean; }; export function PrescreenControls({ canJoin }: PrescreenControlsProps) { const room = useRoom(); const callEmbed = useCallEmbed(); const callJoined = useCallJoined(callEmbed); const direct = useIsDirectRoom(); const inOtherCall = callEmbed && callEmbed.roomId !== room.roomId; const startCall = useCallStart(direct); const joining = callEmbed?.roomId === room.roomId && !callJoined; const micPermission = useMediaPermissions(); const micDenied = micPermission === 'denied'; const disabled = inOtherCall || !canJoin || micDenied; const { microphone, video, sound, toggleMicrophone, toggleVideo, toggleSound } = useCallPreferences(); const [cameraOnJoin] = useSetting(settingsAtom, 'cameraOnJoin'); const handleMicrophoneToggle = useCallback(async () => toggleMicrophone(), [toggleMicrophone]); const handleVideoToggle = useCallback(async () => toggleVideo(), [toggleVideo]); return ( {micDenied && ( Microphone access is blocked. Enable it in your browser settings to join. )} ); }