diff --git a/react/features/base/conference/actions.js b/react/features/base/conference/actions.js
index 3b0282f37..3d03bec72 100644
--- a/react/features/base/conference/actions.js
+++ b/react/features/base/conference/actions.js
@@ -62,13 +62,17 @@ function _addConferenceListeners(conference, dispatch) {
// Dispatches into features/base/media follow:
- // FIXME: This is needed because when Jicofo tells us to start muted
- // lib-jitsi-meet does the actual muting. Perhaps this should be refactored
- // so applications are hinted to start muted, but lib-jitsi-meet doesn't
- // take action.
conference.on(
JitsiConferenceEvents.STARTED_MUTED,
() => {
+ // XXX Jicofo tells lib-jitsi-meet to start with audio and/or video
+ // muted i.e. Jicofo expresses an intent. Lib-jitsi-meet has turned
+ // Jicofo's intent into reality by actually muting the respective
+ // tracks. The reality is expressed in base/tracks already so what
+ // is left is to express Jicofo's intent in base/media.
+ // TODO Maybe the app needs to learn about Jicofo's intent and
+ // transfer that intent to lib-jitsi-meet instead of lib-jitsi-meet
+ // acting on Jicofo's intent without the app's knowledge.
dispatch(setAudioMuted(Boolean(conference.startAudioMuted)));
dispatch(setVideoMuted(Boolean(conference.startVideoMuted)));
});
diff --git a/react/features/base/conference/middleware.js b/react/features/base/conference/middleware.js
index 5dc30a72b..774394200 100644
--- a/react/features/base/conference/middleware.js
+++ b/react/features/base/conference/middleware.js
@@ -32,7 +32,7 @@ import {
/**
* Implements the middleware of the feature base/conference.
*
- * @param {Store} store - Redux store.
+ * @param {Store} store - The redux store.
* @returns {Function}
*/
MiddlewareRegistry.register(store => next => action => {
@@ -40,13 +40,13 @@ MiddlewareRegistry.register(store => next => action => {
case CONNECTION_ESTABLISHED:
return _connectionEstablished(store, next, action);
- case CONFERENCE_JOINED:
- return _conferenceJoined(store, next, action);
-
case CONFERENCE_FAILED:
case CONFERENCE_LEFT:
return _conferenceFailedOrLeft(store, next, action);
+ case CONFERENCE_JOINED:
+ return _conferenceJoined(store, next, action);
+
case PIN_PARTICIPANT:
return _pinParticipant(store, next, action);
@@ -66,13 +66,13 @@ MiddlewareRegistry.register(store => next => action => {
/**
* Notifies the feature base/conference that the action CONNECTION_ESTABLISHED
- * is being dispatched within a specific Redux store.
+ * is being dispatched within a specific redux store.
*
- * @param {Store} store - The Redux store in which the specified action is being
+ * @param {Store} store - The redux store in which the specified action is being
* dispatched.
- * @param {Dispatch} next - The Redux dispatch function to dispatch the
+ * @param {Dispatch} next - The redux dispatch function to dispatch the
* specified action to the specified store.
- * @param {Action} action - The Redux action CONNECTION_ESTABLISHED which is
+ * @param {Action} action - The redux action CONNECTION_ESTABLISHED which is
* being dispatched in the specified store.
* @private
* @returns {Object} The new state that is the result of the reduction of the
@@ -91,37 +91,37 @@ function _connectionEstablished(store, next, action) {
}
/**
- * Does extra sync up on properties that may need to be updated, after
- * the conference failed or was left.
+ * Does extra sync up on properties that may need to be updated after the
+ * conference failed or was left.
*
- * @param {Store} store - The Redux store in which the specified action is being
+ * @param {Store} store - The redux store in which the specified action is being
* dispatched.
- * @param {Dispatch} next - The Redux dispatch function to dispatch the
+ * @param {Dispatch} next - The redux dispatch function to dispatch the
* specified action to the specified store.
- * @param {Action} action - The Redux action {@link CONFERENCE_FAILED} or
+ * @param {Action} action - The redux action {@link CONFERENCE_FAILED} or
* {@link CONFERENCE_LEFT} which is being dispatched in the specified store.
* @private
* @returns {Object} The new state that is the result of the reduction of the
* specified action.
*/
-function _conferenceFailedOrLeft(store, next, action) {
+function _conferenceFailedOrLeft({ dispatch, getState }, next, action) {
const result = next(action);
- const { audioOnly } = store.getState()['features/base/conference'];
- audioOnly && store.dispatch(setAudioOnly(false));
+ getState()['features/base/conference'].audioOnly
+ && dispatch(setAudioOnly(false));
return result;
}
/**
- * Does extra sync up on properties that may need to be updated, after
- * the conference was joined.
+ * Does extra sync up on properties that may need to be updated after the
+ * conference was joined.
*
- * @param {Store} store - The Redux store in which the specified action is being
+ * @param {Store} store - The redux store in which the specified action is being
* dispatched.
- * @param {Dispatch} next - The Redux dispatch function to dispatch the
+ * @param {Dispatch} next - The redux dispatch function to dispatch the
* specified action to the specified store.
- * @param {Action} action - The Redux action CONFERENCE_JOINED which is being
+ * @param {Action} action - The redux action CONFERENCE_JOINED which is being
* dispatched in the specified store.
* @private
* @returns {Object} The new state that is the result of the reduction of the
@@ -144,14 +144,14 @@ function _conferenceJoined(store, next, action) {
/**
* Notifies the feature base/conference that the action PIN_PARTICIPANT is being
- * dispatched within a specific Redux store. Pins the specified remote
+ * dispatched within a specific redux store. Pins the specified remote
* participant in the associated conference, ignores the local participant.
*
- * @param {Store} store - The Redux store in which the specified action is being
+ * @param {Store} store - The redux store in which the specified action is being
* dispatched.
- * @param {Dispatch} next - The Redux dispatch function to dispatch the
+ * @param {Dispatch} next - The redux dispatch function to dispatch the
* specified action to the specified store.
- * @param {Action} action - The Redux action PIN_PARTICIPANT which is being
+ * @param {Action} action - The redux action PIN_PARTICIPANT which is being
* dispatched in the specified store.
* @private
* @returns {Object} The new state that is the result of the reduction of the
@@ -195,26 +195,26 @@ function _pinParticipant(store, next, action) {
* Sets the audio-only flag for the current conference. When audio-only is set,
* local video is muted and last N is set to 0 to avoid receiving remote video.
*
- * @param {Store} store - The Redux store in which the specified action is being
+ * @param {Store} store - The redux store in which the specified action is being
* dispatched.
- * @param {Dispatch} next - The Redux dispatch function to dispatch the
+ * @param {Dispatch} next - The redux dispatch function to dispatch the
* specified action to the specified store.
- * @param {Action} action - The Redux action SET_AUDIO_ONLY which is being
+ * @param {Action} action - The redux action SET_AUDIO_ONLY which is being
* dispatched in the specified store.
* @private
* @returns {Object} The new state that is the result of the reduction of the
* specified action.
*/
-function _setAudioOnly({ dispatch }, next, action) {
+function _setAudioOnly({ dispatch, getState }, next, action) {
const result = next(action);
- const { audioOnly } = action;
+ const { audioOnly } = getState()['features/base/conference'];
// Set lastN to 0 in case audio-only is desired; leave it as undefined,
// otherwise, and the default lastN value will be chosen automatically.
dispatch(setLastN(audioOnly ? 0 : undefined));
- // Mute the local video.
+ // Mute/unmute the local video.
dispatch(setVideoMuted(audioOnly, VIDEO_MUTISM_AUTHORITY.AUDIO_ONLY));
if (typeof APP !== 'undefined') {
@@ -229,11 +229,11 @@ function _setAudioOnly({ dispatch }, next, action) {
/**
* Sets the last N (value) of the video channel in the conference.
*
- * @param {Store} store - The Redux store in which the specified action is being
+ * @param {Store} store - The redux store in which the specified action is being
* dispatched.
- * @param {Dispatch} next - The Redux dispatch function to dispatch the
+ * @param {Dispatch} next - The redux dispatch function to dispatch the
* specified action to the specified store.
- * @param {Action} action - The Redux action SET_LASTN which is being dispatched
+ * @param {Action} action - The redux action SET_LASTN which is being dispatched
* in the specified store.
* @private
* @returns {Object} The new state that is the result of the reduction of the
@@ -257,7 +257,7 @@ function _setLastN(store, next, action) {
* Synchronizes local tracks from state with local tracks in JitsiConference
* instance.
*
- * @param {Store} store - Redux store.
+ * @param {Store} store - The redux store.
* @param {Object} action - Action object.
* @private
* @returns {Promise}
@@ -284,13 +284,13 @@ function _syncConferenceLocalTracksWithState(store, action) {
/**
* Notifies the feature base/conference that the action TRACK_ADDED
- * or TRACK_REMOVED is being dispatched within a specific Redux store.
+ * or TRACK_REMOVED is being dispatched within a specific redux store.
*
- * @param {Store} store - The Redux store in which the specified action is being
+ * @param {Store} store - The redux store in which the specified action is being
* dispatched.
- * @param {Dispatch} next - The Redux dispatch function to dispatch the
+ * @param {Dispatch} next - The redux dispatch function to dispatch the
* specified action to the specified store.
- * @param {Action} action - The Redux action TRACK_ADDED or TRACK_REMOVED which
+ * @param {Action} action - The redux action TRACK_ADDED or TRACK_REMOVED which
* is being dispatched in the specified store.
* @private
* @returns {Object} The new state that is the result of the reduction of the
diff --git a/react/features/base/media/middleware.js b/react/features/base/media/middleware.js
index f2b5ff809..f17971d56 100644
--- a/react/features/base/media/middleware.js
+++ b/react/features/base/media/middleware.js
@@ -72,16 +72,15 @@ function _setRoom({ dispatch, getState }, next, action) {
&& (videoMuted = config.startWithVideoMuted);
// Apply startWithAudioMuted and startWithVideoMuted.
- const { audio, video } = state['features/base/media'];
-
audioMuted = Boolean(audioMuted);
videoMuted = Boolean(videoMuted);
- (audio.muted !== audioMuted) && dispatch(setAudioMuted(audioMuted));
- (video.facingMode !== CAMERA_FACING_MODE.USER)
- && dispatch(setCameraFacingMode(CAMERA_FACING_MODE.USER));
- (Boolean(video.muted) !== videoMuted)
- && dispatch(setVideoMuted(videoMuted));
+ // Unconditionally express the desires/expectations/intents of the app and
+ // the user i.e. the state of base/media. Eventually, practice/reality i.e.
+ // the state of base/tracks will or will not agree with the desires.
+ dispatch(setAudioMuted(audioMuted));
+ dispatch(setCameraFacingMode(CAMERA_FACING_MODE.USER));
+ dispatch(setVideoMuted(videoMuted));
return next(action);
}
diff --git a/react/features/base/tracks/actions.js b/react/features/base/tracks/actions.js
index 259aa7003..b5c59d3fd 100644
--- a/react/features/base/tracks/actions.js
+++ b/react/features/base/tracks/actions.js
@@ -87,19 +87,21 @@ export function destroyLocalTracks() {
*/
export function replaceLocalTrack(oldTrack, newTrack, conference) {
return (dispatch, getState) => {
- const currentConference = conference
- || getState()['features/base/conference'].conference;
+ conference
- return currentConference.replaceTrack(oldTrack, newTrack)
+ // eslint-disable-next-line no-param-reassign
+ || (conference = getState()['features/base/conference'].conference);
+
+ return conference.replaceTrack(oldTrack, newTrack)
.then(() => {
- // We call dispose after doing the replace because
- // dispose will try and do a new o/a after the
- // track removes itself. Doing it after means
- // the JitsiLocalTrack::conference member is already
- // cleared, so it won't try and do the o/a
- const disposePromise = oldTrack
- ? dispatch(_disposeAndRemoveTracks([ oldTrack ]))
- : Promise.resolve();
+ // We call dispose after doing the replace because dispose will
+ // try and do a new o/a after the track removes itself. Doing it
+ // after means the JitsiLocalTrack.conference is already
+ // cleared, so it won't try and do the o/a.
+ const disposePromise
+ = oldTrack
+ ? dispatch(_disposeAndRemoveTracks([ oldTrack ]))
+ : Promise.resolve();
return disposePromise
.then(() => {
@@ -113,10 +115,12 @@ export function replaceLocalTrack(oldTrack, newTrack, conference) {
// track's mute state. If this is not done, the
// current mute state of the app will be reflected
// on the track, not vice-versa.
- const muteAction = newTrack.isVideoTrack()
- ? setVideoMuted : setAudioMuted;
+ const setMuted
+ = newTrack.isVideoTrack()
+ ? setVideoMuted
+ : setAudioMuted;
- return dispatch(muteAction(newTrack.isMuted()));
+ return dispatch(setMuted(newTrack.isMuted()));
}
})
.then(() => {
@@ -366,17 +370,26 @@ export function setTrackMuted(track, muted) {
const f = muted ? 'mute' : 'unmute';
- // FIXME: This operation disregards the authority. It is not a problem
- // (on mobile) at the moment, but it will be once we start not creating
- // tracks early. Refactor this then.
return track[f]().catch(error => {
console.error(`set track ${f} failed`, error);
+ if (navigator.product === 'ReactNative') {
+ // Synchronizing the state of base/tracks into the state of
+ // base/media is not required in React (and, respectively, React
+ // Native) because base/media expresses the app's and the user's
+ // desires/expectations/intents and base/tracks expresses
+ // practice/reality. Unfortunately, the old Web does not comply
+ // and/or does the opposite.
+ return;
+ }
+
const setMuted
= track.mediaType === MEDIA_TYPE.AUDIO
? setAudioMuted
: setVideoMuted;
+ // FIXME The following disregards VIDEO_MUTISM_AUTHORITY (in the
+ // case of setVideoMuted, of course).
dispatch(setMuted(!muted));
});
};
diff --git a/react/features/base/tracks/middleware.js b/react/features/base/tracks/middleware.js
index c63ac141c..504cd15fc 100644
--- a/react/features/base/tracks/middleware.js
+++ b/react/features/base/tracks/middleware.js
@@ -100,35 +100,37 @@ MiddlewareRegistry.register(store => next => action => {
break;
case TRACK_UPDATED:
- // TODO Remove the below calls to APP.UI once components interested in
- // track mute changes are moved into react.
+ // TODO Remove the following calls to APP.UI once components interested
+ // in track mute changes are moved into React and/or redux.
if (typeof APP !== 'undefined') {
const { jitsiTrack } = action.track;
- const isMuted = jitsiTrack.isMuted();
+ const muted = jitsiTrack.isMuted();
const participantID = jitsiTrack.getParticipantId();
const isVideoTrack = jitsiTrack.isVideoTrack();
if (jitsiTrack.isLocal()) {
if (isVideoTrack) {
- APP.conference.videoMuted = isMuted;
+ APP.conference.videoMuted = muted;
} else {
- APP.conference.audioMuted = isMuted;
+ APP.conference.audioMuted = muted;
}
}
if (isVideoTrack) {
- APP.UI.setVideoMuted(participantID, isMuted);
+ APP.UI.setVideoMuted(participantID, muted);
APP.UI.onPeerVideoTypeChanged(
- participantID, jitsiTrack.videoType);
+ participantID,
+ jitsiTrack.videoType);
} else {
- APP.UI.setAudioMuted(participantID, isMuted);
+ APP.UI.setAudioMuted(participantID, muted);
}
- // XXX This function synchronizes track states with media states.
- // This is not required in React, because media is the source of
- // truth, synchronization should always happen in the media -> track
- // direction. The old web, however, does the opposite, hence the
- // need for this.
+ // XXX The following synchronizes the state of base/tracks into the
+ // state of base/media. Which is not required in React (and,
+ // respectively, React Native) because base/media expresses the
+ // app's and the user's desires/expectations/intents and base/tracks
+ // expresses practice/reality. Unfortunately, the old Web does not
+ // comply and/or does the opposite. Hence, the following:
return _trackUpdated(store, next, action);
}
@@ -149,8 +151,8 @@ MiddlewareRegistry.register(store => next => action => {
* @returns {Track} The local Track associated with the specified
* mediaType in the specified store.
*/
-function _getLocalTrack(store, mediaType: MEDIA_TYPE) {
- return getLocalTrack(store.getState()['features/base/tracks'], mediaType);
+function _getLocalTrack({ getState }, mediaType: MEDIA_TYPE) {
+ return getLocalTrack(getState()['features/base/tracks'], mediaType);
}
/**
diff --git a/react/features/mobile/audio-mode/middleware.js b/react/features/mobile/audio-mode/middleware.js
index 781b577ed..a964b80ff 100644
--- a/react/features/mobile/audio-mode/middleware.js
+++ b/react/features/mobile/audio-mode/middleware.js
@@ -16,10 +16,10 @@ import { MiddlewareRegistry } from '../../base/redux';
* based on the type of conference. Audio-only conferences don't use the speaker
* by default, and video conferences do.
*
- * @param {Store} store - Redux store.
+ * @param {Store} store - The redux store.
* @returns {Function}
*/
-MiddlewareRegistry.register(store => next => action => {
+MiddlewareRegistry.register(({ getState }) => next => action => {
const AudioMode = NativeModules.AudioMode;
if (AudioMode) {
@@ -32,34 +32,20 @@ MiddlewareRegistry.register(store => next => action => {
mode = AudioMode.DEFAULT;
break;
- case CONFERENCE_WILL_JOIN: {
- const { audioOnly } = store.getState()['features/base/conference'];
-
- mode = audioOnly ? AudioMode.AUDIO_CALL : AudioMode.VIDEO_CALL;
- break;
- }
-
+ case CONFERENCE_WILL_JOIN:
case SET_AUDIO_ONLY: {
- const { conference } = store.getState()['features/base/conference'];
-
- if (conference) {
+ if (getState()['features/base/conference'].conference
+ || action.conference) {
mode
= action.audioOnly
- ? AudioMode.AUDIO_CALL
- : AudioMode.VIDEO_CALL;
- } else {
- mode = null;
+ ? AudioMode.AUDIO_CALL
+ : AudioMode.VIDEO_CALL;
}
-
break;
}
-
- default:
- mode = null;
- break;
}
- if (mode !== null) {
+ if (typeof mode !== 'undefined') {
AudioMode.setMode(mode)
.catch(err =>
console.error(
diff --git a/react/features/mobile/background/actions.js b/react/features/mobile/background/actions.js
index 198a06b5d..50029f235 100644
--- a/react/features/mobile/background/actions.js
+++ b/react/features/mobile/background/actions.js
@@ -1,10 +1,7 @@
import { setLastN } from '../../base/conference';
import { setVideoMuted, VIDEO_MUTISM_AUTHORITY } from '../../base/media';
-import {
- _SET_APP_STATE_LISTENER,
- APP_STATE_CHANGED
-} from './actionTypes';
+import { _SET_APP_STATE_LISTENER, APP_STATE_CHANGED } from './actionTypes';
/**
* Sets the listener to be used with React Native's AppState API.
@@ -41,7 +38,7 @@ export function _setBackgroundVideoMuted(muted: boolean) {
// for last N will be chosen automatically.
const { audioOnly } = getState()['features/base/conference'];
- !audioOnly && dispatch(setLastN(muted ? 0 : undefined));
+ audioOnly || dispatch(setLastN(muted ? 0 : undefined));
dispatch(setVideoMuted(muted, VIDEO_MUTISM_AUTHORITY.BACKGROUND));
};
}
diff --git a/react/features/mobile/full-screen/middleware.js b/react/features/mobile/full-screen/middleware.js
index b7be61bbc..856e428b2 100644
--- a/react/features/mobile/full-screen/middleware.js
+++ b/react/features/mobile/full-screen/middleware.js
@@ -22,29 +22,31 @@ import { MiddlewareRegistry } from '../../base/redux';
* In immersive mode the status and navigation bars are hidden and thus the
* entire screen will be covered by our application.
*
- * @param {Store} store - Redux store.
+ * @param {Store} store - The redux store.
* @returns {Function}
*/
-MiddlewareRegistry.register(store => next => action => {
+MiddlewareRegistry.register(({ getState }) => next => action => {
+ const result = next(action);
+
let fullScreen = null;
switch (action.type) {
- case APP_STATE_CHANGED: {
- // Check if we just came back from the background and reenable full
+ case APP_STATE_CHANGED:
+ case CONFERENCE_WILL_JOIN:
+ case HIDE_DIALOG:
+ case SET_AUDIO_ONLY: {
+ // Check if we just came back from the background and re-enable full
// screen mode if necessary.
- if (action.appState === 'active') {
- const { audioOnly, conference }
- = store.getState()['features/base/conference'];
+ const { appState } = action;
- fullScreen = conference ? !audioOnly : false;
+ if (typeof appState !== 'undefined' && appState !== 'active') {
+ break;
}
- break;
- }
- case CONFERENCE_WILL_JOIN: {
- const { audioOnly } = store.getState()['features/base/conference'];
+ const { audioOnly, conference }
+ = getState()['features/base/conference'];
- fullScreen = !audioOnly;
+ fullScreen = conference || action.conference ? !audioOnly : false;
break;
}
@@ -52,21 +54,6 @@ MiddlewareRegistry.register(store => next => action => {
case CONFERENCE_LEFT:
fullScreen = false;
break;
-
- case HIDE_DIALOG: {
- const { audioOnly, conference }
- = store.getState()['features/base/conference'];
-
- fullScreen = conference ? !audioOnly : false;
- break;
- }
-
- case SET_AUDIO_ONLY: {
- const { conference } = store.getState()['features/base/conference'];
-
- fullScreen = conference ? !action.audioOnly : false;
- break;
- }
}
if (fullScreen !== null) {
@@ -75,7 +62,7 @@ MiddlewareRegistry.register(store => next => action => {
console.warn(`Failed to set full screen mode: ${err}`));
}
- return next(action);
+ return result;
});
/**
diff --git a/react/features/mobile/proximity/middleware.js b/react/features/mobile/proximity/middleware.js
index f2da529ba..c116e99c2 100644
--- a/react/features/mobile/proximity/middleware.js
+++ b/react/features/mobile/proximity/middleware.js
@@ -14,32 +14,29 @@ import { MiddlewareRegistry } from '../../base/redux';
* the screen and disable touch controls when an object is nearby. The
* functionality is enabled when a conference is in audio-only mode.
*
- * @param {Store} store - Redux store.
+ * @param {Store} store - The redux store.
* @returns {Function}
*/
-MiddlewareRegistry.register(store => next => action => {
+MiddlewareRegistry.register(({ getState }) => next => action => {
+ const result = next(action);
+
switch (action.type) {
- case CONFERENCE_JOINED: {
- const { audioOnly } = store.getState()['features/base/conference'];
-
- _setProximityEnabled(audioOnly);
- break;
- }
-
case CONFERENCE_FAILED:
case CONFERENCE_LEFT:
_setProximityEnabled(false);
break;
+ case CONFERENCE_JOINED:
case SET_AUDIO_ONLY: {
- const { conference } = store.getState()['features/base/conference'];
+ const { audioOnly, conference }
+ = getState()['features/base/conference'];
- conference && _setProximityEnabled(action.audioOnly);
+ conference && _setProximityEnabled(audioOnly);
break;
}
}
- return next(action);
+ return result;
});
/**
diff --git a/react/features/toolbox/components/Toolbox.native.js b/react/features/toolbox/components/Toolbox.native.js
index de2e9d421..a0a7e4937 100644
--- a/react/features/toolbox/components/Toolbox.native.js
+++ b/react/features/toolbox/components/Toolbox.native.js
@@ -3,7 +3,12 @@ import { View } from 'react-native';
import { connect } from 'react-redux';
import { toggleAudioOnly } from '../../base/conference';
-import { MEDIA_TYPE, toggleCameraFacingMode } from '../../base/media';
+import {
+ MEDIA_TYPE,
+ setAudioMuted,
+ setVideoMuted,
+ toggleCameraFacingMode
+} from '../../base/media';
import { Container } from '../../base/react';
import { ColorPalette } from '../../base/styles';
import { beginRoomLockRequest } from '../../room-lock';
@@ -56,11 +61,6 @@ class Toolbox extends Component {
*/
_onShareRoom: React.PropTypes.func,
- /**
- * Handler for toggle audio.
- */
- _onToggleAudio: React.PropTypes.func,
-
/**
* Toggles the audio-only flag of the conference.
*/
@@ -72,11 +72,6 @@ class Toolbox extends Component {
*/
_onToggleCameraFacingMode: React.PropTypes.func,
- /**
- * Handler for toggling video.
- */
- _onToggleVideo: React.PropTypes.func,
-
/**
* Flag showing whether video is muted.
*/
@@ -85,9 +80,25 @@ class Toolbox extends Component {
/**
* Flag showing whether toolbar is visible.
*/
- _visible: React.PropTypes.bool
+ _visible: React.PropTypes.bool,
+
+ dispatch: React.PropTypes.func
};
+ /**
+ * Initializes a new {@code Toolbox} instance.
+ *
+ * @param {Object} props - The read-only React {@code Component} props with
+ * which the new instance is to be initialized.
+ */
+ constructor(props) {
+ super(props);
+
+ // Bind event handlers so they are only bound once per instance.
+ this._onToggleAudio = this._onToggleAudio.bind(this);
+ this._onToggleVideo = this._onToggleVideo.bind(this);
+ }
+
/**
* Implements React's {@link Component#render()}.
*
@@ -144,6 +155,36 @@ class Toolbox extends Component {
};
}
+ /**
+ * Dispatches an action to toggle the mute state of the audio/microphone.
+ *
+ * @private
+ * @returns {void}
+ */
+ _onToggleAudio() {
+ // The user sees the reality i.e. the state of base/tracks and intends
+ // to change reality by tapping on the respective button i.e. the user
+ // sets the state of base/media. Whether the user's intention will turn
+ // into reality is a whole different story which is of no concern to the
+ // tapping.
+ this.props.dispatch(setAudioMuted(!this.props._audioMuted));
+ }
+
+ /**
+ * Dispatches an action to toggle the mute state of the video/camera.
+ *
+ * @private
+ * @returns {void}
+ */
+ _onToggleVideo() {
+ // The user sees the reality i.e. the state of base/tracks and intends
+ // to change reality by tapping on the respective button i.e. the user
+ // sets the state of base/media. Whether the user's intention will turn
+ // into reality is a whole different story which is of no concern to the
+ // tapping.
+ this.props.dispatch(setVideoMuted(!this.props._videoMuted));
+ }
+
/**
* Renders the toolbar which contains the primary buttons such as hangup,
* audio and video mute.
@@ -162,7 +203,7 @@ class Toolbox extends Component {
);
diff --git a/react/features/toolbox/functions.native.js b/react/features/toolbox/functions.native.js
index b1ba82062..1c443c368 100644
--- a/react/features/toolbox/functions.native.js
+++ b/react/features/toolbox/functions.native.js
@@ -3,7 +3,6 @@
import type { Dispatch } from 'redux';
import { appNavigate } from '../app';
-import { toggleAudioMuted, toggleVideoMuted } from '../base/media';
import { getLocalAudioTrack, getLocalVideoTrack } from '../base/tracks';
/**
@@ -19,6 +18,11 @@ import { getLocalAudioTrack, getLocalVideoTrack } from '../base/tracks';
*/
export function abstractMapDispatchToProps(dispatch: Dispatch<*>): Object {
return {
+ // Inject {@code dispatch} into the React Component's props in case it
+ // needs to dispatch an action in the redux store without
+ // {@code mapDispatchToProps}.
+ dispatch,
+
/**
* Dispatches action to leave the current conference.
*
@@ -33,29 +37,6 @@ export function abstractMapDispatchToProps(dispatch: Dispatch<*>): Object {
// expression of (1) the lack of knowledge & (2) the desire to no
// longer have a valid room name to join.
dispatch(appNavigate(undefined));
- },
-
- /**
- * Dispatches an action to toggle the mute state of the
- * audio/microphone.
- *
- * @private
- * @returns {Object} - Dispatched action.
- * @type {Function}
- */
- _onToggleAudio() {
- dispatch(toggleAudioMuted());
- },
-
- /**
- * Dispatches an action to toggle the mute state of the video/camera.
- *
- * @private
- * @returns {Object} - Dispatched action.
- * @type {Function}
- */
- _onToggleVideo() {
- dispatch(toggleVideoMuted());
}
};
}