123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382 |
- 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 { getChat } from '../../../redux/chat/selector';
- import { getAuthorizationState } from '../../../redux/authorization/selector';
- import { prodAwsS3,prodSocketURL, firstLetter, slicedWord,getTimeBySeconds } 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',
- 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: {
- width: 250,
- height: 'auto',
- 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 myVideoRef = useRef<any>(null);
- const companionVideoRef = useRef<any>(null);
- const companionAudioRef = useRef<any>(null);
- const [mutedMyVideo,setMutedMyVideo] = useState<boolean>(false)
- const [mutedMyAudio,setMutedMyAudio] = useState<boolean>(false)
- const [mySocket, setMySocket] = useState<string>('')
- const [companionSocket, setCompanionSocket] = useState<string>('')
- const [myStream, setMyStream] = useState<any>(null)
- const [companionSignal, setCompanionSignal] = useState<string>('')
- 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 [conversationLast, cetConversationLast] = useState<string>('')
- const [fullScreen, setFullScreen] = useState<boolean>(false)
- 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
- }
- }
- const handleMuteAudio = () => {
- if (myStream&&myStream.getAudioTracks()[0]) {
- setMutedMyAudio(!mutedMyAudio)
- myStream.getAudioTracks()[0].enabled = !myStream.getAudioTracks()[0].enabled
- }
- }
- const handleLeaveCall = () => {
- setCallStatus('hanging up...')
- connectionRef.current.destroy();
- setCallStatus('')
- };
- const handleStartCall = useCallback(async () => {
- 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
- });
- setCallStatus('ringing...')
- peer.on("signal", (data: any) => {
- socket.emit("callTo", {
- to: socketId,
- signalData: data,
- from: mySocket,
- userId: _id,
- companionId
- })
- });
- peer.on("stream", (companionStream: any) => {
- companionVideoRef.current.srcObject = companionStream;
- companionAudioRef.current.srcObject = companionStream;
- });
- peer.on('error', (e: any) => console.log('error from peer', e))
- peer.on('connect', () => {
- console.log('CONNECT')
- })
- socket.on("acceptedCall", ({ signal }: any) => {
- setCallStatus('connection')
- peer.signal(signal)
- setCallStatus('conversation')
- });
- connectionRef.current = peer;
- },[socketId,companionId,_id,mySocket,myVideoRef,setCallStatus])
- const handleAnswerCall = useCallback(async () => {
- setCallStatus('connection')
- 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,
- });
- setCallStatus('conversation')
- });
- peer.on("stream", (companionStream: any) => {
- companionVideoRef.current.srcObject = companionStream;
- companionAudioRef.current.srcObject = companionStream;
- });
- peer.signal(companionSignal);
- connectionRef.current = peer;
- },[companionSocket,companionSignal,setCallStatus])
- useEffect(() => {
- socket.on("me", (id: string) => {
- setMySocket(id)
- socketIdChat(id)
- })
- 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)
- }
- })
- }, [setCallStatus])
- useEffect(() => {
- if(callStatus === 'requesting...') handleStartCall()
- }, [callStatus, handleStartCall])
- 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} 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?'transparent':'rgb(70, 70, 70)'}}>
- <MinimizeIcon fontSize='small' />
- </div>
- <div className={classes.rightIconWrapper} onClick={() => setFullScreen(true)}
- style={{backgroundColor:fullScreen?'rgb(70, 70, 70)':'transparent'}}>
- <CropLandscapeIcon fontSize='small' />
- </div>
- <div className={classes.rightIconWrapperClose} onClick={handleLeaveCall}>
- <CloseIcon fontSize='small' />
- </div>
- </div>
- {callStatus !== 'conversation'&&<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}}>
- {`${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 }}/>
- <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" }} /> */}
- <video ref={companionVideoRef} playsInline muted autoPlay controls={false}
- style={{width: '100%',height:fullScreen?'100vh': 'auto',objectFit: 'cover'}} 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={handleLeaveCall}
- 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
|