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 {
...state,
connectionOptions: {
...(state.connectionOptions || {}),
...state.connectionOptions,
...buildConnectionOptions(action.domain)
}
};

View File

@ -25,8 +25,9 @@ import {
* @property {boolean} local - If true, participant is local.
* @property {boolean} pinned - If true, participant is currently a
* "PINNED_ENDPOINT".
* @property {boolean} speaking - If true, participant is currently a dominant
* speaker.
* @property {boolean} dominantSpeaker - If this participant is the dominant
* speaker in the (associated) conference, <tt>true</tt>; otherwise,
* <tt>false</tt>.
* @property {string} email - Participant email.
*/
@ -36,7 +37,7 @@ import {
* @type {string[]}
*/
const PARTICIPANT_PROPS_TO_OMIT_WHEN_UPDATE
= [ 'id', 'local', 'pinned', 'speaking' ];
= [ 'dominantSpeaker', 'id', 'local', 'pinned' ];
/**
* Reducer function for a single participant.
@ -53,10 +54,11 @@ function participant(state, action) {
switch (action.type) {
case DOMINANT_SPEAKER_CHANGED:
// Only one dominant speaker is allowed.
return {
...state,
speaking: state.id === action.participant.id
};
return (
_setStateProperty(
state,
'dominantSpeaker',
state.id === action.participant.id));
case PARTICIPANT_ID_CHANGED:
if (state.id === action.oldValue) {
@ -68,8 +70,7 @@ function participant(state, action) {
avatar: state.avatar || _getAvatarURL(id, state.email)
};
}
return state;
break;
case PARTICIPANT_JOINED: {
const participant = action.participant; // eslint-disable-line no-shadow
@ -94,7 +95,7 @@ function participant(state, action) {
name,
pinned: participant.pinned || false,
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 state;
break;
case PIN_PARTICIPANT:
// Currently, only one pinned participant is allowed.
return {
...state,
pinned: state.id === action.participant.id
};
default:
return state;
return (
_setStateProperty(
state,
'pinned',
state.id === action.participant.id));
}
return state;
}
/**
@ -201,3 +201,30 @@ function _getAvatarURL(participantId, email) {
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.
const audioMuted = !audioTrack || audioTrack.muted;
const renderAudio = !audioMuted && !audioTrack.local;
const participantId = participant.id;
const participantNotInLargeVideo
= participant.id !== largeVideo.participantId;
= participantId !== largeVideo.participantId;
const videoMuted = !videoTrack || videoTrack.muted;
return (
@ -96,7 +97,7 @@ class Thumbnail extends Component {
= { audioTrack.jitsiTrack.getOriginalStream() } /> }
<ParticipantView
participantId = { participant.id }
participantId = { participantId }
showAvatar = { participantNotInLargeVideo }
showVideo = { participantNotInLargeVideo }
zOrder = { 1 } />
@ -104,7 +105,7 @@ class Thumbnail extends Component {
{ participant.role === PARTICIPANT_ROLE.MODERATOR
&& <ModeratorIndicator /> }
{ participant.speaking
{ participant.dominantSpeaker
&& <DominantSpeakerIndicator /> }
{ audioMuted

View File

@ -26,14 +26,18 @@ export function selectParticipant() {
const largeVideo = state['features/largeVideo'];
const tracks = state['features/base/tracks'];
const videoTrack = getTrackByMediaTypeAndParticipant(
tracks, MEDIA_TYPE.VIDEO, largeVideo.participantId);
const id = largeVideo.participantId;
const videoTrack
= getTrackByMediaTypeAndParticipant(
tracks,
MEDIA_TYPE.VIDEO,
id);
try {
conference.selectParticipant(
videoTrack && videoTrack.videoType === VIDEO_TYPE.CAMERA
? largeVideo.participantId
: null);
videoTrack && videoTrack.videoType === VIDEO_TYPE.CAMERA
? id
: null);
} catch (err) {
_handleParticipantError(err);
}
@ -51,11 +55,8 @@ export function selectParticipant() {
export function selectParticipantInLargeVideo() {
return (dispatch, getState) => {
const state = getState();
const participants = state['features/base/participants'];
const tracks = state['features/base/tracks'];
const participantId = _electParticipantInLargeVideo(state);
const largeVideo = state['features/largeVideo'];
const participantId
= _electParticipantInLargeVideo(participants, tracks);
if (participantId !== largeVideo.participantId) {
dispatch({
@ -77,56 +78,53 @@ export function selectParticipantInLargeVideo() {
* @returns {(Track|undefined)}
*/
function _electLastVisibleVideo(tracks) {
let videoTrack;
// First we try to get most recent remote video track.
for (let i = tracks.length - 1; i >= 0; i--) {
if (tracks[i].mediaType === MEDIA_TYPE.VIDEO && !tracks[i].local) {
videoTrack = tracks[i];
break;
for (let i = tracks.length - 1; i >= 0; --i) {
const track = tracks[i];
if (!track.local && track.mediaType === MEDIA_TYPE.VIDEO) {
return track;
}
}
// And if no remote video tracks are available, we select the local one.
if (!videoTrack) {
videoTrack = getLocalVideoTrack(tracks);
}
return videoTrack;
return getLocalVideoTrack(tracks);
}
/**
* Returns the participant ID who is to be on the stage i.e. should be displayed
* in LargeVideo.
* Returns the identifier of the participant who is to be on the stage i.e.
* should be displayed in <tt>LargeVideo</tt>.
*
* @param {Participant[]} participants - All participants.
* @param {Track[]} tracks - All tracks.
* @param {Object} state - The Redux state from which the participant to be
* displayed in <tt>LargeVideo</tt> is to be elected.
* @private
* @returns {(string|undefined)}
*/
function _electParticipantInLargeVideo(participants, tracks) {
// First get the pinned participant. If local participant is pinned, he will
// be shown in LargeVideo.
function _electParticipantInLargeVideo(state) {
// First get the pinned participant. If the local participant is pinned,
// he/she will be shown in LargeVideo.
const participants = state['features/base/participants'];
let participant = participants.find(p => p.pinned);
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) {
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) {
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) {
const videoTrack = _electLastVisibleVideo(tracks);
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);
id = videoTrack && videoTrack.participantId;
id = videoTrack && videoTrack.participantId;
}
}
return id;

View File

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