[RN] base/media is intent, base/tracks is reality

This commit is contained in:
Lyubo Marinov 2017-08-04 16:06:42 -05:00
parent d600504d85
commit 85a168d51b
11 changed files with 195 additions and 188 deletions

View File

@ -62,13 +62,17 @@ function _addConferenceListeners(conference, dispatch) {
// Dispatches into features/base/media follow: // 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( conference.on(
JitsiConferenceEvents.STARTED_MUTED, 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(setAudioMuted(Boolean(conference.startAudioMuted)));
dispatch(setVideoMuted(Boolean(conference.startVideoMuted))); dispatch(setVideoMuted(Boolean(conference.startVideoMuted)));
}); });

View File

@ -32,7 +32,7 @@ import {
/** /**
* Implements the middleware of the feature base/conference. * Implements the middleware of the feature base/conference.
* *
* @param {Store} store - Redux store. * @param {Store} store - The redux store.
* @returns {Function} * @returns {Function}
*/ */
MiddlewareRegistry.register(store => next => action => { MiddlewareRegistry.register(store => next => action => {
@ -40,13 +40,13 @@ MiddlewareRegistry.register(store => next => action => {
case CONNECTION_ESTABLISHED: case CONNECTION_ESTABLISHED:
return _connectionEstablished(store, next, action); return _connectionEstablished(store, next, action);
case CONFERENCE_JOINED:
return _conferenceJoined(store, next, action);
case CONFERENCE_FAILED: case CONFERENCE_FAILED:
case CONFERENCE_LEFT: case CONFERENCE_LEFT:
return _conferenceFailedOrLeft(store, next, action); return _conferenceFailedOrLeft(store, next, action);
case CONFERENCE_JOINED:
return _conferenceJoined(store, next, action);
case PIN_PARTICIPANT: case PIN_PARTICIPANT:
return _pinParticipant(store, next, action); return _pinParticipant(store, next, action);
@ -66,13 +66,13 @@ MiddlewareRegistry.register(store => next => action => {
/** /**
* Notifies the feature base/conference that the action CONNECTION_ESTABLISHED * 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. * 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. * 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. * being dispatched in the specified store.
* @private * @private
* @returns {Object} The new state that is the result of the reduction of the * @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 * Does extra sync up on properties that may need to be updated after the
* the conference failed or was left. * 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. * 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. * 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. * {@link CONFERENCE_LEFT} which is being dispatched in the specified store.
* @private * @private
* @returns {Object} The new state that is the result of the reduction of the * @returns {Object} The new state that is the result of the reduction of the
* specified action. * specified action.
*/ */
function _conferenceFailedOrLeft(store, next, action) { function _conferenceFailedOrLeft({ dispatch, getState }, next, action) {
const result = 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; return result;
} }
/** /**
* Does extra sync up on properties that may need to be updated, after * Does extra sync up on properties that may need to be updated after the
* the conference was joined. * 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. * 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. * 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. * dispatched in the specified store.
* @private * @private
* @returns {Object} The new state that is the result of the reduction of the * @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 * 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. * 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. * 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. * 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. * dispatched in the specified store.
* @private * @private
* @returns {Object} The new state that is the result of the reduction of the * @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, * 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. * 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. * 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. * 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. * dispatched in the specified store.
* @private * @private
* @returns {Object} The new state that is the result of the reduction of the * @returns {Object} The new state that is the result of the reduction of the
* specified action. * specified action.
*/ */
function _setAudioOnly({ dispatch }, next, action) { function _setAudioOnly({ dispatch, getState }, next, action) {
const result = 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, // Set lastN to 0 in case audio-only is desired; leave it as undefined,
// otherwise, and the default lastN value will be chosen automatically. // otherwise, and the default lastN value will be chosen automatically.
dispatch(setLastN(audioOnly ? 0 : undefined)); dispatch(setLastN(audioOnly ? 0 : undefined));
// Mute the local video. // Mute/unmute the local video.
dispatch(setVideoMuted(audioOnly, VIDEO_MUTISM_AUTHORITY.AUDIO_ONLY)); dispatch(setVideoMuted(audioOnly, VIDEO_MUTISM_AUTHORITY.AUDIO_ONLY));
if (typeof APP !== 'undefined') { 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. * 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. * 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. * 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. * in the specified store.
* @private * @private
* @returns {Object} The new state that is the result of the reduction of the * @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 * Synchronizes local tracks from state with local tracks in JitsiConference
* instance. * instance.
* *
* @param {Store} store - Redux store. * @param {Store} store - The redux store.
* @param {Object} action - Action object. * @param {Object} action - Action object.
* @private * @private
* @returns {Promise} * @returns {Promise}
@ -284,13 +284,13 @@ function _syncConferenceLocalTracksWithState(store, action) {
/** /**
* Notifies the feature base/conference that the action TRACK_ADDED * 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. * 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. * 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. * is being dispatched in the specified store.
* @private * @private
* @returns {Object} The new state that is the result of the reduction of the * @returns {Object} The new state that is the result of the reduction of the

View File

@ -72,16 +72,15 @@ function _setRoom({ dispatch, getState }, next, action) {
&& (videoMuted = config.startWithVideoMuted); && (videoMuted = config.startWithVideoMuted);
// Apply startWithAudioMuted and startWithVideoMuted. // Apply startWithAudioMuted and startWithVideoMuted.
const { audio, video } = state['features/base/media'];
audioMuted = Boolean(audioMuted); audioMuted = Boolean(audioMuted);
videoMuted = Boolean(videoMuted); videoMuted = Boolean(videoMuted);
(audio.muted !== audioMuted) && dispatch(setAudioMuted(audioMuted)); // Unconditionally express the desires/expectations/intents of the app and
(video.facingMode !== CAMERA_FACING_MODE.USER) // the user i.e. the state of base/media. Eventually, practice/reality i.e.
&& dispatch(setCameraFacingMode(CAMERA_FACING_MODE.USER)); // the state of base/tracks will or will not agree with the desires.
(Boolean(video.muted) !== videoMuted) dispatch(setAudioMuted(audioMuted));
&& dispatch(setVideoMuted(videoMuted)); dispatch(setCameraFacingMode(CAMERA_FACING_MODE.USER));
dispatch(setVideoMuted(videoMuted));
return next(action); return next(action);
} }

View File

@ -87,19 +87,21 @@ export function destroyLocalTracks() {
*/ */
export function replaceLocalTrack(oldTrack, newTrack, conference) { export function replaceLocalTrack(oldTrack, newTrack, conference) {
return (dispatch, getState) => { return (dispatch, getState) => {
const currentConference = conference conference
|| getState()['features/base/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(() => { .then(() => {
// We call dispose after doing the replace because // We call dispose after doing the replace because dispose will
// dispose will try and do a new o/a after the // try and do a new o/a after the track removes itself. Doing it
// track removes itself. Doing it after means // after means the JitsiLocalTrack.conference is already
// the JitsiLocalTrack::conference member is already // cleared, so it won't try and do the o/a.
// cleared, so it won't try and do the o/a const disposePromise
const disposePromise = oldTrack = oldTrack
? dispatch(_disposeAndRemoveTracks([ oldTrack ])) ? dispatch(_disposeAndRemoveTracks([ oldTrack ]))
: Promise.resolve(); : Promise.resolve();
return disposePromise return disposePromise
.then(() => { .then(() => {
@ -113,10 +115,12 @@ export function replaceLocalTrack(oldTrack, newTrack, conference) {
// track's mute state. If this is not done, the // track's mute state. If this is not done, the
// current mute state of the app will be reflected // current mute state of the app will be reflected
// on the track, not vice-versa. // on the track, not vice-versa.
const muteAction = newTrack.isVideoTrack() const setMuted
? setVideoMuted : setAudioMuted; = newTrack.isVideoTrack()
? setVideoMuted
: setAudioMuted;
return dispatch(muteAction(newTrack.isMuted())); return dispatch(setMuted(newTrack.isMuted()));
} }
}) })
.then(() => { .then(() => {
@ -366,17 +370,26 @@ export function setTrackMuted(track, muted) {
const f = muted ? 'mute' : 'unmute'; 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 => { return track[f]().catch(error => {
console.error(`set track ${f} failed`, 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 const setMuted
= track.mediaType === MEDIA_TYPE.AUDIO = track.mediaType === MEDIA_TYPE.AUDIO
? setAudioMuted ? setAudioMuted
: setVideoMuted; : setVideoMuted;
// FIXME The following disregards VIDEO_MUTISM_AUTHORITY (in the
// case of setVideoMuted, of course).
dispatch(setMuted(!muted)); dispatch(setMuted(!muted));
}); });
}; };

View File

@ -100,35 +100,37 @@ MiddlewareRegistry.register(store => next => action => {
break; break;
case TRACK_UPDATED: case TRACK_UPDATED:
// TODO Remove the below calls to APP.UI once components interested in // TODO Remove the following calls to APP.UI once components interested
// track mute changes are moved into react. // in track mute changes are moved into React and/or redux.
if (typeof APP !== 'undefined') { if (typeof APP !== 'undefined') {
const { jitsiTrack } = action.track; const { jitsiTrack } = action.track;
const isMuted = jitsiTrack.isMuted(); const muted = jitsiTrack.isMuted();
const participantID = jitsiTrack.getParticipantId(); const participantID = jitsiTrack.getParticipantId();
const isVideoTrack = jitsiTrack.isVideoTrack(); const isVideoTrack = jitsiTrack.isVideoTrack();
if (jitsiTrack.isLocal()) { if (jitsiTrack.isLocal()) {
if (isVideoTrack) { if (isVideoTrack) {
APP.conference.videoMuted = isMuted; APP.conference.videoMuted = muted;
} else { } else {
APP.conference.audioMuted = isMuted; APP.conference.audioMuted = muted;
} }
} }
if (isVideoTrack) { if (isVideoTrack) {
APP.UI.setVideoMuted(participantID, isMuted); APP.UI.setVideoMuted(participantID, muted);
APP.UI.onPeerVideoTypeChanged( APP.UI.onPeerVideoTypeChanged(
participantID, jitsiTrack.videoType); participantID,
jitsiTrack.videoType);
} else { } else {
APP.UI.setAudioMuted(participantID, isMuted); APP.UI.setAudioMuted(participantID, muted);
} }
// XXX This function synchronizes track states with media states. // XXX The following synchronizes the state of base/tracks into the
// This is not required in React, because media is the source of // state of base/media. Which is not required in React (and,
// truth, synchronization should always happen in the media -> track // respectively, React Native) because base/media expresses the
// direction. The old web, however, does the opposite, hence the // app's and the user's desires/expectations/intents and base/tracks
// need for this. // expresses practice/reality. Unfortunately, the old Web does not
// comply and/or does the opposite. Hence, the following:
return _trackUpdated(store, next, action); return _trackUpdated(store, next, action);
} }
@ -149,8 +151,8 @@ MiddlewareRegistry.register(store => next => action => {
* @returns {Track} The local <tt>Track</tt> associated with the specified * @returns {Track} The local <tt>Track</tt> associated with the specified
* <tt>mediaType</tt> in the specified <tt>store</tt>. * <tt>mediaType</tt> in the specified <tt>store</tt>.
*/ */
function _getLocalTrack(store, mediaType: MEDIA_TYPE) { function _getLocalTrack({ getState }, mediaType: MEDIA_TYPE) {
return getLocalTrack(store.getState()['features/base/tracks'], mediaType); return getLocalTrack(getState()['features/base/tracks'], mediaType);
} }
/** /**

View File

@ -16,10 +16,10 @@ import { MiddlewareRegistry } from '../../base/redux';
* based on the type of conference. Audio-only conferences don't use the speaker * based on the type of conference. Audio-only conferences don't use the speaker
* by default, and video conferences do. * by default, and video conferences do.
* *
* @param {Store} store - Redux store. * @param {Store} store - The redux store.
* @returns {Function} * @returns {Function}
*/ */
MiddlewareRegistry.register(store => next => action => { MiddlewareRegistry.register(({ getState }) => next => action => {
const AudioMode = NativeModules.AudioMode; const AudioMode = NativeModules.AudioMode;
if (AudioMode) { if (AudioMode) {
@ -32,34 +32,20 @@ MiddlewareRegistry.register(store => next => action => {
mode = AudioMode.DEFAULT; mode = AudioMode.DEFAULT;
break; break;
case CONFERENCE_WILL_JOIN: { case CONFERENCE_WILL_JOIN:
const { audioOnly } = store.getState()['features/base/conference'];
mode = audioOnly ? AudioMode.AUDIO_CALL : AudioMode.VIDEO_CALL;
break;
}
case SET_AUDIO_ONLY: { case SET_AUDIO_ONLY: {
const { conference } = store.getState()['features/base/conference']; if (getState()['features/base/conference'].conference
|| action.conference) {
if (conference) {
mode mode
= action.audioOnly = action.audioOnly
? AudioMode.AUDIO_CALL ? AudioMode.AUDIO_CALL
: AudioMode.VIDEO_CALL; : AudioMode.VIDEO_CALL;
} else {
mode = null;
} }
break; break;
} }
default:
mode = null;
break;
} }
if (mode !== null) { if (typeof mode !== 'undefined') {
AudioMode.setMode(mode) AudioMode.setMode(mode)
.catch(err => .catch(err =>
console.error( console.error(

View File

@ -1,10 +1,7 @@
import { setLastN } from '../../base/conference'; import { setLastN } from '../../base/conference';
import { setVideoMuted, VIDEO_MUTISM_AUTHORITY } from '../../base/media'; import { setVideoMuted, VIDEO_MUTISM_AUTHORITY } from '../../base/media';
import { import { _SET_APP_STATE_LISTENER, APP_STATE_CHANGED } from './actionTypes';
_SET_APP_STATE_LISTENER,
APP_STATE_CHANGED
} from './actionTypes';
/** /**
* Sets the listener to be used with React Native's AppState API. * 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. // for last N will be chosen automatically.
const { audioOnly } = getState()['features/base/conference']; 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)); dispatch(setVideoMuted(muted, VIDEO_MUTISM_AUTHORITY.BACKGROUND));
}; };
} }

View File

@ -22,29 +22,31 @@ import { MiddlewareRegistry } from '../../base/redux';
* In immersive mode the status and navigation bars are hidden and thus the * In immersive mode the status and navigation bars are hidden and thus the
* entire screen will be covered by our application. * entire screen will be covered by our application.
* *
* @param {Store} store - Redux store. * @param {Store} store - The redux store.
* @returns {Function} * @returns {Function}
*/ */
MiddlewareRegistry.register(store => next => action => { MiddlewareRegistry.register(({ getState }) => next => action => {
const result = next(action);
let fullScreen = null; let fullScreen = null;
switch (action.type) { switch (action.type) {
case APP_STATE_CHANGED: { case APP_STATE_CHANGED:
// Check if we just came back from the background and reenable full 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. // screen mode if necessary.
if (action.appState === 'active') { const { appState } = action;
const { audioOnly, conference }
= store.getState()['features/base/conference'];
fullScreen = conference ? !audioOnly : false; if (typeof appState !== 'undefined' && appState !== 'active') {
break;
} }
break;
}
case CONFERENCE_WILL_JOIN: { const { audioOnly, conference }
const { audioOnly } = store.getState()['features/base/conference']; = getState()['features/base/conference'];
fullScreen = !audioOnly; fullScreen = conference || action.conference ? !audioOnly : false;
break; break;
} }
@ -52,21 +54,6 @@ MiddlewareRegistry.register(store => next => action => {
case CONFERENCE_LEFT: case CONFERENCE_LEFT:
fullScreen = false; fullScreen = false;
break; 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) { if (fullScreen !== null) {
@ -75,7 +62,7 @@ MiddlewareRegistry.register(store => next => action => {
console.warn(`Failed to set full screen mode: ${err}`)); console.warn(`Failed to set full screen mode: ${err}`));
} }
return next(action); return result;
}); });
/** /**

View File

@ -14,32 +14,29 @@ import { MiddlewareRegistry } from '../../base/redux';
* the screen and disable touch controls when an object is nearby. The * the screen and disable touch controls when an object is nearby. The
* functionality is enabled when a conference is in audio-only mode. * functionality is enabled when a conference is in audio-only mode.
* *
* @param {Store} store - Redux store. * @param {Store} store - The redux store.
* @returns {Function} * @returns {Function}
*/ */
MiddlewareRegistry.register(store => next => action => { MiddlewareRegistry.register(({ getState }) => next => action => {
const result = next(action);
switch (action.type) { switch (action.type) {
case CONFERENCE_JOINED: {
const { audioOnly } = store.getState()['features/base/conference'];
_setProximityEnabled(audioOnly);
break;
}
case CONFERENCE_FAILED: case CONFERENCE_FAILED:
case CONFERENCE_LEFT: case CONFERENCE_LEFT:
_setProximityEnabled(false); _setProximityEnabled(false);
break; break;
case CONFERENCE_JOINED:
case SET_AUDIO_ONLY: { 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; break;
} }
} }
return next(action); return result;
}); });
/** /**

View File

@ -3,7 +3,12 @@ import { View } from 'react-native';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { toggleAudioOnly } from '../../base/conference'; 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 { Container } from '../../base/react';
import { ColorPalette } from '../../base/styles'; import { ColorPalette } from '../../base/styles';
import { beginRoomLockRequest } from '../../room-lock'; import { beginRoomLockRequest } from '../../room-lock';
@ -56,11 +61,6 @@ class Toolbox extends Component {
*/ */
_onShareRoom: React.PropTypes.func, _onShareRoom: React.PropTypes.func,
/**
* Handler for toggle audio.
*/
_onToggleAudio: React.PropTypes.func,
/** /**
* Toggles the audio-only flag of the conference. * Toggles the audio-only flag of the conference.
*/ */
@ -72,11 +72,6 @@ class Toolbox extends Component {
*/ */
_onToggleCameraFacingMode: React.PropTypes.func, _onToggleCameraFacingMode: React.PropTypes.func,
/**
* Handler for toggling video.
*/
_onToggleVideo: React.PropTypes.func,
/** /**
* Flag showing whether video is muted. * Flag showing whether video is muted.
*/ */
@ -85,9 +80,25 @@ class Toolbox extends Component {
/** /**
* Flag showing whether toolbar is visible. * 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()}. * 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, * Renders the toolbar which contains the primary buttons such as hangup,
* audio and video mute. * audio and video mute.
@ -162,7 +203,7 @@ class Toolbox extends Component {
<ToolbarButton <ToolbarButton
iconName = { audioButtonStyles.iconName } iconName = { audioButtonStyles.iconName }
iconStyle = { audioButtonStyles.iconStyle } iconStyle = { audioButtonStyles.iconStyle }
onClick = { this.props._onToggleAudio } onClick = { this._onToggleAudio }
style = { audioButtonStyles.style } /> style = { audioButtonStyles.style } />
<ToolbarButton <ToolbarButton
iconName = 'hangup' iconName = 'hangup'
@ -174,7 +215,7 @@ class Toolbox extends Component {
disabled = { this.props._audioOnly } disabled = { this.props._audioOnly }
iconName = { videoButtonStyles.iconName } iconName = { videoButtonStyles.iconName }
iconStyle = { videoButtonStyles.iconStyle } iconStyle = { videoButtonStyles.iconStyle }
onClick = { this.props._onToggleVideo } onClick = { this._onToggleVideo }
style = { videoButtonStyles.style } /> style = { videoButtonStyles.style } />
</View> </View>
); );

View File

@ -3,7 +3,6 @@
import type { Dispatch } from 'redux'; import type { Dispatch } from 'redux';
import { appNavigate } from '../app'; import { appNavigate } from '../app';
import { toggleAudioMuted, toggleVideoMuted } from '../base/media';
import { getLocalAudioTrack, getLocalVideoTrack } from '../base/tracks'; import { getLocalAudioTrack, getLocalVideoTrack } from '../base/tracks';
/** /**
@ -19,6 +18,11 @@ import { getLocalAudioTrack, getLocalVideoTrack } from '../base/tracks';
*/ */
export function abstractMapDispatchToProps(dispatch: Dispatch<*>): Object { export function abstractMapDispatchToProps(dispatch: Dispatch<*>): Object {
return { 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. * 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 // expression of (1) the lack of knowledge & (2) the desire to no
// longer have a valid room name to join. // longer have a valid room name to join.
dispatch(appNavigate(undefined)); 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());
} }
}; };
} }