WebRTC iceConnectionState still in 'checking' state
Asked Answered
K

1

7

I want to create simple video chat app. I use WebRTC and WebSockets with Node.js. I want to connect two devicaes in the same network but iceConnectionState stay on 'checking' state ale the time. What could be the problem?

EDITED

server.js

//var uuid = require('uuid');

var fs = require('fs');
var webSocketServer = require('websocket').server;

var cfg = {
    ssl: false,
    port: 15555,
    ssl_key: '/etc/apache2/ssl/apache.key',
    ssl_cert: '/etc/apache2/ssl/apache.crt'
};

var httpServer = (cfg.ssl) ? require('https') : require('http');

var server = null;

if(cfg.ssl) {
    server = httpServer.createServer(
        {
            key: fs.readFileSync(cfg.ssl_key),
            cert: fs.readFileSync(cfg.ssl_cert)
        }, function() {} ).listen(cfg.port);
} else {
    server = httpServer.createServer().listen(cfg.port);
}

var wsServer = new webSocketServer({
    httpServer: server
});

rooms = {};

// this is executed each time the websocket
// server receives an request
wsServer.on('request', function(request) {

    // allow all incoming connections
    var connection = request.accept(null, request.origin);


    // here we read the incoming messages and try to parse them to JSON
    connection.on('message', function(message) {
        // try to parse JSON
        try {
            var data = JSON.parse(message.utf8Data);
        } catch (e) {
            console.log('This does not look like valid JSON');
        }

        // if JSON is valid process the request
        if (data !== undefined && data.type !== undefined) {
            switch (data.type) {
                case 'createRoom':
                    var roomName = data.roomName;
                    console.log('CREATE_ROOM request received');

                    rooms[roomName] = {
                        creatorConnection: connection,
                        partnerConnection: false,
                    }

                    var data = {
                        type: 'roomCreated',
                        payload: roomName
                    };
                    return send(rooms[roomName].creatorConnection, data);
                    break;
                case 'offer':
                    console.log('OFFER received from client');

                    if (rooms[data.roomName].partnerConnection) {
                        // send error to user
                        var data = {
                            type: 'error',
                            payload: 'room is already full'
                        };
                        return send(connection, data);
                    }
                    rooms[data.roomName].partnerConnection = this;

                    console.log('OFFER send to host');
                    return send(rooms[data.roomName].creatorConnection, data);
                    break;
                    // send to other guy
                default:
                    if (this === rooms[data.roomName].partnerConnection) {
                        console.log('send to creator : ' + data.type);
                        return send(rooms[data.roomName].creatorConnection, data);
                    }
                    console.log('send to parther : ' + data.type);
                    return send(rooms[data.roomName].partnerConnection, data);
                    break;
            }
        }
        // if JSON is invalid or type is missing send error
        else {
            var data = {
                type: 'error',
                payload: 'ERROR FROM SERVER: Incorrect data or no data received'
            };
            send(connection, data);
        }
    });

    // this function sends data to the other user
    var send = function(connection, data) {
        try {
            connection.sendUTF(JSON.stringify(data));
        } catch (e) {
            console.log('\n\n!!!### ERROR while sending message ###!!!\n');
            console.log(e + '\n');
            return;
        }
    };
});

client.js

