1

I was building Video Call App using webRTC for my thesis. I already build the app and it works perfectly if I use the same device (different tab browser/different browser). Then, when I try to call another user that logged in on another device, the peer connection always failed. My app was already online at a public address. It happens in my local too. I already used Turn server that work when I tested it on here . In that case, where is my mistake?

Here, the flow how I create and handle the peer connection :

// Call Function
export const call = async (from, to) => {
    let configuration = {
        iceServers: [
            {
                urls: "stun:stun1.l.google.com:19302"
            },
            {
                urls: "turn:a.relay.metered.ca:80",
                username: "xxxxxxxxxx",
                credential: "xxxxxxxxxx",
            },
            {
                urls: "turn:a.relay.metered.ca:80?transport=tcp",
                username: "xxxxxxxxxx",
                credential: "xxxxxxxxxx",
            },
            {
                urls: "turn:a.relay.metered.ca:443",
                username: "xxxxxxxxxx",
                credential: "xxxxxxxxxx",
            },
            {
                urls: "turn:a.relay.metered.ca:443?transport=tcp",
                username: "xxxxxxxxxx",
                credential: "xxxxxxxxxx",
            },
        ],
    };

    const peer = new RTCPeerConnection(configuration);

    const stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: true })
    peer.addTrack(stream.getTracks()[0], stream)

    const offer = await peer.createOffer();

    let message = {
        id : 'call',
        from : from,
        to : to,
        sdpOffer : offer,
        state: 'req_calling'
    };
    sendMessage(message);
    
    await peer.setLocalDescription(offer);
    WebRtcPeer.addPeer(peer)
    
    peer.onicecandidate = function (event) {
        if (event.candidate) {
            const message = {
                id : 'onIceCandidate',
                candidate : event.candidate,
                to : to,
                from: from
            }
            sendMessage(message);
        }
    }

    // get to know when connected to peer
    peer.onconnectionstatechange = function (event) {
        console.log('masuk sono')
        if (peer.connectionState === 'connected') {
            const message = {
                id: 'peerConnected',
                from: localStorage.getItem('me'),
                to: localStorage.getItem('they')
            }
            sendMessage(message)
        }
    }
}
// Answering Call Function

export const incomingCall = async (message) => {
    const configuration = {
        iceServers: [
            {
                urls: "stun:stun1.l.google.com:19302"
            },
            {
                urls: "turn:a.relay.metered.ca:80",
                username: "xxxxxxxxxx",
                credential: "xxxxxxxxxx",
            },
            {
                urls: "turn:a.relay.metered.ca:80?transport=tcp",
                username: "xxxxxxxxxx",
                credential: "xxxxxxxxxx",
            },
            {
                urls: "turn:a.relay.metered.ca:443",
                username: "xxxxxxxxxx",
                credential: "xxxxxxxxxx",
            },
            {
                urls: "turn:a.relay.metered.ca:443?transport=tcp",
                username: "xxxxxxxxxx",
                credential: "xxxxxxxxxx",
            },
        ],
    }

    // create peer using RTC
    const peer = new RTCPeerConnection(configuration);

    peer.setRemoteDescription(new RTCSessionDescription(message.sdpOffer))

    const stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: true })
    peer.addTrack(stream.getTracks()[0], stream)

    const answer = await peer.createAnswer();

    await peer.setLocalDescription(answer);

    WebRtcPeer.addPeer(peer)

    peer.onicecandidate = function (event) {
        if (event.candidate) {
            const msg = {
                id : 'onIceCandidate',
                candidate : event.candidate,
                to : message.from,
                from: message.to
            }
            sendMessage(msg);
        }
    }

    // get to know when connected to peer
    peer.onconnectionstatechange = function (event) {
        if (peer.connectionState === 'connected') {
            const message = {
                id: 'peerConnected',
                from: localStorage.getItem('me'),
                to: localStorage.getItem('they')
            }
            sendMessage(message)
        }
    }

    let response = {
        id: 'incomingCallResponse',
        from: message.from,
        callResponse: 'accept',
        sdpOffer: answer,
        state: 'acc_calling'
    }
    sendMessage(response);
    localStorage.setItem('they', message.from)
}

Every candidate that sends to peer user, I save it to CandidatesQueue in my signalling server because of waiting calling state, if the call is accepted and peer connection on the peer user was created, then I start to sending all the candidates.

If that information is still not enough, here is the repo that I work on : client repo, server repo

I was confused where's the mistake, is it the Turn server ? or is it my code?

if you want to try my app, here's the link : myApp

you can register an account and log in with your registered account.

Thanks!

UPDATE

After doing testing over and over again, it could be connected, but sometimes can't. I already try with 3 different devices, but the behavior was same, sometimes admin1 can be connected to client1, but when client1 tries to connect to admin1 it can't. Also with another user, the behavior was really random, I think the problem was on my laptop1, but when I try the other laptop, the problem occur with random behavior. Any idea?

agam theos
  • 39
  • 6
  • Your code doesn't show any addIcecandidate calls corresponding to the onicecandidate ones, check for those. – Philipp Hancke Jun 05 '23 at 08:49
  • @PhilippHancke yes, I realize that before, and i change the method, decide not using CandidatesQueue anymore, but the error was like this, ```Uncaught (in promise) DOMException: Failed to execute 'addIceCandidate' on 'RTCPeerConnection': The remote description was null``` , Maybe because the remoteDescription for the caller still null. But still, the behaviour was same. I was confused, sometimes callee doesn't need to call addIceCandidate() but the connection already connect. – agam theos Jun 05 '23 at 10:12

1 Answers1

1

Have you used the "perfect negotiation" logic when establish a connection?

You may refer to the following page for detail:

https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API/Perfect_negotiation

The KNVB
  • 3,588
  • 3
  • 29
  • 54
  • Thanks, I will try to create following the perfect negotiation logic – agam theos Jun 05 '23 at 10:14
  • Hi, I already following the instruction above. It works!, the connection always connected, but the problem now is the connection always need about 40 seconds to peer has done connected. Is that normal ? – agam theos Jun 08 '23 at 03:26
  • It may depend on your internet connection speed. Or you may have your own sturn/turn server. Btw, would you give me a tick If my solution works properly,? – The KNVB Jun 08 '23 at 06:11