Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

actions.js 15KB

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