function WebRTC() {

    var wsServer = false;
    var localStream = false;
    var remoteStream = false;
    var peerConnection = false;
    var roomName = null;
    var otherSDP = false;
    var othersCandidates = []; // other guy's icecandidates

    var socketEvent = document.createEvent('Event');
    socketEvent.initEvent('socketEvent', true, true);

    var mediaConstraints = {
        audio: true,
        video: true
    };

    var peerConnectionConfig = {
        iceServers: [
            {url : 'stun:stun.l.google.com:19302'},
            {url : 'stun:stun.anyfirewall.com:3478'},
            {url : 'turn:turn.bistri.com:80',
                credential: 'homeo',
                username: 'homeo'},
            {url : 'turn:turn.anyfirewall.com:443?transport=tcp',
                credential: 'webrtc',
                username: 'webrtc'}
        ]
    };

    var offerAnswerConstraints = { mandatory: {
        OfferToReceiveAudio: true,
        OfferToReceiveVideo: true
    }};

    var sendToServer = function(data) {
        try {
            wsServer.send(JSON.stringify(data));
            return true;
        } catch (e) {
            logError(e);
            return false;
        }
    };

    var createRTCIceCandidate = function(candidate) {
        var iceCandidate;

        debug(candidate);
        debug(JSON.parse(candidate));

        if (typeof(webkitRTCIceCandidate) === 'function') {
            iceCandidate = new webkitRTCIceCandidate(candidate);
        } if (typeof(mozRTCIceCandidate) === 'function') {
            iceCandidate = new mozRTCIceCandidate((candidate));
        } else if (typeof(RTCIceCandidate) === 'function') {
            iceCandidate = new RTCIceCandidate(candidate);
        }

        return iceCandidate;
    };

    var createRTCSessionDescription = function(description) {
        var newSdp;

        if (typeof(RTCSessionDescription) === 'function') {
            newSdp = new RTCSessionDescription(description);
        } else if (typeof(webkitRTCSessionDescription) === 'function') {
            newSdp = new webkitRTCSessionDescription(description);
        } else if (typeof(mozRTCSessionDescription) === 'function') {
            newSdp = new mozRTCSessionDescription(description);
        }

        return newSdp;
    };

    var getRTCPeerConnection = function(stream) {
        var peerConnection = null;

        if (typeof(RTCPeerConnection) === 'function') {
            peerConnection = new RTCPeerConnection(peerConnectionConfig);
        } else if (typeof(webkitRTCPeerConnection) === 'function') {
            peerConnection = new webkitRTCPeerConnection(peerConnectionConfig);
        } else if (typeof(mozRTCPeerConnection) === 'function') {
            peerConnection = new mozRTCPeerConnection(peerConnectionConfig);
        }

        debug("Creating new RTCPeerConnection");

        peerConnection.addStream(stream);

        peerConnection.onaddstream = function(e) {
            debug("Remote stream received");

            remoteStream = e.stream;

            socketEvent.eventType = 'streamAdded';
            document.dispatchEvent(socketEvent);
        };

        peerConnection.onicecandidate = function(event) {
            debug("Retrieving ICE data status changed : " + event.target.iceGatheringState)

            var data = {
                type: 'iceCandidate',
                roomName: roomName,
                payload: event
            };

            sendToServer(data);
        };

        peerConnection.oniceconnectionstatechange = function(event) {
            debug("ICE connection status changed : " + event.target.iceConnectionState)
        };

        return peerConnection;
    };

    var setIceCandidates = function(iceCandidate) {
        // push icecandidate to array if no SDP of other guys is available
        if (!otherSDP) {
            othersCandidates.push(iceCandidate);
        }
        // add icecandidates immediately if not Firefox & if remoteDescription is set
        if (otherSDP && iceCandidate.candidate && iceCandidate.candidate !== null) {
            peerConnection.addIceCandidate(createRTCIceCandidate(iceCandidate.candidate));
        }
    };

    var handshakeDone = function() {
        console.log('handshakeDone');
        peerConnection.setRemoteDescription(createRTCSessionDescription(otherSDP), function() {
            // add other guy's ice-candidates to connection
            for (var i = 0; i < othersCandidates.length; i++) {
                if (othersCandidates[i].candidate) {
                    peerConnection.addIceCandidate(ceateRTCIceCandidate(othersCandidates[i].candidate));
                }
            }
            // fire event
            socketEvent.eventType = 'p2pConnectionReady';
            document.dispatchEvent(socketEvent);
        }, logError);

    };

    var createOffer = function() {
        peerConnection = getRTCPeerConnection(localStream);

        debug('Offer creating');
        peerConnection.createOffer(function(description) {
            debug('Offer created');
            debug('Local description setting');

            peerConnection.setLocalDescription(description, function() {
                debug('Local description set');
                var data = {
                    type: 'offer',
                    roomName: roomName,
                    payload: description
                };

                sendToServer(data);
            }, logError);
        }, logError);
    };

    var createAnswer = function() {
        peerConnection = getRTCPeerConnection(localStream);

        debug('Offer answering');
        debug('Remote description setting');

        peerConnection.setRemoteDescription(createRTCSessionDescription(otherSDP), function () {
            debug('Remote description set');
            debug('Answer creating');

            peerConnection.createAnswer(function(description) {
                debug('Answer created');
                debug('Local description setting');

                peerConnection.setLocalDescription(description, function() {
                    debug('Local description set');

                    for (var i = 0; i < othersCandidates.length; i++) {
                        if (othersCandidates[i].candidate) {
                            peerConnection.addIceCandidate(ceateRTCIceCandidate(othersCandidates[i].candidate));
                        }
                    }

                    // send SDP to other guy
                    var data = {
                        type: 'answer',
                        roomName: roomName,
                        payload: description
                    };

                    sendToServer(data);
                }, logError);
            }, logError);
        }, logError);
    };

    this.connectToSocket = function(wsUrl) {
        wsServer = new WebSocket(wsUrl);

        wsServer.onopen = function(event) {
            console.log((new Date()) + ' Connection successfully established');
        };

        wsServer.onerror = function(e) {
            console.log((new Date()) + ' WebSocket connection error: ');
            logError(e);
        };

        wsServer.onclose = function(event) {
            console.log((new Date()) + ' Connection was closed');
            logError(e);
        };

        wsServer.onmessage = function(message) {
            try {
                var data = JSON.parse(message.data);
            } catch (e) {
                logError(e);
                return;
            }

            switch (data.type) {
                case 'roomCreated':
                    roomName = data.payload;
                    socketEvent.eventType = 'roomCreated';
                    document.dispatchEvent(socketEvent);
                    break;
                case 'offer':
                    otherSDP = data.payload;
                    createAnswer();
                    break;
                case 'answer':
                    otherSDP = data.payload;
                    handshakeDone();
                    break;
                case 'iceCandidate':
                    setIceCandidates(data.payload);
                    break;
            }
        };
    };

    this.getRoomName = function() {
        return roomName;
    };

    this.createRoom = function(roomName) {
        var media = getMedia();

        var onSuccess = function(stream) {
            localVideo.attr('src', URL.createObjectURL(stream));
            localStream = stream;

            var data = {
                type: 'createRoom',
                roomName: roomName,
                payload: false
            };

            return sendToServer(data)
        };

        media(mediaConstraints, onSuccess, logError);
    };

    this.joinRoom = function(rName) {
        var media = getMedia();

        var onSuccess = function(stream) {
            localVideo.attr('src', URL.createObjectURL(stream));
            localStream = stream;
            //TODO
            roomName = rName;

            createOffer();
        };

        media(mediaConstraints, onSuccess, logError);
    };


    var getMedia = function() {
        var media = null;

        if (navigator.getUserMedia) {;
            media = navigator.getUserMedia.bind(navigator);
        } else if (navigator.webkitGetUserMedia) {
            media = navigator.webkitGetUserMedia.bind(navigator);
        } else if (navigator.mozGetUserMedia) {
            media = navigator.mozGetUserMedia.bind(navigator);
        }

        return media;
    };

    // get the other guys media stream
    this.getRemoteStream = function() {
        return remoteStream;
    };

}

