I recently added peer to peer video chat to Humans vs Aliens vs Robots: War, and thought it would be nice to share the start-to finish implementation. Almost all the code in this is still rough and could be done more succinctly and in better form. But it is what it is until I get around to cleaning it up.
What my implementation ended up looking like:
This works between firefox and chrome browsers on all platforms, and if a STUN/TURN server is implemented it seems to work through most common router and firewall setups.
Technology used
nodejs
socketio
peerjs
rfc5766-turn-server
Getting Started - Setting up the display and retrieving configuration information from the server
On the client side I added a video chat dialog that contains two video tags. The local is muted so we don't get echo/feedback (we don't need to hear ourselves):
On displaying the video chat dialog the following is executed:
if (window.localStream == null || (window.localStream != null && window.localStream.ended)){
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
navigator.getUserMedia({audio: true, video: true},
function(stream){
window.localStream = stream;
$("#localVideo").attr("src", URL.createObjectURL(stream));
},
function(error) {
console.log("navigator.getUserMedia error: ", error);
}
);
}
else {
window.localStream.start();
};
socket.emit("peerConfig");
This code starts the local video capture and sends a request to the server to get configuration details for the peer to peer connection. The reason this information is provided from the server is that it includes time-sensitive credentials which are supplied from the turn server. (The turn server credentials only last a few seconds/minutes.)
On the server side in response to the peerConfig request:
socket.on('peerConfig', function(){
var configDetails = {"iceServers": [{"url": "stun:stun.l.google.com:19302"},
{"url":"turn:username@yourdomain.com", "credential":"yourCrdentials", password:"password set in turn server user database"}]};
socket.emit('peerConfigResponse', JSON.stringify(configDetails));
});
This code grabs the next set of credentials from the turn server user database. (In this case a text file that is being fed usernames and passwords from a different node process.) This section will be a little mysterious, but there's some great information out there already that covers Turn server setup. And for now, it's outside the scope of this tutorial. (One of the first posts I gleaned a lot from was: Dialogic.com ).
At any rate, configuration details are created and sent back to the client. Back on the client side:
socket.on("peerConfig", function(data){
if (peer == null){
data = JSON.parse(data);
window.remoteStream = null;
peer = new Peer(peerID, {host: 'yourdomain.com', port: 9000, path: '/', debug: 1, config: data});
peer.on('open', function(id) {
});
peer.on('call', function(call){
call.answer(window.localStream);
console.log("peer called");
makeVideoChatCall(call);
});
peer.on('close', function(call){
window.existingCall.close();
window.existingCall = null;
peer = null;
this.settingUpVideoConnection = false;
});
peer.on('disconnected', function(call){
window.existingCall.close();
window.existingCall = null;
peer = null;
});
peer.on('error', function(err){
window.existingCall = null;
console.log("peer error: ", err.message);
peer = null;
});
}
});
makeVideoChatCall = function makeVideoChatCall(call){
if (window.existingCall) {
window.existingCall.close();
}
window.existingCall = call;
// Wait for stream on the call, then set peer video display
call.on('stream', function(stream){
var element = $('#remoteVideo')[0];
if (typeof element.srcObject !== 'undefined') {
element.srcObject = stream;
} else if (typeof element.mozSrcObject !== 'undefined') {
element.mozSrcObject = stream;
} else if (typeof element.src !== 'undefined') {
element.src = URL.createObjectURL(stream);
} else {
console.log('Error attaching stream to element.');
}
});
call.on('open', function(event){
})
call.on('close', function(event){
window.existingCall = null;
})
call.on('error', function(err){
window.existingCall = null;
window.localStream = null;
window.remoteStream = null;
console.log('CALL ON error', err);
})
// UI stuff - Show end call button etc.
}
Summary
At this point the client is capturing a local audio and video stream and displaying it in the localVideo video element.
The client has created a peer object ready to connect to another peer, and if need be use a stun or turn server to facilitate crossing common firewall and router configurations.
In the next part of this tutorial I'll cover making and answering calls