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.

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