Browse Source

new demo added and link to the demo page is now correct

Dennis Mårtensson 11 years ago
parent
commit
7c6f3ddda3
7 changed files with 805 additions and 1 deletions
  1. 1 1
      README.md
  2. BIN
      site/fullscrean.png
  3. 40 0
      site/index.html
  4. 203 0
      site/script.js
  5. 66 0
      site/server.js
  6. 103 0
      site/style.css
  7. 392 0
      site/webrtc.io.js

+ 1 - 1
README.md

@@ -1,7 +1,7 @@
 #webrtc.io-demo
 ==============
 
-You can have a look at the [demo](http://bit.ly/webrtcio)
+You can have a look at the [demo](http://webrtc.dennis.is/)
 
 ##Instructions on how to setup the demo:
 

BIN
site/fullscrean.png


+ 40 - 0
site/index.html

@@ -0,0 +1,40 @@
+<html>
+  <head>
+    <title>Example webrtc.io</title>
+    <link type="text/css" href="/style.css" rel="stylesheet"></link>
+
+    <script src="/webrtc.io.js"></script>
+  </head>
+  <body onload="init()">
+    <div id="videos">
+      <a href="https://github.com/webRTC"><img style="position: absolute; top: 0; right: 0; border: 0;" src="https://s3.amazonaws.com/github/ribbons/forkme_right_orange_ff7600.png" alt="Fork me on GitHub"></a>
+      <video id="you" class="flip" autoplay></video>
+    </div>
+    <div id="chatbox">
+      <div id="messages">
+      </div>
+      <input id="chatinput" type="text" placeholder="Message:"/>
+    </div>
+
+    <div class="buttonBox">
+      <div id="fullscreen" class="button">Enter Full Screen</div>
+      <div id="newRoom" class="button">Create A New Room</div>
+    </div>
+    
+    <script src="/script.js"></script>
+    <script type="text/javascript">
+
+    var _gaq = _gaq || [];
+    _gaq.push(['_setAccount', 'UA-31155783-1']);
+    _gaq.push(['_setDomainName', 'dennis.is']);
+    _gaq.push(['_trackPageview']);
+
+    (function() {
+      var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
+      ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
+      var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
+    })();
+
+  </script>
+  </body>
+</html>

+ 203 - 0
site/script.js

@@ -0,0 +1,203 @@
+var videos = [];
+var rooms = [1, 2, 3, 4, 5];
+var PeerConnection = window.PeerConnection || window.webkitPeerConnection00 || window.webkitRTCPeerConnection;
+
+function getNumPerRow() {
+  var len = videos.length;
+  var biggest;
+
+  // Ensure length is even for better division.
+  if(len % 2 === 1) {
+    len++;
+  }
+
+  biggest = Math.ceil(Math.sqrt(len));
+  while(len % biggest !== 0) {
+    biggest++;
+  }
+  return biggest;
+}
+
+function subdivideVideos() {
+  var perRow = getNumPerRow();
+  var numInRow = 0;
+  for(var i = 0, len = videos.length; i < len; i++) {
+    var video = videos[i];
+    setWH(video, i);
+    numInRow = (numInRow + 1) % perRow;
+  }
+}
+
+function setWH(video, i) {
+  var perRow = getNumPerRow();
+  var perColumn = Math.ceil(videos.length / perRow);
+  var width = Math.floor((window.innerWidth) / perRow);
+  var height = Math.floor((window.innerHeight - 190) / perColumn);
+  video.width = width;
+  video.height = height;
+  video.style.position = "absolute";
+  video.style.left = (i % perRow) * width + "px";
+  video.style.top = Math.floor(i / perRow) * height + "px";
+}
+
+function cloneVideo(domId, socketId) {
+  var video = document.getElementById(domId);
+  var clone = video.cloneNode(false);
+  clone.id = "remote" + socketId;
+  document.getElementById('videos').appendChild(clone);
+  videos.push(clone);
+  return clone;
+}
+
+function removeVideo(socketId) {
+  var video = document.getElementById('remote' + socketId);
+  if(video) {
+    videos.splice(videos.indexOf(video), 1);
+    video.parentNode.removeChild(video);
+  }
+}
+
+function addToChat(msg, color) {
+  var messages = document.getElementById('messages');
+  msg = sanitize(msg);
+  if(color) {
+    msg = '<span style="color: ' + color + '; padding-left: 15px">' + msg + '</span>';
+  } else {
+    msg = '<strong style="padding-left: 15px">' + msg + '</strong>';
+  }
+  messages.innerHTML = messages.innerHTML + msg + '<br>';
+  messages.scrollTop = 10000;
+}
+
+function sanitize(msg) {
+  return msg.replace(/</g, '&lt;');
+}
+
+function initFullScreen() {
+  var button = document.getElementById("fullscreen");
+  button.addEventListener('click', function(event) {
+    var elem = document.getElementById("videos");
+    //show full screen
+    elem.webkitRequestFullScreen();
+  });
+}
+
+function initNewRoom() {
+  var button = document.getElementById("newRoom");
+
+  button.addEventListener('click', function(event) {
+
+    var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz";
+    var string_length = 8;
+    var randomstring = '';
+    for(var i = 0; i < string_length; i++) {
+      var rnum = Math.floor(Math.random() * chars.length);
+      randomstring += chars.substring(rnum, rnum + 1);
+    }
+
+    window.location.hash = randomstring;
+    location.reload();
+  })
+}
+
+
+var websocketChat = {
+  send: function(message) {
+    rtc._socket.send(message);
+  },
+  recv: function(message) {
+    return message;
+  },
+  event: 'receive_chat_msg'
+};
+
+var dataChannelChat = {
+  send: function(message) {
+    for(var connection in rtc.dataChannels) {
+      var channel = rtc.dataChannels[connection];
+      channel.send(message);
+    }
+  },
+  recv: function(channel, message) {
+    return JSON.parse(message).data;
+  },
+  event: 'data stream data'
+};
+
+function initChat() {
+  var chat;
+
+  if(rtc.dataChannelSupport) {
+    console.log('initializing data channel chat');
+    chat = dataChannelChat;
+  } else {
+    console.log('initializing websocket chat');
+    chat = websocketChat;
+  }
+
+  var input = document.getElementById("chatinput");
+  var room = window.location.hash.slice(1);
+  var color = "#" + ((1 << 24) * Math.random() | 0).toString(16);
+
+  input.addEventListener('keydown', function(event) {
+    var key = event.which || event.keyCode;
+    if(key === 13) {
+      chat.send(JSON.stringify({
+        "eventName": "chat_msg",
+        "data": {
+          "messages": input.value,
+          "room": room,
+          "color": color
+        }
+      }));
+      addToChat(input.value);
+      input.value = "";
+    }
+  }, false);
+  rtc.on(chat.event, function() {
+    data = chat.recv.apply(this, arguments);
+    console.log(data.color);
+    addToChat(data.messages, data.color.toString(16));
+  });
+}
+
+
+function init() {
+  if(PeerConnection) {
+    rtc.createStream({
+      "video": true,
+      "audio": true
+    }, function(stream) {
+      document.getElementById('you').src = URL.createObjectURL(stream);
+      videos.push(document.getElementById('you'));
+      //rtc.attachStream(stream, 'you');
+      subdivideVideos();
+    });
+  } else {
+    alert('Your browser is not supported or you have to turn on flags. In chrome you go to chrome://flags and turn on Enable PeerConnection remember to restart chrome');
+  }
+
+
+  var room = window.location.hash.slice(1);
+
+  rtc.connect("ws:" + window.location.href.substring(window.location.protocol.length).split('#')[0], room);
+
+  rtc.on('add remote stream', function(stream, socketId) {
+    console.log("ADDING REMOTE STREAM...");
+    var clone = cloneVideo('you', socketId);
+    document.getElementById(clone.id).setAttribute("class", "");
+    rtc.attachStream(stream, clone.id);
+    subdivideVideos();
+  });
+  rtc.on('disconnect stream', function(data) {
+    console.log('remove ' + data);
+    removeVideo(data);
+  });
+  initFullScreen();
+  initNewRoom();
+  initChat();
+}
+
+window.onresize = function(event) {
+  subdivideVideos();
+};

+ 66 - 0
site/server.js

@@ -0,0 +1,66 @@
+var app = require('express')();
+var server = require('http').createServer(app);
+var webRTC = require('webrtc.io').listen(server);
+
+server.listen(8000);
+
+
+
+app.get('/', function(req, res) {
+  res.sendfile(__dirname + '/index.html');
+});
+
+app.get('/style.css', function(req, res) {
+  res.sendfile(__dirname + '/style.css');
+});
+
+app.get('/fullscrean.png', function(req, res) {
+  res.sendfile(__dirname + '/fullscrean.png');
+});
+
+app.get('/script.js', function(req, res) {
+  res.sendfile(__dirname + '/script.js');
+});
+
+app.get('/webrtc.io.js', function(req, res) {
+  res.sendfile(__dirname + '/webrtc.io.js');
+});
+
+
+webRTC.rtc.on('connect', function(rtc) {
+  //Client connected
+});
+
+webRTC.rtc.on('send answer', function(rtc) {
+  //answer sent
+});
+
+webRTC.rtc.on('disconnect', function(rtc) {
+  //Client disconnect 
+});
+
+webRTC.rtc.on('chat_msg', function(data, socket) {
+  var roomList = webRTC.rtc.rooms[data.room] || [];
+
+  for (var i = 0; i < roomList.length; i++) {
+    var socketId = roomList[i];
+
+    if (socketId !== socket.id) {
+      var soc = webRTC.rtc.getSocket(socketId);
+
+      if (soc) {
+        soc.send(JSON.stringify({
+          "eventName": "receive_chat_msg",
+          "data": {
+            "messages": data.messages,
+            "color": data.color
+          }
+        }), function(error) {
+          if (error) {
+            console.log(error);
+          }
+        });
+      }
+    }
+  }
+});

+ 103 - 0
site/style.css

@@ -0,0 +1,103 @@
+html, body {
+  height: 100%;
+}
+
+html {
+  overflow-y: scroll;
+}
+
+body {
+  padding: 0;
+  margin: 0;
+  background: -moz-linear-gradient(top, rgba(238,238,238,1) 0%, rgba(238,238,238,0) 100%);
+  background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(238,238,238,1)), color-stop(100%,rgba(238,238,238,0)));
+  background: -webkit-linear-gradient(top, rgba(238,238,238,1) 0%,rgba(238,238,238,0) 100%);
+  background: -o-linear-gradient(top, rgba(238,238,238,1) 0%,rgba(238,238,238,0) 100%);
+  background: -ms-linear-gradient(top, rgba(238,238,238,1) 0%,rgba(238,238,238,0) 100%);
+  background: linear-gradient(to bottom, rgba(238,238,238,1) 0%,rgba(238,238,238,0) 100%);
+  filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#eeeeee', endColorstr='#00eeeeee',GradientType=0 );
+}
+
+#videos {
+  position: absolute;
+  top: 0px;
+  left: 0px;
+  right: 0px;
+  bottom: 0px;
+  z-index: 0;
+}
+
+#chatbox {
+  position: absolute;
+  right: 20px;
+  bottom: 0px;
+  z-index: 1;
+}
+
+#chatinput {
+  background: none repeat scroll 0 0 #F9F9F9;
+  border: 1px solid #CCCCCC;
+  border-radius: 0px 0px 5px 5px;
+  font: 300 18px/40px 'light','Helvetica Neue',Arial,Helvetica,sans-serif;
+  height: 40px;
+  letter-spacing: 1px;
+  margin-bottom: 0px;
+  padding: 5px 10px;
+  width: 277px;
+}
+
+#messages {
+  overflow: scroll;
+  background: none repeat scroll 0 0 #F9F9F9;
+  border: 1px solid #CCCCCC;
+  border-radius: 5px 5px 0px 0px;
+  font: 300 18px/40px 'light','Helvetica Neue',Arial,Helvetica,sans-serif;
+  height: 400px;
+  letter-spacing: 1px;
+  margin-bottom: 0px;
+  padding: 5px 10px;
+  width: 255px;
+}
+
+.flip {
+-webkit-transform: rotateY(180deg);
+}
+
+.buttonBox {
+  position: absolute;
+  top: 2px;
+  left: 2px;
+  z-index: 1;
+}
+.button {
+  -moz-box-shadow:inset 0px 1px 0px 0px #ffffff;
+  -webkit-box-shadow:inset 0px 1px 0px 0px #ffffff;
+  box-shadow:inset 0px 1px 0px 0px #ffffff;
+  background:-webkit-gradient( linear, left top, left bottom, color-stop(0.05, #ededed), color-stop(1, #dfdfdf) );
+  background:-moz-linear-gradient( center top, #ededed 5%, #dfdfdf 100% );
+  filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ededed', endColorstr='#dfdfdf');
+  background-color:#ededed;
+  -moz-border-radius:6px;
+  -webkit-border-radius:6px;
+  border-radius:6px;
+  border:1px solid #dcdcdc;
+  display:inline-block;
+  color:#777777;
+  font-family:arial;
+  font-size:15px;
+  font-weight:bold;
+  padding:6px 24px;
+  text-decoration:none;
+  text-shadow:1px 1px 0px #ffffff;
+}
+
+.button:hover {
+  background:-webkit-gradient( linear, left top, left bottom, color-stop(0.05, #dfdfdf), color-stop(1, #ededed) );
+  background:-moz-linear-gradient( center top, #dfdfdf 5%, #ededed 100% );
+  filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#dfdfdf', endColorstr='#ededed');
+  background-color:#dfdfdf;
+}
+.button:active {
+  position:relative;
+  top:1px;
+}

+ 392 - 0
site/webrtc.io.js

@@ -0,0 +1,392 @@
+//CLIENT
+
+ // Fallbacks for vendor-specific variables until the spec is finalized.
+
+var PeerConnection = window.PeerConnection || window.webkitPeerConnection00 || window.webkitRTCPeerConnection;
+var URL = window.URL || window.webkitURL || window.msURL || window.oURL;
+var getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
+
+(function() {
+
+  var rtc;
+  if ('undefined' === typeof module) {
+    rtc = this.rtc = {};
+  } else {
+    rtc = module.exports = {};
+  }
+
+
+  // Holds a connection to the server.
+  rtc._socket = null;
+
+  // Holds identity for the client
+  rtc._me = null;
+
+  // Holds callbacks for certain events.
+  rtc._events = {};
+
+  rtc.on = function(eventName, callback) {
+    rtc._events[eventName] = rtc._events[eventName] || [];
+    rtc._events[eventName].push(callback);
+  };
+
+  rtc.fire = function(eventName, _) {
+    var events = rtc._events[eventName];
+    var args = Array.prototype.slice.call(arguments, 1);
+
+    if (!events) {
+      return;
+    }
+
+    for (var i = 0, len = events.length; i < len; i++) {
+      events[i].apply(null, args);
+    }
+  };
+
+  // Holds the STUN/ICE server to use for PeerConnections.
+  rtc.SERVER = {iceServers:[{url:"stun:stun.l.google.com:19302"}]};
+
+  // Reference to the lone PeerConnection instance.
+  rtc.peerConnections = {};
+
+  // Array of known peer socket ids
+  rtc.connections = [];
+  // Stream-related variables.
+  rtc.streams = [];
+  rtc.numStreams = 0;
+  rtc.initializedStreams = 0;
+
+
+  // Reference to the data channels
+  rtc.dataChannels = {};
+
+  // PeerConnection datachannel configuration
+  rtc.dataChannelConfig = {optional: [ {RtpDataChannels: true} ] };
+
+
+  // check whether data channel is supported.
+  rtc.checkDataChannelSupport = function() {
+    try {
+      // raises exception if createDataChannel is not supported
+      var pc = new PeerConnection(rtc.SERVER, rtc.dataChannelConfig);
+      channel = pc.createDataChannel('supportCheck', {reliable: false});
+      channel.close();
+      return true;
+    } catch(e) {
+      return false;
+    }
+  };
+
+  rtc.dataChannelSupport = rtc.checkDataChannelSupport();
+
+
+  /**
+   * Connects to the websocket server.
+   */
+  rtc.connect = function(server, room) {
+    room = room || ""; // by default, join a room called the blank string
+    rtc._socket = new WebSocket(server);
+
+    rtc._socket.onopen = function() {
+
+      rtc._socket.send(JSON.stringify({
+        "eventName": "join_room",
+        "data":{
+          "room": room
+        }
+      }));
+
+      rtc._socket.onmessage = function(msg) {
+        var json = JSON.parse(msg.data);
+        rtc.fire(json.eventName, json.data);
+      };
+
+      rtc._socket.onerror = function(err) {
+        console.error('onerror');
+        console.error(err);
+      };
+
+      rtc._socket.onclose = function(data) {
+        rtc.fire('disconnect stream', rtc._socket.id);
+        delete rtc.peerConnections[rtc._socket.id];
+      };
+
+      rtc.on('get_peers', function(data) {
+        rtc.connections = data.connections;
+        rtc._me = data.you;
+        // fire connections event and pass peers
+        rtc.fire('connections', rtc.connections);
+      });
+
+      rtc.on('receive_ice_candidate', function(data) {
+        var candidate = new RTCIceCandidate(data);
+        rtc.peerConnections[data.socketId].addIceCandidate(candidate);
+        rtc.fire('receive ice candidate', candidate);
+      });
+
+      rtc.on('new_peer_connected', function(data) {
+        rtc.connections.push(data.socketId);
+
+        var pc = rtc.createPeerConnection(data.socketId);
+        for (var i = 0; i < rtc.streams.length; i++) {
+          var stream = rtc.streams[i];
+          pc.addStream(stream);
+        }
+      });
+
+      rtc.on('remove_peer_connected', function(data) {
+        rtc.fire('disconnect stream', data.socketId);
+        delete rtc.peerConnections[data.socketId];
+      });
+
+      rtc.on('receive_offer', function(data) {
+        rtc.receiveOffer(data.socketId, data.sdp);
+        rtc.fire('receive offer', data);
+      });
+
+      rtc.on('receive_answer', function(data) {
+        rtc.receiveAnswer(data.socketId, data.sdp);
+        rtc.fire('receive answer', data);
+      });
+
+      rtc.fire('connect');
+    };
+  };
+
+
+  rtc.sendOffers = function() {
+    for (var i = 0, len = rtc.connections.length; i < len; i++) {
+      var socketId = rtc.connections[i];
+      rtc.sendOffer(socketId);
+    }
+  };
+
+  rtc.onClose = function(data) {
+    rtc.on('close_stream', function() {
+      rtc.fire('close_stream', data);
+    });
+  };
+
+  rtc.createPeerConnections = function() {
+    for (var i = 0; i < rtc.connections.length; i++) {
+      rtc.createPeerConnection(rtc.connections[i]);
+    }
+  };
+
+  rtc.createPeerConnection = function(id) {
+    var config;
+    if (rtc.dataChannelSupport)
+      config = rtc.dataChannelConfig;
+
+    var pc = rtc.peerConnections[id] = new PeerConnection(rtc.SERVER, config);
+    pc.onicecandidate = function(event) {
+      if (event.candidate) {
+         rtc._socket.send(JSON.stringify({
+           "eventName": "send_ice_candidate",
+           "data": {
+              "label": event.candidate.label,
+              "candidate": event.candidate.candidate,
+              "socketId": id
+           }
+         }));
+       }
+       rtc.fire('ice candidate', event.candidate);
+     };
+
+    pc.onopen = function() {
+      // TODO: Finalize this API
+      rtc.fire('peer connection opened');
+    };
+
+    pc.onaddstream = function(event) {
+      // TODO: Finalize this API
+      rtc.fire('add remote stream', event.stream, id);
+    };
+
+    if (rtc.dataChannelSupport) {
+      pc.ondatachannel = function (evt) {
+        console.log('data channel connecting ' + id);
+        rtc.addDataChannel(id, evt.channel);
+      };
+    }
+
+    return pc;
+  };
+
+  rtc.sendOffer = function(socketId) {
+    var pc = rtc.peerConnections[socketId];
+    pc.createOffer( function(session_description) {
+    pc.setLocalDescription(session_description);
+    rtc._socket.send(JSON.stringify({
+        "eventName": "send_offer",
+        "data":{
+            "socketId": socketId,
+            "sdp": session_description
+            }
+        }));
+    });
+  };
+
+
+  rtc.receiveOffer = function(socketId, sdp) {
+    var pc = rtc.peerConnections[socketId];
+    pc.setRemoteDescription(new RTCSessionDescription(sdp));
+    rtc.sendAnswer(socketId);
+  };
+
+
+  rtc.sendAnswer = function(socketId) {
+    var pc = rtc.peerConnections[socketId];
+    pc.createAnswer( function(session_description) {
+    pc.setLocalDescription(session_description);
+    rtc._socket.send(JSON.stringify({
+        "eventName": "send_answer",
+        "data":{
+            "socketId": socketId,
+            "sdp": session_description
+            }
+        }));
+    var offer = pc.remoteDescription;
+    });
+  };
+
+
+  rtc.receiveAnswer = function(socketId, sdp) {
+    var pc = rtc.peerConnections[socketId];
+    pc.setRemoteDescription(new RTCSessionDescription(sdp));
+  };
+
+
+  rtc.createStream = function(opt, onSuccess, onFail) {
+    var options;
+    onSuccess = onSuccess ||
+    function() {};
+    onFail = onFail ||
+    function() {};
+
+    options = {
+      video: !!opt.video,
+      audio: !!opt.audio
+    };
+
+    if (getUserMedia) {
+      rtc.numStreams++;
+      getUserMedia.call(navigator, options, function(stream) {
+
+        rtc.streams.push(stream);
+        rtc.initializedStreams++;
+        onSuccess(stream);
+        if (rtc.initializedStreams === rtc.numStreams) {
+          rtc.fire('ready');
+        }
+      }, function() {
+        alert("Could not connect stream.");
+        onFail();
+      });
+    } else {
+      alert('webRTC is not yet supported in this browser.');
+    }
+  };
+
+  rtc.addStreams = function() {
+    for (var i = 0; i < rtc.streams.length; i++) {
+      var stream = rtc.streams[i];
+      for (var connection in rtc.peerConnections) {
+        rtc.peerConnections[connection].addStream(stream);
+      }
+    }
+  };
+
+  rtc.attachStream = function(stream, domId) {
+    document.getElementById(domId).src = URL.createObjectURL(stream);
+  };
+
+
+  rtc.createDataChannel = function(pcOrId, label) {
+    if (!rtc.dataChannelSupport) {
+      alert('webRTC data channel is not yet supported in this browser,' +
+            ' or you must turn on experimental flags');
+      return;
+    }
+
+    if (typeof(pcOrId) === 'string') {
+      id = pcOrId;
+      pc = rtc.peerConnections[pcOrId];
+    } else {
+      pc = pcOrId;
+      id = undefined;
+      for (var key in rtc.peerConnections) {
+        if (rtc.peerConnections[key] === pc)
+          id = key;
+      }
+    }
+
+    if (!id)
+      throw new Error ('attempt to createDataChannel with unknown id');
+
+    if (!pc || !(pc instanceof PeerConnection))
+      throw new Error ('attempt to createDataChannel without peerConnection');
+
+    // need a label
+    label = label || 'fileTransfer' || String(id);
+
+    // chrome only supports reliable false atm.
+    options = {reliable: false};
+
+    try {
+      console.log('createDataChannel ' + id);
+      channel = pc.createDataChannel(label, options);
+    } catch (error) {
+      console.log('seems that DataChannel is NOT actually supported!');
+      throw error;
+    }
+
+    return rtc.addDataChannel(id, channel);
+  };
+
+  rtc.addDataChannel = function(id, channel) {
+
+    channel.onopen = function() {
+      console.log('data stream open ' + id);
+      rtc.fire('data stream open', channel);
+    };
+
+    channel.onclose = function(event) {
+      delete rtc.dataChannels[id];
+      console.log('data stream close ' + id);
+      rtc.fire('data stream close', channel);
+    };
+
+    channel.onmessage = function(message) {
+      console.log('data stream message ' + id);
+      console.log(message);
+      rtc.fire('data stream data', channel, message.data);
+    };
+
+    channel.onerror = function(err) {
+      console.log('data stream error ' + id + ': ' + err);
+      rtc.fire('data stream error', channel, err);
+    };
+
+    // track dataChannel
+    rtc.dataChannels[id] = channel;
+    return channel;
+  };
+
+  rtc.addDataChannels = function() {
+    if (!rtc.dataChannelSupport)
+      return;
+
+    for (var connection in rtc.peerConnections)
+      rtc.createDataChannel(connection);
+  };
+
+
+  rtc.on('ready', function() {
+    rtc.createPeerConnections();
+    rtc.addStreams();
+    rtc.addDataChannels();
+    rtc.sendOffers();
+  });
+
+}).call(this);