Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

functions.ts 23KB

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