Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

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