sink.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479
  1. function Peer(options) {
  2. if (!(this instanceof Peer)) return new Peer(options);
  3. EventEmitter.call(this);
  4. options = util.extend({
  5. // Defaults
  6. }, options);
  7. this._config = options.config || { 'iceServers': [{ 'url': 'stun:stun.l.google.com:19302' }] };
  8. this._id = null;
  9. // User handlers.
  10. this._handlers = {};
  11. // Source to connect to; null if waiting for a connection.
  12. this._peer = options.source || null;
  13. // Booleans to determine what streams to allow.
  14. this._video = options.video;
  15. this._data = options.data != undefined ? options.data : true;
  16. this._audio = options.audio;
  17. // Connections
  18. this._pc = null;
  19. this._dc = null;
  20. this._socket = new WebSocket(options.ws || 'ws://localhost');
  21. // Local streams for multiple use.
  22. this._localVideo = options.localVideo || null;
  23. this._localAudio = options.localAudio || null;
  24. // Init socket msg handlers
  25. var self = this;
  26. this._socket.onopen = function() {
  27. self.socketInit();
  28. };
  29. // Firefoxism: connectDataConnection ports.
  30. if (browserisms == 'Firefox' && !options.source) {
  31. if (!Peer.usedPorts) {
  32. Peer.usedPorts = [];
  33. }
  34. this.localPort = util.randomPort();
  35. while (Peer.usedPorts.indexOf(this.localPort) != -1) {
  36. this.localPort = util.randomPort();
  37. }
  38. this.remotePort = util.randomPort();
  39. while (this.remotePort == this.localPort ||
  40. Peer.usedPorts.indexOf(this.localPort) != -1) {
  41. this.remotePort = util.randomPort();
  42. }
  43. Peer.usedPorts.push(this.remotePort);
  44. Peer.usedPorts.push(this.localPort);
  45. }
  46. };
  47. util.inherits(Peer, EventEmitter);
  48. /** Start up websocket communications. */
  49. Peer.prototype.socketInit = function() {
  50. var self = this;
  51. if (!!this._peer) {
  52. // Announce as a sink if initiated with a source.
  53. this._socket.send(JSON.stringify({
  54. type: 'SINK',
  55. source: this._peer,
  56. isms: browserisms
  57. }));
  58. this._socket.onmessage = function(event) {
  59. var message = JSON.parse(event.data);
  60. switch (message.type) {
  61. case 'SINK-ID':
  62. self._id = message.id;
  63. self.emit('ready', self._id);
  64. self.startPeerConnection();
  65. break;
  66. case 'OFFER':
  67. var sdp = message.sdp;
  68. try {
  69. sdp = new RTCSessionDescription(message.sdp);
  70. } catch(e) {
  71. util.log('Firefox');
  72. }
  73. self._pc.setRemoteDescription(sdp, function() {
  74. util.log('setRemoteDescription: offer');
  75. // If we also have to set up a stream on the sink end, do so.
  76. self.handleStream(false, function() {
  77. self.maybeBrowserisms(false);
  78. });
  79. }, function(err) {
  80. util.log('failed to setRemoteDescription with offer, ', err);
  81. });
  82. break;
  83. case 'CANDIDATE':
  84. util.log(message.candidate);
  85. var candidate = new RTCIceCandidate(message.candidate);
  86. self._pc.addIceCandidate(candidate);
  87. break;
  88. case 'LEAVE':
  89. util.log('counterpart disconnected');
  90. if (!!self._pc && self._pc.readyState != 'closed') {
  91. self._pc.close();
  92. self._pc = null;
  93. self._peer = null;
  94. }
  95. if (!!self._dc && self._dc.readyState != 'closed') {
  96. self._dc.close();
  97. self._dc = null;
  98. }
  99. break;
  100. case 'PORT':
  101. if (browserisms && browserisms == 'Firefox') {
  102. if (!Peer.usedPorts) {
  103. Peer.usedPorts = [];
  104. }
  105. Peer.usedPorts.push(message.local);
  106. Peer.usedPorts.push(message.remote);
  107. self._pc.connectDataConnection(message.local, message.remote);
  108. break;
  109. }
  110. case 'DEFAULT':
  111. util.log('SINK: unrecognized message ', message.type);
  112. break;
  113. }
  114. };
  115. } else {
  116. // Otherwise, this sink is the originator to another sink and should wait
  117. // for an alert to begin the PC process.
  118. this._socket.send(JSON.stringify({
  119. type: 'SOURCE',
  120. isms: browserisms
  121. }));
  122. this._socket.onmessage = function(event) {
  123. var message = JSON.parse(event.data);
  124. switch (message.type) {
  125. case 'SOURCE-ID':
  126. self._id = message.id;
  127. self.emit('ready', self._id);
  128. break;
  129. case 'SINK-CONNECTED':
  130. self._peer = message.sink;
  131. self.startPeerConnection();
  132. self.handleStream(true, function() {
  133. self.maybeBrowserisms(true);
  134. });
  135. break;
  136. case 'ANSWER':
  137. var sdp = message.sdp;
  138. try {
  139. sdp = new RTCSessionDescription(message.sdp);
  140. } catch(e) {
  141. util.log('Firefox');
  142. }
  143. self._pc.setRemoteDescription(sdp, function() {
  144. util.log('setRemoteDescription: answer');
  145. // Firefoxism
  146. if (browserisms == 'Firefox') {
  147. self._pc.connectDataConnection(self.localPort, self.remotePort);
  148. self._socket.send(JSON.stringify({
  149. type: 'PORT',
  150. dst: self._peer,
  151. remote: self.localPort,
  152. local: self.remotePort
  153. }));
  154. }
  155. util.log('ORIGINATOR: PeerConnection success');
  156. }, function(err) {
  157. util.log('failed to setRemoteDescription, ', err);
  158. });
  159. break;
  160. case 'CANDIDATE':
  161. util.log(message.candidate);
  162. var candidate = new RTCIceCandidate(message.candidate);
  163. self._pc.addIceCandidate(candidate);
  164. break;
  165. case 'LEAVE':
  166. util.log('counterpart disconnected');
  167. if (!!self._pc && self._pc.readyState != 'closed') {
  168. self._pc.close();
  169. self._pc = null;
  170. self._peer = null;
  171. }
  172. if (!!self._dc && self._dc.readyState != 'closed') {
  173. self._dc.close();
  174. self._dc = null;
  175. }
  176. break;
  177. case 'DEFAULT':
  178. util.log('ORIGINATOR: message not recognized ', message.type);
  179. }
  180. };
  181. }
  182. // Makes sure things clean up neatly when window is closed.
  183. window.onbeforeunload = function() {
  184. if (!!self._pc && self._pc.readyState != 'closed') {
  185. self._pc.close();
  186. }
  187. if (!!self._socket && !!self._peer) {
  188. self._socket.send(JSON.stringify({ type: 'LEAVE', dst: self._peer }));
  189. if (!!self._dc && self._dc.readyState != 'closed') {
  190. self._dc.close();
  191. }
  192. }
  193. }
  194. };
  195. /** Takes care of ice handlers. */
  196. Peer.prototype.setupIce = function() {
  197. var self = this;
  198. this._pc.onicecandidate = function(event) {
  199. util.log('candidates received');
  200. if (event.candidate) {
  201. self._socket.send(JSON.stringify({
  202. type: 'CANDIDATE',
  203. candidate: event.candidate,
  204. dst: self._peer
  205. }));
  206. } else {
  207. util.log("End of candidates.");
  208. }
  209. };
  210. };
  211. /** Starts a PeerConnection and sets up handlers. */
  212. Peer.prototype.startPeerConnection = function() {
  213. this._pc = new RTCPeerConnection(this._config, { optional:[ { RtpDataChannels: true } ]});
  214. this.setupIce();
  215. this.setupAudioVideo();
  216. };
  217. /** Decide whether to handle Firefoxisms. */
  218. Peer.prototype.maybeBrowserisms = function(originator) {
  219. var self = this;
  220. if (browserisms == 'Firefox' && !this._video && !this._audio/* && !this._stream*/) {
  221. getUserMedia({ audio: true, fake: true }, function(s) {
  222. self._pc.addStream(s);
  223. if (originator) {
  224. self.makeOffer();
  225. } else {
  226. self.makeAnswer();
  227. }
  228. }, function(err) { util.log('crap'); });
  229. } else {
  230. if (originator) {
  231. this.makeOffer();
  232. } else {
  233. this.makeAnswer();
  234. }
  235. }
  236. }
  237. /** Create an answer for PC. */
  238. Peer.prototype.makeAnswer = function() {
  239. var self = this;
  240. this._pc.createAnswer(function(answer) {
  241. util.log('createAnswer');
  242. self._pc.setLocalDescription(answer, function() {
  243. util.log('setLocalDescription: answer');
  244. self._socket.send(JSON.stringify({
  245. type: 'ANSWER',
  246. src: self._id,
  247. sdp: answer,
  248. dst: self._peer
  249. }));
  250. }, function(err) {
  251. util.log('failed to setLocalDescription, ', err)
  252. });
  253. }, function(err) {
  254. util.log('failed to create answer, ', err)
  255. });
  256. };
  257. /** Create an offer for PC. */
  258. Peer.prototype.makeOffer = function() {
  259. var self = this;
  260. this._pc.createOffer(function(offer) {
  261. util.log('createOffer')
  262. self._pc.setLocalDescription(offer, function() {
  263. util.log('setLocalDescription: offer');
  264. self._socket.send(JSON.stringify({
  265. type: 'OFFER',
  266. sdp: offer,
  267. dst: self._peer,
  268. src: self._id
  269. }));
  270. }, function(err) {
  271. util.log('failed to setLocalDescription, ', err);
  272. });
  273. });
  274. };
  275. /** Sets up A/V stream handler. */
  276. Peer.prototype.setupAudioVideo = function() {
  277. var self = this;
  278. util.log('onaddstream handler added');
  279. this._pc.onaddstream = function(obj) {
  280. util.log('Remote stream added');
  281. // this._stream = true;
  282. self.emit('remotestream', obj.type, obj.stream);
  283. };
  284. };
  285. /** Handle the different types of streams requested by user. */
  286. Peer.prototype.handleStream = function(originator, cb) {
  287. if (this._data) {
  288. this.setupDataChannel(originator);
  289. }
  290. this.getAudioVideo(originator, cb);
  291. };
  292. /** Get A/V streams. */
  293. Peer.prototype.getAudioVideo = function(originator, cb) {
  294. var self = this;
  295. if (this._video && !this._localVideo) {
  296. getUserMedia({ video: true }, function(vstream) {
  297. self._pc.addStream(vstream);
  298. self._localVideo = vstream;
  299. util.log('Local video stream added');
  300. self.emit('localstream', 'video', vstream);
  301. if (self._audio && !self._localAudio) {
  302. getUserMedia({ audio: true }, function(astream) {
  303. self._pc.addStream(astream);
  304. self._localAudio = astream;
  305. util.log('Local audio stream added');
  306. self.emit('localstream', 'audio', astream);
  307. cb();
  308. }, function(err) { util.log('Audio cannot start'); cb(); });
  309. } else {
  310. if (self._audio) {
  311. self._pc.addStream(self._localAudio);
  312. }
  313. cb();
  314. }
  315. }, function(err) { util.log('Video cannot start', err); cb(); });
  316. } else if (this._audio && !this._localAudio) {
  317. getUserMedia({ audio: true }, function(astream) {
  318. self._pc.addStream(astream);
  319. self._localAudio = astream;
  320. util.log('Local audio stream added');
  321. self.emit('localstream', 'audio', astream);
  322. cb();
  323. }, function(err) { util.log('Audio cannot start'); cb(); });
  324. } else {
  325. if (this._audio) {
  326. this._pc.addStream(this._localAudio);
  327. }
  328. if (this._video) {
  329. this._pc.addStream(this._localVideo);
  330. }
  331. util.log('no audio/video streams initiated');
  332. cb();
  333. }
  334. };
  335. /** Sets up DataChannel handlers. */
  336. Peer.prototype.setupDataChannel = function(originator, cb) {
  337. var self = this;
  338. if (originator) {
  339. /** ORIGINATOR SETUP */
  340. if (browserisms == 'Webkit') {
  341. this._pc.onstatechange = function() {
  342. util.log('State Change: ', self._pc.readyState);
  343. /*if (self._pc.readyState == 'active') {
  344. util.log('ORIGINATOR: active state detected');
  345. self._dc = self._pc.createDataChannel('StreamAPI', { reliable: false });
  346. self._dc.binaryType = 'blob';
  347. if (!!self._handlers['connection']) {
  348. self._handlers['connection'](self._peer);
  349. }
  350. self._dc.onmessage = function(e) {
  351. self.handleDataMessage(e);
  352. };
  353. }*/
  354. }
  355. } else {
  356. this._pc.onconnection = function() {
  357. util.log('ORIGINATOR: onconnection triggered');
  358. self.startDataChannel();
  359. };
  360. }
  361. } else {
  362. /** TARGET SETUP */
  363. this._pc.ondatachannel = function(dc) {
  364. util.log('SINK: ondatachannel triggered');
  365. self._dc = dc;
  366. self._dc.binaryType = 'blob';
  367. self.emit('connection', self._peer);
  368. self._dc.onmessage = function(e) {
  369. self.handleDataMessage(e);
  370. };
  371. };
  372. this._pc.onconnection = function() {
  373. util.log('SINK: onconnection triggered');
  374. };
  375. }
  376. this._pc.onclosedconnection = function() {
  377. // Remove socket handlers perhaps.
  378. };
  379. };
  380. Peer.prototype.startDataChannel = function() {
  381. var self = this;
  382. this._dc = this._pc.createDataChannel(this._peer, { reliable: false });
  383. this._dc.binaryType = 'blob';
  384. this.emit('connection', this._peer);
  385. this._dc.onmessage = function(e) {
  386. self.handleDataMessage(e);
  387. };
  388. };
  389. /** Allows user to send data. */
  390. Peer.prototype.send = function(data) {
  391. var ab = BinaryPack.pack(data);
  392. this._dc.send(ab);
  393. }
  394. // Handles a DataChannel message.
  395. // TODO: have these extend Peer, which will impl these generic handlers.
  396. Peer.prototype.handleDataMessage = function(e) {
  397. var self = this;
  398. var fr = new FileReader();
  399. fr.onload = function(evt) {
  400. var ab = evt.target.result;
  401. var data = BinaryPack.unpack(ab);
  402. self.emit('data', data);
  403. };
  404. fr.readAsArrayBuffer(e.data);
  405. }
  406. exports.Peer = Peer;