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

functions.ts 28KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809
  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 { 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 the muted state of the given media source for a given participant.
  319. *
  320. * @param {(Function|Object)} stateful - The (whole) redux state, or redux's.
  321. * @param {IParticipant} participant - The participant entity.
  322. * @param {MediaType} mediaType - The media type.
  323. * @returns {boolean} - True its muted, false otherwise.
  324. */
  325. export function getMutedStateByParticipantAndMediaType(
  326. stateful: IStateful,
  327. participant: IParticipant,
  328. mediaType: MediaType): boolean {
  329. const type = mediaType === MEDIA_TYPE.SCREENSHARE ? 'video' : mediaType;
  330. if (participant.local) {
  331. const state = toState(stateful);
  332. const tracks = state['features/base/tracks'];
  333. return isLocalTrackMuted(tracks, mediaType);
  334. }
  335. const sources = participant.sources?.get(type);
  336. if (!sources) {
  337. return true;
  338. }
  339. if (mediaType === MEDIA_TYPE.AUDIO) {
  340. return Array.from(sources.values())[0].muted;
  341. }
  342. const videoType = mediaType === MEDIA_TYPE.VIDEO ? VIDEO_TYPE.CAMERA : VIDEO_TYPE.SCREENSHARE;
  343. const source = Array.from(sources.values()).find(src => src.videoType === videoType);
  344. return source?.muted ?? true;
  345. }
  346. /**
  347. * Returns a count of the known participants in the passed in redux state,
  348. * including fake participants.
  349. *
  350. * @param {(Function|Object)} stateful - The (whole) redux state, or redux's
  351. * {@code getState} function to be used to retrieve the state
  352. * features/base/participants.
  353. * @returns {number}
  354. */
  355. export function getParticipantCountWithFake(stateful: IStateful) {
  356. const state = toState(stateful);
  357. const { local, localScreenShare, remote } = state['features/base/participants'];
  358. return remote.size + (local ? 1 : 0) + (localScreenShare ? 1 : 0);
  359. }
  360. /**
  361. * Returns participant's display name.
  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. * @param {string} id - The ID of the participant's display name to retrieve.
  366. * @returns {string}
  367. */
  368. export function getParticipantDisplayName(stateful: IStateful, id: string): string {
  369. const state = toState(stateful);
  370. const participant = getParticipantById(state, id);
  371. const {
  372. defaultLocalDisplayName,
  373. defaultRemoteDisplayName
  374. } = state['features/base/config'];
  375. if (participant) {
  376. if (isScreenShareParticipant(participant)) {
  377. return getScreenshareParticipantDisplayName(state, id);
  378. }
  379. if (participant.name) {
  380. return participant.name;
  381. }
  382. if (participant.local) {
  383. return defaultLocalDisplayName ?? '';
  384. }
  385. }
  386. return defaultRemoteDisplayName ?? '';
  387. }
  388. /**
  389. * Returns the source names of the screenshare sources in the conference based on the presence shared by the remote
  390. * endpoints. This should be only used for creating/removing virtual screenshare participant tiles when ssrc-rewriting
  391. * is enabled. Once the tile is created, the source-name gets added to the receiver constraints based on which the
  392. * JVB will add the source to the video sources map and signal it to the local endpoint. Only then, a remote track is
  393. * created/remapped and the tracks in redux will be updated. Once the track is updated in redux, the client will
  394. * will continue to use the other track based getter functions for other operations related to screenshare.
  395. *
  396. * @param {(Function|Object)} stateful - The (whole) redux state, or redux's {@code getState} function to be used to
  397. * retrieve the state.
  398. * @returns {string[]}
  399. */
  400. export function getRemoteScreensharesBasedOnPresence(stateful: IStateful): string[] {
  401. const conference = getCurrentConference(stateful);
  402. return conference?.getParticipants()?.reduce((screenshares: string[], participant: IJitsiParticipant) => {
  403. const sources: Map<string, Map<string, ISourceInfo>> = participant.getSources();
  404. const videoSources = sources.get(MEDIA_TYPE.VIDEO);
  405. const screenshareSources = Array.from(videoSources ?? new Map())
  406. .filter(source => source[1].videoType === VIDEO_TYPE.DESKTOP && !source[1].muted)
  407. .map(source => source[0]);
  408. // eslint-disable-next-line no-param-reassign
  409. screenshares = [ ...screenshares, ...screenshareSources ];
  410. return screenshares;
  411. }, []);
  412. }
  413. /**
  414. * Returns screenshare participant's display name.
  415. *
  416. * @param {(Function|Object)} stateful - The (whole) redux state, or redux's {@code getState} function to be used to
  417. * retrieve the state.
  418. * @param {string} id - The ID of the screenshare participant's display name to retrieve.
  419. * @returns {string}
  420. */
  421. export function getScreenshareParticipantDisplayName(stateful: IStateful, id: string) {
  422. const ownerDisplayName = getParticipantDisplayName(stateful, getVirtualScreenshareParticipantOwnerId(id));
  423. return i18next.t('screenshareDisplayName', { name: ownerDisplayName });
  424. }
  425. /**
  426. * Returns a list of IDs of the participants that are currently screensharing.
  427. *
  428. * @param {(Function|Object)} stateful - The (whole) redux state, or redux's {@code getState} function to be used to
  429. * retrieve the state.
  430. * @returns {Array<string>}
  431. */
  432. export function getScreenshareParticipantIds(stateful: IStateful): Array<string> {
  433. return toState(stateful)['features/base/tracks']
  434. .filter(track => track.videoType === VIDEO_TYPE.DESKTOP && !track.muted)
  435. .map(t => t.participantId);
  436. }
  437. /**
  438. * Returns a list of source names associated with a given remote participant and for the given media type.
  439. *
  440. * @param {(Function|Object)} stateful - The (whole) redux state, or redux's {@code getState} function to be used to
  441. * retrieve the state.
  442. * @param {string} id - The id of the participant whose source names are to be retrieved.
  443. * @param {string} mediaType - The type of source, audio or video.
  444. * @returns {Array<string>}
  445. */
  446. export function getSourceNamesByMediaTypeAndParticipant(
  447. stateful: IStateful,
  448. id: string,
  449. mediaType: string): Array<string> {
  450. const participant: IParticipant | undefined = getParticipantById(stateful, id);
  451. if (!participant) {
  452. return [];
  453. }
  454. const sources = participant.sources;
  455. if (!sources) {
  456. return [];
  457. }
  458. return Array.from(sources.get(mediaType) ?? new Map())
  459. .filter(source => source[1].videoType !== VIDEO_TYPE.DESKTOP || !source[1].muted)
  460. .map(s => s[0]);
  461. }
  462. /**
  463. * Returns a list of source names associated with a given remote participant and for the given video type (only for
  464. * video sources).
  465. *
  466. * @param {(Function|Object)} stateful - The (whole) redux state, or redux's {@code getState} function to be used to
  467. * retrieve the state.
  468. * @param {string} id - The id of the participant whose source names are to be retrieved.
  469. * @param {string} videoType - The type of video, camera or desktop.
  470. * @returns {Array<string>}
  471. */
  472. export function getSourceNamesByVideoTypeAndParticipant(
  473. stateful: IStateful,
  474. id: string,
  475. videoType: string): Array<string> {
  476. const participant: IParticipant | undefined = getParticipantById(stateful, id);
  477. if (!participant) {
  478. return [];
  479. }
  480. const sources = participant.sources;
  481. if (!sources) {
  482. return [];
  483. }
  484. return Array.from(sources.get(MEDIA_TYPE.VIDEO) ?? new Map())
  485. .filter(source => source[1].videoType === videoType && (videoType === VIDEO_TYPE.CAMERA || !source[1].muted))
  486. .map(s => s[0]);
  487. }
  488. /**
  489. * Returns the presence status of a participant associated with the passed id.
  490. *
  491. * @param {(Function|Object)} stateful - The (whole) redux state, or redux's
  492. * {@code getState} function to be used to retrieve the state.
  493. * @param {string} id - The id of the participant.
  494. * @returns {string} - The presence status.
  495. */
  496. export function getParticipantPresenceStatus(stateful: IStateful, id: string) {
  497. if (!id) {
  498. return undefined;
  499. }
  500. const participantById = getParticipantById(stateful, id);
  501. if (!participantById) {
  502. return undefined;
  503. }
  504. return participantById.presence;
  505. }
  506. /**
  507. * Selectors for getting all remote participants.
  508. *
  509. * @param {(Function|Object)} stateful - The (whole) redux state, or redux's
  510. * {@code getState} function to be used to retrieve the state
  511. * features/base/participants.
  512. * @returns {Map<string, Object>}
  513. */
  514. export function getRemoteParticipants(stateful: IStateful): Map<string, IParticipant> {
  515. return toState(stateful)['features/base/participants'].remote;
  516. }
  517. /**
  518. * Selectors for the getting the remote participants in the order that they are displayed in the filmstrip.
  519. *
  520. @param {(Function|Object)} stateful - The (whole) redux state, or redux's {@code getState} function to be used to
  521. * retrieve the state features/filmstrip.
  522. * @returns {Array<string>}
  523. */
  524. export function getRemoteParticipantsSorted(stateful: IStateful) {
  525. return toState(stateful)['features/filmstrip'].remoteParticipants;
  526. }
  527. /**
  528. * Returns the participant which has its pinned state set to truthy.
  529. *
  530. * @param {(Function|Object)} stateful - The (whole) redux state, or redux's
  531. * {@code getState} function to be used to retrieve the state
  532. * features/base/participants.
  533. * @returns {(IParticipant|undefined)}
  534. */
  535. export function getPinnedParticipant(stateful: IStateful) {
  536. const state = toState(stateful);
  537. const { pinnedParticipant } = state['features/base/participants'];
  538. const stageFilmstrip = isStageFilmstripAvailable(state);
  539. if (stageFilmstrip) {
  540. const { activeParticipants } = state['features/filmstrip'];
  541. const id = activeParticipants.find(p => p.pinned)?.participantId;
  542. return id ? getParticipantById(stateful, id) : undefined;
  543. }
  544. if (!pinnedParticipant) {
  545. return undefined;
  546. }
  547. return getParticipantById(stateful, pinnedParticipant);
  548. }
  549. /**
  550. * Returns true if the participant is a moderator.
  551. *
  552. * @param {string} participant - Participant object.
  553. * @returns {boolean}
  554. */
  555. export function isParticipantModerator(participant?: IParticipant) {
  556. return participant?.role === PARTICIPANT_ROLE.MODERATOR;
  557. }
  558. /**
  559. * Returns the dominant speaker participant.
  560. *
  561. * @param {(Function|Object)} stateful - The (whole) redux state or redux's
  562. * {@code getState} function to be used to retrieve the state features/base/participants.
  563. * @returns {IParticipant} - The participant from the redux store.
  564. */
  565. export function getDominantSpeakerParticipant(stateful: IStateful) {
  566. const state = toState(stateful)['features/base/participants'];
  567. const { dominantSpeaker } = state;
  568. if (!dominantSpeaker) {
  569. return undefined;
  570. }
  571. return getParticipantById(stateful, dominantSpeaker);
  572. }
  573. /**
  574. * Returns true if all of the meeting participants are moderators.
  575. *
  576. * @param {Object|Function} stateful -Object or function that can be resolved
  577. * to the Redux state.
  578. * @returns {boolean}
  579. */
  580. export function isEveryoneModerator(stateful: IStateful) {
  581. const state = toState(stateful)['features/base/participants'];
  582. return state.numberOfNonModeratorParticipants === 0;
  583. }
  584. /**
  585. * Checks a value and returns true if it's a preloaded icon object.
  586. *
  587. * @param {?string | ?Object} icon - The icon to check.
  588. * @returns {boolean}
  589. */
  590. export function isIconUrl(icon?: string | Object) {
  591. return Boolean(icon) && (typeof icon === 'object' || typeof icon === 'function');
  592. }
  593. /**
  594. * Returns true if the current local participant is a moderator in the
  595. * conference.
  596. *
  597. * @param {Object|Function} stateful - Object or function that can be resolved
  598. * to the Redux state.
  599. * @returns {boolean}
  600. */
  601. export function isLocalParticipantModerator(stateful: IStateful) {
  602. const state = toState(stateful)['features/base/participants'];
  603. const { local } = state;
  604. if (!local) {
  605. return false;
  606. }
  607. return isParticipantModerator(local);
  608. }
  609. /**
  610. * Resolves the first loadable avatar URL for a participant.
  611. *
  612. * @param {Object} participant - The participant to resolve avatars for.
  613. * @param {Store} store - Redux store.
  614. * @returns {?string}
  615. */
  616. async function _getFirstLoadableAvatarUrl(participant: IParticipant, store: IStore) {
  617. for (let i = 0; i < AVATAR_CHECKER_FUNCTIONS.length; i++) {
  618. const url = AVATAR_CHECKER_FUNCTIONS[i](participant, store);
  619. if (url !== null) {
  620. if (AVATAR_CHECKED_URLS.has(url)) {
  621. const { isLoadable, isUsingCORS } = AVATAR_CHECKED_URLS.get(url) || {};
  622. if (isLoadable) {
  623. return {
  624. isUsingCORS,
  625. src: url
  626. };
  627. }
  628. } else {
  629. try {
  630. const { corsAvatarURLs } = store.getState()['features/base/config'];
  631. const useCORS = isIconUrl(url) ? false : isCORSAvatarURL(url, corsAvatarURLs);
  632. const { isUsingCORS, src } = await preloadImage(url, useCORS);
  633. AVATAR_CHECKED_URLS.set(src, {
  634. isLoadable: true,
  635. isUsingCORS
  636. });
  637. return {
  638. isUsingCORS,
  639. src
  640. };
  641. } catch (e) {
  642. AVATAR_CHECKED_URLS.set(url, {
  643. isLoadable: false,
  644. isUsingCORS: false
  645. });
  646. }
  647. }
  648. }
  649. }
  650. return undefined;
  651. }
  652. /**
  653. * Get the participants queue with raised hands.
  654. *
  655. * @param {(Function|Object)} stateful - The (whole) redux state, or redux's
  656. * {@code getState} function to be used to retrieve the state
  657. * features/base/participants.
  658. * @returns {Array<Object>}
  659. */
  660. export function getRaiseHandsQueue(stateful: IStateful): Array<{ id: string; raisedHandTimestamp: number; }> {
  661. const { raisedHandsQueue } = toState(stateful)['features/base/participants'];
  662. return raisedHandsQueue;
  663. }
  664. /**
  665. * Returns whether the given participant has his hand raised or not.
  666. *
  667. * @param {Object} participant - The participant.
  668. * @returns {boolean} - Whether participant has raise hand or not.
  669. */
  670. export function hasRaisedHand(participant?: IParticipant): boolean {
  671. return Boolean(participant?.raisedHandTimestamp);
  672. }
  673. /**
  674. * Add people feature enabling/disabling.
  675. *
  676. * @param {Object|Function} stateful - Object or function that can be resolved
  677. * to the Redux state.
  678. * @returns {boolean}
  679. */
  680. export const addPeopleFeatureControl = (stateful: IStateful) => {
  681. const state = toState(stateful);
  682. return getFeatureFlag(state, ADD_PEOPLE_ENABLED, true)
  683. && (isAddPeopleEnabled(state) || isDialOutEnabled(state));
  684. };
  685. /**
  686. * Controls share dialog visibility.
  687. *
  688. * @param {boolean} addPeopleFeatureEnabled - Checks if add people functionality is enabled.
  689. * @param {Function} dispatch - The Redux dispatch function.
  690. * @returns {Function}
  691. */
  692. export const setShareDialogVisiblity = (addPeopleFeatureEnabled: boolean, dispatch: IStore['dispatch']) => {
  693. if (addPeopleFeatureEnabled) {
  694. dispatch(toggleShareDialog(false));
  695. } else {
  696. dispatch(toggleShareDialog(true));
  697. }
  698. };