Browse Source

fix(filmstrip): Move thumbnails reordering behind a config.js flag.

enableThumbnailReordering flag (enabled by default) will be used to check if the thumbnails needs to be reodred in the UI.
master
Jaya Allamsetty 3 years ago
parent
commit
7827c3d1ad

+ 60
- 35
react/features/base/participants/reducer.js View File

61
     pinnedParticipant: undefined,
61
     pinnedParticipant: undefined,
62
     remote: new Map(),
62
     remote: new Map(),
63
     sortedRemoteParticipants: new Map(),
63
     sortedRemoteParticipants: new Map(),
64
-    speakersList: []
64
+    speakersList: new Map()
65
 };
65
 };
66
 
66
 
67
 /**
67
 /**
96
     case DOMINANT_SPEAKER_CHANGED: {
96
     case DOMINANT_SPEAKER_CHANGED: {
97
         const { participant } = action;
97
         const { participant } = action;
98
         const { id, previousSpeakers = [] } = participant;
98
         const { id, previousSpeakers = [] } = participant;
99
-        const { dominantSpeaker, local } = state;
100
-        const speakersList = [];
99
+        const { dominantSpeaker, local, speakersList } = state;
100
+        const newSpeakers = [ id, ...previousSpeakers ];
101
+        const sortedSpeakersList = Array.from(speakersList);
101
 
102
 
102
         // Update the speakers list.
103
         // Update the speakers list.
103
-        id !== local?.id && speakersList.push(id);
104
-        speakersList.push(...previousSpeakers.filter(p => p !== local?.id));
104
+        for (const speaker of newSpeakers) {
105
+            if (!state.speakersList.has(speaker) && speaker !== local?.id) {
106
+                const remoteParticipant = state.remote.get(speaker);
107
+
108
+                remoteParticipant && sortedSpeakersList.push([ speaker, _getDisplayName(remoteParticipant.name) ]);
109
+            }
110
+        }
111
+
112
+        // Also check if any of the existing speakers have been kicked off the list.
113
+        for (const existingSpeaker of sortedSpeakersList.keys()) {
114
+            if (!newSpeakers.find(s => s === existingSpeaker)) {
115
+                sortedSpeakersList.filter(sortedSpeaker => sortedSpeaker[0] !== existingSpeaker);
116
+            }
117
+        }
118
+
119
+        // Keep the remote speaker list sorted alphabetically.
120
+        sortedSpeakersList.sort((a, b) => a[1].localeCompare(b[1]));
105
 
121
 
106
         // Only one dominant speaker is allowed.
122
         // Only one dominant speaker is allowed.
107
         if (dominantSpeaker) {
123
         if (dominantSpeaker) {
112
             return {
128
             return {
113
                 ...state,
129
                 ...state,
114
                 dominantSpeaker: id,
130
                 dominantSpeaker: id,
115
-                speakersList
131
+                speakersList: new Map(sortedSpeakersList)
116
             };
132
             };
117
         }
133
         }
118
 
134
 
227
         state.remote.set(id, participant);
243
         state.remote.set(id, participant);
228
 
244
 
229
         // Insert the new participant.
245
         // Insert the new participant.
230
-        const displayName = name
231
-            ?? (typeof interfaceConfig === 'object' ? interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME : 'Fellow Jitser');
246
+        const displayName = _getDisplayName(name);
232
         const sortedRemoteParticipants = Array.from(state.sortedRemoteParticipants);
247
         const sortedRemoteParticipants = Array.from(state.sortedRemoteParticipants);
233
 
248
 
234
         sortedRemoteParticipants.push([ id, displayName ]);
249
         sortedRemoteParticipants.push([ id, displayName ]);
297
         }
312
         }
298
 
313
 
299
         // Remove the participant from the list of speakers.
314
         // Remove the participant from the list of speakers.
300
-        state.speakersList = state.speakersList.filter(speaker => speaker !== id);
315
+        state.speakersList.has(id) && state.speakersList.delete(id);
301
 
316
 
302
         if (pinnedParticipant === id) {
317
         if (pinnedParticipant === id) {
303
             state.pinnedParticipant = undefined;
318
             state.pinnedParticipant = undefined;
314
     return state;
329
     return state;
315
 });
330
 });
316
 
331
 
332
+/**
333
+ * Returns the participant's display name, default string if display name is not set on the participant.
334
+ *
335
+ * @param {string} name - The display name of the participant.
336
+ * @returns {string}
337
+ */
338
+ function _getDisplayName(name) {
339
+    return name
340
+        ?? (typeof interfaceConfig === 'object' ? interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME : 'Fellow Jitser');
341
+}
342
+
317
 /**
343
 /**
318
  * Loops trough the participants in the state in order to check if all participants are moderators.
344
  * Loops trough the participants in the state in order to check if all participants are moderators.
319
  *
345
  *
335
     return false;
361
     return false;
336
 }
362
 }
337
 
363
 
338
-
339
-/**
340
- * Updates a specific property for a participant.
341
- *
342
- * @param {State} state - The redux state.
343
- * @param {string} id - The ID of the participant.
344
- * @param {string} property - The property to update.
345
- * @param {*} value - The new value.
346
- * @returns {boolean} - True if a participant was updated and false otherwise.
347
- */
348
-function _updateParticipantProperty(state, id, property, value) {
349
-    const { remote, local } = state;
350
-
351
-    if (remote.has(id)) {
352
-        remote.set(id, set(remote.get(id), property, value));
353
-
354
-        return true;
355
-    } else if (local?.id === id) {
356
-        state.local = set(local, property, value);
357
-
358
-        return true;
359
-    }
360
-
361
-    return false;
362
-}
363
-
364
 /**
364
 /**
365
  * Reducer function for a single participant.
365
  * Reducer function for a single participant.
366
  *
366
  *
458
         role: role || PARTICIPANT_ROLE.NONE
458
         role: role || PARTICIPANT_ROLE.NONE
459
     };
459
     };
460
 }
460
 }
461
+
462
+/**
463
+ * Updates a specific property for a participant.
464
+ *
465
+ * @param {State} state - The redux state.
466
+ * @param {string} id - The ID of the participant.
467
+ * @param {string} property - The property to update.
468
+ * @param {*} value - The new value.
469
+ * @returns {boolean} - True if a participant was updated and false otherwise.
470
+ */
471
+ function _updateParticipantProperty(state, id, property, value) {
472
+    const { remote, local } = state;
473
+
474
+    if (remote.has(id)) {
475
+        remote.set(id, set(remote.get(id), property, value));
476
+
477
+        return true;
478
+    } else if (local?.id === id) {
479
+        state.local = set(local, property, value);
480
+
481
+        return true;
482
+    }
483
+
484
+    return false;
485
+}

