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

functions.ts 25KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742
  1. // @ts-expect-error
  2. import { getGravatarURL } from '@jitsi/js-utils/avatar';
  3. import { IReduxState, IStore } from '../../app/types';
  4. import { isStageFilmstripAvailable } from '../../filmstrip/functions';
  5. import { isAddPeopleEnabled, isDialOutEnabled } from '../../invite/functions';
  6. import { toggleShareDialog } from '../../share-room/actions';
  7. import { IStateful } from '../app/types';
  8. import { GRAVATAR_BASE_URL } from '../avatar/constants';
  9. import { isCORSAvatarURL } from '../avatar/functions';
  10. import { getCurrentConference } from '../conference/functions';
  11. import { ADD_PEOPLE_ENABLED } from '../flags/constants';
  12. import { getFeatureFlag } from '../flags/functions';
  13. import i18next from '../i18n/i18next';
  14. import { MEDIA_TYPE, VIDEO_TYPE } from '../media/constants';
  15. import { toState } from '../redux/functions';
  16. import { getScreenShareTrack } from '../tracks/functions.any';
  17. import { createDeferred } from '../util/helpers';
  18. import {
  19. JIGASI_PARTICIPANT_ICON,
  20. MAX_DISPLAY_NAME_LENGTH,
  21. PARTICIPANT_ROLE,
  22. WHITEBOARD_PARTICIPANT_ICON
  23. } from './constants';
  24. import { preloadImage } from './preloadImage';
  25. import { FakeParticipant, IJitsiParticipant, IParticipant, ISourceInfo } from './types';
  26. /**
  27. * Temp structures for avatar urls to be checked/preloaded.
  28. */
  29. const AVATAR_QUEUE: Object[] = [];
  30. const AVATAR_CHECKED_URLS = new Map();
  31. /* eslint-disable arrow-body-style */
  32. const AVATAR_CHECKER_FUNCTIONS = [
  33. (participant: IParticipant) => {
  34. return participant?.isJigasi ? JIGASI_PARTICIPANT_ICON : null;
  35. },
  36. (participant: IParticipant) => {
  37. return isWhiteboardParticipant(participant) ? WHITEBOARD_PARTICIPANT_ICON : null;
  38. },
  39. (participant: IParticipant) => {
  40. return participant?.avatarURL ? participant.avatarURL : null;
  41. },
  42. (participant: IParticipant, store: IStore) => {
  43. const config = store.getState()['features/base/config'];
  44. const isGravatarDisabled = config.gravatar?.disabled;
  45. if (participant?.email && !isGravatarDisabled) {
  46. const gravatarBaseURL = config.gravatar?.baseUrl
  47. || config.gravatarBaseURL
  48. || GRAVATAR_BASE_URL;
  49. return getGravatarURL(participant.email, gravatarBaseURL);
  50. }
  51. return null;
  52. }
  53. ];
  54. /* eslint-enable arrow-body-style */
  55. /**
  56. * Returns the list of active speakers that should be moved to the top of the sorted list of participants so that the
  57. * dominant speaker is visible always on the vertical filmstrip in stage layout.
  58. *
  59. * @param {Function | Object} stateful - The (whole) redux state, or redux's {@code getState} function to be used to
  60. * retrieve the state.
  61. * @returns {Array<string>}
  62. */
  63. export function getActiveSpeakersToBeDisplayed(stateful: IStateful) {
  64. const state = toState(stateful);
  65. const {
  66. dominantSpeaker,
  67. fakeParticipants,
  68. sortedRemoteVirtualScreenshareParticipants,
  69. speakersList
  70. } = state['features/base/participants'];
  71. const { visibleRemoteParticipants } = state['features/filmstrip'];
  72. let activeSpeakers = new Map(speakersList);
  73. // Do not re-sort the active speakers if dominant speaker is currently visible.
  74. if (dominantSpeaker && visibleRemoteParticipants.has(dominantSpeaker)) {
  75. return activeSpeakers;
  76. }
  77. let availableSlotsForActiveSpeakers = visibleRemoteParticipants.size;
  78. if (activeSpeakers.has(dominantSpeaker ?? '')) {
  79. activeSpeakers.delete(dominantSpeaker ?? '');
  80. }
  81. // Add dominant speaker to the beginning of the list (not including self) since the active speaker list is always
  82. // alphabetically sorted.
  83. if (dominantSpeaker && dominantSpeaker !== getLocalParticipant(state)?.id) {
  84. const updatedSpeakers = Array.from(activeSpeakers);
  85. updatedSpeakers.splice(0, 0, [ dominantSpeaker, getParticipantById(state, dominantSpeaker)?.name ?? '' ]);
  86. activeSpeakers = new Map(updatedSpeakers);
  87. }
  88. // Remove screenshares from the count.
  89. if (sortedRemoteVirtualScreenshareParticipants) {
  90. availableSlotsForActiveSpeakers -= sortedRemoteVirtualScreenshareParticipants.size * 2;
  91. for (const screenshare of Array.from(sortedRemoteVirtualScreenshareParticipants.keys())) {
  92. const ownerId = getVirtualScreenshareParticipantOwnerId(screenshare as string);
  93. activeSpeakers.delete(ownerId);
  94. }
  95. }
  96. // Remove fake participants from the count.
  97. if (fakeParticipants) {
  98. availableSlotsForActiveSpeakers -= fakeParticipants.size;
  99. }
  100. const truncatedSpeakersList = Array.from(activeSpeakers).slice(0, availableSlotsForActiveSpeakers);
  101. truncatedSpeakersList.sort((a: any, b: any) => a[1].localeCompare(b[1]));
  102. return new Map(truncatedSpeakersList);
  103. }
  104. /**
  105. * Resolves the first loadable avatar URL for a participant.
  106. *
  107. * @param {Object} participant - The participant to resolve avatars for.
  108. * @param {Store} store - Redux store.
  109. * @returns {Promise}
  110. */
  111. export function getFirstLoadableAvatarUrl(participant: IParticipant, store: IStore) {
  112. const deferred: any = createDeferred();
  113. const fullPromise = deferred.promise
  114. .then(() => _getFirstLoadableAvatarUrl(participant, store))
  115. .then((result: any) => {
  116. if (AVATAR_QUEUE.length) {
  117. const next: any = AVATAR_QUEUE.shift();
  118. next.resolve();
  119. }
  120. return result;
  121. });
  122. if (AVATAR_QUEUE.length) {
  123. AVATAR_QUEUE.push(deferred);
  124. } else {
  125. deferred.resolve();
  126. }
  127. return fullPromise;
  128. }
  129. /**
  130. * Returns local participant from Redux state.
  131. *
  132. * @param {(Function|Object)} stateful - The (whole) redux state, or redux's
  133. * {@code getState} function to be used to retrieve the state
  134. * features/base/participants.
  135. * @returns {(IParticipant|undefined)}
  136. */
  137. export function getLocalParticipant(stateful: IStateful) {
  138. const state = toState(stateful)['features/base/participants'];
  139. return state.local;
  140. }
  141. /**
  142. * Returns local screen share participant from Redux state.
  143. *
  144. * @param {(Function|Object)} stateful - The (whole) redux state, or redux's
  145. * {@code getState} function to be used to retrieve the state features/base/participants.
  146. * @returns {(IParticipant|undefined)}
  147. */
  148. export function getLocalScreenShareParticipant(stateful: IStateful) {
  149. const state = toState(stateful)['features/base/participants'];
  150. return state.localScreenShare;
  151. }
  152. /**
  153. * Returns screenshare participant.
  154. *
  155. * @param {(Function|Object)} stateful - The (whole) redux state, or redux's {@code getState} function to be used to
  156. * retrieve the state features/base/participants.
  157. * @param {string} id - The owner ID of the screenshare participant to retrieve.
  158. * @returns {(IParticipant|undefined)}
  159. */
  160. export function getVirtualScreenshareParticipantByOwnerId(stateful: IStateful, id: string) {
  161. const state = toState(stateful);
  162. const track = getScreenShareTrack(state['features/base/tracks'], id);
  163. return getParticipantById(stateful, track?.jitsiTrack.getSourceName());
  164. }
  165. /**
  166. * Normalizes a display name so then no invalid values (padding, length...etc)
  167. * can be set.
  168. *
  169. * @param {string} name - The display name to set.
  170. * @returns {string}
  171. */
  172. export function getNormalizedDisplayName(name: string) {
  173. if (!name?.trim()) {
  174. return undefined;
  175. }
  176. return name.trim().substring(0, MAX_DISPLAY_NAME_LENGTH);
  177. }
  178. /**
  179. * Returns participant by ID from Redux state.
  180. *
  181. * @param {(Function|Object)} stateful - The (whole) redux state, or redux's
  182. * {@code getState} function to be used to retrieve the state
  183. * features/base/participants.
  184. * @param {string} id - The ID of the participant to retrieve.
  185. * @private
  186. * @returns {(IParticipant|undefined)}
  187. */
  188. export function getParticipantById(stateful: IStateful, id: string): IParticipant | undefined {
  189. const state = toState(stateful)['features/base/participants'];
  190. const { local, localScreenShare, remote } = state;
  191. return remote.get(id)
  192. || (local?.id === id ? local : undefined)
  193. || (localScreenShare?.id === id ? localScreenShare : undefined);
  194. }
  195. /**
  196. * Returns the participant with the ID matching the passed ID or the local participant if the ID is
  197. * undefined.
  198. *
  199. * @param {(Function|Object)} stateful - The (whole) redux state, or redux's
  200. * {@code getState} function to be used to retrieve the state
  201. * features/base/participants.
  202. * @param {string|undefined} [participantID] - An optional partipantID argument.
  203. * @returns {IParticipant|undefined}
  204. */
  205. export function getParticipantByIdOrUndefined(stateful: IStateful, participantID?: string) {
  206. return participantID ? getParticipantById(stateful, participantID) : getLocalParticipant(stateful);
  207. }
  208. /**
  209. * Returns a count of the known participants in the passed in redux state,
  210. * excluding any fake participants.
  211. *
  212. * @param {(Function|Object)} stateful - The (whole) redux state, or redux's
  213. * {@code getState} function to be used to retrieve the state
  214. * features/base/participants.
  215. * @returns {number}
  216. */
  217. export function getParticipantCount(stateful: IStateful) {
  218. const state = toState(stateful);
  219. const {
  220. local,
  221. remote,
  222. fakeParticipants,
  223. sortedRemoteVirtualScreenshareParticipants
  224. } = state['features/base/participants'];
  225. return remote.size - fakeParticipants.size - sortedRemoteVirtualScreenshareParticipants.size + (local ? 1 : 0);
  226. }
  227. /**
  228. * Returns participant ID of the owner of a virtual screenshare participant.
  229. *
  230. * @param {string} id - The ID of the virtual screenshare participant.
  231. * @private
  232. * @returns {(string|undefined)}
  233. */
  234. export function getVirtualScreenshareParticipantOwnerId(id: string) {
  235. return id.split('-')[0];
  236. }
  237. /**
  238. * Returns the Map with fake participants.
  239. *
  240. * @param {(Function|Object)} stateful - The (whole) redux state, or redux's
  241. * {@code getState} function to be used to retrieve the state
  242. * features/base/participants.
  243. * @returns {Map<string, IParticipant>} - The Map with fake participants.
  244. */
  245. export function getFakeParticipants(stateful: IStateful) {
  246. return toState(stateful)['features/base/participants'].fakeParticipants;
  247. }
  248. /**
  249. * Returns whether the fake participant is a local screenshare.
  250. *
  251. * @param {IParticipant|undefined} participant - The participant entity.
  252. * @returns {boolean} - True if it's a local screenshare participant.
  253. */
  254. export function isLocalScreenshareParticipant(participant?: IParticipant): boolean {
  255. return participant?.fakeParticipant === FakeParticipant.LocalScreenShare;
  256. }
  257. /**
  258. * Returns whether the fake participant is a remote screenshare.
  259. *
  260. * @param {IParticipant|undefined} participant - The participant entity.
  261. * @returns {boolean} - True if it's a remote screenshare participant.
  262. */
  263. export function isRemoteScreenshareParticipant(participant?: IParticipant): boolean {
  264. return participant?.fakeParticipant === FakeParticipant.RemoteScreenShare;
  265. }
  266. /**
  267. * Returns whether the fake participant is of local or virtual screenshare type.
  268. *
  269. * @param {IReduxState} state - The (whole) redux state, or redux's.
  270. * @param {string|undefined} participantId - The participant id.
  271. * @returns {boolean} - True if it's one of the two.
  272. */
  273. export function isScreenShareParticipantById(state: IReduxState, participantId?: string): boolean {
  274. const participant = getParticipantByIdOrUndefined(state, participantId);
  275. return isScreenShareParticipant(participant);
  276. }
  277. /**
  278. * Returns whether the fake participant is of local or virtual screenshare type.
  279. *
  280. * @param {IParticipant|undefined} participant - The participant entity.
  281. * @returns {boolean} - True if it's one of the two.
  282. */
  283. export function isScreenShareParticipant(participant?: IParticipant): boolean {
  284. return isLocalScreenshareParticipant(participant) || isRemoteScreenshareParticipant(participant);
  285. }
  286. /**
  287. * Returns whether the (fake) participant is a shared video.
  288. *
  289. * @param {IParticipant|undefined} participant - The participant entity.
  290. * @returns {boolean} - True if it's a shared video participant.
  291. */
  292. export function isSharedVideoParticipant(participant?: IParticipant): boolean {
  293. return participant?.fakeParticipant === FakeParticipant.SharedVideo;
  294. }
  295. /**
  296. * Returns whether the fake participant is a whiteboard.
  297. *
  298. * @param {IParticipant|undefined} participant - The participant entity.
  299. * @returns {boolean} - True if it's a whiteboard participant.
  300. */
  301. export function isWhiteboardParticipant(participant?: IParticipant): boolean {
  302. return participant?.fakeParticipant === FakeParticipant.Whiteboard;
  303. }
  304. /**
  305. * Returns a count of the known remote participants in the passed in redux state.
  306. *
  307. * @param {(Function|Object)} stateful - The (whole) redux state, or redux's
  308. * {@code getState} function to be used to retrieve the state
  309. * features/base/participants.
  310. * @returns {number}
  311. */
  312. export function getRemoteParticipantCountWithFake(stateful: IStateful) {
  313. const state = toState(stateful);
  314. const participantsState = state['features/base/participants'];
  315. return participantsState.remote.size;
  316. }
  317. /**
  318. * Returns a count of the known participants in the passed in redux state,
  319. * including fake participants.
  320. *
  321. * @param {(Function|Object)} stateful - The (whole) redux state, or redux's
  322. * {@code getState} function to be used to retrieve the state
  323. * features/base/participants.
  324. * @returns {number}
  325. */
  326. export function getParticipantCountWithFake(stateful: IStateful) {
  327. const state = toState(stateful);
  328. const { local, localScreenShare, remote } = state['features/base/participants'];
  329. return remote.size + (local ? 1 : 0) + (localScreenShare ? 1 : 0);
  330. }
  331. /**
  332. * Returns participant's display name.
  333. *
  334. * @param {(Function|Object)} stateful - The (whole) redux state, or redux's {@code getState} function to be used to
  335. * retrieve the state.
  336. * @param {string} id - The ID of the participant's display name to retrieve.
  337. * @returns {string}
  338. */
  339. export function getParticipantDisplayName(stateful: IStateful, id: string): string {
  340. const state = toState(stateful);
  341. const participant = getParticipantById(state, id);
  342. const {
  343. defaultLocalDisplayName,
  344. defaultRemoteDisplayName
  345. } = state['features/base/config'];
  346. if (participant) {
  347. if (isScreenShareParticipant(participant)) {
  348. return getScreenshareParticipantDisplayName(state, id);
  349. }
  350. if (participant.name) {
  351. return participant.name;
  352. }
  353. if (participant.local) {
  354. return defaultLocalDisplayName ?? '';
  355. }
  356. }
  357. return defaultRemoteDisplayName ?? '';
  358. }
  359. /**
  360. * Returns the source names of the screenshare sources in the conference based on the presence shared by the remote
  361. * endpoints. This should be only used for creating/removing virtual screenshare participant tiles when ssrc-rewriting
  362. * is enabled. Once the tile is created, the source-name gets added to the receiver constraints based on which the
  363. * JVB will add the source to the video sources map and signal it to the local endpoint. Only then, a remote track is
  364. * created/remapped and the tracks in redux will be updated. Once the track is updated in redux, the client will
  365. * will continue to use the other track based getter functions for other operations related to screenshare.
  366. *
  367. * @param {(Function|Object)} stateful - The (whole) redux state, or redux's {@code getState} function to be used to
  368. * retrieve the state.
  369. * @returns {string[]}
  370. */
  371. export function getRemoteScreensharesBasedOnPresence(stateful: IStateful): string[] {
  372. const conference = getCurrentConference(stateful);
  373. return conference?.getParticipants()?.reduce((screenshares: string[], participant: IJitsiParticipant) => {
  374. const sources: Map<string, Map<string, ISourceInfo>> = participant.getSources();
  375. const videoSources = sources.get(MEDIA_TYPE.VIDEO);
  376. const screenshareSources = Array.from(videoSources ?? new Map())
  377. .filter(source => source[1].videoType === VIDEO_TYPE.DESKTOP && !source[1].muted)
  378. .map(source => source[0]);
  379. // eslint-disable-next-line no-param-reassign
  380. screenshares = [ ...screenshares, ...screenshareSources ];
  381. return screenshares;
  382. }, []);
  383. }
  384. /**
  385. * Returns screenshare participant's display name.
  386. *
  387. * @param {(Function|Object)} stateful - The (whole) redux state, or redux's {@code getState} function to be used to
  388. * retrieve the state.
  389. * @param {string} id - The ID of the screenshare participant's display name to retrieve.
  390. * @returns {string}
  391. */
  392. export function getScreenshareParticipantDisplayName(stateful: IStateful, id: string) {
  393. const ownerDisplayName = getParticipantDisplayName(stateful, getVirtualScreenshareParticipantOwnerId(id));
  394. return i18next.t('screenshareDisplayName', { name: ownerDisplayName });
  395. }
  396. /**
  397. * Returns a list of IDs of the participants that are currently screensharing.
  398. *
  399. * @param {(Function|Object)} stateful - The (whole) redux state, or redux's {@code getState} function to be used to
  400. * retrieve the state.
  401. * @returns {Array<string>}
  402. */
  403. export function getScreenshareParticipantIds(stateful: IStateful): Array<string> {
  404. return toState(stateful)['features/base/tracks']
  405. .filter(track => track.videoType === VIDEO_TYPE.DESKTOP && !track.muted)
  406. .map(t => t.participantId);
  407. }
  408. /**
  409. * Returns a list source name associated with a given remote participant and for the given media type.
  410. *
  411. * @param {(Function|Object)} stateful - The (whole) redux state, or redux's {@code getState} function to be used to
  412. * retrieve the state.
  413. * @param {string} id - The id of the participant whose source names are to be retrieved.
  414. * @param {string} mediaType - The type of source, audio or video.
  415. * @returns {Array<string>|undefined}
  416. */
  417. export function getSourceNamesByMediaType(
  418. stateful: IStateful,
  419. id: string,
  420. mediaType: string): Array<string> | undefined {
  421. const participant: IParticipant | undefined = getParticipantById(stateful, id);
  422. if (!participant) {
  423. return;
  424. }
  425. const sources = participant.sources;
  426. if (!sources) {
  427. return;
  428. }
  429. return Array.from(sources.get(mediaType) ?? new Map())
  430. .filter(source => source[1].videoType !== VIDEO_TYPE.DESKTOP || !source[1].muted)
  431. .map(s => s[0]);
  432. }
  433. /**
  434. * Returns the presence status of a participant associated with the passed id.
  435. *
  436. * @param {(Function|Object)} stateful - The (whole) redux state, or redux's
  437. * {@code getState} function to be used to retrieve the state.
  438. * @param {string} id - The id of the participant.
  439. * @returns {string} - The presence status.
  440. */
  441. export function getParticipantPresenceStatus(stateful: IStateful, id: string) {
  442. if (!id) {
  443. return undefined;
  444. }
  445. const participantById = getParticipantById(stateful, id);
  446. if (!participantById) {
  447. return undefined;
  448. }
  449. return participantById.presence;
  450. }
  451. /**
  452. * Selectors for getting all remote participants.
  453. *
  454. * @param {(Function|Object)} stateful - The (whole) redux state, or redux's
  455. * {@code getState} function to be used to retrieve the state
  456. * features/base/participants.
  457. * @returns {Map<string, Object>}
  458. */
  459. export function getRemoteParticipants(stateful: IStateful): Map<string, IParticipant> {
  460. return toState(stateful)['features/base/participants'].remote;
  461. }
  462. /**
  463. * Selectors for the getting the remote participants in the order that they are displayed in the filmstrip.
  464. *
  465. @param {(Function|Object)} stateful - The (whole) redux state, or redux's {@code getState} function to be used to
  466. * retrieve the state features/filmstrip.
  467. * @returns {Array<string>}
  468. */
  469. export function getRemoteParticipantsSorted(stateful: IStateful) {
  470. return toState(stateful)['features/filmstrip'].remoteParticipants;
  471. }
  472. /**
  473. * Returns the participant which has its pinned state set to truthy.
  474. *
  475. * @param {(Function|Object)} stateful - The (whole) redux state, or redux's
  476. * {@code getState} function to be used to retrieve the state
  477. * features/base/participants.
  478. * @returns {(IParticipant|undefined)}
  479. */
  480. export function getPinnedParticipant(stateful: IStateful) {
  481. const state = toState(stateful);
  482. const { pinnedParticipant } = state['features/base/participants'];
  483. const stageFilmstrip = isStageFilmstripAvailable(state);
  484. if (stageFilmstrip) {
  485. const { activeParticipants } = state['features/filmstrip'];
  486. const id = activeParticipants.find(p => p.pinned)?.participantId;
  487. return id ? getParticipantById(stateful, id) : undefined;
  488. }
  489. if (!pinnedParticipant) {
  490. return undefined;
  491. }
  492. return getParticipantById(stateful, pinnedParticipant);
  493. }
  494. /**
  495. * Returns true if the participant is a moderator.
  496. *
  497. * @param {string} participant - Participant object.
  498. * @returns {boolean}
  499. */
  500. export function isParticipantModerator(participant?: IParticipant) {
  501. return participant?.role === PARTICIPANT_ROLE.MODERATOR;
  502. }
  503. /**
  504. * Returns the dominant speaker participant.
  505. *
  506. * @param {(Function|Object)} stateful - The (whole) redux state or redux's
  507. * {@code getState} function to be used to retrieve the state features/base/participants.
  508. * @returns {IParticipant} - The participant from the redux store.
  509. */
  510. export function getDominantSpeakerParticipant(stateful: IStateful) {
  511. const state = toState(stateful)['features/base/participants'];
  512. const { dominantSpeaker } = state;
  513. if (!dominantSpeaker) {
  514. return undefined;
  515. }
  516. return getParticipantById(stateful, dominantSpeaker);
  517. }
  518. /**
  519. * Returns true if all of the meeting participants are moderators.
  520. *
  521. * @param {Object|Function} stateful -Object or function that can be resolved
  522. * to the Redux state.
  523. * @returns {boolean}
  524. */
  525. export function isEveryoneModerator(stateful: IStateful) {
  526. const state = toState(stateful)['features/base/participants'];
  527. return state.numberOfNonModeratorParticipants === 0;
  528. }
  529. /**
  530. * Checks a value and returns true if it's a preloaded icon object.
  531. *
  532. * @param {?string | ?Object} icon - The icon to check.
  533. * @returns {boolean}
  534. */
  535. export function isIconUrl(icon?: string | Object) {
  536. return Boolean(icon) && (typeof icon === 'object' || typeof icon === 'function');
  537. }
  538. /**
  539. * Returns true if the current local participant is a moderator in the
  540. * conference.
  541. *
  542. * @param {Object|Function} stateful - Object or function that can be resolved
  543. * to the Redux state.
  544. * @returns {boolean}
  545. */
  546. export function isLocalParticipantModerator(stateful: IStateful) {
  547. const state = toState(stateful)['features/base/participants'];
  548. const { local } = state;
  549. if (!local) {
  550. return false;
  551. }
  552. return isParticipantModerator(local);
  553. }
  554. /**
  555. * Resolves the first loadable avatar URL for a participant.
  556. *
  557. * @param {Object} participant - The participant to resolve avatars for.
  558. * @param {Store} store - Redux store.
  559. * @returns {?string}
  560. */
  561. async function _getFirstLoadableAvatarUrl(participant: IParticipant, store: IStore) {
  562. for (let i = 0; i < AVATAR_CHECKER_FUNCTIONS.length; i++) {
  563. const url = AVATAR_CHECKER_FUNCTIONS[i](participant, store);
  564. if (url !== null) {
  565. if (AVATAR_CHECKED_URLS.has(url)) {
  566. const { isLoadable, isUsingCORS } = AVATAR_CHECKED_URLS.get(url) || {};
  567. if (isLoadable) {
  568. return {
  569. isUsingCORS,
  570. src: url
  571. };
  572. }
  573. } else {
  574. try {
  575. const { corsAvatarURLs } = store.getState()['features/base/config'];
  576. const useCORS = isIconUrl(url) ? false : isCORSAvatarURL(url, corsAvatarURLs);
  577. const { isUsingCORS, src } = await preloadImage(url, useCORS);
  578. AVATAR_CHECKED_URLS.set(src, {
  579. isLoadable: true,
  580. isUsingCORS
  581. });
  582. return {
  583. isUsingCORS,
  584. src
  585. };
  586. } catch (e) {
  587. AVATAR_CHECKED_URLS.set(url, {
  588. isLoadable: false,
  589. isUsingCORS: false
  590. });
  591. }
  592. }
  593. }
  594. }
  595. return undefined;
  596. }
  597. /**
  598. * Get the participants queue with raised hands.
  599. *
  600. * @param {(Function|Object)} stateful - The (whole) redux state, or redux's
  601. * {@code getState} function to be used to retrieve the state
  602. * features/base/participants.
  603. * @returns {Array<Object>}
  604. */
  605. export function getRaiseHandsQueue(stateful: IStateful): Array<{ id: string; raisedHandTimestamp: number; }> {
  606. const { raisedHandsQueue } = toState(stateful)['features/base/participants'];
  607. return raisedHandsQueue;
  608. }
  609. /**
  610. * Returns whether the given participant has his hand raised or not.
  611. *
  612. * @param {Object} participant - The participant.
  613. * @returns {boolean} - Whether participant has raise hand or not.
  614. */
  615. export function hasRaisedHand(participant?: IParticipant): boolean {
  616. return Boolean(participant?.raisedHandTimestamp);
  617. }
  618. /**
  619. * Add people feature enabling/disabling.
  620. *
  621. * @param {Object|Function} stateful - Object or function that can be resolved
  622. * to the Redux state.
  623. * @returns {boolean}
  624. */
  625. export const addPeopleFeatureControl = (stateful: IStateful) => {
  626. const state = toState(stateful);
  627. return getFeatureFlag(state, ADD_PEOPLE_ENABLED, true)
  628. && (isAddPeopleEnabled(state) || isDialOutEnabled(state));
  629. };
  630. /**
  631. * Controls share dialog visibility.
  632. *
  633. * @param {boolean} addPeopleFeatureEnabled - Checks if add people functionality is enabled.
  634. * @param {Function} dispatch - The Redux dispatch function.
  635. * @returns {Function}
  636. */
  637. export const setShareDialogVisiblity = (addPeopleFeatureEnabled: boolean, dispatch: IStore['dispatch']) => {
  638. if (addPeopleFeatureEnabled) {
  639. dispatch(toggleShareDialog(false));
  640. } else {
  641. dispatch(toggleShareDialog(true));
  642. }
  643. };