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

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