bendwidth / frontend / src / videoComponents / VideoPage.js
VideoPage.js
Raw
// 
import './videoPage.css'
import { addAnswer, updateCallStatus } from '../redux-elements/callStatus'
import {useDispatch, useSelector} from 'react-redux'
import { useEffect, useRef, useState } from 'react'
import createPeerConnection from './webRTCUtilities/createPeerConnection'
import ActionButtons from './ActionButtons'
import pair from '../redux-elements/pair'

import socketConnection from './connectionEstablishment/socketConnection'
import serverListeners from './connectionEstablishment/serverListeners'
import translate from '../translationComponents/translate'
import { updateUserDetails, updateWholeUserObject } from '../redux-elements/userDetails'

// entry point to video chat component.
const VideoPage = () => {
    // initial setup of state.
    const dispatch = useDispatch();

    // references to the video tags.
    const smallFeedEl = useRef(null);
    const largeFeedEl = useRef(null);


    // login status of user.
    const isLoggedIn = JSON.parse(sessionStorage.getItem('userData'))["isLoggedIn"]



    // current status of call.
    const callStatus = useSelector(state => state.callStatus)

    // current user details.
    // const userDetails = useSelector(state => state.userDetails);
    const userDetails = JSON.parse(sessionStorage.getItem('userData'))
    const user = useSelector(state=> state.userDetails);
    const [translatedText, setTranslatedText] = useState('')

    useEffect(() => {
        dispatch(updateWholeUserObject(userDetails)); 
    }, [])

    useEffect(()=> {
        const checkRespondentConnected = async () => {
            const uuid = userDetails.uuid;
            const {res, translatingFrom} = await callStatus.socket.emitWithAck("isRespondentConnected", uuid)
            if (res === true) {
                dispatch(updateCallStatus(pair("respondentConnected", true)));
                dispatch(updateUserDetails(pair("sourceLanguage", translatingFrom)))
                console.log("getting respondents language", translatingFrom)
                userDetails.sourceLanguage = translatingFrom;
                sessionStorage.setItem('userData', JSON.stringify(userDetails));
            }
        }
        if (callStatus.socket && userDetails.isRespondent === false) {
            checkRespondentConnected();
        }
    }, [callStatus.socket])
    
    // get user's camera and microphone
    useEffect(() => {
        const fetchMedia = async () => {
            // what inputs do we want to get from user.
            const constraints = {
                video: true,
                audio: true
            }

            // attempt to get a stream.
            try {
                const stream = await navigator.mediaDevices.getUserMedia(constraints);
                // let redux know that we now have media.
                dispatch(updateCallStatus(pair('hasMedia', true)));

                // update redux state with localStream.
                dispatch(updateCallStatus(pair('localStream', stream)));

                // establish a peer connection.
                const { peerConnection, remoteStream } = await createPeerConnection(sendICECandidatesToServer);



                // add event listener to peer to detect if other peer leaves call
                peerConnection.oniceconnectionstatechange = () => {
                    if (peerConnection.iceConnectionState === 'closed') {
                        if (callStatus.status !== "localEnded") {
                            dispatch(updateCallStatus(pair('status', 'remoteEnded')));
                        }   
                    }
                }
                dispatch(updateCallStatus(pair('peerConnection', peerConnection)));
                dispatch(updateCallStatus(pair('remoteStream', remoteStream)));
                

                largeFeedEl.current.srcObject = remoteStream;

                

            } catch (e) {
                console.log(e);
            }
        }
        console.log(isLoggedIn, callStatus.hasMedia, callStatus.socket)
        // attempt to get user media only if user is logged in.
        if (isLoggedIn && !callStatus.hasMedia && callStatus.socket) {  
            fetchMedia();
        }

    }, [callStatus.hasMedia, callStatus.socket])

    useEffect(() => {
        const createAsyncOffer = async () => {
            try {
                const pc = callStatus.peerConnection;
            
            // create the offer.
            const offer = await pc.createOffer();
            pc.setLocalDescription(offer);
            const uuid = userDetails.uuid;
                
                // send the new offer to the server.

            const socket = callStatus.socket;
       
            socket.emit('newOffer', { offer, uuid });
            } catch (error) {
                console.log(error);
            }
        }
        if (callStatus.audio === 'enabled' && callStatus.video === 'enabled' && !callStatus.hasCreatedOffer && userDetails.isRespondent === false && callStatus.respondentConnected===true) {
            console.log("just sent offer.")
            createAsyncOffer();
        }
        

    }, [callStatus.video, callStatus.audio, callStatus.hasCreatedOffer, callStatus.respondentConnected])
    
    // start listenining to server events.
    useEffect(() => {
        if (callStatus.socket) {
            serverListeners(callStatus.socket,userDetails.isRespondent, dispatch);       
        }
      
    }, [callStatus.socket])

    useEffect(()=> {
        const startTranslation = async () => {
            if (user.sourceLanguage != null && callStatus.socket && callStatus.remoteStream) {
                // send remoteStream to translation api.
                const socket = callStatus.socket;
                const uuid = JSON.parse(sessionStorage.getItem('userData'))["uuid"]
                const {localB47, languageCode} = await socket.emitWithAck("getCodes", {uuid, isRespondent: user.isRespondent})
                console.log(localB47, )
                translate(callStatus.remoteStream, localB47, languageCode, setTranslatedText, stopTranslation);
            }
        }
        startTranslation();
       
    }, [user.sourceLanguage, callStatus.socket, callStatus.remoteStream])

    // listen for a remoteStream and socket.
    // useEffect(() => {
    //     if (callStatus.socket && callStatus.remoteStream) {
    //         translate(callStatus.remoteStream)
    //     }  
    // }, [callStatus.socket, callStatus.remoteStream])

    const stopTranslation = () => {
        return callStatus.status === "localEnded" || callStatus.status === "remoteEnded";
    }

    // set redux state with offer from inquirer.
    // set remoteDescription.
    useEffect(() => {
        
        const setOffer = async () => {
            const pc = callStatus.peerConnection;
            await pc.setRemoteDescription(callStatus.offer["offer"]);
        }
        if (userDetails.isRespondent === true && callStatus.offer && callStatus.peerConnection) {
            setOffer()
        }
        
    }, [callStatus.offer, callStatus.peerConnection])

    // create an answer.
    useEffect(() => {
        const createAsyncAnswer = async () => {
            const pc = callStatus.peerConnection;
            const answer = await pc.createAnswer();
            await pc.setLocalDescription(answer);
            dispatch(updateCallStatus(pair('hasCreatedAnswer', true)));
            dispatch(addAnswer({ answer: answer }));
            dispatch(updateCallStatus(pair('answer', answer)));

            const uuid = userDetails.uuid
            const socket = callStatus.socket;

            socket.emit('newAnswer', { answer, uuid });
            // send answer to server

        }
        if (callStatus.offer && callStatus.audio === "enabled" && callStatus.video === "enabled" && !callStatus.hasCreatedAnswer && userDetails.isRespondent === true) {
            createAsyncAnswer();
        }
    }, [callStatus.audio, callStatus.video, callStatus.hasCreatedAnswer, callStatus.offer])
    
    // add answer received from respondent
    useEffect(() => {

        // add received answer to local remoteDescription.
        const addReceivedAnswer = async () => {
            const pc = callStatus.peerConnection;
            await pc.setRemoteDescription(callStatus.answer);
        }
        if (userDetails.isRespondent === false && callStatus.hasCreatedAnswer) {
            addReceivedAnswer();
        }
    }, [callStatus.hasCreatedAnswer])
    

   

    // send ICE candidates to server paying attention to who they belong to.
    const sendICECandidatesToServer = (iceCandidate) => {
        const socket = callStatus.socket;
        const uuid = userDetails.uuid;

        // send ICE candidate to the right person depending on whether they are the inquirer or respondent.
        if (userDetails.isRespondent === true) {
            socket.emit("iceFromRespondentToInquirer", { iceCandidate, uuid });
        } else if (userDetails.isRespondent === false) {
            socket.emit("iceFromInquirerToRespondent", { iceCandidate, uuid });
        }
    }

    
    // after peer connection has been established, request
    // for appropriate ice candidates from backend.
    useEffect(() => {
        const requestForIce = async () => {
            console.log("already requesting for ice")
            const socket = callStatus.socket;
            const uuid = userDetails.uuid;
            const pc = callStatus.peerConnection;
        if (userDetails.isRespondent === true) {
            const inquirerIceCandidates = await socket.emitWithAck("requestForInquirerIce", uuid);
            for (let i = 0; i < inquirerIceCandidates.length; i++) {
                pc.addIceCandidate(inquirerIceCandidates[i]);
            }
        } else if (userDetails.isRespondent === false) {
            const respondentIceCandidates = await socket.emitWithAck("requestForRespondentIce", uuid);
            for (let i = 0; i < respondentIceCandidates.length; i++) {
                pc.addIceCandidate(respondentIceCandidates[i])
            }
        }
        }
        if (callStatus.peerConnection && callStatus.socket && callStatus.hasCreatedAnswer) {
            requestForIce();
        }
        
    }, [callStatus.peerConnection, callStatus.socket, callStatus.hasCreatedAnswer])
    
    // establish connection with the servers.
    useEffect(() => {
        const establishConnection = async () => {
            const { socket, userInfo } = await socketConnection(JSON.parse(sessionStorage.getItem('userData')));
            console.log("established connection and set socket to server")
            dispatch(updateCallStatus(pair('socket', socket)));
        }  
        if (!callStatus.socket) {
            establishConnection();
        }
    }, [])

    return (
        <div>
            <div className="container-fluid">
                <ActionButtons largeFeedEl={largeFeedEl} smallFeedEl={smallFeedEl} />
                <div className="container-fluid video-chat-wrapper">
                    {callStatus.status !== "ongoing" ? <div className='info-box call-ended'>{callStatus.status === "localEnded" ? "You": "John Doe"} ended the call.</div> : <></>}
                    <video id="large-feed" autoPlay controls ref={largeFeedEl}></video>
                    <video id="small-feed" autoPlay controls ref={smallFeedEl} muted ></video>
                    <div className='info-box captions-box'>{translatedText}</div>
                </div>
            
            </div>
        </div>
    )
}
export default VideoPage;