--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/.hgignore Sat Mar 09 13:51:40 2013 +0900
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/README.rst Sat Mar 09 13:51:40 2013 +0900
+Tornado app that can establish WebRTC sessions. Incidentally, that's what
+So yeah, for people who have no idea what WebRTC is; it's a realtime
+communications API for browsers. Open website, dial your friend, have fun.
+And for people who roughly know how WebRTC works; this app:
+#. provides a page with all required elements and scripts and
+#. lets browsers (or even separate tabs) talk to each other via WebSockets
+ in order to connect peer-to-peer
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/REQS Sat Mar 09 13:51:40 2013 +0900
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/app.py Sat Mar 09 13:51:40 2013 +0900
+from tornado.ioloop import IOLoop
+from tornado.options import define, options
+from tornado.web import Application, RequestHandler
+from tornado.websocket import WebSocketHandler
+rel = lambda *x: os.path.abspath(os.path.join(os.path.dirname(__file__), *x))
+class MainHandler(RequestHandler):
+ self.render('index.html')
+class EchoWebSocket(WebSocketHandler):
+ logging.info('WebSocket opened from %s', self.request.remote_ip)
+ EchoWebSocket.clients.append(self)
+ def on_message(self, message):
+ logging.info('got message from %s: %s', self.request.remote_ip, message)
+ for client in EchoWebSocket.clients:
+ client.write_message(message)
+ logging.info('WebSocket closed')
+ EchoWebSocket.clients.remove(self)
+ define('listen', metavar='IP', default='127.0.0.1', help='listen on IP address (default 127.0.0.1)')
+ define('port', metavar='PORT', default=8888, type=int, help='listen on PORT (default 8888)')
+ define('debug', metavar='True|False', default=False, type=bool,
+ help='enable Tornado debug mode: templates will not be cached '
+ 'and the app will watch for changes to its source files '
+ 'and reload itself when anything changes')
+ options.parse_command_line()
+ template_path=rel('templates'),
+ static_path=rel('static'),
+ application = Application([
+ (r'/ws', EchoWebSocket),
+ application.listen(address=options.listen, port=options.port)
+ IOLoop.instance().start()
+if __name__ == '__main__':
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/static/js/main.js Sat Mar 09 13:51:40 2013 +0900
+var ws = new WebSocket('ws://' + location.host + '/ws');
+ init({audio: true, video: true}, true);
+ init({audio: true, video: true}, false);
+function init(constraints, initiator) {
+ getUserMedia(constraints, function(stream) {
+ pc = new RTCPeerConnection(null);
+ attachMediaStream(document.getElementById('local'), stream);
+ pc.onaddstream = function(event) {
+ attachMediaStream(document.getElementById('remote'), event.stream);
+ pc.onicecandidate = function(event) {
+ ws.send(JSON.stringify(event.candidate));
+ ws.onmessage = function (event) {
+ var signal = JSON.parse(event.data);
+ } else if (signal.candidate) {
+ pc.addIceCandidate(new RTCIceCandidate(signal));
+function createOffer() {
+ console.log('creating offer');
+ pc.createOffer(function(offer) {
+ console.log('created offer');
+ pc.setLocalDescription(offer, function() {
+ console.log('setLocalDescription done, sending to remote');
+ ws.send(JSON.stringify(offer));
+function receiveOffer(offer) {
+ console.log('received offer');
+ pc.setRemoteDescription(new RTCSessionDescription(offer), function() {
+ console.log('creating answer');
+ pc.createAnswer(function(answer) {
+ console.log('created answer');
+ pc.setLocalDescription(answer, function() {
+ console.log('setLocalDescription done, sending to remote');
+ ws.send(JSON.stringify(answer));
+function receiveAnswer(answer) {
+ console.log('received answer');
+ pc.setRemoteDescription(new RTCSessionDescription(answer));
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/static/js/webrtc-adapter.js Sat Mar 09 13:51:40 2013 +0900
+var RTCPeerConnection = null;
+var getUserMedia = null;
+var attachMediaStream = null;
+var reattachMediaStream = null;
+var webrtcDetectedBrowser = null;
+if (navigator.mozGetUserMedia) {
+ console.log("This appears to be Firefox");
+ webrtcDetectedBrowser = "firefox";
+ // The RTCPeerConnection object.
+ RTCPeerConnection = mozRTCPeerConnection;
+ // The RTCSessionDescription object.
+ RTCSessionDescription = Object;
+ // The RTCIceCandidate object.
+ RTCIceCandidate = mozRTCIceCandidate;
+ // Get UserMedia (only difference is the prefix).
+ // Code from Adam Barth.
+ getUserMedia = navigator.mozGetUserMedia.bind(navigator);
+ // Attach a media stream to an element.
+ attachMediaStream = function(element, stream) {
+ console.log("Attaching media stream");
+ element.src = URL.createObjectURL(stream);;
+ reattachMediaStream = function(to, from) {
+ console.log("Reattaching media stream");
+ to.mozSrcObject = from.mozSrcObject;
+ // Fake get{Video,Audio}Tracks
+ MediaStream.prototype.getVideoTracks = function() {
+ MediaStream.prototype.getAudioTracks = function() {
+} else if (navigator.webkitGetUserMedia) {
+ console.log("This appears to be Chrome");
+ webrtcDetectedBrowser = "chrome";
+ // The RTCPeerConnection object.
+ RTCPeerConnection = webkitRTCPeerConnection;
+ // Get UserMedia (only difference is the prefix).
+ // Code from Adam Barth.
+ getUserMedia = navigator.webkitGetUserMedia.bind(navigator);
+ // Attach a media stream to an element.
+ attachMediaStream = function(element, stream) {
+ element.src = webkitURL.createObjectURL(stream);
+ reattachMediaStream = function(to, from) {
+ // The representation of tracks in a stream is changed in M26.
+ // Unify them for earlier Chrome versions in the coexisting period.
+ if (!webkitMediaStream.prototype.getVideoTracks) {
+ webkitMediaStream.prototype.getVideoTracks = function() {
+ return this.videoTracks;
+ webkitMediaStream.prototype.getAudioTracks = function() {
+ return this.audioTracks;
+ // New syntax of getXXXStreams method in M26.
+ if (!webkitRTCPeerConnection.prototype.getLocalStreams) {
+ webkitRTCPeerConnection.prototype.getLocalStreams = function() {
+ return this.localStreams;
+ webkitRTCPeerConnection.prototype.getRemoteStreams = function() {
+ return this.remoteStreams;
+ console.log("Browser does not appear to be WebRTC-capable");
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/templates/index.html Sat Mar 09 13:51:40 2013 +0900
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <title>Tornado WebRTC</title>
+ <script src="{{ static_url('js/webrtc-adapter.js') }}"></script>
+ <script src="{{ static_url('js/main.js') }}"></script>
+ <video id="remote" autoplay></video>
+ <video id="local" autoplay muted></video>
+ <button onclick="call()">I want to call</button>
+ <button onclick="receive()">I want to receive a call</button>