provider.js 8.5 KB

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