Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Peer to peer reworking for conference, WebRTC

I have to ask the question again today.

I am in the course of processing a code to connect to one to one to the code allowing for a group conversation. Without beating around the bush, I have this code:

CallToUsers = function(connection) {
    var connection = connection;
    var isChannelReady;
    var isInitiator = false;
    var isStarted = false;
    var servers = null;
    var localStream = connection.getStream();
    var localStreams = [];
    var localConnection;
    var turnReady;
    var remoteStreams = [];
    var remoteStream;
    var pcConfig = {
        'iceServers': [{
            'url': 'stun:stun.l.google.com:19302'
            }]
        };
    var pcConstraints = {
        'optional': [{
            'DtlsSrtpKeyAgreement': true
            }]
        };
    var sdpConstraints = {
        'mandatory': {
            'OfferToReceiveAudio': true,
            'OfferToReceiveVideo': true
        }
    };
    var room;
    var socket = io.connect();
    var divElement = document.createElement('div');
    divElement.setAttribute('id', 'remotesVideo');
    document.body.appendChild(divElement);

    var createRoom = function(room) {
        room = room;
        if(room !== '') {
            console.log('Create or join to room', room);
            socket.emit('create or join', room);
        }

        socket.on('created', function(room) {
            console.log('Created room ' + room);
            isInitiator = true;
        });

        /*socket.on('full', function(room) {
            console.log('Room' + room + ' is full');
        });*/

        socket.on('join', function(room) {
            console.log('Another peer made request to join ' + room);
            isChannelReady = true;
        });

        socket.on('joined', function(room) {
            console.log('User joined to room ' + room);
            isChannelReady = true;
        });

        socket.on('log', function(array) {
            console.log.apply(console, array);
        });
    };

    var sendMessage = function(message) {
        console.log('Client sending a message: ', message);
        socket.emit('message', message);
    };

    window.onbeforeunload = function(e){
        sendMessage('bye');
    };

    var startCall = function() {
        sendMessage('got user media');

        if(isInitiator) {
            maybeStart();
        }

        socket.on('message', function(message) {
            console.log('Client received a message: ' + message);
            if(message === 'got user media') {
                maybeStart();
            } else if(message.type === 'offer') {
                if(!isInitiator && !isStarted) {
                    maybeStart();
                }

                for(var i = 0; i < localStreams.length; i++) {
                    localStreams[i].setRemoteDescription(new RTCSessionDescription(message));
                }

                doAnswer();
            } else if(message.type === 'answer' && isStarted) {
                for(var i = 0; i < localStreams.length; i++) {
                    localStreams[i].setRemoteDescription(new RTCSessionDescription(message));
                }
            } else if(message.type === 'candidate' && isStarted) {
                var candidate = new RTCIceCandidate({
                    sdpMLineIndex: message.label,
                    candidate: message.candidate
                });

                for(var i = 0; i < localStreams.length; i++) {
                    localStreams[i].addIceCandidate(candidate);
                }
            } else if(message === 'bye' && isStarted) {
                handleRemoteEndCall();
            }
        });

        //if(location.hostname != 'localhost') {
            //requestTurn('https://computeengineondemand.appspot.com/turn?username=41784574&key=4080218913');
        //}
    };

    var maybeStart = function() {
        if(!isStarted && typeof localStream != 'undefined' && isChannelReady) {
            createPeerConnection();
            for(var i = 0; i < localStreams.length; i++) {
                localStreams[i].addStream(localStream);
            }
            isStarted = true;

            if(isInitiator) {
                doCall();
            }
        }
    };

    var createPeerConnection = function() {
        try {
                localConnection = new RTCPeerConnection(pcConfig, pcConstraints);
                localConnection.onicecandidate = handleIceCandidate;
                localConnection.onaddstream = handleRemoteStreamAdded;
                localConnection.onremovestream = handleRemoteStreamRemoved;
                localStreams.push(localConnection);
            console.log('Created RTCPeerConnection');
        } catch(e) {
            console.log('exception ' + e.message);
            return;
        }
    };

    var handleIceCandidate = function(event) {
        if(event.candidate) {
            sendMessage({
                type: 'candidate',
                label: event.candidate.sdpMLineIndex,
                id: event.candidate.sdpMid,
                candidate: event.candidate.candidate
            });
        } else {
            console.log('End of candidates');
        }
    };

    var handleRemoteStreamAdded = function(event) {
        console.log('Remote stream added');
        var newVideo = document.createElement('video');
        newVideo.setAttribute('id', Math.floor((Math.random() * 1000) + 1));
        newVideo.muted = false;
        divElement.appendChild(newVideo);
        attachMediaStream(newVideo, event.stream);
        remoteStream = event.stream;
        remoteStreams.push(remoteStream);
    };

    var handleRemoteStreamRemoved = function(event) {
        console.log('Delete');
    };

    var handleCreateOfferError = function(event) {
        console.log('createOffer() error: ' + e);
    }

    var setLocalAndSendMessage = function(sessionDescription) {
        sessionDescription.sdp = preferOpus(sessionDescription.sdp);
        for(var i = 0; i < localStreams.length; i++) {
            localStreams[i].setLocalDescription(sessionDescription);
        }
        sendMessage(sessionDescription);
    };

    var doCall = function() {
        console.log('Start call');
        for(var i = 0; i < localStreams.length; i++) {
            localStreams[i].createOffer(setLocalAndSendMessage, handleCreateOfferError);
        }
    };

    var doAnswer = function() {
        console.log('Sending answer');
        for(var i = 0; i < localStreams.length; i++) {
            localStreams[i].createAnswer(setLocalAndSendMessage, null, sdpConstraints);
        }
    };

    var endCall = function() {
        console.log('Hanging up');
        isStarted = false;
        for(var i = 0; i < localStreams.length; i++) {
            localStreams[i].close();
            localStreams[i] = null;
        }
        sendMessage('bye');
    };

    var handleRemoteEndCall = function() {
    };

    var requestTurn = function(turnUrl) {
        var turnExists = false;

        for(var i in pcConfig.iceServers) {
            if(pcConfig.iceServers[i].url.substr(0, 5) === 'turn:') {
                turnExists = true;
                turnReady = true;
                break;
            }
        }

        if(!turnExists) {
            console.log('Getting TURN server from ', turnUrl);

            var xhr = new XMLHttpRequest();
            xhr.onreadystatechange = function() {
                if(xhr.readyState === 4 && xhr.status === 200) {
                    var turnServer = JSON.parse(xhr.responseText);
                    console.log('Got TURN server: ', turnServer);
                    pc_config.iceServers.push({
                        'url': 'turn:' + turnServer.username + '@' + turnServer.turn,
                        'credential': turnServer.password
                    });
                    turnReady = true;
                }
            };

            xhr.open('GET', turnUrl, true);
            xhr.send();
        }
    };

    var preferOpus = function(sdp) {
        var sdpLines = sdp.split('\r\n');
        var mLineIndex;

        for(var i = 0; i < sdpLines.length; i++) {
            if(sdpLines[i].search('m=audio') !== -1) {
                mLineIndex = i;
                break;
            }
        }

        if(mLineIndex === null) {
            return sdp;
        }

        for(i = 0; i < sdpLines.length; i++) {
            if(sdpLines[i].search('opus/48000') !== -1) {
                var opusPayload = extractSdp(sdpLines[i], /:(\d+) opus\/48000/i);

                if(opusPayload) {
                    sdpLines[mLineIndex] = setDefaultCodec(sdpLines[mLineIndex], opusPayload);
                }

                break;
            }
        }

        sdpLines = removeCN(sdpLines, mLineIndex);

        sdp = sdpLines.join('\r\n');

        return sdp;
    };

    var extractSdp = function(sdpLine, pattern) {
        var result = sdpLine.match(pattern);

        return result && result.length === 2 ? result[1] : null;
    };

    var setDefaultCodec = function(mLine, payload) {
        var elements = mLine.split(' ');
        var newLine = [];
        var index = 0;

        for(var i = 0; i < elements.length; i++)  {
            if(index === 3) {
                newLine[index++] = payload;
            }
            if(elements[i] !== payload) {
                newLine[index++] = elements[i];
            }
        }

        return newLine.join(' ');
    };

    var removeCN = function(sdpLines, mLineIndex) {
        var mLineElements = sdpLines[mLineIndex].split(' ');

        for(var i = sdpLines.length - 1; i >= 0; i--) {
            var payload = extractSdp(sdpLines[i], /a=rtpmap:(\d+) CN\/\d+/i);

            if(payload) {
                var cnPos = mLineElements.indexOf(payload);

                if(cnPos !== -1) {
                    mLineElements.splice(cnPos, 1);
                }

                sdpLines.splice(i, 1);
            }
        }

        sdpLines[mLineIndex] = mLineElements.join(' ');

        return sdpLines;
    };

    return {
        startCall: startCall,
        endCall: endCall,
        createRoom: createRoom
    };
};

