negotiator.js 7.8 KB

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