simplewebrtc.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468
  1. var WebRTC = require('./webrtc');
  2. var WildEmitter = require('wildemitter');
  3. var webrtcSupport = require('webrtcsupport');
  4. var attachMediaStream = require('attachmediastream');
  5. var mockconsole = require('mockconsole');
  6. var SocketIoConnection = require('./socketioconnection');
  7. function SimpleWebRTC(opts) {
  8. var self = this;
  9. var options = opts || {};
  10. var config = this.config = {
  11. url: 'https://sandbox.simplewebrtc.com:443/',
  12. socketio: {/* 'force new connection':true*/},
  13. connection: null,
  14. debug: false,
  15. localVideoEl: '',
  16. remoteVideosEl: '',
  17. enableDataChannels: true,
  18. autoRequestMedia: false,
  19. autoRemoveVideos: true,
  20. adjustPeerVolume: false,
  21. peerVolumeWhenSpeaking: 0.25,
  22. media: {
  23. video: true,
  24. audio: true
  25. },
  26. receiveMedia: {
  27. offerToReceiveAudio: 1,
  28. offerToReceiveVideo: 1
  29. },
  30. localVideo: {
  31. autoplay: true,
  32. mirror: true,
  33. muted: true
  34. }
  35. };
  36. var item, connection;
  37. // We also allow a 'logger' option. It can be any object that implements
  38. // log, warn, and error methods.
  39. // We log nothing by default, following "the rule of silence":
  40. // http://www.linfo.org/rule_of_silence.html
  41. this.logger = function () {
  42. // we assume that if you're in debug mode and you didn't
  43. // pass in a logger, you actually want to log as much as
  44. // possible.
  45. if (opts.debug) {
  46. return opts.logger || console;
  47. } else {
  48. // or we'll use your logger which should have its own logic
  49. // for output. Or we'll return the no-op.
  50. return opts.logger || mockconsole;
  51. }
  52. }();
  53. // set our config from options
  54. for (item in options) {
  55. if (options.hasOwnProperty(item)) {
  56. this.config[item] = options[item];
  57. }
  58. }
  59. // attach detected support for convenience
  60. this.capabilities = webrtcSupport;
  61. // call WildEmitter constructor
  62. WildEmitter.call(this);
  63. // create default SocketIoConnection if it's not passed in
  64. if (this.config.connection === null) {
  65. connection = this.connection = new SocketIoConnection(this.config);
  66. } else {
  67. connection = this.connection = this.config.connection;
  68. }
  69. connection.on('connect', function () {
  70. self.emit('connectionReady', connection.getSessionid());
  71. self.sessionReady = true;
  72. self.testReadiness();
  73. });
  74. connection.on('message', function (message) {
  75. var peers = self.webrtc.getPeers(message.from, message.roomType);
  76. var peer;
  77. if (message.type === 'offer') {
  78. if (peers.length) {
  79. peers.forEach(function (p) {
  80. if (p.sid == message.sid) peer = p;
  81. });
  82. //if (!peer) peer = peers[0]; // fallback for old protocol versions
  83. }
  84. if (!peer) {
  85. peer = self.webrtc.createPeer({
  86. id: message.from,
  87. sid: message.sid,
  88. type: message.roomType,
  89. enableDataChannels: self.config.enableDataChannels && message.roomType !== 'screen',
  90. sharemyscreen: message.roomType === 'screen' && !message.broadcaster,
  91. broadcaster: message.roomType === 'screen' && !message.broadcaster ? self.connection.getSessionid() : null
  92. });
  93. self.emit('createdPeer', peer);
  94. }
  95. peer.handleMessage(message);
  96. } else if (peers.length) {
  97. peers.forEach(function (peer) {
  98. if (message.sid) {
  99. if (peer.sid === message.sid) {
  100. peer.handleMessage(message);
  101. }
  102. } else {
  103. peer.handleMessage(message);
  104. }
  105. });
  106. }
  107. });
  108. connection.on('remove', function (room) {
  109. if (room.id !== self.connection.getSessionid()) {
  110. self.webrtc.removePeers(room.id, room.type);
  111. }
  112. });
  113. // instantiate our main WebRTC helper
  114. // using same logger from logic here
  115. opts.logger = this.logger;
  116. opts.debug = false;
  117. this.webrtc = new WebRTC(opts);
  118. // attach a few methods from underlying lib to simple.
  119. ['mute', 'unmute', 'pauseVideo', 'resumeVideo', 'pause', 'resume', 'sendToAll', 'sendDirectlyToAll', 'getPeers'].forEach(function (method) {
  120. self[method] = self.webrtc[method].bind(self.webrtc);
  121. });
  122. // proxy events from WebRTC
  123. this.webrtc.on('*', function () {
  124. self.emit.apply(self, arguments);
  125. });
  126. // log all events in debug mode
  127. if (config.debug) {
  128. this.on('*', this.logger.log.bind(this.logger, 'SimpleWebRTC event:'));
  129. }
  130. // check for readiness
  131. this.webrtc.on('localStream', function () {
  132. self.testReadiness();
  133. });
  134. this.webrtc.on('message', function (payload) {
  135. self.connection.emit('message', payload);
  136. });
  137. this.webrtc.on('peerStreamAdded', this.handlePeerStreamAdded.bind(this));
  138. this.webrtc.on('peerStreamRemoved', this.handlePeerStreamRemoved.bind(this));
  139. // echo cancellation attempts
  140. if (this.config.adjustPeerVolume) {
  141. this.webrtc.on('speaking', this.setVolumeForAll.bind(this, this.config.peerVolumeWhenSpeaking));
  142. this.webrtc.on('stoppedSpeaking', this.setVolumeForAll.bind(this, 1));
  143. }
  144. connection.on('stunservers', function (args) {
  145. // resets/overrides the config
  146. self.webrtc.config.peerConnectionConfig.iceServers = args;
  147. self.emit('stunservers', args);
  148. });
  149. connection.on('turnservers', function (args) {
  150. // appends to the config
  151. self.webrtc.config.peerConnectionConfig.iceServers = self.webrtc.config.peerConnectionConfig.iceServers.concat(args);
  152. self.emit('turnservers', args);
  153. });
  154. this.webrtc.on('iceFailed', function (peer) {
  155. // local ice failure
  156. });
  157. this.webrtc.on('connectivityError', function (peer) {
  158. // remote ice failure
  159. });
  160. // sending mute/unmute to all peers
  161. this.webrtc.on('audioOn', function () {
  162. self.webrtc.sendToAll('unmute', {name: 'audio'});
  163. });
  164. this.webrtc.on('audioOff', function () {
  165. self.webrtc.sendToAll('mute', {name: 'audio'});
  166. });
  167. this.webrtc.on('videoOn', function () {
  168. self.webrtc.sendToAll('unmute', {name: 'video'});
  169. });
  170. this.webrtc.on('videoOff', function () {
  171. self.webrtc.sendToAll('mute', {name: 'video'});
  172. });
  173. // screensharing events
  174. this.webrtc.on('localScreen', function (stream) {
  175. var item,
  176. el = document.createElement('video'),
  177. container = self.getRemoteVideoContainer();
  178. el.oncontextmenu = function () { return false; };
  179. el.id = 'localScreen';
  180. attachMediaStream(stream, el);
  181. if (container) {
  182. container.appendChild(el);
  183. }
  184. self.emit('localScreenAdded', el);
  185. self.connection.emit('shareScreen');
  186. self.webrtc.peers.forEach(function (existingPeer) {
  187. var peer;
  188. if (existingPeer.type === 'video') {
  189. peer = self.webrtc.createPeer({
  190. id: existingPeer.id,
  191. type: 'screen',
  192. sharemyscreen: true,
  193. enableDataChannels: false,
  194. receiveMedia: {
  195. offerToReceiveAudio: 0,
  196. offerToReceiveVideo: 0
  197. },
  198. broadcaster: self.connection.getSessionid(),
  199. });
  200. self.emit('createdPeer', peer);
  201. peer.start();
  202. }
  203. });
  204. });
  205. this.webrtc.on('localScreenStopped', function (stream) {
  206. if (self.getLocalScreen()) {
  207. self.stopScreenShare();
  208. }
  209. /*
  210. self.connection.emit('unshareScreen');
  211. self.webrtc.peers.forEach(function (peer) {
  212. if (peer.sharemyscreen) {
  213. peer.end();
  214. }
  215. });
  216. */
  217. });
  218. this.webrtc.on('channelMessage', function (peer, label, data) {
  219. if (data.type == 'volume') {
  220. self.emit('remoteVolumeChange', peer, data.volume);
  221. }
  222. });
  223. if (this.config.autoRequestMedia) this.startLocalVideo();
  224. }
  225. SimpleWebRTC.prototype = Object.create(WildEmitter.prototype, {
  226. constructor: {
  227. value: SimpleWebRTC
  228. }
  229. });
  230. SimpleWebRTC.prototype.leaveRoom = function () {
  231. if (this.roomName) {
  232. this.connection.emit('leave');
  233. while (this.webrtc.peers.length) {
  234. this.webrtc.peers[0].end();
  235. }
  236. if (this.getLocalScreen()) {
  237. this.stopScreenShare();
  238. }
  239. this.emit('leftRoom', this.roomName);
  240. this.roomName = undefined;
  241. }
  242. };
  243. SimpleWebRTC.prototype.disconnect = function () {
  244. this.connection.disconnect();
  245. delete this.connection;
  246. };
  247. SimpleWebRTC.prototype.handlePeerStreamAdded = function (peer) {
  248. var self = this;
  249. var container = this.getRemoteVideoContainer();
  250. var video = attachMediaStream(peer.stream);
  251. // store video element as part of peer for easy removal
  252. peer.videoEl = video;
  253. video.id = this.getDomId(peer);
  254. if (container) container.appendChild(video);
  255. this.emit('videoAdded', video, peer);
  256. // send our mute status to new peer if we're muted
  257. // currently called with a small delay because it arrives before
  258. // the video element is created otherwise (which happens after
  259. // the async setRemoteDescription-createAnswer)
  260. window.setTimeout(function () {
  261. if (!self.webrtc.isAudioEnabled()) {
  262. peer.send('mute', {name: 'audio'});
  263. }
  264. if (!self.webrtc.isVideoEnabled()) {
  265. peer.send('mute', {name: 'video'});
  266. }
  267. }, 250);
  268. };
  269. SimpleWebRTC.prototype.handlePeerStreamRemoved = function (peer) {
  270. var container = this.getRemoteVideoContainer();
  271. var videoEl = peer.videoEl;
  272. if (this.config.autoRemoveVideos && container && videoEl) {
  273. container.removeChild(videoEl);
  274. }
  275. if (videoEl) this.emit('videoRemoved', videoEl, peer);
  276. };
  277. SimpleWebRTC.prototype.getDomId = function (peer) {
  278. return [peer.id, peer.type, peer.broadcaster ? 'broadcasting' : 'incoming'].join('_');
  279. };
  280. // set volume on video tag for all peers takse a value between 0 and 1
  281. SimpleWebRTC.prototype.setVolumeForAll = function (volume) {
  282. this.webrtc.peers.forEach(function (peer) {
  283. if (peer.videoEl) peer.videoEl.volume = volume;
  284. });
  285. };
  286. SimpleWebRTC.prototype.joinRoom = function (name, cb) {
  287. var self = this;
  288. this.roomName = name;
  289. this.connection.emit('join', name, function (err, roomDescription) {
  290. console.log('join CB', err, roomDescription);
  291. if (err) {
  292. self.emit('error', err);
  293. } else {
  294. var id,
  295. client,
  296. type,
  297. peer;
  298. for (id in roomDescription.clients) {
  299. client = roomDescription.clients[id];
  300. for (type in client) {
  301. if (client[type]) {
  302. peer = self.webrtc.createPeer({
  303. id: id,
  304. type: type,
  305. enableDataChannels: self.config.enableDataChannels && type !== 'screen',
  306. receiveMedia: {
  307. offerToReceiveAudio: type !== 'screen' && self.config.receiveMedia.offerToReceiveAudio ? 1 : 0,
  308. offerToReceiveVideo: self.config.receiveMedia.offerToReceiveVideo
  309. }
  310. });
  311. self.emit('createdPeer', peer);
  312. peer.start();
  313. }
  314. }
  315. }
  316. }
  317. if (cb) cb(err, roomDescription);
  318. self.emit('joinedRoom', name);
  319. });
  320. };
  321. SimpleWebRTC.prototype.getEl = function (idOrEl) {
  322. if (typeof idOrEl === 'string') {
  323. return document.getElementById(idOrEl);
  324. } else {
  325. return idOrEl;
  326. }
  327. };
  328. SimpleWebRTC.prototype.startLocalVideo = function () {
  329. var self = this;
  330. this.webrtc.startLocalMedia(this.config.media, function (err, stream) {
  331. if (err) {
  332. self.emit('localMediaError', err);
  333. } else {
  334. attachMediaStream(stream, self.getLocalVideoContainer(), self.config.localVideo);
  335. }
  336. });
  337. };
  338. SimpleWebRTC.prototype.stopLocalVideo = function () {
  339. this.webrtc.stopLocalMedia();
  340. };
  341. // this accepts either element ID or element
  342. // and either the video tag itself or a container
  343. // that will be used to put the video tag into.
  344. SimpleWebRTC.prototype.getLocalVideoContainer = function () {
  345. var el = this.getEl(this.config.localVideoEl);
  346. if (el && el.tagName === 'VIDEO') {
  347. el.oncontextmenu = function () { return false; };
  348. return el;
  349. } else if (el) {
  350. var video = document.createElement('video');
  351. video.oncontextmenu = function () { return false; };
  352. el.appendChild(video);
  353. return video;
  354. } else {
  355. return;
  356. }
  357. };
  358. SimpleWebRTC.prototype.getRemoteVideoContainer = function () {
  359. return this.getEl(this.config.remoteVideosEl);
  360. };
  361. SimpleWebRTC.prototype.shareScreen = function (cb) {
  362. this.webrtc.startScreenShare(cb);
  363. };
  364. SimpleWebRTC.prototype.getLocalScreen = function () {
  365. return this.webrtc.localScreen;
  366. };
  367. SimpleWebRTC.prototype.stopScreenShare = function () {
  368. this.connection.emit('unshareScreen');
  369. var videoEl = document.getElementById('localScreen');
  370. var container = this.getRemoteVideoContainer();
  371. if (this.config.autoRemoveVideos && container && videoEl) {
  372. container.removeChild(videoEl);
  373. }
  374. // a hack to emit the event the removes the video
  375. // element that we want
  376. if (videoEl) {
  377. this.emit('videoRemoved', videoEl);
  378. }
  379. if (this.getLocalScreen()) {
  380. this.webrtc.stopScreenShare();
  381. }
  382. this.webrtc.peers.forEach(function (peer) {
  383. if (peer.broadcaster) {
  384. peer.end();
  385. }
  386. });
  387. };
  388. SimpleWebRTC.prototype.testReadiness = function () {
  389. var self = this;
  390. if (this.sessionReady) {
  391. if (!this.config.media.video && !this.config.media.audio) {
  392. self.emit('readyToCall', self.connection.getSessionid());
  393. } else if (this.webrtc.localStreams.length > 0) {
  394. self.emit('readyToCall', self.connection.getSessionid());
  395. }
  396. }
  397. };
  398. SimpleWebRTC.prototype.createRoom = function (name, cb) {
  399. this.roomName = name;
  400. if (arguments.length === 2) {
  401. this.connection.emit('create', name, cb);
  402. } else {
  403. this.connection.emit('create', name);
  404. }
  405. };
  406. SimpleWebRTC.prototype.sendFile = function () {
  407. if (!webrtcSupport.dataChannel) {
  408. return this.emit('error', new Error('DataChannelNotSupported'));
  409. }
  410. };
  411. module.exports = SimpleWebRTC;