Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

functions.ts 28KB

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