You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

JitsiConference.js 35KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085
  1. /* global Strophe, $, Promise */
  2. /* jshint -W101 */
  3. var logger = require("jitsi-meet-logger").getLogger(__filename);
  4. var RTC = require("./modules/RTC/RTC");
  5. var XMPPEvents = require("./service/xmpp/XMPPEvents");
  6. var AuthenticationEvents = require("./service/authentication/AuthenticationEvents");
  7. var RTCEvents = require("./service/RTC/RTCEvents");
  8. var EventEmitter = require("events");
  9. var JitsiConferenceEvents = require("./JitsiConferenceEvents");
  10. var JitsiConferenceErrors = require("./JitsiConferenceErrors");
  11. var JitsiParticipant = require("./JitsiParticipant");
  12. var Statistics = require("./modules/statistics/statistics");
  13. var JitsiDTMFManager = require('./modules/DTMF/JitsiDTMFManager');
  14. var JitsiTrackEvents = require("./JitsiTrackEvents");
  15. var Settings = require("./modules/settings/Settings");
  16. /**
  17. * Creates a JitsiConference object with the given name and properties.
  18. * Note: this constructor is not a part of the public API (objects should be
  19. * created using JitsiConnection.createConference).
  20. * @param options.config properties / settings related to the conference that will be created.
  21. * @param options.name the name of the conference
  22. * @param options.connection the JitsiConnection object for this JitsiConference.
  23. * @constructor
  24. */
  25. function JitsiConference(options) {
  26. if(!options.name || options.name.toLowerCase() !== options.name) {
  27. logger.error("Invalid conference name (no conference name passed or it"
  28. + "contains invalid characters like capital letters)!");
  29. return;
  30. }
  31. this.options = options;
  32. this.connection = this.options.connection;
  33. this.xmpp = this.connection.xmpp;
  34. this.eventEmitter = new EventEmitter();
  35. var confID = this.options.name + '@' + this.xmpp.options.hosts.muc;
  36. this.settings = new Settings(confID);
  37. this.room = this.xmpp.createRoom(this.options.name, this.options.config,
  38. this.settings);
  39. this.room.updateDeviceAvailability(RTC.getDeviceAvailability());
  40. this.rtc = new RTC(this.room, options);
  41. this.statistics = new Statistics({
  42. callStatsID: this.options.config.callStatsID,
  43. callStatsSecret: this.options.config.callStatsSecret,
  44. disableThirdPartyRequests: this.options.config.disableThirdPartyRequests
  45. });
  46. setupListeners(this);
  47. JitsiMeetJS._gumFailedHandler.push(function(error) {
  48. this.statistics.sendGetUserMediaFailed(error);
  49. }.bind(this));
  50. this.participants = {};
  51. this.lastDominantSpeaker = null;
  52. this.dtmfManager = null;
  53. this.somebodySupportsDTMF = false;
  54. this.authEnabled = false;
  55. this.authIdentity;
  56. this.startAudioMuted = false;
  57. this.startVideoMuted = false;
  58. this.startMutedPolicy = {audio: false, video: false};
  59. this.availableDevices = {
  60. audio: undefined,
  61. video: undefined
  62. };
  63. }
  64. /**
  65. * Joins the conference.
  66. * @param password {string} the password
  67. */
  68. JitsiConference.prototype.join = function (password) {
  69. if(this.room)
  70. this.room.join(password);
  71. };
  72. /**
  73. * Check if joined to the conference.
  74. */
  75. JitsiConference.prototype.isJoined = function () {
  76. return this.room && this.room.joined;
  77. };
  78. /**
  79. * Leaves the conference.
  80. */
  81. JitsiConference.prototype.leave = function () {
  82. if(this.xmpp && this.room)
  83. this.xmpp.leaveRoom(this.room.roomjid);
  84. this.room = null;
  85. };
  86. /**
  87. * Returns name of this conference.
  88. */
  89. JitsiConference.prototype.getName = function () {
  90. return this.options.name;
  91. };
  92. /**
  93. * Check if authentication is enabled for this conference.
  94. */
  95. JitsiConference.prototype.isAuthEnabled = function () {
  96. return this.authEnabled;
  97. };
  98. /**
  99. * Check if user is logged in.
  100. */
  101. JitsiConference.prototype.isLoggedIn = function () {
  102. return !!this.authIdentity;
  103. };
  104. /**
  105. * Get authorized login.
  106. */
  107. JitsiConference.prototype.getAuthLogin = function () {
  108. return this.authIdentity;
  109. };
  110. /**
  111. * Check if external authentication is enabled for this conference.
  112. */
  113. JitsiConference.prototype.isExternalAuthEnabled = function () {
  114. return this.room && this.room.moderator.isExternalAuthEnabled();
  115. };
  116. /**
  117. * Get url for external authentication.
  118. * @param {boolean} [urlForPopup] if true then return url for login popup,
  119. * else url of login page.
  120. * @returns {Promise}
  121. */
  122. JitsiConference.prototype.getExternalAuthUrl = function (urlForPopup) {
  123. return new Promise(function (resolve, reject) {
  124. if (!this.isExternalAuthEnabled()) {
  125. reject();
  126. return;
  127. }
  128. if (urlForPopup) {
  129. this.room.moderator.getPopupLoginUrl(resolve, reject);
  130. } else {
  131. this.room.moderator.getLoginUrl(resolve, reject);
  132. }
  133. }.bind(this));
  134. };
  135. /**
  136. * Returns the local tracks.
  137. */
  138. JitsiConference.prototype.getLocalTracks = function () {
  139. if (this.rtc) {
  140. return this.rtc.localStreams;
  141. } else {
  142. return [];
  143. }
  144. };
  145. /**
  146. * Attaches a handler for events(For example - "participant joined".) in the conference. All possible event are defined
  147. * in JitsiConferenceEvents.
  148. * @param eventId the event ID.
  149. * @param handler handler for the event.
  150. *
  151. * Note: consider adding eventing functionality by extending an EventEmitter impl, instead of rolling ourselves
  152. */
  153. JitsiConference.prototype.on = function (eventId, handler) {
  154. if(this.eventEmitter)
  155. this.eventEmitter.on(eventId, handler);
  156. };
  157. /**
  158. * Removes event listener
  159. * @param eventId the event ID.
  160. * @param [handler] optional, the specific handler to unbind
  161. *
  162. * Note: consider adding eventing functionality by extending an EventEmitter impl, instead of rolling ourselves
  163. */
  164. JitsiConference.prototype.off = function (eventId, handler) {
  165. if(this.eventEmitter)
  166. this.eventEmitter.removeListener(eventId, handler);
  167. };
  168. // Common aliases for event emitter
  169. JitsiConference.prototype.addEventListener = JitsiConference.prototype.on;
  170. JitsiConference.prototype.removeEventListener = JitsiConference.prototype.off;
  171. /**
  172. * Receives notifications from another participants for commands / custom events
  173. * (send by sendPresenceCommand method).
  174. * @param command {String} the name of the command
  175. * @param handler {Function} handler for the command
  176. */
  177. JitsiConference.prototype.addCommandListener = function (command, handler) {
  178. if(this.room)
  179. this.room.addPresenceListener(command, handler);
  180. };
  181. /**
  182. * Removes command listener
  183. * @param command {String} the name of the command
  184. */
  185. JitsiConference.prototype.removeCommandListener = function (command) {
  186. if(this.room)
  187. this.room.removePresenceListener(command);
  188. };
  189. /**
  190. * Sends text message to the other participants in the conference
  191. * @param message the text message.
  192. */
  193. JitsiConference.prototype.sendTextMessage = function (message) {
  194. if(this.room)
  195. this.room.sendMessage(message);
  196. };
  197. /**
  198. * Send presence command.
  199. * @param name the name of the command.
  200. * @param values Object with keys and values that will be send.
  201. **/
  202. JitsiConference.prototype.sendCommand = function (name, values) {
  203. if(this.room) {
  204. this.room.addToPresence(name, values);
  205. this.room.sendPresence();
  206. }
  207. };
  208. /**
  209. * Send presence command one time.
  210. * @param name the name of the command.
  211. * @param values Object with keys and values that will be send.
  212. **/
  213. JitsiConference.prototype.sendCommandOnce = function (name, values) {
  214. this.sendCommand(name, values);
  215. this.removeCommand(name);
  216. };
  217. /**
  218. * Send presence command.
  219. * @param name the name of the command.
  220. * @param values Object with keys and values that will be send.
  221. * @param persistent if false the command will be sent only one time
  222. **/
  223. JitsiConference.prototype.removeCommand = function (name) {
  224. if(this.room)
  225. this.room.removeFromPresence(name);
  226. };
  227. /**
  228. * Sets the display name for this conference.
  229. * @param name the display name to set
  230. */
  231. JitsiConference.prototype.setDisplayName = function(name) {
  232. if(this.room){
  233. // remove previously set nickname
  234. this.room.removeFromPresence("nick");
  235. this.room.addToPresence("nick", {attributes: {xmlns: 'http://jabber.org/protocol/nick'}, value: name});
  236. this.room.sendPresence();
  237. }
  238. };
  239. /**
  240. * Set new subject for this conference. (available only for moderator)
  241. * @param {string} subject new subject
  242. */
  243. JitsiConference.prototype.setSubject = function (subject) {
  244. if (this.room && this.isModerator()) {
  245. this.room.setSubject(subject);
  246. }
  247. };
  248. /**
  249. * Adds JitsiLocalTrack object to the conference.
  250. * @param track the JitsiLocalTrack object.
  251. */
  252. JitsiConference.prototype.addTrack = function (track) {
  253. this.room.addStream(track.getOriginalStream(), function () {
  254. this.rtc.addLocalStream(track);
  255. if (track.startMuted) {
  256. track.mute();
  257. }
  258. track.muteHandler = this._fireMuteChangeEvent.bind(this, track);
  259. track.stopHandler = this.removeTrack.bind(this, track);
  260. track.audioLevelHandler = this._fireAudioLevelChangeEvent.bind(this);
  261. track.addEventListener(JitsiTrackEvents.TRACK_MUTE_CHANGED,
  262. track.muteHandler);
  263. track.addEventListener(JitsiTrackEvents.TRACK_STOPPED,
  264. track.stopHandler);
  265. track.addEventListener(JitsiTrackEvents.TRACK_AUDIO_LEVEL_CHANGED,
  266. track.audioLevelHandler);
  267. this.eventEmitter.emit(JitsiConferenceEvents.TRACK_ADDED, track);
  268. }.bind(this));
  269. };
  270. /**
  271. * Fires TRACK_AUDIO_LEVEL_CHANGED change conference event.
  272. * @param audioLevel the audio level
  273. */
  274. JitsiConference.prototype._fireAudioLevelChangeEvent = function (audioLevel) {
  275. this.eventEmitter.emit(
  276. JitsiConferenceEvents.TRACK_AUDIO_LEVEL_CHANGED,
  277. this.myUserId(), audioLevel);
  278. };
  279. /**
  280. * Fires TRACK_MUTE_CHANGED change conference event.
  281. * @param track the JitsiTrack object related to the event.
  282. */
  283. JitsiConference.prototype._fireMuteChangeEvent = function (track) {
  284. this.eventEmitter.emit(JitsiConferenceEvents.TRACK_MUTE_CHANGED, track);
  285. };
  286. /**
  287. * Removes JitsiLocalTrack object to the conference.
  288. * @param track the JitsiLocalTrack object.
  289. */
  290. JitsiConference.prototype.removeTrack = function (track) {
  291. if(!this.room){
  292. if(this.rtc)
  293. this.rtc.removeLocalStream(track);
  294. return;
  295. }
  296. this.room.removeStream(track.getOriginalStream(), function(){
  297. this.rtc.removeLocalStream(track);
  298. track.removeEventListener(JitsiTrackEvents.TRACK_MUTE_CHANGED, track.muteHandler);
  299. track.removeEventListener(JitsiTrackEvents.TRACK_STOPPED, track.stopHandler);
  300. track.removeEventListener(JitsiTrackEvents.TRACK_AUDIO_LEVEL_CHANGED, track.audioLevelHandler);
  301. this.eventEmitter.emit(JitsiConferenceEvents.TRACK_REMOVED, track);
  302. }.bind(this));
  303. };
  304. /**
  305. * Get role of the local user.
  306. * @returns {string} user role: 'moderator' or 'none'
  307. */
  308. JitsiConference.prototype.getRole = function () {
  309. return this.room.role;
  310. };
  311. /**
  312. * Check if local user is moderator.
  313. * @returns {boolean} true if local user is moderator, false otherwise.
  314. */
  315. JitsiConference.prototype.isModerator = function () {
  316. return this.room.isModerator();
  317. };
  318. /**
  319. * Set password for the room.
  320. * @param {string} password new password for the room.
  321. * @returns {Promise}
  322. */
  323. JitsiConference.prototype.lock = function (password) {
  324. if (!this.isModerator()) {
  325. return Promise.reject();
  326. }
  327. var conference = this;
  328. return new Promise(function (resolve, reject) {
  329. conference.room.lockRoom(password || "", function () {
  330. resolve();
  331. }, function (err) {
  332. reject(err);
  333. }, function () {
  334. reject(JitsiConferenceErrors.PASSWORD_NOT_SUPPORTED);
  335. });
  336. });
  337. };
  338. /**
  339. * Remove password from the room.
  340. * @returns {Promise}
  341. */
  342. JitsiConference.prototype.unlock = function () {
  343. return this.lock();
  344. };
  345. /**
  346. * Elects the participant with the given id to be the selected participant or the speaker.
  347. * @param id the identifier of the participant
  348. */
  349. JitsiConference.prototype.selectParticipant = function(participantId) {
  350. if (this.rtc) {
  351. this.rtc.selectedEndpoint(participantId);
  352. }
  353. };
  354. /**
  355. *
  356. * @param id the identifier of the participant
  357. */
  358. JitsiConference.prototype.pinParticipant = function(participantId) {
  359. if (this.rtc) {
  360. this.rtc.pinEndpoint(participantId);
  361. }
  362. };
  363. /**
  364. * Returns the list of participants for this conference.
  365. * @return Array<JitsiParticipant> a list of participant identifiers containing all conference participants.
  366. */
  367. JitsiConference.prototype.getParticipants = function() {
  368. return Object.keys(this.participants).map(function (key) {
  369. return this.participants[key];
  370. }, this);
  371. };
  372. /**
  373. * @returns {JitsiParticipant} the participant in this conference with the specified id (or
  374. * undefined if there isn't one).
  375. * @param id the id of the participant.
  376. */
  377. JitsiConference.prototype.getParticipantById = function(id) {
  378. return this.participants[id];
  379. };
  380. /**
  381. * Kick participant from this conference.
  382. * @param {string} id id of the participant to kick
  383. */
  384. JitsiConference.prototype.kickParticipant = function (id) {
  385. var participant = this.getParticipantById(id);
  386. if (!participant) {
  387. return;
  388. }
  389. this.room.kick(participant.getJid());
  390. };
  391. /**
  392. * Kick participant from this conference.
  393. * @param {string} id id of the participant to kick
  394. */
  395. JitsiConference.prototype.muteParticipant = function (id) {
  396. var participant = this.getParticipantById(id);
  397. if (!participant) {
  398. return;
  399. }
  400. this.room.muteParticipant(participant.getJid(), true);
  401. };
  402. JitsiConference.prototype.onMemberJoined = function (jid, nick, role) {
  403. var id = Strophe.getResourceFromJid(jid);
  404. if (id === 'focus' || this.myUserId() === id) {
  405. return;
  406. }
  407. var participant = new JitsiParticipant(jid, this, nick);
  408. participant._role = role;
  409. this.participants[id] = participant;
  410. this.eventEmitter.emit(JitsiConferenceEvents.USER_JOINED, id, participant);
  411. this.xmpp.connection.disco.info(
  412. jid, "node", function(iq) {
  413. participant._supportsDTMF = $(iq).find(
  414. '>query>feature[var="urn:xmpp:jingle:dtmf:0"]').length > 0;
  415. this.updateDTMFSupport();
  416. }.bind(this)
  417. );
  418. };
  419. JitsiConference.prototype.onMemberLeft = function (jid) {
  420. var id = Strophe.getResourceFromJid(jid);
  421. if (id === 'focus' || this.myUserId() === id) {
  422. return;
  423. }
  424. var participant = this.participants[id];
  425. delete this.participants[id];
  426. this.eventEmitter.emit(JitsiConferenceEvents.USER_LEFT, id, participant);
  427. };
  428. JitsiConference.prototype.onUserRoleChanged = function (jid, role) {
  429. var id = Strophe.getResourceFromJid(jid);
  430. var participant = this.getParticipantById(id);
  431. if (!participant) {
  432. return;
  433. }
  434. participant._role = role;
  435. this.eventEmitter.emit(JitsiConferenceEvents.USER_ROLE_CHANGED, id, role);
  436. };
  437. JitsiConference.prototype.onDisplayNameChanged = function (jid, displayName) {
  438. var id = Strophe.getResourceFromJid(jid);
  439. var participant = this.getParticipantById(id);
  440. if (!participant) {
  441. return;
  442. }
  443. participant._displayName = displayName;
  444. this.eventEmitter.emit(JitsiConferenceEvents.DISPLAY_NAME_CHANGED, id, displayName);
  445. };
  446. JitsiConference.prototype.onTrackAdded = function (track) {
  447. var id = track.getParticipantId();
  448. var participant = this.getParticipantById(id);
  449. if (!participant) {
  450. return;
  451. }
  452. // add track to JitsiParticipant
  453. participant._tracks.push(track);
  454. var emitter = this.eventEmitter;
  455. track.addEventListener(
  456. JitsiTrackEvents.TRACK_STOPPED,
  457. function () {
  458. // remove track from JitsiParticipant
  459. var pos = participant._tracks.indexOf(track);
  460. if (pos > -1) {
  461. participant._tracks.splice(pos, 1);
  462. }
  463. emitter.emit(JitsiConferenceEvents.TRACK_REMOVED, track);
  464. }
  465. );
  466. track.addEventListener(
  467. JitsiTrackEvents.TRACK_MUTE_CHANGED,
  468. function () {
  469. emitter.emit(JitsiConferenceEvents.TRACK_MUTE_CHANGED, track);
  470. }
  471. );
  472. track.addEventListener(
  473. JitsiTrackEvents.TRACK_AUDIO_LEVEL_CHANGED,
  474. function (audioLevel) {
  475. emitter.emit(JitsiConferenceEvents.TRACK_AUDIO_LEVEL_CHANGED, id, audioLevel);
  476. }
  477. );
  478. this.eventEmitter.emit(JitsiConferenceEvents.TRACK_ADDED, track);
  479. };
  480. JitsiConference.prototype.updateDTMFSupport = function () {
  481. var somebodySupportsDTMF = false;
  482. var participants = this.getParticipants();
  483. // check if at least 1 participant supports DTMF
  484. for (var i = 0; i < participants.length; i += 1) {
  485. if (participants[i].supportsDTMF()) {
  486. somebodySupportsDTMF = true;
  487. break;
  488. }
  489. }
  490. if (somebodySupportsDTMF !== this.somebodySupportsDTMF) {
  491. this.somebodySupportsDTMF = somebodySupportsDTMF;
  492. this.eventEmitter.emit(JitsiConferenceEvents.DTMF_SUPPORT_CHANGED, somebodySupportsDTMF);
  493. }
  494. };
  495. /**
  496. * Allows to check if there is at least one user in the conference
  497. * that supports DTMF.
  498. * @returns {boolean} true if somebody supports DTMF, false otherwise
  499. */
  500. JitsiConference.prototype.isDTMFSupported = function () {
  501. return this.somebodySupportsDTMF;
  502. };
  503. /**
  504. * Returns the local user's ID
  505. * @return {string} local user's ID
  506. */
  507. JitsiConference.prototype.myUserId = function () {
  508. return (this.room && this.room.myroomjid)? Strophe.getResourceFromJid(this.room.myroomjid) : null;
  509. };
  510. JitsiConference.prototype.sendTones = function (tones, duration, pause) {
  511. if (!this.dtmfManager) {
  512. var connection = this.xmpp.connection.jingle.activecall.peerconnection;
  513. if (!connection) {
  514. logger.warn("cannot sendTones: no conneciton");
  515. return;
  516. }
  517. var tracks = this.getLocalTracks().filter(function (track) {
  518. return track.isAudioTrack();
  519. });
  520. if (!tracks.length) {
  521. logger.warn("cannot sendTones: no local audio stream");
  522. return;
  523. }
  524. this.dtmfManager = new JitsiDTMFManager(tracks[0], connection);
  525. }
  526. this.dtmfManager.sendTones(tones, duration, pause);
  527. };
  528. /**
  529. * Returns true if the recording is supproted and false if not.
  530. */
  531. JitsiConference.prototype.isRecordingSupported = function () {
  532. if(this.room)
  533. return this.room.isRecordingSupported();
  534. return false;
  535. };
  536. /**
  537. * Returns null if the recording is not supported, "on" if the recording started
  538. * and "off" if the recording is not started.
  539. */
  540. JitsiConference.prototype.getRecordingState = function () {
  541. if(this.room)
  542. return this.room.getRecordingState();
  543. return "off";
  544. }
  545. /**
  546. * Returns the url of the recorded video.
  547. */
  548. JitsiConference.prototype.getRecordingURL = function () {
  549. if(this.room)
  550. return this.room.getRecordingURL();
  551. return null;
  552. }
  553. /**
  554. * Starts/stops the recording
  555. */
  556. JitsiConference.prototype.toggleRecording = function (options) {
  557. if(this.room)
  558. return this.room.toggleRecording(options, function (status, error) {
  559. this.eventEmitter.emit(
  560. JitsiConferenceEvents.RECORDING_STATE_CHANGED, status, error);
  561. }.bind(this));
  562. this.eventEmitter.emit(
  563. JitsiConferenceEvents.RECORDING_STATE_CHANGED, "error",
  564. new Error("The conference is not created yet!"));
  565. }
  566. /**
  567. * Returns true if the SIP calls are supported and false otherwise
  568. */
  569. JitsiConference.prototype.isSIPCallingSupported = function () {
  570. if(this.room)
  571. return this.room.isSIPCallingSupported();
  572. return false;
  573. }
  574. /**
  575. * Dials a number.
  576. * @param number the number
  577. */
  578. JitsiConference.prototype.dial = function (number) {
  579. if(this.room)
  580. return this.room.dial(number);
  581. return new Promise(function(resolve, reject){
  582. reject(new Error("The conference is not created yet!"))});
  583. }
  584. /**
  585. * Hangup an existing call
  586. */
  587. JitsiConference.prototype.hangup = function () {
  588. if(this.room)
  589. return this.room.hangup();
  590. return new Promise(function(resolve, reject){
  591. reject(new Error("The conference is not created yet!"))});
  592. }
  593. /**
  594. * Returns the phone number for joining the conference.
  595. */
  596. JitsiConference.prototype.getPhoneNumber = function () {
  597. if(this.room)
  598. return this.room.getPhoneNumber();
  599. return null;
  600. }
  601. /**
  602. * Returns the pin for joining the conference with phone.
  603. */
  604. JitsiConference.prototype.getPhonePin = function () {
  605. if(this.room)
  606. return this.room.getPhonePin();
  607. return null;
  608. }
  609. /**
  610. * Returns the connection state for the current room. Its ice connection state
  611. * for its session.
  612. */
  613. JitsiConference.prototype.getConnectionState = function () {
  614. if(this.room)
  615. return this.room.getConnectionState();
  616. return null;
  617. }
  618. /**
  619. * Make all new participants mute their audio/video on join.
  620. * @param policy {Object} object with 2 boolean properties for video and audio:
  621. * @param {boolean} audio if audio should be muted.
  622. * @param {boolean} video if video should be muted.
  623. */
  624. JitsiConference.prototype.setStartMutedPolicy = function (policy) {
  625. if (!this.isModerator()) {
  626. return;
  627. }
  628. this.startMutedPolicy = policy;
  629. this.room.removeFromPresence("startmuted");
  630. this.room.addToPresence("startmuted", {
  631. attributes: {
  632. audio: policy.audio,
  633. video: policy.video,
  634. xmlns: 'http://jitsi.org/jitmeet/start-muted'
  635. }
  636. });
  637. this.room.sendPresence();
  638. };
  639. /**
  640. * Returns current start muted policy
  641. * @returns {Object} with 2 proprties - audio and video.
  642. */
  643. JitsiConference.prototype.getStartMutedPolicy = function () {
  644. return this.startMutedPolicy;
  645. };
  646. /**
  647. * Check if audio is muted on join.
  648. */
  649. JitsiConference.prototype.isStartAudioMuted = function () {
  650. return this.startAudioMuted;
  651. };
  652. /**
  653. * Check if video is muted on join.
  654. */
  655. JitsiConference.prototype.isStartVideoMuted = function () {
  656. return this.startVideoMuted;
  657. };
  658. /**
  659. * Get object with internal logs.
  660. */
  661. JitsiConference.prototype.getLogs = function () {
  662. var data = this.xmpp.getJingleLog();
  663. var metadata = {};
  664. metadata.time = new Date();
  665. metadata.url = window.location.href;
  666. metadata.ua = navigator.userAgent;
  667. var log = this.xmpp.getXmppLog();
  668. if (log) {
  669. metadata.xmpp = log;
  670. }
  671. data.metadata = metadata;
  672. return data;
  673. };
  674. /**
  675. * Sends the given feedback through CallStats if enabled.
  676. *
  677. * @param overallFeedback an integer between 1 and 5 indicating the
  678. * user feedback
  679. * @param detailedFeedback detailed feedback from the user. Not yet used
  680. */
  681. JitsiConference.prototype.sendFeedback =
  682. function(overallFeedback, detailedFeedback){
  683. this.statistics.sendFeedback(overallFeedback, detailedFeedback);
  684. }
  685. /**
  686. * Returns true if the callstats integration is enabled, otherwise returns
  687. * false.
  688. *
  689. * @returns true if the callstats integration is enabled, otherwise returns
  690. * false.
  691. */
  692. JitsiConference.prototype.isCallstatsEnabled = function () {
  693. return this.statistics.isCallstatsEnabled();
  694. }
  695. /**
  696. * Setups the listeners needed for the conference.
  697. * @param conference the conference
  698. */
  699. function setupListeners(conference) {
  700. conference.xmpp.addListener(XMPPEvents.CALL_INCOMING, function (event) {
  701. conference.rtc.onIncommingCall(event);
  702. conference.statistics.startRemoteStats(event.peerconnection);
  703. });
  704. conference.room.addListener(XMPPEvents.REMOTE_STREAM_RECEIVED,
  705. function (data, sid, thessrc) {
  706. var track = conference.rtc.createRemoteStream(data, sid, thessrc);
  707. if (track) {
  708. conference.onTrackAdded(track);
  709. }
  710. }
  711. );
  712. conference.rtc.addListener(RTCEvents.FAKE_VIDEO_TRACK_CREATED,
  713. function (track) {
  714. conference.onTrackAdded(track);
  715. }
  716. );
  717. conference.room.addListener(XMPPEvents.AUDIO_MUTED_BY_FOCUS,
  718. function (value) {
  719. conference.rtc.setAudioMute(value);
  720. }
  721. );
  722. conference.room.addListener(XMPPEvents.SUBJECT_CHANGED, function (subject) {
  723. conference.eventEmitter.emit(JitsiConferenceEvents.SUBJECT_CHANGED, subject);
  724. });
  725. conference.room.addListener(XMPPEvents.MUC_JOINED, function () {
  726. conference.eventEmitter.emit(JitsiConferenceEvents.CONFERENCE_JOINED);
  727. });
  728. conference.room.addListener(XMPPEvents.ROOM_JOIN_ERROR, function (pres) {
  729. conference.eventEmitter.emit(JitsiConferenceEvents.CONFERENCE_FAILED, JitsiConferenceErrors.CONNECTION_ERROR, pres);
  730. });
  731. conference.room.addListener(XMPPEvents.ROOM_CONNECT_ERROR, function (pres) {
  732. conference.eventEmitter.emit(JitsiConferenceEvents.CONFERENCE_FAILED, JitsiConferenceErrors.CONNECTION_ERROR, pres);
  733. });
  734. conference.room.addListener(XMPPEvents.PASSWORD_REQUIRED, function (pres) {
  735. conference.eventEmitter.emit(JitsiConferenceEvents.CONFERENCE_FAILED, JitsiConferenceErrors.PASSWORD_REQUIRED, pres);
  736. });
  737. conference.room.addListener(XMPPEvents.AUTHENTICATION_REQUIRED, function () {
  738. conference.eventEmitter.emit(JitsiConferenceEvents.CONFERENCE_FAILED, JitsiConferenceErrors.AUTHENTICATION_REQUIRED);
  739. });
  740. conference.room.addListener(XMPPEvents.BRIDGE_DOWN, function () {
  741. conference.eventEmitter.emit(JitsiConferenceEvents.CONFERENCE_FAILED, JitsiConferenceErrors.VIDEOBRIDGE_NOT_AVAILABLE);
  742. });
  743. conference.room.addListener(XMPPEvents.RESERVATION_ERROR, function (code, msg) {
  744. conference.eventEmitter.emit(JitsiConferenceEvents.CONFERENCE_FAILED, JitsiConferenceErrors.RESERVATION_ERROR, code, msg);
  745. });
  746. conference.room.addListener(XMPPEvents.GRACEFUL_SHUTDOWN, function () {
  747. conference.eventEmitter.emit(JitsiConferenceEvents.CONFERENCE_FAILED, JitsiConferenceErrors.GRACEFUL_SHUTDOWN);
  748. });
  749. conference.room.addListener(XMPPEvents.JINGLE_FATAL_ERROR, function () {
  750. conference.eventEmitter.emit(JitsiConferenceEvents.CONFERENCE_FAILED, JitsiConferenceErrors.JINGLE_FATAL_ERROR);
  751. });
  752. conference.room.addListener(XMPPEvents.MUC_DESTROYED, function (reason) {
  753. conference.eventEmitter.emit(JitsiConferenceEvents.CONFERENCE_FAILED, JitsiConferenceErrors.CONFERENCE_DESTROYED, reason);
  754. });
  755. conference.room.addListener(XMPPEvents.CHAT_ERROR_RECEIVED, function (err, msg) {
  756. conference.eventEmitter.emit(JitsiConferenceEvents.CONFERENCE_ERROR, JitsiConferenceErrors.CHAT_ERROR, err, msg);
  757. });
  758. conference.room.addListener(XMPPEvents.FOCUS_DISCONNECTED, function (focus, retrySec) {
  759. conference.eventEmitter.emit(JitsiConferenceEvents.CONFERENCE_FAILED, JitsiConferenceErrors.FOCUS_DISCONNECTED, focus, retrySec);
  760. });
  761. // FIXME
  762. // conference.room.addListener(XMPPEvents.MUC_JOINED, function () {
  763. // conference.eventEmitter.emit(JitsiConferenceEvents.CONFERENCE_LEFT);
  764. // });
  765. conference.room.addListener(XMPPEvents.KICKED, function () {
  766. conference.eventEmitter.emit(JitsiConferenceEvents.KICKED);
  767. });
  768. conference.room.addListener(XMPPEvents.MUC_MEMBER_JOINED, conference.onMemberJoined.bind(conference));
  769. conference.room.addListener(XMPPEvents.MUC_MEMBER_LEFT, conference.onMemberLeft.bind(conference));
  770. conference.room.addListener(XMPPEvents.DISPLAY_NAME_CHANGED, conference.onDisplayNameChanged.bind(conference));
  771. conference.room.addListener(XMPPEvents.LOCAL_ROLE_CHANGED, function (role) {
  772. conference.eventEmitter.emit(JitsiConferenceEvents.USER_ROLE_CHANGED, conference.myUserId(), role);
  773. });
  774. conference.room.addListener(XMPPEvents.MUC_ROLE_CHANGED, conference.onUserRoleChanged.bind(conference));
  775. conference.room.addListener(XMPPEvents.CONNECTION_INTERRUPTED, function () {
  776. conference.eventEmitter.emit(JitsiConferenceEvents.CONNECTION_INTERRUPTED);
  777. });
  778. conference.room.addListener(XMPPEvents.RECORDING_STATE_CHANGED,
  779. function () {
  780. conference.eventEmitter.emit(
  781. JitsiConferenceEvents.RECORDING_STATE_CHANGED);
  782. });
  783. conference.room.addListener(XMPPEvents.PHONE_NUMBER_CHANGED, function () {
  784. conference.eventEmitter.emit(
  785. JitsiConferenceEvents.PHONE_NUMBER_CHANGED);
  786. });
  787. conference.room.addListener(XMPPEvents.CONNECTION_RESTORED, function () {
  788. conference.eventEmitter.emit(JitsiConferenceEvents.CONNECTION_RESTORED);
  789. });
  790. conference.room.addListener(XMPPEvents.CONFERENCE_SETUP_FAILED, function () {
  791. conference.eventEmitter.emit(JitsiConferenceEvents.CONFERENCE_FAILED, JitsiConferenceErrors.SETUP_FAILED);
  792. });
  793. conference.room.addListener(AuthenticationEvents.IDENTITY_UPDATED, function (authEnabled, authIdentity) {
  794. conference.authEnabled = authEnabled;
  795. conference.authIdentity = authIdentity;
  796. });
  797. conference.room.addListener(XMPPEvents.MESSAGE_RECEIVED, function (jid, displayName, txt, myJid, ts) {
  798. var id = Strophe.getResourceFromJid(jid);
  799. conference.eventEmitter.emit(JitsiConferenceEvents.MESSAGE_RECEIVED, id, txt, ts);
  800. });
  801. conference.room.addListener(XMPPEvents.PRESENCE_STATUS, function (jid, status) {
  802. var id = Strophe.getResourceFromJid(jid);
  803. var participant = conference.getParticipantById(id);
  804. if (!participant || participant._status === status) {
  805. return;
  806. }
  807. participant._status = status;
  808. conference.eventEmitter.emit(JitsiConferenceEvents.USER_STATUS_CHANGED, id, status);
  809. });
  810. conference.rtc.addListener(RTCEvents.DOMINANTSPEAKER_CHANGED, function (id) {
  811. if(conference.lastDominantSpeaker !== id && conference.room) {
  812. conference.lastDominantSpeaker = id;
  813. conference.eventEmitter.emit(JitsiConferenceEvents.DOMINANT_SPEAKER_CHANGED, id);
  814. }
  815. });
  816. conference.rtc.addListener(RTCEvents.LASTN_CHANGED, function (oldValue, newValue) {
  817. conference.eventEmitter.emit(JitsiConferenceEvents.IN_LAST_N_CHANGED, oldValue, newValue);
  818. });
  819. conference.rtc.addListener(RTCEvents.LASTN_ENDPOINT_CHANGED,
  820. function (lastNEndpoints, endpointsEnteringLastN) {
  821. conference.eventEmitter.emit(JitsiConferenceEvents.LAST_N_ENDPOINTS_CHANGED,
  822. lastNEndpoints, endpointsEnteringLastN);
  823. });
  824. conference.xmpp.addListener(XMPPEvents.PASSWORD_REQUIRED, function () {
  825. conference.eventEmitter.emit(JitsiConferenceErrors.PASSWORD_REQUIRED);
  826. });
  827. conference.xmpp.addListener(XMPPEvents.START_MUTED_FROM_FOCUS,
  828. function (audioMuted, videoMuted) {
  829. conference.startAudioMuted = audioMuted;
  830. conference.startVideoMuted = videoMuted;
  831. // mute existing local tracks because this is initial mute from
  832. // Jicofo
  833. conference.getLocalTracks().forEach(function (track) {
  834. if (conference.startAudioMuted && track.isAudioTrack()) {
  835. track.mute();
  836. }
  837. if (conference.startVideoMuted && track.isVideoTrack()) {
  838. track.mute();
  839. }
  840. });
  841. conference.eventEmitter.emit(JitsiConferenceEvents.STARTED_MUTED);
  842. });
  843. conference.room.addPresenceListener("startmuted", function (data, from) {
  844. var isModerator = false;
  845. if (conference.myUserId() === from && conference.isModerator()) {
  846. isModerator = true;
  847. } else {
  848. var participant = conference.getParticipantById(from);
  849. if (participant && participant.isModerator()) {
  850. isModerator = true;
  851. }
  852. }
  853. if (!isModerator) {
  854. return;
  855. }
  856. var startAudioMuted = data.attributes.audio === 'true';
  857. var startVideoMuted = data.attributes.video === 'true';
  858. var updated = false;
  859. if (startAudioMuted !== conference.startMutedPolicy.audio) {
  860. conference.startMutedPolicy.audio = startAudioMuted;
  861. updated = true;
  862. }
  863. if (startVideoMuted !== conference.startMutedPolicy.video) {
  864. conference.startMutedPolicy.video = startVideoMuted;
  865. updated = true;
  866. }
  867. if (updated) {
  868. conference.eventEmitter.emit(
  869. JitsiConferenceEvents.START_MUTED_POLICY_CHANGED,
  870. conference.startMutedPolicy
  871. );
  872. }
  873. });
  874. conference.rtc.addListener(RTCEvents.AVAILABLE_DEVICES_CHANGED, function (devices) {
  875. conference.room.updateDeviceAvailability(devices);
  876. });
  877. conference.room.addPresenceListener("devices", function (data, from) {
  878. var isAudioAvailable = false;
  879. var isVideoAvailable = false;
  880. data.children.forEach(function (config) {
  881. if (config.tagName === 'audio') {
  882. isAudioAvailable = config.value === 'true';
  883. }
  884. if (config.tagName === 'video') {
  885. isVideoAvailable = config.value === 'true';
  886. }
  887. });
  888. var availableDevices;
  889. if (conference.myUserId() === from) {
  890. availableDevices = conference.availableDevices;
  891. } else {
  892. var participant = conference.getParticipantById(from);
  893. if (!participant) {
  894. return;
  895. }
  896. availableDevices = participant._availableDevices;
  897. }
  898. var updated = false;
  899. if (availableDevices.audio !== isAudioAvailable) {
  900. updated = true;
  901. availableDevices.audio = isAudioAvailable;
  902. }
  903. if (availableDevices.video !== isVideoAvailable) {
  904. updated = true;
  905. availableDevices.video = isVideoAvailable;
  906. }
  907. if (updated) {
  908. conference.eventEmitter.emit(JitsiConferenceEvents.AVAILABLE_DEVICES_CHANGED, from, availableDevices);
  909. }
  910. });
  911. if(conference.statistics) {
  912. //FIXME: Maybe remove event should not be associated with the conference.
  913. conference.statistics.addAudioLevelListener(function (ssrc, level) {
  914. var userId = null;
  915. var jid = conference.room.getJidBySSRC(ssrc);
  916. if (!jid)
  917. return;
  918. conference.rtc.setAudioLevel(jid, level);
  919. });
  920. conference.xmpp.addListener(XMPPEvents.DISPOSE_CONFERENCE,
  921. function () {
  922. conference.statistics.dispose();
  923. });
  924. conference.room.addListener(XMPPEvents.PEERCONNECTION_READY,
  925. function (session) {
  926. conference.statistics.startCallStats(
  927. session, conference.settings);
  928. });
  929. conference.room.addListener(XMPPEvents.CONFERENCE_SETUP_FAILED,
  930. function () {
  931. conference.statistics.sendSetupFailedEvent();
  932. });
  933. conference.on(JitsiConferenceEvents.TRACK_MUTE_CHANGED,
  934. function (track) {
  935. if(!track.isLocal())
  936. return;
  937. var type = (track.getType() === "audio")? "audio" : "video";
  938. conference.statistics.sendMuteEvent(track.isMuted(), type);
  939. });
  940. conference.room.addListener(XMPPEvents.CREATE_OFFER_FAILED, function (e, pc) {
  941. conference.statistics.sendCreateOfferFailed(e, pc);
  942. });
  943. conference.room.addListener(XMPPEvents.CREATE_ANSWER_FAILED, function (e, pc) {
  944. conference.statistics.sendCreateAnswerFailed(e, pc);
  945. });
  946. conference.room.addListener(XMPPEvents.SET_LOCAL_DESCRIPTION_FAILED,
  947. function (e, pc) {
  948. conference.statistics.sendSetLocalDescFailed(e, pc);
  949. }
  950. );
  951. conference.room.addListener(XMPPEvents.SET_REMOTE_DESCRIPTION_FAILED,
  952. function (e, pc) {
  953. conference.statistics.sendSetRemoteDescFailed(e, pc);
  954. }
  955. );
  956. conference.room.addListener(XMPPEvents.ADD_ICE_CANDIDATE_FAILED,
  957. function (e, pc) {
  958. conference.statistics.sendAddIceCandidateFailed(e, pc);
  959. }
  960. );
  961. }
  962. }
  963. module.exports = JitsiConference;