Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

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