main.js

var createRoomButton = $('#createRoomButton');
var joinRoomButton = $('#joinRoomButton');
var roomNameInput = $('#roomNameInput');
var localVideo = $('#localVideo');
var remoteVideo = $('#remoteVideo');
var roomNameField = $('#roomNameField');

var wsProtocol = location.protocol == 'http:' ? 'ws://' : 'wss://';
var wsServerAddress = wsProtocol + document.location.host + ":15555" ;

initialize();


function initialize() {
    WebRTC = new WebRTC();

    WebRTC.connectToSocket(wsServerAddress);

    createRoomButton.click(function() {
        WebRTC.createRoom(roomNameInput.val());
    });

    joinRoomButton.click(function() {
        WebRTC.joinRoom(roomNameInput.val());
    });

    document.addEventListener('socketEvent', function(socketEvent) {
        switch (socketEvent.eventType) {
            case 'roomCreated':
                $('#loginSection').hide();
                $('#roomSection').show();
                roomNameField.html(WebRTC.getRoomName());
                break;

            case 'p2pConnectionReady':
                $('#loginSection').hide();
                $('#roomSection').show();
                roomNameField.html(WebRTC.getRoomName());
                break;

            case 'streamAdded':
                var stream = WebRTC.getRemoteStream();
                remoteVideo.attr('src', URL.createObjectURL(stream));
                break;
        }
    });
}

