From 14adace866d4c71c5a9e5e39129f770670d78427 Mon Sep 17 00:00:00 2001
From: Moritz Langenstein <ml5717@ic.ac.uk>
Date: Fri, 11 Oct 2019 18:03:50 +0100
Subject: [PATCH] (ml5717) Pre-built liowebrtc to dist/

---
 .gitignore                 |   1 -
 dist/Edge.js               |  56 ++++
 dist/PeerGraph.js          | 130 ++++++++
 dist/PeerNode.js           | 103 ++++++
 dist/PeerOptimizer.js      | 129 ++++++++
 dist/constants.js          |  42 +++
 dist/liowebrtc.js          | 656 +++++++++++++++++++++++++++++++++++++
 dist/localmedia.js         | 324 ++++++++++++++++++
 dist/peer.js               | 356 ++++++++++++++++++++
 dist/socketioconnection.js |  51 +++
 dist/webrtc.js             | 236 +++++++++++++
 dist/webrtcsupport.js      |  45 +++
 12 files changed, 2128 insertions(+), 1 deletion(-)
 create mode 100644 dist/Edge.js
 create mode 100644 dist/PeerGraph.js
 create mode 100644 dist/PeerNode.js
 create mode 100644 dist/PeerOptimizer.js
 create mode 100644 dist/constants.js
 create mode 100644 dist/liowebrtc.js
 create mode 100644 dist/localmedia.js
 create mode 100644 dist/peer.js
 create mode 100644 dist/socketioconnection.js
 create mode 100644 dist/webrtc.js
 create mode 100644 dist/webrtcsupport.js

diff --git a/.gitignore b/.gitignore
index 74c2dde..d5ba7b3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,4 @@
 node_modules
 .DS_Store
-dist
 yarn-error.log
 .eslintcache
