diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..07e6e47 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/node_modules diff --git a/PeerConnection.js b/PeerConnection.js new file mode 100644 index 0000000..3727dd1 --- /dev/null +++ b/PeerConnection.js @@ -0,0 +1,83 @@ +/* MIT License: https://webrtc-experiment.appspot.com/licence/ + 2013, Muaz Khan--[github.com/muaz-khan] + + Demo & Documentation: http://bit.ly/RTCPeerConnection-Documentation */ +window.moz = !! navigator.mozGetUserMedia; +var PeerConnection = function (options) { + var PeerConnection = window.mozRTCPeerConnection || window.webkitRTCPeerConnection, + SessionDescription = window.mozRTCSessionDescription || window.RTCSessionDescription, + IceCandidate = window.mozRTCIceCandidate || window.RTCIceCandidate; + + // See https://gist.github.com/zziuni/3741933 for a list of public STUN servers + var iceServers = { + iceServers: [{ url: 'stun:stunserver.org' }] + }; + + var optional = { + optional: [] + }; + if (!moz) { + // See http://www.webrtc.org/interop under "Constraints / configurations issues." + optional.optional = [{ + DtlsSrtpKeyAgreement: true + } + ]; + } + + var peerConnection = new PeerConnection(iceServers, optional); + peerConnection.onicecandidate = function(event) { + if (!event.candidate) return; + options.onicecandidate(event.candidate); + } + peerConnection.onaddstream = function(event) { + console.log('------------onaddstream'); + options.onaddstream(event.stream); + } + + var constraints = options.constraints || { + optional: [], + mandatory: { + OfferToReceiveAudio: true, + OfferToReceiveVideo: true + } + }; + if (moz) constraints.mandatory.MozDontOfferDataChannel = true; + + return { + createOffer: function (callback) { + peerConnection.createOffer(function (sessionDescription) { + peerConnection.setLocalDescription(sessionDescription); + callback(sessionDescription); + }, null, constraints); + }, + + createAnswer: function (offerSDP, callback) { + peerConnection.setRemoteDescription(new SessionDescription(offerSDP)); + peerConnection.createAnswer(function (sessionDescription) { + peerConnection.setLocalDescription(sessionDescription); + callback(sessionDescription); + }, null, constraints); + }, + + setRemoteDescription: function (sdp) { + console.log('--------adding answer sdp:'); + console.log(sdp.sdp); + + sdp = new SessionDescription(sdp); + peerConnection.setRemoteDescription(sdp); + }, + + addICECandidate: function (candidate) { + console.log("addICE: got candidate: " + candidate.candidate); + peerConnection.addIceCandidate(new IceCandidate({ + sdpMLineIndex: candidate.sdpMLineIndex, + candidate: candidate.candidate + })); + }, + + addStream: function(stream) { + console.log("stream provided, attaching..."); + peerConnection.addStream(stream); + } + }; +}; diff --git a/README b/README new file mode 100644 index 0000000..5ff06e3 --- /dev/null +++ b/README @@ -0,0 +1,55 @@ +This is a *minimal* demo of WebRTC video calling between two browsers. +Because the signalling layer isn't standardized, you'll need to run a +small node.js server to pass signalling messages between the two +browsers. The media connections are point-to-point, though, once the +signalling has happened no server is required. + +This demo is based on Silvia Pfeiffer's WebRTC via Web Sockets demo, +and Muaz Khan's WebRTC-Experiment websocket-over-nodejs demo. +Silvia's demo became obsolete (it uses ROAP signalling while JSEP is +now the standard) and Muaz's demo was more complex than I liked which +made it difficult to figure out what the WebRTC API's were doing. +Both of their code was very helpful to me, though! + +Silvia's Blog post: +http://blog.gingertech.net/2012/06/04/video-conferencing-in-html5-webrtc-via-web-sockets/ + +Muaz's WebRTC Experiments web site: +https://www.webrtc-experiment.com/ + +Muaz's WebSocket over Node.js demo: +https://github.com/muaz-khan/WebRTC-Experiment/tree/master/websocket-over-nodejs + +Installation: + +Install node.js from http://nodejs.org + +Install Node's websocket package: +$ npm install websocket + +Install the node-static package to serve static files: +$ npm install node-static + +Usage: + +Run the Node server: +$ node websocket-server.js + +Point two browsers at http://your-server-address:1337/. You'll be +prompted to allow the use of your camera and microphone. + +One of the browsers clicks "Call" and the call should be set up. + +To close the call just reload the page. Like I said: this is a +minimal demo. + +Debugging the Node Server: + install https://github.com/node-inspector/node-inspector + run the node app in debug mode (will stop at the first line) + $ node --debug-brk websocket-server.js + then in another terminal + $ node node_modules/node-inspector/bin/inspector.js + - or - + $ node_modules/.bin/node-inspector + open chrome to http://127.0.0.1:8080/debug?port=5858 + step once to go to the first line of the program diff --git a/call.js b/call.js new file mode 100644 index 0000000..54fc641 --- /dev/null +++ b/call.js @@ -0,0 +1,78 @@ +/* MIT License: https://webrtc-experiment.appspot.com/licence/ */ + +var call = function (config) { + var + peerConnection = PeerConnection(makePeerConfig()), + webSocket = new WebSocket('ws://' + document.location.host + '/'); + + // configure the signalling WebSocket + webSocket.onmessage = function (event) { + console.log("received a message: " + event.data); + onIncomingMessage(JSON.parse(event.data)); + }; + webSocket.push = webSocket.send; + webSocket.send = function (data) { + webSocket.push(JSON.stringify(data)); + }; + + function onIncomingMessage(response) { + // the other client has sent me an offer SDP + if (response.offerSDP) { + console.log("received offerSDP " + response.offerSDP + ", will answer"); + peerConnection.addStream(config.localStream); + peerConnection.createAnswer(response.offerSDP, function (sdp) { + console.log("sending answer SDP"); + webSocket.send({ + answerSDP: sdp + }); + }); + } + + // the other client has sent me an answer SDP + if (response.answerSDP) { + peerConnection.setRemoteDescription(response.answerSDP); + } + + // the other client has sent me an ICE candidate + if (response.candidate) { + console.log("got a candidate message, passing to RTCPeerConnection"); + peerConnection.addICECandidate({ + sdpMLineIndex: response.candidate.sdpMLineIndex, + candidate: response.candidate.candidate + }); + } + } + + // PeerConnection.js's options structure + function makePeerConfig() { + return { + onicecandidate: function (candidate) { + console.log("onICE"); + webSocket.send({ + candidate: { + sdpMLineIndex: candidate.sdpMLineIndex, + candidate: candidate.candidate + } + }); + }, + onaddstream: function (stream) { + console.log("onRemoteStream"); + config.video['src'] = URL.createObjectURL(stream); + } + }; + } + + return { + initiateCall: function(localStream) { + // attach the stream to the peer connection + peerConnection.addStream(localStream); + // create the offer SDP and send it when it's ready + peerConnection.createOffer(function (sdp) { + console.log("sending offer SDP"); + webSocket.send({ + offerSDP: sdp + }) + }); + } + }; +}; diff --git a/index.html b/index.html new file mode 100644 index 0000000..3200c6b --- /dev/null +++ b/index.html @@ -0,0 +1,51 @@ + + + + Example WebRTC Point-to-point Video + + + +

Example WebRTC Point-to-point Video

+ + +
+
+ + +
+ + + + + diff --git a/websocket-server.js b/websocket-server.js new file mode 100644 index 0000000..6c3be01 --- /dev/null +++ b/websocket-server.js @@ -0,0 +1,69 @@ +// adapted from: +// http://blog.gingertech.net/2012/06/04/video-conferencing-in-html5-webrtc-via-web-sockets/ + +var WebSocketServer = require('websocket').server; +var http = require('http'); +var static = require('node-static'); +var clients = []; + +var staticServer = new static.Server('.'); + +// set up node-static to serve static files in this directory, +// i.e., http://localhost:1337/index.html +var server = http.createServer(function(request, response) { + request.addListener('end', function() { + staticServer.serve(request, response); + }).resume(); +}); +server.listen(1337, function() { + console.log((new Date()) + " Server is listening on port 1337"); +}); + +// create the server +wsServer = new WebSocketServer({ + httpServer: server +}); + +function sendCallback(err) { + if (err) console.error((new Date()) + "send() error: " + err); +} + +// This callback function is called every time someone +// tries to connect to the WebSocket server +wsServer.on('request', function(request) { + console.log((new Date()) + ' Connection from origin ' + request.origin + '.'); + var connection = request.accept(null, request.origin); + console.log((new Date()) + ' Connection remoteAddress ' + connection.remoteAddress); + clients.push(connection); + + // This is the most important callback for us, we'll handle + // all messages from users here. + connection.on('message', function(message) { + if (message.type === 'utf8') { + // process WebSocket message + console.log((new Date()) + ' Received Message ' + message.utf8Data); + // broadcast message to all connected clients + clients.forEach(function (outputConnection) { + if (outputConnection != connection) { + outputConnection.send(message.utf8Data, sendCallback); + } + }); + } + }); + + connection.on('close', function(connection) { + // close user connection + console.log((new Date()) + " Peer disconnected."); + for (var i = 0; i < clients.length; i++) { + if (!clients[i].connected) + clients.remove(i); + } + }); +}); + +// Array Remove - By John Resig (MIT Licensed) +Array.prototype.remove = function(from, to) { + var rest = this.slice((to || from) + 1 || this.length); + this.length = from < 0 ? this.length + from : from; + return this.push.apply(this, rest); +};