Help me please so slow connect

. I wrote the system but it was connecting too late. I was looking for an example to find out why.

import React, { Component } from ‘react’;
import { View, StyleSheet, Image, TouchableHighlight, Dimensions } from “react-native”;
import {
RTCPeerConnection,
RTCIceCandidate,
RTCSessionDescription,
RTCView,
MediaStream,
MediaStreamTrack,
mediaDevices
} from ‘react-native-webrtc’;

const screenWidth = Math.round(Dimensions.get(‘window’).width);
const screenHeight = Math.round(Dimensions.get(‘window’).height);

class Videocall extends Component {
static navigationOptions = ({ navigation }) => {
return {
title: navigation.getParam(‘username’,""),
};
};

constructor(){
    super();
    this.handleJoin=this.handleJoin.bind(this)
    this.setupWebRTC=this.setupWebRTC.bind(this)
    this.onConnectionStateChange=this.onConnectionStateChange.bind(this)
    this.onAddStream=this.onAddStream.bind(this)
    this.onIceCandidate=this.onIceCandidate.bind(this)
    this.handleOffer=this.handleOffer.bind(this)
    this.onReceiveOffer=this.onReceiveOffer.bind(this)
    this.onReceiveAnswer=this.onReceiveAnswer.bind(this)
    this.state={
        user:{},
        socket:{},
        caller:false,
        localStreamURL:null,
        remoteStreamURL:null,
        iceConnectionState:'',
        iceCandidates:[],
        isAnswerReceived:false,
        isOfferReceived:false,
        offer:{},
        answer:{},
        localVideo:{},
        remoteVideo:{},
        localVideoStream:{},
        desc:""
    }
}

async setupWebRTC() {
    const configuration = {"iceServers": [{"url": "stun1:stun.l.google.com:19302"}]};
    const pc = new RTCPeerConnection(configuration);
    pc.onconnectionstatechange=this.onConnectionStateChange
    pc.onaddstream=this.onAddStream
    pc.onicecandidate=this.onIceCandidate


    pc.addStream(this.state.localVideoStream)
    console.log("localstream",this.state.localVideoStream);
    this.pc = pc;
}

async handleJoin(e) {
    console.log('***** HANDLE JOIN ****');
    await this.setupWebRTC();
    const { pc } = this;

    try {
        // Create Offer
        pc.createOffer({offerToReceiveVideo:true,
            offerToReceiveAudio:true}).then(desc => {
            pc.setLocalDescription(desc).then(() => {
                console.log("Sdp",desc);
                this.setState({desc});
            });
        });
    } catch (error) {
        console.log(error);
    }
}

onConnectionStateChange(e) {
    console.log("onConnectionStateChange",e);
    this.setState({
        iceConnectionState: e.target.iceConnectionState
    })
}

onAddStream(e) {
    console.log("onAddStream",e.stream.toURL());
    console.log("onAddStream full",e.stream);
    this.setState({
        remoteVideo:e.stream,
        remoteStreamURL: e.stream.toURL()
    })
    this.remoteStream = e.stream
}

onIceCandidate(e) {
    const { candidate } = e;
    if (candidate) {
        console.log('BURASI ÇALIŞIYOR !!!',this.state.iceCandidates.length);
        const { iceCandidates } = this.state;
        if (Array.isArray(iceCandidates)) {
            this.setState({
                iceCandidates: [...iceCandidates, candidate]
            })
        } else {
            this.setState({
                iceCandidates: [candidate]
            })
        }
    } else {
        if (this.state.iceCandidates.length > 1) {
            console.log('BURASI ÇALILTI !!!');
            //send this to signaling server
            let offerOrAnswer = {
                receiverUserId:this.props.navigation.getParam("receiver"),
                type: this.state.isOfferReceived ? 'answer' : 'offer',
                payload: {
                    description: this.pc.localDescription,
                    iceCandidates: this.state.iceCandidates
                }
            }
            console.log("offerOrAnswer", offerOrAnswer);
            // send offer to signaling server
            if (offerOrAnswer.type == "offer") {
                console.log("offerOrAnswer", offerOrAnswer.type);
                setTimeout(() => {
                    this.state.socket.emit('offer', JSON.stringify(offerOrAnswer));
                }, 5000);
                console.log("emit called");
            } else {
                this.state.socket.emit('answer', JSON.stringify(offerOrAnswer));
            }
        } else {
            console.error("No candidates found");
        }
    }
}

onReceiveOffer(offer) {
    this.setState({
        offer:JSON.parse(offer),
        isOfferReceived: true
    }, () => {
        console.log("offer received", offer)
        this.handleOffer();
    })
}

handleOffer() {
    const { payload } = this.state.offer;
    this.setupWebRTC();

    const { pc } = this;
    var offerSdp = { "sdp": payload.description.sdp, "type": "offer" };
    console.log("offerSdp",offerSdp);

    pc.setRemoteDescription(new RTCSessionDescription(offerSdp))


    if (Array.isArray(payload.candidates)) {
        payload.candidates.forEach((c) => peer.addIceCandidate(new RTCIceCandidate(c)))
    }
    try {
        // Create Offer
        pc.createAnswer().then(answer => {
            pc.setLocalDescription(answer).then(() => {
                // Send pc.localDescription to peer
                console.log("answer generated",answer);
                this.setState({answer});
            });
        });
    } catch (error) {
        console.log(error);
    }
}

onReceiveAnswer(answer) {
    const { payload } = JSON.parse(answer);
    console.log(" onReceiveAnswer payload",payload)
    var answerSdp = { "sdp": payload.description.sdp, "type": "answer" };
    //set answersdp to current peer RemoteDescription.
    this.pc.setRemoteDescription(new RTCSessionDescription(answerSdp))
    payload.iceCandidates.forEach(c => this.pc.addIceCandidate(new RTCIceCandidate(c)))
    this.setState({
        answer:JSON.parse(answer),
        isAnswerReceived: true
    }, () => {
        console.log("answerReceived")
    })
}

componentDidMount(){
    const self = this;
    const { navigation } = this.props;
    const user = navigation.getParam('user', {});
    const caller = navigation.getParam('caller', false);
    const socket = navigation.getParam('socket', {});
    this.setState({
        user,
        socket
    });

    socket.on('offer', function (offer) {
        console.log("Offeronsocket",offer)
        self.onReceiveOffer(offer);
    });

    socket.on('answer', function(answer){
        console.log("answeronsocket called",answer)
        self.onReceiveAnswer(answer);
    });

    // WebRTC getUserMedia setup
    let isFront = true;
    mediaDevices.enumerateDevices().then(sourceInfos => {
        console.log(sourceInfos);
        let videoSourceId;
        for (let i = 0; i < sourceInfos.length; i++) {
            const sourceInfo = sourceInfos[i];
            if(sourceInfo.facing == (isFront ? "front" : "back")) {
                videoSourceId = sourceInfo.deviceId;
                console.log(sourceInfo);
            }
        }
        mediaDevices.getUserMedia({
            audio: true,
            video: {
                mandatory: {
                    minWidth: 500, // Provide your own width, height and frame rate here
                    minHeight: 300,
                    minFrameRate: 30
                },
                facingMode: (isFront ? "user" : "environment"),
                optional: (videoSourceId ? [{sourceId: videoSourceId}] : [])
            }
        })
            .then(stream => {
                // Got stream!
                console.log("getUserMedia----stream",stream);
                this.setState({
                    localVideoStream:stream,
                    localStreamURL: stream.toURL()
                })
            }).then(()=>{
            if(caller){
                this.handleJoin();
            }
        })
            .catch(error => {
                // Log error
                console.log(error);
            });
    });

    //handling join call
    console.log(caller);

}

onCallHangUp=()=>{
    this.props.navigation.goBack();
}

render() {
    return (
        <View style={styles.root}>
            <RTCView streamURL={this.state.remoteStreamURL} style={styles.localVideo} />
            <RTCView streamURL={this.state.localStreamURL} style={styles.remoteVideo} />
            <View style={styles.buttonContainer}>
                <TouchableHighlight style={[styles.button, styles.buttonCall]} onPress={() => this.onCallHangUp()}>
                    <Image style={styles.icon} source={{uri: 'https://png.icons8.com/call/win8/100/ffffff'}}/>
                </TouchableHighlight>
            </View>
        </View>
    )
}

}

