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

actions.ts 29KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961
  1. import { createStartMutedConfigurationEvent } from '../../analytics/AnalyticsEvents';
  2. import { sendAnalytics } from '../../analytics/functions';
  3. import { appNavigate } from '../../app/actions';
  4. import { IReduxState, IStore } from '../../app/types';
  5. import { endpointMessageReceived } from '../../subtitles/actions.any';
  6. import { getReplaceParticipant } from '../config/functions';
  7. import { disconnect } from '../connection/actions';
  8. import { JITSI_CONNECTION_CONFERENCE_KEY } from '../connection/constants';
  9. import { JitsiConferenceEvents, JitsiE2ePingEvents } from '../lib-jitsi-meet';
  10. import { setAudioMuted, setAudioUnmutePermissions, setVideoMuted, setVideoUnmutePermissions } from '../media/actions';
  11. import { MEDIA_TYPE } from '../media/constants';
  12. import {
  13. dominantSpeakerChanged,
  14. participantKicked,
  15. participantMutedUs,
  16. participantPresenceChanged,
  17. participantRoleChanged,
  18. participantSourcesUpdated,
  19. participantUpdated
  20. } from '../participants/actions';
  21. import { getNormalizedDisplayName } from '../participants/functions';
  22. import { IJitsiParticipant } from '../participants/types';
  23. import { toState } from '../redux/functions';
  24. import {
  25. destroyLocalTracks,
  26. replaceLocalTrack,
  27. trackAdded,
  28. trackRemoved
  29. } from '../tracks/actions.any';
  30. import { getLocalTracks } from '../tracks/functions';
  31. import { getBackendSafeRoomName } from '../util/uri';
  32. import {
  33. AUTH_STATUS_CHANGED,
  34. CONFERENCE_FAILED,
  35. CONFERENCE_JOINED,
  36. CONFERENCE_JOIN_IN_PROGRESS,
  37. CONFERENCE_LEFT,
  38. CONFERENCE_LOCAL_SUBJECT_CHANGED,
  39. CONFERENCE_SUBJECT_CHANGED,
  40. CONFERENCE_TIMESTAMP_CHANGED,
  41. CONFERENCE_UNIQUE_ID_SET,
  42. CONFERENCE_WILL_JOIN,
  43. CONFERENCE_WILL_LEAVE,
  44. DATA_CHANNEL_CLOSED,
  45. DATA_CHANNEL_OPENED,
  46. E2E_RTT_CHANGED,
  47. KICKED_OUT,
  48. LOCK_STATE_CHANGED,
  49. NON_PARTICIPANT_MESSAGE_RECEIVED,
  50. P2P_STATUS_CHANGED,
  51. SEND_TONES,
  52. SET_FOLLOW_ME,
  53. SET_OBFUSCATED_ROOM,
  54. SET_PASSWORD,
  55. SET_PASSWORD_FAILED,
  56. SET_PENDING_SUBJECT_CHANGE,
  57. SET_ROOM,
  58. SET_START_MUTED_POLICY,
  59. SET_START_REACTIONS_MUTED
  60. } from './actionTypes';
  61. import {
  62. AVATAR_URL_COMMAND,
  63. EMAIL_COMMAND,
  64. JITSI_CONFERENCE_URL_KEY
  65. } from './constants';
  66. import {
  67. _addLocalTracksToConference,
  68. commonUserJoinedHandling,
  69. commonUserLeftHandling,
  70. getConferenceOptions,
  71. getConferenceState,
  72. getCurrentConference,
  73. sendLocalParticipant
  74. } from './functions';
  75. import logger from './logger';
  76. import { IJitsiConference } from './reducer';
  77. /**
  78. * Adds conference (event) listeners.
  79. *
  80. * @param {JitsiConference} conference - The JitsiConference instance.
  81. * @param {Dispatch} dispatch - The Redux dispatch function.
  82. * @param {Object} state - The Redux state.
  83. * @private
  84. * @returns {void}
  85. */
  86. function _addConferenceListeners(conference: IJitsiConference, dispatch: IStore['dispatch'], state: IReduxState) {
  87. // A simple logger for conference errors received through
  88. // the listener. These errors are not handled now, but logged.
  89. conference.on(JitsiConferenceEvents.CONFERENCE_ERROR,
  90. (error: Error) => logger.error('Conference error.', error));
  91. // Dispatches into features/base/conference follow:
  92. conference.on(
  93. JitsiConferenceEvents.AUTH_STATUS_CHANGED,
  94. (authEnabled: boolean, authLogin: string) => dispatch(authStatusChanged(authEnabled, authLogin)));
  95. conference.on(
  96. JitsiConferenceEvents.CONFERENCE_FAILED, // @ts-ignore
  97. (...args: any[]) => dispatch(conferenceFailed(conference, ...args)));
  98. conference.on(
  99. JitsiConferenceEvents.CONFERENCE_JOINED, // @ts-ignore
  100. (...args: any[]) => dispatch(conferenceJoined(conference, ...args)));
  101. conference.on(
  102. JitsiConferenceEvents.CONFERENCE_UNIQUE_ID_SET, // @ts-ignore
  103. (...args: any[]) => dispatch(conferenceUniqueIdSet(conference, ...args)));
  104. conference.on(
  105. JitsiConferenceEvents.CONFERENCE_JOIN_IN_PROGRESS, // @ts-ignore
  106. (...args: any[]) => dispatch(conferenceJoinInProgress(conference, ...args)));
  107. conference.on(
  108. JitsiConferenceEvents.CONFERENCE_LEFT,
  109. (...args: any[]) => {
  110. dispatch(conferenceTimestampChanged(0));
  111. // @ts-ignore
  112. dispatch(conferenceLeft(conference, ...args));
  113. });
  114. conference.on(JitsiConferenceEvents.SUBJECT_CHANGED, // @ts-ignore
  115. (...args: any[]) => dispatch(conferenceSubjectChanged(...args)));
  116. conference.on(JitsiConferenceEvents.CONFERENCE_CREATED_TIMESTAMP, // @ts-ignore
  117. (...args: any[]) => dispatch(conferenceTimestampChanged(...args)));
  118. conference.on(
  119. JitsiConferenceEvents.KICKED, // @ts-ignore
  120. (...args: any[]) => dispatch(kickedOut(conference, ...args)));
  121. conference.on(
  122. JitsiConferenceEvents.PARTICIPANT_KICKED,
  123. (kicker: any, kicked: any) => dispatch(participantKicked(kicker, kicked)));
  124. conference.on(
  125. JitsiConferenceEvents.PARTICIPANT_SOURCE_UPDATED,
  126. (jitsiParticipant: IJitsiParticipant) => dispatch(participantSourcesUpdated(jitsiParticipant)));
  127. conference.on(
  128. JitsiConferenceEvents.LOCK_STATE_CHANGED, // @ts-ignore
  129. (...args: any[]) => dispatch(lockStateChanged(conference, ...args)));
  130. // Dispatches into features/base/media follow:
  131. conference.on(
  132. JitsiConferenceEvents.STARTED_MUTED,
  133. () => {
  134. const audioMuted = Boolean(conference.isStartAudioMuted());
  135. const videoMuted = Boolean(conference.isStartVideoMuted());
  136. const localTracks = getLocalTracks(state['features/base/tracks']);
  137. sendAnalytics(createStartMutedConfigurationEvent('remote', audioMuted, videoMuted));
  138. logger.log(`Start muted: ${audioMuted ? 'audio, ' : ''}${videoMuted ? 'video' : ''}`);
  139. // XXX Jicofo tells lib-jitsi-meet to start with audio and/or video
  140. // muted i.e. Jicofo expresses an intent. Lib-jitsi-meet has turned
  141. // Jicofo's intent into reality by actually muting the respective
  142. // tracks. The reality is expressed in base/tracks already so what
  143. // is left is to express Jicofo's intent in base/media.
  144. // TODO Maybe the app needs to learn about Jicofo's intent and
  145. // transfer that intent to lib-jitsi-meet instead of lib-jitsi-meet
  146. // acting on Jicofo's intent without the app's knowledge.
  147. dispatch(setAudioMuted(audioMuted));
  148. dispatch(setVideoMuted(videoMuted));
  149. // Remove the tracks from peerconnection as well.
  150. for (const track of localTracks) {
  151. const trackType = track.jitsiTrack.getType();
  152. // Do not remove the audio track on RN. Starting with iOS 15 it will fail to unmute otherwise.
  153. if ((audioMuted && trackType === MEDIA_TYPE.AUDIO && navigator.product !== 'ReactNative')
  154. || (videoMuted && trackType === MEDIA_TYPE.VIDEO)) {
  155. dispatch(replaceLocalTrack(track.jitsiTrack, null, conference));
  156. }
  157. }
  158. });
  159. conference.on(
  160. JitsiConferenceEvents.AUDIO_UNMUTE_PERMISSIONS_CHANGED,
  161. (disableAudioMuteChange: boolean) => {
  162. dispatch(setAudioUnmutePermissions(disableAudioMuteChange));
  163. });
  164. conference.on(
  165. JitsiConferenceEvents.VIDEO_UNMUTE_PERMISSIONS_CHANGED,
  166. (disableVideoMuteChange: boolean) => {
  167. dispatch(setVideoUnmutePermissions(disableVideoMuteChange));
  168. });
  169. // Dispatches into features/base/tracks follow:
  170. conference.on(
  171. JitsiConferenceEvents.TRACK_ADDED,
  172. (t: any) => t && !t.isLocal() && dispatch(trackAdded(t)));
  173. conference.on(
  174. JitsiConferenceEvents.TRACK_REMOVED,
  175. (t: any) => t && !t.isLocal() && dispatch(trackRemoved(t)));
  176. conference.on(
  177. JitsiConferenceEvents.TRACK_MUTE_CHANGED,
  178. (track: any, participantThatMutedUs: any) => {
  179. if (participantThatMutedUs) {
  180. dispatch(participantMutedUs(participantThatMutedUs, track));
  181. }
  182. });
  183. conference.on(JitsiConferenceEvents.TRACK_UNMUTE_REJECTED, (track: any) => dispatch(destroyLocalTracks(track)));
  184. // Dispatches into features/base/participants follow:
  185. conference.on(
  186. JitsiConferenceEvents.DISPLAY_NAME_CHANGED,
  187. (id: string, displayName: string) => dispatch(participantUpdated({
  188. conference,
  189. id,
  190. name: getNormalizedDisplayName(displayName)
  191. })));
  192. conference.on(
  193. JitsiConferenceEvents.DOMINANT_SPEAKER_CHANGED,
  194. (dominant: string, previous: string[], silence: boolean | string) => {
  195. dispatch(dominantSpeakerChanged(dominant, previous, Boolean(silence), conference));
  196. });
  197. conference.on(
  198. JitsiConferenceEvents.ENDPOINT_MESSAGE_RECEIVED, // @ts-ignore
  199. (...args: any[]) => dispatch(endpointMessageReceived(...args)));
  200. conference.on(
  201. JitsiConferenceEvents.NON_PARTICIPANT_MESSAGE_RECEIVED, // @ts-ignore
  202. (...args: any[]) => dispatch(nonParticipantMessageReceived(...args)));
  203. conference.on(
  204. JitsiConferenceEvents.USER_JOINED,
  205. (_id: string, user: any) => commonUserJoinedHandling({ dispatch }, conference, user));
  206. conference.on(
  207. JitsiConferenceEvents.USER_LEFT,
  208. (_id: string, user: any) => commonUserLeftHandling({ dispatch }, conference, user));
  209. conference.on(
  210. JitsiConferenceEvents.USER_ROLE_CHANGED, // @ts-ignore
  211. (...args: any[]) => dispatch(participantRoleChanged(...args)));
  212. conference.on(
  213. JitsiConferenceEvents.USER_STATUS_CHANGED, // @ts-ignore
  214. (...args: any[]) => dispatch(participantPresenceChanged(...args)));
  215. conference.on(
  216. JitsiE2ePingEvents.E2E_RTT_CHANGED, // @ts-ignore
  217. (...args: any[]) => dispatch(e2eRttChanged(...args)));
  218. conference.on(
  219. JitsiConferenceEvents.BOT_TYPE_CHANGED,
  220. (id: string, botType: string) => dispatch(participantUpdated({
  221. conference,
  222. id,
  223. botType
  224. })));
  225. conference.addCommandListener(
  226. AVATAR_URL_COMMAND,
  227. (data: { value: string; }, id: string) => dispatch(participantUpdated({
  228. conference,
  229. id,
  230. avatarURL: data.value
  231. })));
  232. conference.addCommandListener(
  233. EMAIL_COMMAND,
  234. (data: { value: string; }, id: string) => dispatch(participantUpdated({
  235. conference,
  236. id,
  237. email: data.value
  238. })));
  239. }
  240. /**
  241. * Create an action for when the end-to-end RTT against a specific remote participant has changed.
  242. *
  243. * @param {Object} participant - The participant against which the rtt is measured.
  244. * @param {number} rtt - The rtt.
  245. * @returns {{
  246. * type: E2E_RTT_CHANGED,
  247. * e2eRtt: {
  248. * participant: Object,
  249. * rtt: number
  250. * }
  251. * }}
  252. */
  253. export function e2eRttChanged(participant: Object, rtt: number) {
  254. return {
  255. type: E2E_RTT_CHANGED,
  256. e2eRtt: {
  257. rtt,
  258. participant
  259. }
  260. };
  261. }
  262. /**
  263. * Updates the current known state of server-side authentication.
  264. *
  265. * @param {boolean} authEnabled - Whether or not server authentication is
  266. * enabled.
  267. * @param {string} authLogin - The current name of the logged in user, if any.
  268. * @returns {{
  269. * type: AUTH_STATUS_CHANGED,
  270. * authEnabled: boolean,
  271. * authLogin: string
  272. * }}
  273. */
  274. export function authStatusChanged(authEnabled: boolean, authLogin: string) {
  275. return {
  276. type: AUTH_STATUS_CHANGED,
  277. authEnabled,
  278. authLogin
  279. };
  280. }
  281. /**
  282. * Signals that a specific conference has failed.
  283. *
  284. * @param {JitsiConference} conference - The JitsiConference that has failed.
  285. * @param {string} error - The error describing/detailing the cause of the
  286. * failure.
  287. * @param {any} params - Rest of the params that we receive together with the event.
  288. * @returns {{
  289. * type: CONFERENCE_FAILED,
  290. * conference: JitsiConference,
  291. * error: Error
  292. * }}
  293. * @public
  294. */
  295. export function conferenceFailed(conference: IJitsiConference, error: string, ...params: any) {
  296. return {
  297. type: CONFERENCE_FAILED,
  298. conference,
  299. // Make the error resemble an Error instance (to the extent that
  300. // jitsi-meet needs it).
  301. error: {
  302. name: error,
  303. params,
  304. recoverable: undefined
  305. }
  306. };
  307. }
  308. /**
  309. * Signals that a specific conference has been joined.
  310. *
  311. * @param {JitsiConference} conference - The JitsiConference instance which was
  312. * joined by the local participant.
  313. * @returns {{
  314. * type: CONFERENCE_JOINED,
  315. * conference: JitsiConference
  316. * }}
  317. */
  318. export function conferenceJoined(conference: IJitsiConference) {
  319. return {
  320. type: CONFERENCE_JOINED,
  321. conference
  322. };
  323. }
  324. /**
  325. * Signals that a specific conference join is in progress.
  326. *
  327. * @param {JitsiConference} conference - The JitsiConference instance for which join by the local participant
  328. * is in progress.
  329. * @returns {{
  330. * type: CONFERENCE_JOIN_IN_PROGRESS,
  331. * conference: JitsiConference
  332. * }}
  333. */
  334. export function conferenceJoinInProgress(conference: IJitsiConference) {
  335. return {
  336. type: CONFERENCE_JOIN_IN_PROGRESS,
  337. conference
  338. };
  339. }
  340. /**
  341. * Signals that a specific conference has been left.
  342. *
  343. * @param {JitsiConference} conference - The JitsiConference instance which was
  344. * left by the local participant.
  345. * @returns {{
  346. * type: CONFERENCE_LEFT,
  347. * conference: JitsiConference
  348. * }}
  349. */
  350. export function conferenceLeft(conference: Partial<IJitsiConference>) {
  351. return {
  352. type: CONFERENCE_LEFT,
  353. conference
  354. };
  355. }
  356. /**
  357. * Signals that the conference subject has been changed.
  358. *
  359. * @param {string} subject - The new subject.
  360. * @returns {{
  361. * type: CONFERENCE_SUBJECT_CHANGED,
  362. * subject: string
  363. * }}
  364. */
  365. export function conferenceSubjectChanged(subject: string) {
  366. return {
  367. type: CONFERENCE_SUBJECT_CHANGED,
  368. subject
  369. };
  370. }
  371. /**
  372. * Signals that the conference timestamp has been changed.
  373. *
  374. * @param {number} conferenceTimestamp - The UTC timestamp.
  375. * @returns {{
  376. * type: CONFERENCE_TIMESTAMP_CHANGED,
  377. * conferenceTimestamp
  378. * }}
  379. */
  380. export function conferenceTimestampChanged(conferenceTimestamp: number) {
  381. return {
  382. type: CONFERENCE_TIMESTAMP_CHANGED,
  383. conferenceTimestamp
  384. };
  385. }
  386. /**
  387. * Signals that the unique identifier for conference has been set.
  388. *
  389. * @param {JitsiConference} conference - The JitsiConference instance, where the uuid has been set.
  390. * @returns {{
  391. * type: CONFERENCE_UNIQUE_ID_SET,
  392. * conference: JitsiConference,
  393. * }}
  394. */
  395. export function conferenceUniqueIdSet(conference: IJitsiConference) {
  396. return {
  397. type: CONFERENCE_UNIQUE_ID_SET,
  398. conference
  399. };
  400. }
  401. /**
  402. * Adds any existing local tracks to a specific conference before the conference
  403. * is joined. Then signals the intention of the application to have the local
  404. * participant join the specified conference.
  405. *
  406. * @param {JitsiConference} conference - The {@code JitsiConference} instance
  407. * the local participant will (try to) join.
  408. * @returns {Function}
  409. */
  410. export function _conferenceWillJoin(conference: IJitsiConference) {
  411. return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
  412. const localTracks
  413. = getLocalTracks(getState()['features/base/tracks'])
  414. .map(t => t.jitsiTrack);
  415. if (localTracks.length) {
  416. _addLocalTracksToConference(conference, localTracks);
  417. }
  418. dispatch(conferenceWillJoin(conference));
  419. };
  420. }
  421. /**
  422. * Signals the intention of the application to have the local participant
  423. * join the specified conference.
  424. *
  425. * @param {JitsiConference} conference - The {@code JitsiConference} instance
  426. * the local participant will (try to) join.
  427. * @returns {{
  428. * type: CONFERENCE_WILL_JOIN,
  429. * conference: JitsiConference
  430. * }}
  431. */
  432. export function conferenceWillJoin(conference: IJitsiConference) {
  433. return {
  434. type: CONFERENCE_WILL_JOIN,
  435. conference
  436. };
  437. }
  438. /**
  439. * Signals the intention of the application to have the local participant leave
  440. * a specific conference. Similar in fashion to CONFERENCE_LEFT. Contrary to it
  441. * though, it's not guaranteed because CONFERENCE_LEFT may be triggered by
  442. * lib-jitsi-meet and not the application.
  443. *
  444. * @param {JitsiConference} conference - The JitsiConference instance which will
  445. * be left by the local participant.
  446. * @returns {{
  447. * type: CONFERENCE_LEFT,
  448. * conference: JitsiConference
  449. * }}
  450. */
  451. export function conferenceWillLeave(conference: IJitsiConference) {
  452. return {
  453. type: CONFERENCE_WILL_LEAVE,
  454. conference
  455. };
  456. }
  457. /**
  458. * Initializes a new conference.
  459. *
  460. * @param {string} overrideRoom - Override the room to join, instead of taking it
  461. * from Redux.
  462. * @returns {Function}
  463. */
  464. export function createConference(overrideRoom?: string | String) {
  465. return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
  466. const state = getState();
  467. const { connection, locationURL } = state['features/base/connection'];
  468. if (!connection) {
  469. throw new Error('Cannot create a conference without a connection!');
  470. }
  471. const { password, room } = state['features/base/conference'];
  472. if (!room) {
  473. throw new Error('Cannot join a conference without a room name!');
  474. }
  475. // XXX: revisit this.
  476. // Hide the custom domain in the room name.
  477. const tmp: any = overrideRoom || room;
  478. let _room: any = getBackendSafeRoomName(tmp);
  479. if (tmp.domain) {
  480. // eslint-disable-next-line no-new-wrappers
  481. _room = new String(tmp);
  482. // $FlowExpectedError
  483. _room.domain = tmp.domain;
  484. }
  485. const conference = connection.initJitsiConference(_room, getConferenceOptions(state));
  486. // @ts-ignore
  487. connection[JITSI_CONNECTION_CONFERENCE_KEY] = conference;
  488. conference[JITSI_CONFERENCE_URL_KEY] = locationURL;
  489. dispatch(_conferenceWillJoin(conference));
  490. _addConferenceListeners(conference, dispatch, state);
  491. sendLocalParticipant(state, conference);
  492. const replaceParticipant = getReplaceParticipant(state);
  493. conference.join(password, replaceParticipant);
  494. };
  495. }
  496. /**
  497. * Will try to join the conference again in case it failed earlier with
  498. * {@link JitsiConferenceErrors.AUTHENTICATION_REQUIRED}. It means that Jicofo
  499. * did not allow to create new room from anonymous domain, but it can be tried
  500. * again later in case authenticated user created it in the meantime.
  501. *
  502. * @returns {Function}
  503. */
  504. export function checkIfCanJoin() {
  505. return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
  506. const { authRequired, password }
  507. = getState()['features/base/conference'];
  508. const replaceParticipant = getReplaceParticipant(getState());
  509. // @ts-ignore
  510. authRequired && dispatch(_conferenceWillJoin(authRequired));
  511. authRequired?.join(password, replaceParticipant);
  512. };
  513. }
  514. /**
  515. * Signals the data channel with the bridge has successfully opened.
  516. *
  517. * @returns {{
  518. * type: DATA_CHANNEL_OPENED
  519. * }}
  520. */
  521. export function dataChannelOpened() {
  522. return {
  523. type: DATA_CHANNEL_OPENED
  524. };
  525. }
  526. /**
  527. * Signals the data channel with the bridge was abruptly closed.
  528. *
  529. * @param {number} code - Close code.
  530. * @param {string} reason - Close reason.
  531. *
  532. * @returns {{
  533. * type: DATA_CHANNEL_CLOSED,
  534. * code: number,
  535. * reason: string
  536. * }}
  537. */
  538. export function dataChannelClosed(code: number, reason: string) {
  539. return {
  540. type: DATA_CHANNEL_CLOSED,
  541. code,
  542. reason
  543. };
  544. }
  545. /**
  546. * Action to end a conference for all participants.
  547. *
  548. * @returns {Function}
  549. */
  550. export function endConference() {
  551. return async (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
  552. const { conference } = getConferenceState(toState(getState));
  553. conference?.end();
  554. };
  555. }
  556. /**
  557. * Signals that we've been kicked out of the conference.
  558. *
  559. * @param {JitsiConference} conference - The {@link JitsiConference} instance
  560. * for which the event is being signaled.
  561. * @param {JitsiParticipant} participant - The {@link JitsiParticipant}
  562. * instance which initiated the kick event.
  563. * @returns {{
  564. * type: KICKED_OUT,
  565. * conference: JitsiConference,
  566. * participant: JitsiParticipant
  567. * }}
  568. */
  569. export function kickedOut(conference: Object, participant: Object) {
  570. return {
  571. type: KICKED_OUT,
  572. conference,
  573. participant
  574. };
  575. }
  576. /**
  577. * Action to leave a conference.
  578. *
  579. * @returns {Function}
  580. */
  581. export function leaveConference() {
  582. return async (dispatch: IStore['dispatch']) => {
  583. // FIXME: these should be unified.
  584. if (navigator.product === 'ReactNative') {
  585. dispatch(appNavigate(undefined));
  586. } else {
  587. dispatch(disconnect(true));
  588. }
  589. };
  590. }
  591. /**
  592. * Signals that the lock state of a specific JitsiConference changed.
  593. *
  594. * @param {JitsiConference} conference - The JitsiConference which had its lock
  595. * state changed.
  596. * @param {boolean} locked - If the specified conference became locked, true;
  597. * otherwise, false.
  598. * @returns {{
  599. * type: LOCK_STATE_CHANGED,
  600. * conference: JitsiConference,
  601. * locked: boolean
  602. * }}
  603. */
  604. export function lockStateChanged(conference: Object, locked: boolean) {
  605. return {
  606. type: LOCK_STATE_CHANGED,
  607. conference,
  608. locked
  609. };
  610. }
  611. /**
  612. * Signals that a non participant endpoint message has been received.
  613. *
  614. * @param {string} id - The resource id of the sender.
  615. * @param {Object} json - The json carried by the endpoint message.
  616. * @returns {{
  617. * type: NON_PARTICIPANT_MESSAGE_RECEIVED,
  618. * id: Object,
  619. * json: Object
  620. * }}
  621. */
  622. export function nonParticipantMessageReceived(id: String, json: Object) {
  623. return {
  624. type: NON_PARTICIPANT_MESSAGE_RECEIVED,
  625. id,
  626. json
  627. };
  628. }
  629. /**
  630. * Updates the known state of start muted policies.
  631. *
  632. * @param {boolean} audioMuted - Whether or not members will join the conference
  633. * as audio muted.
  634. * @param {boolean} videoMuted - Whether or not members will join the conference
  635. * as video muted.
  636. * @returns {{
  637. * type: SET_START_MUTED_POLICY,
  638. * startAudioMutedPolicy: boolean,
  639. * startVideoMutedPolicy: boolean
  640. * }}
  641. */
  642. export function onStartMutedPolicyChanged(
  643. audioMuted: boolean, videoMuted: boolean) {
  644. return {
  645. type: SET_START_MUTED_POLICY,
  646. startAudioMutedPolicy: audioMuted,
  647. startVideoMutedPolicy: videoMuted
  648. };
  649. }
  650. /**
  651. * Sets whether or not peer2peer is currently enabled.
  652. *
  653. * @param {boolean} p2p - Whether or not peer2peer is currently active.
  654. * @returns {{
  655. * type: P2P_STATUS_CHANGED,
  656. * p2p: boolean
  657. * }}
  658. */
  659. export function p2pStatusChanged(p2p: boolean) {
  660. return {
  661. type: P2P_STATUS_CHANGED,
  662. p2p
  663. };
  664. }
  665. /**
  666. * Signals to play touch tones.
  667. *
  668. * @param {string} tones - The tones to play.
  669. * @param {number} [duration] - How long to play each tone.
  670. * @param {number} [pause] - How long to pause between each tone.
  671. * @returns {{
  672. * type: SEND_TONES,
  673. * tones: string,
  674. * duration: number,
  675. * pause: number
  676. * }}
  677. */
  678. export function sendTones(tones: string, duration: number, pause: number) {
  679. return {
  680. type: SEND_TONES,
  681. tones,
  682. duration,
  683. pause
  684. };
  685. }
  686. /**
  687. * Enables or disables the Follow Me feature.
  688. *
  689. * @param {boolean} enabled - Whether or not Follow Me should be enabled.
  690. * @returns {{
  691. * type: SET_FOLLOW_ME,
  692. * enabled: boolean
  693. * }}
  694. */
  695. export function setFollowMe(enabled: boolean) {
  696. return {
  697. type: SET_FOLLOW_ME,
  698. enabled
  699. };
  700. }
  701. /**
  702. * Enables or disables the Mute reaction sounds feature.
  703. *
  704. * @param {boolean} muted - Whether or not reaction sounds should be muted for all participants.
  705. * @param {boolean} updateBackend - Whether or not the moderator should notify all participants for the new setting.
  706. * @returns {{
  707. * type: SET_START_REACTIONS_MUTED,
  708. * muted: boolean
  709. * }}
  710. */
  711. export function setStartReactionsMuted(muted: boolean, updateBackend = false) {
  712. return {
  713. type: SET_START_REACTIONS_MUTED,
  714. muted,
  715. updateBackend
  716. };
  717. }
  718. /**
  719. * Sets the password to join or lock a specific JitsiConference.
  720. *
  721. * @param {JitsiConference} conference - The JitsiConference which requires a
  722. * password to join or is to be locked with the specified password.
  723. * @param {Function} method - The JitsiConference method of password protection
  724. * such as join or lock.
  725. * @param {string} password - The password with which the specified conference
  726. * is to be joined or locked.
  727. * @returns {Function}
  728. */
  729. export function setPassword(
  730. conference: IJitsiConference | undefined,
  731. method: Function | undefined,
  732. password: string) {
  733. return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
  734. if (!conference) {
  735. return;
  736. }
  737. switch (method) {
  738. case conference.join: {
  739. let state = getState()['features/base/conference'];
  740. dispatch({
  741. type: SET_PASSWORD,
  742. conference,
  743. method,
  744. password
  745. });
  746. // Join the conference with the newly-set password.
  747. // Make sure that the action did set the password.
  748. state = getState()['features/base/conference'];
  749. if (state.password === password
  750. // Make sure that the application still wants the
  751. // conference joined.
  752. && !state.conference) {
  753. method.call(conference, password);
  754. }
  755. break;
  756. }
  757. case conference.lock: {
  758. const state = getState()['features/base/conference'];
  759. if (state.conference === conference) {
  760. return (
  761. method.call(conference, password)
  762. .then(() => dispatch({
  763. type: SET_PASSWORD,
  764. conference,
  765. method,
  766. password
  767. }))
  768. .catch((error: Error) => dispatch({
  769. type: SET_PASSWORD_FAILED,
  770. error
  771. }))
  772. );
  773. }
  774. return Promise.reject();
  775. }
  776. }
  777. };
  778. }
  779. /**
  780. * Sets the obfuscated room name of the conference to be joined.
  781. *
  782. * @param {(string)} obfuscatedRoom - Obfuscated room name.
  783. * @param {(string)} obfuscatedRoomSource - The room name that was obfuscated.
  784. * @returns {{
  785. * type: SET_OBFUSCATED_ROOM,
  786. * room: string
  787. * }}
  788. */
  789. export function setObfuscatedRoom(obfuscatedRoom: string, obfuscatedRoomSource: string) {
  790. return {
  791. type: SET_OBFUSCATED_ROOM,
  792. obfuscatedRoom,
  793. obfuscatedRoomSource
  794. };
  795. }
  796. /**
  797. * Sets (the name of) the room of the conference to be joined.
  798. *
  799. * @param {(string|undefined)} room - The name of the room of the conference to
  800. * be joined.
  801. * @returns {{
  802. * type: SET_ROOM,
  803. * room: string
  804. * }}
  805. */
  806. export function setRoom(room?: string) {
  807. return {
  808. type: SET_ROOM,
  809. room
  810. };
  811. }
  812. /**
  813. * Sets whether or not members should join audio and/or video muted.
  814. *
  815. * @param {boolean} startAudioMuted - Whether or not members will join the
  816. * conference as audio muted.
  817. * @param {boolean} startVideoMuted - Whether or not members will join the
  818. * conference as video muted.
  819. * @returns {Function}
  820. */
  821. export function setStartMutedPolicy(
  822. startAudioMuted: boolean, startVideoMuted: boolean) {
  823. return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
  824. const conference = getCurrentConference(getState());
  825. conference?.setStartMutedPolicy({
  826. audio: startAudioMuted,
  827. video: startVideoMuted
  828. });
  829. return dispatch(
  830. onStartMutedPolicyChanged(startAudioMuted, startVideoMuted));
  831. };
  832. }
  833. /**
  834. * Sets the conference subject.
  835. *
  836. * @param {string} subject - The new subject.
  837. * @returns {void}
  838. */
  839. export function setSubject(subject: string) {
  840. return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
  841. const { conference } = getState()['features/base/conference'];
  842. if (conference) {
  843. conference.setSubject(subject || '');
  844. } else {
  845. dispatch({
  846. type: SET_PENDING_SUBJECT_CHANGE,
  847. subject
  848. });
  849. }
  850. };
  851. }
  852. /**
  853. * Sets the conference local subject.
  854. *
  855. * @param {string} localSubject - The new local subject.
  856. * @returns {{
  857. * type: CONFERENCE_LOCAL_SUBJECT_CHANGED,
  858. * localSubject: string
  859. * }}
  860. */
  861. export function setLocalSubject(localSubject: string) {
  862. return {
  863. type: CONFERENCE_LOCAL_SUBJECT_CHANGED,
  864. localSubject
  865. };
  866. }