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/app/middlewares.any.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import '../recording/middleware';
import '../rejoin/middleware';
import '../room-lock/middleware';
import '../rtcstats/middleware';
import '../speaker-stats/middleware';
import '../subtitles/middleware';
import '../toolbox/middleware';
import '../transcribing/middleware';
Expand Down
1 change: 1 addition & 0 deletions react/features/app/reducers.any.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import '../reactions/reducer';
import '../recent-list/reducer';
import '../recording/reducer';
import '../settings/reducer';
import '../speaker-stats/reducer';
import '../subtitles/reducer';
import '../screen-share/reducer';
import '../toolbox/reducer';
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 };
}, {});
}
39 changes: 39 additions & 0 deletions react/features/speaker-stats/actionTypes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// @flow

/**
* Action type to start search.
*
* {
* type: INIT_SEARCH
* }
*/
export const INIT_SEARCH = 'INIT_SEARCH';

/**
* Action type to start stats retrieval.
*
* {
* type: INIT_UPDATE_STATS,
* getSpeakerStats: Function
* }
*/
export const INIT_UPDATE_STATS = 'INIT_UPDATE_STATS';

/**
* Action type to update stats.
*
* {
* type: UPDATE_STATS,
* stats: Object
* }
*/
export const UPDATE_STATS = 'UPDATE_STATS';

/**
* Action type to initiate reordering of the stats.
*
* {
* type: INIT_REORDER_STATS
* }
*/
export const INIT_REORDER_STATS = 'INIT_REORDER_STATS';
58 changes: 58 additions & 0 deletions react/features/speaker-stats/actions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// @flow

import {
INIT_SEARCH,
INIT_UPDATE_STATS,
UPDATE_STATS,
INIT_REORDER_STATS
} from './actionTypes';

/**
* Starts a search by criteria.
*
* @param {string} criteria - The search criteria.
* @returns {Object}
*/
export function initSearch(criteria: string) {
return {
type: INIT_SEARCH,
criteria
};
}

/**
* Gets the new stats and triggers update.
*
* @param {Function} getSpeakerStats - Function to get the speaker stats.
* @returns {Object}
*/
export function initUpdateStats(getSpeakerStats: Function) {
return {
type: INIT_UPDATE_STATS,
getSpeakerStats
};
}

/**
* Updates the stats with new stats.
*
* @param {Object} stats - The new stats.
* @returns {Object}
*/
export function updateStats(stats: Object) {
return {
type: UPDATE_STATS,
stats
};
}

/**
* Initiates reordering of the stats.
*
* @returns {Object}
*/
export function initReorderStats() {
return {
type: INIT_REORDER_STATS
};
}
98 changes: 34 additions & 64 deletions react/features/speaker-stats/components/SpeakerStats.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
// @flow

import React, { Component } from 'react';
import type { Dispatch } from 'redux';

import { Dialog } from '../../base/dialog';
import { translate } from '../../base/i18n';
import { getLocalParticipant } from '../../base/participants';
import { connect } from '../../base/redux';
import { escapeRegexp } from '../../base/util';
import { initUpdateStats, initSearch } from '../actions';
import { SPEAKER_STATS_RELOAD_INTERVAL } from '../constants';
import { getSpeakerStats, getSearchCriteria } from '../functions';

import SpeakerStatsItem from './SpeakerStatsItem';
import SpeakerStatsLabels from './SpeakerStatsLabels';
Expand All @@ -25,38 +29,37 @@ type Props = {
_localDisplayName: string,

/**
* The JitsiConference from which stats will be pulled.
* The speaker paricipant stats.
*/
conference: Object,
_stats: Object,

/**
* The function to translate human-readable text.
* The search criteria.
*/
t: Function
};
_criteria: string,

/**
* The type of the React {@code Component} state of {@link SpeakerStats}.
*/
type State = {
/**
* The JitsiConference from which stats will be pulled.
*/
conference: Object,

/**
* The stats summary provided by the JitsiConference.
* Redux store dispatch method.
*/
stats: Object,
dispatch: Dispatch<any>,

/**
* The search input criteria.
* The function to translate human-readable text.
*/
criteria: string,
t: Function
};

