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.js 18KB

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