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

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