While the conversation between two users working well, then when adding another member, it no longer connects to the other two peers, although clearly the logs I see that attaches to the same room.

In the console, no errors.

Does anyone would be kind to help?

@edit

I'm adding server code too if needed:

var static = require('node-static');
var http = require('http');
var file = new(static.Server)();
var app = http.createServer(function (req, res) {
  file.serve(req, res);
}).listen(2017);


var io = require('socket.io').listen(app);
io.sockets.on('connection', function (socket){

    function log(){
        var array = [">>> "];
      for (var i = 0; i < arguments.length; i++) {
        array.push(arguments[i]);
      }
        socket.emit('log', array);
    }

    socket.on('message', function (message) {
        log('Got message: ', message);
        socket.broadcast.emit('message', message); // should be room only
    });

    socket.on('create or join', function (room) {
        var numClients = io.sockets.clients(room).length;

        log('Room ' + room + ' has ' + numClients + ' client(s)');
        log('Request to create or join room', room);

        if (numClients == 0){
            socket.join(room);
            socket.emit('created', room);
        } else {
            io.sockets.in(room).emit('join', room);
            socket.join(room);
            socket.emit('joined', room);
        }
        socket.emit('emit(): client ' + socket.id + ' joined room ' + room);
        socket.broadcast.emit('broadcast(): client ' + socket.id + ' joined room ' + room);

    });

});
like image 449
Jake Avatar asked Mar 27 '14 18:03

Jake


1 Answers

Finally solved the problem. The general rule of writing conferences, where all peers are connected to is this: each new incoming plays the role of answerer, and after creating a connection with all offers who send him an offer, begins to act as offerer (except the first peer, which is always the offer). @Mert Koksal, thank you for your interest thread.

like image 123
Jake Avatar answered Nov 15 '22 11:11

Jake