DEV Community

Mukit, Ataul
Mukit, Ataul

Posted on • Updated on

Easy Multiparty Video Conferencing in 4 Steps

simple-peer is an excellent library which makes developing WebRTC solutions piece of cake. The best part about it is how elegantly it hides all the intricacies and gives you an easy to use interface for WebRTC based implementations for video conferencing and data transfer. Since screen sharing is almost the same as passing video data, so you can use this for screen casting as well.

Let me break it down in few simple steps:

Step 1

Get hold of simplepeer.min.js from https://github.com/feross/simple-peer

and include in your html file.

Step 2

Find a websocket library for passing data to and fro for initial communication.
In my case I used easyrtc (https://github.com/priologic/easyrtc)

Step 3

Write a wrapper around the websockets layer so that in main codes the socket library is not exposed. This way you can change inner websocket library anytime without changing the codes for WebRTC communication establishment.

Here is some wrapper I came up with called hub (hub.js). Inside hub.js the .on method is used to listen to custom/user-defined events. Whenever a new peer joins in I throw the app.peer event with peer id (example in step 4). Similarly there are some other events invoked by the inner websocket library which is duly passed to the hub.


var hub = { 

    msgMap: {}, 
    
    connect:function connect(url){
        easyrtcConnect(url);
        },
    
    send: function(peerid, msgType, content){
        easyrtc.sendDataWS(peerid, msgType,  content);

    },

    sendToAll: function(msgType, content){
        easyrtc.sendDataWS({targetRoom:"default"}, msgType, content);
        
    },

    on: function(type, callback){
        this.msgMap[type] = callback;
    },

    
    event: function(peerid, type, msg){
        var callback = this.msgMap[type];
        if(callback){
            callback(peerid, msg);
        };      
    },


    peerMap: {},

    setPeer: function(peerid, peer){
        this.peerMap[peerid] = peer;
    },
    
    getPeer: function(peerid){
        return this.peerMap[peerid];
    },

    removePeer: function(peerid){
        delete this.peerMap[peerid];
    },

    iteratePeers: function(callback){
        var value;
        for (var key in this.peerMap) {
                value = this.peerMap[key];
                callback(key, value);
        }
    }
    
};

Step 4

As discussed, I notify the hub when a new peer/user joins in (i.e gets online/connects to socket) from the inner layer.
Example: hub.event(easyrtcid, "app.peer", userid);

Inorder to have a multiparty conference the best way is to let the newest online user send a hello message to all other peers when he/she logs in.
The easyrtc library gives me the list of connections who are online, the moment I join in. From inside the inner layer I throw the app.peer event with the easyrtc id (namely the socket.id). For multiple connections (read users), the hub.event(easyrtcid, "app.peer", userid) is thrown multiple times.


hub.on('app.peer', function(peerid, userid){
    
    hub.send(peerid, 'hello'); // sending hello

});

Putting it a little figuratively would yield this


-----------------------|- HELLO -> User A
User D-->Sends-->------|- HELLO -> User B
-----------------------|- HELLO -> User C

Once a call request comes, the users accept by default.

And while accepting we create the SimplePeer object used for WebRTC connection.
Please go through the documentation of simple-peer to understand the initiation and events.

hub.on('hello', function (peerid, msg) {        
    
    var peer = new SimplePeer({ initiator: false, stream: localStream });       

    hub.setPeer(peerid, peer);

    peer.on('signal', function (data) {
        
        hub.send(peerid, 'signal', data);       
    
    });

    peerCreated(peerid, peer);

    hub.send(peerid, 'ack', '1'); // sending acknowledgement
    
});

Note that the above event handler is used to receive the hello from the newly connected user and in return we send an 'ack'.

ON HELLO User A ---- SEND ACK ----> USER D

ON HELLO User B ---- SEND ACK ----> USER D

ON HELLO User C ---- SEND ACK ----> USER D

The rest is simple, when the user originating the communication receives an ack, he/she forms a SimplePeer object as initiator. All that is left is passing the SDP Offer info to the other party when simple-peer library notifies elegantly with its signal event : peer.on('signal' .. ).

hub.on('ack', function (peerid, msg) {

    //debugger;

    if(msg == "1"){

        var peer = new SimplePeer({ initiator: true, stream: localStream});
        
        hub.setPeer(peerid, peer);  
        
        peer.on('signal', function (data) {
            hub.send(peerid, 'signal', data);
        });

        peerCreated(peerid, peer);
    }
});

Inside the peerCreated method, we do the necessary measures as per the simple-peer documentation.

function peerCreated(peerid, peer){
    
    peer.peerid = peerid; // you can choose to skip this

    //debugger;

    peer.on('connect', function () {
        console.log('CONNECT')
        peer.send('call established .. ' + selfID);
    });
    
    peer.on('error', function (err) { console.log('error', err) });
    

    peer.on('data', function (data) {
        console.log('data: ' + data)
    });

    peer.on('stream', function (stream) {
        console.log('new stream arrived .. ', this.peerid); 
        
        createRemoteVideoElement(peerid, stream);
            
    });

    peer.on('track', function (track, stream) {
        console.log('new track arrived .. ', this.peerid);  
        
        createRemoteVideoTrackElement(peerid, track, stream);
            
    });


    peer.on('removestream', function (stream) {
        //removeRemoteVideoElement(peerid); 
        console.log("stream removed .. ", peerid); // hardly called

    });

    peer.on('close', function () {
        console.log("connection closed .. ", peerid);
        removeRemoteVideoElement(peerid);
    
    });
    

}

However to start of proceedings we need to get the camera and microphone access ( user media stream) and store it in a localStream variable :

// get video/voice stream
navigator.getUserMedia({ video: true, audio: true }, gotMedia, function () {})

// This method starts of proceedings
function gotMedia (ownstream) {

    localStream = ownstream;
        
    connectToSocket(); // once socket connects we receive "app.peer" on hub

    var video = document.getElementById('me');
        video.srcObject = ownstream;
        video.play();

}

And that's all for now!

Top comments (5)

Collapse
 
richard21266663 profile image
richard

Great article waiting for future article like this

Collapse
 
lucpattyn profile image
Mukit, Ataul

Thanks so much for appreciating!

Collapse
 
bitnom profile image
bitnom • Edited

why not ice?

Collapse
 
lucpattyn profile image
Mukit, Ataul

It is just a demo. You can of course specify the ice candidates if you play with the codes.

Collapse
 
bitnom profile image
bitnom

Thanks for the example. After going through basically every webrtc abstraction, I landed on simple-peer which just seems to work apart from the rest.