negotiator.js 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  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: {
  8. data: {},
  9. media: {}
  10. }, // type => {peerId: {pc_id: pc}}.
  11. //providers: {}, // provider's id => providers (there may be multiple providers/client.
  12. queue: [] // connections that are delayed due to a PC being in use.
  13. }
  14. Negotiator._idPrefix = 'pc_';
  15. /** Returns a PeerConnection object set up correctly (for data, media). */
  16. // Options preceeded with _ are ones we add artificially.
  17. Negotiator.startConnection = function(connection, options) {
  18. //Negotiator._addProvider(provider);
  19. var pc = Negotiator._getPeerConnection(connection, options);
  20. if (connection.type === 'media' && options._stream) {
  21. // Add the stream.
  22. pc.addStream(options._stream);
  23. }
  24. // Set the connection's PC.
  25. connection.pc = pc;
  26. // What do we need to do now?
  27. if (options.originator) {
  28. if (connection.type === 'data') {
  29. // Create the datachannel.
  30. var dc = pc.createDataChannel(connection.label, {reliable: options.reliable});
  31. connection.initialize(dc);
  32. }
  33. if (!util.supports.onnegotiationneeded) {
  34. Negotiator._makeOffer(connection);
  35. }
  36. } else {
  37. Negotiator.handleSDP('OFFER', connection, options.sdp);
  38. }
  39. }
  40. Negotiator._getPeerConnection = function(connection, options) {
  41. if (!Negotiator.pcs[connection.type]) {
  42. util.error(connection.type + ' is not a valid connection type. Maybe you overrode the `type` property somewhere.');
  43. }
  44. if (!Negotiator.pcs[connection.type][connection.peer]) {
  45. Negotiator.pcs[connection.type][connection.peer] = {};
  46. }
  47. var peerConnections = Negotiator.pcs[connection.type][connection.peer];
  48. var pc;
  49. if (options.multiplex) {
  50. // Find an existing PC to use.
  51. ids = Object.keys(peerConnections);
  52. for (var i = 0, ii = ids.length; i < ii; i += 1) {
  53. pc = peerConnections[ids[i]];
  54. if (pc.signalingState === 'stable') {
  55. break; // We can go ahead and use this PC.
  56. }
  57. }
  58. } else if (options.pc) { // Simplest case: PC id already provided for us.
  59. pc = Negotiator.pcs[connection.type][connection.peer][options.pc];
  60. }
  61. if (!pc || pc.signalingState !== 'stable') {
  62. pc = Negotiator._startPeerConnection(connection);
  63. }
  64. return pc;
  65. }
  66. /*
  67. Negotiator._addProvider = function(provider) {
  68. if ((!provider.id && !provider.disconnected) || !provider.socket.open) {
  69. // Wait for provider to obtain an ID.
  70. provider.on('open', function(id) {
  71. Negotiator._addProvider(provider);
  72. });
  73. } else {
  74. Negotiator.providers[provider.id] = provider;
  75. }
  76. }*/
  77. /** Start a PC. */
  78. Negotiator._startPeerConnection = function(connection) {
  79. util.log('Creating RTCPeerConnection.');
  80. var id = Negotiator._idPrefix + util.randomToken();
  81. pc = new RTCPeerConnection(connection.provider.options.config, {optional: [{RtpDataChannels: true}]});
  82. Negotiator.pcs[connection.type][connection.peer][id] = pc;
  83. Negotiator._setupListeners(connection, pc, id);
  84. return pc;
  85. }
  86. /** Set up various WebRTC listeners. */
  87. Negotiator._setupListeners = function(connection, pc, pc_id) {
  88. var peerId = connection.peer;
  89. var connectionId = connection.id;
  90. var provider = connection.provider;
  91. // ICE CANDIDATES.
  92. util.log('Listening for ICE candidates.');
  93. pc.onicecandidate = function(evt) {
  94. if (evt.candidate) {
  95. util.log('Received ICE candidates for:', connection.peer);
  96. provider.socket.send({
  97. type: 'CANDIDATE',
  98. payload: {
  99. candidate: evt.candidate,
  100. type: connection.type,
  101. connectionId: connection.id
  102. },
  103. dst: peerId,
  104. });
  105. }
  106. };
  107. pc.oniceconnectionstatechange = function() {
  108. switch (pc.iceConnectionState) {
  109. case 'failed':
  110. util.log('iceConnectionState is disconnected, closing connections to ' + peerId);
  111. Negotiator._cleanup();
  112. break;
  113. case 'completed':
  114. pc.onicecandidate = util.noop;
  115. break;
  116. }
  117. };
  118. // Fallback for older Chrome impls.
  119. pc.onicechange = pc.oniceconnectionstatechange;
  120. // ONNEGOTIATIONNEEDED (Chrome)
  121. util.log('Listening for `negotiationneeded`');
  122. pc.onnegotiationneeded = function() {
  123. util.log('`negotiationneeded` triggered');
  124. Negotiator._makeOffer(connection);
  125. };
  126. // DATACONNECTION.
  127. util.log('Listening for data channel');
  128. // Fired between offer and answer, so options should already be saved
  129. // in the options hash.
  130. pc.ondatachannel = function(evt) {
  131. util.log('Received data channel');
  132. var dc = evt.channel;
  133. var connection = provider.getConnection(peerId, connectionId);
  134. connection.initialize(dc);
  135. };
  136. // MEDIACONNECTION.
  137. util.log('Listening for remote stream');
  138. pc.onaddstream = function(evt) {
  139. util.log('Received remote stream');
  140. var stream = evt.stream;
  141. provider.getConnection(peerId, id).receiveStream(stream);
  142. };
  143. }
  144. Negotiator._cleanup = function(provider, peerId, connectionId) {
  145. // TODO: close PeerConnection when all connections are closed.
  146. util.log('Cleanup PeerConnection for ' + peerId);
  147. /*if (!!this.pc && (this.pc.readyState !== 'closed' || this.pc.signalingState !== 'closed')) {
  148. this.pc.close();
  149. this.pc = null;
  150. }*/
  151. provider.socket.send({
  152. type: 'LEAVE',
  153. dst: peerId
  154. });
  155. }
  156. Negotiator._makeOffer = function(connection) {
  157. var pc = connection.pc;
  158. pc.createOffer(function(offer) {
  159. util.log('Created offer.');
  160. if (!util.supports.reliable) {
  161. //offer.sdp = Reliable.higherBandwidthSDP(offer.sdp);
  162. }
  163. pc.setLocalDescription(offer, function() {
  164. util.log('Set localDescription: offer', 'for:', connection.peer);
  165. connection.provider.socket.send({
  166. type: 'OFFER',
  167. payload: {
  168. sdp: offer,
  169. type: connection.type,
  170. connectionId: connection.id
  171. },
  172. dst: connection.peer,
  173. });
  174. }, function(err) {
  175. connection.provider.emit('error', err);
  176. util.log('Failed to setLocalDescription, ', err);
  177. });
  178. }, function(err) {
  179. connection.provider.emit('error', err);
  180. util.log('Failed to createOffer, ', err);
  181. });
  182. }
  183. Negotiator._makeAnswer = function(connection) {
  184. var pc = connection.pc;
  185. pc.createAnswer(function(answer) {
  186. util.log('Created answer.');
  187. if (!util.supports.reliable) {
  188. // TODO
  189. //answer.sdp = Reliable.higherBandwidthSDP(answer.sdp);
  190. }
  191. pc.setLocalDescription(answer, function() {
  192. util.log('Set localDescription: answer', 'for:', connection.peer);
  193. connection.provider.socket.send({
  194. type: 'ANSWER',
  195. payload: {
  196. sdp: answer,
  197. type: connection.type,
  198. connectionId: connection.id
  199. },
  200. dst: connection.peer
  201. });
  202. }, function(err) {
  203. connection.provider.emit('error', err);
  204. util.log('Failed to setLocalDescription, ', err);
  205. });
  206. }, function(err) {
  207. connection.provider.emit('error', err);
  208. util.log('Failed to create answer, ', err);
  209. });
  210. }
  211. /** Handle an SDP. */
  212. Negotiator.handleSDP = function(type, connection, sdp) {
  213. sdp = new RTCSessionDescription(sdp);
  214. var pc = connection.pc;
  215. util.log('Setting remote description', sdp);
  216. pc.setRemoteDescription(sdp, function() {
  217. util.log('Set remoteDescription:', type, 'for:', connection.peer);
  218. if (type === 'OFFER') {
  219. if (connection.type === 'media') {
  220. if (connection.localStream) {
  221. // Add local stream (from answer).
  222. pc.addStream(connection.localStream);
  223. }
  224. util.setZeroTimeout(function(){
  225. // Add remote streams
  226. connection.addStream(pc.getRemoteStreams()[0]);
  227. });
  228. }
  229. // TODO. also, why setZeroTimeout up there?
  230. Negotiator._makeAnswer(connection);
  231. }
  232. }, function(err) {
  233. connection.provider.emit('error', err);
  234. util.log('Failed to setRemoteDescription, ', err);
  235. });
  236. }
  237. /** Handle a candidate. */
  238. Negotiator.handleCandidate = function(connection, candidate) {
  239. var candidate = new RTCIceCandidate(candidate);
  240. connection.pc.addIceCandidate(candidate);
  241. util.log('Added ICE candidate for:', connection.peer);
  242. }
  243. /** Handle peer leaving. */
  244. Negotiator.handleLeave = function(connection) {
  245. util.log('Peer ' + connection.peer + ' disconnected.');
  246. // TODO: clean up PC if this is the last connection on that PC.
  247. }