You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

middleware.js 23KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673
  1. // @flow
  2. import { reloadNow } from '../../app';
  3. import {
  4. ACTION_PINNED,
  5. ACTION_UNPINNED,
  6. createAudioOnlyChangedEvent,
  7. createConnectionEvent,
  8. createPinnedEvent,
  9. sendAnalytics
  10. } from '../../analytics';
  11. import { CONNECTION_ESTABLISHED, CONNECTION_FAILED } from '../connection';
  12. import { setVideoMuted, VIDEO_MUTISM_AUTHORITY } from '../media';
  13. import {
  14. getLocalParticipant,
  15. getParticipantById,
  16. getPinnedParticipant,
  17. PARTICIPANT_UPDATED,
  18. PIN_PARTICIPANT
  19. } from '../participants';
  20. import { MiddlewareRegistry, StateListenerRegistry } from '../redux';
  21. import UIEvents from '../../../../service/UI/UIEvents';
  22. import { TRACK_ADDED, TRACK_REMOVED } from '../tracks';
  23. import {
  24. conferenceFailed,
  25. conferenceWillLeave,
  26. createConference,
  27. setLastN,
  28. setSubject
  29. } from './actions';
  30. import {
  31. CONFERENCE_FAILED,
  32. CONFERENCE_JOINED,
  33. CONFERENCE_SUBJECT_CHANGED,
  34. CONFERENCE_WILL_LEAVE,
  35. DATA_CHANNEL_OPENED,
  36. SET_AUDIO_ONLY,
  37. SET_LASTN,
  38. SET_PENDING_SUBJECT_CHANGE
  39. } from './actionTypes';
  40. import {
  41. _addLocalTracksToConference,
  42. forEachConference,
  43. getCurrentConference,
  44. _handleParticipantError,
  45. _removeLocalTracksFromConference
  46. } from './functions';
  47. const logger = require('jitsi-meet-logger').getLogger(__filename);
  48. declare var APP: Object;
  49. /**
  50. * Handler for before unload event.
  51. */
  52. let beforeUnloadHandler;
  53. /**
  54. * Implements the middleware of the feature base/conference.
  55. *
  56. * @param {Store} store - The redux store.
  57. * @returns {Function}
  58. */
  59. MiddlewareRegistry.register(store => next => action => {
  60. switch (action.type) {
  61. case CONFERENCE_FAILED:
  62. return _conferenceFailed(store, next, action);
  63. case CONFERENCE_JOINED:
  64. return _conferenceJoined(store, next, action);
  65. case CONNECTION_ESTABLISHED:
  66. return _connectionEstablished(store, next, action);
  67. case CONNECTION_FAILED:
  68. return _connectionFailed(store, next, action);
  69. case CONFERENCE_SUBJECT_CHANGED:
  70. return _conferenceSubjectChanged(store, next, action);
  71. case CONFERENCE_WILL_LEAVE:
  72. _conferenceWillLeave();
  73. break;
  74. case DATA_CHANNEL_OPENED:
  75. return _syncReceiveVideoQuality(store, next, action);
  76. case PARTICIPANT_UPDATED:
  77. return _updateLocalParticipantInConference(store, next, action);
  78. case PIN_PARTICIPANT:
  79. return _pinParticipant(store, next, action);
  80. case SET_AUDIO_ONLY:
  81. return _setAudioOnly(store, next, action);
  82. case SET_LASTN:
  83. return _setLastN(store, next, action);
  84. case TRACK_ADDED:
  85. case TRACK_REMOVED:
  86. return _trackAddedOrRemoved(store, next, action);
  87. }
  88. return next(action);
  89. });
  90. /**
  91. * Registers a change handler for state['features/base/conference'] to update
  92. * the preferred video quality levels based on user preferred and internal
  93. * settings.
  94. */
  95. StateListenerRegistry.register(
  96. /* selector */ state => state['features/base/conference'],
  97. /* listener */ (currentState, store, previousState = {}) => {
  98. const {
  99. conference,
  100. maxReceiverVideoQuality,
  101. preferredReceiverVideoQuality
  102. } = currentState;
  103. const changedPreferredVideoQuality = preferredReceiverVideoQuality
  104. !== previousState.preferredReceiverVideoQuality;
  105. const changedMaxVideoQuality = maxReceiverVideoQuality
  106. !== previousState.maxReceiverVideoQuality;
  107. if (changedPreferredVideoQuality || changedMaxVideoQuality) {
  108. _setReceiverVideoConstraint(
  109. conference,
  110. preferredReceiverVideoQuality,
  111. maxReceiverVideoQuality);
  112. }
  113. });
  114. /**
  115. * Makes sure to leave a failed conference in order to release any allocated
  116. * resources like peer connections, emit participant left events, etc.
  117. *
  118. * @param {Store} store - The redux store in which the specified {@code action}
  119. * is being dispatched.
  120. * @param {Dispatch} next - The redux {@code dispatch} function to dispatch the
  121. * specified {@code action} to the specified {@code store}.
  122. * @param {Action} action - The redux action {@code CONFERENCE_FAILED} which is
  123. * being dispatched in the specified {@code store}.
  124. * @private
  125. * @returns {Object} The value returned by {@code next(action)}.
  126. */
  127. function _conferenceFailed(store, next, action) {
  128. const result = next(action);
  129. // FIXME: Workaround for the web version. Currently, the creation of the
  130. // conference is handled by /conference.js and appropriate failure handlers
  131. // are set there.
  132. if (typeof APP !== 'undefined') {
  133. if (typeof beforeUnloadHandler !== 'undefined') {
  134. window.removeEventListener('beforeunload', beforeUnloadHandler);
  135. beforeUnloadHandler = undefined;
  136. }
  137. return result;
  138. }
  139. // XXX After next(action), it is clear whether the error is recoverable.
  140. const { conference, error } = action;
  141. !error.recoverable
  142. && conference
  143. && conference.leave().catch(reason => {
  144. // Even though we don't care too much about the failure, it may be
  145. // good to know that it happen, so log it (on the info level).
  146. logger.info('JitsiConference.leave() rejected with:', reason);
  147. });
  148. return result;
  149. }
  150. /**
  151. * Does extra sync up on properties that may need to be updated after the
  152. * conference was joined.
  153. *
  154. * @param {Store} store - The redux store in which the specified {@code action}
  155. * is being dispatched.
  156. * @param {Dispatch} next - The redux {@code dispatch} function to dispatch the
  157. * specified {@code action} to the specified {@code store}.
  158. * @param {Action} action - The redux action {@code CONFERENCE_JOINED} which is
  159. * being dispatched in the specified {@code store}.
  160. * @private
  161. * @returns {Object} The value returned by {@code next(action)}.
  162. */
  163. function _conferenceJoined({ dispatch, getState }, next, action) {
  164. const result = next(action);
  165. const {
  166. audioOnly,
  167. conference,
  168. pendingSubjectChange
  169. } = getState()['features/base/conference'];
  170. if (pendingSubjectChange) {
  171. dispatch(setSubject(pendingSubjectChange));
  172. }
  173. // FIXME On Web the audio only mode for "start audio only" is toggled before
  174. // conference is added to the redux store ("on conference joined" action)
  175. // and the LastN value needs to be synchronized here.
  176. audioOnly && conference.getLastN() !== 0 && dispatch(setLastN(0));
  177. // FIXME: Very dirty solution. This will work on web only.
  178. // When the user closes the window or quits the browser, lib-jitsi-meet
  179. // handles the process of leaving the conference. This is temporary solution
  180. // that should cover the described use case as part of the effort to
  181. // implement the conferenceWillLeave action for web.
  182. beforeUnloadHandler = () => {
  183. dispatch(conferenceWillLeave(conference));
  184. };
  185. window.addEventListener('beforeunload', beforeUnloadHandler);
  186. return result;
  187. }
  188. /**
  189. * Notifies the feature base/conference that the action
  190. * {@code CONNECTION_ESTABLISHED} is being dispatched within a specific redux
  191. * store.
  192. *
  193. * @param {Store} store - The redux store in which the specified {@code action}
  194. * is being dispatched.
  195. * @param {Dispatch} next - The redux {@code dispatch} function to dispatch the
  196. * specified {@code action} to the specified {@code store}.
  197. * @param {Action} action - The redux action {@code CONNECTION_ESTABLISHED}
  198. * which is being dispatched in the specified {@code store}.
  199. * @private
  200. * @returns {Object} The value returned by {@code next(action)}.
  201. */
  202. function _connectionEstablished({ dispatch }, next, action) {
  203. const result = next(action);
  204. // FIXME: Workaround for the web version. Currently, the creation of the
  205. // conference is handled by /conference.js.
  206. typeof APP === 'undefined' && dispatch(createConference());
  207. return result;
  208. }
  209. /**
  210. * Notifies the feature base/conference that the action
  211. * {@code CONNECTION_FAILED} is being dispatched within a specific redux
  212. * store.
  213. *
  214. * @param {Store} store - The redux store in which the specified {@code action}
  215. * is being dispatched.
  216. * @param {Dispatch} next - The redux {@code dispatch} function to dispatch the
  217. * specified {@code action} to the specified {@code store}.
  218. * @param {Action} action - The redux action {@code CONNECTION_FAILED} which is
  219. * being dispatched in the specified {@code store}.
  220. * @private
  221. * @returns {Object} The value returned by {@code next(action)}.
  222. */
  223. function _connectionFailed({ dispatch, getState }, next, action) {
  224. // In the case of a split-brain error, reload early and prevent further
  225. // handling of the action.
  226. if (_isMaybeSplitBrainError(getState, action)) {
  227. dispatch(reloadNow());
  228. return;
  229. }
  230. const result = next(action);
  231. if (typeof beforeUnloadHandler !== 'undefined') {
  232. window.removeEventListener('beforeunload', beforeUnloadHandler);
  233. beforeUnloadHandler = undefined;
  234. }
  235. // FIXME: Workaround for the web version. Currently, the creation of the
  236. // conference is handled by /conference.js and appropriate failure handlers
  237. // are set there.
  238. if (typeof APP === 'undefined') {
  239. const { connection } = action;
  240. const { error } = action;
  241. forEachConference(getState, conference => {
  242. // It feels that it would make things easier if JitsiConference
  243. // in lib-jitsi-meet would monitor it's connection and emit
  244. // CONFERENCE_FAILED when it's dropped. It has more knowledge on
  245. // whether it can recover or not. But because the reload screen
  246. // and the retry logic is implemented in the app maybe it can be
  247. // left this way for now.
  248. if (conference.getConnection() === connection) {
  249. // XXX Note that on mobile the error type passed to
  250. // connectionFailed is always an object with .name property.
  251. // This fact needs to be checked prior to enabling this logic on
  252. // web.
  253. const conferenceAction
  254. = conferenceFailed(conference, error.name);
  255. // Copy the recoverable flag if set on the CONNECTION_FAILED
  256. // action to not emit recoverable action caused by
  257. // a non-recoverable one.
  258. if (typeof error.recoverable !== 'undefined') {
  259. conferenceAction.error.recoverable = error.recoverable;
  260. }
  261. dispatch(conferenceAction);
  262. }
  263. return true;
  264. });
  265. }
  266. return result;
  267. }
  268. /**
  269. * Notifies the feature base/conference that the action
  270. * {@code CONFERENCE_SUBJECT_CHANGED} is being dispatched within a specific
  271. * redux store.
  272. *
  273. * @param {Store} store - The redux store in which the specified {@code action}
  274. * is being dispatched.
  275. * @param {Dispatch} next - The redux {@code dispatch} function to dispatch the
  276. * specified {@code action} to the specified {@code store}.
  277. * @param {Action} action - The redux action {@code CONFERENCE_SUBJECT_CHANGED}
  278. * which is being dispatched in the specified {@code store}.
  279. * @private
  280. * @returns {Object} The value returned by {@code next(action)}.
  281. */
  282. function _conferenceSubjectChanged({ dispatch, getState }, next, action) {
  283. const result = next(action);
  284. const { subject } = getState()['features/base/conference'];
  285. if (subject) {
  286. dispatch({
  287. type: SET_PENDING_SUBJECT_CHANGE,
  288. subject: undefined
  289. });
  290. }
  291. typeof APP === 'object' && APP.API.notifySubjectChanged(subject);
  292. return result;
  293. }
  294. /**
  295. * Notifies the feature base/conference that the action
  296. * {@code CONFERENCE_WILL_LEAVE} is being dispatched within a specific redux
  297. * store.
  298. *
  299. * @private
  300. * @returns {void}
  301. */
  302. function _conferenceWillLeave() {
  303. if (typeof beforeUnloadHandler !== 'undefined') {
  304. window.removeEventListener('beforeunload', beforeUnloadHandler);
  305. beforeUnloadHandler = undefined;
  306. }
  307. }
  308. /**
  309. * Returns whether or not a CONNECTION_FAILED action is for a possible split
  310. * brain error. A split brain error occurs when at least two users join a
  311. * conference on different bridges. It is assumed the split brain scenario
  312. * occurs very early on in the call.
  313. *
  314. * @param {Function} getState - The redux function for fetching the current
  315. * state.
  316. * @param {Action} action - The redux action {@code CONNECTION_FAILED} which is
  317. * being dispatched in the specified {@code store}.
  318. * @private
  319. * @returns {boolean}
  320. */
  321. function _isMaybeSplitBrainError(getState, action) {
  322. const { error } = action;
  323. const isShardChangedError = error
  324. && error.message === 'item-not-found'
  325. && error.details
  326. && error.details.shard_changed;
  327. if (isShardChangedError) {
  328. const state = getState();
  329. const { timeEstablished } = state['features/base/connection'];
  330. const { _immediateReloadThreshold } = state['features/base/config'];
  331. const timeSinceConnectionEstablished
  332. = timeEstablished && Date.now() - timeEstablished;
  333. const reloadThreshold = typeof _immediateReloadThreshold === 'number'
  334. ? _immediateReloadThreshold : 1500;
  335. const isWithinSplitBrainThreshold = !timeEstablished
  336. || timeSinceConnectionEstablished <= reloadThreshold;
  337. sendAnalytics(createConnectionEvent('failed', {
  338. ...error,
  339. connectionEstablished: timeEstablished,
  340. splitBrain: isWithinSplitBrainThreshold,
  341. timeSinceConnectionEstablished
  342. }));
  343. return isWithinSplitBrainThreshold;
  344. }
  345. return false;
  346. }
  347. /**
  348. * Notifies the feature base/conference that the action {@code PIN_PARTICIPANT}
  349. * is being dispatched within a specific redux store. Pins the specified remote
  350. * participant in the associated conference, ignores the local participant.
  351. *
  352. * @param {Store} store - The redux store in which the specified {@code action}
  353. * is being dispatched.
  354. * @param {Dispatch} next - The redux {@code dispatch} function to dispatch the
  355. * specified {@code action} to the specified {@code store}.
  356. * @param {Action} action - The redux action {@code PIN_PARTICIPANT} which is
  357. * being dispatched in the specified {@code store}.
  358. * @private
  359. * @returns {Object} The value returned by {@code next(action)}.
  360. */
  361. function _pinParticipant({ getState }, next, action) {
  362. const state = getState();
  363. const { conference } = state['features/base/conference'];
  364. if (!conference) {
  365. return next(action);
  366. }
  367. const participants = state['features/base/participants'];
  368. const id = action.participant.id;
  369. const participantById = getParticipantById(participants, id);
  370. if (typeof APP !== 'undefined') {
  371. const pinnedParticipant = getPinnedParticipant(participants);
  372. const actionName = id ? ACTION_PINNED : ACTION_UNPINNED;
  373. const local
  374. = (participantById && participantById.local)
  375. || (!id && pinnedParticipant && pinnedParticipant.local);
  376. let participantIdForEvent;
  377. if (local) {
  378. participantIdForEvent = local;
  379. } else {
  380. participantIdForEvent = actionName === ACTION_PINNED
  381. ? id : pinnedParticipant && pinnedParticipant.id;
  382. }
  383. sendAnalytics(createPinnedEvent(
  384. actionName,
  385. participantIdForEvent,
  386. {
  387. local,
  388. 'participant_count': conference.getParticipantCount()
  389. }));
  390. }
  391. // The following condition prevents signaling to pin local participant and
  392. // shared videos. The logic is:
  393. // - If we have an ID, we check if the participant identified by that ID is
  394. // local or a bot/fake participant (such as with shared video).
  395. // - If we don't have an ID (i.e. no participant identified by an ID), we
  396. // check for local participant. If she's currently pinned, then this
  397. // action will unpin her and that's why we won't signal here too.
  398. let pin;
  399. if (participantById) {
  400. pin = !participantById.local && !participantById.isFakeParticipant;
  401. } else {
  402. const localParticipant = getLocalParticipant(participants);
  403. pin = !localParticipant || !localParticipant.pinned;
  404. }
  405. if (pin) {
  406. try {
  407. conference.pinParticipant(id);
  408. } catch (err) {
  409. _handleParticipantError(err);
  410. }
  411. }
  412. return next(action);
  413. }
  414. /**
  415. * Sets the audio-only flag for the current conference. When audio-only is set,
  416. * local video is muted and last N is set to 0 to avoid receiving remote video.
  417. *
  418. * @param {Store} store - The redux store in which the specified {@code action}
  419. * is being dispatched.
  420. * @param {Dispatch} next - The redux {@code dispatch} function to dispatch the
  421. * specified {@code action} to the specified {@code store}.
  422. * @param {Action} action - The redux action {@code SET_AUDIO_ONLY} which is
  423. * being dispatched in the specified {@code store}.
  424. * @private
  425. * @returns {Object} The value returned by {@code next(action)}.
  426. */
  427. function _setAudioOnly({ dispatch, getState }, next, action) {
  428. const { audioOnly: oldValue } = getState()['features/base/conference'];
  429. const result = next(action);
  430. const { audioOnly: newValue } = getState()['features/base/conference'];
  431. // Send analytics. We could've done it in the action creator setAudioOnly.
  432. // I don't know why it has to happen as early as possible but the analytics
  433. // were originally sent before the SET_AUDIO_ONLY action was even dispatched
  434. // in the redux store so I'm now sending the analytics as early as possible.
  435. if (oldValue !== newValue) {
  436. sendAnalytics(createAudioOnlyChangedEvent(newValue));
  437. logger.log(`Audio-only ${newValue ? 'enabled' : 'disabled'}`);
  438. }
  439. // Set lastN to 0 in case audio-only is desired; leave it as undefined,
  440. // otherwise, and the default lastN value will be chosen automatically.
  441. dispatch(setLastN(newValue ? 0 : undefined));
  442. // Mute/unmute the local video.
  443. dispatch(
  444. setVideoMuted(
  445. newValue,
  446. VIDEO_MUTISM_AUTHORITY.AUDIO_ONLY,
  447. action.ensureVideoTrack));
  448. if (typeof APP !== 'undefined') {
  449. // TODO This should be a temporary solution that lasts only until video
  450. // tracks and all ui is moved into react/redux on the web.
  451. APP.UI.emitEvent(UIEvents.TOGGLE_AUDIO_ONLY, newValue);
  452. }
  453. return result;
  454. }
  455. /**
  456. * Sets the last N (value) of the video channel in the conference.
  457. *
  458. * @param {Store} store - The redux store in which the specified {@code action}
  459. * is being dispatched.
  460. * @param {Dispatch} next - The redux {@code dispatch} function to dispatch the
  461. * specified {@code action} to the specified {@code store}.
  462. * @param {Action} action - The redux action {@code SET_LASTN} which is being
  463. * dispatched in the specified {@code store}.
  464. * @private
  465. * @returns {Object} The value returned by {@code next(action)}.
  466. */
  467. function _setLastN({ getState }, next, action) {
  468. const { conference } = getState()['features/base/conference'];
  469. if (conference) {
  470. try {
  471. conference.setLastN(action.lastN);
  472. } catch (err) {
  473. logger.error(`Failed to set lastN: ${err}`);
  474. }
  475. }
  476. return next(action);
  477. }
  478. /**
  479. * Helper function for updating the preferred receiver video constraint, based
  480. * on the user preference and the internal maximum.
  481. *
  482. * @param {JitsiConference} conference - The JitsiConference instance for the
  483. * current call.
  484. * @param {number} preferred - The user preferred max frame height.
  485. * @param {number} max - The maximum frame height the application should
  486. * receive.
  487. * @returns {void}
  488. */
  489. function _setReceiverVideoConstraint(conference, preferred, max) {
  490. if (conference) {
  491. conference.setReceiverVideoConstraint(Math.min(preferred, max));
  492. }
  493. }
  494. /**
  495. * Synchronizes local tracks from state with local tracks in JitsiConference
  496. * instance.
  497. *
  498. * @param {Store} store - The redux store.
  499. * @param {Object} action - Action object.
  500. * @private
  501. * @returns {Promise}
  502. */
  503. function _syncConferenceLocalTracksWithState({ getState }, action) {
  504. const conference = getCurrentConference(getState);
  505. let promise;
  506. if (conference) {
  507. const track = action.track.jitsiTrack;
  508. if (action.type === TRACK_ADDED) {
  509. promise = _addLocalTracksToConference(conference, [ track ]);
  510. } else {
  511. promise = _removeLocalTracksFromConference(conference, [ track ]);
  512. }
  513. }
  514. return promise || Promise.resolve();
  515. }
  516. /**
  517. * Sets the maximum receive video quality.
  518. *
  519. * @param {Store} store - The redux store in which the specified {@code action}
  520. * is being dispatched.
  521. * @param {Dispatch} next - The redux {@code dispatch} function to dispatch the
  522. * specified {@code action} to the specified {@code store}.
  523. * @param {Action} action - The redux action {@code DATA_CHANNEL_STATUS_CHANGED}
  524. * which is being dispatched in the specified {@code store}.
  525. * @private
  526. * @returns {Object} The value returned by {@code next(action)}.
  527. */
  528. function _syncReceiveVideoQuality({ getState }, next, action) {
  529. const {
  530. conference,
  531. maxReceiverVideoQuality,
  532. preferredReceiverVideoQuality
  533. } = getState()['features/base/conference'];
  534. _setReceiverVideoConstraint(
  535. conference,
  536. preferredReceiverVideoQuality,
  537. maxReceiverVideoQuality);
  538. return next(action);
  539. }
  540. /**
  541. * Notifies the feature base/conference that the action {@code TRACK_ADDED}
  542. * or {@code TRACK_REMOVED} is being dispatched within a specific redux store.
  543. *
  544. * @param {Store} store - The redux store in which the specified {@code action}
  545. * is being dispatched.
  546. * @param {Dispatch} next - The redux {@code dispatch} function to dispatch the
  547. * specified {@code action} to the specified {@code store}.
  548. * @param {Action} action - The redux action {@code TRACK_ADDED} or
  549. * {@code TRACK_REMOVED} which is being dispatched in the specified
  550. * {@code store}.
  551. * @private
  552. * @returns {Object} The value returned by {@code next(action)}.
  553. */
  554. function _trackAddedOrRemoved(store, next, action) {
  555. const track = action.track;
  556. if (track && track.local) {
  557. return (
  558. _syncConferenceLocalTracksWithState(store, action)
  559. .then(() => next(action)));
  560. }
  561. return next(action);
  562. }
  563. /**
  564. * Updates the conference object when the local participant is updated.
  565. *
  566. * @param {Store} store - The redux store in which the specified {@code action}
  567. * is being dispatched.
  568. * @param {Dispatch} next - The redux {@code dispatch} function to dispatch the
  569. * specified {@code action} to the specified {@code store}.
  570. * @param {Action} action - The redux action which is being dispatched in the
  571. * specified {@code store}.
  572. * @private
  573. * @returns {Object} The value returned by {@code next(action)}.
  574. */
  575. function _updateLocalParticipantInConference({ getState }, next, action) {
  576. const { conference } = getState()['features/base/conference'];
  577. const { participant } = action;
  578. const result = next(action);
  579. if (conference && participant.local && 'name' in participant) {
  580. conference.setDisplayName(participant.name);
  581. }
  582. return result;
  583. }