Browse Source

now giving the socket.io and client fils

Dennis Mårtensson 12 years ago
parent
commit
a3e761f866
4 changed files with 515 additions and 0 deletions
  1. 140 0
      example/index.html
  2. 39 0
      example/style.css
  3. 221 0
      lib/io.js
  4. 115 0
      server.js

+ 140 - 0
example/index.html

@@ -0,0 +1,140 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Example webrtc.io</title>
+    <link type="text/css" href="/style.css" rel="stylesheet"></link>
+
+    <script src="http://localhost/socket.io/socket.io.js"></script>
+    <script src="/io.js"></script>
+  </head>
+  <body onload="init()">
+    <div id="videos">
+      <video id="you" autoplay></video>
+    </div>
+    <div id="chatbox">
+      <div id="messages">
+      </div>
+      <div id="chat">
+        <input id="chatinput" type="text"/>
+      </div>
+    </div>
+    <script>
+      var videos = [];
+
+      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 - 200) / perRow);
+        var height = Math.floor(window.innerHeight / perColumn);
+        console.log(width, height);
+        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 initChat() {
+        var input = document.getElementById("chatinput");
+        input.addEventListener('keydown', function(event) {
+          var key = event.which || event.keyCode;
+          if (key === 13) {
+            rtc._socket.emit('chat msg', input.value);
+            addToChat(input.value);
+            input.value = "";
+          }
+        }, false);
+        rtc._socket.on('receive chat msg', function(data) {
+          console.log(data.color);
+          addToChat(data.msg, data.color.toString(16));
+        });
+      }
+
+      function addToChat(msg, color) {
+        var messages = document.getElementById('messages');
+        msg = sanitize(msg);
+        if (color) {
+          msg = '<span style="color: #' + color + ';">' + msg + '</span>';
+        } else {
+          msg = "<strong>" + msg + "</strong>";
+        }
+        messages.innerHTML = messages.innerHTML + msg + '<br>';
+      }
+
+      function sanitize(msg) {
+        return msg.replace(/</g, '&lt;');
+      }
+
+      function init() {
+        rtc.createStream('you', function(stream) {
+          videos.push(document.getElementById('you'));
+          rtc.attachStream(stream, 'you');
+          subdivideVideos();
+        });
+
+        rtc.connect("http://localhost/");
+        rtc.on('add remote stream', function(stream, socketId) {
+          console.log("ADDING REMOTE STREAM...");
+          var clone = cloneVideo('you', socketId);
+          rtc.attachStream(stream, clone.id);
+          subdivideVideos();
+        });
+        rtc.on('disconnect stream', function(data) {
+            console.log('remove ' + data);
+            removeVideo(data);
+        });
+        initChat();
+      }
+      
+      function onClose(data) {
+//        video.parentNode.removeChild(video);
+      }
+
+      window.onresize = function(event) {
+        subdivideVideos();
+      };
+    </script>
+  </body>
+</html>

+ 39 - 0
example/style.css

@@ -0,0 +1,39 @@
+html, body {
+  height: 100%;
+}
+
+html {
+  overflow-y: scroll;
+}
+
+body {
+  padding: 0;
+  margin: 0;
+}
+
+#videos {
+  position: absolute;
+  top: 0;
+  left: 0;
+  bottom: 0;
+  right: 200px;
+}
+
+#chatbox {
+  position: absolute;
+  top: 0;
+  width: 200px;
+  right: 0;
+  bottom: 0;
+}
+
+#chatinput {
+  display: block;
+  border: 1px solid black;
+  -moz-box-sizing: border-box;
+  -webkit-box-sizing: border-box;
+  box-sizing: border-box;
+  width: 100%;
+  padding: 7px 3px;
+}
+

+ 221 - 0
lib/io.js

