feat(stage-filmstrip) Added user configurable max (#11324)
The user can set the max number of participants that can be displayed on stage Send the number on follow me to all participants
This commit is contained in:
parent
6687c3f4ab
commit
c05a983c98
|
@ -919,6 +919,7 @@
|
|||
"incomingMessage": "Incoming message",
|
||||
"language": "Language",
|
||||
"loggedIn": "Logged in as {{name}}",
|
||||
"maxStageParticipants": "Maximum number of participants who can be pinned to the main stage",
|
||||
"microphones": "Microphones",
|
||||
"moderator": "Moderator",
|
||||
"more": "More",
|
||||
|
|
|
@ -159,3 +159,14 @@ export const REMOVE_STAGE_PARTICIPANT = 'REMOVE_STAGE_PARTICIPANT';
|
|||
* }
|
||||
*/
|
||||
export const SET_STAGE_PARTICIPANTS = 'SET_STAGE_PARTICIPANTS';
|
||||
|
||||
|
||||
/**
|
||||
* The type of Redux action which sets the max number of active participants.
|
||||
* (the participants displayed on the stage filmstrip).
|
||||
* {
|
||||
* type: SET_MAX_STAGE_PARTICIPANTS,
|
||||
* maxParticipants: Number
|
||||
* }
|
||||
*/
|
||||
export const SET_MAX_STAGE_PARTICIPANTS = 'SET_MAX_STAGE_PARTICIPANTS';
|
||||
|
|
|
@ -22,7 +22,8 @@ import {
|
|||
SET_USER_FILMSTRIP_WIDTH,
|
||||
SET_USER_IS_RESIZING,
|
||||
SET_VERTICAL_VIEW_DIMENSIONS,
|
||||
SET_VOLUME
|
||||
SET_VOLUME,
|
||||
SET_MAX_STAGE_PARTICIPANTS
|
||||
} from './actionTypes';
|
||||
import {
|
||||
HORIZONTAL_FILMSTRIP_MARGIN,
|
||||
|
@ -435,3 +436,16 @@ export function setStageParticipants(queue) {
|
|||
queue
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the max number of participants to be displayed on stage.
|
||||
*
|
||||
* @param {number} maxParticipants - Max number of participants.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function setMaxStageParticipants(maxParticipants) {
|
||||
return {
|
||||
type: SET_MAX_STAGE_PARTICIPANTS,
|
||||
maxParticipants
|
||||
};
|
||||
}
|
||||
|
|
|
@ -293,4 +293,4 @@ export const ACTIVE_PARTICIPANT_TIMEOUT = 1000 * 60;
|
|||
/**
|
||||
* The max number of participants to be displayed on the stage filmstrip.
|
||||
*/
|
||||
export const MAX_ACTIVE_PARTICIPANTS = 4;
|
||||
export const MAX_ACTIVE_PARTICIPANTS = 6;
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
// @flow
|
||||
|
||||
import { batch } from 'react-redux';
|
||||
|
||||
import VideoLayout from '../../../modules/UI/videolayout/VideoLayout';
|
||||
import {
|
||||
DOMINANT_SPEAKER_CHANGED,
|
||||
|
@ -18,7 +20,12 @@ import {
|
|||
setTileView
|
||||
} from '../video-layout';
|
||||
|
||||
import { ADD_STAGE_PARTICIPANT, REMOVE_STAGE_PARTICIPANT, SET_USER_FILMSTRIP_WIDTH } from './actionTypes';
|
||||
import {
|
||||
ADD_STAGE_PARTICIPANT,
|
||||
REMOVE_STAGE_PARTICIPANT,
|
||||
SET_MAX_STAGE_PARTICIPANTS,
|
||||
SET_USER_FILMSTRIP_WIDTH
|
||||
} from './actionTypes';
|
||||
import {
|
||||
addStageParticipant,
|
||||
removeStageParticipant,
|
||||
|
@ -122,7 +129,7 @@ MiddlewareRegistry.register(store => next => action => {
|
|||
const { dispatch, getState } = store;
|
||||
const { participantId, pinned } = action;
|
||||
const state = getState();
|
||||
const { activeParticipants } = state['features/filmstrip'];
|
||||
const { activeParticipants, maxStageParticipants } = state['features/filmstrip'];
|
||||
let queue;
|
||||
|
||||
if (activeParticipants.find(p => p.participantId === participantId)) {
|
||||
|
@ -134,7 +141,7 @@ MiddlewareRegistry.register(store => next => action => {
|
|||
const tid = timers.get(participantId);
|
||||
|
||||
clearTimeout(tid);
|
||||
} else if (activeParticipants.length < MAX_ACTIVE_PARTICIPANTS) {
|
||||
} else if (activeParticipants.length < maxStageParticipants) {
|
||||
queue = [ ...activeParticipants, {
|
||||
participantId,
|
||||
pinned
|
||||
|
@ -217,6 +224,22 @@ MiddlewareRegistry.register(store => next => action => {
|
|||
}
|
||||
break;
|
||||
}
|
||||
case SET_MAX_STAGE_PARTICIPANTS: {
|
||||
const { maxParticipants } = action;
|
||||
const { activeParticipants } = store.getState()['features/filmstrip'];
|
||||
const newMax = Math.min(MAX_ACTIVE_PARTICIPANTS, maxParticipants);
|
||||
|
||||
action.maxParticipants = newMax;
|
||||
|
||||
if (newMax < activeParticipants.length) {
|
||||
const toRemove = activeParticipants.slice(0, activeParticipants.length - newMax);
|
||||
|
||||
batch(() => {
|
||||
toRemove.forEach(p => store.dispatch(removeStageParticipant(p.participantId)));
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result ?? next(action);
|
||||
|
|
|
@ -17,7 +17,8 @@ import {
|
|||
SET_USER_IS_RESIZING,
|
||||
SET_VERTICAL_VIEW_DIMENSIONS,
|
||||
SET_VISIBLE_REMOTE_PARTICIPANTS,
|
||||
SET_VOLUME
|
||||
SET_VOLUME,
|
||||
SET_MAX_STAGE_PARTICIPANTS
|
||||
} from './actionTypes';
|
||||
|
||||
const DEFAULT_STATE = {
|
||||
|
@ -51,6 +52,14 @@ const DEFAULT_STATE = {
|
|||
*/
|
||||
isResizing: false,
|
||||
|
||||
/**
|
||||
* The current max number of participants to be displayed on the stage filmstrip.
|
||||
*
|
||||
* @public
|
||||
* @type {Number}
|
||||
*/
|
||||
maxStageParticipants: 4,
|
||||
|
||||
/**
|
||||
* The custom audio volume levels per participant.
|
||||
*
|
||||
|
@ -258,6 +267,12 @@ ReducerRegistry.register(
|
|||
activeParticipants: state.activeParticipants.filter(p => p.participantId !== action.participantId)
|
||||
};
|
||||
}
|
||||
case SET_MAX_STAGE_PARTICIPANTS: {
|
||||
return {
|
||||
...state,
|
||||
maxStageParticipants: action.maxParticipants
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return state;
|
||||
|
|
|
@ -11,7 +11,7 @@ import {
|
|||
} from '../base/participants';
|
||||
import { MiddlewareRegistry } from '../base/redux';
|
||||
import { setFilmstripVisible } from '../filmstrip';
|
||||
import { addStageParticipant } from '../filmstrip/actions.web';
|
||||
import { addStageParticipant, setMaxStageParticipants } from '../filmstrip/actions.web';
|
||||
import { setTileView } from '../video-layout';
|
||||
|
||||
import {
|
||||
|
@ -189,6 +189,11 @@ function _onFollowMeCommand(attributes = {}, id, store) {
|
|||
stageParticipants.forEach(p => store.dispatch(addStageParticipant(p.participantId, true)));
|
||||
}
|
||||
}
|
||||
|
||||
if (attributes.maxStageParticipants !== undefined
|
||||
&& oldState.maxStageParticipants !== attributes.maxStageParticipants) {
|
||||
store.dispatch(setMaxStageParticipants(Number(attributes.maxStageParticipants)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -70,6 +70,13 @@ StateListenerRegistry.register(
|
|||
/* selector */ state => state['features/video-layout'].tileViewEnabled,
|
||||
/* listener */ _sendFollowMeCommand);
|
||||
|
||||
/**
|
||||
* Subscribes to changes to the max number of stage participants setting.
|
||||
*/
|
||||
StateListenerRegistry.register(
|
||||
/* selector */ state => state['features/filmstrip'].maxStageParticipants,
|
||||
/* listener */ _sendFollowMeCommand);
|
||||
|
||||
/**
|
||||
* Private selector for returning state from redux that should be respected by
|
||||
* other participants while follow me is enabled.
|
||||
|
@ -83,6 +90,7 @@ function _getFollowMeState(state) {
|
|||
|
||||
return {
|
||||
filmstripVisible: state['features/filmstrip'].visible,
|
||||
maxStageParticipants: stageFilmstrip ? state['features/filmstrip'].maxStageParticipants : undefined,
|
||||
nextOnStage: stageFilmstrip ? undefined : pinnedParticipant && pinnedParticipant.id,
|
||||
pinnedStageParticipants: stageFilmstrip ? JSON.stringify(getPinnedActiveParticipants(state)) : undefined,
|
||||
sharedDocumentVisible: state['features/etherpad'].editing,
|
||||
|
|
|
@ -10,6 +10,7 @@ import {
|
|||
import { openDialog } from '../base/dialog';
|
||||
import { i18next } from '../base/i18n';
|
||||
import { updateSettings } from '../base/settings';
|
||||
import { setMaxStageParticipants } from '../filmstrip/actions.web';
|
||||
import { setScreenshareFramerate } from '../screen-share/actions';
|
||||
|
||||
import {
|
||||
|
@ -116,6 +117,10 @@ export function submitMoreTab(newState: Object): Function {
|
|||
if (newState.hideSelfView !== currentState.hideSelfView) {
|
||||
dispatch(updateSettings({ disableSelfView: newState.hideSelfView }));
|
||||
}
|
||||
|
||||
if (Number(newState.maxStageParticipants) !== currentState.maxStageParticipants) {
|
||||
dispatch(setMaxStageParticipants(Number(newState.maxStageParticipants)));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ import { AbstractDialogTab } from '../../../base/dialog';
|
|||
import type { Props as AbstractDialogTabProps } from '../../../base/dialog';
|
||||
import { translate } from '../../../base/i18n';
|
||||
import TouchmoveHack from '../../../chat/components/web/TouchmoveHack';
|
||||
import { MAX_ACTIVE_PARTICIPANTS } from '../../../filmstrip';
|
||||
import { SS_DEFAULT_FRAME_RATE } from '../../constants';
|
||||
|
||||
/**
|
||||
|
@ -22,7 +23,7 @@ export type Props = {
|
|||
/**
|
||||
* The currently selected desktop share frame rate in the frame rate select dropdown.
|
||||
*/
|
||||
currentFramerate: string,
|
||||
currentFramerate: string,
|
||||
|
||||
/**
|
||||
* The currently selected language to display in the language select
|
||||
|
@ -99,7 +100,7 @@ type State = {
|
|||
/**
|
||||
* Whether or not the desktop share frame rate select dropdown is open.
|
||||
*/
|
||||
isFramerateSelectOpen: boolean,
|
||||
isFramerateSelectOpen: boolean,
|
||||
|
||||
/**
|
||||
* Whether or not the language select dropdown is open.
|
||||
|
@ -124,7 +125,8 @@ class MoreTab extends AbstractDialogTab<Props, State> {
|
|||
|
||||
this.state = {
|
||||
isFramerateSelectOpen: false,
|
||||
isLanguageSelectOpen: false
|
||||
isLanguageSelectOpen: false,
|
||||
isMaxStageParticipantsOpen: false
|
||||
};
|
||||
|
||||
// Bind event handler so it is only bound once for every instance.
|
||||
|
@ -136,6 +138,9 @@ class MoreTab extends AbstractDialogTab<Props, State> {
|
|||
this._onShowPrejoinPageChanged = this._onShowPrejoinPageChanged.bind(this);
|
||||
this._onKeyboardShortcutEnableChanged = this._onKeyboardShortcutEnableChanged.bind(this);
|
||||
this._onHideSelfViewChanged = this._onHideSelfViewChanged.bind(this);
|
||||
this._renderMaxStageParticipantsSelect = this._renderMaxStageParticipantsSelect.bind(this);
|
||||
this._onMaxStageParticipantsSelect = this._onMaxStageParticipantsSelect.bind(this);
|
||||
this._onMaxStageParticipantsOpenChange = this._onMaxStageParticipantsOpenChange.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -277,6 +282,34 @@ class MoreTab extends AbstractDialogTab<Props, State> {
|
|||
super._onChange({ hideSelfView: checked });
|
||||
}
|
||||
|
||||
_onMaxStageParticipantsOpenChange: (Object) => void;
|
||||
|
||||
/**
|
||||
* Callback invoked to toggle display of the max stage participants select dropdown.
|
||||
*
|
||||
* @param {Object} event - The event for opening or closing the dropdown.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onMaxStageParticipantsOpenChange({ isOpen }) {
|
||||
this.setState({ isMaxStageParticipantsOpen: isOpen });
|
||||
}
|
||||
|
||||
_onMaxStageParticipantsSelect: (Object) => void;
|
||||
|
||||
/**
|
||||
* Callback invoked to select a max number of stage participants from the select dropdown.
|
||||
*
|
||||
* @param {Object} e - The key event to handle.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onMaxStageParticipantsSelect(e) {
|
||||
const maxParticipants = e.currentTarget.getAttribute('data-maxparticipants');
|
||||
|
||||
super._onChange({ maxStageParticipants: maxParticipants });
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the React Element for the desktop share frame rate dropdown.
|
||||
*
|
||||
|
@ -490,6 +523,54 @@ class MoreTab extends AbstractDialogTab<Props, State> {
|
|||
);
|
||||
}
|
||||
|
||||
_renderMaxStageParticipantsSelect: () => void;
|
||||
|
||||
/**
|
||||
* Returns the React Element for the max stage participants dropdown.
|
||||
*
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
_renderMaxStageParticipantsSelect() {
|
||||
const { maxStageParticipants, t } = this.props;
|
||||
const maxParticipantsItems = Array(MAX_ACTIVE_PARTICIPANTS).fill(0)
|
||||
.map((no, index) => (
|
||||
<DropdownItem
|
||||
data-maxparticipants = { index + 1 }
|
||||
key = { index + 1 }
|
||||
onClick = { this._onMaxStageParticipantsSelect }>
|
||||
{index + 1}
|
||||
</DropdownItem>));
|
||||
|
||||
return (
|
||||
<div
|
||||
className = 'settings-sub-pane-element'
|
||||
key = 'maxStageParticipants'>
|
||||
<h2 className = 'mock-atlaskit-label'>
|
||||
{ t('settings.maxStageParticipants') }
|
||||
</h2>
|
||||
<div className = 'dropdown-menu'>
|
||||
<TouchmoveHack
|
||||
flex = { true }
|
||||
isModal = { true }>
|
||||
<DropdownMenu
|
||||
isOpen = { this.state.isMaxStageParticipantsOpen }
|
||||
onOpenChange = { this._onMaxStageParticipantsOpenChange }
|
||||
shouldFitContainer = { true }
|
||||
trigger = { maxStageParticipants }
|
||||
triggerButtonProps = {{
|
||||
shouldFitContainer: true
|
||||
}}
|
||||
triggerType = 'button'>
|
||||
<DropdownItemGroup>
|
||||
{ maxParticipantsItems }
|
||||
</DropdownItemGroup>
|
||||
</DropdownMenu>
|
||||
</TouchmoveHack>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the React element that needs to be displayed on the right half of the more tabs.
|
||||
*
|
||||
|
@ -505,6 +586,7 @@ class MoreTab extends AbstractDialogTab<Props, State> {
|
|||
key = 'settings-sub-pane-right'>
|
||||
{ showLanguageSettings && this._renderLanguageSelect() }
|
||||
{ this._renderFramerateSelect() }
|
||||
{ this._renderMaxStageParticipantsSelect() }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -333,8 +333,8 @@ function _mapStateToProps(state, ownProps) {
|
|||
tabs.push({
|
||||
name: SETTINGS_TABS.CALENDAR,
|
||||
component: CalendarTab,
|
||||
label: 'settings-pane settings.calendar.title',
|
||||
styles: `${classes.settingsDialog} calendar-pane`
|
||||
label: 'settings.calendar.title',
|
||||
styles: `settings-pane ${classes.settingsDialog} calendar-pane`
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -364,7 +364,8 @@ function _mapStateToProps(state, ownProps) {
|
|||
currentLanguage: tabState?.currentLanguage,
|
||||
hideSelfView: tabState?.hideSelfView,
|
||||
showPrejoinPage: tabState?.showPrejoinPage,
|
||||
enabledNotifications: tabState?.enabledNotifications
|
||||
enabledNotifications: tabState?.enabledNotifications,
|
||||
maxStageParticipants: tabState?.maxStageParticipants
|
||||
};
|
||||
},
|
||||
styles: `settings-pane ${classes.settingsDialog} more-pane`,
|
||||
|
|
|
@ -130,7 +130,8 @@ export function getMoreTabProps(stateful: Object | Function) {
|
|||
enabledNotifications,
|
||||
showNotificationsSettings: Object.keys(enabledNotifications).length > 0,
|
||||
showPrejoinPage: !state['features/base/settings'].userSelectedSkipPrejoin,
|
||||
showPrejoinSettings: state['features/base/config'].prejoinConfig?.enabled
|
||||
showPrejoinSettings: state['features/base/config'].prejoinConfig?.enabled,
|
||||
maxStageParticipants: state['features/filmstrip'].maxStageParticipants
|
||||
};
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue