您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  1. /* global APP, config */
  2. const UI = {};
  3. import Logger from '@jitsi/logger';
  4. import EventEmitter from 'events';
  5. import {
  6. conferenceWillInit
  7. } from '../../react/features/base/conference/actions';
  8. import { isMobileBrowser } from '../../react/features/base/environment/utils';
  9. import { setColorAlpha } from '../../react/features/base/util/helpers';
  10. import { setDocumentUrl } from '../../react/features/etherpad/actions';
  11. import { setFilmstripVisible } from '../../react/features/filmstrip/actions.any';
  12. import {
  13. setNotificationsEnabled,
  14. showNotification
  15. } from '../../react/features/notifications/actions';
  16. import { NOTIFICATION_TIMEOUT_TYPE } from '../../react/features/notifications/constants';
  17. import { joinLeaveNotificationsDisabled } from '../../react/features/notifications/functions';
  18. import {
  19. dockToolbox,
  20. setToolboxEnabled,
  21. showToolbox
  22. } from '../../react/features/toolbox/actions.web';
  23. import UIEvents from '../../service/UI/UIEvents';
  24. import EtherpadManager from './etherpad/Etherpad';
  25. import UIUtil from './util/UIUtil';
  26. import VideoLayout from './videolayout/VideoLayout';
  27. const logger = Logger.getLogger(__filename);
  28. const eventEmitter = new EventEmitter();
  29. UI.eventEmitter = eventEmitter;
  30. let etherpadManager;
  31. const UIListeners = new Map([
  32. [
  33. UIEvents.ETHERPAD_CLICKED,
  34. () => etherpadManager && etherpadManager.toggleEtherpad()
  35. ], [
  36. UIEvents.TOGGLE_FILMSTRIP,
  37. () => UI.toggleFilmstrip()
  38. ]
  39. ]);
  40. /**
  41. * Indicates if we're currently in full screen mode.
  42. *
  43. * @return {boolean} {true} to indicate that we're currently in full screen
  44. * mode, {false} otherwise
  45. */
  46. UI.isFullScreen = function() {
  47. return UIUtil.isFullScreen();
  48. };
  49. /**
  50. * Initialize conference UI.
  51. */
  52. UI.initConference = function() {
  53. UI.showToolbar();
  54. };
  55. /**
  56. * Starts the UI module and initializes all related components.
  57. */
  58. UI.start = function() {
  59. APP.store.dispatch(conferenceWillInit());
  60. if (isMobileBrowser()) {
  61. document.body.classList.add('mobile-browser');
  62. } else {
  63. document.body.classList.add('desktop-browser');
  64. }
  65. if (config.backgroundAlpha !== undefined) {
  66. const backgroundColor = getComputedStyle(document.body).getPropertyValue('background-color');
  67. const alphaColor = setColorAlpha(backgroundColor, config.backgroundAlpha);
  68. document.body.style.backgroundColor = alphaColor;
  69. }
  70. if (config.iAmRecorder) {
  71. // in case of iAmSipGateway keep local video visible
  72. if (!config.iAmSipGateway) {
  73. APP.store.dispatch(setNotificationsEnabled(false));
  74. }
  75. APP.store.dispatch(setToolboxEnabled(false));
  76. }
  77. };
  78. /**
  79. * Setup some UI event listeners.
  80. */
  81. UI.registerListeners
  82. = () => UIListeners.forEach((value, key) => UI.addListener(key, value));
  83. /**
  84. *
  85. */
  86. function onResize() {
  87. VideoLayout.onResize();
  88. }
  89. /**
  90. * Setup some DOM event listeners.
  91. */
  92. UI.bindEvents = () => {
  93. // Resize and reposition videos in full screen mode.
  94. document.addEventListener('webkitfullscreenchange', onResize);
  95. document.addEventListener('mozfullscreenchange', onResize);
  96. document.addEventListener('fullscreenchange', onResize);
  97. window.addEventListener('resize', onResize);
  98. };
  99. /**
  100. * Unbind some DOM event listeners.
  101. */
  102. UI.unbindEvents = () => {
  103. document.removeEventListener('webkitfullscreenchange', onResize);
  104. document.removeEventListener('mozfullscreenchange', onResize);
  105. document.removeEventListener('fullscreenchange', onResize);
  106. window.removeEventListener('resize', onResize);
  107. };
  108. /**
  109. * Setup and show Etherpad.
  110. * @param {string} name etherpad id
  111. */
  112. UI.initEtherpad = name => {
  113. if (etherpadManager || !config.etherpad_base || !name) {
  114. return;
  115. }
  116. logger.log('Etherpad is enabled');
  117. etherpadManager = new EtherpadManager(eventEmitter);
  118. const url = new URL(name, config.etherpad_base);
  119. APP.store.dispatch(setDocumentUrl(url.toString()));
  120. if (config.openSharedDocumentOnJoin) {
  121. etherpadManager.toggleEtherpad();
  122. }
  123. };
  124. /**
  125. * Returns the shared document manager object.
  126. * @return {EtherpadManager} the shared document manager object
  127. */
  128. UI.getSharedDocumentManager = () => etherpadManager;
  129. /**
  130. * Show user on UI.
  131. * @param {JitsiParticipant} user
  132. */
  133. UI.addUser = function(user) {
  134. const status = user.getStatus();
  135. if (status) {
  136. // FIXME: move updateUserStatus in participantPresenceChanged action
  137. UI.updateUserStatus(user, status);
  138. }
  139. };
  140. /**
  141. * Updates the user status.
  142. *
  143. * @param {JitsiParticipant} user - The user which status we need to update.
  144. * @param {string} status - The new status.
  145. */
  146. UI.updateUserStatus = (user, status) => {
  147. const reduxState = APP.store.getState() || {};
  148. const { calleeInfoVisible } = reduxState['features/invite'] || {};
  149. // We hide status updates when join/leave notifications are disabled,
  150. // as jigasi is the component with statuses and they are seen as join/leave notifications.
  151. if (!status || calleeInfoVisible || joinLeaveNotificationsDisabled()) {
  152. return;
  153. }
  154. const displayName = user.getDisplayName();
  155. APP.store.dispatch(showNotification({
  156. titleKey: `${displayName} connected`,
  157. descriptionKey: 'dialOut.statusMessage'
  158. }, NOTIFICATION_TIMEOUT_TYPE.SHORT));
  159. };
  160. /**
  161. * Toggles filmstrip.
  162. */
  163. UI.toggleFilmstrip = function() {
  164. const { visible } = APP.store.getState()['features/filmstrip'];
  165. APP.store.dispatch(setFilmstripVisible(!visible));
  166. };
  167. /**
  168. * Sets muted audio state for participant
  169. */
  170. UI.setAudioMuted = function(id) {
  171. // FIXME: Maybe this can be removed!
  172. if (APP.conference.isLocalId(id)) {
  173. APP.conference.updateAudioIconEnabled();
  174. }
  175. };
  176. /**
  177. * Sets muted video state for participant
  178. */
  179. UI.setVideoMuted = function(id) {
  180. VideoLayout._updateLargeVideoIfDisplayed(id, true);
  181. if (APP.conference.isLocalId(id)) {
  182. APP.conference.updateVideoIconEnabled();
  183. }
  184. };
  185. UI.updateLargeVideo = (id, forceUpdate) => VideoLayout.updateLargeVideo(id, forceUpdate);
  186. /**
  187. * Adds a listener that would be notified on the given type of event.
  188. *
  189. * @param type the type of the event we're listening for
  190. * @param listener a function that would be called when notified
  191. */
  192. UI.addListener = function(type, listener) {
  193. eventEmitter.on(type, listener);
  194. };
  195. /**
  196. * Removes the all listeners for all events.
  197. *
  198. * @returns {void}
  199. */
  200. UI.removeAllListeners = function() {
  201. eventEmitter.removeAllListeners();
  202. };
  203. /**
  204. * Emits the event of given type by specifying the parameters in options.
  205. *
  206. * @param type the type of the event we're emitting
  207. * @param options the parameters for the event
  208. */
  209. UI.emitEvent = (type, ...options) => eventEmitter.emit(type, ...options);
  210. // Used by torture.
  211. UI.showToolbar = timeout => APP.store.dispatch(showToolbox(timeout));
  212. // Used by torture.
  213. UI.dockToolbar = dock => APP.store.dispatch(dockToolbox(dock));
  214. UI.handleLastNEndpoints = function(leavingIds, enteringIds) {
  215. VideoLayout.onLastNEndpointsChanged(leavingIds, enteringIds);
  216. };
  217. /**
  218. * Update audio level visualization for specified user.
  219. * @param {string} id user id
  220. * @param {number} lvl audio level
  221. */
  222. UI.setAudioLevel = (id, lvl) => VideoLayout.setAudioLevel(id, lvl);
  223. /**
  224. * Update list of available physical devices.
  225. */
  226. UI.onAvailableDevicesChanged = function() {
  227. APP.conference.updateAudioIconEnabled();
  228. APP.conference.updateVideoIconEnabled();
  229. };
  230. /**
  231. * Returns the id of the current video shown on large.
  232. * Currently used by tests (torture).
  233. */
  234. UI.getLargeVideoID = function() {
  235. return VideoLayout.getLargeVideoID();
  236. };
  237. /**
  238. * Returns the current video shown on large.
  239. * Currently used by tests (torture).
  240. */
  241. UI.getLargeVideo = function() {
  242. return VideoLayout.getLargeVideo();
  243. };
  244. // TODO: Export every function separately. For now there is no point of doing
  245. // this because we are importing everything.
  246. export default UI;