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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425
  1. /* global APP, JitsiMeetJS, $, config, interfaceConfig, toastr */
  2. /* jshint -W101 */
  3. var UI = {};
  4. import Chat from "./side_pannels/chat/Chat";
  5. import Toolbar from "./toolbars/Toolbar";
  6. import ToolbarToggler from "./toolbars/ToolbarToggler";
  7. import BottomToolbar from "./toolbars/BottomToolbar";
  8. import ContactList from "./side_pannels/contactlist/ContactList";
  9. import Avatar from "./avatar/Avatar";
  10. import PanelToggler from "./side_pannels/SidePanelToggler";
  11. import UIUtil from "./util/UIUtil";
  12. import UIEvents from "../../service/UI/UIEvents";
  13. import CQEvents from '../../service/connectionquality/CQEvents';
  14. import EtherpadManager from './etherpad/Etherpad';
  15. import SharedVideoManager from './shared_video/SharedVideo';
  16. import Recording from "./recording/Recording";
  17. import VideoLayout from "./videolayout/VideoLayout";
  18. import FilmStrip from "./videolayout/FilmStrip";
  19. import SettingsMenu from "./side_pannels/settings/SettingsMenu";
  20. import Settings from "./../settings/Settings";
  21. import { reload } from '../util/helpers';
  22. import RingOverlay from "./ring_overlay/RingOverlay";
  23. var EventEmitter = require("events");
  24. UI.messageHandler = require("./util/MessageHandler");
  25. var messageHandler = UI.messageHandler;
  26. var JitsiPopover = require("./util/JitsiPopover");
  27. var Feedback = require("./Feedback");
  28. import FollowMe from "../FollowMe";
  29. var eventEmitter = new EventEmitter();
  30. UI.eventEmitter = eventEmitter;
  31. let etherpadManager;
  32. let sharedVideoManager;
  33. let followMeHandler;
  34. let deviceErrorDialog;
  35. const TrackErrors = JitsiMeetJS.errors.track;
  36. const JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP = {
  37. microphone: {},
  38. camera: {}
  39. };
  40. JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.camera[TrackErrors.UNSUPPORTED_RESOLUTION]
  41. = "dialog.cameraUnsupportedResolutionError";
  42. JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.camera[TrackErrors.GENERAL]
  43. = "dialog.cameraUnknownError";
  44. JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.camera[TrackErrors.PERMISSION_DENIED]
  45. = "dialog.cameraPermissionDeniedError";
  46. JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.camera[TrackErrors.NOT_FOUND]
  47. = "dialog.cameraNotFoundError";
  48. JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.camera[TrackErrors.CONSTRAINT_FAILED]
  49. = "dialog.cameraConstraintFailedError";
  50. JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.microphone[TrackErrors.GENERAL]
  51. = "dialog.micUnknownError";
  52. JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.microphone[TrackErrors.PERMISSION_DENIED]
  53. = "dialog.micPermissionDeniedError";
  54. JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.microphone[TrackErrors.NOT_FOUND]
  55. = "dialog.micNotFoundError";
  56. JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.microphone[TrackErrors.CONSTRAINT_FAILED]
  57. = "dialog.micConstraintFailedError";
  58. /**
  59. * Prompt user for nickname.
  60. */
  61. function promptDisplayName() {
  62. let nickRequiredMsg
  63. = APP.translation.translateString("dialog.displayNameRequired");
  64. let defaultNickMsg = APP.translation.translateString("defaultNickname");
  65. let message = `
  66. <h2 data-i18n="dialog.displayNameRequired">${nickRequiredMsg}</h2>
  67. <input name="displayName" type="text"
  68. data-i18n="[placeholder]defaultNickname"
  69. placeholder="${defaultNickMsg}" autofocus>`;
  70. // Don't use a translation string, because we're too early in the call and
  71. // the translation may not be initialised.
  72. let buttons = {Ok:true};
  73. let dialog = messageHandler.openDialog(
  74. null,
  75. message,
  76. true,
  77. buttons,
  78. function (e, v, m, f) {
  79. e.preventDefault();
  80. if (v) {
  81. let displayName = f.displayName;
  82. if (displayName) {
  83. UI.inputDisplayNameHandler(displayName);
  84. dialog.close();
  85. return;
  86. }
  87. }
  88. },
  89. function () {
  90. let form = $.prompt.getPrompt();
  91. let input = form.find("input[name='displayName']");
  92. input.focus();
  93. let button = form.find("button");
  94. button.attr("disabled", "disabled");
  95. input.keyup(function () {
  96. if (input.val()) {
  97. button.removeAttr("disabled");
  98. } else {
  99. button.attr("disabled", "disabled");
  100. }
  101. });
  102. }
  103. );
  104. }
  105. /**
  106. * Initialize chat.
  107. */
  108. function setupChat() {
  109. Chat.init(eventEmitter);
  110. $("#toggle_smileys").click(function() {
  111. Chat.toggleSmileys();
  112. });
  113. }
  114. /**
  115. * Initialize toolbars.
  116. */
  117. function setupToolbars() {
  118. Toolbar.init(eventEmitter);
  119. BottomToolbar.setupListeners(eventEmitter);
  120. }
  121. /**
  122. * Toggles the application in and out of full screen mode
  123. * (a.k.a. presentation mode in Chrome).
  124. * @see https://developer.mozilla.org/en-US/docs/Web/API/Fullscreen_API
  125. */
  126. function toggleFullScreen () {
  127. // alternative standard method
  128. let isNotFullScreen = !document.fullscreenElement &&
  129. !document.mozFullScreenElement && // current working methods
  130. !document.webkitFullscreenElement &&
  131. !document.msFullscreenElement;
  132. if (isNotFullScreen) {
  133. if (document.documentElement.requestFullscreen) {
  134. document.documentElement.requestFullscreen();
  135. } else if (document.documentElement.msRequestFullscreen) {
  136. document.documentElement.msRequestFullscreen();
  137. } else if (document.documentElement.mozRequestFullScreen) {
  138. document.documentElement.mozRequestFullScreen();
  139. } else if (document.documentElement.webkitRequestFullscreen) {
  140. document.documentElement
  141. .webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT);
  142. }
  143. } else {
  144. if (document.exitFullscreen) {
  145. document.exitFullscreen();
  146. } else if (document.msExitFullscreen) {
  147. document.msExitFullscreen();
  148. } else if (document.mozCancelFullScreen) {
  149. document.mozCancelFullScreen();
  150. } else if (document.webkitExitFullscreen) {
  151. document.webkitExitFullscreen();
  152. }
  153. }
  154. }
  155. /**
  156. * Notify user that server has shut down.
  157. */
  158. UI.notifyGracefulShutdown = function () {
  159. messageHandler.openMessageDialog(
  160. 'dialog.serviceUnavailable',
  161. 'dialog.gracefulShutdown'
  162. );
  163. };
  164. /**
  165. * Notify user that reservation error happened.
  166. */
  167. UI.notifyReservationError = function (code, msg) {
  168. var title = APP.translation.generateTranslationHTML(
  169. "dialog.reservationError");
  170. var message = APP.translation.generateTranslationHTML(
  171. "dialog.reservationErrorMsg", {code: code, msg: msg});
  172. messageHandler.openDialog(
  173. title,
  174. message,
  175. true, {},
  176. function (event, value, message, formVals) {
  177. return false;
  178. }
  179. );
  180. };
  181. /**
  182. * Notify user that he has been kicked from the server.
  183. */
  184. UI.notifyKicked = function () {
  185. messageHandler.openMessageDialog("dialog.sessTerminated", "dialog.kickMessage");
  186. };
  187. /**
  188. * Notify user that conference was destroyed.
  189. * @param reason {string} the reason text
  190. */
  191. UI.notifyConferenceDestroyed = function (reason) {
  192. //FIXME: use Session Terminated from translation, but
  193. // 'reason' text comes from XMPP packet and is not translated
  194. var title = APP.translation.generateTranslationHTML("dialog.sessTerminated");
  195. messageHandler.openDialog(
  196. title, reason, true, {},
  197. function (event, value, message, formVals) {
  198. return false;
  199. }
  200. );
  201. };
  202. /**
  203. * Notify user that Jitsi Videobridge is not accessible.
  204. */
  205. UI.notifyBridgeDown = function () {
  206. messageHandler.showError("dialog.error", "dialog.bridgeUnavailable");
  207. };
  208. /**
  209. * Show chat error.
  210. * @param err the Error
  211. * @param msg
  212. */
  213. UI.showChatError = function (err, msg) {
  214. if (interfaceConfig.filmStripOnly) {
  215. return;
  216. }
  217. Chat.chatAddError(err, msg);
  218. };
  219. /**
  220. * Change nickname for the user.
  221. * @param {string} id user id
  222. * @param {string} displayName new nickname
  223. */
  224. UI.changeDisplayName = function (id, displayName) {
  225. ContactList.onDisplayNameChange(id, displayName);
  226. VideoLayout.onDisplayNameChanged(id, displayName);
  227. if (APP.conference.isLocalId(id) || id === 'localVideoContainer') {
  228. SettingsMenu.changeDisplayName(displayName);
  229. Chat.setChatConversationMode(!!displayName);
  230. }
  231. };
  232. /**
  233. * Sets the "raised hand" status for a participant.
  234. */
  235. UI.setRaisedHandStatus = (participant, raisedHandStatus) => {
  236. VideoLayout.setRaisedHandStatus(participant.getId(), raisedHandStatus);
  237. if (raisedHandStatus) {
  238. messageHandler.notify(participant.getDisplayName(), 'notify.somebody',
  239. 'connected', 'notify.raisedHand');
  240. }
  241. };
  242. /**
  243. * Sets the local "raised hand" status.
  244. */
  245. UI.setLocalRaisedHandStatus = (raisedHandStatus) => {
  246. VideoLayout.setRaisedHandStatus(APP.conference.localId, raisedHandStatus);
  247. };
  248. /**
  249. * Initialize conference UI.
  250. */
  251. UI.initConference = function () {
  252. let id = APP.conference.localId;
  253. Toolbar.updateRoomUrl(window.location.href);
  254. // Add myself to the contact list.
  255. ContactList.addContact(id);
  256. //update default button states before showing the toolbar
  257. //if local role changes buttons state will be again updated
  258. UI.updateLocalRole(false);
  259. // Once we've joined the muc show the toolbar
  260. ToolbarToggler.showToolbar();
  261. let displayName = config.displayJids ? id : Settings.getDisplayName();
  262. if (displayName) {
  263. UI.changeDisplayName('localVideoContainer', displayName);
  264. }
  265. // Make sure we configure our avatar id, before creating avatar for us
  266. UI.setUserEmail(id, Settings.getEmail());
  267. Toolbar.checkAutoEnableDesktopSharing();
  268. if(!interfaceConfig.filmStripOnly) {
  269. Feedback.init(eventEmitter);
  270. }
  271. // FollowMe attempts to copy certain aspects of the moderator's UI into the
  272. // other participants' UI. Consequently, it needs (1) read and write access
  273. // to the UI (depending on the moderator role of the local participant) and
  274. // (2) APP.conference as means of communication between the participants.
  275. followMeHandler = new FollowMe(APP.conference, UI);
  276. };
  277. UI.mucJoined = function () {
  278. VideoLayout.mucJoined();
  279. };
  280. /**
  281. * Setup some UI event listeners.
  282. */
  283. function registerListeners() {
  284. UI.addListener(UIEvents.ETHERPAD_CLICKED, function () {
  285. if (etherpadManager) {
  286. etherpadManager.toggleEtherpad();
  287. }
  288. });
  289. UI.addListener(UIEvents.SHARED_VIDEO_CLICKED, function () {
  290. if (sharedVideoManager) {
  291. sharedVideoManager.toggleSharedVideo();
  292. }
  293. });
  294. UI.addListener(UIEvents.FULLSCREEN_TOGGLE, toggleFullScreen);
  295. UI.addListener(UIEvents.TOGGLE_CHAT, UI.toggleChat);
  296. UI.addListener(UIEvents.TOGGLE_SETTINGS, function () {
  297. PanelToggler.toggleSettingsMenu();
  298. });
  299. UI.addListener(UIEvents.TOGGLE_CONTACT_LIST, UI.toggleContactList);
  300. UI.addListener(UIEvents.TOGGLE_FILM_STRIP, function () {
  301. UI.toggleFilmStrip();
  302. VideoLayout.resizeVideoArea(PanelToggler.isVisible(), true, false);
  303. });
  304. UI.addListener(UIEvents.FOLLOW_ME_ENABLED, function (isEnabled) {
  305. if (followMeHandler)
  306. followMeHandler.enableFollowMe(isEnabled);
  307. });
  308. }
  309. /**
  310. * Setup some DOM event listeners.
  311. */
  312. function bindEvents() {
  313. function onResize() {
  314. PanelToggler.resizeChat();
  315. VideoLayout.resizeVideoArea(PanelToggler.isVisible());
  316. }
  317. // Resize and reposition videos in full screen mode.
  318. $(document).on(
  319. 'webkitfullscreenchange mozfullscreenchange fullscreenchange',
  320. onResize
  321. );
  322. $(window).resize(onResize);
  323. }
  324. /**
  325. * Returns the shared document manager object.
  326. * @return {EtherpadManager} the shared document manager object
  327. */
  328. UI.getSharedVideoManager = function () {
  329. return sharedVideoManager;
  330. };
  331. /**
  332. * Starts the UI module and initializes all related components.
  333. *
  334. * @returns {boolean} true if the UI is ready and the conference should be
  335. * esablished, false - otherwise (for example in the case of welcome page)
  336. */
  337. UI.start = function () {
  338. document.title = interfaceConfig.APP_NAME;
  339. var setupWelcomePage = null;
  340. if(config.enableWelcomePage && window.location.pathname == "/" &&
  341. Settings.isWelcomePageEnabled()) {
  342. $("#videoconference_page").hide();
  343. if (!setupWelcomePage)
  344. setupWelcomePage = require("./welcome_page/WelcomePage");
  345. setupWelcomePage();
  346. // Return false to indicate that the UI hasn't been fully started and
  347. // conference ready. We're still waiting for input from the user.
  348. return false;
  349. }
  350. $("#welcome_page").hide();
  351. // Set the defaults for prompt dialogs.
  352. $.prompt.setDefaults({persistent: false});
  353. registerListeners();
  354. ToolbarToggler.init();
  355. BottomToolbar.init();
  356. FilmStrip.init(eventEmitter);
  357. VideoLayout.init(eventEmitter);
  358. if (!interfaceConfig.filmStripOnly) {
  359. VideoLayout.initLargeVideo(PanelToggler.isVisible());
  360. }
  361. VideoLayout.resizeVideoArea(PanelToggler.isVisible(), true, true);
  362. ContactList.init(eventEmitter);
  363. bindEvents();
  364. sharedVideoManager = new SharedVideoManager(eventEmitter);
  365. if (!interfaceConfig.filmStripOnly) {
  366. $("#videospace").mousemove(function () {
  367. return ToolbarToggler.showToolbar();
  368. });
  369. setupToolbars();
  370. setupChat();
  371. // Initialise the recording module.
  372. if (config.enableRecording)
  373. Recording.init(eventEmitter, config.recordingType);
  374. // Display notice message at the top of the toolbar
  375. if (config.noticeMessage) {
  376. $('#noticeText').text(config.noticeMessage);
  377. $('#notice').css({display: 'block'});
  378. }
  379. $("#downloadlog").click(function (event) {
  380. let logs = APP.conference.getLogs();
  381. let data = encodeURIComponent(JSON.stringify(logs, null, ' '));
  382. let elem = event.target.parentNode;
  383. elem.download = 'meetlog.json';
  384. elem.href = 'data:application/json;charset=utf-8,\n' + data;
  385. });
  386. } else {
  387. $("#header").css("display", "none");
  388. $("#downloadlog").css("display", "none");
  389. BottomToolbar.hide();
  390. FilmStrip.setupFilmStripOnly();
  391. messageHandler.enableNotifications(false);
  392. $('body').popover("disable");
  393. JitsiPopover.enabled = false;
  394. }
  395. document.title = interfaceConfig.APP_NAME;
  396. if(config.requireDisplayName) {
  397. if (!APP.settings.getDisplayName()) {
  398. promptDisplayName();
  399. }
  400. }
  401. if (!interfaceConfig.filmStripOnly) {
  402. toastr.options = {
  403. "closeButton": true,
  404. "debug": false,
  405. "positionClass": "notification-bottom-right",
  406. "onclick": null,
  407. "showDuration": "300",
  408. "hideDuration": "1000",
  409. "timeOut": "2000",
  410. "extendedTimeOut": "1000",
  411. "showEasing": "swing",
  412. "hideEasing": "linear",
  413. "showMethod": "fadeIn",
  414. "hideMethod": "fadeOut",
  415. "reposition": function () {
  416. if (PanelToggler.isVisible()) {
  417. $("#toast-container").addClass("notification-bottom-right-center");
  418. } else {
  419. $("#toast-container").removeClass("notification-bottom-right-center");
  420. }
  421. },
  422. "newestOnTop": false
  423. };
  424. SettingsMenu.init(eventEmitter);
  425. }
  426. if(APP.tokenData.callee) {
  427. UI.showRingOverLay();
  428. }
  429. // Return true to indicate that the UI has been fully started and
  430. // conference ready.
  431. return true;
  432. };
  433. /**
  434. * Show local stream on UI.
  435. * @param {JitsiTrack} track stream to show
  436. */
  437. UI.addLocalStream = function (track) {
  438. switch (track.getType()) {
  439. case 'audio':
  440. VideoLayout.changeLocalAudio(track);
  441. break;
  442. case 'video':
  443. VideoLayout.changeLocalVideo(track);
  444. break;
  445. default:
  446. console.error("Unknown stream type: " + track.getType());
  447. break;
  448. }
  449. };
  450. /**
  451. * Show remote stream on UI.
  452. * @param {JitsiTrack} track stream to show
  453. */
  454. UI.addRemoteStream = function (track) {
  455. VideoLayout.onRemoteStreamAdded(track);
  456. };
  457. /**
  458. * Removed remote stream from UI.
  459. * @param {JitsiTrack} track stream to remove
  460. */
  461. UI.removeRemoteStream = function (track) {
  462. VideoLayout.onRemoteStreamRemoved(track);
  463. };
  464. function chatAddError(errorMessage, originalText) {
  465. return Chat.chatAddError(errorMessage, originalText);
  466. }
  467. /**
  468. * Update chat subject.
  469. * @param {string} subject new chat subject
  470. */
  471. UI.setSubject = function (subject) {
  472. Chat.setSubject(subject);
  473. };
  474. /**
  475. * Setup and show Etherpad.
  476. * @param {string} name etherpad id
  477. */
  478. UI.initEtherpad = function (name) {
  479. if (etherpadManager || !config.etherpad_base || !name) {
  480. return;
  481. }
  482. console.log('Etherpad is enabled');
  483. etherpadManager
  484. = new EtherpadManager(config.etherpad_base, name, eventEmitter);
  485. Toolbar.showEtherpadButton();
  486. };
  487. /**
  488. * Returns the shared document manager object.
  489. * @return {EtherpadManager} the shared document manager object
  490. */
  491. UI.getSharedDocumentManager = function () {
  492. return etherpadManager;
  493. };
  494. /**
  495. * Show user on UI.
  496. * @param {string} id user id
  497. * @param {string} displayName user nickname
  498. */
  499. UI.addUser = function (id, displayName) {
  500. UI.hideRingOverLay();
  501. ContactList.addContact(id);
  502. messageHandler.notify(
  503. displayName,'notify.somebody', 'connected', 'notify.connected'
  504. );
  505. if (!config.startAudioMuted ||
  506. config.startAudioMuted > APP.conference.membersCount)
  507. UIUtil.playSoundNotification('userJoined');
  508. // Add Peer's container
  509. VideoLayout.addParticipantContainer(id);
  510. // Configure avatar
  511. UI.setUserEmail(id);
  512. // set initial display name
  513. if(displayName)
  514. UI.changeDisplayName(id, displayName);
  515. };
  516. /**
  517. * Remove user from UI.
  518. * @param {string} id user id
  519. * @param {string} displayName user nickname
  520. */
  521. UI.removeUser = function (id, displayName) {
  522. ContactList.removeContact(id);
  523. messageHandler.notify(
  524. displayName,'notify.somebody', 'disconnected', 'notify.disconnected'
  525. );
  526. if (!config.startAudioMuted
  527. || config.startAudioMuted > APP.conference.membersCount) {
  528. UIUtil.playSoundNotification('userLeft');
  529. }
  530. VideoLayout.removeParticipantContainer(id);
  531. };
  532. UI.updateUserStatus = function (id, status) {
  533. VideoLayout.setPresenceStatus(id, status);
  534. };
  535. /**
  536. * Update videotype for specified user.
  537. * @param {string} id user id
  538. * @param {string} newVideoType new videotype
  539. */
  540. UI.onPeerVideoTypeChanged = (id, newVideoType) => {
  541. VideoLayout.onVideoTypeChanged(id, newVideoType);
  542. };
  543. /**
  544. * Update local user role and show notification if user is moderator.
  545. * @param {boolean} isModerator if local user is moderator or not
  546. */
  547. UI.updateLocalRole = function (isModerator) {
  548. VideoLayout.showModeratorIndicator();
  549. Toolbar.showSipCallButton(isModerator);
  550. Toolbar.showSharedVideoButton(isModerator);
  551. Recording.showRecordingButton(isModerator);
  552. SettingsMenu.showStartMutedOptions(isModerator);
  553. SettingsMenu.showFollowMeOptions(isModerator);
  554. if (isModerator) {
  555. messageHandler.notify(null, "notify.me", 'connected', "notify.moderator");
  556. Recording.checkAutoRecord();
  557. }
  558. };
  559. /**
  560. * Check the role for the user and reflect it in the UI, moderator ui indication
  561. * and notifies user who is the moderator
  562. * @param user to check for moderator
  563. */
  564. UI.updateUserRole = function (user) {
  565. VideoLayout.showModeratorIndicator();
  566. if (!user.isModerator()) {
  567. return;
  568. }
  569. var displayName = user.getDisplayName();
  570. if (displayName) {
  571. messageHandler.notify(
  572. displayName, 'notify.somebody',
  573. 'connected', 'notify.grantedTo', {
  574. to: UIUtil.escapeHtml(displayName)
  575. }
  576. );
  577. } else {
  578. messageHandler.notify(
  579. '', 'notify.somebody',
  580. 'connected', 'notify.grantedToUnknown', {}
  581. );
  582. }
  583. };
  584. /**
  585. * Toggles smileys in the chat.
  586. */
  587. UI.toggleSmileys = function () {
  588. Chat.toggleSmileys();
  589. };
  590. /**
  591. * Toggles film strip.
  592. */
  593. UI.toggleFilmStrip = function () {
  594. var self = FilmStrip;
  595. self.toggleFilmStrip.apply(self, arguments);
  596. };
  597. /**
  598. * Indicates if the film strip is currently visible or not.
  599. * @returns {true} if the film strip is currently visible, otherwise
  600. */
  601. UI.isFilmStripVisible = function () {
  602. return FilmStrip.isFilmStripVisible();
  603. };
  604. /**
  605. * Toggles chat panel.
  606. */
  607. UI.toggleChat = function () {
  608. PanelToggler.toggleChat();
  609. };
  610. /**
  611. * Toggles contact list panel.
  612. */
  613. UI.toggleContactList = function () {
  614. PanelToggler.toggleContactList();
  615. };
  616. /**
  617. * Handle new user display name.
  618. */
  619. UI.inputDisplayNameHandler = function (newDisplayName) {
  620. eventEmitter.emit(UIEvents.NICKNAME_CHANGED, newDisplayName);
  621. };
  622. /**
  623. * Return the type of the remote video.
  624. * @param jid the jid for the remote video
  625. * @returns the video type video or screen.
  626. */
  627. UI.getRemoteVideoType = function (jid) {
  628. return VideoLayout.getRemoteVideoType(jid);
  629. };
  630. UI.connectionIndicatorShowMore = function(id) {
  631. VideoLayout.showMore(id);
  632. };
  633. // FIXME check if someone user this
  634. UI.showLoginPopup = function(callback) {
  635. console.log('password is required');
  636. var message = '<h2 data-i18n="dialog.passwordRequired">';
  637. message += APP.translation.translateString(
  638. "dialog.passwordRequired");
  639. message += '</h2>' +
  640. '<input name="username" type="text" ' +
  641. 'placeholder="user@domain.net" autofocus>' +
  642. '<input name="password" ' +
  643. 'type="password" data-i18n="[placeholder]dialog.userPassword"' +
  644. ' placeholder="user password">';
  645. messageHandler.openTwoButtonDialog(null, null, null, message,
  646. true,
  647. "dialog.Ok",
  648. function (e, v, m, f) {
  649. if (v) {
  650. if (f.username && f.password) {
  651. callback(f.username, f.password);
  652. }
  653. }
  654. },
  655. null, null, ':input:first'
  656. );
  657. };
  658. UI.askForNickname = function () {
  659. return window.prompt('Your nickname (optional)');
  660. };
  661. /**
  662. * Sets muted audio state for participant
  663. */
  664. UI.setAudioMuted = function (id, muted) {
  665. VideoLayout.onAudioMute(id, muted);
  666. if (APP.conference.isLocalId(id)) {
  667. Toolbar.markAudioIconAsMuted(muted);
  668. }
  669. };
  670. /**
  671. * Sets muted video state for participant
  672. */
  673. UI.setVideoMuted = function (id, muted) {
  674. VideoLayout.onVideoMute(id, muted);
  675. if (APP.conference.isLocalId(id)) {
  676. Toolbar.markVideoIconAsMuted(muted);
  677. }
  678. };
  679. /**
  680. * Adds a listener that would be notified on the given type of event.
  681. *
  682. * @param type the type of the event we're listening for
  683. * @param listener a function that would be called when notified
  684. */
  685. UI.addListener = function (type, listener) {
  686. eventEmitter.on(type, listener);
  687. };
  688. /**
  689. * Removes the given listener for the given type of event.
  690. *
  691. * @param type the type of the event we're listening for
  692. * @param listener the listener we want to remove
  693. */
  694. UI.removeListener = function (type, listener) {
  695. eventEmitter.removeListener(type, listener);
  696. };
  697. UI.clickOnVideo = function (videoNumber) {
  698. var remoteVideos = $(".videocontainer:not(#mixedstream)");
  699. if (remoteVideos.length > videoNumber) {
  700. remoteVideos[videoNumber].click();
  701. }
  702. };
  703. //Used by torture
  704. UI.showToolbar = function () {
  705. return ToolbarToggler.showToolbar();
  706. };
  707. //Used by torture
  708. UI.dockToolbar = function (isDock) {
  709. ToolbarToggler.dockToolbar(isDock);
  710. };
  711. /**
  712. * Updates the avatar for participant.
  713. * @param {string} id user id
  714. * @param {stirng} avatarUrl the URL for the avatar
  715. */
  716. function changeAvatar(id, avatarUrl) {
  717. VideoLayout.changeUserAvatar(id, avatarUrl);
  718. ContactList.changeUserAvatar(id, avatarUrl);
  719. if (APP.conference.isLocalId(id)) {
  720. SettingsMenu.changeAvatar(avatarUrl);
  721. }
  722. }
  723. /**
  724. * Update user email.
  725. * @param {string} id user id
  726. * @param {stirng} email user email
  727. */
  728. UI.setUserEmail = function (id, email) {
  729. // update avatar
  730. Avatar.setUserEmail(id, email);
  731. changeAvatar(id, Avatar.getAvatarUrl(id));
  732. };
  733. /**
  734. * Update user avatar URL.
  735. * @param {string} id user id
  736. * @param {stirng} url user avatar url
  737. */
  738. UI.setUserAvatarUrl = function (id, url) {
  739. // update avatar
  740. Avatar.setUserAvatarUrl(id, url);
  741. changeAvatar(id, Avatar.getAvatarUrl(id));
  742. };
  743. /**
  744. * Notify user that connection failed.
  745. * @param {string} stropheErrorMsg raw Strophe error message
  746. */
  747. UI.notifyConnectionFailed = function (stropheErrorMsg) {
  748. var title = APP.translation.generateTranslationHTML(
  749. "dialog.error");
  750. var message;
  751. if (stropheErrorMsg) {
  752. message = APP.translation.generateTranslationHTML(
  753. "dialog.connectErrorWithMsg", {msg: stropheErrorMsg});
  754. } else {
  755. message = APP.translation.generateTranslationHTML(
  756. "dialog.connectError");
  757. }
  758. messageHandler.openDialog(
  759. title, message, true, {}, function (e, v, m, f) { return false; }
  760. );
  761. };
  762. /**
  763. * Notify user that maximum users limit has been reached.
  764. */
  765. UI.notifyMaxUsersLimitReached = function () {
  766. var title = APP.translation.generateTranslationHTML(
  767. "dialog.error");
  768. var message = APP.translation.generateTranslationHTML(
  769. "dialog.maxUsersLimitReached");
  770. messageHandler.openDialog(
  771. title, message, true, {}, function (e, v, m, f) { return false; }
  772. );
  773. };
  774. /**
  775. * Notify user that he was automatically muted when joned the conference.
  776. */
  777. UI.notifyInitiallyMuted = function () {
  778. messageHandler.notify(
  779. null, "notify.mutedTitle", "connected", "notify.muted", null, {timeOut: 120000}
  780. );
  781. };
  782. /**
  783. * Mark user as dominant speaker.
  784. * @param {string} id user id
  785. */
  786. UI.markDominantSpeaker = function (id) {
  787. VideoLayout.onDominantSpeakerChanged(id);
  788. };
  789. UI.handleLastNEndpoints = function (ids, enteringIds) {
  790. VideoLayout.onLastNEndpointsChanged(ids, enteringIds);
  791. };
  792. /**
  793. * Update audio level visualization for specified user.
  794. * @param {string} id user id
  795. * @param {number} lvl audio level
  796. */
  797. UI.setAudioLevel = function (id, lvl) {
  798. VideoLayout.setAudioLevel(id, lvl);
  799. };
  800. /**
  801. * Update state of desktop sharing buttons.
  802. */
  803. UI.updateDesktopSharingButtons = function () {
  804. Toolbar.updateDesktopSharingButtonState();
  805. };
  806. /**
  807. * Hide connection quality statistics from UI.
  808. */
  809. UI.hideStats = function () {
  810. VideoLayout.hideStats();
  811. };
  812. /**
  813. * Update local connection quality statistics.
  814. * @param {number} percent
  815. * @param {object} stats
  816. */
  817. UI.updateLocalStats = function (percent, stats) {
  818. VideoLayout.updateLocalConnectionStats(percent, stats);
  819. };
  820. /**
  821. * Update connection quality statistics for remote user.
  822. * @param {string} id user id
  823. * @param {number} percent
  824. * @param {object} stats
  825. */
  826. UI.updateRemoteStats = function (id, percent, stats) {
  827. VideoLayout.updateConnectionStats(id, percent, stats);
  828. };
  829. /**
  830. * Mark video as interrupted or not.
  831. * @param {boolean} interrupted if video is interrupted
  832. */
  833. UI.markVideoInterrupted = function (interrupted) {
  834. if (interrupted) {
  835. VideoLayout.onVideoInterrupted();
  836. } else {
  837. VideoLayout.onVideoRestored();
  838. }
  839. };
  840. /**
  841. * Mark room as locked or not.
  842. * @param {boolean} locked if room is locked.
  843. */
  844. UI.markRoomLocked = function (locked) {
  845. if (locked) {
  846. Toolbar.lockLockButton();
  847. } else {
  848. Toolbar.unlockLockButton();
  849. }
  850. };
  851. /**
  852. * Add chat message.
  853. * @param {string} from user id
  854. * @param {string} displayName user nickname
  855. * @param {string} message message text
  856. * @param {number} stamp timestamp when message was created
  857. */
  858. UI.addMessage = function (from, displayName, message, stamp) {
  859. Chat.updateChatConversation(from, displayName, message, stamp);
  860. };
  861. UI.updateDTMFSupport = function (isDTMFSupported) {
  862. //TODO: enable when the UI is ready
  863. //Toolbar.showDialPadButton(dtmfSupport);
  864. };
  865. /**
  866. * Invite participants to conference.
  867. * @param {string} roomUrl
  868. * @param {string} conferenceName
  869. * @param {string} key
  870. * @param {string} nick
  871. */
  872. UI.inviteParticipants = function (roomUrl, conferenceName, key, nick) {
  873. let keyText = "";
  874. if (key) {
  875. keyText = APP.translation.translateString(
  876. "email.sharedKey", {sharedKey: key}
  877. );
  878. }
  879. let and = APP.translation.translateString("email.and");
  880. let supportedBrowsers = `Chromium, Google Chrome ${and} Opera`;
  881. let subject = APP.translation.translateString(
  882. "email.subject", {appName:interfaceConfig.APP_NAME, conferenceName}
  883. );
  884. let body = APP.translation.translateString(
  885. "email.body", {
  886. appName:interfaceConfig.APP_NAME,
  887. sharedKeyText: keyText,
  888. roomUrl,
  889. supportedBrowsers
  890. }
  891. );
  892. body = body.replace(/\n/g, "%0D%0A");
  893. if (nick) {
  894. body += "%0D%0A%0D%0A" + UIUtil.escapeHtml(nick);
  895. }
  896. if (interfaceConfig.INVITATION_POWERED_BY) {
  897. body += "%0D%0A%0D%0A--%0D%0Apowered by jitsi.org";
  898. }
  899. window.open(`mailto:?subject=${subject}&body=${body}`, '_blank');
  900. };
  901. /**
  902. * Show user feedback dialog if its required or just show "thank you" dialog.
  903. * @returns {Promise} when dialog is closed.
  904. */
  905. UI.requestFeedback = function () {
  906. return new Promise(function (resolve, reject) {
  907. if (Feedback.isEnabled()) {
  908. // If the user has already entered feedback, we'll show the window and
  909. // immidiately start the conference dispose timeout.
  910. if (Feedback.feedbackScore > 0) {
  911. Feedback.openFeedbackWindow();
  912. resolve();
  913. } else { // Otherwise we'll wait for user's feedback.
  914. Feedback.openFeedbackWindow(resolve);
  915. }
  916. } else {
  917. // If the feedback functionality isn't enabled we show a thank you
  918. // dialog.
  919. messageHandler.openMessageDialog(
  920. null, null, null,
  921. APP.translation.translateString(
  922. "dialog.thankYou", {appName:interfaceConfig.APP_NAME}
  923. )
  924. );
  925. resolve();
  926. }
  927. });
  928. };
  929. UI.updateRecordingState = function (state) {
  930. Recording.updateRecordingState(state);
  931. };
  932. UI.notifyTokenAuthFailed = function () {
  933. messageHandler.showError("dialog.error", "dialog.tokenAuthFailed");
  934. };
  935. UI.notifyInternalError = function () {
  936. messageHandler.showError("dialog.sorry", "dialog.internalError");
  937. };
  938. UI.notifyFocusDisconnected = function (focus, retrySec) {
  939. messageHandler.notify(
  940. null, "notify.focus",
  941. 'disconnected', "notify.focusFail",
  942. {component: focus, ms: retrySec}
  943. );
  944. };
  945. /**
  946. * Notify user that focus left the conference so page should be reloaded.
  947. */
  948. UI.notifyFocusLeft = function () {
  949. let title = APP.translation.generateTranslationHTML(
  950. 'dialog.serviceUnavailable'
  951. );
  952. let msg = APP.translation.generateTranslationHTML(
  953. 'dialog.jicofoUnavailable'
  954. );
  955. messageHandler.openDialog(
  956. title,
  957. msg,
  958. true, // persistent
  959. [{title: 'retry'}],
  960. function () {
  961. reload();
  962. return false;
  963. }
  964. );
  965. };
  966. /**
  967. * Updates auth info on the UI.
  968. * @param {boolean} isAuthEnabled if authentication is enabled
  969. * @param {string} [login] current login
  970. */
  971. UI.updateAuthInfo = function (isAuthEnabled, login) {
  972. let loggedIn = !!login;
  973. Toolbar.showAuthenticateButton(isAuthEnabled);
  974. if (isAuthEnabled) {
  975. Toolbar.setAuthenticatedIdentity(login);
  976. Toolbar.showLoginButton(!loggedIn);
  977. Toolbar.showLogoutButton(loggedIn);
  978. }
  979. };
  980. UI.onStartMutedChanged = function (startAudioMuted, startVideoMuted) {
  981. SettingsMenu.updateStartMutedBox(startAudioMuted, startVideoMuted);
  982. };
  983. /**
  984. * Update list of available physical devices.
  985. * @param {object[]} devices new list of available devices
  986. */
  987. UI.onAvailableDevicesChanged = function (devices) {
  988. SettingsMenu.changeDevicesList(devices);
  989. };
  990. /**
  991. * Sets microphone's <select> element to select microphone ID from settings.
  992. */
  993. UI.setSelectedMicFromSettings = function () {
  994. SettingsMenu.setSelectedMicFromSettings();
  995. };
  996. /**
  997. * Sets camera's <select> element to select camera ID from settings.
  998. */
  999. UI.setSelectedCameraFromSettings = function () {
  1000. SettingsMenu.setSelectedCameraFromSettings();
  1001. };
  1002. /**
  1003. * Sets audio outputs's <select> element to select audio output ID from
  1004. * settings.
  1005. */
  1006. UI.setSelectedAudioOutputFromSettings = function () {
  1007. SettingsMenu.setSelectedAudioOutputFromSettings();
  1008. };
  1009. /**
  1010. * Returns the id of the current video shown on large.
  1011. * Currently used by tests (torture).
  1012. */
  1013. UI.getLargeVideoID = function () {
  1014. return VideoLayout.getLargeVideoID();
  1015. };
  1016. /**
  1017. * Returns the current video shown on large.
  1018. * Currently used by tests (torture).
  1019. */
  1020. UI.getLargeVideo = function () {
  1021. return VideoLayout.getLargeVideo();
  1022. };
  1023. /**
  1024. * Shows dialog with a link to FF extension.
  1025. */
  1026. UI.showExtensionRequiredDialog = function (url) {
  1027. messageHandler.openMessageDialog(
  1028. "dialog.extensionRequired",
  1029. null,
  1030. null,
  1031. APP.translation.generateTranslationHTML(
  1032. "dialog.firefoxExtensionPrompt", {url: url}));
  1033. };
  1034. /**
  1035. * Shows dialog with combined information about camera and microphone errors.
  1036. * @param {JitsiTrackError} micError
  1037. * @param {JitsiTrackError} cameraError
  1038. */
  1039. UI.showDeviceErrorDialog = function (micError, cameraError) {
  1040. let localStoragePropName = "doNotShowErrorAgain";
  1041. let isMicJitsiTrackErrorAndHasName = micError && micError.name &&
  1042. micError instanceof JitsiMeetJS.JitsiTrackError;
  1043. let isCameraJitsiTrackErrorAndHasName = cameraError && cameraError.name &&
  1044. cameraError instanceof JitsiMeetJS.JitsiTrackError;
  1045. let showDoNotShowWarning = false;
  1046. if (micError && cameraError && isMicJitsiTrackErrorAndHasName &&
  1047. isCameraJitsiTrackErrorAndHasName) {
  1048. showDoNotShowWarning = true;
  1049. } else if (micError && isMicJitsiTrackErrorAndHasName && !cameraError) {
  1050. showDoNotShowWarning = true;
  1051. } else if (cameraError && isCameraJitsiTrackErrorAndHasName && !micError) {
  1052. showDoNotShowWarning = true;
  1053. }
  1054. if (micError) {
  1055. localStoragePropName += "-mic-" + micError.name;
  1056. }
  1057. if (cameraError) {
  1058. localStoragePropName += "-camera-" + cameraError.name;
  1059. }
  1060. if (showDoNotShowWarning) {
  1061. if (window.localStorage[localStoragePropName] === "true") {
  1062. return;
  1063. }
  1064. }
  1065. let title = getTitleKey();
  1066. let titleMsg = `<span data-i18n="${title}"></span>`;
  1067. let cameraJitsiTrackErrorMsg = cameraError
  1068. ? JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.camera[cameraError.name]
  1069. : undefined;
  1070. let micJitsiTrackErrorMsg = micError
  1071. ? JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.microphone[micError.name]
  1072. : undefined;
  1073. let cameraErrorMsg = cameraError
  1074. ? cameraJitsiTrackErrorMsg ||
  1075. JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.camera[TrackErrors.GENERAL]
  1076. : "";
  1077. let micErrorMsg = micError
  1078. ? micJitsiTrackErrorMsg ||
  1079. JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.microphone[TrackErrors.GENERAL]
  1080. : "";
  1081. let additionalCameraErrorMsg = !cameraJitsiTrackErrorMsg && cameraError &&
  1082. cameraError.message
  1083. ? `<div>${cameraError.message}</div>`
  1084. : ``;
  1085. let additionalMicErrorMsg = !micJitsiTrackErrorMsg && micError &&
  1086. micError.message
  1087. ? `<div>${micError.message}</div>`
  1088. : ``;
  1089. let doNotShowWarningAgainSection = showDoNotShowWarning
  1090. ? `<label>
  1091. <input type='checkbox' id='doNotShowWarningAgain'>
  1092. <span data-i18n='dialog.doNotShowWarningAgain'></span>
  1093. </label>`
  1094. : ``;
  1095. let message = '';
  1096. if (micError) {
  1097. message = `
  1098. ${message}
  1099. <h3 data-i18n='dialog.micErrorPresent'></h3>
  1100. <h4 data-i18n='${micErrorMsg}'></h4>
  1101. ${additionalMicErrorMsg}`;
  1102. }
  1103. if (cameraError) {
  1104. message = `
  1105. ${message}
  1106. <h3 data-i18n='dialog.cameraErrorPresent'></h3>
  1107. <h4 data-i18n='${cameraErrorMsg}'></h4>
  1108. ${additionalCameraErrorMsg}`;
  1109. }
  1110. message = `${message}${doNotShowWarningAgainSection}`;
  1111. // To make sure we don't have multiple error dialogs open at the same time,
  1112. // we will just close the previous one if we are going to show a new one.
  1113. deviceErrorDialog && deviceErrorDialog.close();
  1114. deviceErrorDialog = messageHandler.openDialog(
  1115. titleMsg,
  1116. message,
  1117. false,
  1118. {Ok: true},
  1119. function () {
  1120. let form = $.prompt.getPrompt();
  1121. if (form) {
  1122. let input = form.find("#doNotShowWarningAgain");
  1123. if (input.length) {
  1124. window.localStorage[localStoragePropName] =
  1125. input.prop("checked");
  1126. }
  1127. }
  1128. },
  1129. null,
  1130. function () {
  1131. // Reset dialog reference to null to avoid memory leaks when
  1132. // user closed the dialog manually.
  1133. deviceErrorDialog = null;
  1134. }
  1135. );
  1136. APP.translation.translateElement($(".jqibox"));
  1137. function getTitleKey() {
  1138. let title = "dialog.error";
  1139. if (micError && micError.name === TrackErrors.PERMISSION_DENIED) {
  1140. if (cameraError && cameraError.name === TrackErrors.PERMISSION_DENIED) {
  1141. title = "dialog.permissionDenied";
  1142. } else if (!cameraError) {
  1143. title = "dialog.permissionDenied";
  1144. }
  1145. } else if (cameraError &&
  1146. cameraError.name === TrackErrors.PERMISSION_DENIED) {
  1147. title = "dialog.permissionDenied";
  1148. }
  1149. return title;
  1150. }
  1151. };
  1152. UI.updateDevicesAvailability = function (id, devices) {
  1153. VideoLayout.setDeviceAvailabilityIcons(id, devices);
  1154. };
  1155. /**
  1156. * Show shared video.
  1157. * @param {string} id the id of the sender of the command
  1158. * @param {string} url video url
  1159. * @param {string} attributes
  1160. */
  1161. UI.onSharedVideoStart = function (id, url, attributes) {
  1162. if (sharedVideoManager)
  1163. sharedVideoManager.onSharedVideoStart(id, url, attributes);
  1164. };
  1165. /**
  1166. * Update shared video.
  1167. * @param {string} id the id of the sender of the command
  1168. * @param {string} url video url
  1169. * @param {string} attributes
  1170. */
  1171. UI.onSharedVideoUpdate = function (id, url, attributes) {
  1172. if (sharedVideoManager)
  1173. sharedVideoManager.onSharedVideoUpdate(id, url, attributes);
  1174. };
  1175. /**
  1176. * Stop showing shared video.
  1177. * @param {string} id the id of the sender of the command
  1178. * @param {string} attributes
  1179. */
  1180. UI.onSharedVideoStop = function (id, attributes) {
  1181. if (sharedVideoManager)
  1182. sharedVideoManager.onSharedVideoStop(id, attributes);
  1183. };
  1184. /**
  1185. * Disables camera toolbar button.
  1186. */
  1187. UI.disableCameraButton = function () {
  1188. Toolbar.markVideoIconAsDisabled(true);
  1189. };
  1190. /**
  1191. * Enables camera toolbar button.
  1192. */
  1193. UI.enableCameraButton = function () {
  1194. Toolbar.markVideoIconAsDisabled(false);
  1195. };
  1196. /**
  1197. * Disables microphone toolbar button.
  1198. */
  1199. UI.disableMicrophoneButton = function () {
  1200. Toolbar.markAudioIconAsDisabled(true);
  1201. };
  1202. /**
  1203. * Enables microphone toolbar button.
  1204. */
  1205. UI.enableMicrophoneButton = function () {
  1206. Toolbar.markAudioIconAsDisabled(false);
  1207. };
  1208. let bottomToolbarEnabled = null;
  1209. UI.showRingOverLay = function () {
  1210. RingOverlay.show(APP.tokenData.callee);
  1211. ToolbarToggler.setAlwaysVisibleToolbar(true);
  1212. FilmStrip.toggleFilmStrip(false);
  1213. };
  1214. UI.hideRingOverLay = function () {
  1215. if (!RingOverlay.hide())
  1216. return;
  1217. ToolbarToggler.resetAlwaysVisibleToolbar();
  1218. FilmStrip.toggleFilmStrip(true);
  1219. };
  1220. /**
  1221. * Shows or hides the keyboard shortcuts panel.'
  1222. */
  1223. UI.toggleKeyboardShortcutsPanel = function() {
  1224. $('#keyboard-shortcuts').toggle();
  1225. };
  1226. module.exports = UI;