Mukit, Ataul
Posted on April 22, 2018
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!
Posted on April 22, 2018
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.