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.

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