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 14KB

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