+ 1
- 1
react/features/filmstrip/actions.web.js View File

132
         const state = getState();
132
         const state = getState();
133
         const { id: localId } = getLocalParticipant(state);
133
         const { id: localId } = getLocalParticipant(state);
134
 
134
 
135
-        // Use the reordered list of participants.
135
+        // Use the list that correctly represents the current order of the participants as visible in the UI.
136
         const { remoteParticipants } = state['features/filmstrip'];
136
         const { remoteParticipants } = state['features/filmstrip'];
137
         const participants = [ localId, ...remoteParticipants ];
137
         const participants = [ localId, ...remoteParticipants ];
138
         const { id, pinned } = getParticipantById(state, participants[n]);
138
         const { id, pinned } = getParticipantById(state, participants[n]);

+ 47
- 10
react/features/filmstrip/components/web/Filmstrip.js View File

94
      */
94
      */
95
     _thumbnailWidth: number,
95
     _thumbnailWidth: number,
96
 
96
 
97
+    /**
98
+     * Flag that indicates whether the thumbnails will be reordered.
99
+     */
100
+    _thumbnailsReordered: Boolean,
101
+
97
     /**
102
     /**
98
      * Additional CSS class names to add to the container of all the thumbnails.
103
      * Additional CSS class names to add to the container of all the thumbnails.
99
      */
104
      */
222
         );
227
         );
223
     }
228
     }
224
 
229
 
230
+    /**
231
+     * Calculates the start and stop indices based on whether the thumbnails need to be reordered in the filmstrip.
232
+     *
233
+     * @param {number} startIndex - The start index.
234
+     * @param {number} stopIndex - The stop index.
235
+     * @returns {Object}
236
+     */
237
+    _calculateIndices(startIndex, stopIndex) {
238
+        const { _currentLayout, _thumbnailsReordered } = this.props;
239
+        let start = startIndex;
240
+        let stop = stopIndex;
241
+
242
+        if (_thumbnailsReordered) {
243
+            // In tile view, the start index needs to be offset by 1 because the first thumbnail is that of the local
244
+            // endpoint. The remote participants start from index 1.
245
+            if (_currentLayout === LAYOUTS.TILE_VIEW) {
246
+                start = startIndex > 0 ? startIndex - 1 : 0;
247
+                stop = stopIndex - 1;
248
+            }
249
+        }
250
+
251
+        return {
252
+            startIndex: start,
253
+            stopIndex: stop
254
+        };
255
+    }
256
+
225
     _onTabIn: () => void;
