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 32KB

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