1

I have created video chat app using webRTC and Django channel.My app working 90% of time properly when using it in the same network but fail when using it on a different network.I am not able to see a video of remote person when using it on a different network.

I used chrome://webrtc-internals/ to trace my webRTC response and I got iceconnectionstate : failed when called using different networkenter image description here

Below screenshot is taken when I got success in the same network enter image description here

And after success also it gave addIceCandidateFailed error on the same network but video calling works properly and this error comes only in chrome but not in firefox. Below is the screenshot enter image description here

Below is my STUN/TUNE server configuration which is free.I got this from one of the StackOverflow links.

 var peerConnectionConfig = {
        iceServers: [{
                urls: ["turn:173.194.72.127:19305?transport=udp",
                    "turn:[2404:6800:4008:C01::7F]:19305?transport=udp",
                    "turn:173.194.72.127:443?transport=tcp",
                    "turn:[2404:6800:4008:C01::7F]:443?transport=tcp"
                ],
                username: "CKjCuLwFEgahxNRjuTAYzc/s6OMT",
                credential: "u1SQDR/SQsPQIxXNWQT7czc/G4c="
            },
            {
                urls: ["stun:stun.l.google.com:19302"]
            }
        ]
    };

Below is my webRTC javascript code

$(function() {
    var initiator,pc;
    var isSender = false;
    var peerConnectionConfig = {
        iceServers: [{
                urls: ["turn:173.194.XX.127:19305?transport=udp",
                    "turn:[2404:XXXX:XXXX:C01::7F]:19305?transport=udp",
                    "turn:173.194.XX.127:443?transport=tcp",
                    "turn:[2404:XXXX:XXXX:C01::7F]:443?transport=tcp"
                ],
                username: "XXXXXXXXXX",
                credential: "YYYYYYYYYYY"
            },
            {
                urls: ["stun:stun.l.google.com:19302"]
            }
        ]
    };
    $.ajax({
        type: "GET",
        url: '/isRoomExist/?roomName=121' ,
        beforeSend: function() {},
        success: function(data) {
            data = JSON.parse(data);
            initiatorCtrl(data[0].flgInitiator);
        }
    });
    var ws_scheme = window.location.protocol == "https:" ? "wss" : "ws";
    var chatsock = new ReconnectingWebSocket(ws_scheme + '://' + window.location.host + "/chat" + window.location.pathname);

    function initiatorCtrl(event) {
        if (event == "fullhouse") {
            alert("full house");
        }
        if (event == "initiator") {
            initiator = false;
            init();
        }
        if (event == "not initiator") {
            initiator = true;
            init();
        }
    }

    function init() {
        var constraints = {
            audio: true,
            video: true
        };
        getUserMedia(constraints, connect, fail);
    }

    function connect(stream) {
        pc = new RTCPeerConnection(peerConnectionConfig);

        if (stream) {
            pc.addStream(stream);
            $('#local').attachStream(stream);
        }

        pc.onaddstream = function(event) {
            $('#remote').attachStream(event.stream);
            logStreaming(true);
        };
        pc.onicecandidate = function(event) {
            if (event.candidate) {
                chatsock.send(JSON.stringify(event.candidate));
                isSender = true;
            }
        };
        if (initiator) {
            createOffer();
        } else {
            log('waiting for offer...');
        }
        logStreaming(false);

        chatsock.onmessage = function(event) {
            var signal1 = JSON.parse(event.data);
            var signal = JSON.parse(signal1);

            if (isSender) {
                isSender = false
            } else {
                if (signal.sdp) {
                    if (initiator) {
                        receiveAnswer(signal);
                    } else {
                        receiveOffer(signal);
                    }
                } else if (signal.candidate) {
                    pc.addIceCandidate(new RTCIceCandidate(signal));
                }
            }
        };
    }

    function createOffer() {
        pc.createOffer(function(offer) {
            pc.setLocalDescription(offer, function() {
                chatsock.send(JSON.stringify(offer));
                isSender = true;
            }, fail);
        }, fail);
    }

    function receiveOffer(offer) {
        pc.setRemoteDescription(new RTCSessionDescription(offer), function() {
            pc.createAnswer(function(answer) {
                pc.setLocalDescription(answer, function() {
                    chatsock.send(JSON.stringify(answer));
                    isSender = true;
                }, fail);
            }, fail);
        }, fail);
    }

    function receiveAnswer(answer) {
        pc.setRemoteDescription(new RTCSessionDescription(answer));
    }
    function log() {
        console.log(Array.prototype.join.call(arguments, ' '))
        console.log.apply(console, arguments);
    }
    function logStreaming(streaming) {
        $('#streaming').text(streaming ? '[streaming]' : '[..]');
    }
    function fail() {
        console.error.apply(console, arguments);
    }
    jQuery.fn.attachStream = function(stream) {
        this.each(function() {
            this.src = URL.createObjectURL(stream);
            this.play();
        });
    };


});
Ajay
  • 2,483
  • 2
  • 16
  • 27
