negotiator.js 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  1. /**
  2. * Manages all negotiations between Peers.
  3. */
  4. // TODO: LOCKS.
  5. // TODO: FIREFOX new PC after offer made for DC.
  6. var Negotiator = {
  7. pcs: {}, // pc id => pc.
  8. providers: {}, // provider's id => providers (there may be multiple providers/client.
  9. options: {},
  10. queue: [] // connections that are delayed due to a PC being in use.
  11. }
  12. Negotiator._idPrefix = 'pc_'
  13. Negotiator.startConnection = function(type, peer, connection, provider, options) {
  14. Negotiator._addProvider(peer, provider);
  15. var pc;
  16. // options.pc is the PC's ID.
  17. pc = Negotiator.pcs[options.pc]
  18. if (!pc || pc.signalingState !== 'stable') {
  19. pc = Negotiator._startPeerConnection(peer, provider);
  20. }
  21. if (type === 'media' && options._stream) {
  22. // Add the stream.
  23. pc.addStream(options._stream);
  24. }
  25. // What do we need to do now?
  26. if (options.originator) {
  27. if (type === 'data') {
  28. // Create the datachannel.
  29. dc = pc.createDataChannel(options.label, {reliable: reliable});
  30. connection = provider.getConnection(peer, connection);
  31. connection.initialize(dc);
  32. }
  33. if (!util.supports.onnegotiationneeded) {
  34. Negotiator._makeOffer(peer, connection, options);
  35. }
  36. } else {
  37. Negotiator._handleSDP(peer, connection, options);
  38. }
  39. return pc;
  40. }
  41. Negotiator._addProvider = function(peer, provider) {
  42. if ((!provider.id && !provider.disconnected) || !provider.socket.open) {
  43. // Wait for provider to obtain an ID.
  44. provider.on('open', function(id) {
  45. Negotiator._addProvider(peer, provider);
  46. });
  47. } else {
  48. Negotiator.providers[provider.id] = provider;
  49. }
  50. }
  51. /** Start a PC. */
  52. Negotiator._startPeerConnection = function(peer, provider) {
  53. util.log('Creating RTCPeerConnection.');
  54. var id = Negotiator._idPrefix + util.randomToken();
  55. pc = new RTCPeerConnection(provider.options.config, {optional: [{RtpDataChannels: true}]});
  56. Negotiator.pcs[id] = pc;
  57. Negotiator._startListeners(peer, provider, pc, id);
  58. return pc;
  59. }
  60. /** Set up various WebRTC listeners. */
  61. Negotiator._setupListeners = function(peer, provider, pc, id) {
  62. // ICE CANDIDATES.
  63. util.log('Listening for ICE candidates.');
  64. pc.onicecandidate = function(evt) {
  65. if (evt.candidate) {
  66. util.log('Received ICE candidates.');
  67. provider.socket.send({
  68. type: 'CANDIDATE',
  69. payload: {
  70. candidate: evt.candidate,
  71. pc: id // Send along this PC's ID.
  72. },
  73. dst: peer,
  74. });
  75. }
  76. };
  77. pc.oniceconnectionstatechange = function() {
  78. switch (pc.iceConnectionState) {
  79. case 'failed':
  80. util.log('iceConnectionState is disconnected, closing connections to ' + self.peer);
  81. Negotiator._cleanup();
  82. break;
  83. case 'completed':
  84. pc.onicecandidate = null;
  85. break;
  86. }
  87. };
  88. // Fallback for older Chrome impls.
  89. pc.onicechange = pc.oniceconnectionstatechange;
  90. // ONNEGOTIATIONNEEDED (Chrome)
  91. util.log('Listening for `negotiationneeded`');
  92. pc.onnegotiationneeded = function() {
  93. util.log('`negotiationneeded` triggered');
  94. Negotiator._makeOffer();
  95. };
  96. // DATACONNECTION.
  97. util.log('Listening for data channel');
  98. // Fired between offer and answer, so options should already be saved
  99. // in the options hash.
  100. pc.ondatachannel = function(evt) {
  101. util.log('Received data channel');
  102. var dc = evt.channel;
  103. connection = provider.getConnection(peer, connection);
  104. connection.initialize(dc);
  105. };
  106. // MEDIACONNECTION.
  107. util.log('Listening for remote stream');
  108. pc.onaddstream = function(evt) {
  109. util.log('Received remote stream');
  110. var stream = evt.stream;
  111. provider.getConnection(peer, id).receiveStream(stream);
  112. };
  113. }
  114. Negotiator._cleanup = function() {
  115. // TODO
  116. }
  117. Negotiator._makeOffer = function() {
  118. // TODO
  119. pc.createOffer(function(offer) {
  120. util.log('Created offer.');
  121. // Firefox currently does not support multiplexing once an offer is made.
  122. self.firefoxSingular = true;
  123. if (util.browserisms === 'Webkit') {
  124. //offer.sdp = Reliable.higherBandwidthSDP(offer.sdp);
  125. }
  126. self.pc.setLocalDescription(offer, function() {
  127. util.log('Set localDescription to offer');
  128. self._socket.send({
  129. type: 'OFFER',
  130. payload: {
  131. sdp: offer,
  132. config: self._options.config,
  133. labels: self.labels,
  134. call: !!self._call
  135. },
  136. dst: self.peer,
  137. manager: self._managerId
  138. });
  139. // We can now reset labels because all info has been communicated.
  140. self.labels = {};
  141. }, function(err) {
  142. self.emit('error', err);
  143. util.log('Failed to setLocalDescription, ', err);
  144. });
  145. }, function(err) {
  146. self.emit('error', err);
  147. util.log('Failed to createOffer, ', err);
  148. });
  149. }
  150. Negotiator._makeAnswer = function() {
  151. // TODO
  152. }
  153. /** Create an answer for PC. */
  154. ConnectionManager.prototype._makeAnswer = function() {
  155. var self = this;
  156. this.pc.createAnswer(function(answer) {
  157. util.log('Created answer.');
  158. if (util.browserisms === 'Webkit') {
  159. //answer.sdp = Reliable.higherBandwidthSDP(answer.sdp);
  160. }
  161. self.pc.setLocalDescription(answer, function() {
  162. util.log('Set localDescription to answer.');
  163. self._socket.send({
  164. type: 'ANSWER',
  165. payload: {
  166. sdp: answer
  167. },
  168. dst: self.peer,
  169. manager: self._managerId
  170. });
  171. }, function(err) {
  172. self.emit('error', err);
  173. util.log('Failed to setLocalDescription, ', err);
  174. });
  175. }, function(err) {
  176. self.emit('error', err);
  177. util.log('Failed to create answer, ', err);
  178. });
  179. }
  180. /** Clean up PC, close related DCs. */
  181. ConnectionManager.prototype._cleanup = function() {
  182. util.log('Cleanup ConnectionManager for ' + this.peer);
  183. if (!!this.pc && (this.pc.readyState !== 'closed' || this.pc.signalingState !== 'closed')) {
  184. this.pc.close();
  185. this.pc = null;
  186. }
  187. var self = this;
  188. this._socket.send({
  189. type: 'LEAVE',
  190. dst: self.peer
  191. });
  192. this.destroyed = true;
  193. this.emit('close');
  194. }
  195. /** Handle an SDP. */
  196. ConnectionManager.prototype.handleSDP = function(sdp, type, call) {
  197. sdp = new RTCSessionDescription(sdp);
  198. var self = this;
  199. this.pc.setRemoteDescription(sdp, function() {
  200. util.log('Set remoteDescription: ' + type);
  201. if (type === 'OFFER') {
  202. if (call && !self._call) {
  203. self._call = new MediaConnection(self.peer);
  204. self._call.on('answer', function(stream){
  205. if (stream) {
  206. self.pc.addStream(stream);
  207. }
  208. self._makeAnswer();
  209. util.setZeroTimeout(function(){
  210. // Add remote streams
  211. self._call.receiveStream(self.pc.getRemoteStreams()[0]);
  212. });
  213. });
  214. self.emit('call', self._call);
  215. } else {
  216. self._makeAnswer();
  217. }
  218. } else {
  219. // Got answer from remote
  220. self._lock = false;
  221. }
  222. }, function(err) {
  223. self.emit('error', err);
  224. util.log('Failed to setRemoteDescription, ', err);
  225. });
  226. }
  227. /** Handle a candidate. */
  228. ConnectionManager.prototype.handleCandidate = function(message) {
  229. var candidate = new RTCIceCandidate(message.candidate);
  230. this.pc.addIceCandidate(candidate);
  231. util.log('Added ICE candidate.');
  232. }
  233. /** Updates label:[serialization, reliable, metadata] pairs from offer. */
  234. ConnectionManager.prototype.handleUpdate = function(updates) {
  235. var labels = Object.keys(updates);
  236. for (var i = 0, ii = labels.length; i < ii; i += 1) {
  237. var label = labels[i];
  238. this.labels[label] = updates[label];
  239. }
  240. }
  241. /** Handle peer leaving. */
  242. ConnectionManager.prototype.handleLeave = function() {
  243. util.log('Peer ' + this.peer + ' disconnected.');
  244. this.close();
  245. }
  246. /** Closes manager and all related connections. */
  247. ConnectionManager.prototype.close = function() {
  248. if (this.destroyed) {
  249. this.emit('error', new Error('Connections to ' + this.peer + 'are already closed.'));
  250. return;
  251. }
  252. var labels = Object.keys(this.connections);
  253. for (var i = 0, ii = labels.length; i < ii; i += 1) {
  254. var label = labels[i];
  255. var connection = this.connections[label];
  256. connection.close();
  257. }
  258. // TODO: close the call.
  259. this.connections = null;
  260. this._cleanup();
  261. }