Reduce the number of unnecessary Redux state changes

This commit is contained in:
Lyubomir Marinov 2016-11-04 13:28:47 -05:00
parent 3fa62c3757
commit 4d335e086b
5 changed files with 90 additions and 66 deletions

View File

@ -46,7 +46,7 @@ ReducerRegistry.register('features/base/connection',
return { return {
...state, ...state,
connectionOptions: { connectionOptions: {
...(state.connectionOptions || {}), ...state.connectionOptions,
...buildConnectionOptions(action.domain) ...buildConnectionOptions(action.domain)
} }
}; };

View File

@ -25,8 +25,9 @@ import {
* @property {boolean} local - If true, participant is local. * @property {boolean} local - If true, participant is local.
* @property {boolean} pinned - If true, participant is currently a * @property {boolean} pinned - If true, participant is currently a
* "PINNED_ENDPOINT". * "PINNED_ENDPOINT".
* @property {boolean} speaking - If true, participant is currently a dominant * @property {boolean} dominantSpeaker - If this participant is the dominant
* speaker. * speaker in the (associated) conference, <tt>true</tt>; otherwise,
* <tt>false</tt>.
* @property {string} email - Participant email. * @property {string} email - Participant email.
*/ */
@ -36,7 +37,7 @@ import {
* @type {string[]} * @type {string[]}
*/ */
const PARTICIPANT_PROPS_TO_OMIT_WHEN_UPDATE const PARTICIPANT_PROPS_TO_OMIT_WHEN_UPDATE
= [ 'id', 'local', 'pinned', 'speaking' ]; = [ 'dominantSpeaker', 'id', 'local', 'pinned' ];
/** /**
* Reducer function for a single participant. * Reducer function for a single participant.
@ -53,10 +54,11 @@ function participant(state, action) {
switch (action.type) { switch (action.type) {
case DOMINANT_SPEAKER_CHANGED: case DOMINANT_SPEAKER_CHANGED:
// Only one dominant speaker is allowed. // Only one dominant speaker is allowed.
return { return (
...state, _setStateProperty(
speaking: state.id === action.participant.id state,
}; 'dominantSpeaker',
state.id === action.participant.id));
case PARTICIPANT_ID_CHANGED: case PARTICIPANT_ID_CHANGED:
if (state.id === action.oldValue) { if (state.id === action.oldValue) {
@ -68,8 +70,7 @@ function participant(state, action) {
avatar: state.avatar || _getAvatarURL(id, state.email) avatar: state.avatar || _getAvatarURL(id, state.email)
}; };
} }
break;
return state;
case PARTICIPANT_JOINED: { case PARTICIPANT_JOINED: {
const participant = action.participant; // eslint-disable-line no-shadow const participant = action.participant; // eslint-disable-line no-shadow
@ -94,7 +95,7 @@ function participant(state, action) {
name, name,
pinned: participant.pinned || false, pinned: participant.pinned || false,
role: participant.role || PARTICIPANT_ROLE.NONE, role: participant.role || PARTICIPANT_ROLE.NONE,
speaking: participant.speaking || false dominantSpeaker: participant.dominantSpeaker || false
}; };
} }
@ -117,19 +118,18 @@ function participant(state, action) {
return newState; return newState;
} }
break;
return state;
case PIN_PARTICIPANT: case PIN_PARTICIPANT:
// Currently, only one pinned participant is allowed. // Currently, only one pinned participant is allowed.
return { return (
...state, _setStateProperty(
pinned: state.id === action.participant.id state,
}; 'pinned',
state.id === action.participant.id));
default:
return state;
} }
return state;
} }
/** /**
@ -201,3 +201,30 @@ function _getAvatarURL(participantId, email) {
return urlPref + avatarId + urlSuf; return urlPref + avatarId + urlSuf;
} }
/**
* Sets a specific property of a specific state to a specific value. Prevents
* unnecessary state changes (when the specified <tt>value</tt> is equal to the
* value of the specified <tt>property</tt> of the specified <tt>state</tt>).
*
* @param {Object} state - The (Redux) state from which a new state is to be
* constructed by setting the specified <tt>property</tt> to the specified
* <tt>value</tt>.
* @param {string} property - The property of <tt>state</tt> which is to be
* assigned the specified <tt>value</tt> (in the new state).
* @param {*} value - The value to assign to the specified <tt>property</tt>.
* @returns {Object} The specified <tt>state</tt> if the value of the specified
* <tt>property</tt> equals the specified <tt>value/tt>; otherwise, a new state
* constructed from the specified <tt>state</tt> by setting the specified
* <tt>property</tt> to the specified <tt>value</tt>.
*/
function _setStateProperty(state, property, value) {
if (state[property] !== value) {
return {
...state,
[property]: value
};
}
return state;
}

View File

@ -81,8 +81,9 @@ class Thumbnail extends Component {
// participants would be hearing themselves. // participants would be hearing themselves.
const audioMuted = !audioTrack || audioTrack.muted; const audioMuted = !audioTrack || audioTrack.muted;
const renderAudio = !audioMuted && !audioTrack.local; const renderAudio = !audioMuted && !audioTrack.local;
const participantId = participant.id;
const participantNotInLargeVideo const participantNotInLargeVideo
= participant.id !== largeVideo.participantId; = participantId !== largeVideo.participantId;
const videoMuted = !videoTrack || videoTrack.muted; const videoMuted = !videoTrack || videoTrack.muted;
return ( return (
@ -96,7 +97,7 @@ class Thumbnail extends Component {
= { audioTrack.jitsiTrack.getOriginalStream() } /> } = { audioTrack.jitsiTrack.getOriginalStream() } /> }
<ParticipantView <ParticipantView
participantId = { participant.id } participantId = { participantId }
showAvatar = { participantNotInLargeVideo } showAvatar = { participantNotInLargeVideo }
showVideo = { participantNotInLargeVideo } showVideo = { participantNotInLargeVideo }
zOrder = { 1 } /> zOrder = { 1 } />
@ -104,7 +105,7 @@ class Thumbnail extends Component {
{ participant.role === PARTICIPANT_ROLE.MODERATOR { participant.role === PARTICIPANT_ROLE.MODERATOR
&& <ModeratorIndicator /> } && <ModeratorIndicator /> }
{ participant.speaking { participant.dominantSpeaker
&& <DominantSpeakerIndicator /> } && <DominantSpeakerIndicator /> }
{ audioMuted { audioMuted

View File

@ -26,13 +26,17 @@ export function selectParticipant() {
const largeVideo = state['features/largeVideo']; const largeVideo = state['features/largeVideo'];
const tracks = state['features/base/tracks']; const tracks = state['features/base/tracks'];
const videoTrack = getTrackByMediaTypeAndParticipant( const id = largeVideo.participantId;
tracks, MEDIA_TYPE.VIDEO, largeVideo.participantId); const videoTrack
= getTrackByMediaTypeAndParticipant(
tracks,
MEDIA_TYPE.VIDEO,
id);
try { try {
conference.selectParticipant( conference.selectParticipant(
videoTrack && videoTrack.videoType === VIDEO_TYPE.CAMERA videoTrack && videoTrack.videoType === VIDEO_TYPE.CAMERA
? largeVideo.participantId ? id
: null); : null);
} catch (err) { } catch (err) {
_handleParticipantError(err); _handleParticipantError(err);
@ -51,11 +55,8 @@ export function selectParticipant() {
export function selectParticipantInLargeVideo() { export function selectParticipantInLargeVideo() {
return (dispatch, getState) => { return (dispatch, getState) => {
const state = getState(); const state = getState();
const participants = state['features/base/participants']; const participantId = _electParticipantInLargeVideo(state);
const tracks = state['features/base/tracks'];
const largeVideo = state['features/largeVideo']; const largeVideo = state['features/largeVideo'];
const participantId
= _electParticipantInLargeVideo(participants, tracks);
if (participantId !== largeVideo.participantId) { if (participantId !== largeVideo.participantId) {
dispatch({ dispatch({
@ -77,57 +78,54 @@ export function selectParticipantInLargeVideo() {
* @returns {(Track|undefined)} * @returns {(Track|undefined)}
*/ */
function _electLastVisibleVideo(tracks) { function _electLastVisibleVideo(tracks) {
let videoTrack;
// First we try to get most recent remote video track. // First we try to get most recent remote video track.
for (let i = tracks.length - 1; i >= 0; i--) { for (let i = tracks.length - 1; i >= 0; --i) {
if (tracks[i].mediaType === MEDIA_TYPE.VIDEO && !tracks[i].local) { const track = tracks[i];
videoTrack = tracks[i];
break; if (!track.local && track.mediaType === MEDIA_TYPE.VIDEO) {
return track;
} }
} }
// And if no remote video tracks are available, we select the local one. // And if no remote video tracks are available, we select the local one.
if (!videoTrack) { return getLocalVideoTrack(tracks);
videoTrack = getLocalVideoTrack(tracks);
}
return videoTrack;
} }
/** /**
* Returns the participant ID who is to be on the stage i.e. should be displayed * Returns the identifier of the participant who is to be on the stage i.e.
* in LargeVideo. * should be displayed in <tt>LargeVideo</tt>.
* *
* @param {Participant[]} participants - All participants. * @param {Object} state - The Redux state from which the participant to be
* @param {Track[]} tracks - All tracks. * displayed in <tt>LargeVideo</tt> is to be elected.
* @private * @private
* @returns {(string|undefined)} * @returns {(string|undefined)}
*/ */
function _electParticipantInLargeVideo(participants, tracks) { function _electParticipantInLargeVideo(state) {
// First get the pinned participant. If local participant is pinned, he will // First get the pinned participant. If the local participant is pinned,
// be shown in LargeVideo. // he/she will be shown in LargeVideo.
const participants = state['features/base/participants'];
let participant = participants.find(p => p.pinned); let participant = participants.find(p => p.pinned);
let id = participant ? participant.id : undefined; let id = participant ? participant.id : undefined;
// If no participant is pinned, get the dominant speaker. But local
// participant won't be displayed in LargeVideo even if he is the dominant
// speaker.
if (!id) { if (!id) {
participant = participants.find(p => p.speaking && !p.local); // No participant is pinned so get the dominant speaker. But the local
// participant won't be displayed in LargeVideo even if he/she is the
// dominant speaker.
participant = participants.find(p => p.dominantSpeaker && !p.local);
if (participant) { if (participant) {
id = participant.id; id = participant.id;
} }
}
// If no participant is pinned and no dominant speaker, just get the
// participant with last visible video track. This may turn out to be local
// participant.
if (!id) { if (!id) {
// There is no dominant speaker so get the participant with the last
// visible video track. This may turn out to be the local
// participant.
const tracks = state['features/base/tracks'];
const videoTrack = _electLastVisibleVideo(tracks); const videoTrack = _electLastVisibleVideo(tracks);
id = videoTrack && videoTrack.participantId; id = videoTrack && videoTrack.participantId;
} }
}
return id; return id;
} }

View File

@ -24,16 +24,14 @@ ReducerRegistry.register(
participantId: action.newValue participantId: action.newValue
}; };
} }
break;
return state;
case LARGE_VIDEO_PARTICIPANT_CHANGED: case LARGE_VIDEO_PARTICIPANT_CHANGED:
return { return {
...state, ...state,
participantId: action.participantId participantId: action.participantId
}; };
default:
return state;
} }
return state;
}); });