You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

actions.ts 21KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720
  1. import { IStore } from '../../app/types';
  2. import { showNotification } from '../../notifications/actions';
  3. import { NOTIFICATION_TIMEOUT_TYPE } from '../../notifications/constants';
  4. import { set } from '../redux/functions';
  5. import {
  6. DOMINANT_SPEAKER_CHANGED,
  7. GRANT_MODERATOR,
  8. HIDDEN_PARTICIPANT_JOINED,
  9. HIDDEN_PARTICIPANT_LEFT,
  10. KICK_PARTICIPANT,
  11. LOCAL_PARTICIPANT_AUDIO_LEVEL_CHANGED,
  12. LOCAL_PARTICIPANT_RAISE_HAND,
  13. MUTE_REMOTE_PARTICIPANT,
  14. OVERWRITE_PARTICIPANTS_NAMES,
  15. OVERWRITE_PARTICIPANT_NAME,
  16. PARTICIPANT_ID_CHANGED,
  17. PARTICIPANT_JOINED,
  18. PARTICIPANT_KICKED,
  19. PARTICIPANT_LEFT,
  20. PARTICIPANT_UPDATED,
  21. PIN_PARTICIPANT,
  22. RAISE_HAND_UPDATED,
  23. SCREENSHARE_PARTICIPANT_NAME_CHANGED,
  24. SET_LOADABLE_AVATAR_URL,
  25. SET_LOCAL_PARTICIPANT_RECORDING_STATUS
  26. } from './actionTypes';
  27. import {
  28. DISCO_REMOTE_CONTROL_FEATURE
  29. } from './constants';
  30. import {
  31. getLocalParticipant,
  32. getNormalizedDisplayName,
  33. getParticipantById,
  34. getParticipantDisplayName,
  35. getVirtualScreenshareParticipantOwnerId
  36. } from './functions';
  37. import logger from './logger';
  38. import { Participant } from './types';
  39. /**
  40. * Create an action for when dominant speaker changes.
  41. *
  42. * @param {string} dominantSpeaker - Participant ID of the dominant speaker.
  43. * @param {Array<string>} previousSpeakers - Participant IDs of the previous speakers.
  44. * @param {boolean} silence - Whether the dominant speaker is silent or not.
  45. * @param {JitsiConference} conference - The {@code JitsiConference} associated
  46. * with the participant identified by the specified {@code id}. Only the local
  47. * participant is allowed to not specify an associated {@code JitsiConference}
  48. * instance.
  49. * @returns {{
  50. * type: DOMINANT_SPEAKER_CHANGED,
  51. * participant: {
  52. * conference: JitsiConference,
  53. * id: string,
  54. * previousSpeakers: Array<string>,
  55. * silence: boolean
  56. * }
  57. * }}
  58. */
  59. export function dominantSpeakerChanged(
  60. dominantSpeaker: string, previousSpeakers: string[], silence: boolean, conference: any) {
  61. return {
  62. type: DOMINANT_SPEAKER_CHANGED,
  63. participant: {
  64. conference,
  65. id: dominantSpeaker,
  66. previousSpeakers,
  67. silence
  68. }
  69. };
  70. }
  71. /**
  72. * Create an action for granting moderator to a participant.
  73. *
  74. * @param {string} id - Participant's ID.
  75. * @returns {{
  76. * type: GRANT_MODERATOR,
  77. * id: string
  78. * }}
  79. */
  80. export function grantModerator(id: string) {
  81. return {
  82. type: GRANT_MODERATOR,
  83. id
  84. };
  85. }
  86. /**
  87. * Create an action for removing a participant from the conference.
  88. *
  89. * @param {string} id - Participant's ID.
  90. * @returns {{
  91. * type: KICK_PARTICIPANT,
  92. * id: string
  93. * }}
  94. */
  95. export function kickParticipant(id: string) {
  96. return {
  97. type: KICK_PARTICIPANT,
  98. id
  99. };
  100. }
  101. /**
  102. * Creates an action to signal the connection status of the local participant
  103. * has changed.
  104. *
  105. * @param {string} connectionStatus - The current connection status of the local
  106. * participant, as enumerated by the library's participantConnectionStatus
  107. * constants.
  108. * @returns {Function}
  109. */
  110. export function localParticipantConnectionStatusChanged(connectionStatus: string) {
  111. return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
  112. const participant = getLocalParticipant(getState);
  113. if (participant) {
  114. return dispatch(participantConnectionStatusChanged(
  115. participant.id,
  116. connectionStatus));
  117. }
  118. };
  119. }
  120. /**
  121. * Action to signal that the ID of local participant has changed. It happens
  122. * when the local participant joins a new conference or leaves an existing
  123. * conference.
  124. *
  125. * @param {string} id - New ID for local participant.
  126. * @returns {Function}
  127. */
  128. export function localParticipantIdChanged(id: string) {
  129. return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
  130. const participant = getLocalParticipant(getState);
  131. if (participant) {
  132. return dispatch({
  133. type: PARTICIPANT_ID_CHANGED,
  134. // XXX A participant is identified by an id-conference pair.
  135. // Only the local participant is with an undefined conference.
  136. conference: undefined,
  137. newValue: id,
  138. oldValue: participant.id
  139. });
  140. }
  141. };
  142. }
  143. /**
  144. * Action to signal that a local participant has joined.
  145. *
  146. * @param {Participant} participant={} - Information about participant.
  147. * @returns {{
  148. * type: PARTICIPANT_JOINED,
  149. * participant: Participant
  150. * }}
  151. */
  152. export function localParticipantJoined(participant: Participant = { id: '' }) {
  153. return participantJoined(set(participant, 'local', true));
  154. }
  155. /**
  156. * Action to remove a local participant.
  157. *
  158. * @returns {Function}
  159. */
  160. export function localParticipantLeft() {
  161. return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
  162. const participant = getLocalParticipant(getState);
  163. if (participant) {
  164. return (
  165. dispatch(
  166. participantLeft(
  167. participant.id,
  168. // XXX Only the local participant is allowed to leave
  169. // without stating the JitsiConference instance because
  170. // the local participant is uniquely identified by the
  171. // very fact that there is only one local participant
  172. // (and the fact that the local participant "joins" at
  173. // the beginning of the app and "leaves" at the end of
  174. // the app).
  175. undefined)));
  176. }
  177. };
  178. }
  179. /**
  180. * Action to signal the role of the local participant has changed. It can happen
  181. * when the participant has joined a conference, even before a non-default local
  182. * id has been set, or after a moderator leaves.
  183. *
  184. * @param {string} role - The new role of the local participant.
  185. * @returns {Function}
  186. */
  187. export function localParticipantRoleChanged(role: string) {
  188. return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
  189. const participant = getLocalParticipant(getState);
  190. if (participant) {
  191. return dispatch(participantRoleChanged(participant.id, role));
  192. }
  193. };
  194. }
  195. /**
  196. * Create an action for muting another participant in the conference.
  197. *
  198. * @param {string} id - Participant's ID.
  199. * @param {MEDIA_TYPE} mediaType - The media to mute.
  200. * @returns {{
  201. * type: MUTE_REMOTE_PARTICIPANT,
  202. * id: string,
  203. * mediaType: MEDIA_TYPE
  204. * }}
  205. */
  206. export function muteRemoteParticipant(id: string, mediaType: string) {
  207. return {
  208. type: MUTE_REMOTE_PARTICIPANT,
  209. id,
  210. mediaType
  211. };
  212. }
  213. /**
  214. * Action to update a participant's connection status.
  215. *
  216. * @param {string} id - Participant's ID.
  217. * @param {string} connectionStatus - The new connection status of the
  218. * participant.
  219. * @returns {{
  220. * type: PARTICIPANT_UPDATED,
  221. * participant: {
  222. * connectionStatus: string,
  223. * id: string
  224. * }
  225. * }}
  226. */
  227. export function participantConnectionStatusChanged(id: string, connectionStatus: string) {
  228. return {
  229. type: PARTICIPANT_UPDATED,
  230. participant: {
  231. connectionStatus,
  232. id
  233. }
  234. };
  235. }
  236. /**
  237. * Action to signal that a participant has joined.
  238. *
  239. * @param {Participant} participant - Information about participant.
  240. * @returns {{
  241. * type: PARTICIPANT_JOINED,
  242. * participant: Participant
  243. * }}
  244. */
  245. export function participantJoined(participant: Participant) {
  246. // Only the local participant is not identified with an id-conference pair.
  247. if (participant.local) {
  248. return {
  249. type: PARTICIPANT_JOINED,
  250. participant
  251. };
  252. }
  253. // In other words, a remote participant is identified with an id-conference
  254. // pair.
  255. const { conference } = participant;
  256. if (!conference) {
  257. throw Error(
  258. 'A remote participant must be associated with a JitsiConference!');
  259. }
  260. return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
  261. // A remote participant is only expected to join in a joined or joining
  262. // conference. The following check is really necessary because a
  263. // JitsiConference may have moved into leaving but may still manage to
  264. // sneak a PARTICIPANT_JOINED in if its leave is delayed for any purpose
  265. // (which is not outragous given that leaving involves network
  266. // requests.)
  267. const stateFeaturesBaseConference
  268. = getState()['features/base/conference'];
  269. if (conference === stateFeaturesBaseConference.conference
  270. || conference === stateFeaturesBaseConference.joining) {
  271. return dispatch({
  272. type: PARTICIPANT_JOINED,
  273. participant
  274. });
  275. }
  276. };
  277. }
  278. /**
  279. * Updates the features of a remote participant.
  280. *
  281. * @param {JitsiParticipant} jitsiParticipant - The ID of the participant.
  282. * @returns {{
  283. * type: PARTICIPANT_UPDATED,
  284. * participant: Participant
  285. * }}
  286. */
  287. export function updateRemoteParticipantFeatures(jitsiParticipant: any) {
  288. return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
  289. if (!jitsiParticipant) {
  290. return;
  291. }
  292. const id = jitsiParticipant.getId();
  293. jitsiParticipant.getFeatures()
  294. .then((features: Map<string, string>) => {
  295. const supportsRemoteControl = features.has(DISCO_REMOTE_CONTROL_FEATURE);
  296. const participant = getParticipantById(getState(), id);
  297. if (!participant || participant.local) {
  298. return;
  299. }
  300. if (participant?.supportsRemoteControl !== supportsRemoteControl) {
  301. return dispatch({
  302. type: PARTICIPANT_UPDATED,
  303. participant: {
  304. id,
  305. supportsRemoteControl
  306. }
  307. });
  308. }
  309. })
  310. .catch((error: any) => {
  311. logger.error(`Failed to get participant features for ${id}!`, error);
  312. });
  313. };
  314. }
  315. /**
  316. * Action to signal that a hidden participant has joined the conference.
  317. *
  318. * @param {string} id - The id of the participant.
  319. * @param {string} displayName - The display name, or undefined when
  320. * unknown.
  321. * @returns {{
  322. * type: HIDDEN_PARTICIPANT_JOINED,
  323. * displayName: string,
  324. * id: string
  325. * }}
  326. */
  327. export function hiddenParticipantJoined(id: string, displayName: string) {
  328. return {
  329. type: HIDDEN_PARTICIPANT_JOINED,
  330. id,
  331. displayName
  332. };
  333. }
  334. /**
  335. * Action to signal that a hidden participant has left the conference.
  336. *
  337. * @param {string} id - The id of the participant.
  338. * @returns {{
  339. * type: HIDDEN_PARTICIPANT_LEFT,
  340. * id: string
  341. * }}
  342. */
  343. export function hiddenParticipantLeft(id: string) {
  344. return {
  345. type: HIDDEN_PARTICIPANT_LEFT,
  346. id
  347. };
  348. }
  349. /**
  350. * Action to signal that a participant has left.
  351. *
  352. * @param {string} id - Participant's ID.
  353. * @param {JitsiConference} conference - The {@code JitsiConference} associated
  354. * with the participant identified by the specified {@code id}. Only the local
  355. * participant is allowed to not specify an associated {@code JitsiConference}
  356. * instance.
  357. * @param {Object} participantLeftProps - Other participant properties.
  358. * @typedef {Object} participantLeftProps
  359. * @param {boolean} participantLeftProps.isReplaced - Whether the participant is to be replaced in the meeting.
  360. * @param {boolean} participantLeftProps.isVirtualScreenshareParticipant - Whether the participant is a
  361. * virtual screen share participant.
  362. * @param {boolean} participantLeftProps.isFakeParticipant - Whether the participant is a fake participant.
  363. *
  364. * @returns {{
  365. * type: PARTICIPANT_LEFT,
  366. * participant: {
  367. * conference: JitsiConference,
  368. * id: string
  369. * }
  370. * }}
  371. */
  372. export function participantLeft(id: string, conference: any, participantLeftProps: any = {}) {
  373. return {
  374. type: PARTICIPANT_LEFT,
  375. participant: {
  376. conference,
  377. id,
  378. isReplaced: participantLeftProps.isReplaced,
  379. isVirtualScreenshareParticipant: participantLeftProps.isVirtualScreenshareParticipant,
  380. isWhiteboard: participantLeftProps.isWhiteboard,
  381. isFakeParticipant: participantLeftProps.isFakeParticipant
  382. }
  383. };
  384. }
  385. /**
  386. * Action to signal that a participant's presence status has changed.
  387. *
  388. * @param {string} id - Participant's ID.
  389. * @param {string} presence - Participant's new presence status.
  390. * @returns {{
  391. * type: PARTICIPANT_UPDATED,
  392. * participant: {
  393. * id: string,
  394. * presence: string
  395. * }
  396. * }}
  397. */
  398. export function participantPresenceChanged(id: string, presence: string) {
  399. return participantUpdated({
  400. id,
  401. presence
  402. });
  403. }
  404. /**
  405. * Action to signal that a participant's role has changed.
  406. *
  407. * @param {string} id - Participant's ID.
  408. * @param {PARTICIPANT_ROLE} role - Participant's new role.
  409. * @returns {{
  410. * type: PARTICIPANT_UPDATED,
  411. * participant: {
  412. * id: string,
  413. * role: PARTICIPANT_ROLE
  414. * }
  415. * }}
  416. */
  417. export function participantRoleChanged(id: string, role: string) {
  418. return participantUpdated({
  419. id,
  420. role
  421. });
  422. }
  423. /**
  424. * Action to signal that a participant's display name has changed.
  425. *
  426. * @param {string} id - Screenshare participant's ID.
  427. * @param {name} name - The new display name of the screenshare participant's owner.
  428. * @returns {{
  429. * type: SCREENSHARE_PARTICIPANT_NAME_CHANGED,
  430. * id: string,
  431. * name: string
  432. * }}
  433. */
  434. export function screenshareParticipantDisplayNameChanged(id: string, name: string) {
  435. return {
  436. type: SCREENSHARE_PARTICIPANT_NAME_CHANGED,
  437. id,
  438. name
  439. };
  440. }
  441. /**
  442. * Action to signal that some of participant properties has been changed.
  443. *
  444. * @param {Participant} participant={} - Information about participant. To
  445. * identify the participant the object should contain either property id with
  446. * value the id of the participant or property local with value true (if the
  447. * local participant hasn't joined the conference yet).
  448. * @returns {{
  449. * type: PARTICIPANT_UPDATED,
  450. * participant: Participant
  451. * }}
  452. */
  453. export function participantUpdated(participant: Participant = { id: '' }) {
  454. const participantToUpdate = {
  455. ...participant
  456. };
  457. if (participant.name) {
  458. participantToUpdate.name = getNormalizedDisplayName(participant.name);
  459. }
  460. return {
  461. type: PARTICIPANT_UPDATED,
  462. participant: participantToUpdate
  463. };
  464. }
  465. /**
  466. * Action to signal that a participant has muted us.
  467. *
  468. * @param {JitsiParticipant} participant - Information about participant.
  469. * @param {JitsiLocalTrack} track - Information about the track that has been muted.
  470. * @returns {Promise}
  471. */
  472. export function participantMutedUs(participant: any, track: any) {
  473. return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
  474. if (!participant) {
  475. return;
  476. }
  477. const isAudio = track.isAudioTrack();
  478. dispatch(showNotification({
  479. titleKey: isAudio ? 'notify.mutedRemotelyTitle' : 'notify.videoMutedRemotelyTitle',
  480. titleArguments: {
  481. participantDisplayName: getParticipantDisplayName(getState, participant.getId())
  482. }
  483. }, NOTIFICATION_TIMEOUT_TYPE.MEDIUM));
  484. };
  485. }
  486. /**
  487. * Action to create a virtual screenshare participant.
  488. *
  489. * @param {(string)} sourceName - JitsiTrack instance.
  490. * @param {(boolean)} local - JitsiTrack instance.
  491. * @returns {Function}
  492. */
  493. export function createVirtualScreenshareParticipant(sourceName: string, local: boolean) {
  494. return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
  495. const state = getState();
  496. const ownerId = getVirtualScreenshareParticipantOwnerId(sourceName);
  497. const ownerName = getParticipantDisplayName(state, ownerId);
  498. dispatch(participantJoined({
  499. conference: state['features/base/conference'].conference,
  500. id: sourceName,
  501. isVirtualScreenshareParticipant: true,
  502. isLocalScreenShare: local,
  503. name: ownerName
  504. }));
  505. };
  506. }
  507. /**
  508. * Action to signal that a participant had been kicked.
  509. *
  510. * @param {JitsiParticipant} kicker - Information about participant performing the kick.
  511. * @param {JitsiParticipant} kicked - Information about participant that was kicked.
  512. * @returns {Promise}
  513. */
  514. export function participantKicked(kicker: any, kicked: any) {
  515. return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
  516. dispatch({
  517. type: PARTICIPANT_KICKED,
  518. kicked: kicked.getId(),
  519. kicker: kicker?.getId()
  520. });
  521. if (kicked.isReplaced?.()) {
  522. return;
  523. }
  524. dispatch(showNotification({
  525. titleArguments: {
  526. kicked:
  527. getParticipantDisplayName(getState, kicked.getId()),
  528. kicker:
  529. getParticipantDisplayName(getState, kicker.getId())
  530. },
  531. titleKey: 'notify.kickParticipant'
  532. }, NOTIFICATION_TIMEOUT_TYPE.MEDIUM));
  533. };
  534. }
  535. /**
  536. * Create an action which pins a conference participant.
  537. *
  538. * @param {string|null} id - The ID of the conference participant to pin or null
  539. * if none of the conference's participants are to be pinned.
  540. * @returns {{
  541. * type: PIN_PARTICIPANT,
  542. * participant: {
  543. * id: string
  544. * }
  545. * }}
  546. */
  547. export function pinParticipant(id: string | null) {
  548. return {
  549. type: PIN_PARTICIPANT,
  550. participant: {
  551. id
  552. }
  553. };
  554. }
  555. /**
  556. * Creates an action which notifies the app that the loadable URL of the avatar of a participant got updated.
  557. *
  558. * @param {string} participantId - The ID of the participant.
  559. * @param {string} url - The new URL.
  560. * @param {boolean} useCORS - Indicates whether we need to use CORS for this URL.
  561. * @returns {{
  562. * type: SET_LOADABLE_AVATAR_URL,
  563. * participant: {
  564. * id: string,
  565. * loadableAvatarUrl: string,
  566. * loadableAvatarUrlUseCORS: boolean
  567. * }
  568. * }}
  569. */
  570. export function setLoadableAvatarUrl(participantId: string, url: string, useCORS: boolean) {
  571. return {
  572. type: SET_LOADABLE_AVATAR_URL,
  573. participant: {
  574. id: participantId,
  575. loadableAvatarUrl: url,
  576. loadableAvatarUrlUseCORS: useCORS
  577. }
  578. };
  579. }
  580. /**
  581. * Raise hand for the local participant.
  582. *
  583. * @param {boolean} enabled - Raise or lower hand.
  584. * @returns {{
  585. * type: LOCAL_PARTICIPANT_RAISE_HAND,
  586. * raisedHandTimestamp: number
  587. * }}
  588. */
  589. export function raiseHand(enabled: boolean) {
  590. return {
  591. type: LOCAL_PARTICIPANT_RAISE_HAND,
  592. raisedHandTimestamp: enabled ? Date.now() : 0
  593. };
  594. }
  595. /**
  596. * Update raise hand queue of participants.
  597. *
  598. * @param {Object} participant - Participant that updated raised hand.
  599. * @returns {{
  600. * type: RAISE_HAND_UPDATED,
  601. * participant: Object
  602. * }}
  603. */
  604. export function raiseHandUpdateQueue(participant: Participant) {
  605. return {
  606. type: RAISE_HAND_UPDATED,
  607. participant
  608. };
  609. }
  610. /**
  611. * Notifies if the local participant audio level has changed.
  612. *
  613. * @param {number} level - The audio level.
  614. * @returns {{
  615. * type: LOCAL_PARTICIPANT_AUDIO_LEVEL_CHANGED,
  616. * level: number
  617. * }}
  618. */
  619. export function localParticipantAudioLevelChanged(level: number) {
  620. return {
  621. type: LOCAL_PARTICIPANT_AUDIO_LEVEL_CHANGED,
  622. level
  623. };
  624. }
  625. /**
  626. * Overwrites the name of the participant with the given id.
  627. *
  628. * @param {string} id - Participant id;.
  629. * @param {string} name - New participant name.
  630. * @returns {Object}
  631. */
  632. export function overwriteParticipantName(id: string, name: string) {
  633. return {
  634. type: OVERWRITE_PARTICIPANT_NAME,
  635. id,
  636. name
  637. };
  638. }
  639. /**
  640. * Overwrites the names of the given participants.
  641. *
  642. * @param {Array<Object>} participantList - The list of participants to overwrite.
  643. * @returns {Object}
  644. */
  645. export function overwriteParticipantsNames(participantList: Participant[]) {
  646. return {
  647. type: OVERWRITE_PARTICIPANTS_NAMES,
  648. participantList
  649. };
  650. }
  651. /**
  652. * Local video recording status for the local participant.
  653. *
  654. * @param {boolean} recording - If local recording is ongoing.
  655. * @param {boolean} onlySelf - If recording only local streams.
  656. * @returns {{
  657. * type: SET_LOCAL_PARTICIPANT_RECORDING_STATUS,
  658. * recording: boolean
  659. * }}
  660. */
  661. export function updateLocalRecordingStatus(recording: boolean, onlySelf: boolean) {
  662. return {
  663. type: SET_LOCAL_PARTICIPANT_RECORDING_STATUS,
  664. recording,
  665. onlySelf
  666. };
  667. }