您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

actions.ts 20KB

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