export default Videocall;

const remoteVideoWidth=Math.round(screenWidth-114);

const styles = StyleSheet.create({
root: {
flex: 1,
backgroundColor: “rgb(255,255,255)”
},
localVideo: {
top: 0,
left: 0,
width: screenWidth,
height: screenHeight,
backgroundColor:“black”,
position: “absolute”
},
remoteVideo: {
top: 0,
left: remoteVideoWidth,
width: 114,
height: 170,
backgroundColor: “rgba(230, 230, 230,1)”,
position: “absolute”
},
buttonContainer:{
top: screenHeight-170,
left: screenWidth/2.4,
flexDirection:‘row’,
marginTop:20,
},
button: {
width:60,
height:60,
flexDirection: ‘row’,
justifyContent: ‘center’,
alignItems: ‘center’,
marginBottom:20,
borderRadius:30,
margin:10,
shadowColor: ‘black’,
shadowOpacity: .8,
shadowOffset: {
height:2,
width:-2
},
elevation:4,
},
buttonCall: {
backgroundColor: “red”,
},
icon: {
width:35,
height:35,
}
});

A common issue would be the ice candidate processing.
Make sure the candidates are stored and only added after the answer has been created.
There is a race condition where sometimes candidates will go through the system quickly and try to process before the answer has been created.
That is of-course if you are keeping to the standards of trickle ice.

