From 4997ae79e366681a75289ce36f3cacb99ce22762 Mon Sep 17 00:00:00 2001 From: Lyubomir Marinov Date: Tue, 25 Oct 2016 11:43:15 -0500 Subject: [PATCH] Fix toolbar's mute buttons when starting muted The toolbar's mute buttons depict respective features/base/media states. However, (un)muting is practically carried out by features/base/tracks. When the mobile app enters a conference configured to invite the joining participant to mute themselves, the tracks would be muted but the toolbar's mute buttons would not reflect that. --- react/features/base/lib-jitsi-meet/actions.js | 2 +- .../base/lib-jitsi-meet/middleware.js | 15 ++-- react/features/base/lib-jitsi-meet/reducer.js | 4 +- react/features/base/media/middleware.js | 13 +-- react/features/base/tracks/functions.js | 12 ++- react/features/base/tracks/middleware.js | 87 ++++++++++++++++++- react/features/largeVideo/middleware.js | 5 +- 7 files changed, 107 insertions(+), 31 deletions(-) diff --git a/react/features/base/lib-jitsi-meet/actions.js b/react/features/base/lib-jitsi-meet/actions.js index 734bb55f7..aa97ae36d 100644 --- a/react/features/base/lib-jitsi-meet/actions.js +++ b/react/features/base/lib-jitsi-meet/actions.js @@ -34,7 +34,7 @@ export function disposeLib() { */ export function initLib() { return (dispatch, getState) => { - const config = getState()['features/base/lib'].config; + const config = getState()['features/base/lib-jitsi-meet'].config; if (!config) { throw new Error('Cannot initialize lib-jitsi-meet without config'); diff --git a/react/features/base/lib-jitsi-meet/middleware.js b/react/features/base/lib-jitsi-meet/middleware.js index cd8674d94..05ec87da6 100644 --- a/react/features/base/lib-jitsi-meet/middleware.js +++ b/react/features/base/lib-jitsi-meet/middleware.js @@ -19,26 +19,23 @@ import { SET_CONFIG } from './actionTypes'; MiddlewareRegistry.register(store => next => action => { switch (action.type) { case PARTICIPANT_LEFT: - if (action.participant.local) { - store.dispatch(disposeLib()); - } + action.participant.local && store.dispatch(disposeLib()); break; case SET_CONFIG: { const { dispatch, getState } = store; - const libInitialized = getState()['features/base/lib'].initialized; + const initialized + = getState()['features/base/lib-jitsi-meet'].initialized; // XXX If we already have config, that means new config is coming, which // means that we should dispose instance of lib initialized with // previous config first. // TODO Currently 'disposeLib' actually does not dispose lib-jitsi-meet. // This functionality should be implemented. - const promise = libInitialized - ? dispatch(disposeLib()) - : Promise.resolve(); + const promise + = initialized ? dispatch(disposeLib()) : Promise.resolve(); - promise - .then(dispatch(initLib())); + promise.then(dispatch(initLib())); break; } diff --git a/react/features/base/lib-jitsi-meet/reducer.js b/react/features/base/lib-jitsi-meet/reducer.js index 995d6f65d..0a7304561 100644 --- a/react/features/base/lib-jitsi-meet/reducer.js +++ b/react/features/base/lib-jitsi-meet/reducer.js @@ -8,7 +8,7 @@ import { } from './actionTypes'; /** - * Initial state of 'features/base/lib'. + * Initial state of 'features/base/lib-jitsi-meet'. * * @type {{ * initializationError: null, @@ -31,7 +31,7 @@ const INITIAL_STATE = { }; ReducerRegistry.register( - 'features/base/lib', + 'features/base/lib-jitsi-meet', (state = INITIAL_STATE, action) => { switch (action.type) { case LIB_DISPOSED: diff --git a/react/features/base/media/middleware.js b/react/features/base/media/middleware.js index 51dec59fb..6b88d4f52 100644 --- a/react/features/base/media/middleware.js +++ b/react/features/base/media/middleware.js @@ -42,15 +42,10 @@ function resetInitialMediaState(store) { const { dispatch, getState } = store; const state = getState()['features/base/media']; - if (state.audio.muted) { - dispatch(audioMutedChanged(false)); - } - if (state.video.facingMode !== CAMERA_FACING_MODE.USER) { - dispatch(cameraFacingModeChanged(CAMERA_FACING_MODE.USER)); - } - if (state.video.muted) { - dispatch(videoMutedChanged(false)); - } + state.audio.muted && dispatch(audioMutedChanged(false)); + (state.video.facingMode !== CAMERA_FACING_MODE.USER) + && dispatch(cameraFacingModeChanged(CAMERA_FACING_MODE.USER)); + state.video.muted && dispatch(videoMutedChanged(false)); } /** diff --git a/react/features/base/tracks/functions.js b/react/features/base/tracks/functions.js index 29fdb39be..f92a5aade 100644 --- a/react/features/base/tracks/functions.js +++ b/react/features/base/tracks/functions.js @@ -72,14 +72,18 @@ export function getTracksByMediaType(tracks, mediaType) { } /** - * Mute or unmute local track if any. + * Mutes or unmutes a specific JitsiLocalTrack. If the muted state of + * the specified track is already in accord with the specified + * muted value, then does nothing. * - * @param {JitsiLocalTrack} track - Track instance. - * @param {boolean} muted - If audio stream should be muted or unmuted. + * @param {JitsiLocalTrack} track - The JitsiLocalTrack to mute or + * unmute. + * @param {boolean} muted - If the specified track is to be muted, then + * true; otherwise, false. * @returns {Promise} */ export function setTrackMuted(track, muted) { - if (!track) { + if (track.isMuted() === muted) { return Promise.resolve(); } diff --git a/react/features/base/tracks/middleware.js b/react/features/base/tracks/middleware.js index 90c5ecea3..75a871c37 100644 --- a/react/features/base/tracks/middleware.js +++ b/react/features/base/tracks/middleware.js @@ -4,9 +4,11 @@ import { } from '../lib-jitsi-meet'; import { AUDIO_MUTED_CHANGED, + audioMutedChanged, CAMERA_FACING_MODE_CHANGED, MEDIA_TYPE, - VIDEO_MUTED_CHANGED + VIDEO_MUTED_CHANGED, + videoMutedChanged } from '../media'; import { MiddlewareRegistry } from '../redux'; @@ -14,6 +16,7 @@ import { createLocalTracks, destroyLocalTracks } from './actions'; +import { TRACK_UPDATED } from './actionTypes'; import { getLocalTrack, setTrackMuted @@ -50,6 +53,9 @@ MiddlewareRegistry.register(store => next => action => { store.dispatch(destroyLocalTracks()); break; + case TRACK_UPDATED: + return _trackUpdated(store, next, action); + case VIDEO_MUTED_CHANGED: _mutedChanged(store, action, MEDIA_TYPE.VIDEO); break; @@ -58,6 +64,22 @@ MiddlewareRegistry.register(store => next => action => { return next(action); }); +/** + * Gets the local track associated with a specific MEDIA_TYPE in a + * specific Redux store. + * + * @param {Store} store - The Redux store from which the local track associated + * with the specified mediaType is to be retrieved. + * @param {MEDIA_TYPE} mediaType - The MEDIA_TYPE of the local track to + * be retrieved from the specified store. + * @private + * @returns {Track} The local Track associated with the specified + * mediaType in the specified store. + */ +function _getLocalTrack(store, mediaType) { + return getLocalTrack(store.getState()['features/base/tracks'], mediaType); +} + /** * Mutes or unmutes a local track with a specific media type. * @@ -70,8 +92,67 @@ MiddlewareRegistry.register(store => next => action => { * @returns {void} */ function _mutedChanged(store, action, mediaType) { - const tracks = store.getState()['features/base/tracks']; - const localTrack = getLocalTrack(tracks, mediaType); + const localTrack = _getLocalTrack(store, mediaType); localTrack && setTrackMuted(localTrack.jitsiTrack, action.muted); } + +/** + * Intercepts the action TRACK_UPDATED in order to synchronize the + * muted states of the local tracks of features/base/tracks with the muted + * states of features/base/media. + * + * @param {Store} store - The Redux store in which the specified action + * is being dispatched. + * @param {Dispatch} next - The Redux dispatch function to dispatch the + * specified action to the specified store. + * @param {Action} action - The Redux action TRACK_UPDATED which is + * being dispatched in the specified store. + * @private + * @returns {void} + */ +function _trackUpdated(store, next, action) { + // Determine the muted state of the local track before the update. + const track = action.track; + let mediaType; + let oldMuted; + + if ('muted' in track) { + // XXX The return value of JitsiTrack.getType() is of type MEDIA_TYPE + // that happens to be compatible with the type MEDIA_TYPE defined by + // jitsi-meet-react. + mediaType = track.jitsiTrack.getType(); + + const localTrack = _getLocalTrack(store, mediaType); + + if (localTrack) { + oldMuted = localTrack.muted; + } + } + + const result = next(action); + + if (typeof oldMuted !== 'undefined') { + // Determine the muted state of the local track after the update. If the + // muted states before and after the update differ, then the respective + // media state should by synchronized. + const localTrack = _getLocalTrack(store, mediaType); + + if (localTrack) { + const newMuted = localTrack.muted; + + if (oldMuted !== newMuted) { + switch (mediaType) { + case MEDIA_TYPE.AUDIO: + store.dispatch(audioMutedChanged(newMuted)); + break; + case MEDIA_TYPE.VIDEO: + store.dispatch(videoMutedChanged(newMuted)); + break; + } + } + } + } + + return result; +} diff --git a/react/features/largeVideo/middleware.js b/react/features/largeVideo/middleware.js index 2c91237fd..374428096 100644 --- a/react/features/largeVideo/middleware.js +++ b/react/features/largeVideo/middleware.js @@ -49,9 +49,8 @@ MiddlewareRegistry.register(store => next => action => { action.track.jitsiTrack); const participantId = state['features/largeVideo'].participantId; - if (track.participantId === participantId) { - store.dispatch(selectParticipant()); - } + (track.participantId === participantId) + && store.dispatch(selectParticipant()); } break; }