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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706
  1. import { NOTIFICATION_TIMEOUT_TYPE, 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. LOCAL_PARTICIPANT_AUDIO_LEVEL_CHANGED,
  10. LOCAL_PARTICIPANT_RAISE_HAND,
  11. MUTE_REMOTE_PARTICIPANT,
  12. OVERWRITE_PARTICIPANT_NAME,
  13. OVERWRITE_PARTICIPANTS_NAMES,
  14. PARTICIPANT_ID_CHANGED,
  15. PARTICIPANT_JOINED,
  16. PARTICIPANT_KICKED,
  17. PARTICIPANT_LEFT,
  18. PARTICIPANT_UPDATED,
  19. PIN_PARTICIPANT,
  20. RAISE_HAND_UPDATED,
  21. SCREENSHARE_PARTICIPANT_NAME_CHANGED,
  22. SET_LOADABLE_AVATAR_URL,
  23. SET_LOCAL_PARTICIPANT_RECORDING_STATUS
  24. } from './actionTypes';
  25. import {
  26. DISCO_REMOTE_CONTROL_FEATURE
  27. } from './constants';
  28. import {
  29. getLocalParticipant,
  30. getVirtualScreenshareParticipantOwnerId,
  31. getNormalizedDisplayName,
  32. getParticipantDisplayName,
  33. getParticipantById
  34. } from './functions';
  35. import logger from './logger';
  36. /**
  37. * Create an action for when dominant speaker changes.
  38. *
  39. * @param {string} dominantSpeaker - Participant ID of the dominant speaker.
  40. * @param {Array<string>} previousSpeakers - Participant IDs of the previous speakers.
  41. * @param {JitsiConference} conference - The {@code JitsiConference} associated
  42. * with the participant identified by the specified {@code id}. Only the local
  43. * participant is allowed to not specify an associated {@code JitsiConference}
  44. * instance.
  45. * @returns {{
  46. * type: DOMINANT_SPEAKER_CHANGED,
  47. * participant: {
  48. * conference: JitsiConference,
  49. * id: string,
  50. * previousSpeakers: Array<string>
  51. * }
  52. * }}
  53. */
  54. export function dominantSpeakerChanged(dominantSpeaker, previousSpeakers, conference) {
  55. return {
  56. type: DOMINANT_SPEAKER_CHANGED,
  57. participant: {
  58. conference,
  59. id: dominantSpeaker,
  60. previousSpeakers
  61. }
  62. };
  63. }
  64. /**
  65. * Create an action for granting moderator to a participant.
  66. *
  67. * @param {string} id - Participant's ID.
  68. * @returns {{
  69. * type: GRANT_MODERATOR,
  70. * id: string
  71. * }}
  72. */
  73. export function grantModerator(id) {
  74. return {
  75. type: GRANT_MODERATOR,
  76. id
  77. };
  78. }
  79. /**
  80. * Create an action for removing a participant from the conference.
  81. *
  82. * @param {string} id - Participant's ID.
  83. * @returns {{
  84. * type: KICK_PARTICIPANT,
  85. * id: string
  86. * }}
  87. */
  88. export function kickParticipant(id) {
  89. return {
  90. type: KICK_PARTICIPANT,
  91. id
  92. };
  93. }
  94. /**
  95. * Creates an action to signal the connection status of the local participant
  96. * has changed.
  97. *
  98. * @param {string} connectionStatus - The current connection status of the local
  99. * participant, as enumerated by the library's participantConnectionStatus
  100. * constants.
  101. * @returns {Function}
  102. */
  103. export function localParticipantConnectionStatusChanged(connectionStatus) {
  104. return (dispatch, getState) => {
  105. const participant = getLocalParticipant(getState);
  106. if (participant) {
  107. return dispatch(participantConnectionStatusChanged(
  108. participant.id,
  109. connectionStatus));
  110. }
  111. };
  112. }
  113. /**
  114. * Action to signal that the ID of local participant has changed. It happens
  115. * when the local participant joins a new conference or leaves an existing
  116. * conference.
  117. *
  118. * @param {string} id - New ID for local participant.
  119. * @returns {Function}
  120. */
  121. export function localParticipantIdChanged(id) {
  122. return (dispatch, getState) => {
  123. const participant = getLocalParticipant(getState);
  124. if (participant) {
  125. return dispatch({
  126. type: PARTICIPANT_ID_CHANGED,
  127. // XXX A participant is identified by an id-conference pair.
  128. // Only the local participant is with an undefined conference.
  129. conference: undefined,
  130. newValue: id,
  131. oldValue: participant.id
  132. });
  133. }
  134. };
  135. }
  136. /**
  137. * Action to signal that a local participant has joined.
  138. *
  139. * @param {Participant} participant={} - Information about participant.
  140. * @returns {{
  141. * type: PARTICIPANT_JOINED,
  142. * participant: Participant
  143. * }}
  144. */
  145. export function localParticipantJoined(participant = {}) {
  146. return participantJoined(set(participant, 'local', true));
  147. }
  148. /**
  149. * Action to remove a local participant.
  150. *
  151. * @returns {Function}
  152. */
  153. export function localParticipantLeft() {
  154. return (dispatch, getState) => {
  155. const participant = getLocalParticipant(getState);
  156. if (participant) {
  157. return (
  158. dispatch(
  159. participantLeft(
  160. participant.id,
  161. // XXX Only the local participant is allowed to leave
  162. // without stating the JitsiConference instance because
  163. // the local participant is uniquely identified by the
  164. // very fact that there is only one local participant
  165. // (and the fact that the local participant "joins" at
  166. // the beginning of the app and "leaves" at the end of
  167. // the app).
  168. undefined)));
  169. }
  170. };
  171. }
  172. /**
  173. * Action to signal the role of the local participant has changed. It can happen
  174. * when the participant has joined a conference, even before a non-default local
  175. * id has been set, or after a moderator leaves.
  176. *
  177. * @param {string} role - The new role of the local participant.
  178. * @returns {Function}
  179. */
  180. export function localParticipantRoleChanged(role) {
  181. return (dispatch, getState) => {
  182. const participant = getLocalParticipant(getState);
  183. if (participant) {
  184. return dispatch(participantRoleChanged(participant.id, role));
  185. }
  186. };
  187. }
  188. /**
  189. * Create an action for muting another participant in the conference.
  190. *
  191. * @param {string} id - Participant's ID.
  192. * @param {MEDIA_TYPE} mediaType - The media to mute.
  193. * @returns {{
  194. * type: MUTE_REMOTE_PARTICIPANT,
  195. * id: string,
  196. * mediaType: MEDIA_TYPE
  197. * }}
  198. */
  199. export function muteRemoteParticipant(id, mediaType) {
  200. return {
  201. type: MUTE_REMOTE_PARTICIPANT,
  202. id,
  203. mediaType
  204. };
  205. }
  206. /**
  207. * Action to update a participant's connection status.
  208. *
  209. * @param {string} id - Participant's ID.
  210. * @param {string} connectionStatus - The new connection status of the
  211. * participant.
  212. * @returns {{
  213. * type: PARTICIPANT_UPDATED,
  214. * participant: {
  215. * connectionStatus: string,
  216. * id: string
  217. * }
  218. * }}
  219. */
  220. export function participantConnectionStatusChanged(id, connectionStatus) {
  221. return {
  222. type: PARTICIPANT_UPDATED,
  223. participant: {
  224. connectionStatus,
  225. id
  226. }
  227. };
  228. }
  229. /**
  230. * Action to signal that a participant has joined.
  231. *
  232. * @param {Participant} participant - Information about participant.
  233. * @returns {{
  234. * type: PARTICIPANT_JOINED,
  235. * participant: Participant
  236. * }}
  237. */
  238. export function participantJoined(participant) {
  239. // Only the local participant is not identified with an id-conference pair.
  240. if (participant.local) {
  241. return {
  242. type: PARTICIPANT_JOINED,
  243. participant
  244. };
  245. }
  246. // In other words, a remote participant is identified with an id-conference
  247. // pair.
  248. const { conference } = participant;
  249. if (!conference) {
  250. throw Error(
  251. 'A remote participant must be associated with a JitsiConference!');
  252. }
  253. return (dispatch, getState) => {
  254. // A remote participant is only expected to join in a joined or joining
  255. // conference. The following check is really necessary because a
  256. // JitsiConference may have moved into leaving but may still manage to
  257. // sneak a PARTICIPANT_JOINED in if its leave is delayed for any purpose
  258. // (which is not outragous given that leaving involves network
  259. // requests.)
  260. const stateFeaturesBaseConference
  261. = getState()['features/base/conference'];
  262. if (conference === stateFeaturesBaseConference.conference
  263. || conference === stateFeaturesBaseConference.joining) {
  264. return dispatch({
  265. type: PARTICIPANT_JOINED,
  266. participant
  267. });
  268. }
  269. };
  270. }
  271. /**
  272. * Updates the features of a remote participant.
  273. *
  274. * @param {JitsiParticipant} jitsiParticipant - The ID of the participant.
  275. * @returns {{
  276. * type: PARTICIPANT_UPDATED,
  277. * participant: Participant
  278. * }}
  279. */
  280. export function updateRemoteParticipantFeatures(jitsiParticipant) {
  281. return (dispatch, getState) => {
  282. if (!jitsiParticipant) {
  283. return;
  284. }
  285. const id = jitsiParticipant.getId();
  286. jitsiParticipant.getFeatures()
  287. .then(features => {
  288. const supportsRemoteControl = features.has(DISCO_REMOTE_CONTROL_FEATURE);
  289. const participant = getParticipantById(getState(), id);
  290. if (!participant || participant.local) {
  291. return;
  292. }
  293. if (participant?.supportsRemoteControl !== supportsRemoteControl) {
  294. return dispatch({
  295. type: PARTICIPANT_UPDATED,
  296. participant: {
  297. id,
  298. supportsRemoteControl
  299. }
  300. });
  301. }
  302. })
  303. .catch(error => {
  304. logger.error(`Failed to get participant features for ${id}!`, error);
  305. });
  306. };
  307. }
  308. /**
  309. * Action to signal that a hidden participant has joined the conference.
  310. *
  311. * @param {string} id - The id of the participant.
  312. * @param {string} displayName - The display name, or undefined when
  313. * unknown.
  314. * @returns {{
  315. * type: HIDDEN_PARTICIPANT_JOINED,
  316. * displayName: string,
  317. * id: string
  318. * }}
  319. */
  320. export function hiddenParticipantJoined(id, displayName) {
  321. return {
  322. type: HIDDEN_PARTICIPANT_JOINED,
  323. id,
  324. displayName
  325. };
  326. }
  327. /**
  328. * Action to signal that a hidden participant has left the conference.
  329. *
  330. * @param {string} id - The id of the participant.
  331. * @returns {{
  332. * type: HIDDEN_PARTICIPANT_LEFT,
  333. * id: string
  334. * }}
  335. */
  336. export function hiddenParticipantLeft(id) {
  337. return {
  338. type: HIDDEN_PARTICIPANT_LEFT,
  339. id
  340. };
  341. }
  342. /**
  343. * Action to signal that a participant has left.
  344. *
  345. * @param {string} id - Participant's ID.
  346. * @param {JitsiConference} conference - The {@code JitsiConference} associated
  347. * with the participant identified by the specified {@code id}. Only the local
  348. * participant is allowed to not specify an associated {@code JitsiConference}
  349. * instance.
  350. * @param {boolean} isReplaced - Whether the participant is to be replaced in the meeting.
  351. * @param {boolean} isVirtualScreenshareParticipant - Whether the participant is a virtual screen share participant.
  352. * @returns {{
  353. * type: PARTICIPANT_LEFT,
  354. * participant: {
  355. * conference: JitsiConference,
  356. * id: string
  357. * }
  358. * }}
  359. */
  360. export function participantLeft(id, conference, isReplaced, isVirtualScreenshareParticipant) {
  361. return {
  362. type: PARTICIPANT_LEFT,
  363. participant: {
  364. conference,
  365. id,
  366. isReplaced,
  367. isVirtualScreenshareParticipant
  368. }
  369. };
  370. }
  371. /**
  372. * Action to signal that a participant's presence status has changed.
  373. *
  374. * @param {string} id - Participant's ID.
  375. * @param {string} presence - Participant's new presence status.
  376. * @returns {{
  377. * type: PARTICIPANT_UPDATED,
  378. * participant: {
  379. * id: string,
  380. * presence: string
  381. * }
  382. * }}
  383. */
  384. export function participantPresenceChanged(id, presence) {
  385. return participantUpdated({
  386. id,
  387. presence
  388. });
  389. }
  390. /**
  391. * Action to signal that a participant's role has changed.
  392. *
  393. * @param {string} id - Participant's ID.
  394. * @param {PARTICIPANT_ROLE} role - Participant's new role.
  395. * @returns {{
  396. * type: PARTICIPANT_UPDATED,
  397. * participant: {
  398. * id: string,
  399. * role: PARTICIPANT_ROLE
  400. * }
  401. * }}
  402. */
  403. export function participantRoleChanged(id, role) {
  404. return participantUpdated({
  405. id,
  406. role
  407. });
  408. }
  409. /**
  410. * Action to signal that a participant's display name has changed.
  411. *
  412. * @param {string} id - Screenshare participant's ID.
  413. * @param {name} name - The new display name of the screenshare participant's owner.
  414. * @returns {{
  415. * type: SCREENSHARE_PARTICIPANT_NAME_CHANGED,
  416. * id: string,
  417. * name: string
  418. * }}
  419. */
  420. export function screenshareParticipantDisplayNameChanged(id, name) {
  421. return {
  422. type: SCREENSHARE_PARTICIPANT_NAME_CHANGED,
  423. id,
  424. name
  425. };
  426. }
  427. /**
  428. * Action to signal that some of participant properties has been changed.
  429. *
  430. * @param {Participant} participant={} - Information about participant. To
  431. * identify the participant the object should contain either property id with
  432. * value the id of the participant or property local with value true (if the
  433. * local participant hasn't joined the conference yet).
  434. * @returns {{
  435. * type: PARTICIPANT_UPDATED,
  436. * participant: Participant
  437. * }}
  438. */
  439. export function participantUpdated(participant = {}) {
  440. const participantToUpdate = {
  441. ...participant
  442. };
  443. if (participant.name) {
  444. participantToUpdate.name = getNormalizedDisplayName(participant.name);
  445. }
  446. return {
  447. type: PARTICIPANT_UPDATED,
  448. participant: participantToUpdate
  449. };
  450. }
  451. /**
  452. * Action to signal that a participant has muted us.
  453. *
  454. * @param {JitsiParticipant} participant - Information about participant.
  455. * @param {JitsiLocalTrack} track - Information about the track that has been muted.
  456. * @returns {Promise}
  457. */
  458. export function participantMutedUs(participant, track) {
  459. return (dispatch, getState) => {
  460. if (!participant) {
  461. return;
  462. }
  463. const isAudio = track.isAudioTrack();
  464. dispatch(showNotification({
  465. titleKey: isAudio ? 'notify.mutedRemotelyTitle' : 'notify.videoMutedRemotelyTitle',
  466. titleArguments: {
  467. participantDisplayName: getParticipantDisplayName(getState, participant.getId())
  468. }
  469. }, NOTIFICATION_TIMEOUT_TYPE.MEDIUM));
  470. };
  471. }
  472. /**
  473. * Action to create a virtual screenshare participant.
  474. *
  475. * @param {(string)} sourceName - JitsiTrack instance.
  476. * @param {(boolean)} local - JitsiTrack instance.
  477. * @returns {Function}
  478. */
  479. export function createVirtualScreenshareParticipant(sourceName, local) {
  480. return (dispatch, getState) => {
  481. const state = getState();
  482. const ownerId = getVirtualScreenshareParticipantOwnerId(sourceName);
  483. const ownerName = getParticipantDisplayName(state, ownerId);
  484. dispatch(participantJoined({
  485. conference: state['features/base/conference'].conference,
  486. id: sourceName,
  487. isVirtualScreenshareParticipant: true,
  488. isLocalScreenShare: local,
  489. name: ownerName
  490. }));
  491. };
  492. }
  493. /**
  494. * Action to signal that a participant had been kicked.
  495. *
  496. * @param {JitsiParticipant} kicker - Information about participant performing the kick.
  497. * @param {JitsiParticipant} kicked - Information about participant that was kicked.
  498. * @returns {Promise}
  499. */
  500. export function participantKicked(kicker, kicked) {
  501. return (dispatch, getState) => {
  502. dispatch({
  503. type: PARTICIPANT_KICKED,
  504. kicked: kicked.getId(),
  505. kicker: kicker?.getId()
  506. });
  507. if (kicked.isReplaced && kicked.isReplaced()) {
  508. return;
  509. }
  510. dispatch(showNotification({
  511. titleArguments: {
  512. kicked:
  513. getParticipantDisplayName(getState, kicked.getId()),
  514. kicker:
  515. getParticipantDisplayName(getState, kicker.getId())
  516. },
  517. titleKey: 'notify.kickParticipant'
  518. }, NOTIFICATION_TIMEOUT_TYPE.MEDIUM));
  519. };
  520. }
  521. /**
  522. * Create an action which pins a conference participant.
  523. *
  524. * @param {string|null} id - The ID of the conference participant to pin or null
  525. * if none of the conference's participants are to be pinned.
  526. * @returns {{
  527. * type: PIN_PARTICIPANT,
  528. * participant: {
  529. * id: string
  530. * }
  531. * }}
  532. */
  533. export function pinParticipant(id) {
  534. return {
  535. type: PIN_PARTICIPANT,
  536. participant: {
  537. id
  538. }
  539. };
  540. }
  541. /**
  542. * Creates an action which notifies the app that the loadable URL of the avatar of a participant got updated.
  543. *
  544. * @param {string} participantId - The ID of the participant.
  545. * @param {string} url - The new URL.
  546. * @param {boolean} useCORS - Indicates whether we need to use CORS for this URL.
  547. * @returns {{
  548. * type: SET_LOADABLE_AVATAR_URL,
  549. * participant: {
  550. * id: string,
  551. * loadableAvatarUrl: string,
  552. * loadableAvatarUrlUseCORS: boolean
  553. * }
  554. * }}
  555. */
  556. export function setLoadableAvatarUrl(participantId, url, useCORS) {
  557. return {
  558. type: SET_LOADABLE_AVATAR_URL,
  559. participant: {
  560. id: participantId,
  561. loadableAvatarUrl: url,
  562. loadableAvatarUrlUseCORS: useCORS
  563. }
  564. };
  565. }
  566. /**
  567. * Raise hand for the local participant.
  568. *
  569. * @param {boolean} enabled - Raise or lower hand.
  570. * @returns {{
  571. * type: LOCAL_PARTICIPANT_RAISE_HAND,
  572. * raisedHandTimestamp: number
  573. * }}
  574. */
  575. export function raiseHand(enabled) {
  576. return {
  577. type: LOCAL_PARTICIPANT_RAISE_HAND,
  578. raisedHandTimestamp: enabled ? Date.now() : 0
  579. };
  580. }
  581. /**
  582. * Update raise hand queue of participants.
  583. *
  584. * @param {Object} participant - Participant that updated raised hand.
  585. * @returns {{
  586. * type: RAISE_HAND_UPDATED,
  587. * participant: Object
  588. * }}
  589. */
  590. export function raiseHandUpdateQueue(participant) {
  591. return {
  592. type: RAISE_HAND_UPDATED,
  593. participant
  594. };
  595. }
  596. /**
  597. * Notifies if the local participant audio level has changed.
  598. *
  599. * @param {number} level - The audio level.
  600. * @returns {{
  601. * type: LOCAL_PARTICIPANT_AUDIO_LEVEL_CHANGED,
  602. * level: number
  603. * }}
  604. */
  605. export function localParticipantAudioLevelChanged(level) {
  606. return {
  607. type: LOCAL_PARTICIPANT_AUDIO_LEVEL_CHANGED,
  608. level
  609. };
  610. }
  611. /**
  612. * Overwrites the name of the participant with the given id.
  613. *
  614. * @param {string} id - Participant id;.
  615. * @param {string} name - New participant name.
  616. * @returns {Object}
  617. */
  618. export function overwriteParticipantName(id, name) {
  619. return {
  620. type: OVERWRITE_PARTICIPANT_NAME,
  621. id,
  622. name
  623. };
  624. }
  625. /**
  626. * Overwrites the names of the given participants.
  627. *
  628. * @param {Array<Object>} participantList - The list of participants to overwrite.
  629. * @returns {Object}
  630. */
  631. export function overwriteParticipantsNames(participantList) {
  632. return {
  633. type: OVERWRITE_PARTICIPANTS_NAMES,
  634. participantList
  635. };
  636. }
  637. /**
  638. * Local video recording status for the local participant.
  639. *
  640. * @param {boolean} recording - If local recording is ongoing.
  641. * @param {boolean} onlySelf - If recording only local streams.
  642. * @returns {{
  643. * type: SET_LOCAL_PARTICIPANT_RECORDING_STATUS,
  644. * recording: boolean
  645. * }}
  646. */
  647. export function updateLocalRecordingStatus(recording, onlySelf) {
  648. return {
  649. type: SET_LOCAL_PARTICIPANT_RECORDING_STATUS,
  650. recording,
  651. onlySelf
  652. };
  653. }