common.js

function debug(message) {
    console.log(message);
}

function logError(e) {
    console.error(e);
}

index.html

<!doctype html> 
<html>
    <head>
        <title>WebRTC</title>
        <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
        <meta content="text/html;charset=utf-8" http-equiv="Content-Type">
<meta content="utf-8" http-equiv="encoding">
        <link href="css/bootstrap.css" rel="stylesheet">
    </head>
    <body style="padding: 5em;">
        <section id="loginSection">
            <div class="row" style="margin-bottom: 2em;">
                <div clsss="col-xs-12">
                    <button id="createRoomButton" type="button" class="btn btn-default btn-lg center-block">
                      Create room
                    </button>
                </div>
            </div>
            <div class="row" style="margin-bottom: 4em;">
                <div clsss="col-xs-12">
                    <button id="joinRoomButton" type="button" class="btn btn-default btn-lg center-block">
                      Join room
                    </button>
                </div>
            </div>
            
            <div class="row">
                <div clsss="col-xs-4">
                    <input id="roomNameInput" type="text" style="width: 200px;" class="form-control center-block" 
                           placeholder="Room's name" aria-describedby="basic-addon2">
                </div>
            </div>
        </section>
        
        <section id="roomSection" style="display: none;">
            <h3>
                Room's name: 
                <span id="roomNameField" style="font-weight: bold;"></span>
            </h3>
            <div class="video-wrapper">
                <video id="remoteVideo" autoplay="true"></video>
                <video id="localVideo"  autoplay="true" muted="true" width="200" height="200"></video>
            </div>
        </section>
        
        <script src="js/jquery-2.1.4.js" type="text/javascript"></script>
        <script src="js/bootstrap.js" type="text/javascript"></script>
        <script src="js/common.js" type="text/javascript"></script>
        <script src="js/client.js" type="text/javascript"></script>
        <script src="js/main.js" type="text/javascript"></script>
    </body>
</html>
Kokaras answered 12/10, 2015 at 10:50 Comment(2)
Where's the html? I'm willing to test and debug the WebRTC stuff, but I don't want to have to write the html to fit your JavaScript before I can start. (especially if you already wrote it!)Duce
I added html and edited other files. It works only in local network with Firefox 28 and with Google Chrome for Android.Kokaras
D
4

You still might have to shim this for Mozilla vs. Chrome, but it looks like the problem was that you were trying to send the RTCiceCandidateEvent through the WebSocket instead of just the RTCiceCandidate and it wasn't passing through the socket.

I tried this:

peerConnection.onicecandidate = function(event) {
    debug("Retrieving ICE data status changed : " + event.target.iceGatheringState)
    console.log(event);
    console.log(event.candidate);
    if(event.candidate) {
        var data = {
            type: 'iceCandidate',
            roomName: roomName,
            payload: event.candidate
        };
        sendToServer(data);
    }
};

and then this to add it right away on the other end after your wsServer.onmessage passed it to setIceCandidates (but like I said, you might need to shim it for Firefox):

var setIceCandidates = function(iceCandidate) {
    peerConnection.addIceCandidate(new RTCIceCandidate(iceCandidate));

};

I recommend filtering out the undefined iceCandidate's on the sender's end in peerConnection.onicecandidate instead of where you did in setIceCandidates on the receiving end. Also, I don't think you need to store the iceCandidate's in that othersCandidates array and add them later. I've never done that in my versions. The iceCandidate stuff seems to be well automated and doesn't need that extra help. (If you know of an advantage to that, please let me know.)

On a side note, it's a bit more compact to use that debug function you made to do your console.log()'s, but it replaces the line number that would give you the source of the error!

Duce answered 14/10, 2015 at 3:37 Comment(2)
I have one more quesrion. I want to implement video chat for many users in one room. I have two options: star topology and mesh topology. In my opinion mesh topology is easiest to implement but for many users in one room probably it will be too slow. I prefer star topology. I'm looking for library/framework which help me to implement proxy server to transfer data between users. I found licode but in my opinion is to high level framework because I create this app for my Engineering Thesis and it can not be to easy...Kokaras
Sorry, I don't have any experience with either of those.Duce

© 2022 - 2024 — McMap. All rights reserved.