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.

UI.js 21KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697
  1. var UI = {};
  2. var VideoLayout = require("./videolayout/VideoLayout.js");
  3. var AudioLevels = require("./audio_levels/AudioLevels.js");
  4. var Prezi = require("./prezi/Prezi.js");
  5. var Etherpad = require("./etherpad/Etherpad.js");
  6. var Chat = require("./side_pannels/chat/Chat.js");
  7. var Toolbar = require("./toolbars/Toolbar");
  8. var ToolbarToggler = require("./toolbars/ToolbarToggler");
  9. var BottomToolbar = require("./toolbars/BottomToolbar");
  10. var ContactList = require("./side_pannels/contactlist/ContactList");
  11. var Avatar = require("./avatar/Avatar");
  12. var EventEmitter = require("events");
  13. var SettingsMenu = require("./side_pannels/settings/SettingsMenu");
  14. var Settings = require("./side_pannels/settings/Settings");
  15. var PanelToggler = require("./side_pannels/SidePanelToggler");
  16. var RoomNameGenerator = require("./welcome_page/RoomnameGenerator");
  17. UI.messageHandler = require("./util/MessageHandler");
  18. var messageHandler = UI.messageHandler;
  19. var Authentication = require("./authentication/Authentication");
  20. var UIUtil = require("./util/UIUtil");
  21. var NicknameHandler = require("./util/NicknameHandler");
  22. var eventEmitter = new EventEmitter();
  23. var roomName = null;
  24. function setupPrezi()
  25. {
  26. $("#reloadPresentationLink").click(function()
  27. {
  28. Prezi.reloadPresentation();
  29. });
  30. }
  31. function setupChat()
  32. {
  33. Chat.init();
  34. $("#toggle_smileys").click(function() {
  35. Chat.toggleSmileys();
  36. });
  37. }
  38. function setupToolbars() {
  39. Toolbar.init(UI);
  40. Toolbar.setupButtonsFromConfig();
  41. BottomToolbar.init();
  42. }
  43. function streamHandler(stream) {
  44. switch (stream.type)
  45. {
  46. case "audio":
  47. VideoLayout.changeLocalAudio(stream);
  48. break;
  49. case "video":
  50. VideoLayout.changeLocalVideo(stream);
  51. break;
  52. case "stream":
  53. VideoLayout.changeLocalStream(stream);
  54. break;
  55. }
  56. }
  57. function onDisposeConference(unload) {
  58. Toolbar.showAuthenticateButton(false);
  59. };
  60. function onDisplayNameChanged(jid, displayName) {
  61. ContactList.onDisplayNameChange(jid, displayName);
  62. SettingsMenu.onDisplayNameChange(jid, displayName);
  63. VideoLayout.onDisplayNameChanged(jid, displayName);
  64. }
  65. function registerListeners() {
  66. RTC.addStreamListener(streamHandler, StreamEventTypes.EVENT_TYPE_LOCAL_CREATED);
  67. RTC.addStreamListener(streamHandler, StreamEventTypes.EVENT_TYPE_LOCAL_CHANGED);
  68. RTC.addStreamListener(function (stream) {
  69. VideoLayout.onRemoteStreamAdded(stream);
  70. }, StreamEventTypes.EVENT_TYPE_REMOTE_CREATED);
  71. VideoLayout.init();
  72. statistics.addAudioLevelListener(function(jid, audioLevel)
  73. {
  74. var resourceJid;
  75. if(jid === statistics.LOCAL_JID)
  76. {
  77. resourceJid = AudioLevels.LOCAL_LEVEL;
  78. if(RTC.localAudio.isMuted())
  79. {
  80. audioLevel = 0;
  81. }
  82. }
  83. else
  84. {
  85. resourceJid = Strophe.getResourceFromJid(jid);
  86. }
  87. AudioLevels.updateAudioLevel(resourceJid, audioLevel,
  88. UI.getLargeVideoState().userResourceJid);
  89. });
  90. desktopsharing.addListener(function () {
  91. ToolbarToggler.showDesktopSharingButton();
  92. }, DesktopSharingEventTypes.INIT);
  93. desktopsharing.addListener(
  94. Toolbar.changeDesktopSharingButtonState,
  95. DesktopSharingEventTypes.SWITCHING_DONE);
  96. xmpp.addListener(XMPPEvents.DISPOSE_CONFERENCE, onDisposeConference);
  97. xmpp.addListener(XMPPEvents.KICKED, function () {
  98. messageHandler.openMessageDialog("Session Terminated",
  99. "Ouch! You have been kicked out of the meet!");
  100. });
  101. xmpp.addListener(XMPPEvents.BRIDGE_DOWN, function () {
  102. messageHandler.showError("Error",
  103. "Jitsi Videobridge is currently unavailable. Please try again later!");
  104. });
  105. xmpp.addListener(XMPPEvents.USER_ID_CHANGED, Avatar.setUserAvatar);
  106. xmpp.addListener(XMPPEvents.CHANGED_STREAMS, function (jid, changedStreams) {
  107. for(stream in changedStreams)
  108. {
  109. // might need to update the direction if participant just went from sendrecv to recvonly
  110. if (stream.type === 'video' || stream.type === 'screen') {
  111. var el = $('#participant_' + Strophe.getResourceFromJid(jid) + '>video');
  112. switch (stream.direction) {
  113. case 'sendrecv':
  114. el.show();
  115. break;
  116. case 'recvonly':
  117. el.hide();
  118. // FIXME: Check if we have to change large video
  119. //VideoLayout.updateLargeVideo(el);
  120. break;
  121. }
  122. }
  123. }
  124. });
  125. xmpp.addListener(XMPPEvents.DISPLAY_NAME_CHANGED, onDisplayNameChanged);
  126. xmpp.addListener(XMPPEvents.MUC_JOINED, onMucJoined);
  127. xmpp.addListener(XMPPEvents.LOCALROLE_CHANGED, onLocalRoleChange);
  128. xmpp.addListener(XMPPEvents.MUC_ENTER, onMucEntered);
  129. xmpp.addListener(XMPPEvents.MUC_ROLE_CHANGED, onMucRoleChanged);
  130. xmpp.addListener(XMPPEvents.PRESENCE_STATUS, onMucPresenceStatus);
  131. xmpp.addListener(XMPPEvents.SUBJECT_CHANGED, chatSetSubject);
  132. xmpp.addListener(XMPPEvents.MESSAGE_RECEIVED, updateChatConversation);
  133. xmpp.addListener(XMPPEvents.MUC_LEFT, onMucLeft);
  134. xmpp.addListener(XMPPEvents.PASSWORD_REQUIRED, onPasswordReqiured);
  135. xmpp.addListener(XMPPEvents.CHAT_ERROR_RECEIVED, chatAddError);
  136. xmpp.addListener(XMPPEvents.ETHERPAD, initEtherpad);
  137. connectionquality.addListener(CQEvents.LOCALSTATS_UPDATED,
  138. VideoLayout.updateLocalConnectionStats);
  139. connectionquality.addListener(CQEvents.REMOTESTATS_UPDATED,
  140. VideoLayout.updateConnectionStats);
  141. connectionquality.addListener(CQEvents.STOP,
  142. VideoLayout.onStatsStop);
  143. }
  144. function bindEvents()
  145. {
  146. /**
  147. * Resizes and repositions videos in full screen mode.
  148. */
  149. $(document).on('webkitfullscreenchange mozfullscreenchange fullscreenchange',
  150. function () {
  151. VideoLayout.resizeLargeVideoContainer();
  152. VideoLayout.positionLarge();
  153. }
  154. );
  155. $(window).resize(function () {
  156. VideoLayout.resizeLargeVideoContainer();
  157. VideoLayout.positionLarge();
  158. });
  159. }
  160. UI.start = function () {
  161. document.title = interfaceConfig.APP_NAME;
  162. if(config.enableWelcomePage && window.location.pathname == "/" &&
  163. (!window.localStorage.welcomePageDisabled || window.localStorage.welcomePageDisabled == "false"))
  164. {
  165. $("#videoconference_page").hide();
  166. var setupWelcomePage = require("./welcome_page/WelcomePage");
  167. setupWelcomePage();
  168. return;
  169. }
  170. if (interfaceConfig.SHOW_JITSI_WATERMARK) {
  171. var leftWatermarkDiv
  172. = $("#largeVideoContainer div[class='watermark leftwatermark']");
  173. leftWatermarkDiv.css({display: 'block'});
  174. leftWatermarkDiv.parent().get(0).href
  175. = interfaceConfig.JITSI_WATERMARK_LINK;
  176. }
  177. if (interfaceConfig.SHOW_BRAND_WATERMARK) {
  178. var rightWatermarkDiv
  179. = $("#largeVideoContainer div[class='watermark rightwatermark']");
  180. rightWatermarkDiv.css({display: 'block'});
  181. rightWatermarkDiv.parent().get(0).href
  182. = interfaceConfig.BRAND_WATERMARK_LINK;
  183. rightWatermarkDiv.get(0).style.backgroundImage
  184. = "url(images/rightwatermark.png)";
  185. }
  186. if (interfaceConfig.SHOW_POWERED_BY) {
  187. $("#largeVideoContainer>a[class='poweredby']").css({display: 'block'});
  188. }
  189. $("#welcome_page").hide();
  190. VideoLayout.resizeLargeVideoContainer();
  191. $("#videospace").mousemove(function () {
  192. return ToolbarToggler.showToolbar();
  193. });
  194. // Set the defaults for prompt dialogs.
  195. jQuery.prompt.setDefaults({persistent: false});
  196. NicknameHandler.init(eventEmitter);
  197. registerListeners();
  198. bindEvents();
  199. setupPrezi();
  200. setupToolbars();
  201. setupChat();
  202. document.title = interfaceConfig.APP_NAME;
  203. $("#downloadlog").click(function (event) {
  204. dump(event.target);
  205. });
  206. if(config.enableWelcomePage && window.location.pathname == "/" &&
  207. (!window.localStorage.welcomePageDisabled || window.localStorage.welcomePageDisabled == "false"))
  208. {
  209. $("#videoconference_page").hide();
  210. var setupWelcomePage = require("./welcome_page/WelcomePage");
  211. setupWelcomePage();
  212. return;
  213. }
  214. $("#welcome_page").hide();
  215. document.getElementById('largeVideo').volume = 0;
  216. if (!$('#settings').is(':visible')) {
  217. console.log('init');
  218. init();
  219. } else {
  220. loginInfo.onsubmit = function (e) {
  221. if (e.preventDefault) e.preventDefault();
  222. $('#settings').hide();
  223. init();
  224. };
  225. }
  226. toastr.options = {
  227. "closeButton": true,
  228. "debug": false,
  229. "positionClass": "notification-bottom-right",
  230. "onclick": null,
  231. "showDuration": "300",
  232. "hideDuration": "1000",
  233. "timeOut": "2000",
  234. "extendedTimeOut": "1000",
  235. "showEasing": "swing",
  236. "hideEasing": "linear",
  237. "showMethod": "fadeIn",
  238. "hideMethod": "fadeOut",
  239. "reposition": function() {
  240. if(PanelToggler.isVisible()) {
  241. $("#toast-container").addClass("notification-bottom-right-center");
  242. } else {
  243. $("#toast-container").removeClass("notification-bottom-right-center");
  244. }
  245. },
  246. "newestOnTop": false
  247. };
  248. $('#settingsmenu>input').keyup(function(event){
  249. if(event.keyCode === 13) {//enter
  250. SettingsMenu.update();
  251. }
  252. });
  253. $("#updateSettings").click(function () {
  254. SettingsMenu.update();
  255. });
  256. };
  257. UI.toggleSmileys = function () {
  258. Chat.toggleSmileys();
  259. };
  260. function chatAddError(errorMessage, originalText)
  261. {
  262. return Chat.chatAddError(errorMessage, originalText);
  263. };
  264. function chatSetSubject(text)
  265. {
  266. return Chat.chatSetSubject(text);
  267. };
  268. function updateChatConversation(from, displayName, message) {
  269. return Chat.updateChatConversation(from, displayName, message);
  270. };
  271. function onMucJoined(jid, info) {
  272. Toolbar.updateRoomUrl(window.location.href);
  273. document.getElementById('localNick').appendChild(
  274. document.createTextNode(Strophe.getResourceFromJid(jid) + ' (me)')
  275. );
  276. var settings = Settings.getSettings();
  277. // Add myself to the contact list.
  278. ContactList.addContact(jid, settings.email || settings.uid);
  279. // Once we've joined the muc show the toolbar
  280. ToolbarToggler.showToolbar();
  281. // Show authenticate button if needed
  282. Toolbar.showAuthenticateButton(
  283. xmpp.isExternalAuthEnabled() && !xmpp.isModerator());
  284. var displayName = !config.displayJids
  285. ? info.displayName : Strophe.getResourceFromJid(jid);
  286. if (displayName)
  287. onDisplayNameChanged('localVideoContainer', displayName + ' (me)');
  288. }
  289. function initEtherpad(name) {
  290. Etherpad.init(name);
  291. };
  292. function onMucLeft(jid) {
  293. console.log('left.muc', jid);
  294. var displayName = $('#participant_' + Strophe.getResourceFromJid(jid) +
  295. '>.displayname').html();
  296. messageHandler.notify(displayName || 'Somebody',
  297. 'disconnected',
  298. 'disconnected');
  299. // Need to call this with a slight delay, otherwise the element couldn't be
  300. // found for some reason.
  301. // XXX(gp) it works fine without the timeout for me (with Chrome 38).
  302. window.setTimeout(function () {
  303. var container = document.getElementById(
  304. 'participant_' + Strophe.getResourceFromJid(jid));
  305. if (container) {
  306. ContactList.removeContact(jid);
  307. VideoLayout.removeConnectionIndicator(jid);
  308. // hide here, wait for video to close before removing
  309. $(container).hide();
  310. VideoLayout.resizeThumbnails();
  311. }
  312. }, 10);
  313. VideoLayout.participantLeft(jid);
  314. };
  315. UI.getSettings = function () {
  316. return Settings.getSettings();
  317. };
  318. UI.toggleFilmStrip = function () {
  319. return BottomToolbar.toggleFilmStrip();
  320. };
  321. UI.toggleChat = function () {
  322. return BottomToolbar.toggleChat();
  323. };
  324. UI.toggleContactList = function () {
  325. return BottomToolbar.toggleContactList();
  326. };
  327. function onLocalRoleChange(jid, info, pres, isModerator, isExternalAuthEnabled)
  328. {
  329. console.info("My role changed, new role: " + info.role);
  330. onModeratorStatusChanged(isModerator);
  331. VideoLayout.showModeratorIndicator();
  332. Toolbar.showAuthenticateButton(
  333. isExternalAuthEnabled && !isModerator);
  334. if (isModerator) {
  335. Authentication.closeAuthenticationWindow();
  336. messageHandler.notify(
  337. 'Me', 'connected', 'Moderator rights granted !');
  338. }
  339. }
  340. function onModeratorStatusChanged(isModerator) {
  341. Toolbar.showSipCallButton(isModerator);
  342. Toolbar.showRecordingButton(
  343. isModerator); //&&
  344. // FIXME:
  345. // Recording visible if
  346. // there are at least 2(+ 1 focus) participants
  347. //Object.keys(connection.emuc.members).length >= 3);
  348. if (isModerator && config.etherpad_base) {
  349. Etherpad.init();
  350. }
  351. };
  352. function onPasswordReqiured(callback) {
  353. // password is required
  354. Toolbar.lockLockButton();
  355. messageHandler.openTwoButtonDialog(null,
  356. '<h2>Password required</h2>' +
  357. '<input id="lockKey" type="text" placeholder="password" autofocus>',
  358. true,
  359. "Ok",
  360. function (e, v, m, f) {},
  361. function (event) {
  362. document.getElementById('lockKey').focus();
  363. },
  364. function (e, v, m, f) {
  365. if (v) {
  366. var lockKey = document.getElementById('lockKey');
  367. if (lockKey.value !== null) {
  368. Toolbar.setSharedKey(lockKey.value);
  369. callback(lockKey.value);
  370. }
  371. }
  372. }
  373. );
  374. }
  375. function onMucEntered(jid, id, displayName) {
  376. messageHandler.notify(displayName || 'Somebody',
  377. 'connected',
  378. 'connected');
  379. // Add Peer's container
  380. VideoLayout.ensurePeerContainerExists(jid,id);
  381. }
  382. function onMucPresenceStatus( jid, info) {
  383. VideoLayout.setPresenceStatus(
  384. 'participant_' + Strophe.getResourceFromJid(jid), info.status);
  385. }
  386. function onMucRoleChanged(role, displayName) {
  387. VideoLayout.showModeratorIndicator();
  388. if (role === 'moderator') {
  389. var displayName = displayName;
  390. if (!displayName) {
  391. displayName = 'Somebody';
  392. }
  393. messageHandler.notify(
  394. displayName,
  395. 'connected',
  396. 'Moderator rights granted to ' + displayName + '!');
  397. }
  398. }
  399. UI.onAuthenticationRequired = function (intervalCallback) {
  400. Authentication.openAuthenticationDialog(
  401. roomName, intervalCallback, function () {
  402. Toolbar.authenticateClicked();
  403. });
  404. };
  405. UI.setRecordingButtonState = function (state) {
  406. Toolbar.setRecordingButtonState(state);
  407. };
  408. UI.inputDisplayNameHandler = function (value) {
  409. VideoLayout.inputDisplayNameHandler(value);
  410. };
  411. UI.getLargeVideoState = function()
  412. {
  413. return VideoLayout.getLargeVideoState();
  414. };
  415. UI.showLocalAudioIndicator = function (mute) {
  416. VideoLayout.showLocalAudioIndicator(mute);
  417. };
  418. UI.generateRoomName = function() {
  419. if(roomName)
  420. return roomName;
  421. var roomnode = null;
  422. var path = window.location.pathname;
  423. // determinde the room node from the url
  424. // TODO: just the roomnode or the whole bare jid?
  425. if (config.getroomnode && typeof config.getroomnode === 'function') {
  426. // custom function might be responsible for doing the pushstate
  427. roomnode = config.getroomnode(path);
  428. } else {
  429. /* fall back to default strategy
  430. * this is making assumptions about how the URL->room mapping happens.
  431. * It currently assumes deployment at root, with a rewrite like the
  432. * following one (for nginx):
  433. location ~ ^/([a-zA-Z0-9]+)$ {
  434. rewrite ^/(.*)$ / break;
  435. }
  436. */
  437. if (path.length > 1) {
  438. roomnode = path.substr(1).toLowerCase();
  439. } else {
  440. var word = RoomNameGenerator.generateRoomWithoutSeparator();
  441. roomnode = word.toLowerCase();
  442. window.history.pushState('VideoChat',
  443. 'Room: ' + word, window.location.pathname + word);
  444. }
  445. }
  446. roomName = roomnode + '@' + config.hosts.muc;
  447. return roomName;
  448. };
  449. UI.connectionIndicatorShowMore = function(id)
  450. {
  451. return VideoLayout.connectionIndicators[id].showMore();
  452. };
  453. UI.showToolbar = function () {
  454. return ToolbarToggler.showToolbar();
  455. };
  456. UI.dockToolbar = function (isDock) {
  457. return ToolbarToggler.dockToolbar(isDock);
  458. };
  459. UI.getCreadentials = function () {
  460. return {
  461. bosh: document.getElementById('boshURL').value,
  462. password: document.getElementById('password').value,
  463. jid: document.getElementById('jid').value
  464. };
  465. };
  466. UI.disableConnect = function () {
  467. document.getElementById('connect').disabled = true;
  468. };
  469. UI.showLoginPopup = function(callback)
  470. {
  471. console.log('password is required');
  472. UI.messageHandler.openTwoButtonDialog(null,
  473. '<h2>Password required</h2>' +
  474. '<input id="passwordrequired.username" type="text" placeholder="user@domain.net" autofocus>' +
  475. '<input id="passwordrequired.password" type="password" placeholder="user password">',
  476. true,
  477. "Ok",
  478. function (e, v, m, f) {
  479. if (v) {
  480. var username = document.getElementById('passwordrequired.username');
  481. var password = document.getElementById('passwordrequired.password');
  482. if (username.value !== null && password.value != null) {
  483. callback(username.value, password.value);
  484. }
  485. }
  486. },
  487. function (event) {
  488. document.getElementById('passwordrequired.username').focus();
  489. }
  490. );
  491. }
  492. UI.checkForNicknameAndJoin = function () {
  493. Authentication.closeAuthenticationDialog();
  494. Authentication.stopInterval();
  495. var nick = null;
  496. if (config.useNicks) {
  497. nick = window.prompt('Your nickname (optional)');
  498. }
  499. xmpp.joinRoom(roomName, config.useNicks, nick);
  500. }
  501. function dump(elem, filename) {
  502. elem = elem.parentNode;
  503. elem.download = filename || 'meetlog.json';
  504. elem.href = 'data:application/json;charset=utf-8,\n';
  505. var data = xmpp.populateData();
  506. var metadata = {};
  507. metadata.time = new Date();
  508. metadata.url = window.location.href;
  509. metadata.ua = navigator.userAgent;
  510. var log = xmpp.getLogger();
  511. if (log) {
  512. metadata.xmpp = log;
  513. }
  514. data.metadata = metadata;
  515. elem.href += encodeURIComponent(JSON.stringify(data, null, ' '));
  516. return false;
  517. }
  518. UI.getRoomName = function () {
  519. return roomName;
  520. }
  521. /**
  522. * Mutes/unmutes the local video.
  523. *
  524. * @param mute <tt>true</tt> to mute the local video; otherwise, <tt>false</tt>
  525. * @param options an object which specifies optional arguments such as the
  526. * <tt>boolean</tt> key <tt>byUser</tt> with default value <tt>true</tt> which
  527. * specifies whether the method was initiated in response to a user command (in
  528. * contrast to an automatic decision taken by the application logic)
  529. */
  530. function setVideoMute(mute, options) {
  531. xmpp.setVideoMute(
  532. mute,
  533. function (mute) {
  534. var video = $('#video');
  535. var communicativeClass = "icon-camera";
  536. var muteClass = "icon-camera icon-camera-disabled";
  537. if (mute) {
  538. video.removeClass(communicativeClass);
  539. video.addClass(muteClass);
  540. } else {
  541. video.removeClass(muteClass);
  542. video.addClass(communicativeClass);
  543. }
  544. },
  545. options);
  546. }
  547. /**
  548. * Mutes/unmutes the local video.
  549. */
  550. UI.toggleVideo = function () {
  551. UIUtil.buttonClick("#video", "icon-camera icon-camera-disabled");
  552. setVideoMute(!RTC.localVideo.isMuted());
  553. };
  554. /**
  555. * Mutes / unmutes audio for the local participant.
  556. */
  557. UI.toggleAudio = function() {
  558. UI.setAudioMuted(!RTC.localAudio.isMuted());
  559. };
  560. /**
  561. * Sets muted audio state for the local participant.
  562. */
  563. UI.setAudioMuted = function (mute) {
  564. if(!xmpp.setAudioMute(mute, function () {
  565. UI.showLocalAudioIndicator(mute);
  566. UIUtil.buttonClick("#mute", "icon-microphone icon-mic-disabled");
  567. }))
  568. {
  569. // We still click the button.
  570. UIUtil.buttonClick("#mute", "icon-microphone icon-mic-disabled");
  571. return;
  572. }
  573. }
  574. UI.onLastNChanged = function (oldValue, newValue) {
  575. if (config.muteLocalVideoIfNotInLastN) {
  576. setVideoMute(!newValue, { 'byUser': false });
  577. }
  578. }
  579. UI.addListener = function (type, listener) {
  580. eventEmitter.on(type, listener);
  581. }
  582. UI.clickOnVideo = function (videoNumber) {
  583. var remoteVideos = $(".videocontainer:not(#mixedstream)");
  584. if (remoteVideos.length > videoNumber) {
  585. remoteVideos[videoNumber].click();
  586. }
  587. }
  588. module.exports = UI;