Creating an offer via the onnegotiationneeded hook for the PeerConnection and only for the person creating the call seems to be a lot more reliable from what i’ve tested.
You might also want to check the logging on the oniceconnectionstatechange hook to see if things are actually getting to the connected state.

What i would also suggest is making sure you use a TURN server instead of just a STUN server as that will help negotiate the connection faster and more reliably especially if the networking between each device is erratic.

1 Like

Thanks for comment but I do not know what to do. Sorry my english is bad.
do you think my code is good?

i think there is a problem here
onIceCandidate(e) {
const { candidate } = e;
console.log(candidate);

    if (candidate) {
        console.log('BURASI ÇALIŞIYOR !!!',this.state.iceCandidates.length);
        const { iceCandidates } = this.state;
        console.log('state candidate --> ',iceCandidates);
        if (Array.isArray(iceCandidates)) {
            this.setState({
                iceCandidates: [...iceCandidates, candidate]
            })
        } else {
            this.setState({
                iceCandidates: [candidate]
            })
        }
    } else {
        if (this.state.iceCandidates.length > 1) {
            console.log('BURASI ÇALILTI !!!');
            //send this to signaling server
            let offerOrAnswer = {
                receiverUserId:this.props.navigation.getParam("receiver"),
                type: this.state.isOfferReceived ? 'answer' : 'offer',
                payload: {
                    description: this.pc.localDescription,
                    iceCandidates: this.state.iceCandidates
                }
            }
            console.log("offerOrAnswer", offerOrAnswer);
            // send offer to signaling server
            if (offerOrAnswer.type == "offer") {
                console.log("offerOrAnswer", offerOrAnswer.type);
                setTimeout(() => {
                    this.state.socket.emit('offer', JSON.stringify(offerOrAnswer));
                }, 5000);
                console.log("emit called");
            } else {
                this.state.socket.emit('answer', JSON.stringify(offerOrAnswer));
            }
        } else {
            console.error("No candidates found");
        }
    }
}![Ekran Resmi 2020-01-29 15.39.57|689x273](upload://oUiTo1n8Eo3b6nwbX7Hy7Ky8IWP.png)

Your code could do with cleaning up some what to make it more understandable.
For starters though, you should move the offer/answer handing away from the candidate hook.
You can do it that way but i wouldn’t advise it as a clean method.

Is there an example I can examine?

Not that i know of but i could provide a few basic snippets from a private project.

I would be very pleased

For the offer creation i’d go for something like this.

// peer connection constraints.
var SessionConstraints = {
     offerToReceiveAudio: true,
     offerToReceiveVideo: true,
     voiceActivityDetection: true
};

// add this to your peer connection creation but only for the person creating the call.
pc.onnegotiationneeded = this.handleNegotiation;

// create this function to handle creating the offer.
async handleNegotiation() {
     let offerDescription = await this.pc.createOffer( SessionConstraints );
     await this.pc.setLocalDescription( offerDescription );

     // add some code here to send the session description.
}

The way i handle ice candidates is as follows give/take.
You need to call this function when receiving candidates on either end.

var candidates = [];

handleRemoteCandidate( candidate ) {
	if ( this.pc == null || ( this.pc != null && this.pc.remoteDescription == null ) ) {
		return candidates.push( candidate );
	};

	iceCandidate = new RTCIceCandidate( candidate );
	this.pc.addIceCandidate( candidate );
};

Then what i do is have another function which i call after creating the answer.
For example, you’d call this function on the client receiving the call directly after creating the answer.
But you would also call this on the person who created the call after you’ve set the remote session description. Can be a crude way to deal with it but works.

processCandidates() {
	if ( candidates.length < 1 ) { return; };

	for ( const candidate of candidates ) {
		let iceCandidate = new RTCIceCandidate( candidate );
		this.pc.addIceCandidate( iceCandidate );
	};

	candidates = [];
};

Actually, I can connect successfully. But a second person opens the call 1 minute after participating.
onIceCandidate(e) {
const { candidate } = e;
console.log(candidate);

    if (candidate) {
        console.log('BURASI ÇALIŞIYOR !!!',this.state.iceCandidates.length);
        const { iceCandidates } = this.state;
        console.log('state candidate --> ',iceCandidates);
        this.setState({
            iceCandidates: [candidate]
        })
        if (Array.isArray(iceCandidates)) {
            this.setState({
                iceCandidates: [...iceCandidates, candidate]
            })
        } else {
            this.setState({
                iceCandidates: [candidate]
            })
        }
    } else {
        if (this.state.iceCandidates.length > 1) {
            console.log( this.state.isOfferReceived);
            //send this to signaling server
            let offerOrAnswer = {
                receiverUserId:this.props.navigation.getParam("receiver"),
                type: this.state.isOfferReceived ? 'answer' : 'offer',
                payload: {
                    description: this.pc.localDescription,
                    iceCandidates: this.state.iceCandidates
                }
            }
            // send offer to signaling server
            if (offerOrAnswer.type == "offer") {
                this.state.socket.emit('offer', JSON.stringify(offerOrAnswer))
                /*
                setTimeout(() => {
                    this.state.socket.emit('offer', JSON.stringify(offerOrAnswer));
                }, 5000);
               */
            } else {
                this.state.socket.emit('answer', JSON.stringify(offerOrAnswer));
            }
        } else {
            console.error("No candidates found");
        }
    }
}

it works late here

The main reason i included some simple code for the candidate handling was just to prevent an edge case when dealing with trickle ice but doesn’t mean you need to handle candidates in that way.

What i’d suggest is using a TURN server along side the STUN server you’re already using.
See if that cuts your connection time down.
Also if things are working correctly you shouldn’t need any timers/delays.