/**
* React component for displaying a list of speaker stats.
*
* @extends Component
*/
class SpeakerStats extends Component<Props, State> {
class SpeakerStats extends Component<Props> {
_updateInterval: IntervalID;

/**
Expand All @@ -68,14 +71,11 @@ class SpeakerStats extends Component<Props, State> {
constructor(props) {
super(props);

this.state = {
stats: this._getSpeakerStats(),
criteria: ''
};

// Bind event handlers so they are only bound once per instance.
this._updateStats = this._updateStats.bind(this);
this._onSearch = this._onSearch.bind(this);

this._updateStats();
}

/**
Expand All @@ -84,7 +84,7 @@ class SpeakerStats extends Component<Props, State> {
* @inheritdoc
*/
componentDidMount() {
this._updateInterval = setInterval(this._updateStats, 1000);
this._updateInterval = setInterval(() => this._updateStats(), SPEAKER_STATS_RELOAD_INTERVAL);
}

/**
Expand All @@ -104,14 +104,14 @@ class SpeakerStats extends Component<Props, State> {
* @returns {ReactElement}
*/
render() {
const userIds = Object.keys(this.state.stats);
const userIds = Object.keys(this.props._stats);
const items = userIds.map(userId => this._createStatsItem(userId));

return (
<Dialog
cancelKey = { 'dialog.close' }
cancelKey = 'dialog.close'
submitDisabled = { true }
titleKey = { 'speakerStats.speakerStats' }>
titleKey = 'speakerStats.speakerStats'>
<div className = 'speaker-stats'>
<SpeakerStatsSearch onSearch = { this._onSearch } />
<SpeakerStatsLabels />
Expand All @@ -121,32 +121,6 @@ class SpeakerStats extends Component<Props, State> {
);
}

/**
* Update the internal state with the latest speaker stats.
*
* @returns {void}
* @private
*/
_getSpeakerStats() {
const stats = { ...this.props.conference.getSpeakerStats() };

if (this.state?.criteria) {
const searchRegex = new RegExp(this.state.criteria, 'gi');

for (const id in stats) {
if (stats[id].hasOwnProperty('_isLocalStats')) {
const name = stats[id].isLocalStats() ? this.props._localDisplayName : stats[id].getDisplayName();

if (!name || !name.match(searchRegex)) {
delete stats[id];
}
}
}
}

return stats;
}

/**
* Create a SpeakerStatsItem instance for the passed in user id.
*
Expand All @@ -156,9 +130,9 @@ class SpeakerStats extends Component<Props, State> {
* @private
*/
_createStatsItem(userId) {
const statsModel = this.state.stats[userId];
const statsModel = this.props._stats[userId];

if (!statsModel) {
if (!statsModel || statsModel.hidden) {
return null;
}

Expand All @@ -177,7 +151,7 @@ class SpeakerStats extends Component<Props, State> {
= displayName ? `${displayName} (${meString})` : meString;
} else {
displayName
= this.state.stats[userId].getDisplayName()
= this.props._stats[userId].getDisplayName()
|| interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME;
}

Expand All @@ -201,10 +175,7 @@ class SpeakerStats extends Component<Props, State> {
* @protected
*/
_onSearch(criteria = '') {
this.setState({
...this.state,
criteria: escapeRegexp(criteria)
});
this.props.dispatch(initSearch(escapeRegexp(criteria)));
}

_updateStats: () => void;
Expand All @@ -216,12 +187,7 @@ class SpeakerStats extends Component<Props, State> {
* @private
*/
_updateStats() {
const stats = this._getSpeakerStats();

this.setState({
...this.state,
stats
});
this.props.dispatch(initUpdateStats(() => this.props.conference.getSpeakerStats()));
}
}

Expand All @@ -231,7 +197,9 @@ class SpeakerStats extends Component<Props, State> {
* @param {Object} state - The redux state.
* @private
* @returns {{
* _localDisplayName: ?string
* _localDisplayName: ?string,
* _stats: Object,
* _criteria: string,
* }}
*/
function _mapStateToProps(state) {
Expand All @@ -244,7 +212,9 @@ function _mapStateToProps(state) {
* @private
* @type {string|undefined}
*/
_localDisplayName: localParticipant && localParticipant.name
_localDisplayName: localParticipant && localParticipant.name,
_stats: getSpeakerStats(state),
_criteria: getSearchCriteria(state)
};
}

Expand Down
1 change: 1 addition & 0 deletions react/features/speaker-stats/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const SPEAKER_STATS_RELOAD_INTERVAL = 1000;
Loading