import { makeStyles, Typography } from '@material-ui/core' import { useState,useEffect,useCallback,useRef } from 'react'; import { useSelector } from 'react-redux'; import io from 'socket.io-client'; import Moveable from "react-moveable"; import { OnDrag } from "react-moveable"; import ListItemText from '@mui/material/ListItemText'; import ListItemAvatar from '@mui/material/ListItemAvatar'; import Avatar from '@mui/material/Avatar'; import PhoneIcon from '@mui/icons-material/Phone'; import MinimizeIcon from '@mui/icons-material/Minimize'; import CropLandscapeIcon from '@mui/icons-material/CropLandscape'; import CloseIcon from '@mui/icons-material/Close'; import VideocamIcon from '@mui/icons-material/Videocam'; import VideocamOffIcon from '@mui/icons-material/VideocamOff'; import MicIcon from '@mui/icons-material/Mic'; import MicOffIcon from '@mui/icons-material/MicOff'; import CallEndIcon from '@mui/icons-material/CallEnd'; import StopScreenShareIcon from '@mui/icons-material/StopScreenShare'; import ScreenShareIcon from '@mui/icons-material/ScreenShare'; import Alert from '@mui/material/Alert'; import { getChat } from '../../../redux/chat/selector'; import { getAuthorizationState } from '../../../redux/authorization/selector'; import { prodAwsS3,prodBaseURL,prodSocketURL, firstLetter, slicedWord,getTimeBySeconds,playNotification } from '../../../helpers' import { socketIdChat } from '../../../api-data'; import { current } from '@reduxjs/toolkit'; const Peer = require('simple-peer') const socket = io(prodSocketURL) const useStyles = makeStyles({ container: { position: 'absolute', left: 0, top: 0, width: '100vw', height:'100vh', overflow: 'hidden', zIndex:100, display: 'flex', justifyContent: 'center', alignItems: 'center', alignContent: "center", backgroundColor: 'rgba(104, 105, 104, 0.6)', }, modalCall: { background: 'rgb(36, 36, 36)', position:'relative', display: 'flex', flexDirection: 'column', justifyContent: 'start', alignItems: 'center', justifyItems:"center", borderRadius: 7, }, rightIcons: { display: 'flex', justifyContent: 'end', alignContent: 'center', alignItems: 'center', width: '100%', position: 'absolute', left: 0, top: 0, zIndex:1 }, rightIconWrapper: { color: '#ffffff', cursor: 'pointer', padding:'3px 10px 3px 10px', }, rightIconWrapperClose: { color: '#ffffff', cursor: 'pointer', padding: '3px 10px 3px 10px', backgroundColor:'rgb(36, 36, 36)', borderTopRightRadius:7, '&:hover': { backgroundColor:'#f02a2a' } }, bottomWrapper: { display: 'flex', justifyContent: 'center', padding: 5, position: "absolute", left: 0, bottom: 0, width:'100%' }, bottomItem: { display: 'flex', flexDirection: 'column', justifyContent: 'center', alignContent: 'center', alignItems: 'center', cursor:'pointer', width: 80, }, bottomIcon: { cursor:'pointer', }, bottomIconEndAccept: { cursor: 'pointer', '&:hover': { animation: `$shake 1s`, animationIterationCount:'infinite', } }, ringPulsate: { backgroundColor:"rgb(80, 80, 80)", borderRadius: '50%', height: 60, width: 60, position: 'absolute', left: 10, top: -8, animation: `$pulsate 1.5s ease-out`, animationIterationCount: 'infinite', opacity: 0, }, titleIconBottom: { color: '#ffffff', fontSize: 13, paddingTop:7 }, myVideo: { cursor: 'pointer', position: 'absolute', top: 0, left: 0, zIndex: 150, }, '@keyframes pulsate': { '0%': {transform: 'scale(1, 1)', opacity: 0}, '50%': { opacity: 1}, '100%': {transform: 'scale(1.2, 1.2)', opacity: 0}, }, '@keyframes shake': { '0%': { transform: 'rotate(0deg)'}, '11%': { transform: 'rotate(10deg)'}, '22%': { transform: 'rotate(20deg)'}, '33%': { transform: 'rotate(30deg)'}, '44%': { transform: 'rotate(20deg)'}, '55%': { transform: 'rotate(10deg)'}, '66%': { transform: 'rotate(0deg)'}, '77%': { transform: 'rotate(-10deg)'}, '88%': { transform: 'rotate(-20deg)'}, '100%': { transform: 'rotate(-30deg)'}, }, }) interface ICallBar { callStatus:any, setCallStatus: any, } const CallBar = ({callStatus,setCallStatus}:ICallBar) => { const classes = useStyles() const { _id } = useSelector(getAuthorizationState) const chat = useSelector(getChat) const { socketId, companionId } = chat const connectionRef = useRef(null); const idAudioIntervalRef = useRef(null); const myVideoRef = useRef(null); const companionVideoRef = useRef(null); const companionAudioRef = useRef(null); const [mySocket, setMySocket] = useState('') const [mutedMyVideo,setMutedMyVideo] = useState(false) const [mutedMyAudio,setMutedMyAudio] = useState(false) const [companionSocket, setCompanionSocket] = useState('') const [myStream, setMyStream] = useState(null) const [myShareStream, setMyShareStream] = useState(null) const [companionSignal, setCompanionSignal] = useState(null) const [name, setName] = useState('') const [lastName, setLastName] = useState('') const [avatarUrl, setAvatarUrl] = useState('') const [color, setColor] = useState('') const [number, setNumber] = useState('') const [callLast, setCallLast] = useState(0) const [conversationLast, cetConversationLast] = useState('') const [fullScreen, setFullScreen] = useState(false) const [alert, setAlert] = useState('') const [audioHtml, setAudioHtml] = useState(null) const handleLeaveCall = useCallback(() => { if (callStatus === 'is calling you') { socket.emit("answerCall", { signal: 'declined', to: companionSocket, }); } if(connectionRef.current) connectionRef.current.destroy(); if(idAudioIntervalRef.current) clearInterval(idAudioIntervalRef.current) myVideoRef.current.srcObject = null companionVideoRef.current.srcObject = null companionAudioRef.current.srcObject = null if (audioHtml) { audioHtml.pause() setAudioHtml(null) } if(myStream) myStream.getTracks().forEach((track:any) => track.stop()) setMyShareStream(null) setMutedMyVideo(false) setMutedMyAudio(false) setCompanionSocket('') setMyStream(null) setCompanionSignal(null) setName('') setLastName('') setAvatarUrl('') setColor('') setNumber('') setCallLast(0) cetConversationLast('') setFullScreen(false) setAlert('') setCallStatus('') }, [setCallStatus, callStatus, companionSocket, audioHtml, myStream]) const handleHangUp = () => setCallStatus('hanging up...') const handleConversationLast = (e: any) => cetConversationLast(getTimeBySeconds(e.target.currentTime)) const handleMuteShare = async () => { try { if (myStream) { const mediaDevices: any = navigator.mediaDevices; const stream = await mediaDevices.getDisplayMedia({ video: true, audio: true }) setMyShareStream(stream) const shareTrack = stream.getVideoTracks()[0] const videoTrack = myStream.getVideoTracks()[0] const senders = connectionRef.current._pc.getSenders() const replaceStream = (track:any) => senders.forEach((sender: any) => sender.track.kind === "video" && sender.replaceTrack(track)) shareTrack.onended = () => { setMyShareStream(null) replaceStream(videoTrack) } replaceStream(shareTrack) } else { setAlert(`You can not enable Share before stream started`) setTimeout(() => setAlert(''),1000) } } catch (e: any) { setCallStatus('permission not allowed') } } const handleMuteVideo = () => { if (myStream&&myStream.getVideoTracks()[0]) { setMutedMyVideo(!mutedMyVideo) myStream.getVideoTracks()[0].enabled = !myStream.getVideoTracks()[0].enabled } else { setAlert(`You can not ${mutedMyVideo ? 'enable' : 'disable'} Video before stream started`) setTimeout(() => setAlert(''),1000) } } const handleMuteAudio = () => { if (myStream&&myStream.getAudioTracks()[0]) { setMutedMyAudio(!mutedMyAudio) myStream.getAudioTracks()[0].enabled = !myStream.getAudioTracks()[0].enabled } else { setAlert(`You can not ${mutedMyAudio ? 'enable' : 'disable'} Audio before stream started`) setTimeout(() => setAlert(''),1000) } } const handleStartCall = useCallback(async () => { try { setCallStatus('waiting...') const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true }) setMyStream(stream) myVideoRef.current.srcObject = stream; const peer = new Peer({ initiator: true, trickle: false, stream }); const audioRing = playNotification(`${prodBaseURL}/calling.mp3`) audioRing.loop = true setAudioHtml(audioRing) idAudioIntervalRef.current = setInterval(() => setCallLast(prevState => prevState + 1), 1000) peer.on("signal", (data: any) => { setCallStatus('ringing...') socket.emit("callTo", { to: socketId, signalData: data, from: mySocket, userId: _id, companionId, peer }) }); peer.on("stream", (companionStream: any) => { companionVideoRef.current.srcObject = companionStream; companionAudioRef.current.srcObject = companionStream; }); peer.on('connect', () => { setCallStatus('connection') setAlert('') audioRing.pause() clearInterval(idAudioIntervalRef.current) setTimeout(() => setCallStatus('conversation'),1000) }) peer.on("close", () => setCallStatus('hanging up...')); peer.on('error', () => setCallStatus('connection lost')) socket.on("acceptedCall", ({ signal }: any) => { if (signal === 'declined') setCallStatus('request declined') else if (signal === 'busy') setCallStatus('line busy') else peer.signal(signal) }); connectionRef.current = peer; } catch (e:any) { setCallStatus('permission not allowed') } },[socketId,companionId,_id,mySocket,myVideoRef,setCallStatus]) const handleAnswerCall = useCallback(async () => { try { setCallStatus('waiting...') audioHtml.pause() const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true }) setMyStream(stream) myVideoRef.current.srcObject = stream; const peer = new Peer({ initiator: false, trickle: false, stream:false, }); peer.on("signal", (data: any) => { socket.emit("answerCall", { signal: data, to: companionSocket, }); }); peer.on("stream", (companionStream: any) => { companionVideoRef.current.srcObject = companionStream; companionAudioRef.current.srcObject = companionStream; }); peer.on('connect', () => { setCallStatus('connection') setAlert('') setTimeout(() => setCallStatus('conversation'),1000) }) peer.on("close", () => setCallStatus('hanging up...')); peer.on('error', () => setCallStatus('connection lost')) peer.signal(companionSignal); connectionRef.current = peer; } catch (e: any) { setCallStatus('permission not allowed') } }, [companionSocket, companionSignal, setCallStatus,audioHtml]) useEffect(() => { socket.on("me", (id: string) => { setMySocket(id) socketIdChat(id) }) },[]) useEffect(() => { socket.on('incomeCall', ({ name, lastName, avatarUrl, color, number, from, signal }: any) => { if (connectionRef.current === null) { setCallStatus('is calling you') setName(name) setLastName(lastName) setAvatarUrl(avatarUrl) setColor(color) setNumber(number) setCompanionSocket(from) setCompanionSignal(signal) const audioRing = playNotification(`${prodBaseURL}/ringing.mp3`) audioRing.loop = true setAudioHtml(audioRing) } else if (companionSocket !== from) { socket.emit("answerCall", { signal: 'busy', to: companionSocket, }); } }) }, [setCallStatus,companionSocket]) useEffect(() => { if (callLast === 60) setCallStatus('have not got response') }, [callLast, setCallStatus]) useEffect(() => { if(callStatus === 'requesting...') handleStartCall() }, [callStatus, handleStartCall]) useEffect(() => { if (callStatus === 'hanging up...' || callStatus === 'connection lost' || callStatus === 'request declined' || callStatus === 'have not got response' || callStatus === 'permission not allowed' || callStatus === 'line busy') handleLeaveCall() }, [callStatus, handleLeaveCall, setCallStatus]) useEffect(() => { if (callStatus === '') { setName(chat.name) setLastName(chat.lastName) setAvatarUrl(chat.avatarUrl) setColor(chat.color) setNumber(chat.number) } }, [callStatus, chat]) return (
) } export default CallBar