123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482 |
- 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 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';
- 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<any>(null);
- const idAudioIntervalRef = useRef<any>(null);
- const myVideoRef = useRef<any>(null);
- const companionVideoRef = useRef<any>(null);
- const companionAudioRef = useRef<any>(null);
- const [mySocket, setMySocket] = useState<string>('')
- const [mutedMyVideo,setMutedMyVideo] = useState<boolean>(false)
- const [mutedMyAudio,setMutedMyAudio] = useState<boolean>(false)
- const [companionSocket, setCompanionSocket] = useState<string>('')
- const [myStream, setMyStream] = useState<any>(null)
- const [companionSignal, setCompanionSignal] = useState<any>(null)
- const [name, setName] = useState<string>('')
- const [lastName, setLastName] = useState<string>('')
- const [avatarUrl, setAvatarUrl] = useState<string>('')
- const [color, setColor] = useState<string>('')
- const [number, setNumber] = useState<string>('')
- const [callLast, setCallLast] = useState<number>(0)
- const [conversationLast, cetConversationLast] = useState<string>('')
- const [fullScreen, setFullScreen] = useState<boolean>(false)
- const [alert, setAlert] = useState<string>('')
- const [audioHtml, setAudioHtml] = useState<any>(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())
- 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 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`)
- }
- }
- 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`)
- }
- }
-
- const handleStartCall = useCallback(async () => {
- try {
- setCallStatus('waiting...')
- const mediaDevices: any = navigator.mediaDevices
- const stream = await mediaDevices.getDisplayMedia({
- 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,
- });
- 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 (
- <div className={classes.container} style={{ top: callStatus ? 0 : '-100%'}}>
- <video className={classes.myVideo} style={{width: !myStream || mutedMyVideo?0:250,height: !myStream || mutedMyVideo?0:'auto'}}
- ref={myVideoRef} playsInline autoPlay muted controls={false} />
- <Moveable
- target={myVideoRef.current}
- draggable={true}
- throttleDrag={0}
- hideDefaultLines={true}
- renderDirections={[]}
- rotationPosition="none"
- origin={false}
- onDrag={({ target, transform }: OnDrag) =>
- target!.style.transform = transform }
- />
- <div className={classes.modalCall} style={{width: fullScreen?'100vw':'34vw',height:fullScreen?'100vh':'auto'}}>
- <div className={classes.rightIcons}>
- <div className={classes.rightIconWrapper} onClick={() => setFullScreen(false)}
- style={{backgroundColor:fullScreen?'rgb(36, 36, 36)':'rgb(70, 70, 70)'}}>
- <MinimizeIcon fontSize='small' />
- </div>
- <div className={classes.rightIconWrapper} onClick={() => setFullScreen(true)}
- style={{backgroundColor:fullScreen?'rgb(70, 70, 70)':'rgb(36, 36, 36)'}}>
- <CropLandscapeIcon fontSize='small' />
- </div>
- <div className={classes.rightIconWrapperClose} onClick={handleHangUp}>
- <CloseIcon fontSize='small' />
- </div>
- </div>
- <div style={{ width: '100%', position: "relative" }}>
- {alert && <Alert variant="filled" severity="info" sx={{ backgroundColor: "rgb(70, 70, 70)" }}
- style={{ width: fullScreen?'auto':"100%", position: 'absolute', left: 0, bottom: -48 }}>{alert}</Alert>}
- {!fullScreen && <>
- <ListItemAvatar style={{margin:'25px 0px 5px 0px'}}>
- <Avatar alt={name} src={avatarUrl?`${prodAwsS3}/${avatarUrl}`:undefined}
- sx={{ background: color, width: 120, height: 120, marginRight: 2, fontSize:30,zIndex:0,margin:'0 auto'}}>
- {`${firstLetter(name)}${firstLetter(lastName)}`}
- </Avatar>
- </ListItemAvatar>
- <ListItemText primary={`${firstLetter(name)}${slicedWord(name, 15, 1)}
- ${firstLetter(lastName)}${slicedWord(lastName, 15, 1)}`}
- primaryTypographyProps={{ color: '#dfdfdf', fontSize: 20, fontWeight: 500,textAlign: "center" }}/>
- <ListItemText primary={number} primaryTypographyProps={{ color: '#ffffff', fontSize: 15, fontWeight: 500, textAlign: "center" }} />
- <ListItemText secondary={callStatus} secondaryTypographyProps={{ color: "#dfdfdf", textAlign: "center" }} />
- <ListItemText secondary={conversationLast} secondaryTypographyProps={{ color: "#dfdfdf", textAlign: "center" }} />
- </>}
- </div>
- <video ref={companionVideoRef} playsInline muted autoPlay controls={false}
- style={{width: '100%',height:fullScreen?'100vh': 'auto',objectFit: 'cover',
- backgroundColor:'rgb(36, 36, 36)'}} onTimeUpdate={handleConversationLast} />
- <audio ref={companionAudioRef} autoPlay />
- <div className={classes.bottomWrapper}>
- <div className={classes.bottomItem} onClick={handleMuteVideo}>
- <Avatar className={classes.bottomIcon}
- sx={{backgroundColor: mutedMyVideo?'#ffffff':'rgb(88, 88, 88)',color: mutedMyVideo?'rgb(36, 36, 36)':'#ffffff', width: 44, height: 44,zIndex:0}}>
- {mutedMyVideo?<VideocamOffIcon fontSize="medium" />:<VideocamIcon fontSize="medium" />}
- </Avatar>
- <Typography variant="h6" className={classes.titleIconBottom}>{mutedMyVideo?'Start Video':'Stop Video'}</Typography>
- </div>
- <div className={classes.bottomItem}>
- <Avatar className={classes.bottomIconEndAccept} onClick={handleHangUp}
- sx={{backgroundColor: '#f02a2a',color: '#ffffff', width: 44, height: 44,zIndex:0}}>
- <CallEndIcon fontSize="medium" />
- </Avatar>
- <Typography variant="h6" className={classes.titleIconBottom}>
- {callStatus === 'is calling you' ? 'Decline' : 'End Call'}
- </Typography>
- </div>
- {callStatus === 'is calling you' &&
- <div className={classes.bottomItem} style={{position:"relative"}}>
- <div className={classes.ringPulsate}></div>
- <Avatar className={classes.bottomIconEndAccept} onClick={handleAnswerCall}
- sx={{ backgroundColor: '#21f519', color: '#ffffff', width: 44, height: 44, zIndex: 0 }}>
- <PhoneIcon fontSize="medium" />
- </Avatar>
- <Typography variant="h6" className={classes.titleIconBottom}>
- Accept
- </Typography>
- </div>}
- <div className={classes.bottomItem}>
- <Avatar className={classes.bottomIcon} onClick={handleMuteAudio}
- sx={{backgroundColor: mutedMyAudio?'#ffffff':'rgb(88, 88, 88)',color: mutedMyAudio?'rgb(36, 36, 36)':'#ffffff', width: 44, height: 44,zIndex:0}}>
- {mutedMyAudio?<MicOffIcon fontSize="medium" />:<MicIcon fontSize="medium" />}
- </Avatar>
- <Typography variant="h6" className={classes.titleIconBottom}>{mutedMyAudio?'Unmute':'Mute'}</Typography>
- </div>
- </div>
- </div>
- </div>
- )
- }
- export default CallBar
|