feat(Filmstrip): Reorder the visible participants in the filmstrip. (#9707)
* feat(Filmstrip): Reorder the visible participants in the filmstrip. The participants are ordered alphabetically and the endpoints with screenshares, shared-videos and dominant speakers (in that order) are bumped to the top of the list. The local participant is also moved to the top left corner as opposed to the bottom right corner. * squash: Implement review comments. * squash: store alphabetically sorted list in redux and move shared videos to top. * squash: Use the DEFAULT_REMOTE_DISPLAY_NAME from interfaceConfig for users without a display name.
This commit is contained in:
parent
a7a44902ec
commit
40099e97ec
|
@ -2106,7 +2106,7 @@ export default {
|
|||
|
||||
room.on(
|
||||
JitsiConferenceEvents.DOMINANT_SPEAKER_CHANGED,
|
||||
id => APP.store.dispatch(dominantSpeakerChanged(id, room)));
|
||||
(dominant, previous) => APP.store.dispatch(dominantSpeakerChanged(dominant, previous, room)));
|
||||
|
||||
room.on(
|
||||
JitsiConferenceEvents.CONFERENCE_CREATED_TIMESTAMP,
|
||||
|
|
|
@ -170,7 +170,7 @@ function _addConferenceListeners(conference, dispatch, state) {
|
|||
|
||||
conference.on(
|
||||
JitsiConferenceEvents.DOMINANT_SPEAKER_CHANGED,
|
||||
id => dispatch(dominantSpeakerChanged(id, conference)));
|
||||
(dominant, previous) => dispatch(dominantSpeakerChanged(dominant, previous, conference)));
|
||||
|
||||
conference.on(
|
||||
JitsiConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
|
||||
|
|
|
@ -6,7 +6,9 @@
|
|||
* {
|
||||
* type: DOMINANT_SPEAKER_CHANGED,
|
||||
* participant: {
|
||||
* id: string
|
||||
* conference: JitsiConference,
|
||||
* id: string,
|
||||
* previousSpeakers: Array<string>
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
|
|
|
@ -31,7 +31,8 @@ import logger from './logger';
|
|||
/**
|
||||
* Create an action for when dominant speaker changes.
|
||||
*
|
||||
* @param {string} id - Participant's ID.
|
||||
* @param {string} dominantSpeaker - Participant ID of the dominant speaker.
|
||||
* @param {Array<string>} previousSpeakers - Participant IDs of the previous speakers.
|
||||
* @param {JitsiConference} conference - The {@code JitsiConference} associated
|
||||
* with the participant identified by the specified {@code id}. Only the local
|
||||
* participant is allowed to not specify an associated {@code JitsiConference}
|
||||
|
@ -40,16 +41,18 @@ import logger from './logger';
|
|||
* type: DOMINANT_SPEAKER_CHANGED,
|
||||
* participant: {
|
||||
* conference: JitsiConference,
|
||||
* id: string
|
||||
* id: string,
|
||||
* previousSpeakers: Array<string>
|
||||
* }
|
||||
* }}
|
||||
*/
|
||||
export function dominantSpeakerChanged(id, conference) {
|
||||
export function dominantSpeakerChanged(dominantSpeaker, previousSpeakers, conference) {
|
||||
return {
|
||||
type: DOMINANT_SPEAKER_CHANGED,
|
||||
participant: {
|
||||
conference,
|
||||
id
|
||||
id: dominantSpeaker,
|
||||
previousSpeakers
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -14,6 +14,8 @@ import {
|
|||
import { LOCAL_PARTICIPANT_DEFAULT_ID, PARTICIPANT_ROLE } from './constants';
|
||||
import { isParticipantModerator } from './functions';
|
||||
|
||||
declare var interfaceConfig: Object;
|
||||
|
||||
/**
|
||||
* Participant object.
|
||||
* @typedef {Object} Participant
|
||||
|
@ -51,13 +53,15 @@ const PARTICIPANT_PROPS_TO_OMIT_WHEN_UPDATE = [
|
|||
];
|
||||
|
||||
const DEFAULT_STATE = {
|
||||
haveParticipantWithScreenSharingFeature: false,
|
||||
dominantSpeaker: undefined,
|
||||
everyoneIsModerator: false,
|
||||
pinnedParticipant: undefined,
|
||||
fakeParticipants: new Map(),
|
||||
haveParticipantWithScreenSharingFeature: false,
|
||||
local: undefined,
|
||||
pinnedParticipant: undefined,
|
||||
remote: new Map(),
|
||||
fakeParticipants: new Map()
|
||||
sortedRemoteParticipants: new Map(),
|
||||
speakersList: []
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -91,8 +95,13 @@ ReducerRegistry.register('features/base/participants', (state = DEFAULT_STATE, a
|
|||
}
|
||||
case DOMINANT_SPEAKER_CHANGED: {
|
||||
const { participant } = action;
|
||||
const { id } = participant;
|
||||
const { dominantSpeaker } = state;
|
||||
const { id, previousSpeakers = [] } = participant;
|
||||
const { dominantSpeaker, local } = state;
|
||||
const speakersList = [];
|
||||
|
||||
// Update the speakers list.
|
||||
id !== local?.id && speakersList.push(id);
|
||||
speakersList.push(...previousSpeakers.filter(p => p !== local?.id));
|
||||
|
||||
// Only one dominant speaker is allowed.
|
||||
if (dominantSpeaker) {
|
||||
|
@ -102,7 +111,8 @@ ReducerRegistry.register('features/base/participants', (state = DEFAULT_STATE, a
|
|||
if (_updateParticipantProperty(state, id, 'dominantSpeaker', true)) {
|
||||
return {
|
||||
...state,
|
||||
dominantSpeaker: id
|
||||
dominantSpeaker: id,
|
||||
speakersList
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -180,21 +190,22 @@ ReducerRegistry.register('features/base/participants', (state = DEFAULT_STATE, a
|
|||
}
|
||||
case PARTICIPANT_JOINED: {
|
||||
const participant = _participantJoined(action);
|
||||
const { id, isFakeParticipant, name, pinned } = participant;
|
||||
const { pinnedParticipant, dominantSpeaker } = state;
|
||||
|
||||
if (participant.pinned) {
|
||||
if (pinned) {
|
||||
if (pinnedParticipant) {
|
||||
_updateParticipantProperty(state, pinnedParticipant, 'pinned', false);
|
||||
}
|
||||
|
||||
state.pinnedParticipant = participant.id;
|
||||
state.pinnedParticipant = id;
|
||||
}
|
||||
|
||||
if (participant.dominantSpeaker) {
|
||||
if (dominantSpeaker) {
|
||||
_updateParticipantProperty(state, dominantSpeaker, 'dominantSpeaker', false);
|
||||
}
|
||||
state.dominantSpeaker = participant.id;
|
||||
state.dominantSpeaker = id;
|
||||
}
|
||||
|
||||
const isModerator = isParticipantModerator(participant);
|
||||
|
@ -213,10 +224,21 @@ ReducerRegistry.register('features/base/participants', (state = DEFAULT_STATE, a
|
|||
};
|
||||
}
|
||||
|
||||
state.remote.set(participant.id, participant);
|
||||
state.remote.set(id, participant);
|
||||
|
||||
if (participant.isFakeParticipant) {
|
||||
state.fakeParticipants.set(participant.id, participant);
|
||||
// Insert the new participant.
|
||||
const displayName = name
|
||||
?? (typeof interfaceConfig === 'object' ? interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME : 'Fellow Jitser');
|
||||
const sortedRemoteParticipants = Array.from(state.sortedRemoteParticipants);
|
||||
|
||||
sortedRemoteParticipants.push([ id, displayName ]);
|
||||
sortedRemoteParticipants.sort((a, b) => a[1].localeCompare(b[1]));
|
||||
|
||||
// The sort order of participants is preserved since Map remembers the original insertion order of the keys.
|
||||
state.sortedRemoteParticipants = new Map(sortedRemoteParticipants);
|
||||
|
||||
if (isFakeParticipant) {
|
||||
state.fakeParticipants.set(id, participant);
|
||||
}
|
||||
|
||||
return { ...state };
|
||||
|
@ -242,6 +264,8 @@ ReducerRegistry.register('features/base/participants', (state = DEFAULT_STATE, a
|
|||
return state;
|
||||
}
|
||||
|
||||
state.sortedRemoteParticipants.delete(id);
|
||||
|
||||
if (!state.everyoneIsModerator && !isParticipantModerator(oldParticipant)) {
|
||||
state.everyoneIsModerator = _isEveryoneModerator(state);
|
||||
}
|
||||
|
@ -272,6 +296,9 @@ ReducerRegistry.register('features/base/participants', (state = DEFAULT_STATE, a
|
|||
state.dominantSpeaker = undefined;
|
||||
}
|
||||
|
||||
// Remove the participant from the list of speakers.
|
||||
state.speakersList = state.speakersList.filter(speaker => speaker !== id);
|
||||
|
||||
if (pinnedParticipant === id) {
|
||||
state.pinnedParticipant = undefined;
|
||||
}
|
||||
|
|
|
@ -50,6 +50,15 @@ export const SET_TILE_VIEW_DIMENSIONS = 'SET_TILE_VIEW_DIMENSIONS';
|
|||
*/
|
||||
export const SET_HORIZONTAL_VIEW_DIMENSIONS = 'SET_HORIZONTAL_VIEW_DIMENSIONS';
|
||||
|
||||
/**
|
||||
* The type of (redux) action which sets the reordered list of the remote participants in the filmstrip.
|
||||
* {
|
||||
* type: SET_REMOTE_PARTICIPANTS,
|
||||
* participants: Array<string>
|
||||
* }
|
||||
*/
|
||||
export const SET_REMOTE_PARTICIPANTS = 'SET_REMOTE_PARTICIPANTS';
|
||||
|
||||
/**
|
||||
* The type of (redux) action which sets the dimensions of the thumbnails in vertical view.
|
||||
*
|
||||
|
|
|
@ -5,6 +5,7 @@ import { getLocalParticipant, getRemoteParticipants, pinParticipant } from '../b
|
|||
|
||||
import {
|
||||
SET_HORIZONTAL_VIEW_DIMENSIONS,
|
||||
SET_REMOTE_PARTICIPANTS,
|
||||
SET_TILE_VIEW_DIMENSIONS,
|
||||
SET_VERTICAL_VIEW_DIMENSIONS,
|
||||
SET_VISIBLE_REMOTE_PARTICIPANTS,
|
||||
|
@ -25,6 +26,23 @@ import {
|
|||
calculateThumbnailSizeForVerticalView
|
||||
} from './functions';
|
||||
|
||||
/**
|
||||
* Sets the list of the reordered remote participants based on which the visible participants in the filmstrip will be
|
||||
* determined.
|
||||
*
|
||||
* @param {Array<string>} participants - The list of the remote participant endpoint IDs.
|
||||
* @returns {{
|
||||
type: SET_REMOTE_PARTICIPANTS,
|
||||
participants: Array<string>
|
||||
}}
|
||||
*/
|
||||
export function setRemoteParticipants(participants: Array<string>) {
|
||||
return {
|
||||
type: SET_REMOTE_PARTICIPANTS,
|
||||
participants
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the dimensions of the tile view grid.
|
||||
*
|
||||
|
|
|
@ -269,11 +269,11 @@ class Filmstrip extends PureComponent <Props> {
|
|||
return `empty-${index}`;
|
||||
}
|
||||
|
||||
if (index === _remoteParticipantsLength) {
|
||||
if (index === 0) {
|
||||
return 'local';
|
||||
}
|
||||
|
||||
return _remoteParticipants[index];
|
||||
return _remoteParticipants[index - 1];
|
||||
}
|
||||
|
||||
_onListItemsRendered: Object => void;
|
||||
|
@ -287,7 +287,7 @@ class Filmstrip extends PureComponent <Props> {
|
|||
_onListItemsRendered({ visibleStartIndex, visibleStopIndex }) {
|
||||
const { dispatch } = this.props;
|
||||
|
||||
dispatch(setVisibleRemoteParticipants(visibleStartIndex, visibleStopIndex));
|
||||
dispatch(setVisibleRemoteParticipants(visibleStartIndex, visibleStopIndex + 1));
|
||||
}
|
||||
|
||||
_onGridItemsRendered: Object => void;
|
||||
|
@ -305,9 +305,12 @@ class Filmstrip extends PureComponent <Props> {
|
|||
visibleRowStopIndex
|
||||
}) {
|
||||
const { _columns, dispatch } = this.props;
|
||||
const startIndex = (visibleRowStartIndex * _columns) + visibleColumnStartIndex;
|
||||
let startIndex = (visibleRowStartIndex * _columns) + visibleColumnStartIndex;
|
||||
const endIndex = (visibleRowStopIndex * _columns) + visibleColumnStopIndex;
|
||||
|
||||
// In tile view, the start index needs to be offset by 1 because the first participant is the local
|
||||
// participant.
|
||||
startIndex = startIndex > 0 ? startIndex - 1 : 0;
|
||||
dispatch(setVisibleRemoteParticipants(startIndex, endIndex));
|
||||
}
|
||||
|
||||
|
|
|
@ -126,7 +126,8 @@ function _mapStateToProps(state, ownProps) {
|
|||
return {};
|
||||
}
|
||||
|
||||
if (index === remoteParticipantsLength) {
|
||||
// Make the local participant as the first thumbnail (top left corner) in tile view.
|
||||
if (index === 0) {
|
||||
return {
|
||||
_participantID: 'local',
|
||||
_horizontalOffset: horizontalOffset
|
||||
|
@ -134,7 +135,7 @@ function _mapStateToProps(state, ownProps) {
|
|||
}
|
||||
|
||||
return {
|
||||
_participantID: remoteParticipants[index],
|
||||
_participantID: remoteParticipants[index - 1],
|
||||
_horizontalOffset: horizontalOffset
|
||||
};
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ import {
|
|||
isRemoteTrackMuted
|
||||
} from '../base/tracks/functions';
|
||||
|
||||
import { setRemoteParticipants } from './actions.web';
|
||||
import {
|
||||
ASPECT_RATIO_BREAKPOINT,
|
||||
DISPLAY_AVATAR,
|
||||
|
@ -265,3 +266,36 @@ export function computeDisplayMode(input: Object) {
|
|||
// check hovering and change state to avatar with name
|
||||
return isHovered ? DISPLAY_AVATAR_WITH_NAME : DISPLAY_AVATAR;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the reorderd list of the remote participants.
|
||||
*
|
||||
* @param {*} store - The redux store.
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
export function updateRemoteParticipants(store: Object) {
|
||||
const state = store.getState();
|
||||
const { fakeParticipants, sortedRemoteParticipants, speakersList } = state['features/base/participants'];
|
||||
const { remoteScreenShares } = state['features/video-layout'];
|
||||
const screenShares = (remoteScreenShares || []).slice();
|
||||
let speakers = (speakersList || []).slice();
|
||||
const remoteParticipants = new Map(sortedRemoteParticipants);
|
||||
const sharedVideos = fakeParticipants ? Array.from(fakeParticipants.keys()) : [];
|
||||
|
||||
for (const screenshare of screenShares) {
|
||||
remoteParticipants.delete(screenshare);
|
||||
speakers = speakers.filter(speaker => speaker !== screenshare);
|
||||
}
|
||||
for (const sharedVideo of sharedVideos) {
|
||||
remoteParticipants.delete(sharedVideo);
|
||||
speakers = speakers.filter(speaker => speaker !== sharedVideo);
|
||||
}
|
||||
for (const speaker of speakers) {
|
||||
remoteParticipants.delete(speaker);
|
||||
}
|
||||
const reorderedParticipants
|
||||
= [ ...screenShares.reverse(), ...sharedVideos, ...speakers, ...Array.from(remoteParticipants.keys()) ];
|
||||
|
||||
store.dispatch(setRemoteParticipants(reorderedParticipants));
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// @flow
|
||||
|
||||
import VideoLayout from '../../../modules/UI/videolayout/VideoLayout';
|
||||
import { PARTICIPANT_JOINED, PARTICIPANT_LEFT } from '../base/participants';
|
||||
import { MiddlewareRegistry } from '../base/redux';
|
||||
import { CLIENT_RESIZED } from '../base/responsive-ui';
|
||||
import { SETTINGS_UPDATED } from '../base/settings';
|
||||
|
@ -9,8 +10,13 @@ import {
|
|||
LAYOUTS
|
||||
} from '../video-layout';
|
||||
|
||||
import { setHorizontalViewDimensions, setTileViewDimensions, setVerticalViewDimensions } from './actions.web';
|
||||
|
||||
import {
|
||||
setHorizontalViewDimensions,
|
||||
setRemoteParticipants,
|
||||
setTileViewDimensions,
|
||||
setVerticalViewDimensions
|
||||
} from './actions.web';
|
||||
import { updateRemoteParticipants } from './functions.web';
|
||||
import './subscriber.web';
|
||||
|
||||
/**
|
||||
|
@ -41,6 +47,14 @@ MiddlewareRegistry.register(store => next => action => {
|
|||
}
|
||||
break;
|
||||
}
|
||||
case PARTICIPANT_JOINED: {
|
||||
updateRemoteParticipants(store);
|
||||
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.
|
||||
|
@ -53,3 +67,22 @@ MiddlewareRegistry.register(store => next => action => {
|
|||
return result;
|
||||
});
|
||||
|
||||
/**
|
||||
* Private helper to calculate the reordered list of remote participants when a participant leaves.
|
||||
*
|
||||
* @param {*} store - The redux store.
|
||||
* @param {string} participantId - The endpoint id of the participant leaving the call.
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
function _updateRemoteParticipantsOnLeave(store, participantId = null) {
|
||||
if (!participantId) {
|
||||
return;
|
||||
}
|
||||
const state = store.getState();
|
||||
const { remoteParticipants } = state['features/filmstrip'];
|
||||
const reorderedParticipants = new Set(remoteParticipants);
|
||||
|
||||
reorderedParticipants.delete(participantId)
|
||||
&& store.dispatch(setRemoteParticipants(Array.from(reorderedParticipants)));
|
||||
}
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
// @flow
|
||||
|
||||
import { PARTICIPANT_JOINED, PARTICIPANT_LEFT } from '../base/participants';
|
||||
import { PARTICIPANT_LEFT } from '../base/participants';
|
||||
import { ReducerRegistry } from '../base/redux';
|
||||
|
||||
import {
|
||||
SET_FILMSTRIP_ENABLED,
|
||||
SET_FILMSTRIP_VISIBLE,
|
||||
SET_HORIZONTAL_VIEW_DIMENSIONS,
|
||||
SET_REMOTE_PARTICIPANTS,
|
||||
SET_TILE_VIEW_DIMENSIONS,
|
||||
SET_VERTICAL_VIEW_DIMENSIONS,
|
||||
SET_VISIBLE_REMOTE_PARTICIPANTS,
|
||||
|
@ -40,8 +41,8 @@ const DEFAULT_STATE = {
|
|||
/**
|
||||
* The ordered IDs of the remote participants displayed in the filmstrip.
|
||||
*
|
||||
* NOTE: Currently the order will match the one from the base/participants array. But this is good initial step for
|
||||
* reordering the remote participants.
|
||||
* @public
|
||||
* @type {Array<string>}
|
||||
*/
|
||||
remoteParticipants: [],
|
||||
|
||||
|
@ -77,22 +78,21 @@ const DEFAULT_STATE = {
|
|||
*/
|
||||
visibleParticipantsEndIndex: 0,
|
||||
|
||||
/**
|
||||
* The visible participants in the filmstrip.
|
||||
*
|
||||
* @public
|
||||
* @type {Array<string>}
|
||||
*/
|
||||
visibleParticipants: [],
|
||||
|
||||
|
||||
/**
|
||||
* The start index in the remote participants array that is visible in the filmstrip.
|
||||
*
|
||||
* @public
|
||||
* @type {number}
|
||||
*/
|
||||
visibleParticipantsStartIndex: 0
|
||||
visibleParticipantsStartIndex: 0,
|
||||
|
||||
/**
|
||||
* The visible remote participants in the filmstrip.
|
||||
*
|
||||
* @public
|
||||
* @type {Set<string>}
|
||||
*/
|
||||
visibleRemoteParticipants: new Set()
|
||||
};
|
||||
|
||||
ReducerRegistry.register(
|
||||
|
@ -116,6 +116,14 @@ ReducerRegistry.register(
|
|||
...state,
|
||||
horizontalViewDimensions: action.dimensions
|
||||
};
|
||||
case SET_REMOTE_PARTICIPANTS: {
|
||||
const { visibleParticipantsStartIndex: startIndex, visibleParticipantsEndIndex: endIndex } = state;
|
||||
|
||||
state.remoteParticipants = action.participants;
|
||||
state.visibleRemoteParticipants = new Set(state.remoteParticipants.slice(startIndex, endIndex));
|
||||
|
||||
return { ...state };
|
||||
}
|
||||
case SET_TILE_VIEW_DIMENSIONS:
|
||||
return {
|
||||
...state,
|
||||
|
@ -138,27 +146,13 @@ ReducerRegistry.register(
|
|||
[action.participantId]: action.volume
|
||||
}
|
||||
};
|
||||
case SET_VISIBLE_REMOTE_PARTICIPANTS:
|
||||
case SET_VISIBLE_REMOTE_PARTICIPANTS: {
|
||||
return {
|
||||
...state,
|
||||
visibleParticipantsStartIndex: action.startIndex,
|
||||
visibleParticipantsEndIndex: action.endIndex,
|
||||
visibleParticipants: state.remoteParticipants.slice(action.startIndex, action.endIndex + 1)
|
||||
visibleRemoteParticipants: new Set(state.remoteParticipants.slice(action.startIndex, action.endIndex))
|
||||
};
|
||||
case PARTICIPANT_JOINED: {
|
||||
const { id, local } = action.participant;
|
||||
|
||||
if (!local) {
|
||||
state.remoteParticipants = [ ...state.remoteParticipants, id ];
|
||||
|
||||
const { visibleParticipantsStartIndex: startIndex, visibleParticipantsEndIndex: endIndex } = state;
|
||||
|
||||
if (state.remoteParticipants.length - 1 <= endIndex) {
|
||||
state.visibleParticipants = state.remoteParticipants.slice(startIndex, endIndex + 1);
|
||||
}
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
case PARTICIPANT_LEFT: {
|
||||
const { id, local } = action.participant;
|
||||
|
@ -166,25 +160,6 @@ ReducerRegistry.register(
|
|||
if (local) {
|
||||
return state;
|
||||
}
|
||||
|
||||
let removedParticipantIndex = 0;
|
||||
|
||||
state.remoteParticipants = state.remoteParticipants.filter((participantId, index) => {
|
||||
if (participantId === id) {
|
||||
removedParticipantIndex = index;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
const { visibleParticipantsStartIndex: startIndex, visibleParticipantsEndIndex: endIndex } = state;
|
||||
|
||||
if (removedParticipantIndex >= startIndex && removedParticipantIndex <= endIndex) {
|
||||
state.visibleParticipants = state.remoteParticipants.slice(startIndex, endIndex + 1);
|
||||
}
|
||||
|
||||
delete state.participantsVolume[id];
|
||||
|
||||
return state;
|
||||
|
|
|
@ -8,13 +8,18 @@ import { getParticipantsPaneOpen } from '../participants-pane/functions';
|
|||
import { setOverflowDrawer } from '../toolbox/actions.web';
|
||||
import { getCurrentLayout, getTileViewGridDimensions, shouldDisplayTileView, LAYOUTS } from '../video-layout';
|
||||
|
||||
import { setHorizontalViewDimensions, setTileViewDimensions, setVerticalViewDimensions } from './actions.web';
|
||||
import {
|
||||
setHorizontalViewDimensions,
|
||||
setTileViewDimensions,
|
||||
setVerticalViewDimensions
|
||||
} from './actions.web';
|
||||
import {
|
||||
ASPECT_RATIO_BREAKPOINT,
|
||||
DISPLAY_DRAWER_THRESHOLD,
|
||||
SINGLE_COLUMN_BREAKPOINT,
|
||||
TWO_COLUMN_BREAKPOINT
|
||||
} from './constants';
|
||||
import { updateRemoteParticipants } from './functions.web';
|
||||
|
||||
/**
|
||||
* Listens for changes in the number of participants to calculate the dimensions of the tile view grid and the tiles.
|
||||
|
@ -153,3 +158,36 @@ StateListenerRegistry.register(
|
|||
store.dispatch(setTileViewDimensions(gridDimensions));
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Listens for changes to the screensharing status of the remote participants to recompute the reordered list of the
|
||||
* remote endpoints.
|
||||
*/
|
||||
StateListenerRegistry.register(
|
||||
/* selector */ state => state['features/video-layout'].remoteScreenShares,
|
||||
/* listener */ (remoteScreenShares, store) => updateRemoteParticipants(store));
|
||||
|
||||
/**
|
||||
* Listens for changes to the dominant speaker to recompute the reordered list of the remote endpoints.
|
||||
*/
|
||||
StateListenerRegistry.register(
|
||||
/* selector */ state => state['features/base/participants'].dominantSpeaker,
|
||||
/* listener */ (dominantSpeaker, store) => _reorderDominantSpeakers(store));
|
||||
|
||||
/**
|
||||
* Private helper function that reorders the remote participants based on dominant speaker changes.
|
||||
*
|
||||
* @param {*} store - The redux store.
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
function _reorderDominantSpeakers(store) {
|
||||
const state = store.getState();
|
||||
const { dominantSpeaker, local } = state['features/base/participants'];
|
||||
const { visibleRemoteParticipants } = state['features/filmstrip'];
|
||||
|
||||
// Reorder the participants if the new dominant speaker is currently not visible.
|
||||
if (dominantSpeaker !== local?.id && !visibleRemoteParticipants.has(dominantSpeaker)) {
|
||||
updateRemoteParticipants(store);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,8 +22,8 @@ declare var APP: Object;
|
|||
* scrolling through the thumbnails prompting updates to the selected endpoints.
|
||||
*/
|
||||
StateListenerRegistry.register(
|
||||
/* selector */ state => state['features/filmstrip'].visibleParticipants,
|
||||
/* listener */ debounce((visibleParticipants, store) => {
|
||||
/* selector */ state => state['features/filmstrip'].visibleRemoteParticipants,
|
||||
/* listener */ debounce((visibleRemoteParticipants, store) => {
|
||||
_updateReceiverVideoConstraints(store);
|
||||
}, 100));
|
||||
|
||||
|
@ -191,11 +191,11 @@ function _updateReceiverVideoConstraints({ getState }) {
|
|||
const { maxReceiverVideoQuality, preferredVideoQuality } = state['features/video-quality'];
|
||||
const { participantId: largeVideoParticipantId } = state['features/large-video'];
|
||||
const maxFrameHeight = Math.min(maxReceiverVideoQuality, preferredVideoQuality);
|
||||
let { visibleParticipants } = state['features/filmstrip'];
|
||||
let { visibleRemoteParticipants } = state['features/filmstrip'];
|
||||
|
||||
// TODO: implement this on mobile.
|
||||
if (navigator.product === 'ReactNative') {
|
||||
visibleParticipants = Array.from(state['features/base/participants'].remote.keys());
|
||||
visibleRemoteParticipants = new Set(Array.from(state['features/base/participants'].remote.keys()));
|
||||
}
|
||||
|
||||
const receiverConstraints = {
|
||||
|
@ -208,22 +208,22 @@ function _updateReceiverVideoConstraints({ getState }) {
|
|||
|
||||
// Tile view.
|
||||
if (shouldDisplayTileView(state)) {
|
||||
if (!visibleParticipants?.length) {
|
||||
if (!visibleRemoteParticipants?.size) {
|
||||
return;
|
||||
}
|
||||
|
||||
visibleParticipants.forEach(participantId => {
|
||||
visibleRemoteParticipants.forEach(participantId => {
|
||||
receiverConstraints.constraints[participantId] = { 'maxHeight': maxFrameHeight };
|
||||
});
|
||||
|
||||
// Stage view.
|
||||
} else {
|
||||
if (!visibleParticipants?.length && !largeVideoParticipantId) {
|
||||
if (!visibleRemoteParticipants?.size && !largeVideoParticipantId) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (visibleParticipants?.length > 0) {
|
||||
visibleParticipants.forEach(participantId => {
|
||||
if (visibleRemoteParticipants?.size > 0) {
|
||||
visibleRemoteParticipants.forEach(participantId => {
|
||||
receiverConstraints.constraints[participantId] = { 'maxHeight': VIDEO_QUALITY_LEVELS.LOW };
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue