Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Additional setting to order participants in speaker stats #9751

7 changes: 7 additions & 0 deletions config.js
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,13 @@ var config = {
// Specifies whether there will be a search field in speaker stats or not
// disableSpeakerStatsSearch: false,

// Specifies whether participants in speaker stats should be ordered or not, and with what priority
// speakerStatsOrder: [
// 'role', <- Moderators on top
// 'name', <- Alphabetically by name
// 'hasLeft', <- The ones that have left in the bottom
// ] <- the order of the array elements determines priority

// How many participants while in the tile view mode, before the receiving video quality is reduced from HD to SD.
// Use -1 to disable.
// maxFullResolutionParticipants: 2,
Expand Down
1 change: 1 addition & 0 deletions react/features/base/config/configWhitelist.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ export default [
'disableShortcuts',
'disableShowMoreStats',
'disableSpeakerStatsSearch',
'speakerStatsOrder',
'disableSimulcast',
'disableThirdPartyRequests',
'disableTileView',
Expand Down
16 changes: 16 additions & 0 deletions react/features/base/util/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -185,3 +185,19 @@ function parseShorthandColor(color) {

return [ r, g, b ];
}

/**
* Sorts an object by a sort function, same functionality as array.sort().
*
* @param {Object} object - The data object.
* @param {Function} callback - The sort function.
* @returns {void}
*/
export function objectSort(object: Object, callback: Function) {
return Object.entries(object)
.sort(([ , a ], [ , b ]) => callback(a, b))
.reduce((row, [ key, value ]) => {
return { ...row,
[key]: value };
}, {});
}
85 changes: 81 additions & 4 deletions react/features/speaker-stats/components/SpeakerStats.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,16 @@ import React, { Component } from 'react';

import { Dialog } from '../../base/dialog';
import { translate } from '../../base/i18n';
import { getLocalParticipant } from '../../base/participants';
import { getLocalParticipant, getParticipantById, PARTICIPANT_ROLE } from '../../base/participants';
import { connect } from '../../base/redux';
import { escapeRegexp } from '../../base/util';
import { escapeRegexp, objectSort } from '../../base/util';
import { getSpeakerStatsOrder } from '../functions';

import SpeakerStatsItem from './SpeakerStatsItem';
import SpeakerStatsLabels from './SpeakerStatsLabels';
import SpeakerStatsSearch from './SpeakerStatsSearch';

declare var APP: Object;
declare var interfaceConfig: Object;

/**
Expand All @@ -24,6 +26,11 @@ type Props = {
*/
_localDisplayName: string,

/**
* The configuration setting to order paricipants.
*/
_speakerStatsOrder: Array<String>,

/**
* The JitsiConference from which stats will be pulled.
*/
Expand Down Expand Up @@ -144,9 +151,77 @@ class SpeakerStats extends Component<Props, State> {
}
}

if (this.props._speakerStatsOrder.length) {
return this._getSortedSpeakerStats(stats);
}

return stats;
}

/**
* Get sorted speaker stats based on a configuration setting.
*
* @param {Object} stats - Unordered speaker stats.
* @returns {Object} - Ordered speaker stats.
* @private
*/
_getSortedSpeakerStats(stats) {
for (const id in stats) {
if (stats[id].hasOwnProperty('_hasLeft') && !stats[id].hasLeft()) {
if (this.props._speakerStatsOrder.includes('name')) {
const { _localDisplayName } = this.props;

if (stats[id].isLocalStats()) {
stats[id].setDisplayName(_localDisplayName);
}
}

if (this.props._speakerStatsOrder.includes('role')) {
const participant = getParticipantById(APP.store.getState(), stats[id].getUserId());

stats[id].isModerator = participant.role === PARTICIPANT_ROLE.MODERATOR;
}
}
}

return objectSort(stats, (currentParticipant, nextParticipant) => {
if (this.props._speakerStatsOrder.includes('hasLeft')) {
if (nextParticipant.hasLeft() && !currentParticipant.hasLeft()) {
return -1;
} else if (currentParticipant.hasLeft() && !nextParticipant.hasLeft()) {
return 1;
}
}

let result;

for (const sortCriteria of this.props._speakerStatsOrder) {
switch (sortCriteria) {
case 'role':
if (!nextParticipant.isModerator && currentParticipant.isModerator) {
result = -1;
} else if (!currentParticipant.isModerator && nextParticipant.isModerator) {
result = 1;
} else {
result = 0;
}
break;
case 'name':
result = (currentParticipant.getDisplayName() || '').localeCompare(
nextParticipant.getDisplayName() || ''
);
break;
}

if (result !== 0) {
break;
}
}

return result;
});
}

/**
* Create a SpeakerStatsItem instance for the passed in user id.
*
Expand Down Expand Up @@ -231,7 +306,8 @@ class SpeakerStats extends Component<Props, State> {
* @param {Object} state - The redux state.
* @private
* @returns {{
* _localDisplayName: ?string
* _localDisplayName: ?string,
* _speakerStatsOrder: Array<string>
* }}
*/
function _mapStateToProps(state) {
Expand All @@ -244,7 +320,8 @@ function _mapStateToProps(state) {
* @private
* @type {string|undefined}
*/
_localDisplayName: localParticipant && localParticipant.name
_localDisplayName: localParticipant && localParticipant.name,
_speakerStatsOrder: getSpeakerStatsOrder(state)
};
}

Expand Down
10 changes: 10 additions & 0 deletions react/features/speaker-stats/functions.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,13 @@
export function isSpeakerStatsSearchDisabled(state: Object) {
return state['features/base/config']?.disableSpeakerStatsSearch;
}

/**
* Gets whether participants in speaker stats should be ordered or not, and with what priority.
*
* @param {*} state - The redux state.
* @returns {Array<string>} - The speaker stats order array or an empty array.
*/
export function getSpeakerStatsOrder(state: Object) {
return state['features/base/config']?.speakerStatsOrder ?? [];
}