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

actions.any.ts 34KB

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