Pārlūkot izejas kodu

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.
master
Jaya Allamsetty 3 gadus atpakaļ
vecāks
revīzija
40099e97ec
Revīzijas autora e-pasta adrese nav piesaistīta nevienam kontam

+ 1
- 1
conference.js Parādīt failu

@@ -2106,7 +2106,7 @@ export default {
2106 2106
 
2107 2107
         room.on(
2108 2108
             JitsiConferenceEvents.DOMINANT_SPEAKER_CHANGED,
2109
-            id => APP.store.dispatch(dominantSpeakerChanged(id, room)));
2109
+            (dominant, previous) => APP.store.dispatch(dominantSpeakerChanged(dominant, previous, room)));
2110 2110
 
2111 2111
         room.on(
2112 2112
             JitsiConferenceEvents.CONFERENCE_CREATED_TIMESTAMP,

+ 1
- 1
react/features/base/conference/actions.js Parādīt failu

@@ -170,7 +170,7 @@ function _addConferenceListeners(conference, dispatch, state) {
170 170
 
171 171
     conference.on(
172 172
         JitsiConferenceEvents.DOMINANT_SPEAKER_CHANGED,
173
-        id => dispatch(dominantSpeakerChanged(id, conference)));
173
+        (dominant, previous) => dispatch(dominantSpeakerChanged(dominant, previous, conference)));
174 174
 
175 175
     conference.on(
176 176
         JitsiConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,

+ 3
- 1
react/features/base/participants/actionTypes.js Parādīt failu

@@ -6,7 +6,9 @@
6 6
  * {
7 7
  *     type: DOMINANT_SPEAKER_CHANGED,
8 8
  *     participant: {
9
- *         id: string
9
+ *         conference: JitsiConference,
10
+ *         id: string,
11
+ *         previousSpeakers: Array<string>
10 12
  *     }
11 13
  * }
12 14
  */

+ 7
- 4
react/features/base/participants/actions.js Parādīt failu

@@ -31,7 +31,8 @@ import logger from './logger';
31 31
 /**
32 32
  * Create an action for when dominant speaker changes.
33 33
  *
34
- * @param {string} id - Participant's ID.
34
+ * @param {string} dominantSpeaker - Participant ID of the dominant speaker.
35
+ * @param {Array<string>} previousSpeakers - Participant IDs of the previous speakers.
35 36
  * @param {JitsiConference} conference - The {@code JitsiConference} associated
36 37
  * with the participant identified by the specified {@code id}. Only the local
37 38
  * participant is allowed to not specify an associated {@code JitsiConference}
@@ -40,16 +41,18 @@ import logger from './logger';
40 41
  *     type: DOMINANT_SPEAKER_CHANGED,
41 42
  *     participant: {
42 43
  *         conference: JitsiConference,
43
- *         id: string
44
+ *         id: string,
45
+ *         previousSpeakers: Array<string>
44 46
  *     }
45 47
  * }}
46 48
  */
47
-export function dominantSpeakerChanged(id, conference) {
49
+export function dominantSpeakerChanged(dominantSpeaker, previousSpeakers, conference) {
48 50
     return {
49 51
         type: DOMINANT_SPEAKER_CHANGED,
50 52
         participant: {
51 53
             conference,
52
-            id
54
+            id: dominantSpeaker,
55
+            previousSpeakers
53 56
         }
54 57
     };
55 58
 }

+ 39
- 12
react/features/base/participants/reducer.js Parādīt failu

@@ -14,6 +14,8 @@ import {
14 14
 import { LOCAL_PARTICIPANT_DEFAULT_ID, PARTICIPANT_ROLE } from './constants';
15 15
 import { isParticipantModerator } from './functions';
16 16
 
17
+declare var interfaceConfig: Object;
18
+
17 19
 /**
18 20
  * Participant object.
19 21
  * @typedef {Object} Participant
@@ -51,13 +53,15 @@ const PARTICIPANT_PROPS_TO_OMIT_WHEN_UPDATE = [
51 53
 ];
52 54
 
53 55
 const DEFAULT_STATE = {
54
-    haveParticipantWithScreenSharingFeature: false,
55 56
     dominantSpeaker: undefined,
56 57
     everyoneIsModerator: false,
57
-    pinnedParticipant: undefined,
58
+    fakeParticipants: new Map(),
59
+    haveParticipantWithScreenSharingFeature: false,
58 60
     local: undefined,
61
+    pinnedParticipant: undefined,
59 62
     remote: new Map(),
60
-    fakeParticipants: new Map()
63
+    sortedRemoteParticipants: new Map(),
64
+    speakersList: []
61 65
 };
62 66
 
63 67
 /**
@@ -91,8 +95,13 @@ ReducerRegistry.register('features/base/participants', (state = DEFAULT_STATE, a
91 95
     }
92 96
     case DOMINANT_SPEAKER_CHANGED: {
93 97
         const { participant } = action;
94
-        const { id } = participant;
95
-        const { dominantSpeaker } = state;
98
+        const { id, previousSpeakers = [] } = participant;
99
+        const { dominantSpeaker, local } = state;
100
+        const speakersList = [];
101
+
102
+        // Update the speakers list.
103
+        id !== local?.id && speakersList.push(id);
104
+        speakersList.push(...previousSpeakers.filter(p => p !== local?.id));
96 105
 
97 106
         // Only one dominant speaker is allowed.
98 107
         if (dominantSpeaker) {
@@ -102,7 +111,8 @@ ReducerRegistry.register('features/base/participants', (state = DEFAULT_STATE, a
102 111
         if (_updateParticipantProperty(state, id, 'dominantSpeaker', true)) {
103 112
             return {
104 113
                 ...state,
105
-                dominantSpeaker: id
114
+                dominantSpeaker: id,
115
+                speakersList
106 116
             };
107 117
         }
108 118
 
@@ -180,21 +190,22 @@ ReducerRegistry.register('features/base/participants', (state = DEFAULT_STATE, a
180 190
     }
181 191
     case PARTICIPANT_JOINED: {
182 192
         const participant = _participantJoined(action);
193
+        const { id, isFakeParticipant, name, pinned } = participant;
183 194
         const { pinnedParticipant, dominantSpeaker } = state;
184 195
 
185
-        if (participant.pinned) {
196
+        if (pinned) {
186 197
             if (pinnedParticipant) {
187 198
                 _updateParticipantProperty(state, pinnedParticipant, 'pinned', false);
188 199
             }
189 200
 
190
-            state.pinnedParticipant = participant.id;
201
+            state.pinnedParticipant = id;
191 202
         }
192 203
 
193 204
         if (participant.dominantSpeaker) {
194 205
             if (dominantSpeaker) {
195 206
                 _updateParticipantProperty(state, dominantSpeaker, 'dominantSpeaker', false);
196 207
             }
197
-            state.dominantSpeaker = participant.id;
208
+            state.dominantSpeaker = id;
198 209
         }
199 210
 
200 211
         const isModerator = isParticipantModerator(participant);
@@ -213,10 +224,21 @@ ReducerRegistry.register('features/base/participants', (state = DEFAULT_STATE, a
213 224
             };
214 225
         }
215 226
 
216
-        state.remote.set(participant.id, participant);
227
+        state.remote.set(id, participant);
228
+
229
+        // Insert the new participant.
230
+        const displayName = name
231
+            ?? (typeof interfaceConfig === 'object' ? interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME : 'Fellow Jitser');
232
+        const sortedRemoteParticipants = Array.from(state.sortedRemoteParticipants);
233
+
234
+        sortedRemoteParticipants.push([ id, displayName ]);
235
+        sortedRemoteParticipants.sort((a, b) => a[1].localeCompare(b[1]));
217 236
 
218
-        if (participant.isFakeParticipant) {
219
-            state.fakeParticipants.set(participant.id, participant);
237
+        // The sort order of participants is preserved since Map remembers the original insertion order of the keys.
238
+        state.sortedRemoteParticipants = new Map(sortedRemoteParticipants);
239
+
240
+        if (isFakeParticipant) {
241
+            state.fakeParticipants.set(id, participant);
220 242
         }
221 243
 
222 244
         return { ...state };
@@ -242,6 +264,8 @@ ReducerRegistry.register('features/base/participants', (state = DEFAULT_STATE, a
242 264
             return state;
243 265
         }
244 266
 
267
+        state.sortedRemoteParticipants.delete(id);
268
+
245 269
         if (!state.everyoneIsModerator && !isParticipantModerator(oldParticipant)) {
246 270
             state.everyoneIsModerator = _isEveryoneModerator(state);
247 271
         }
@@ -272,6 +296,9 @@ ReducerRegistry.register('features/base/participants', (state = DEFAULT_STATE, a
272 296
             state.dominantSpeaker = undefined;
273 297
         }
274 298
 
299
+        // Remove the participant from the list of speakers.
300
+        state.speakersList = state.speakersList.filter(speaker => speaker !== id);
301
+
275 302
         if (pinnedParticipant === id) {
276 303
             state.pinnedParticipant = undefined;
277 304
         }

+ 9
- 0
react/features/filmstrip/actionTypes.js Parādīt failu

@@ -50,6 +50,15 @@ export const SET_TILE_VIEW_DIMENSIONS = 'SET_TILE_VIEW_DIMENSIONS';
50 50
  */
51 51
 export const SET_HORIZONTAL_VIEW_DIMENSIONS = 'SET_HORIZONTAL_VIEW_DIMENSIONS';
52 52
 
53
+/**
54
+ * The type of (redux) action which sets the reordered list of the remote participants in the filmstrip.
55
+ * {
56
+ *      type: SET_REMOTE_PARTICIPANTS,
57
+ *      participants: Array<string>
58
+ * }
59
+ */
60
+export const SET_REMOTE_PARTICIPANTS = 'SET_REMOTE_PARTICIPANTS';
61
+
53 62
 /**
54 63
  * The type of (redux) action which sets the dimensions of the thumbnails in vertical view.
55 64
  *

+ 18
- 0
react/features/filmstrip/actions.web.js Parādīt failu

@@ -5,6 +5,7 @@ import { getLocalParticipant, getRemoteParticipants, pinParticipant } from '../b
5 5
 
6 6
 import {
7 7
     SET_HORIZONTAL_VIEW_DIMENSIONS,
8
+    SET_REMOTE_PARTICIPANTS,
8 9
     SET_TILE_VIEW_DIMENSIONS,
9 10
     SET_VERTICAL_VIEW_DIMENSIONS,
10 11
     SET_VISIBLE_REMOTE_PARTICIPANTS,
@@ -25,6 +26,23 @@ import {
25 26
     calculateThumbnailSizeForVerticalView
26 27
 } from './functions';
27 28
 
29
+/**
30
+ * Sets the list of the reordered remote participants based on which the visible participants in the filmstrip will be
31
+ * determined.
32
+ *
33
+ * @param {Array<string>} participants - The list of the remote participant endpoint IDs.
34
+ * @returns {{
35
+        type: SET_REMOTE_PARTICIPANTS,
36
+        participants: Array<string>
37
+    }}
38
+ */
39
+export function setRemoteParticipants(participants: Array<string>) {
40
+    return {
41
+        type: SET_REMOTE_PARTICIPANTS,
42
+        participants
43
+    };
44
+}
45
+
28 46
 /**
29 47
  * Sets the dimensions of the tile view grid.
30 48
  *

+ 7
- 4
react/features/filmstrip/components/web/Filmstrip.js Parādīt failu

@@ -269,11 +269,11 @@ class Filmstrip extends PureComponent <Props> {
269 269
             return `empty-${index}`;
270 270
         }
271 271
 
272
-        if (index === _remoteParticipantsLength) {
272
+        if (index === 0) {
273 273
             return 'local';
274 274
         }
275 275
 
276
-        return _remoteParticipants[index];
276
+        return _remoteParticipants[index - 1];
277 277
     }
278 278
 
279 279
     _onListItemsRendered: Object => void;
@@ -287,7 +287,7 @@ class Filmstrip extends PureComponent <Props> {
287 287
     _onListItemsRendered({ visibleStartIndex, visibleStopIndex }) {
288 288
         const { dispatch } = this.props;
289 289
 
290
-        dispatch(setVisibleRemoteParticipants(visibleStartIndex, visibleStopIndex));
290
+        dispatch(setVisibleRemoteParticipants(visibleStartIndex, visibleStopIndex + 1));
291 291
     }
292 292
 
293 293
     _onGridItemsRendered: Object => void;
@@ -305,9 +305,12 @@ class Filmstrip extends PureComponent <Props> {
305 305
         visibleRowStopIndex
306 306
     }) {
307 307
         const { _columns, dispatch } = this.props;
308
-        const startIndex = (visibleRowStartIndex * _columns) + visibleColumnStartIndex;
308
+        let startIndex = (visibleRowStartIndex * _columns) + visibleColumnStartIndex;
309 309
         const endIndex = (visibleRowStopIndex * _columns) + visibleColumnStopIndex;
310 310
 
311
+        // In tile view, the start index needs to be offset by 1 because the first participant is the local
312
+        // participant.
313
+        startIndex = startIndex > 0 ? startIndex - 1 : 0;
311 314
         dispatch(setVisibleRemoteParticipants(startIndex, endIndex));
312 315
     }
313 316
 

+ 3
- 2
react/features/filmstrip/components/web/ThumbnailWrapper.js Parādīt failu

@@ -126,7 +126,8 @@ function _mapStateToProps(state, ownProps) {
126 126
             return {};
127 127
         }
128 128
 
129
-        if (index === remoteParticipantsLength) {
129
+        // Make the local participant as the first thumbnail (top left corner) in tile view.
130
+        if (index === 0) {
130 131
             return {
131 132
                 _participantID: 'local',
132 133
                 _horizontalOffset: horizontalOffset
@@ -134,7 +135,7 @@ function _mapStateToProps(state, ownProps) {
134 135
         }
135 136
 
136 137
         return {
137
-            _participantID: remoteParticipants[index],
138
+            _participantID: remoteParticipants[index - 1],
138 139
             _horizontalOffset: horizontalOffset
139 140
         };
140 141
     }

+ 34
- 0
react/features/filmstrip/functions.web.js Parādīt failu

@@ -16,6 +16,7 @@ import {
16 16
     isRemoteTrackMuted
17 17
 } from '../base/tracks/functions';
18 18
 
19
+import { setRemoteParticipants } from './actions.web';
19 20
 import {
20 21
     ASPECT_RATIO_BREAKPOINT,
21 22
     DISPLAY_AVATAR,
@@ -265,3 +266,36 @@ export function computeDisplayMode(input: Object) {
265 266
     // check hovering and change state to avatar with name
266 267
     return isHovered ? DISPLAY_AVATAR_WITH_NAME : DISPLAY_AVATAR;
267 268
 }
269
+
270
+/**
271
+ * Computes the reorderd list of the remote participants.
272
+ *
273
+ * @param {*} store - The redux store.
274
+ * @returns {void}
275
+ * @private
276
+ */
277
+export function updateRemoteParticipants(store: Object) {
278
+    const state = store.getState();
279
+    const { fakeParticipants, sortedRemoteParticipants, speakersList } = state['features/base/participants'];
280
+    const { remoteScreenShares } = state['features/video-layout'];
281
+    const screenShares = (remoteScreenShares || []).slice();
282
+    let speakers = (speakersList || []).slice();
283
+    const remoteParticipants = new Map(sortedRemoteParticipants);
284
+    const sharedVideos = fakeParticipants ? Array.from(fakeParticipants.keys()) : [];
285
+
286
+    for (const screenshare of screenShares) {
287
+        remoteParticipants.delete(screenshare);
288
+        speakers = speakers.filter(speaker => speaker !== screenshare);
289
+    }
290
+    for (const sharedVideo of sharedVideos) {
291
+        remoteParticipants.delete(sharedVideo);
292
+        speakers = speakers.filter(speaker => speaker !== sharedVideo);
293
+    }
294
+    for (const speaker of speakers) {
295
+        remoteParticipants.delete(speaker);
296
+    }
297
+    const reorderedParticipants
298
+        = [ ...screenShares.reverse(), ...sharedVideos, ...speakers, ...Array.from(remoteParticipants.keys()) ];
299
+
300
+    store.dispatch(setRemoteParticipants(reorderedParticipants));
301
+}

+ 35
- 2
react/features/filmstrip/middleware.web.js Parādīt failu

@@ -1,6 +1,7 @@
1 1
 // @flow
2 2
 
3 3
 import VideoLayout from '../../../modules/UI/videolayout/VideoLayout';
4
+import { PARTICIPANT_JOINED, PARTICIPANT_LEFT } from '../base/participants';
4 5
 import { MiddlewareRegistry } from '../base/redux';
5 6
 import { CLIENT_RESIZED } from '../base/responsive-ui';
6 7
 import { SETTINGS_UPDATED } from '../base/settings';
@@ -9,8 +10,13 @@ import {
9 10
     LAYOUTS
10 11
 } from '../video-layout';
11 12
 
12
-import { setHorizontalViewDimensions, setTileViewDimensions, setVerticalViewDimensions } from './actions.web';
13
-
13
+import {
14
+    setHorizontalViewDimensions,
15
+    setRemoteParticipants,
16
+    setTileViewDimensions,
17
+    setVerticalViewDimensions
18
+} from './actions.web';
19
+import { updateRemoteParticipants } from './functions.web';
14 20
 import './subscriber.web';
15 21
 
16 22
 /**
@@ -41,6 +47,14 @@ MiddlewareRegistry.register(store => next => action => {
41 47
         }
42 48
         break;
43 49
     }
50
+    case PARTICIPANT_JOINED: {
51
+        updateRemoteParticipants(store);
52
+        break;
53
+    }
54
+    case PARTICIPANT_LEFT: {
55
+        _updateRemoteParticipantsOnLeave(store, action.participant?.id);
56
+        break;
57
+    }
44 58
     case SETTINGS_UPDATED: {
45 59
         if (typeof action.settings?.localFlipX === 'boolean') {
46 60
             // TODO: This needs to be removed once the large video is Reactified.
@@ -53,3 +67,22 @@ MiddlewareRegistry.register(store => next => action => {
53 67
     return result;
54 68
 });
55 69
 
70
+/**
71
+ * Private helper to calculate the reordered list of remote participants when a participant leaves.
72
+ *
73
+ * @param {*} store - The redux store.
74
+ * @param {string} participantId - The endpoint id of the participant leaving the call.
75
+ * @returns {void}
76
+ * @private
77
+ */
78
+function _updateRemoteParticipantsOnLeave(store, participantId = null) {
79
+    if (!participantId) {
80
+        return;
81
+    }
82
+    const state = store.getState();
83
+    const { remoteParticipants } = state['features/filmstrip'];
84
+    const reorderedParticipants = new Set(remoteParticipants);
85
+
86
+    reorderedParticipants.delete(participantId)
87
+        && store.dispatch(setRemoteParticipants(Array.from(reorderedParticipants)));
88
+}

+ 20
- 45
react/features/filmstrip/reducer.js Parādīt failu

@@ -1,12 +1,13 @@
1 1
 // @flow
2 2
 
3
-import { PARTICIPANT_JOINED, PARTICIPANT_LEFT } from '../base/participants';
3
+import { PARTICIPANT_LEFT } from '../base/participants';
4 4
 import { ReducerRegistry } from '../base/redux';
5 5
 
6 6
 import {
7 7
     SET_FILMSTRIP_ENABLED,
8 8
     SET_FILMSTRIP_VISIBLE,
9 9
     SET_HORIZONTAL_VIEW_DIMENSIONS,
10
+    SET_REMOTE_PARTICIPANTS,
10 11
     SET_TILE_VIEW_DIMENSIONS,
11 12
     SET_VERTICAL_VIEW_DIMENSIONS,
12 13
     SET_VISIBLE_REMOTE_PARTICIPANTS,
@@ -40,8 +41,8 @@ const DEFAULT_STATE = {
40 41
     /**
41 42
      * The ordered IDs of the remote participants displayed in the filmstrip.
42 43
      *
43
-     * NOTE: Currently the order will match the one from the base/participants array. But this is good initial step for
44
-     * reordering the remote participants.
44
+     * @public
45
+     * @type {Array<string>}
45 46
      */
46 47
     remoteParticipants: [],
47 48
 
@@ -78,21 +79,20 @@ const DEFAULT_STATE = {
78 79
     visibleParticipantsEndIndex: 0,
79 80
 
80 81
     /**
81
-     * The visible participants in the filmstrip.
82
+     * The start index in the remote participants array that is visible in the filmstrip.
82 83
      *
83 84
      * @public
84
-     * @type {Array<string>}
85
+     * @type {number}
85 86
      */
86
-    visibleParticipants: [],
87
-
87
+    visibleParticipantsStartIndex: 0,
88 88
 
89 89
     /**
90
-     * The start index in the remote participants array that is visible in the filmstrip.
90
+     * The visible remote participants in the filmstrip.
91 91
      *
92 92
      * @public
93
-     * @type {number}
93
+     * @type {Set<string>}
94 94
      */
95
-    visibleParticipantsStartIndex: 0
95
+    visibleRemoteParticipants: new Set()
96 96
 };
97 97
 
98 98
 ReducerRegistry.register(
@@ -116,6 +116,14 @@ ReducerRegistry.register(
116 116
                 ...state,
117 117
                 horizontalViewDimensions: action.dimensions
118 118
             };
119
+        case SET_REMOTE_PARTICIPANTS: {
120
+            const { visibleParticipantsStartIndex: startIndex, visibleParticipantsEndIndex: endIndex } = state;
121
+
122
+            state.remoteParticipants = action.participants;
123
+            state.visibleRemoteParticipants = new Set(state.remoteParticipants.slice(startIndex, endIndex));
124
+
125
+            return { ...state };
126
+        }
119 127
         case SET_TILE_VIEW_DIMENSIONS:
120 128
             return {
121 129
                 ...state,
@@ -138,27 +146,13 @@ ReducerRegistry.register(
138 146
                     [action.participantId]: action.volume
139 147
                 }
140 148
             };
141
-        case SET_VISIBLE_REMOTE_PARTICIPANTS:
149
+        case SET_VISIBLE_REMOTE_PARTICIPANTS: {
142 150
             return {
143 151
                 ...state,
144 152
                 visibleParticipantsStartIndex: action.startIndex,
145 153
                 visibleParticipantsEndIndex: action.endIndex,
146
-                visibleParticipants: state.remoteParticipants.slice(action.startIndex, action.endIndex + 1)
154
+                visibleRemoteParticipants: new Set(state.remoteParticipants.slice(action.startIndex, action.endIndex))
147 155
             };
148
-        case PARTICIPANT_JOINED: {
149
-            const { id, local } = action.participant;
150
-
151
-            if (!local) {
152
-                state.remoteParticipants = [ ...state.remoteParticipants, id ];
153
-
154
-                const { visibleParticipantsStartIndex: startIndex, visibleParticipantsEndIndex: endIndex } = state;
155
-
156
-                if (state.remoteParticipants.length - 1 <= endIndex) {
157
-                    state.visibleParticipants = state.remoteParticipants.slice(startIndex, endIndex + 1);
158
-                }
159
-            }
160
-
161
-            return state;
162 156
         }
163 157
         case PARTICIPANT_LEFT: {
164 158
             const { id, local } = action.participant;
@@ -166,25 +160,6 @@ ReducerRegistry.register(
166 160
             if (local) {
167 161
                 return state;
168 162
             }
169
-
170
-            let removedParticipantIndex = 0;
171
-
172
-            state.remoteParticipants = state.remoteParticipants.filter((participantId, index) => {
173
-                if (participantId === id) {
174
-                    removedParticipantIndex = index;
175
-
176
-                    return false;
177
-                }
178
-
179
-                return true;
180
-            });
181
-
182
-            const { visibleParticipantsStartIndex: startIndex, visibleParticipantsEndIndex: endIndex } = state;
183
-
184
-            if (removedParticipantIndex >= startIndex && removedParticipantIndex <= endIndex) {
185
-                state.visibleParticipants = state.remoteParticipants.slice(startIndex, endIndex + 1);
186
-            }
187
-
188 163
             delete state.participantsVolume[id];
189 164
 
190 165
             return state;

+ 39
- 1
react/features/filmstrip/subscriber.web.js Parādīt failu

@@ -8,13 +8,18 @@ import { getParticipantsPaneOpen } from '../participants-pane/functions';
8 8
 import { setOverflowDrawer } from '../toolbox/actions.web';
9 9
 import { getCurrentLayout, getTileViewGridDimensions, shouldDisplayTileView, LAYOUTS } from '../video-layout';
10 10
 
11
-import { setHorizontalViewDimensions, setTileViewDimensions, setVerticalViewDimensions } from './actions.web';
11
+import {
12
+    setHorizontalViewDimensions,
13
+    setTileViewDimensions,
14
+    setVerticalViewDimensions
15
+} from './actions.web';
12 16
 import {
13 17
     ASPECT_RATIO_BREAKPOINT,
14 18
     DISPLAY_DRAWER_THRESHOLD,
15 19
     SINGLE_COLUMN_BREAKPOINT,
16 20
     TWO_COLUMN_BREAKPOINT
17 21
 } from './constants';
22
+import { updateRemoteParticipants } from './functions.web';
18 23
 
19 24
 /**
20 25
  * 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(
153 158
             store.dispatch(setTileViewDimensions(gridDimensions));
154 159
         }
155 160
     });
161
+
162
+/**
163
+ * Listens for changes to the screensharing status of the remote participants to recompute the reordered list of the
164
+ * remote endpoints.
165
+ */
166
+StateListenerRegistry.register(
167
+    /* selector */ state => state['features/video-layout'].remoteScreenShares,
168
+    /* listener */ (remoteScreenShares, store) => updateRemoteParticipants(store));
169
+
170
+/**
171
+ * Listens for changes to the dominant speaker to recompute the reordered list of the remote endpoints.
172
+ */
173
+StateListenerRegistry.register(
174
+    /* selector */ state => state['features/base/participants'].dominantSpeaker,
175
+    /* listener */ (dominantSpeaker, store) => _reorderDominantSpeakers(store));
176
+
177
+/**
178
+ * Private helper function that reorders the remote participants based on dominant speaker changes.
179
+ *
180
+ * @param {*} store - The redux store.
181
+ * @returns {void}
182
+ * @private
183
+ */
184
+function _reorderDominantSpeakers(store) {
185
+    const state = store.getState();
186
+    const { dominantSpeaker, local } = state['features/base/participants'];
187
+    const { visibleRemoteParticipants } = state['features/filmstrip'];
188
+
189
+    // Reorder the participants if the new dominant speaker is currently not visible.
190
+    if (dominantSpeaker !== local?.id && !visibleRemoteParticipants.has(dominantSpeaker)) {
191
+        updateRemoteParticipants(store);
192
+    }
193
+}

+ 9
- 9
react/features/video-quality/subscriber.js Parādīt failu

@@ -22,8 +22,8 @@ declare var APP: Object;
22 22
  * scrolling through the thumbnails prompting updates to the selected endpoints.
23 23
  */
24 24
 StateListenerRegistry.register(
25
-    /* selector */ state => state['features/filmstrip'].visibleParticipants,
26
-    /* listener */ debounce((visibleParticipants, store) => {
25
+    /* selector */ state => state['features/filmstrip'].visibleRemoteParticipants,
26
+    /* listener */ debounce((visibleRemoteParticipants, store) => {
27 27
         _updateReceiverVideoConstraints(store);
28 28
     }, 100));
29 29
 
@@ -191,11 +191,11 @@ function _updateReceiverVideoConstraints({ getState }) {
191 191
     const { maxReceiverVideoQuality, preferredVideoQuality } = state['features/video-quality'];
192 192
     const { participantId: largeVideoParticipantId } = state['features/large-video'];
193 193
     const maxFrameHeight = Math.min(maxReceiverVideoQuality, preferredVideoQuality);
194
-    let { visibleParticipants } = state['features/filmstrip'];
194
+    let { visibleRemoteParticipants } = state['features/filmstrip'];
195 195
 
196 196
     // TODO: implement this on mobile.
197 197
     if (navigator.product === 'ReactNative') {
198
-        visibleParticipants = Array.from(state['features/base/participants'].remote.keys());
198
+        visibleRemoteParticipants = new Set(Array.from(state['features/base/participants'].remote.keys()));
199 199
     }
200 200
 
201 201
     const receiverConstraints = {
@@ -208,22 +208,22 @@ function _updateReceiverVideoConstraints({ getState }) {
208 208
 
209 209
     // Tile view.
210 210
     if (shouldDisplayTileView(state)) {
211
-        if (!visibleParticipants?.length) {
211
+        if (!visibleRemoteParticipants?.size) {
212 212
             return;
213 213
         }
214 214
 
215
-        visibleParticipants.forEach(participantId => {
215
+        visibleRemoteParticipants.forEach(participantId => {
216 216
             receiverConstraints.constraints[participantId] = { 'maxHeight': maxFrameHeight };
217 217
         });
218 218
 
219 219
     // Stage view.
220 220
     } else {
221
-        if (!visibleParticipants?.length && !largeVideoParticipantId) {
221
+        if (!visibleRemoteParticipants?.size && !largeVideoParticipantId) {
222 222
             return;
223 223
         }
224 224
 
225
-        if (visibleParticipants?.length > 0) {
226
-            visibleParticipants.forEach(participantId => {
225
+        if (visibleRemoteParticipants?.size > 0) {
226
+            visibleRemoteParticipants.forEach(participantId => {
227 227
                 receiverConstraints.constraints[participantId] = { 'maxHeight': VIDEO_QUALITY_LEVELS.LOW };
228 228
             });
229 229
         }

Notiek ielāde…
Atcelt
Saglabāt