From 85a168d51ba82c2bd7fe6a4c2c490804cf9eccd7 Mon Sep 17 00:00:00 2001 From: Lyubo Marinov Date: Fri, 4 Aug 2017 16:06:42 -0500 Subject: [PATCH] [RN] base/media is intent, base/tracks is reality --- react/features/base/conference/actions.js | 12 ++- react/features/base/conference/middleware.js | 78 +++++++++---------- react/features/base/media/middleware.js | 13 ++-- react/features/base/tracks/actions.js | 47 +++++++---- react/features/base/tracks/middleware.js | 32 ++++---- .../features/mobile/audio-mode/middleware.js | 30 ++----- react/features/mobile/background/actions.js | 7 +- .../features/mobile/full-screen/middleware.js | 45 ++++------- react/features/mobile/proximity/middleware.js | 21 +++-- .../toolbox/components/Toolbox.native.js | 69 ++++++++++++---- react/features/toolbox/functions.native.js | 29 ++----- 11 files changed, 195 insertions(+), 188 deletions(-) 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()); } }; }