diff --git a/dist/Edge.js b/dist/Edge.js
new file mode 100644
index 0000000..4f0eec1
--- /dev/null
+++ b/dist/Edge.js
@@ -0,0 +1,56 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+var Edge = function () {
+  function Edge(startNode, endNode) {
+    var weight = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
+
+    _classCallCheck(this, Edge);
+
+    this.node1 = startNode;
+    this.node2 = endNode;
+    this.weight = weight;
+  }
+
+  _createClass(Edge, [{
+    key: "getId",
+    value: function getId() {
+      var startNodeId = this.node1.getId();
+      var endNodeId = this.node2.getId();
+      return startNodeId + "_" + endNodeId;
+    }
+  }, {
+    key: "getWeight",
+    value: function getWeight() {
+      return this.weight;
+    }
+  }, {
+    key: "setWeight",
+    value: function setWeight(weight) {
+      this.weight = weight;
+    }
+  }, {
+    key: "reverse",
+    value: function reverse() {
+      var tmp = this.node1;
+      this.node1 = this.node2;
+      this.node2 = tmp;
+    }
+  }, {
+    key: "toString",
+    value: function toString() {
+      return this.getId();
+    }
+  }]);
+
+  return Edge;
+}();
+
+exports.default = Edge;
\ No newline at end of file
diff --git a/dist/PeerGraph.js b/dist/PeerGraph.js
new file mode 100644
index 0000000..45a57e7
--- /dev/null
+++ b/dist/PeerGraph.js
@@ -0,0 +1,130 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+var PeerGraph = function () {
+  function PeerGraph() {
+    _classCallCheck(this, PeerGraph);
+
+    this.nodes = {};
+    this.edges = {};
+    this.edgeCount = 0;
+  }
+
+  _createClass(PeerGraph, [{
+    key: "addEdge",
+    value: function addEdge(edge) {
+      var node1 = this.getNodeById(edge.node1.getId());
+      var node2 = this.getNodeById(edge.node2.getId());
+      if (!node1) {
+        this.addNode(edge.node1);
+        node1 = this.getNodeById(edge.node1.getId());
+      }
+      if (!node2) {
+        this.addNode(edge.node2);
+        node2 = this.getNodeById(edge.node2.getId());
+      }
+
+      if (this.edges[edge.getId()]) {
+        // throw new Error('Edge already exists');
+      } else {
+        this.edges[edge.getId()] = edge;
+      }
+      // Add edge to both node instances because it's an undirected graph
+      node1.addEdge(edge);
+      node2.addEdge(edge);
+      return this;
+    }
+  }, {
+    key: "addNode",
+    value: function addNode(newNode) {
+      this.nodes[newNode.getId()] = newNode;
+      return this;
+    }
+  }, {
+    key: "getNodeById",
+    value: function getNodeById(id) {
+      return this.nodes[id];
+    }
+  }, {
+    key: "getNeighbors",
+    value: function getNeighbors(node) {
+      return node.getNeighbors();
+    }
+  }, {
+    key: "getWeight",
+    value: function getWeight() {
+      return this.getAllEdges().reduce(function (weight, edge) {
+        return weight + edge.weight;
+      }, 0);
+    }
+  }, {
+    key: "getAllNodes",
+    value: function getAllNodes() {
+      return Object.values(this.nodes);
+    }
+  }, {
+    key: "getAllEdges",
+    value: function getAllEdges() {
+      return Object.values(this.edges);
+    }
+  }, {
+    key: "findNodeById",
+    value: function findNodeById(nodeId) {
+      if (this.nodes[nodeId]) {
+        return this.nodes[nodeId];
+      }
+      return null;
+    }
+  }, {
+    key: "findEdge",
+    value: function findEdge(node1, node2) {
+      var node = this.getNodeById(node1.getId());
+      if (!node) {
+        return null;
+      }
+      return node.findEdge(node2);
+    }
+  }, {
+    key: "deleteEdge",
+    value: function deleteEdge(edge) {
+      if (!edge) {
+        return;
+      }
+      if (this.edges[edge.getId()]) {
+        delete this.edges[edge.getId()];
+      }
+      var node1 = this.getNodeById(edge.node1.getId());
+      var node2 = this.getNodeById(edge.node2.getId());
+      node1.deleteEdge(edge);
+      node2.deleteEdge(edge);
+    }
+  }, {
+    key: "getNodeIndices",
+    value: function getNodeIndices() {
+      var nodeIndices = {};
+      this.getAllNodes().forEach(function (node, index) {
+        nodeIndices[node.getId()] = index;
+      });
+      return nodeIndices;
+    }
+  }, {
+    key: "toString",
+    value: function toString() {
+      return Object.keys(this.nodes).toString();
+    }
+  }, {
+    key: "toJSON",
+    value: function toJSON() {}
+  }]);
+
+  return PeerGraph;
+}();
+
+exports.default = PeerGraph;
\ No newline at end of file
diff --git a/dist/PeerNode.js b/dist/PeerNode.js
new file mode 100644
index 0000000..c689119
--- /dev/null
+++ b/dist/PeerNode.js
@@ -0,0 +1,103 @@
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+var Node = function () {
+  function Node(value) {
+    _classCallCheck(this, Node);
+
+    if (value === undefined) {
+      throw new Error('Node must have an ID');
+    }
+    this.value = value;
+    this.edges = {};
+  }
+
+  _createClass(Node, [{
+    key: 'addEdge',
+    value: function addEdge(edge) {
+      this.edges[edge.getId()] = edge;
+      return this;
+    }
+  }, {
+    key: 'deleteEdge',
+    value: function deleteEdge(edge) {
+      delete this.edges[edge.getId()];
+    }
+  }, {
+    key: 'getEdges',
+    value: function getEdges() {
+      return Object.values(this.edges);
+    }
+  }, {
+    key: 'getDegree',
+    value: function getDegree() {
+      return Object.keys(this.edges).length;
+    }
+  }, {
+    key: 'getNeighbors',
+    value: function getNeighbors() {
+      var _this = this;
+
+      var edges = Object.values(this.edges);
+      var nodes = edges.map(function (e) {
+        return e.node1 === _this ? e.node2 : e.node1;
+      });
+      return nodes;
+    }
+  }, {
+    key: 'hasEdge',
+    value: function hasEdge(requiredEdge) {
+      var edgeNode = this.edges.filter(function (edge) {
+        return edge.getId() === requiredEdge.getId();
+      });
+      return !!edgeNode.length;
+    }
+  }, {
+    key: 'hasNeighbor',
+    value: function hasNeighbor(node) {
+      var nodeWeWant = Object.values(this.edges).filter(function (e) {
+        return e.node1.getId() === node.getId() || e.node2.getId() === node.getId();
+      });
+      return !!nodeWeWant.length;
+    }
+  }, {
+    key: 'findEdge',
+    value: function findEdge(node) {
+      var result = Object.values(this.edges).filter(function (e) {
+        return e.node1.getId() === node.getId() || e.node2.getId() === node.getId();
+      });
+      return result.length ? result[0] : null;
+    }
+  }, {
+    key: 'getId',
+    value: function getId() {
+      return this.value;
+    }
+  }, {
+    key: 'deleteAllEdges',
+    value: function deleteAllEdges() {
+      var _this2 = this;
+
+      this.getEdges().forEach(function (e) {
+        return _this2.deleteEdge(e);
+      });
+      return this;
+    }
+  }, {
+    key: 'toString',
+    value: function toString() {
+      return '' + this.value;
+    }
+  }]);
+
+  return Node;
+}();
+
+exports.default = Node;
\ No newline at end of file
diff --git a/dist/PeerOptimizer.js b/dist/PeerOptimizer.js
new file mode 100644
index 0000000..2193282
--- /dev/null
+++ b/dist/PeerOptimizer.js
@@ -0,0 +1,129 @@
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.Graph = undefined;
+exports.addNode = addNode;
+exports.addConnection = addConnection;
+exports.removeConnection = removeConnection;
+exports.getNeighbors = getNeighbors;
+exports.isNeighbor = isNeighbor;
+exports.getPeerLatencies = getPeerLatencies;
+exports.average = average;
+exports.squaredDiffs = squaredDiffs;
+exports.stdDeviation = stdDeviation;
+exports.getLatencyZScores = getLatencyZScores;
+exports.getDroppablePeer = getDroppablePeer;
+
+var _PeerGraph = require('./PeerGraph');
+
+var _PeerGraph2 = _interopRequireDefault(_PeerGraph);
+
+var _Edge = require('./Edge');
+
+var _Edge2 = _interopRequireDefault(_Edge);
+
+var _PeerNode = require('./PeerNode');
+
+var _PeerNode2 = _interopRequireDefault(_PeerNode);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+var Graph = exports.Graph = new _PeerGraph2.default();
+
+function addNode(nodeId) {
+  var node = new _PeerNode2.default(nodeId);
+  Graph.addNode(node);
+}
+
+function addConnection(node1Id, node2Id) {
+  var latency = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
+
+  var nodeA = Graph.getNodeById(node1Id) || new _PeerNode2.default(node1Id);
+  var nodeB = Graph.getNodeById(node2Id) || new _PeerNode2.default(node2Id);
+  var edgeAB = new _Edge2.default(nodeA, nodeB, latency);
+  return Graph.addEdge(edgeAB);
+}
+
+function removeConnection(node1Id, node2Id) {
+  var nodeA = Graph.getNodeById(node1Id);
+  var nodeB = Graph.getNodeById(node2Id);
+  if (nodeA && nodeB) Graph.deleteEdge(Graph.findEdge(nodeA, nodeB));
+}
+
+function getNeighbors(nodeId) {
+  var node = Graph.getNodeById(nodeId);
+  var neighbors = node.getNeighbors();
+  return neighbors.map(function (n) {
+    return n.getId();
+  });
+}
+
+function isNeighbor(node1Id, node2Id) {
+  var nodeA = Graph.getNodeById(node1Id) || new _PeerNode2.default(node1Id);
+  var nodeB = Graph.getNodeById(node2Id) || new _PeerNode2.default(node2Id);
+  if (nodeA.hasNeighbor(nodeB)) {
+    return true;
+  }
+  return false;
+}
+
+function getPeerLatencies(nodeId) {
+  var node = Graph.findNodeById(nodeId);
+  if (node) {
+    var result = {};
+    var edges = node.getEdges();
+    edges.forEach(function (e) {
+      var id = e.node1.getId() === nodeId ? e.node2.getId() : e.node1.getId();
+      var latency = e.getWeight();
+      result[id] = latency;
+    });
+    return result;
+  }
+}
+
+function average(vals) {
+  var total = vals.reduce(function (sum, val) {
+    return val + sum;
+  });
+  return total / vals.length;
+}
+
+function squaredDiffs(vals, avg) {
+  var sqd = vals.map(function (val) {
+    return Math.pow(val - avg, 2);
+  });
+  return sqd;
+}
+
+function stdDeviation(sqDiffs) {
+  var sum = sqDiffs.reduce(function (total, x) {
+    return total + x;
+  });
+  return Math.sqrt(sum / sqDiffs.length);
+}
+
+function getLatencyZScores(nodeId) {
+  var peerLatencyCache = getPeerLatencies(nodeId);
+  var peerIds = Object.keys(peerLatencyCache);
+  var peerLatencies = Object.values(peerLatencyCache);
+  var avg = average(peerLatencies);
+  var standardDeviation = stdDeviation(squaredDiffs(peerLatencies, avg));
+  var zScores = {};
+  peerIds.forEach(function (val, i) {
+    zScores[val] = (peerLatencies[i] - avg) / standardDeviation;
+  });
+  return zScores;
+}
+
+function getDroppablePeer(nodeId) {
+  var zScores = getLatencyZScores(nodeId);
+  var droppable = zScores.filter(function (s) {
+    return s <= -1;
+  });
+  var orderedDroppable = droppable.sort(function (a, b) {
+    return b - a;
+  });
+  return orderedDroppable[0];
+}
\ No newline at end of file
diff --git a/dist/constants.js b/dist/constants.js
new file mode 100644
index 0000000..ce701fe
--- /dev/null
+++ b/dist/constants.js
@@ -0,0 +1,42 @@
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+var inheritedMethods = exports.inheritedMethods = ['mute', 'unmute', 'pauseVideo', 'resumeVideo', 'pause', 'resume', 'sendToAll', 'sendDirectlyToAll', 'getPeers', 'getPeerByNick', 'getPeerById', 'shout', 'whisper', 'broadcast', 'transmit'];
+
+var defaultConfig = exports.defaultConfig = {
+  url: 'https://sm1.lio.app:443/',
+  socketio: { forceNew: true },
+  connection: null,
+  debug: false,
+  localVideoEl: '',
+  remoteVideosEl: '',
+  enableDataChannels: true,
+  autoRequestMedia: false,
+  dataOnly: false,
+  autoRemoveVideos: true,
+  adjustPeerVolume: true,
+  peerVolumeWhenSpeaking: 0.25,
+  media: {
+    video: true,
+    audio: true
+  },
+  receiveMedia: {
+    offerToReceiveAudio: 1,
+    offerToReceiveVideo: 1
+  },
+  localVideo: {
+    autoplay: true,
+    mirror: true,
+    muted: true,
+    audio: false
+  },
+  constraints: {
+    maxPeers: 0,
+    minPeers: 2
+  },
+  selfOptimize: true
+};
+
+var defaultChannel = exports.defaultChannel = 'liowebrtc';
\ No newline at end of file
diff --git a/dist/liowebrtc.js b/dist/liowebrtc.js
new file mode 100644
index 0000000..a07362a
--- /dev/null
+++ b/dist/liowebrtc.js
@@ -0,0 +1,656 @@
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
+
+var _wildemitter = require('wildemitter');
+
+var _wildemitter2 = _interopRequireDefault(_wildemitter);
+
+var _attachmediastream = require('attachmediastream');
+
+var _attachmediastream2 = _interopRequireDefault(_attachmediastream);
+
+var _mockconsole = require('mockconsole');
+
+var _mockconsole2 = _interopRequireDefault(_mockconsole);
+
+var _webrtc = require('./webrtc');
+
+var _webrtc2 = _interopRequireDefault(_webrtc);
+
+var _webrtcsupport = require('./webrtcsupport');
+
+var _webrtcsupport2 = _interopRequireDefault(_webrtcsupport);
+
+var _socketioconnection = require('./socketioconnection');
+
+var _socketioconnection2 = _interopRequireDefault(_socketioconnection);
+
+var _PeerOptimizer = require('./PeerOptimizer');
+
+var _constants = require('./constants');
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+var LioWebRTC = function (_WildEmitter) {
+  _inherits(LioWebRTC, _WildEmitter);
+
+  function LioWebRTC(opts) {
+    _classCallCheck(this, LioWebRTC);
+
+    var _this = _possibleConstructorReturn(this, (LioWebRTC.__proto__ || Object.getPrototypeOf(LioWebRTC)).call(this));
+
+    var self = _this;
+    var options = opts || {};
+    _this.config = _constants.defaultConfig;
+    var config = _this.config;
+
+    _this.peerDataCache = {};
+    _this.unconnectivePeers = {};
+    _this.id = '';
+    _this.roomCount = 0;
+    _this.roomName = '';
+
+    var connection = void 0;
+    // Set up logger
+    _this.logger = function () {
+      if (opts.debug) {
+        return opts.logger || console;
+      }
+      return opts.logger || _mockconsole2.default;
+    }();
+
+    // Set our config from options
+    Object.keys(options).forEach(function (o) {
+      _this.config[o] = options[o];
+    });
+
+    if (options.dataOnly) {
+      _this.config.media.video = false;
+      _this.config.media.audio = false;
+      _this.config.receiveMedia.offerToReceiveAudio = false;
+      _this.config.receiveMedia.offerToReceiveVideo = false;
+    }
+
+    if (!_this.config.media.video && _this.config.media.audio) {
+      _this.config.localVideo.audio = true;
+    }
+    _this.capabilities = _webrtcsupport2.default;
+    if (_this.config.connection === null) {
+      connection = _this.connection = new _socketioconnection2.default(_this.config);
+    } else {
+      connection = _this.connection = _this.config.connection;
+    }
+
+    connection.on('connect', function () {
+      self.emit('connectionReady', connection.getSessionid());
+      self.sessionReady = true;
+      self.testReadiness();
+    });
+
+    connection.on('message', function (message) {
+      var peers = self.webrtc.getPeers(message.from, message.roomType);
+      var totalPeers = self.webrtc.getPeers().length;
+      var peer = void 0;
+
+      if (message.type === 'offer') {
+        if (peers.length) {
+          peers.forEach(function (p) {
+            if (p.sid === message.sid) peer = p;
+          });
+          // if (!peer) peer = peers[0]; // fallback for old protocol versions
+        }
+        if (_this.config.dataOnly && _this.config.constraints.maxPeers > 0 && totalPeers >= _this.config.constraints.maxPeers) {
+          return;
+        }
+        if (!peer) {
+          peer = self.webrtc.createPeer({
+            id: message.from,
+            sid: message.sid,
+            type: message.roomType,
+            enableDataChannels: self.config.enableDataChannels,
+            sharemyscreen: message.roomType === 'screen' && !message.broadcaster,
+            broadcaster: message.roomType === 'screen' && !message.broadcaster ? self.connection.getSessionid() : null
+          });
+          if (_this.config.dataOnly && _this.config.constraints.maxPeers > 0) {
+            _this.sendPing(peer, peer.id, true);
+          } else {
+            peer.start();
+            _this.emit('createdPeer', peer);
+          }
+        } else {
+          return;
+        }
+        peer.handleMessage(message);
+      } else if (peers.length) {
+        peers.forEach(function (p) {
+          p.handleMessage(message);
+        });
+      }
+    });
+
+    connection.on('remove', function (room) {
+      if (room.id !== self.connection.getSessionid()) {
+        self.webrtc.removePeers(room.id, room.type);
+      }
+    });
+
+    opts.logger = _this.logger;
+    opts.debug = false;
+    _this.webrtc = new _webrtc2.default(opts);
+    _constants.inheritedMethods.forEach(function (method) {
+      self[method] = self.webrtc[method].bind(self.webrtc);
+    });
+
+    // proxy events from WebRTC
+    _this.webrtc.on('*', function () {
+      // eslint-disable-line
+      self.emit.apply(self, arguments); // eslint-disable-line
+    });
+
+    // log all events in debug mode
+    if (config.debug) {
+      _this.on('*', _this.logger.log.bind(_this.logger, 'LioWebRTC event:'));
+    }
+
+    // check for readiness
+    _this.webrtc.on('localStream', function () {
+      self.testReadiness();
+    });
+
+    _this.webrtc.on('message', function (payload) {
+      self.connection.emit('message', payload);
+    });
+
+    _this.webrtc.on('peerStreamAdded', _this.handlePeerStreamAdded.bind(_this));
+    _this.webrtc.on('peerStreamRemoved', _this.handlePeerStreamRemoved.bind(_this));
+
+    // echo cancellation attempts
+    if (_this.config.adjustPeerVolume) {
+      _this.webrtc.on('speaking', _this.setVolumeForAll.bind(_this, _this.config.peerVolumeWhenSpeaking));
+      _this.webrtc.on('stoppedSpeaking', _this.setVolumeForAll.bind(_this, 1));
+    }
+
+    connection.on('stunservers', function (args) {
+      // resets/overrides the config
+      self.webrtc.config.peerConnectionConfig.iceServers = args;
+      self.emit('stunservers', args);
+    });
+    connection.on('turnservers', function (args) {
+      // appends to the config
+      self.webrtc.config.peerConnectionConfig.iceServers = self.webrtc.config.peerConnectionConfig.iceServers.concat(args);
+      self.emit('turnservers', args);
+    });
+    /*
+    this.webrtc.on('iceFailed', (peer) => {
+      // local ice failure
+    });
+    this.webrtc.on('connectivityError', (peer) => {
+      // remote ice failure
+    });
+    */
+
+    // sending mute/unmute to all peers
+    _this.webrtc.on('audioOn', function () {
+      self.webrtc.sendToAll('unmute', { name: 'audio' });
+    });
+    _this.webrtc.on('audioOff', function () {
+      self.webrtc.sendToAll('mute', { name: 'audio' });
+    });
+    _this.webrtc.on('videoOn', function () {
+      self.webrtc.sendToAll('unmute', { name: 'video' });
+    });
+    _this.webrtc.on('videoOff', function () {
+      self.webrtc.sendToAll('mute', { name: 'video' });
+    });
+
+    self.on('removedPeer', function (peer) {
+      if (peer.id) {
+        (0, _PeerOptimizer.removeConnection)(_this.id, peer.id);
+      }
+    });
+
+    self.on('channelClose', function (channel) {
+      if (channel.label === 'liowebrtc' && _this.config.dataOnly && _this.config.constraints.maxPeers > 0 && (0, _PeerOptimizer.getNeighbors)(_this.id).length < _this.config.constraints.minPeers) {
+        _this.connectToRandomPeer();
+      }
+    });
+
+    _this.webrtc.on('channelMessage', function (peer, label, data) {
+      if (data.payload._id && _this.peerDataCache[data.payload._id]) {
+        return;
+      }
+      switch (data.type) {
+        case '_volume':
+          self.emit('remoteVolumeChange', data.payload, peer);
+          break;
+        case '_propagate':
+          if (_this.seenPeerEvent(data.payload._id)) {
+            return;
+          }
+          // Re-propagate message
+          _this.propagateMessage(data.payload);
+          _this.cachePeerEvent(data.payload._id, data.payload.senderId);
+          // Emit the propagated data as if it were received directly
+          self.emit('receivedPeerData', data.payload.type, data.payload.payload, {
+            id: data.payload.senderId,
+            nick: data.payload.senderNick,
+            isForwarded: true
+          });
+          break;
+        case '_ping':
+          _this.sendPong(peer, data.payload);
+          break;
+        case '_pong':
+          (0, _PeerOptimizer.addConnection)(_this.id, peer.id, Date.now() - data.payload[0] + data.payload[1]);
+          break;
+        case '_connections':
+          data.payload.forEach(function (connection) {
+            return (0, _PeerOptimizer.addConnection)(peer.id, connection.id, connection.weight);
+          });
+          break;
+        default:
+          if (_this.seenPeerEvent(data._id)) {
+            return;
+          }
+          _this.cachePeerEvent(data._id, peer.id);
+          self.emit('receivedPeerData', data.type, data.payload, peer);
+          if (_this.config.constraints.maxPeers > 0 && data.shout) {
+            data.senderId = peer.id;
+            var fwdData = Object.assign({}, { senderId: peer.id, senderNick: peer.nick }, data);
+            _this.propagateMessage(fwdData);
+          }
+          break;
+      }
+    });
+
+    if (_this.config.autoRequestMedia) _this.startLocalVideo();
+    return _this;
+  }
+
+  _createClass(LioWebRTC, [{
+    key: 'cachePeerEvent',
+    value: function cachePeerEvent(eventId, peerId) {
+      if (!this.peerDataCache[eventId]) {
+        this.peerDataCache[eventId] = {
+          recipients: _defineProperty({}, peerId, true),
+          timestamp: Date.now()
+        };
+        return;
+      }
+      if (!this.peerDataCache[eventId].recipients[peerId]) {
+        this.peerDataCache[eventId].recipients[peerId] = true;
+      }
+      if (Object.keys(this.peerDataCache).length > 1024) {
+        // Sort by timestamp
+        var sortedCache = Object.entries(this.peerDataCache).sort(function (a, b) {
+          return a[1].timestamp - b[1].timestamp;
+        });
+        // Delete oldest item
+        delete this.peerDataCache[sortedCache[0][0]];
+      }
+    }
+  }, {
+    key: 'seenPeerEvent',
+    value: function seenPeerEvent(eventId) {
+      if (this.peerDataCache[eventId]) {
+        return true;
+      }
+      return false;
+    }
+  }, {
+    key: 'sendPong',
+    value: function sendPong(peer, start) {
+      var channel = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : _constants.defaultChannel;
+
+      var now = Date.now();
+      peer.sendDirectly('_pong', [now, now - start], channel);
+    }
+  }, {
+    key: 'sendPing',
+    value: function sendPing(peer, peerId) {
+      var firstPing = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
+      var channel = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : _constants.defaultChannel;
+
+      if (firstPing) peer.start();
+      setTimeout(this.ping.bind(this, peer, peerId, firstPing, channel), 1000);
+    }
+  }, {
+    key: 'ping',
+    value: function ping(peer, peerId, firstPing, channel) {
+      var tries = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 0;
+
+      if (peer.sendDirectly('_ping', Date.now(), channel)) {
+        // this.logger.log('sent ping to', peer.id);
+        if (firstPing) this.emit('createdPeer', peer);
+      } else {
+        // The channel is closed
+        if (tries === 2) {
+          this.unconnectivePeers[peerId] = true;
+          peer.end(false);
+          return;
+        }
+        setTimeout(this.ping.bind(this, peer, peerId, firstPing, channel, tries + 1), 1000);
+      }
+    }
+  }, {
+    key: 'connectToRandomPeer',
+    value: function connectToRandomPeer() {
+      var _this2 = this;
+
+      this.getClients(function (err, clients) {
+        var ids = Object.keys(clients).filter(function (c) {
+          return !(_this2.unconnectivePeers[c] === true || c === _this2.id || (0, _PeerOptimizer.isNeighbor)(_this2.id, c));
+        });
+        if (ids.length) {
+          var randId = ids[Math.floor(Math.random() * ids.length)];
+          _this2.connectToPeer(randId, clients[randId]);
+        }
+      });
+    }
+  }, {
+    key: 'sendConnections',
+    value: function sendConnections(peer) {
+      var _this3 = this;
+
+      var channel = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : _constants.defaultChannel;
+
+      if (peer.sendDirectly('_connections', this.getPeers().map(function (p) {
+        var edge = _PeerOptimizer.Graph.findEdge(_this3.id, p.id);
+        return { id: p.id, weight: edge.getWeight() };
+      }), channel)) {
+        // connections sent
+      } else {
+        peer.end();
+      }
+    }
+  }, {
+    key: 'propagateMessage',
+    value: function propagateMessage(data) {
+      var _this4 = this;
+
+      var channel = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : _constants.defaultChannel;
+
+      this.getPeers().forEach(function (peer) {
+        if (!_this4.peerDataCache[data._id]) {
+          _this4.cachePeerEvent(data._id, data.senderId);
+        }
+        if (!_this4.peerDataCache[data._id].recipients[peer.id]) {
+          peer.sendDirectly('_propagate', data, channel, true);
+        }
+      });
+    }
+  }, {
+    key: 'trimPeers',
+    value: function trimPeers() {
+      var pid = (0, _PeerOptimizer.getDroppablePeer)();
+      var peer = this.webrtc.getPeerById(pid);
+    }
+  }, {
+    key: 'leaveRoom',
+    value: function leaveRoom() {
+      if (this.roomName) {
+        this.connection.emit('leave');
+        while (this.webrtc.peers.length) {
+          this.webrtc.peers[0].end();
+        }
+        this.emit('leftRoom', this.roomName);
+        this.roomName = undefined;
+      }
+    }
+  }, {
+    key: 'disconnect',
+    value: function disconnect() {
+      this.connection.disconnect();
+      delete this.connection;
+    }
+  }, {
+    key: 'handlePeerStreamAdded',
+    value: function handlePeerStreamAdded(stream, peer) {
+      var self = this;
+      //this.emit('peerStreamAdded', stream, peer);
+
+      // send our mute status to new peer if we're muted
+      // currently called with a small delay because it arrives before
+      // the video element is created otherwise (which happens after
+      // the async setRemoteDescription-createAnswer)
+      setTimeout(function () {
+        if (!self.webrtc.isAudioEnabled()) {
+          peer.send('mute', { name: 'audio' });
+        }
+        if (!self.webrtc.isVideoEnabled()) {
+          peer.send('mute', { name: 'video' });
+        }
+      }, 250);
+    }
+  }, {
+    key: 'handlePeerStreamRemoved',
+    value: function handlePeerStreamRemoved(peer) {
+      // (this.config.media.video) this.emit('peerStreamRemoved', peer);
+    }
+  }, {
+    key: 'getDomId',
+    value: function getDomId(peer) {
+      // eslint-disable-line
+      return [peer.id, peer.type, peer.broadcaster ? 'broadcasting' : 'incoming'].join('_');
+    }
+  }, {
+    key: 'getMyId',
+    value: function getMyId() {
+      return this.id;
+    }
+  }, {
+    key: 'getContainerId',
+    value: function getContainerId(peer) {
+      return 'container_' + this.getDomId(peer);
+    }
+
+    // set volume on video tag for all peers takse a value between 0 and 1
+
+  }, {
+    key: 'setVolumeForAll',
+    value: function setVolumeForAll(volume) {
+      this.webrtc.peers.forEach(function (peer) {
+        if (peer.videoEl) peer.videoEl.volume = volume;
+      });
+    }
+  }, {
+    key: 'getClients',
+    value: function getClients(callback) {
+      this.connection.emit('getClients', this.roomName, function (err, clients) {
+        if (callback) callback(err, clients.clients);
+      });
+    }
+  }, {
+    key: 'joinRoom',
+    value: function joinRoom(name, cb) {
+      var _this5 = this;
+
+      var self = this;
+      this.roomName = name;
+      this.connection.emit('join', name, function (err, roomDescription) {
+        if (err) {
+          self.emit('error', err);
+        } else {
+          var id = void 0;
+          var client = void 0;
+          var type = void 0;
+          var peer = void 0;
+
+          _this5.roomCount = Object.keys(roomDescription.clients).length;
+          // console.log(roomDescription);
+          _this5.id = roomDescription.you;
+          (0, _PeerOptimizer.addNode)(_this5.id);
+          _this5.unconnectivePeers[_this5.id] = true;
+          var _iteratorNormalCompletion = true;
+          var _didIteratorError = false;
+          var _iteratorError = undefined;
+
+          try {
+            for (var _iterator = Object.keys(roomDescription.clients).reverse().filter(function (item) {
+              return item !== _this5.id;
+            })[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
+              id = _step.value;
+
+              client = roomDescription.clients[id];
+              for (type in client) {
+                if (client[type]) {
+                  var peerCount = _this5.webrtc.getPeers().length;
+                  if (_this5.config.dataOnly && _this5.config.constraints.maxPeers > 0 && (peerCount >= _this5.config.constraints.minPeers || peerCount >= _this5.config.constraints.maxPeers)) {
+                    break;
+                  }
+                  peer = self.webrtc.createPeer({
+                    id: id,
+                    type: type,
+                    enableDataChannels: self.config.enableDataChannels && type !== 'screen',
+                    receiveMedia: {
+                      offerToReceiveAudio: type !== 'screen' && !_this5.config.dataOnly && _this5.config.receiveMedia.offerToReceiveAudio ? 1 : 0,
+                      offerToReceiveVideo: !_this5.config.dataOnly && self.config.receiveMedia.offerToReceiveVideo ? 1 : 0
+                    }
+                  });
+                  if (_this5.config.dataOnly && _this5.config.constraints.maxPeers > 0) {
+                    _this5.sendPing(peer, peer.id, true);
+                  } else {
+                    peer.start();
+                    _this5.emit('createdPeer', peer);
+                  }
+                }
+              }
+            }
+          } catch (err) {
+            _didIteratorError = true;
+            _iteratorError = err;
+          } finally {
+            try {
+              if (!_iteratorNormalCompletion && _iterator.return) {
+                _iterator.return();
+              }
+            } finally {
+              if (_didIteratorError) {
+                throw _iteratorError;
+              }
+            }
+          }
+        }
+
+        if (cb) cb(err, roomDescription);
+        self.emit('joinedRoom', name);
+      });
+    }
+  }, {
+    key: 'startLocalVideo',
+    value: function startLocalVideo() {
+      var _this6 = this;
+
+      var self = this;
+      this.webrtc.start(this.config.media, function (err, stream) {
+        if (err) {
+          self.emit('localMediaError', err);
+        } else {
+          (0, _attachmediastream2.default)(stream, _this6.config.localVideoEl, _this6.config.localVideo);
+        }
+      });
+    }
+  }, {
+    key: 'attachStream',
+    value: function attachStream(stream, el, opts) {
+      // eslint-disable-line
+      var options = {
+        autoplay: true,
+        muted: false,
+        mirror: true,
+        audio: false
+      };
+      (0, _attachmediastream2.default)(stream, el, opts || options);
+    }
+  }, {
+    key: 'setLocalVideo',
+    value: function setLocalVideo(element) {
+      this.config.localVideoEl = element;
+    }
+  }, {
+    key: 'stopLocalVideo',
+    value: function stopLocalVideo() {
+      this.webrtc.stop();
+    }
+  }, {
+    key: 'quit',
+    value: function quit() {
+      this.stopLocalVideo();
+      this.leaveRoom();
+      this.disconnect();
+    }
+  }, {
+    key: 'testReadiness',
+    value: function testReadiness() {
+      var self = this;
+      if (this.sessionReady) {
+        if (this.config.dataOnly || !this.config.media.video && !this.config.media.audio || this.webrtc.localStreams.length > 0) {
+          self.emit('ready', self.connection.getSessionid());
+        }
+      }
+    }
+  }, {
+    key: 'connectToPeer',
+    value: function connectToPeer(peerId, client) {
+      var type = void 0;
+      var peer = void 0;
+      for (type in client) {
+        if (client[type]) {
+          var peerCount = this.webrtc.getPeers().length;
+          if (this.config.constraints.maxPeers > 0 && peerCount >= this.config.constraints.maxPeers) {
+            break;
+          }
+          peer = this.webrtc.createPeer({
+            id: peerId,
+            type: type,
+            enableDataChannels: this.config.enableDataChannels && type !== 'screen',
+            receiveMedia: {
+              offerToReceiveAudio: type !== 'screen' && !this.config.dataOnly && this.config.receiveMedia.offerToReceiveAudio ? 1 : 0,
+              offerToReceiveVideo: !this.config.dataOnly && this.config.receiveMedia.offerToReceiveVideo ? 1 : 0
+            }
+          });
+          if (this.config.dataOnly && this.config.constraints.maxPeers > 0) {
+            this.sendPing(peer, peerId, true);
+          } else {
+            peer.start();
+            this.emit('createdPeer', peer);
+          }
+        }
+      }
+    }
+  }, {
+    key: 'createRoom',
+    value: function createRoom(name, cb) {
+      this.roomName = name;
+      if (arguments.length === 2) {
+        this.connection.emit('create', name, cb);
+      } else {
+        this.connection.emit('create', name);
+      }
+    }
+  }, {
+    key: 'sendFile',
+    value: function sendFile() {
+      if (!_webrtcsupport2.default.dataChannel) {
+        return this.emit('error', new Error('DataChannelNotSupported'));
+      }
+    }
+  }]);
+
+  return LioWebRTC;
+}(_wildemitter2.default);
+
+exports.default = LioWebRTC;
\ No newline at end of file
diff --git a/dist/localmedia.js b/dist/localmedia.js
new file mode 100644
index 0000000..140fc6a
--- /dev/null
+++ b/dist/localmedia.js
@@ -0,0 +1,324 @@
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
+
+var _hark = require('hark');
+
+var _hark2 = _interopRequireDefault(_hark);
+
+var _wildemitter = require('wildemitter');
+
+var _wildemitter2 = _interopRequireDefault(_wildemitter);
+
+var _mockconsole = require('mockconsole');
+
+var _mockconsole2 = _interopRequireDefault(_mockconsole);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+function isAllTracksEnded(stream) {
+  var isAllTracksEnded = true;
+  stream.getTracks().forEach(function (t) {
+    isAllTracksEnded = t.readyState === 'ended' && isAllTracksEnded;
+  });
+  return isAllTracksEnded;
+}
+
+function shouldWorkAroundFirefoxStopStream() {
+  if (typeof window === 'undefined') {
+    return false;
+  }
+  if (!window.navigator.mozGetUserMedia) {
+    return false;
+  }
+  var match = window.navigator.userAgent.match(/Firefox\/(\d+)\./);
+  var version = match && match.length >= 1 && parseInt(match[1], 10);
+  return version < 50;
+}
+
+var LocalMedia = function (_WildEmitter) {
+  _inherits(LocalMedia, _WildEmitter);
+
+  function LocalMedia(opts) {
+    _classCallCheck(this, LocalMedia);
+
+    var _this = _possibleConstructorReturn(this, (LocalMedia.__proto__ || Object.getPrototypeOf(LocalMedia)).call(this));
+
+    var config = _this.config = {
+      detectSpeakingEvents: false,
+      audioFallback: false,
+      media: {
+        audio: true,
+        video: true
+      },
+      harkOptions: null,
+      logger: _mockconsole2.default
+    };
+
+    var item = void 0;
+    for (item in opts) {
+      if (opts.hasOwnProperty(item)) {
+        _this.config[item] = opts[item];
+      }
+    }
+
+    _this.logger = config.logger;
+    _this._log = _this.logger.log.bind(_this.logger, 'LocalMedia:');
+    _this._logerror = _this.logger.error.bind(_this.logger, 'LocalMedia:');
+
+    _this.localStreams = [];
+    _this.localScreens = [];
+
+    if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
+      _this._logerror('Your browser does not support local media capture.');
+    }
+
+    _this._audioMonitors = [];
+    _this.on('localStreamStopped', _this._stopAudioMonitor.bind(_this));
+    _this.on('localScreenStopped', _this._stopAudioMonitor.bind(_this));
+    return _this;
+  }
+
+  _createClass(LocalMedia, [{
+    key: 'start',
+    value: function start(mediaConstraints, cb) {
+      var self = this;
+      var constraints = mediaConstraints || this.config.media;
+
+      this.emit('localStreamRequested', constraints);
+
+      navigator.mediaDevices.getUserMedia(constraints).then(function (stream) {
+        if (constraints.audio && self.config.detectSpeakingEvents) {
+          self._setupAudioMonitor(stream, self.config.harkOptions);
+        }
+        self.localStreams.push(stream);
+
+        stream.getTracks().forEach(function (track) {
+          track.addEventListener('ended', function () {
+            if (isAllTracksEnded(stream)) {
+              self._removeStream(stream);
+            }
+          });
+        });
+
+        self.emit('localStream', stream);
+
+        if (cb) {
+          return cb(null, stream);
+        }
+      }).catch(function (err) {
+        // Fallback for users without a camera
+        if (self.config.audioFallback && err.name === 'NotFoundError' && constraints.video !== false) {
+          constraints.video = false;
+          self.start(constraints, cb);
+          return;
+        }
+
+        self.emit('localStreamRequestFailed', constraints);
+
+        if (cb) {
+          return cb(err, null);
+        }
+      });
+    }
+  }, {
+    key: 'stop',
+    value: function stop(stream) {
+      this.stopStream(stream);
+    }
+  }, {
+    key: 'stopStream',
+    value: function stopStream(stream) {
+      var self = this;
+
+      if (stream) {
+        var idx = this.localStreams.indexOf(stream);
+        if (idx > -1) {
+          stream.getTracks().forEach(function (track) {
+            track.stop();
+          });
+
+          // Half-working fix for Firefox, see: https://bugzilla.mozilla.org/show_bug.cgi?id=1208373
+          if (shouldWorkAroundFirefoxStopStream()) {
+            this._removeStream(stream);
+          }
+        }
+      } else {
+        this.localStreams.forEach(function (stream) {
+          stream.getTracks().forEach(function (track) {
+            track.stop();
+          });
+
+          // Half-working fix for Firefox, see: https://bugzilla.mozilla.org/show_bug.cgi?id=1208373
+          if (shouldWorkAroundFirefoxStopStream()) {
+            self._removeStream(stream);
+          }
+        });
+      }
+    }
+    // Audio controls
+
+  }, {
+    key: 'mute',
+    value: function mute() {
+      this._audioEnabled(false);
+      this.emit('audioOff');
+    }
+  }, {
+    key: 'unmute',
+    value: function unmute() {
+      this._audioEnabled(true);
+      this.emit('audioOn');
+    }
+
+    // Video controls
+
+  }, {
+    key: 'pauseVideo',
+    value: function pauseVideo() {
+      this._videoEnabled(false);
+      this.emit('videoOff');
+    }
+  }, {
+    key: 'resumeVideo',
+    value: function resumeVideo() {
+      this._videoEnabled(true);
+      this.emit('videoOn');
+    }
+
+    // Combined controls
+
+  }, {
+    key: 'pause',
+    value: function pause() {
+      this.mute();
+      this.pauseVideo();
+    }
+  }, {
+    key: 'resume',
+    value: function resume() {
+      this.unmute();
+      this.resumeVideo();
+    }
+
+    // Internal methods for enabling/disabling audio/video
+
+  }, {
+    key: '_audioEnabled',
+    value: function _audioEnabled(bool) {
+      this.localStreams.forEach(function (stream) {
+        stream.getAudioTracks().forEach(function (track) {
+          track.enabled = !!bool;
+        });
+      });
+    }
+  }, {
+    key: '_videoEnabled',
+    value: function _videoEnabled(bool) {
+      this.localStreams.forEach(function (stream) {
+        stream.getVideoTracks().forEach(function (track) {
+          track.enabled = !!bool;
+        });
+      });
+    }
+
+    // check if all audio streams are enabled
+
+  }, {
+    key: 'isAudioEnabled',
+    value: function isAudioEnabled() {
+      var enabled = true;
+      this.localStreams.forEach(function (stream) {
+        stream.getAudioTracks().forEach(function (track) {
+          enabled = enabled && track.enabled;
+        });
+      });
+      return enabled;
+    }
+
+    // check if all video streams are enabled
+
+  }, {
+    key: 'isVideoEnabled',
+    value: function isVideoEnabled() {
+      var enabled = true;
+      this.localStreams.forEach(function (stream) {
+        stream.getVideoTracks().forEach(function (track) {
+          enabled = enabled && track.enabled;
+        });
+      });
+      return enabled;
+    }
+  }, {
+    key: '_removeStream',
+    value: function _removeStream(stream) {
+      var idx = this.localStreams.indexOf(stream);
+      if (idx > -1) {
+        this.localStreams.splice(idx, 1);
+        this.emit('localStreamStopped', stream);
+      } else {
+        idx = this.localScreens.indexOf(stream);
+        if (idx > -1) {
+          this.localScreens.splice(idx, 1);
+          this.emit('localScreenStopped', stream);
+        }
+      }
+    }
+  }, {
+    key: '_setupAudioMonitor',
+    value: function _setupAudioMonitor(stream, harkOptions) {
+      this._log('Setup audio');
+      var audio = (0, _hark2.default)(stream, harkOptions);
+      var self = this;
+      var timeout = void 0;
+
+      audio.on('speaking', function () {
+        self.emit('speaking');
+      });
+
+      audio.on('stopped_speaking', function () {
+        if (timeout) {
+          clearTimeout(timeout);
+        }
+
+        timeout = setTimeout(function () {
+          self.emit('stoppedSpeaking');
+        }, 1000);
+      });
+      audio.on('volume_change', function (volume, threshold) {
+        self.emit('volumeChange', volume, threshold);
+      });
+
+      this._audioMonitors.push({ audio: audio, stream: stream });
+    }
+  }, {
+    key: '_stopAudioMonitor',
+    value: function _stopAudioMonitor(stream) {
+      var idx = -1;
+      this._audioMonitors.forEach(function (monitors, i) {
+        if (monitors.stream === stream) {
+          idx = i;
+        }
+      });
+
+      if (idx > -1) {
+        this._audioMonitors[idx].audio.stop();
+        this._audioMonitors.splice(idx, 1);
+      }
+    }
+  }]);
+
+  return LocalMedia;
+}(_wildemitter2.default);
+
+exports.default = LocalMedia;
\ No newline at end of file
diff --git a/dist/peer.js b/dist/peer.js
new file mode 100644
index 0000000..e044006
--- /dev/null
+++ b/dist/peer.js
@@ -0,0 +1,356 @@
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
+
+var _rtcpeerconnection = require('rtcpeerconnection');
+
+var _rtcpeerconnection2 = _interopRequireDefault(_rtcpeerconnection);
+
+var _wildemitter = require('wildemitter');
+
+var _wildemitter2 = _interopRequireDefault(_wildemitter);
+
+var _filetransfer = require('filetransfer');
+
+var _filetransfer2 = _interopRequireDefault(_filetransfer);
+
+var _webrtcsupport = require('./webrtcsupport');
+
+var _webrtcsupport2 = _interopRequireDefault(_webrtcsupport);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+function isAllTracksEnded(stream) {
+  var isAllTracksEnded = true;
+  stream.getTracks().forEach(function (t) {
+    isAllTracksEnded = t.readyState === 'ended' && isAllTracksEnded;
+  });
+  return isAllTracksEnded;
+}
+
+var Peer = function (_WildEmitter) {
+  _inherits(Peer, _WildEmitter);
+
+  function Peer(options) {
+    _classCallCheck(this, Peer);
+
+    var _this = _possibleConstructorReturn(this, (Peer.__proto__ || Object.getPrototypeOf(Peer)).call(this));
+
+    var self = _this;
+    _this.id = options.id;
+    _this.parent = options.parent;
+    _this.type = options.type || 'video';
+    _this.oneway = options.oneway || false;
+    _this.sharemyscreen = options.sharemyscreen || false;
+    _this.browserPrefix = options.prefix;
+    _this.stream = options.stream;
+    _this.enableDataChannels = options.enableDataChannels === undefined ? _this.parent.config.enableDataChannels : options.enableDataChannels;
+    _this.receiveMedia = options.receiveMedia || _this.parent.config.receiveMedia;
+    _this.channels = {};
+    _this.sid = options.sid || Date.now().toString();
+    // Create an RTCPeerConnection via the polyfill
+    _this.pc = new _rtcpeerconnection2.default(_this.parent.config.peerConnectionConfig, _this.parent.config.peerConnectionConstraints);
+    _this.pc.on('ice', _this.onIceCandidate.bind(_this));
+    _this.pc.on('endOfCandidates', function (event) {
+      self.send('endOfCandidates', event);
+    });
+    _this.pc.on('offer', function (offer) {
+      if (self.parent.config.nick) offer.nick = self.parent.config.nick;
+      self.send('offer', offer);
+    });
+    _this.pc.on('answer', function (answer) {
+      if (self.parent.config.nick) answer.nick = self.parent.config.nick;
+      self.send('answer', answer);
+    });
+    _this.pc.on('addStream', _this.handleRemoteStreamAdded.bind(_this));
+    _this.pc.on('addChannel', _this.handleDataChannelAdded.bind(_this));
+    _this.pc.on('removeStream', _this.handleStreamRemoved.bind(_this));
+    // Just fire negotiation needed events for now
+    // When browser re-negotiation handling seems to work
+    // we can use this as the trigger for starting the offer/answer process
+    // automatically. We'll just leave it be for now while this stabalizes.
+    _this.pc.on('negotiationNeeded', _this.emit.bind(_this, 'negotiationNeeded'));
+    _this.pc.on('iceConnectionStateChange', _this.emit.bind(_this, 'iceConnectionStateChange'));
+    _this.pc.on('iceConnectionStateChange', function () {
+      switch (self.pc.iceConnectionState) {
+        case 'failed':
+          // currently, in chrome only the initiator goes to failed
+          // so we need to signal this to the peer
+          if (self.pc.pc.localDescription.type === 'offer') {
+            self.parent.emit('iceFailed', self);
+            self.send('connectivityError');
+          }
+          break;
+        case 'closed':
+          _this.handleStreamRemoved(false);
+          break;
+        default:
+          break;
+      }
+    });
+    _this.pc.on('signalingStateChange', _this.emit.bind(_this, 'signalingStateChange'));
+    _this.logger = _this.parent.logger;
+
+    _this.parent.localStreams.forEach(function (stream) {
+      self.pc.addStream(stream);
+    });
+
+    _this.on('channelOpen', function (channel) {});
+
+    // proxy events to parent
+    _this.on('*', function () {
+      var _self$parent;
+
+      (_self$parent = self.parent).emit.apply(_self$parent, arguments);
+    });
+    return _this;
+  }
+
+  _createClass(Peer, [{
+    key: 'handleMessage',
+    value: function handleMessage(message) {
+      var self = this;
+      this.logger.log('getting', message.type, message);
+      if (message.prefix) this.browserPrefix = message.prefix;
+
+      if (message.type === 'offer') {
+        if (!this.nick) {
+          var n = message.payload.nick;
+          this.nick = n;
+        }
+        // delete message.payload.nick;
+        this.pc.handleOffer(message.payload, function (err) {
+          if (err) {
+            return;
+          }
+          // auto-accept
+          self.pc.answer(function (err, sessionDescription) {
+            // self.send('answer', sessionDescription);
+            // console.log('answering', sessionDescription);
+          });
+        });
+      } else if (message.type === 'answer') {
+        if (!this.nick) this.nick = message.payload.nick;
+        delete message.payload.nick;
+        this.pc.handleAnswer(message.payload);
+      } else if (message.type === 'candidate') {
+        this.pc.processIce(message.payload);
+      } else if (message.type === 'connectivityError') {
+        this.parent.emit('connectivityError', self);
+      } else if (message.type === 'mute') {
+        this.parent.emit('mute', { id: message.from, name: message.payload.name });
+      } else if (message.type === 'unmute') {
+        this.parent.emit('unmute', { id: message.from, name: message.payload.name });
+      } else if (message.type === 'endOfCandidates') {
+        // Edge requires an end-of-candidates. Since only Edge will have mLines or tracks on the
+        // shim this will only be called in Edge.
+        var mLines = this.pc.pc.transceivers || [];
+        mLines.forEach(function (mLine) {
+          if (mLine.iceTransport) {
+            mLine.iceTransport.addRemoteCandidate({});
+          }
+        });
+      } else if (message.type === 'signalData') {
+        this.parent.emit('receivedSignalData', message.payload.type, message.payload.payload, self);
+      }
+    }
+
+    // send via signaling channel
+
+  }, {
+    key: 'send',
+    value: function send(messageType, payload) {
+      var message = {
+        to: this.id,
+        sid: this.sid,
+        broadcaster: this.broadcaster,
+        roomType: this.type,
+        type: messageType,
+        payload: payload,
+        prefix: _webrtcsupport2.default.prefix,
+        timestamp: Date.now()
+      };
+      this.logger.log('sending', messageType, message);
+      this.parent.emit('message', message);
+    }
+
+    // send via data channel
+    // returns true when message was sent and false if channel is not open
+
+  }, {
+    key: 'sendDirectly',
+    value: function sendDirectly(messageType, payload) {
+      var channel = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'liowebrtc';
+      var shout = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
+      var messageId = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : Date.now() + '_' + Math.random() * 1000000;
+
+      var message = {
+        type: messageType,
+        payload: payload,
+        _id: messageId,
+        shout: shout
+      };
+      this.logger.log('sending via datachannel', channel, messageType, message);
+      var dc = this.getDataChannel(channel);
+      if (dc.readyState !== 'open') return false;
+      dc.send(JSON.stringify(message));
+      return true;
+    }
+
+    // Internal method registering handlers for a data channel and emitting events on the peer
+
+  }, {
+    key: '_observeDataChannel',
+    value: function _observeDataChannel(channel, peer) {
+      var self = this;
+      channel.onclose = this.emit.bind(this, 'channelClose', channel);
+      channel.onerror = this.emit.bind(this, 'channelError', channel);
+      channel.onmessage = function (event) {
+        self.emit('channelMessage', self, channel.label, JSON.parse(event.data), channel, event);
+      };
+      channel.onopen = this.emit.bind(this, 'channelOpen', channel, peer);
+    }
+
+    // Fetch or create a data channel by the given name
+
+  }, {
+    key: 'getDataChannel',
+    value: function getDataChannel() {
+      var name = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'liowebrtc';
+      var opts = arguments[1];
+
+      var channel = this.channels[name];
+      opts || (opts = {});
+      if (channel) return channel;
+      // if we don't have one by this label, create it
+      channel = this.channels[name] = this.pc.createDataChannel(name, opts);
+      this._observeDataChannel(channel, this);
+      return channel;
+    }
+  }, {
+    key: 'onIceCandidate',
+    value: function onIceCandidate(candidate) {
+      if (this.closed) return;
+      if (candidate) {
+        var pcConfig = this.parent.config.peerConnectionConfig;
+        if (_webrtcsupport2.default.prefix === 'moz' && pcConfig && pcConfig.iceTransports && candidate.candidate && candidate.candidate.candidate && !candidate.candidate.candidate.includes(pcConfig.iceTransports)) {
+          this.logger.log('Ignoring ice candidate not matching pcConfig iceTransports type: ', pcConfig.iceTransports);
+        } else {
+          this.send('candidate', candidate);
+        }
+      } else {
+        this.logger.log('End of candidates.');
+      }
+    }
+  }, {
+    key: 'start',
+    value: function start() {
+      var self = this;
+
+      // well, the webrtc api requires that we either
+      // a) create a datachannel a priori
+      // b) do a renegotiation later to add the SCTP m-line
+      // Let's do (a) first...
+      if (this.enableDataChannels) {
+        this.getDataChannel('liowebrtc');
+      }
+
+      this.pc.offer(this.receiveMedia, function (err, sessionDescription) {
+        // self.send('offer', sessionDescription);
+      });
+    }
+  }, {
+    key: 'icerestart',
+    value: function icerestart() {
+      var constraints = this.receiveMedia;
+      constraints.mandatory.IceRestart = true;
+      this.pc.offer(constraints, function (err, success) {});
+    }
+  }, {
+    key: 'end',
+    value: function end() {
+      var emitRemoval = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
+
+      if (this.closed) return;
+      this.pc.close();
+      this.handleStreamRemoved(emitRemoval);
+      if (emitRemoval) {
+        this.parent.emit('removedPeer', this);
+      }
+    }
+  }, {
+    key: 'handleRemoteStreamAdded',
+    value: function handleRemoteStreamAdded(event) {
+      var self = this;
+      if (this.stream) {
+        this.logger.warn('Already have a remote stream');
+      } else {
+        this.stream = event.stream;
+
+        this.stream.getTracks().forEach(function (track) {
+          track.addEventListener('ended', function () {
+            if (isAllTracksEnded(self.stream)) {
+              self.end();
+            }
+          });
+        });
+
+        this.parent.emit('peerStreamAdded', this.stream, this);
+      }
+    }
+  }, {
+    key: 'handleStreamRemoved',
+    value: function handleStreamRemoved() {
+      var emitRemoval = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
+
+      var peerIndex = this.parent.peers.indexOf(this);
+      if (peerIndex > -1) {
+        this.parent.peers.splice(peerIndex, 1);
+        this.closed = true;
+        if (emitRemoval) this.parent.emit('peerStreamRemoved', this);
+      }
+    }
+  }, {
+    key: 'handleDataChannelAdded',
+    value: function handleDataChannelAdded(channel) {
+      this.channels[channel.label] = channel;
+      //this._observeDataChannel(channel, this);
+    }
+  }, {
+    key: 'sendFile',
+    value: function sendFile(file) {
+      var sender = new _filetransfer2.default.Sender();
+      var dc = this.getDataChannel('filetransfer' + new Date().getTime(), {
+        protocol: INBAND_FILETRANSFER_V1
+      });
+      // override onopen
+      dc.onopen = function () {
+        dc.send(JSON.stringify({
+          size: file.size,
+          name: file.name
+        }));
+        sender.send(file, dc);
+      };
+      // override onclose
+      dc.onclose = function () {
+        // ('sender received transfer');
+        sender.emit('complete');
+      };
+      return sender;
+    }
+  }]);
+
+  return Peer;
+}(_wildemitter2.default);
+
+exports.default = Peer;
\ No newline at end of file
diff --git a/dist/socketioconnection.js b/dist/socketioconnection.js
new file mode 100644
index 0000000..9bd8d89
--- /dev/null
+++ b/dist/socketioconnection.js
@@ -0,0 +1,51 @@
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
+
+var _socket = require('socket.io-client');
+
+var _socket2 = _interopRequireDefault(_socket);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+var SocketIoConnection = function () {
+  function SocketIoConnection(config) {
+    _classCallCheck(this, SocketIoConnection);
+
+    this.connection = (0, _socket2.default)(config.url, config.socketio);
+  }
+
+  _createClass(SocketIoConnection, [{
+    key: 'on',
+    value: function on(ev, fn) {
+      this.connection.on(ev, fn);
+    }
+  }, {
+    key: 'emit',
+    value: function emit() {
+      var _connection;
+
+      (_connection = this.connection).emit.apply(_connection, arguments);
+    }
+  }, {
+    key: 'getSessionid',
+    value: function getSessionid() {
+      return this.connection.id;
+    }
+  }, {
+    key: 'disconnect',
+    value: function disconnect() {
+      return this.connection.disconnect();
+    }
+  }]);
+
+  return SocketIoConnection;
+}();
+
+exports.default = SocketIoConnection;
\ No newline at end of file
diff --git a/dist/webrtc.js b/dist/webrtc.js
new file mode 100644
index 0000000..dbe7b73
--- /dev/null
+++ b/dist/webrtc.js
@@ -0,0 +1,236 @@
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
+
+var _util = require('util');
+
+var _util2 = _interopRequireDefault(_util);
+
+var _mockconsole = require('mockconsole');
+
+var _mockconsole2 = _interopRequireDefault(_mockconsole);
+
+var _localmedia = require('./localmedia');
+
+var _localmedia2 = _interopRequireDefault(_localmedia);
+
+var _peer = require('./peer');
+
+var _peer2 = _interopRequireDefault(_peer);
+
+var _webrtcsupport = require('./webrtcsupport');
+
+var _webrtcsupport2 = _interopRequireDefault(_webrtcsupport);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+var WebRTC = function (_LocalMedia) {
+  _inherits(WebRTC, _LocalMedia);
+
+  function WebRTC(opts) {
+    _classCallCheck(this, WebRTC);
+
+    var _this = _possibleConstructorReturn(this, (WebRTC.__proto__ || Object.getPrototypeOf(WebRTC)).call(this, opts));
+
+    var self = _this;
+    var options = opts || {};
+    var config = _this.config = {
+      debug: false,
+      peerConnectionConfig: {
+        iceServers: [{ urls: 'stun:stun.l.google.com:19302' }]
+      },
+      peerConnectionConstraints: {
+        optional: []
+      },
+      receiveMedia: {
+        offerToReceiveAudio: 1,
+        offerToReceiveVideo: 1
+      },
+      enableDataChannels: true
+    };
+    var item = void 0;
+
+    _this.logger = function () {
+      // we assume that if you're in debug mode and you didn't
+      // pass in a logger, you actually want to log as much as
+      // possible.
+      if (opts.debug) {
+        return opts.logger || console;
+      }
+      // or we'll use your logger which should have its own logic
+      // for output. Or we'll return the no-op.
+      return opts.logger || _mockconsole2.default;
+    }();
+
+    // set options
+    for (item in options) {
+      if (options.hasOwnProperty(item)) {
+        _this.config[item] = options[item];
+      }
+    }
+
+    // check for support
+    if (!_webrtcsupport2.default.support) {
+      _this.logger.error('Your browser doesn\'t seem to support WebRTC');
+    }
+
+    // where we'll store our peer connections
+    _this.peers = [];
+
+    // call localMedia constructor
+    // localMedia.call(this, this.config);
+
+    _this.on('speaking', function () {
+      if (!self.hardMuted) {
+        self.peers.forEach(function (peer) {
+          if (peer.enableDataChannels) {
+            var dc = peer.getDataChannel('liowebrtc');
+            if (dc.readyState !== 'open') return;
+            dc.sendDirectlyToAll(JSON.stringify({ type: 'speaking' }));
+          }
+        });
+      }
+    });
+    _this.on('stoppedSpeaking', function () {
+      if (!self.hardMuted) {
+        self.peers.forEach(function (peer) {
+          if (peer.enableDataChannels) {
+            var dc = peer.getDataChannel('liowebrtc');
+            if (dc.readyState !== 'open') return;
+            dc.sendDirectlyToAll(JSON.stringify({ type: 'stoppedSpeaking' }));
+          }
+        });
+      }
+    });
+    _this.on('volumeChange', function (volume, treshold) {
+      if (!self.hardMuted) {
+        self.peers.forEach(function (peer) {
+          if (peer.enableDataChannels) {
+            var dc = peer.getDataChannel('liowebrtc');
+            if (dc.readyState !== 'open') return;
+            dc.sendDirectlyToAll(JSON.stringify({ type: 'payload', volume: volume }));
+          }
+        });
+      }
+    });
+
+    // log events in debug mode
+    if (_this.config.debug) {
+      _this.on('*', function (event, val1, val2) {
+        var logger = void 0;
+        // if you didn't pass in a logger and you explicitly turning on debug
+        // we're just going to assume you're wanting log output with console
+        if (self.config.logger === _mockconsole2.default) {
+          logger = console;
+        } else {
+          logger = self.logger;
+        }
+        logger.log('event:', event, val1, val2);
+      });
+    }
+    return _this;
+  }
+
+  _createClass(WebRTC, [{
+    key: 'createPeer',
+    value: function createPeer(opts) {
+      var peer = void 0;
+      opts.parent = this;
+      peer = new _peer2.default(opts);
+      this.peers.push(peer);
+      return peer;
+    }
+
+    // removes peers
+
+  }, {
+    key: 'removePeers',
+    value: function removePeers(id, type) {
+      this.getPeers(id, type).forEach(function (peer) {
+        peer.end();
+      });
+    }
+
+    // fetches all Peer objects by session id and/or type
+
+  }, {
+    key: 'getPeers',
+    value: function getPeers(sessionId, type) {
+      return this.peers.filter(function (peer) {
+        return (!sessionId || peer.id === sessionId) && (!type || peer.type === type);
+      });
+    }
+  }, {
+    key: 'getPeerById',
+    value: function getPeerById(id) {
+      return this.peers.filter(function (p) {
+        return p.id === id;
+      })[0];
+    }
+  }, {
+    key: 'getPeerByNick',
+    value: function getPeerByNick(nick) {
+      return this.peers.filter(function (p) {
+        return p.nick === nick;
+      })[0];
+    }
+
+    // sends message to all
+
+  }, {
+    key: 'sendToAll',
+    value: function sendToAll(message, payload) {
+      this.peers.forEach(function (peer) {
+        peer.send(message, payload);
+      });
+    }
+
+    // sends message to all using a datachannel
+    // only sends to anyone who has an open datachannel
+
+  }, {
+    key: 'sendDirectlyToAll',
+    value: function sendDirectlyToAll(message, payload, channel, shout) {
+      var msgId = Date.now() + '_' + Math.random() * 1000000;
+      this.peers.forEach(function (peer) {
+        if (peer.enableDataChannels) {
+          peer.sendDirectly(message, payload, channel, shout, msgId);
+        }
+      });
+    }
+  }, {
+    key: 'shout',
+    value: function shout(messageType, payload) {
+      this.sendDirectlyToAll(messageType, payload, 'liowebrtc', true);
+    }
+  }, {
+    key: 'whisper',
+    value: function whisper(peer, messageType, payload) {
+      peer.sendDirectly(messageType, payload);
+    }
+  }, {
+    key: 'broadcast',
+    value: function broadcast(messageType, payload) {
+      this.sendToAll('signalData', { type: messageType, payload: payload });
+    }
+  }, {
+    key: 'transmit',
+    value: function transmit(peer, messageType, payload) {
+      peer.send('signalData', { type: messageType, payload: payload });
+    }
+  }]);
+
+  return WebRTC;
+}(_localmedia2.default);
+
+exports.default = WebRTC;
\ No newline at end of file
diff --git a/dist/webrtcsupport.js b/dist/webrtcsupport.js
new file mode 100644
index 0000000..b624b30
--- /dev/null
+++ b/dist/webrtcsupport.js
@@ -0,0 +1,45 @@
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+var prefix = void 0;
+var version = void 0;
+
+if (window.mozRTCPeerConnection || navigator.mozGetUserMedia) {
+  prefix = 'moz';
+  version = parseInt(navigator.userAgent.match(/Firefox\/([0-9]+)\./)[1], 10);
+} else if (window.webkitRTCPeerConnection || navigator.webkitGetUserMedia) {
+  prefix = 'webkit';
+  version = navigator.userAgent.match(/Chrom(e|ium)/) && parseInt(navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./)[2], 10);
+}
+
+var PC = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection;
+var IceCandidate = window.mozRTCIceCandidate || window.RTCIceCandidate;
+var SessionDescription = window.mozRTCSessionDescription || window.RTCSessionDescription;
+var MediaStream = window.webkitMediaStream || window.MediaStream;
+var screenSharing = window.location.protocol === 'https:' && (prefix === 'webkit' && version >= 26 || prefix === 'moz' && version >= 33);
+var AudioContext = window.AudioContext || window.webkitAudioContext;
+var videoEl = document.createElement('video');
+var supportVp8 = videoEl && videoEl.canPlayType && videoEl.canPlayType('video/webm; codecs="vp8", vorbis') === 'probably';
+var getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.msGetUserMedia || navigator.mozGetUserMedia;
+
+// export support flags and constructors.prototype && PC
+exports.default = {
+  prefix: prefix,
+  browserVersion: version,
+  support: !!PC && !!getUserMedia,
+  supportRTCPeerConnection: !!PC,
+  supportVp8: supportVp8,
+  supportGetUserMedia: !!getUserMedia,
+  supportDataChannel: !!(PC && PC.prototype && PC.prototype.createDataChannel),
+  supportWebAudio: !!(AudioContext && AudioContext.prototype.createMediaStreamSource),
+  supportMediaStream: !!(MediaStream && MediaStream.prototype.removeTrack),
+  supportScreenSharing: !!screenSharing,
+  AudioContext: AudioContext,
+  PeerConnection: PC,
+  SessionDescription: SessionDescription,
+  IceCandidate: IceCandidate,
+  MediaStream: MediaStream,
+  getUserMedia: getUserMedia
+};
\ No newline at end of file
-- 
GitLab