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

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