257
     _onTabIn: () => void;
226
 
258
 
227
     /**
259
     /**
262
      * @returns {string} - The key.
294
      * @returns {string} - The key.
263
      */
295
      */
264
     _gridItemKey({ columnIndex, rowIndex }) {
296
     _gridItemKey({ columnIndex, rowIndex }) {
265
-        const { _columns, _remoteParticipants, _remoteParticipantsLength } = this.props;
297
+        const { _columns, _remoteParticipants, _remoteParticipantsLength, _thumbnailsReordered } = this.props;
266
         const index = (rowIndex * _columns) + columnIndex;
298
         const index = (rowIndex * _columns) + columnIndex;
267
 
299
 
300
+        // When the thumbnails are reordered, local participant is inserted at index 0.
301
+        const localIndex = _thumbnailsReordered ? 0 : _remoteParticipantsLength;
302
+        const remoteIndex = _thumbnailsReordered ? index - 1 : index;
303
+
268
         if (index > _remoteParticipantsLength) {
304
         if (index > _remoteParticipantsLength) {
269
             return `empty-${index}`;
305
             return `empty-${index}`;
270
         }
306
         }
271
 
307
 
272
-        if (index === 0) {
308
+        if (index === localIndex) {
273
             return 'local';
309
             return 'local';
274
         }
310
         }
275
 
311
 
276
-        return _remoteParticipants[index - 1];
312
+        return _remoteParticipants[remoteIndex];
277
     }
313
     }
278
 
314
 
279
     _onListItemsRendered: Object => void;
315
     _onListItemsRendered: Object => void;
286
      */
322
      */
287
     _onListItemsRendered({ visibleStartIndex, visibleStopIndex }) {
323
     _onListItemsRendered({ visibleStartIndex, visibleStopIndex }) {
288
         const { dispatch } = this.props;
324
         const { dispatch } = this.props;
325
+        const { startIndex, stopIndex } = this._calculateIndices(visibleStartIndex, visibleStopIndex);
289
 
326
 
290
-        dispatch(setVisibleRemoteParticipants(visibleStartIndex, visibleStopIndex + 1));
327
+        dispatch(setVisibleRemoteParticipants(startIndex, stopIndex));
291
     }
328
     }
292
 
329
 
293
     _onGridItemsRendered: Object => void;
330
     _onGridItemsRendered: Object => void;
305
         visibleRowStopIndex
342
         visibleRowStopIndex
306
     }) {
343
     }) {
307
         const { _columns, dispatch } = this.props;
344
         const { _columns, dispatch } = this.props;
308
-        let startIndex = (visibleRowStartIndex * _columns) + visibleColumnStartIndex;
309
-        const endIndex = (visibleRowStopIndex * _columns) + visibleColumnStopIndex;
345
+        const start = (visibleRowStartIndex * _columns) + visibleColumnStartIndex;
346
+        const stop = (visibleRowStopIndex * _columns) + visibleColumnStopIndex;
347
+        const { startIndex, stopIndex } = this._calculateIndices(start, stop);
310
 
348
 
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;
314
-        dispatch(setVisibleRemoteParticipants(startIndex, endIndex));
349
+        dispatch(setVisibleRemoteParticipants(startIndex, stopIndex));
315
     }
350
     }
316
 
351
 
317
     /**
352
     /**
493
  */
528
  */