Arti Berde
  • 1,182
  • 1
  • 11
  • 23

2 Answers2

3

As per your webrtc-internals, you are adding the remote candidates before setting the remote description.

Put the candidate's in a queue, until you receive the remote description.
After setting the remote description,
you can send local candidates to remote user from queue or from onicecandidate
and add the remote candidates to your pc.

Update:
The order of messages is in general not the same as the POST order from the other client because the POSTs are async and the server may handle candidates faster(just relay & smaller size) than offer/answer(need to handle routing/cdr/forking).

Queuing Remote Candidates: We need to process remote offer before adding the candidates.
Queuing Local Candidates: If call/offer is forked to multiple destinations(user logged in mobile's & browser's Or group calling), then only first answer will be accepted by initiator. So all receiving endpoints need to queue local candidates, until their answer got acknowledged.

Reference Example

Ajay
  • 2,483
  • 2
  • 16
  • 27
  • But when I got addIceCandidateFailed I am able to do video calling properly on the same network.I will modify my code according to your suggestion to solve addIceCandidateFailed error.But I didn't understand why it not working on different networks.Thanks for your solution. – Arti Berde May 26 '17 at 10:47
  • 1
    Read https://www.html5rocks.com/en/tutorials/webrtc/infrastructure/ . candidates require to establish connection between peers located across networks. It all depends on Network/NAT conditions. – Ajay May 26 '17 at 10:52
  • I read this previously, and for NAT I am using STUN/TURN.[ {urls: ["turn:173.194.72.127:19305?transport=udp", "turn:[2404:6800:4008:C01::7F]:19305?transport=udp", "turn:173.194.72.127:443?transport=tcp", "turn:[2404:6800:4008:C01::7F]:443?transport=tcp" ], username:"CKjCuLwFEgahxNRjuTAYzc/s6OMT", credential:"u1SQDR/SQsPQIxXNWQT7czc/G4c=" }, {urls:["stun:stun.l.google.com:19302"]} ].I got this from one of stackoverflow links.Should i need to setup my own TURN server for better performance – Arti Berde May 26 '17 at 11:11
  • 1
    No one will provide free turn server, you need to setup your own. ` {urls:["stun:stun.l.google.com:19302"]}` this google stun server you can use, mostly it work in normal network if you set the candidates properly. – Ajay May 26 '17 at 12:46
  • @Ajay Where's your evidence that Chrome generates candidates before setting the localDescription? That would be a spec violation. Queuing should be unnecessary and is the wrong advice here IMHO. - The real answer here seems to be that free turn servers is a myth. – jib May 26 '17 at 19:40
  • 1
    @jib i tested latest canary, candidates are gathered after setLocalDescription only. Recently i read somewhere, they are going to hit stun/turn servers before setLD, to make gathering fast. I updated the answer for queuing. – Ajay May 27 '17 at 05:05
  • @Ajay Browsers can probably save time by hitting the servers early, without changing when candidates are report to the JavaScript. This JavaScript timing invariant is important to ensure that candidates never hit the signaling channel before the offer/answer they belong to. – jib May 27 '17 at 13:28
0

Checking message order as suggested by Ajay really helps.

There is one thing to add: links to diagrams that make order checking easier: SDP packet exchange diagram and ICE candidate exchange diagram

Also there is an excellent article explaining the diagrams: article on mdn.mozillademos.org

Fedorov7890
  • 1,173
  • 13
  • 28