Vaibhav Verma
Vaibhav Verma

Reputation: 147

why is it taking two offers to establish webrtc connection in chrome?

I am trying to build an video and audio calling application using webrtc, but i am facing a strange issue here i.e the connection is not getting established in the first time but it gets established when i send the offer again. I have tried to print the connectionState of the peer, it stays 'new' in the first click but changes to connecting and connected when the offer is sent again. I am new to webrtc, i just cannot figure it out at all.

following is the code

import socketContext from '../components/socketContext';
import fetch from 'isomorphic-unfetch';
import params from '../config/params';
import SearchBar from '../components/SearchBar';
import withAuth from '../components/withAuth';
import Cookie from 'js-cookie';

class VideoConf extends React.Component{

    constructor(props){
        super(props);
        this.state = {
            friends: [],
            cameraStream: undefined,
            screenStream: undefined
        }
        this.RTCPeerConnection = undefined;
        this.RTCSessionDescription = undefined;
        this.peerConnection = undefined;
        this.configuration = {"iceServers": [{"urls": ["stun:stun.l.google.com:19302"]}]};
    }

    static contextType = socketContext;

    startConnection = async ()=>{
        try{
            const mediaStream = await navigator.mediaDevices.getUserMedia({audio:true,video:true});

            const localVideo = document.getElementById('local-video');

            localVideo.srcObject = mediaStream;
            localVideo.onloadedmetadata = function(e){
                localVideo.play();
            }

            this.peerConnection.ontrack = function ({ streams: [stream] }) {
                const remoteVideo = document.getElementById("remote-video");
                if (remoteVideo) {
                    remoteVideo.srcObject = stream;

                    remoteVideo.onloadedmetadata = function(e){
                        remoteVideo.play();
                    }
                }
            };

            mediaStream.getTracks().forEach(track => this.peerConnection.addTrack(track, mediaStream));
            window.videoStream = mediaStream;
            window.pc = this.peerConnection;
            this.setState({
                cameraStream: mediaStream
            })

        }catch(err){
            alert(err.message);
        }
    }

    shareScreen =  async ()=>{
        const mediaStream = await navigator.mediaDevices.getDisplayMedia({
            video: {
                cursor: 'always',
                displaySurface: 'browser'
            }
        });
        const localVideo = document.getElementById('local-video');
        localVideo.srcObject = mediaStream;
        localVideo.onloadedmetadata = function(e){
            localVideo.play();
        }

        this.peerConnection.getSenders().map(sender =>{
            if(sender.track.kind == 'video'){
                sender.replaceTrack(mediaStream.getTracks()[0]);
            }
        });

        this.setState({
            screenStream: mediaStream
        });
    }

    componentDidMount = async ()=>{

        const response = await fetch(`${params.hostname}/api/get-friends`,{
            headers:{
                Authorization:`Bearer ${Cookie.get('token')}`
            }
        })
        if(response.ok){
            const json = await response.json();
            this.setState({
                friends: json.data
            });
        }

        this.RTCPeerConnection = window.RTCPeerConnection;
        this.RTCSessionDescription = window.RTCSessionDescription;
        this.peerConnection = new this.RTCPeerConnection(this.configuration);

        let socket = this.context.socket;

        socket.on("call-made", async data => {
            await this.peerConnection.setRemoteDescription(
                new this.RTCSessionDescription(data.offer)
            );
            const answer = await this.peerConnection.createAnswer();
            await this.peerConnection.setLocalDescription(new this.RTCSessionDescription(answer));

            socket.emit("make-answer", {
                answer,
                to: data.socket
            });
        });

        socket.on("answer-made", async data => {
            await this.peerConnection.setRemoteDescription(
                new this.RTCSessionDescription(data.answer)
            );
        });

        // this.peerConnection.addEventListener("negotiationneeded", ev => {
        //     alert('ping it on');
        //     this.callUser(0);
        // });

        this.startConnection();

    }

    callUser = async (i) => {
        let socket = this.context.socket;
        let frndId = this.state.friends[i];

        const offer = await this.peerConnection.createOffer();
        await this.peerConnection.setLocalDescription(new this.RTCSessionDescription(offer));
        socket.emit('call-user',{
            offer,
            to: frndId.id
        });
    }

    render(){
        let {friends} = this.state;
        return(
            <div>
                <div className="container">
                    <div className = "row no-gutters">

                        <div className = "col-12 col-md-3 text-center p-3 border" style = {{minHeight:'60vh'}}>
                            <SearchBar handleSearch = {this.handleSearch} />
                            <div>
                                {
                                    friends.map((frnd,i)=>{
                                        return (
                                            <div key = {i} onClick = {()=>this.callUser(i)} style = {{cursor:'pointer'}}>
                                                {frnd.name}
                                            </div>
                                        );                                        
                                    })
                                }
                            </div>
                        </div>

                        <div className = "col-12 col-md-9 text-center">
                            <div>
                                <button className = "btn btn-success" onClick = {this.shareScreen}>Share screen</button>
                            </div>
                            <div className="video-container">
                                <video autoPlay id="remote-video" style={{width:'25vw',height:'25vh'}} ></video>
                                <video autoPlay muted id="local-video" style={{width:'25vw',height:'25vh'}} ></video>
                            </div>
                        </div>

                    </div>
                </div>
            </div>
        )
    }
}

export default withAuth(VideoConf);

i am using sockets to send and answer the offer and i am sure there is not problem in the backend part. It would be great help if someone can figure it out.

Upvotes: 2

Views: 902

Answers (1)

Sean DuBois
Sean DuBois

Reputation: 4232

You don't have a handler for on icecandidate so your browsers don't know the remote addresses to communicate on.

It works the second time because some candidates have been gathered, this process is called trickle ice

Upvotes: 3

Related Questions