494
 function _mapStateToProps(state) {
529
 function _mapStateToProps(state) {
495
     const toolbarButtons = getToolbarButtons(state);
530
     const toolbarButtons = getToolbarButtons(state);
531
+    const { enableThumbnailReordering = true } = state['features/base/config'];
496
     const { visible, remoteParticipants } = state['features/filmstrip'];
532
     const { visible, remoteParticipants } = state['features/filmstrip'];
497
     const reduceHeight = state['features/toolbox'].visible && toolbarButtons.length;
533
     const reduceHeight = state['features/toolbox'].visible && toolbarButtons.length;
498
     const remoteVideosVisible = shouldRemoteVideosBeVisible(state);
534
     const remoteVideosVisible = shouldRemoteVideosBeVisible(state);
565
         _rows: gridDimensions.rows,
601
         _rows: gridDimensions.rows,
566
         _thumbnailWidth: _thumbnailSize?.width,
602
         _thumbnailWidth: _thumbnailSize?.width,
567
         _thumbnailHeight: _thumbnailSize?.height,
603
         _thumbnailHeight: _thumbnailSize?.height,
604
+        _thumbnailsReordered: enableThumbnailReordering,
568
         _videosClassName: videosClassName,
605
         _videosClassName: videosClassName,
569
         _visible: visible,
606
         _visible: visible,
570
         _isToolboxVisible: isToolboxVisible(state)
607
         _isToolboxVisible: isToolboxVisible(state)

+ 7
- 3
react/features/filmstrip/components/web/ThumbnailWrapper.js View File

104
     const _currentLayout = getCurrentLayout(state);
104
     const _currentLayout = getCurrentLayout(state);
105
     const { remoteParticipants } = state['features/filmstrip'];
105
     const { remoteParticipants } = state['features/filmstrip'];
106
     const remoteParticipantsLength = remoteParticipants.length;
106
     const remoteParticipantsLength = remoteParticipants.length;
107
+    const { enableThumbnailReordering = true } = state['features/base/config'];
107
 
108
 
108
     if (_currentLayout === LAYOUTS.TILE_VIEW) {
109
     if (_currentLayout === LAYOUTS.TILE_VIEW) {
109
         const { columnIndex, rowIndex } = ownProps;
110
         const { columnIndex, rowIndex } = ownProps;
126
             return {};
127
             return {};
127
         }
128
         }
128
 
129
 
129
-        // Make the local participant as the first thumbnail (top left corner) in tile view.
130
-        if (index === 0) {
130
+        // When the thumbnails are reordered, local participant is inserted at index 0.
131
+        const localIndex = enableThumbnailReordering ? 0 : remoteParticipantsLength;
132
+        const remoteIndex = enableThumbnailReordering ? index - 1 : index;
133
+
134
+        if (index === localIndex) {
131
             return {
135
             return {
132
                 _participantID: 'local',
136
                 _participantID: 'local',
133
                 _horizontalOffset: horizontalOffset
137
                 _horizontalOffset: horizontalOffset
135
         }
139
         }
136
 
140
 
137
         return {
141
         return {
138
-            _participantID: remoteParticipants[index - 1],
142
+            _participantID: remoteParticipants[remoteIndex],
139
             _horizontalOffset: horizontalOffset
143
             _horizontalOffset: horizontalOffset
140
         };
144
         };
141
     }
145
     }

+ 28
- 7
react/features/filmstrip/functions.any.js View File

6
  * Computes the reorderd list of the remote participants.
6
  * Computes the reorderd list of the remote participants.
7
  *
7
  *
8
  * @param {*} store - The redux store.
8
  * @param {*} store - The redux store.
9
+ * @param {string} participantId - The endpoint id of the participant that joined the call.
9
  * @returns {void}
10
  * @returns {void}
10
  * @private
11
  * @private
11
  */
12
  */
12
-export function updateRemoteParticipants(store: Object) {
13
+ export function updateRemoteParticipants(store: Object, participantId: ?number) {
13
     const state = store.getState();
14
     const state = store.getState();
15
+    const { enableThumbnailReordering = true } = state['features/base/config'];
16
+    let reorderedParticipants = [];
17
+
18
+    if (!enableThumbnailReordering) {
19
+        if (participantId) {
20
+            const { remoteParticipants } = state['features/filmstrip'];
21
+
22
+            reorderedParticipants = [ ...remoteParticipants, participantId ];
23
+            store.dispatch(setRemoteParticipants(reorderedParticipants));
24
+        }
25
+
26
+        return;
27
+    }
28
+
14
     const { fakeParticipants, sortedRemoteParticipants, speakersList } = state['features/base/participants'];
29
     const { fakeParticipants, sortedRemoteParticipants, speakersList } = state['features/base/participants'];
15
     const { remoteScreenShares } = state['features/video-layout'];
30
     const { remoteScreenShares } = state['features/video-layout'];
16
     const screenShares = (remoteScreenShares || []).slice();
31
     const screenShares = (remoteScreenShares || []).slice();
17
-    let speakers = (speakersList || []).slice();
32
+    const speakers = new Map(speakersList);
18
     const remoteParticipants = new Map(sortedRemoteParticipants);
33
     const remoteParticipants = new Map(sortedRemoteParticipants);
19
     const sharedVideos = fakeParticipants ? Array.from(fakeParticipants.keys()) : [];
34
     const sharedVideos = fakeParticipants ? Array.from(fakeParticipants.keys()) : [];
20
 
35
 
21
     for (const screenshare of screenShares) {
36
     for (const screenshare of screenShares) {
22
         remoteParticipants.delete(screenshare);
37
         remoteParticipants.delete(screenshare);
23
-        speakers = speakers.filter(speaker => speaker !== screenshare);
38
+        speakers.delete(screenshare);
24
     }
39
     }
25
     for (const sharedVideo of sharedVideos) {
40
     for (const sharedVideo of sharedVideos) {
26
         remoteParticipants.delete(sharedVideo);
41
         remoteParticipants.delete(sharedVideo);
27
-        speakers = speakers.filter(speaker => speaker !== sharedVideo);
42
+        speakers.delete(sharedVideo);
28
     }
43
     }
29
-    for (const speaker of speakers) {
44
+    for (const speaker of speakers.keys()) {
30
         remoteParticipants.delete(speaker);
45
         remoteParticipants.delete(speaker);
31
     }
46
     }
32
-    const reorderedParticipants
33
-        = [ ...screenShares.reverse(), ...sharedVideos, ...speakers, ...Array.from(remoteParticipants.keys()) ];
47
+
48
+    // Always update the order of the thumnails.
49
+    reorderedParticipants = [
50
+        ...screenShares.reverse(),
51
+        ...sharedVideos,
52
+        ...Array.from(speakers.keys()),
53
+        ...Array.from(remoteParticipants.keys())
54
+    ];
34
 
55
 
35
     store.dispatch(setRemoteParticipants(reorderedParticipants));
56
     store.dispatch(setRemoteParticipants(reorderedParticipants));
36
 }
57
 }

+ 1
- 1
react/features/filmstrip/middleware.web.js View File

47
         break;
47
         break;
48
     }
48
     }