@@ -0,0 +1,221 @@
+(function() {
+
+var rtc = this.rtc = {};
+
+// Fallbacks for vendor-specific variables until the spec is finalized.
+var PeerConnection = window.PeerConnection || window.webkitPeerConnection00;
+var URL = window.URL || window.webkitURL || window.msURL || window.oURL;
+var getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia ||
+                   navigator.mozGetUserMedia || navigator.msGetUserMedia;
+
+// Holds a connection to the server.
+rtc._socket = null;
+
+// Holds callbacks for certain events.
+rtc._events = {};
+
+// Holds the STUN server to use for PeerConnections.
+rtc.SERVER = "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;
+
+/**
+ * Connects to the socket.io server.
+ */
+rtc.connect = function(server) {
+  rtc._socket = io.connect(server);
+  rtc._socket.on('connect', function() {
+    rtc.fire('connect');
+  });
+
+  // TODO: Fix possible race condition if get peers is not emitted
+  // before the "ready" event is fired.
+  rtc._socket.on('get peers', function(data) {
+    var peers = data.connections;
+    rtc.connections = peers;
+
+    // fire connections event and pass peers
+    rtc.fire('connections', peers);
+  });
+
+  rtc._socket.on('receive ice candidate', function(data) {
+    var candidate = new IceCandidate(data.label, data.candidate);
+    rtc.peerConnections[data.socketId].processIceMessage(candidate);
+    rtc.fire('receive ice candidate', candidate);
+  });
+  rtc._socket.on('new peer connected', function(data) {
+    var pc = rtc.createPeerConnection(data.socketId);
+    for (var i = 0; i < rtc.streams.length; i++) {
+      var stream = rtc.streams[i];
+      pc.addStream(stream);
+    }
+  });
+  rtc._socket.on('remove peer connected', function(data) {
+    console.log(data);
+    //the actual onremovestream function is not yet supported. Here is a temporary workaround
+    rtc.fire('disconnect stream', data.socketId);
+    onClose(data.socketId);
+    //rtc.peerConnections[data.socketId].close();
+    delete rtc.peerConnections[data.socketId];
+  });
+
+  rtc._socket.on('receive offer', function(data) {
+    rtc.receiveOffer(data.socketId, data.sdp);
+    rtc.fire('receive offer', data);
+  });
+
+  rtc._socket.on('receive answer', function(data) {
+    rtc.receiveAnswer(data.socketId, data.sdp);
+    rtc.fire('receive answer', data);
+  });
+};
+
+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._socket.on('close stream', function() {
+        rtc.fire('close stream', data);
+    });
+}
+
+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);
+  }
+};
+rtc.createPeerConnections = function() {
+    for (var i = 0; i < rtc.connections.length; i++) {
+        rtc.createPeerConnection(rtc.connections[i]);
+    }
+};
+
+rtc.createPeerConnection = function(id) {
+  var pc = rtc.peerConnections[id] = new PeerConnection(rtc.SERVER, function(candidate, moreToFollow) {
+    if (candidate) {
+      rtc._socket.emit('receive ice candidate', {
+        label: candidate.label,
+        candidate: candidate.toSdp(),
+        socketId: id
+      });
+    }
+    rtc.fire('ice candidate', candidate, moreToFollow);
+  });
+
+  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);
+  };
+  return pc;
+};
+
+rtc.sendOffer = function(socketId) {
+  var pc = rtc.peerConnections[socketId];
+  // TODO: Abstract away video: true, audio: true for offers
+  var offer = pc.createOffer({ video: true, audio: true });
+  pc.setLocalDescription(pc.SDP_OFFER, offer);
+  rtc._socket.emit('send offer', { socketId: socketId, sdp: offer.toSdp() });
+  pc.startIce();
+};
+
+rtc.receiveOffer = function(socketId, sdp) {
+  var pc = rtc.peerConnections[socketId];
+  pc.setRemoteDescription(pc.SDP_OFFER, new SessionDescription(sdp));
+  rtc.sendAnswer(socketId);
+};
+
+rtc.sendAnswer = function(socketId) {
+  var pc = rtc.peerConnections[socketId];
+  var offer = pc.remoteDescription;
+  // TODO: Abstract away video: true, audio: true for answers
+  var answer = pc.createAnswer(offer.toSdp(), {video: true, audio: true});
+  pc.setLocalDescription(pc.SDP_ANSWER, answer);
+  rtc._socket.emit('send answer', { socketId: socketId, sdp: answer.toSdp() });
+  pc.startIce();
+};
+
+rtc.receiveAnswer = function(socketId, sdp) {
+  var pc = rtc.peerConnections[socketId];
+  pc.setRemoteDescription(pc.SDP_ANSWER, new SessionDescription(sdp));
+};
+
+rtc.createStream = function(domId, onSuccess, onFail) {
+  var el = document.getElementById(domId);
+  var options;
+  onSuccess = onSuccess || function() {};
+  onFail = onFail || function() {};
+
+  if (el.tagName.toLowerCase() === "audio") {
+    options = { audio: true };
+  } else {
+    options = { video: true, audio: true };
+  }
+
+  if (getUserMedia)  {  
+    rtc.numStreams++;
+    getUserMedia.call(navigator, options, function(stream) {
+      el.src = URL.createObjectURL(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.on('ready', function() {
+  rtc.createPeerConnections();
+  rtc.addStreams();
+  rtc.sendOffers();
+});
+
+}).call(this);

+ 115 - 0
server.js

@@ -0,0 +1,115 @@
+//npm install express@2.5.1 
+
+
+var app = require('express').createServer()
+  , io = require('socket.io').listen(app);
+
+app.listen(80);
+
+app.get('/', function (req, res) {
+  res.sendfile(__dirname + '/example/index.html');
+});
+
+app.get('/style.css', function (req, res) {
+  res.sendfile(__dirname + '/example/style.css');
+});
+
+app.get('/io.js', function (req, res) {
+  res.sendfile(__dirname + '/lib/io.js');
+});
+
+var connections = [];
+var colors = {};
+
+io.sockets.on('connection', function(socket) {
+  console.log("connection received");
+  connections.push(socket);
+  colors[socket.id] = Math.floor(Math.random()* 0xFFFFFF)
+
+  var connectionsId = [];
+
+  for (var i = 0, len = connections.length; i < len; i++) {
+    var id = connections[i].id;
+
+    if (id !== socket.id) {
+      connectionsId.push(id);
+    }
+  }
+
+  socket.emit('get peers', {
+    connections: connectionsId
+  });
+
+  socket.broadcast.emit('new peer connected', { socketId: socket.id });
+
+  socket.on('disconnect', function() {
+
+    for (var i = 0; i < connections.length; i++) {
+      var id = connections[i].id;
+
+      if (id == socket.id) {
+        connections.splice(i, 1);
+        i--;
+        socket.broadcast.emit('remove peer connected', { socketId: socket.id });
+      }
+    }
+  });
+
+  socket.on('receive ice candidate', function(data) {
+    console.log("ice candidate received");
+
+    var soc = getSocket(data.socketId);
+
+    if (soc) {
+      soc.emit('receive ice candidate', {
+        label: data.label,
+        candidate: data.candidate,
+        socketId: socket.id
+      });
+    }
+  });
+
+  socket.on('send offer', function(data) {
+    console.log("offer received");
+
+    var soc = getSocket(data.socketId);
+
+    if (soc) {
+      soc.emit('receive offer', {
+        sdp: data.sdp,
+        socketId: socket.id
+      });
+    }
+  });
+
+  socket.on('send answer', function(data) {
+    console.log("answer received");
+
+    var soc = getSocket(data.socketId);
+
+    if (soc) {
+      soc.emit('receive answer', {
+        sdp: data.sdp,
+        socketId: socket.id
+      });
+    }
+  });
+
+
+  socket.on('chat msg', function(msg) {
+    console.log("chat received");
+    
+    socket.broadcast.emit('receive chat msg', { msg: msg, color: colors[socket.id]});
+  });
+
+});
+
+
+function getSocket(id) {
+  for (var i = 0; i < connections.length; i++) {
+    var socket = connections[i];
+    if (id === socket.id) {
+      return socket;
+    }
+  }
+}