diff --git a/react/features/base/conference/actionTypes.js b/react/features/base/conference/actionTypes.js
index a53bcf804..3d3da4e95 100644
--- a/react/features/base/conference/actionTypes.js
+++ b/react/features/base/conference/actionTypes.js
@@ -1,37 +1,34 @@
import { Symbol } from '../react';
/**
- * Action type to signal that we are joining the conference.
+ * The type of the Redux action which signals that a specific conference has
+ * been joined.
*
* {
- * type: CONFERENCE_JOINED,
- * conference: {
- * jitsiConference: JitsiConference
- * }
+ * type: CONFERENCE_JOINED,
+ * conference: JitsiConference
* }
*/
export const CONFERENCE_JOINED = Symbol('CONFERENCE_JOINED');
/**
- * Action type to signal that we have left the conference.
+ * The type of the Redux action which signals that a specific conference has
+ * been left.
*
* {
- * type: CONFERENCE_LEFT,
- * conference: {
- * jitsiConference: JitsiConference
- * }
+ * type: CONFERENCE_LEFT,
+ * conference: JitsiConference
* }
*/
export const CONFERENCE_LEFT = Symbol('CONFERENCE_LEFT');
/**
- * Action type to signal that we will leave the specified conference.
+ * The type of the Redux action which signals that a specific conference will be
+ * left.
*
* {
- * type: CONFERENCE_WILL_LEAVE,
- * conference: {
- * jitsiConference: JitsiConference
- * }
+ * type: CONFERENCE_WILL_LEAVE,
+ * conference: JitsiConference
* }
*/
export const CONFERENCE_WILL_LEAVE = Symbol('CONFERENCE_WILL_LEAVE');
@@ -41,8 +38,8 @@ export const CONFERENCE_WILL_LEAVE = Symbol('CONFERENCE_WILL_LEAVE');
* conference to be joined.
*
* {
- * type: SET_ROOM,
- * room: string
+ * type: SET_ROOM,
+ * room: string
* }
*/
export const SET_ROOM = Symbol('SET_ROOM');
diff --git a/react/features/base/conference/actions.js b/react/features/base/conference/actions.js
index 016d6b87b..e992be685 100644
--- a/react/features/base/conference/actions.js
+++ b/react/features/base/conference/actions.js
@@ -19,34 +19,52 @@ import { _addLocalTracksToConference } from './functions';
import './middleware';
import './reducer';
-const JitsiConferenceEvents = JitsiMeetJS.events.conference;
-
/**
- * Initializes a new conference.
+ * Adds conference (event) listeners.
*
- * @returns {Function}
+ * @param {JitsiConference} conference - The JitsiConference instance.
+ * @param {Dispatch} dispatch - The Redux dispatch function.
+ * @private
+ * @returns {void}
*/
-export function createConference() {
- return (dispatch, getState) => {
- const state = getState();
- const connection = state['features/base/connection'].jitsiConnection;
- const room = state['features/base/conference'].room;
+function _addConferenceListeners(conference, dispatch) {
+ const JitsiConferenceEvents = JitsiMeetJS.events.conference;
- if (!connection) {
- throw new Error('Cannot create conference without connection');
- }
- if (typeof room === 'undefined' || room === '') {
- throw new Error('Cannot join conference without room name');
- }
+ conference.on(
+ JitsiConferenceEvents.CONFERENCE_JOINED,
+ (...args) => dispatch(_conferenceJoined(conference, ...args)));
+ conference.on(
+ JitsiConferenceEvents.CONFERENCE_LEFT,
+ (...args) => dispatch(_conferenceLeft(conference, ...args)));
- // TODO Take options from config.
- const conference
- = connection.initJitsiConference(room, { openSctp: true });
+ conference.on(
+ JitsiConferenceEvents.DOMINANT_SPEAKER_CHANGED,
+ (...args) => dispatch(dominantSpeakerChanged(...args)));
- _setupConferenceListeners(conference, dispatch);
+ conference.on(
+ JitsiConferenceEvents.TRACK_ADDED,
+ t => t && !t.isLocal() && dispatch(trackAdded(t)));
+ conference.on(
+ JitsiConferenceEvents.TRACK_REMOVED,
+ t => t && !t.isLocal() && dispatch(trackRemoved(t)));
- conference.join();
- };
+ conference.on(
+ JitsiConferenceEvents.USER_JOINED,
+ (id, user) => dispatch(participantJoined({
+ id,
+ name: user.getDisplayName(),
+ role: user.getRole()
+ })));
+ conference.on(
+ JitsiConferenceEvents.USER_LEFT,
+ (...args) => dispatch(participantLeft(...args)));
+ conference.on(
+ JitsiConferenceEvents.USER_ROLE_CHANGED,
+ (...args) => dispatch(participantRoleChanged(...args)));
+
+ conference.addCommandListener(
+ EMAIL_COMMAND,
+ (data, id) => dispatch(changeParticipantEmail(id, data.value)));
}
/**
@@ -70,37 +88,31 @@ function _conferenceJoined(conference) {
dispatch({
type: CONFERENCE_JOINED,
- conference: {
- jitsiConference: conference
- }
+ conference
});
};
}
/**
- * Signal that we have left the conference.
+ * Signals that a specific conference has been left.
*
* @param {JitsiConference} conference - The JitsiConference instance which was
* left by the local participant.
* @returns {{
* type: CONFERENCE_LEFT,
- * conference: {
- * jitsiConference: JitsiConference
- * }
+ * conference: JitsiConference
* }}
*/
function _conferenceLeft(conference) {
return {
type: CONFERENCE_LEFT,
- conference: {
- jitsiConference: conference
- }
+ conference
};
}
/**
- * Signal the intention of the application to have the local participant leave a
- * specific conference. Similar in fashion to CONFERENCE_LEFT. Contrary to it
+ * Signals the intention of the application to have the local participant leave
+ * a specific conference. Similar in fashion to CONFERENCE_LEFT. Contrary to it
* though, it's not guaranteed because CONFERENCE_LEFT may be triggered by
* lib-jitsi-meet and not the application.
*
@@ -108,17 +120,43 @@ function _conferenceLeft(conference) {
* be left by the local participant.
* @returns {{
* type: CONFERENCE_LEFT,
- * conference: {
- * jitsiConference: JitsiConference
- * }
+ * conference: JitsiConference
* }}
*/
export function conferenceWillLeave(conference) {
return {
type: CONFERENCE_WILL_LEAVE,
- conference: {
- jitsiConference: conference
+ conference
+ };
+}
+
+/**
+ * Initializes a new conference.
+ *
+ * @returns {Function}
+ */
+export function createConference() {
+ return (dispatch, getState) => {
+ const state = getState();
+ const connection = state['features/base/connection'].connection;
+
+ if (!connection) {
+ throw new Error('Cannot create conference without connection');
}
+
+ const room = state['features/base/conference'].room;
+
+ if (typeof room === 'undefined' || room === '') {
+ throw new Error('Cannot join conference without room name');
+ }
+
+ // TODO Take options from config.
+ const conference
+ = connection.initJitsiConference(room, { openSctp: true });
+
+ _addConferenceListeners(conference, dispatch);
+
+ conference.join();
};
}
@@ -138,49 +176,3 @@ export function setRoom(room) {
room
};
}
-
-/**
- * Setup various conference event handlers.
- *
- * @param {JitsiConference} conference - The JitsiConference instance.
- * @param {Dispatch} dispatch - The Redux dispatch function.
- * @private
- * @returns {void}
- */
-function _setupConferenceListeners(conference, dispatch) {
- conference.on(
- JitsiConferenceEvents.CONFERENCE_JOINED,
- () => dispatch(_conferenceJoined(conference)));
- conference.on(
- JitsiConferenceEvents.CONFERENCE_LEFT,
- () => dispatch(_conferenceLeft(conference)));
-
- conference.on(
- JitsiConferenceEvents.DOMINANT_SPEAKER_CHANGED,
- id => dispatch(dominantSpeakerChanged(id)));
-
- conference.on(
- JitsiConferenceEvents.TRACK_ADDED,
- t => t && !t.isLocal() && dispatch(trackAdded(t)));
- conference.on(
- JitsiConferenceEvents.TRACK_REMOVED,
- t => t && !t.isLocal() && dispatch(trackRemoved(t)));
-
- conference.on(
- JitsiConferenceEvents.USER_JOINED,
- (id, user) => dispatch(participantJoined({
- id,
- name: user.getDisplayName(),
- role: user.getRole()
- })));
- conference.on(
- JitsiConferenceEvents.USER_LEFT,
- id => dispatch(participantLeft(id)));
- conference.on(
- JitsiConferenceEvents.USER_ROLE_CHANGED,
- (id, role) => dispatch(participantRoleChanged(id, role)));
-
- conference.addCommandListener(
- EMAIL_COMMAND,
- (data, id) => dispatch(changeParticipantEmail(id, data.value)));
-}
diff --git a/react/features/base/conference/middleware.js b/react/features/base/conference/middleware.js
index 6c96f57b2..eb7c538b8 100644
--- a/react/features/base/conference/middleware.js
+++ b/react/features/base/conference/middleware.js
@@ -95,7 +95,7 @@ function _pinParticipant(store, next, action) {
pin = !localParticipant || !localParticipant.pinned;
}
if (pin) {
- const conference = state['features/base/conference'].jitsiConference;
+ const conference = state['features/base/conference'].conference;
try {
conference.pinParticipant(id);
@@ -107,6 +107,35 @@ function _pinParticipant(store, next, action) {
return next(action);
}
+/**
+ * Synchronizes local tracks from state with local tracks in JitsiConference
+ * instance.
+ *
+ * @param {Store} store - Redux store.
+ * @param {Object} action - Action object.
+ * @private
+ * @returns {Promise}
+ */
+function _syncConferenceLocalTracksWithState(store, action) {
+ const state = store.getState()['features/base/conference'];
+ const conference = state.conference;
+ let promise;
+
+ // XXX The conference may already be in the process of being left, that's
+ // why we should not add/remove local tracks to such conference.
+ if (conference && conference !== state.leaving) {
+ const track = action.track.jitsiTrack;
+
+ if (action.type === TRACK_ADDED) {
+ promise = _addLocalTracksToConference(conference, [ track ]);
+ } else {
+ promise = _removeLocalTracksFromConference(conference, [ track ]);
+ }
+ }
+
+ return promise || Promise.resolve();
+}
+
/**
* Notifies the feature base/conference that the action TRACK_ADDED
* or TRACK_REMOVED is being dispatched within a specific Redux store.
@@ -132,33 +161,3 @@ function _trackAddedOrRemoved(store, next, action) {
return next(action);
}
-
-/**
- * Synchronizes local tracks from state with local tracks in JitsiConference
- * instance.
- *
- * @param {Store} store - Redux store.
- * @param {Object} action - Action object.
- * @private
- * @returns {Promise}
- */
-function _syncConferenceLocalTracksWithState(store, action) {
- const conferenceState = store.getState()['features/base/conference'];
- const conference = conferenceState.jitsiConference;
- const leavingConference = conferenceState.leavingJitsiConference;
- let promise;
-
- // XXX The conference in state might be already in 'leaving' state, that's
- // why we should not add/remove local tracks to such conference.
- if (conference && conference !== leavingConference) {
- const track = action.track.jitsiTrack;
-
- if (action.type === TRACK_ADDED) {
- promise = _addLocalTracksToConference(conference, [ track ]);
- } else {
- promise = _removeLocalTracksFromConference(conference, [ track ]);
- }
- }
-
- return promise || Promise.resolve();
-}
diff --git a/react/features/base/conference/reducer.js b/react/features/base/conference/reducer.js
index 1790ef3f5..43c9032e1 100644
--- a/react/features/base/conference/reducer.js
+++ b/react/features/base/conference/reducer.js
@@ -1,4 +1,9 @@
-import { ReducerRegistry, setStateProperty } from '../redux';
+import JitsiMeetJS from '../lib-jitsi-meet';
+import {
+ ReducerRegistry,
+ setStateProperties,
+ setStateProperty
+} from '../redux';
import {
CONFERENCE_JOINED,
@@ -8,63 +13,104 @@ import {
} from './actionTypes';
import { isRoomValid } from './functions';
-const INITIAL_STATE = {
- jitsiConference: null,
-
- /**
- * Instance of JitsiConference that is currently in 'leaving' state.
- */
- leavingJitsiConference: null,
-
- /**
- * The name of the room of the conference (to be) joined (i.e.
- * {@link #jitsiConference}).
- *
- * @type {string}
- */
- room: null
-};
-
/**
* Listen for actions that contain the conference object, so that it can be
* stored for use by other action creators.
*/
-ReducerRegistry.register('features/base/conference',
- (state = INITIAL_STATE, action) => {
- switch (action.type) {
- case CONFERENCE_JOINED:
- return (
- setStateProperty(
- state,
- 'jitsiConference',
- action.conference.jitsiConference));
+ReducerRegistry.register('features/base/conference', (state = {}, action) => {
+ switch (action.type) {
+ case CONFERENCE_JOINED:
+ return _conferenceJoined(state, action);
- case CONFERENCE_LEFT:
- if (state.jitsiConference === action.conference.jitsiConference) {
- return {
- ...state,
- jitsiConference: null,
- leavingJitsiConference: state.leavingJitsiConference
- === action.conference.jitsiConference
- ? null
- : state.leavingJitsiConference
- };
- }
- break;
+ case CONFERENCE_LEFT:
+ return _conferenceLeft(state, action);
- case CONFERENCE_WILL_LEAVE:
- return (
- setStateProperty(
- state,
- 'leavingJitsiConference',
- action.conference.jitsiConference));
+ case CONFERENCE_WILL_LEAVE:
+ return _conferenceWillLeave(state, action);
- case SET_ROOM:
- return _setRoom(state, action);
- }
+ case SET_ROOM:
+ return _setRoom(state, action);
+ }
+ return state;
+});
+
+/**
+ * Reduces a specific Redux action CONFERENCE_JOINED of the feature
+ * base/conference.
+ *
+ * @param {Object} state - The Redux state of the feature base/conference.
+ * @param {Action} action - The Redux action CONFERENCE_JOINED to reduce.
+ * @private
+ * @returns {Object} The new state of the feature base/conference after the
+ * reduction of the specified action.
+ */
+function _conferenceJoined(state, action) {
+ return (
+ setStateProperties(state, {
+ /**
+ * The JitsiConference instance represented by the Redux state of
+ * the feature base/conference.
+ *
+ * @type {JitsiConference}
+ */
+ conference: action.conference,
+ leaving: undefined
+ }));
+}
+
+/**
+ * Reduces a specific Redux action CONFERENCE_LEFT of the feature
+ * base/conference.
+ *
+ * @param {Object} state - The Redux state of the feature base/conference.
+ * @param {Action} action - The Redux action CONFERENCE_LEFT to reduce.
+ * @private
+ * @returns {Object} The new state of the feature base/conference after the
+ * reduction of the specified action.
+ */
+function _conferenceLeft(state, action) {
+ const conference = action.conference;
+
+ if (state.conference !== conference) {
return state;
- });
+ }
+
+ return (
+ setStateProperties(state, {
+ conference: undefined,
+ leaving: undefined
+ }));
+}
+
+/**
+ * Reduces a specific Redux action CONFERENCE_WILL_LEAVE of the feature
+ * base/conference.
+ *
+ * @param {Object} state - The Redux state of the feature base/conference.
+ * @param {Action} action - The Redux action CONFERENCE_WILL_LEAVE to reduce.
+ * @private
+ * @returns {Object} The new state of the feature base/conference after the
+ * reduction of the specified action.
+ */
+function _conferenceWillLeave(state, action) {
+ const conference = action.conference;
+
+ if (state.conference !== conference) {
+ return state;
+ }
+
+ return (
+ setStateProperties(state, {
+ /**
+ * The JitsiConference instance which is currently in the process of
+ * being left.
+ *
+ * @type {JitsiConference}
+ */
+ leaving: conference
+ }));
+}
/**
* Reduces a specific Redux action SET_ROOM of the feature base/conference.
@@ -85,8 +131,13 @@ function _setRoom(state, action) {
// Technically, there are multiple values which don't represent valid
// room names. Practically, each of them is as bad as the rest of them
// because we can't use any of them to join a conference.
- room = INITIAL_STATE.room;
+ room = undefined;
}
+ /**
+ * The name of the room of the conference (to be) joined.
+ *
+ * @type {string}
+ */
return setStateProperty(state, 'room', room);
}
diff --git a/react/features/base/connection/actions.js b/react/features/base/connection/actions.js
index 5cf1fa258..2ad1d6686 100644
--- a/react/features/base/connection/actions.js
+++ b/react/features/base/connection/actions.js
@@ -105,8 +105,8 @@ export function connect() {
export function disconnect() {
return (dispatch, getState) => {
const state = getState();
- const conference = state['features/base/conference'].jitsiConference;
- const connection = state['features/base/connection'].jitsiConnection;
+ const conference = state['features/base/conference'].conference;
+ const connection = state['features/base/connection'].connection;
let promise;
diff --git a/react/features/base/connection/reducer.js b/react/features/base/connection/reducer.js
index cedb00f04..a2e08477e 100644
--- a/react/features/base/connection/reducer.js
+++ b/react/features/base/connection/reducer.js
@@ -35,8 +35,8 @@ ReducerRegistry.register('features/base/connection', (state = {}, action) => {
* reduction of the specified action.
*/
function _connectionDisconnected(state, action) {
- if (state.jitsiConnection === action.connection) {
- return setStateProperty(state, 'jitsiConnection', undefined);
+ if (state.connection === action.connection) {
+ return setStateProperty(state, 'connection', undefined);
}
return state;
@@ -53,7 +53,7 @@ function _connectionDisconnected(state, action) {
* reduction of the specified action.
*/
function _connectionEstablished(state, action) {
- return setStateProperty(state, 'jitsiConnection', action.connection);
+ return setStateProperty(state, 'connection', action.connection);
}
/**
diff --git a/react/features/base/participants/middleware.js b/react/features/base/participants/middleware.js
index f22fb6e5c..93018af41 100644
--- a/react/features/base/participants/middleware.js
+++ b/react/features/base/participants/middleware.js
@@ -17,9 +17,7 @@ import { LOCAL_PARTICIPANT_DEFAULT_ID } from './constants';
MiddlewareRegistry.register(store => next => action => {
switch (action.type) {
case CONFERENCE_JOINED:
- store.dispatch(
- localParticipantIdChanged(
- action.conference.jitsiConference.myUserId()));
+ store.dispatch(localParticipantIdChanged(action.conference.myUserId()));
break;
case CONFERENCE_LEFT:
diff --git a/react/features/base/redux/functions.js b/react/features/base/redux/functions.js
index a64ee6130..6aecd65f4 100644
--- a/react/features/base/redux/functions.js
+++ b/react/features/base/redux/functions.js
@@ -1,3 +1,26 @@
+/**
+ * Sets specific properties of a specific state to specific values and prevents
+ * unnecessary state changes.
+ *
+ * @param {Object} target - The state on which the specified properties are to
+ * be set.
+ * @param {Object} source - The map of properties to values which are to be set
+ * on the specified target.
+ * @returns {Object} The specified target if the values of the specified
+ * properties equal the specified values; otherwise, a new state constructed
+ * from the specified target by setting the specified properties to the
+ * specified values.
+ */
+export function setStateProperties(target, source) {
+ let t = target;
+
+ for (const property in source) { // eslint-disable-line guard-for-in
+ t = setStateProperty(t, property, source[property], t === target);
+ }
+
+ return t;
+}
+
/**
* Sets a specific property of a specific state to a specific value. Prevents
* unnecessary state changes (when the specified value is equal to the
@@ -15,11 +38,36 @@
* property to the specified value.
*/
export function setStateProperty(state, property, value) {
+ return _setStateProperty(state, property, value, /* copyOnWrite */ false);
+}
+
+/* eslint-disable max-params */
+
+/**
+ * Sets a specific property of a specific state to a specific value. Prevents
+ * unnecessary state changes (when the specified value is equal to the
+ * value of the specified property of the specified state).
+ *
+ * @param {Object} state - The (Redux) state from which a state is to be
+ * constructed by setting the specified property to the specified
+ * value.
+ * @param {string} property - The property of state which is to be
+ * assigned the specified value.
+ * @param {*} value - The value to assign to the specified property.
+ * @param {boolean} copyOnWrite - If the specified state is to not be
+ * modified, true; otherwise, false.
+ * @returns {Object} The specified state if the value of the specified
+ * property equals the specified value/tt> or copyOnWrite
+ * is truthy; otherwise, a new state constructed from the specified
+ * state by setting the specified property to the specified
+ * value.
+ */
+function _setStateProperty(state, property, value, copyOnWrite) {
// Delete state properties that are to be set to undefined. (It is a matter
// of personal preference, mostly.)
if (typeof value === 'undefined'
&& Object.prototype.hasOwnProperty.call(state, property)) {
- const newState = { ...state };
+ const newState = copyOnWrite ? { ...state } : state;
if (delete newState[property]) {
return newState;
@@ -27,11 +75,17 @@ export function setStateProperty(state, property, value) {
}
if (state[property] !== value) {
- return {
- ...state,
- [property]: value
- };
+ if (copyOnWrite) {
+ return {
+ ...state,
+ [property]: value
+ };
+ }
+
+ state[property] = value;
}
return state;
}
+
+/* eslint-enable max-params */
diff --git a/react/features/conference/components/Conference.native.js b/react/features/conference/components/Conference.native.js
index 2353788b2..28bb756ec 100644
--- a/react/features/conference/components/Conference.native.js
+++ b/react/features/conference/components/Conference.native.js
@@ -1,10 +1,7 @@
import React, { Component } from 'react';
import { connect as reactReduxConnect } from 'react-redux';
-import {
- connect,
- disconnect
-} from '../../base/connection';
+import { connect, disconnect } from '../../base/connection';
import { Container } from '../../base/react';
import { FilmStrip } from '../../filmStrip';
import { LargeVideo } from '../../largeVideo';
diff --git a/react/features/largeVideo/actions.js b/react/features/largeVideo/actions.js
index 064a8f1a6..c64f733d8 100644
--- a/react/features/largeVideo/actions.js
+++ b/react/features/largeVideo/actions.js
@@ -20,7 +20,7 @@ import './reducer';
export function selectParticipant() {
return (dispatch, getState) => {
const state = getState();
- const conference = state['features/base/conference'].jitsiConference;
+ const conference = state['features/base/conference'].conference;
if (conference) {
const largeVideo = state['features/largeVideo'];