sink.js 11 KB

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