49
     case PARTICIPANT_JOINED: {
49
     case PARTICIPANT_JOINED: {
50
-        updateRemoteParticipants(store);
50
+        updateRemoteParticipants(store, action.participant?.id);
51
         break;
51
         break;
52
     }
52
     }
53
     case PARTICIPANT_LEFT: {
53
     case PARTICIPANT_LEFT: {

+ 2
- 1
react/features/filmstrip/reducer.js View File

155
                 ...state,
155
                 ...state,
156
                 visibleParticipantsStartIndex: action.startIndex,
156
                 visibleParticipantsStartIndex: action.startIndex,
157
                 visibleParticipantsEndIndex: action.endIndex,
157
                 visibleParticipantsEndIndex: action.endIndex,
158
-                visibleRemoteParticipants: new Set(state.remoteParticipants.slice(action.startIndex, action.endIndex))
158
+                visibleRemoteParticipants:
159
+                    new Set(state.remoteParticipants.slice(action.startIndex, action.endIndex + 1))
159
             };
160
             };
160
         }
161
         }
161
         case PARTICIPANT_LEFT: {
162
         case PARTICIPANT_LEFT: {

+ 1
- 19
react/features/filmstrip/subscriber.any.js View File

17
  */
17
  */
18
 StateListenerRegistry.register(
18
 StateListenerRegistry.register(
19
     /* selector */ state => state['features/base/participants'].dominantSpeaker,
19
     /* selector */ state => state['features/base/participants'].dominantSpeaker,
20
-    /* listener */ (dominantSpeaker, store) => _reorderDominantSpeakers(store));
21
-
22
-/**
23
- * Private helper function that reorders the remote participants based on dominant speaker changes.
24
- *
25
- * @param {*} store - The redux store.
26
- * @returns {void}
27
- * @private
28
- */
29
-function _reorderDominantSpeakers(store) {
30
-    const state = store.getState();
31
-    const { dominantSpeaker, local } = state['features/base/participants'];
32
-    const { visibleRemoteParticipants } = state['features/filmstrip'];
33
-
34
-    // Reorder the participants if the new dominant speaker is currently not visible.
35
-    if (dominantSpeaker !== local?.id && !visibleRemoteParticipants.has(dominantSpeaker)) {
36
-        updateRemoteParticipants(store);
37
-    }
38
-}
20
+    /* listener */ (dominantSpeaker, store) => updateRemoteParticipants(store));

Loading…
Cancel
Save