import type { FC } from '../../../lib/teact/teact'; import React, { memo, useCallback, useEffect, useMemo, useRef, } from '../../../lib/teact/teact'; import { getActions, withGlobal } from '../../../global'; import '../../../global/actions/calls'; import type { ApiPhoneCall, ApiUser } from '../../../api/types'; import { IS_ANDROID, IS_IOS, IS_REQUEST_FULLSCREEN_SUPPORTED, IS_SINGLE_COLUMN_LAYOUT, } from '../../../util/environment'; import { LOCAL_TGS_URLS } from '../../common/helpers/animatedAssets'; import buildClassName from '../../../util/buildClassName'; import { selectPhoneCallUser } from '../../../global/selectors/calls'; import useLang from '../../../hooks/useLang'; import renderText from '../../common/helpers/renderText'; import useFlag from '../../../hooks/useFlag'; import { formatMediaDuration } from '../../../util/dateFormat'; import { getStreams, IS_SCREENSHARE_SUPPORTED, switchCameraInputP2p, toggleStreamP2p, } from '../../../lib/secret-sauce'; import useInterval from '../../../hooks/useInterval'; import useForceUpdate from '../../../hooks/useForceUpdate'; import Modal from '../../ui/Modal'; import Avatar from '../../common/Avatar'; import Button from '../../ui/Button'; import PhoneCallButton from './PhoneCallButton'; import AnimatedIcon from '../../common/AnimatedIcon'; import styles from './PhoneCall.module.scss'; type StateProps = { user?: ApiUser; phoneCall?: ApiPhoneCall; isOutgoing: boolean; isCallPanelVisible?: boolean; }; const PhoneCall: FC = ({ user, isOutgoing, phoneCall, isCallPanelVisible, }) => { const lang = useLang(); const { hangUp, acceptCall, playGroupCallSound, toggleGroupCallPanel, connectToActivePhoneCall, } = getActions(); // eslint-disable-next-line no-null/no-null const containerRef = useRef(null); const [isFullscreen, openFullscreen, closeFullscreen] = useFlag(); const toggleFullscreen = useCallback(() => { if (isFullscreen) { closeFullscreen(); } else { openFullscreen(); } }, [closeFullscreen, isFullscreen, openFullscreen]); const handleToggleFullscreen = useCallback(() => { if (!containerRef.current) return; if (isFullscreen) { document.exitFullscreen().then(closeFullscreen); } else { containerRef.current.requestFullscreen().then(openFullscreen); } }, [closeFullscreen, isFullscreen, openFullscreen]); useEffect(() => { if (!IS_REQUEST_FULLSCREEN_SUPPORTED) return undefined; const container = containerRef.current; if (!container) return undefined; container.addEventListener('fullscreenchange', toggleFullscreen); return () => { container.removeEventListener('fullscreenchange', toggleFullscreen); }; }, [toggleFullscreen]); const handleClose = useCallback(() => { toggleGroupCallPanel(); if (isFullscreen) { closeFullscreen(); } }, [closeFullscreen, isFullscreen, toggleGroupCallPanel]); const isDiscarded = phoneCall?.state === 'discarded'; const isBusy = phoneCall?.reason === 'busy'; const isIncomingRequested = phoneCall?.state === 'requested' && !isOutgoing; const isOutgoingRequested = (phoneCall?.state === 'requested' || phoneCall?.state === 'waiting') && isOutgoing; const isActive = phoneCall?.state === 'active'; const isConnected = phoneCall?.isConnected; const [isHangingUp, startHangingUp, stopHangingUp] = useFlag(); const handleHangUp = useCallback(() => { startHangingUp(); hangUp(); }, [hangUp, startHangingUp]); useEffect(() => { if (isHangingUp) { playGroupCallSound({ sound: 'end' }); } else if (isIncomingRequested) { playGroupCallSound({ sound: 'incoming' }); } else if (isBusy) { playGroupCallSound({ sound: 'busy' }); } else if (isDiscarded) { playGroupCallSound({ sound: 'end' }); } else if (isOutgoingRequested) { playGroupCallSound({ sound: 'ringing' }); } else if (isConnected) { playGroupCallSound({ sound: 'connect' }); } }, [isBusy, isDiscarded, isIncomingRequested, isOutgoingRequested, isConnected, playGroupCallSound, isHangingUp]); useEffect(() => { if (phoneCall?.id) { stopHangingUp(); } else { connectToActivePhoneCall(); } }, [connectToActivePhoneCall, phoneCall?.id, stopHangingUp]); const forceUpdate = useForceUpdate(); useInterval(() => { forceUpdate(); }, isConnected ? 1000 : undefined); const callStatus = useMemo(() => { const state = phoneCall?.state; if (isHangingUp) { return lang('lng_call_status_hanging'); } if (isBusy) return 'busy'; if (state === 'requesting') { return lang('lng_call_status_requesting'); } else if (state === 'requested') { return isOutgoing ? lang('lng_call_status_ringing') : lang('lng_call_status_incoming'); } else if (state === 'waiting') { return lang('lng_call_status_waiting'); } else if (state === 'active' && isConnected) { return undefined; } else { return lang('lng_call_status_exchanging'); } }, [isBusy, isConnected, isHangingUp, isOutgoing, lang, phoneCall?.state]); const hasVideo = phoneCall?.videoState === 'active'; const hasPresentation = phoneCall?.screencastState === 'active'; const streams = getStreams(); const hasOwnAudio = streams?.ownAudio?.getTracks()[0].enabled; const hasOwnPresentation = streams?.ownPresentation?.getTracks()[0].enabled; const hasOwnVideo = streams?.ownVideo?.getTracks()[0].enabled; const [isHidingPresentation, startHidingPresentation, stopHidingPresentation] = useFlag(); const [isHidingVideo, startHidingVideo, stopHidingVideo] = useFlag(); const handleTogglePresentation = useCallback(() => { if (hasOwnPresentation) { startHidingPresentation(); } if (hasOwnVideo) { startHidingVideo(); } setTimeout(async () => { await toggleStreamP2p('presentation'); stopHidingPresentation(); stopHidingVideo(); }, 250); }, [ hasOwnPresentation, hasOwnVideo, startHidingPresentation, startHidingVideo, stopHidingPresentation, stopHidingVideo, ]); const handleToggleVideo = useCallback(() => { if (hasOwnVideo) { startHidingVideo(); } if (hasOwnPresentation) { startHidingPresentation(); } setTimeout(async () => { await toggleStreamP2p('video'); stopHidingPresentation(); stopHidingVideo(); }, 250); }, [ hasOwnPresentation, hasOwnVideo, startHidingPresentation, startHidingVideo, stopHidingPresentation, stopHidingVideo, ]); const handleToggleAudio = useCallback(() => { void toggleStreamP2p('audio'); }, []); const [isEmojiOpen, openEmoji, closeEmoji] = useFlag(); const [isFlipping, startFlipping, stopFlipping] = useFlag(); const handleFlipCamera = useCallback(() => { startFlipping(); switchCameraInputP2p(); setTimeout(stopFlipping, 250); }, [startFlipping, stopFlipping]); const timeElapsed = phoneCall?.startDate && (Number(new Date()) / 1000 - phoneCall.startDate); useEffect(() => { if (phoneCall?.state === 'discarded') { setTimeout(hangUp, 250); } }, [hangUp, phoneCall?.reason, phoneCall?.state]); return ( {phoneCall?.screencastState === 'active' && streams?.presentation && ); }; export default memo(withGlobal( (global): StateProps => { const { phoneCall, currentUserId } = global; return { isCallPanelVisible: Boolean(global.isCallPanelVisible), user: selectPhoneCallUser(global), isOutgoing: phoneCall?.adminId === currentUserId, phoneCall, }; }, )(PhoneCall));