Thumbnail reordering and participant pane enhancements.

* fix(participant-pane) Use the sorted participant list from redux instead of sorting it on every render making it better performant. Match the participant order with that of the order in the filmstrip. Also move the participants with raised hand to the top of the list.

* ref(filmstrip) Move enableThumbnailReordering flag to testing section.

* fix(participants) Add new selectors for getting sorted participants.
This commit is contained in:
Jaya Allamsetty 2021-09-10 13:37:05 -04:00 committed by GitHub
parent 535bd81d61
commit 40a485ec6c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 69 additions and 68 deletions

View File

@ -41,6 +41,10 @@ var config = {
// issues related to insertable streams.
// disableE2EE: false,
// Enables/disables thumbnail reordering in the filmstrip. It is enabled by default unless explicitly
// disabled by the below option.
// enableThumbnailReordering: true,
// P2P test mode disables automatic switching to P2P when there are 2
// participants in the conference.
p2pTestMode: false

View File

@ -273,6 +273,17 @@ export function getRemoteParticipants(stateful: Object | Function) {
return toState(stateful)['features/base/participants'].remote;
}
/**
* Selectors for the getting the remote participants in the order that they are displayed in the filmstrip.
*
@param {(Function|Object)} stateful - The (whole) redux state, or redux's {@code getState} function to be used to
* retrieve the state features/filmstrip.
* @returns {Array<string>}
*/
export function getRemoteParticipantsSorted(stateful: Object | Function) {
return toState(stateful)['features/filmstrip'].remoteParticipants;
}
/**
* Returns the participant which has its pinned state set to truthy.
*
@ -444,67 +455,45 @@ async function _getFirstLoadableAvatarUrl(participant, store) {
return undefined;
}
/**
* Selector for retrieving sorted participants by display name.
* Selector for retrieving ids of participants in the order that they are displayed in the filmstrip (with the
* exception of participants with raised hand). The participants are reordered as follows.
* 1. Local participant.
* 2. Participants with raised hand.
* 3. Participants with screenshare sorted alphabetically by their display name.
* 4. Shared video participants.
* 5. Recent speakers sorted alphabetically by their display name.
* 6. Rest of the participants sorted alphabetically by their display name.
*
* @param {(Function|Object)} stateful - The (whole) redux state, or redux's
* {@code getState} function to be used to retrieve the state
* features/base/participants.
* @returns {Array<Object>}
*/
export function getSortedParticipants(stateful: Object | Function) {
const localParticipant = getLocalParticipant(stateful);
const remoteParticipants = getRemoteParticipants(stateful);
const raisedHandParticipantIds = getRaiseHandsQueue(stateful);
const items = [];
const dominantSpeaker = getDominantSpeakerParticipant(stateful);
const raisedHandParticipants = [];
raisedHandParticipantIds
.map(id => remoteParticipants.get(id) || localParticipant)
.forEach(p => {
if (p !== dominantSpeaker) {
raisedHandParticipants.push(p);
}
});
remoteParticipants.forEach(p => {
if (p !== dominantSpeaker && !raisedHandParticipantIds.find(id => p.id === id)) {
items.push(p);
}
});
if (!raisedHandParticipantIds.find(id => localParticipant.id === id)) {
items.push(localParticipant);
}
items.sort((a, b) =>
getParticipantDisplayName(stateful, a.id).localeCompare(getParticipantDisplayName(stateful, b.id))
);
items.unshift(...raisedHandParticipants);
if (dominantSpeaker && dominantSpeaker !== localParticipant) {
items.unshift(dominantSpeaker);
}
return items;
}
/**
* Selector for retrieving ids of alphabetically sorted participants by name.
*
* @param {(Function|Object)} stateful - The (whole) redux state, or redux's
* {@code getState} function to be used to retrieve the state
* features/base/participants.
* {@code getState} function to be used to retrieve the state features/base/participants.
* @returns {Array<string>}
*/
export function getSortedParticipantIds(stateful: Object | Function): Array<string> {
const participantIds = getSortedParticipants(stateful).map((p): Object => p.id);
const { id } = getLocalParticipant(stateful);
const remoteParticipants = getRemoteParticipantsSorted(stateful);
const reorderedParticipants = new Set(remoteParticipants);
const raisedHandParticipants = getRaiseHandsQueue(stateful);
const remoteRaisedHandParticipants = new Set(raisedHandParticipants || []);
return participantIds;
for (const participant of remoteRaisedHandParticipants.keys()) {
// Avoid duplicates.
if (reorderedParticipants.has(participant)) {
reorderedParticipants.delete(participant);
} else {
remoteRaisedHandParticipants.delete(participant);
}
}
// Remove self.
remoteRaisedHandParticipants.has(id) && remoteRaisedHandParticipants.delete(id);
// Move self and participants with raised hand to the top of the list.
return [
id,
...Array.from(remoteRaisedHandParticipants.keys()),
...Array.from(reorderedParticipants.keys())
];
}
/**

View File

@ -61,11 +61,11 @@ const DEFAULT_STATE = {
haveParticipantWithScreenSharingFeature: false,
local: undefined,
pinnedParticipant: undefined,
raisedHandsQueue: [],
remote: new Map(),
sortedRemoteParticipants: new Map(),
sortedRemoteScreenshares: new Map(),
speakersList: new Map(),
raisedHandsQueue: []
speakersList: new Map()
};
/**

View File

@ -240,10 +240,10 @@ class Filmstrip extends PureComponent <Props> {
let stop = stopIndex;
if (_thumbnailsReordered) {
// In tile view, the start index needs to be offset by 1 because the first thumbnail is that of the local
// In tile view, the indices needs to be offset by 1 because the first thumbnail is that of the local
// endpoint. The remote participants start from index 1.
if (_currentLayout === LAYOUTS.TILE_VIEW) {
start = startIndex > 0 ? startIndex - 1 : 0;
start = Math.max(startIndex - 1, 0);
stop = stopIndex - 1;
}
}
@ -528,7 +528,8 @@ class Filmstrip extends PureComponent <Props> {
*/
function _mapStateToProps(state) {
const toolbarButtons = getToolbarButtons(state);
const { enableThumbnailReordering = true } = state['features/base/config'];
const { testing = {} } = state['features/base/config'];
const enableThumbnailReordering = testing.enableThumbnailReordering ?? true;
const { visible, remoteParticipants } = state['features/filmstrip'];
const reduceHeight = state['features/toolbox'].visible && toolbarButtons.length;
const remoteVideosVisible = shouldRemoteVideosBeVisible(state);

View File

@ -104,7 +104,8 @@ function _mapStateToProps(state, ownProps) {
const _currentLayout = getCurrentLayout(state);
const { remoteParticipants } = state['features/filmstrip'];
const remoteParticipantsLength = remoteParticipants.length;
const { enableThumbnailReordering = true } = state['features/base/config'];
const { testing = {} } = state['features/base/config'];
const enableThumbnailReordering = testing.enableThumbnailReordering ?? true;
if (_currentLayout === LAYOUTS.TILE_VIEW) {
const { columnIndex, rowIndex } = ownProps;

View File

@ -12,7 +12,8 @@ import { setRemoteParticipants } from './actions';
*/
export function updateRemoteParticipants(store: Object, participantId: ?number) {
const state = store.getState();
const { enableThumbnailReordering = true } = state['features/base/config'];
const { testing = {} } = state['features/base/config'];
const enableThumbnailReordering = testing.enableThumbnailReordering ?? true;
let reorderedParticipants = [];
if (!enableThumbnailReordering) {

View File

@ -22,6 +22,15 @@ import './subscriber';
* The middleware of the feature Filmstrip.
*/
MiddlewareRegistry.register(store => next => action => {
if (action.type === PARTICIPANT_LEFT) {
// This has to be executed before we remove the participant from features/base/participants state in order to
// remove the related thumbnail component before we need to re-render it. If we do this after next()
// we will be in sitation where the participant exists in the remoteParticipants array in features/filmstrip
// but doesn't exist in features/base/participants state which will lead to rendering a thumbnail for
// non-existing participant.
updateRemoteParticipantsOnLeave(store, action.participant?.id);
}
const result = next(action);
switch (action.type) {
@ -50,10 +59,6 @@ MiddlewareRegistry.register(store => next => action => {
updateRemoteParticipants(store, action.participant?.id);
break;
}
case PARTICIPANT_LEFT: {
updateRemoteParticipantsOnLeave(store, action.participant?.id);
break;
}
case SETTINGS_UPDATED: {
if (typeof action.settings?.localFlipX === 'boolean') {
// TODO: This needs to be removed once the large video is Reactified.

View File

@ -396,7 +396,7 @@ class MeetingParticipantContextMenu extends Component<Props, State> {
}
const actions
= _participant.isFakeParticipant ? (
= _participant?.isFakeParticipant ? (
<>
{_localVideoOwner && (
<ContextMenuItem onClick = { this._onStopSharedVideo }>

View File

@ -170,7 +170,7 @@ function MeetingParticipantItem({
videoMediaState = { _videoMediaState }
youText = { youText }>
{!overflowDrawer && !_participant.isFakeParticipant
{!overflowDrawer && !_participant?.isFakeParticipant
&& <>
<ParticipantQuickAction
askUnmuteText = { askUnmuteText }
@ -184,7 +184,7 @@ function MeetingParticipantItem({
</>
}
{!overflowDrawer && _localVideoOwner && _participant.isFakeParticipant && (
{!overflowDrawer && _localVideoOwner && _participant?.isFakeParticipant && (
<ParticipantActionEllipsis
aria-label = { participantActionEllipsisLabel }
onClick = { onContextMenu } />