Selaa lähdekoodia

feat(multi-stream) Add fake participant tile for screen share.

prioritize participants with screen shares
support local screen share track
auto pin screen share
support screen share for large video
ensure fake screen share participants are sorted
fix local screen share in vertical filmstrip
fix local screen share in tile mode
use FakeScreenShareParticipant component for screen share thumbnails
ensure changes are behind feature flag and update jsdocs
fix bug where local screen share was not rendering
update receiver constraints to include SS source names
remove fake ss participant creation on track update
fix: handle screenshare muted change and track removal
refactor: update key values for sortedFakeScreenShareParticipants
address PR comments
refactor getter for screenshare tracks
rename state to sortedRemoteFakeScreenShareParticipants
master
William Liang 3 vuotta sitten
vanhempi
commit
70090fd716
No account linked to committer's email address
31 muutettua tiedostoa jossa 852 lisäystä ja 80 poistoa
  1. 2
    1
      css/filmstrip/_horizontal_filmstrip.scss
  2. 2
    1
      css/filmstrip/_tile_view_overrides.scss
  3. 20
    1
      css/filmstrip/_vertical_filmstrip.scss
  4. 20
    13
      modules/UI/videolayout/LargeVideoManager.js
  5. 20
    2
      modules/UI/videolayout/VideoLayout.js
  6. 6
    1
      react/features/base/lastn/middleware.js
  7. 38
    6
      react/features/base/participants/functions.js
  8. 48
    4
      react/features/base/participants/reducer.js
  9. 12
    0
      react/features/base/tracks/actionTypes.js
  10. 26
    2
      react/features/base/tracks/actions.js
  11. 43
    0
      react/features/base/tracks/functions.js
  12. 70
    1
      react/features/base/tracks/middleware.js
  13. 12
    4
      react/features/connection-indicator/components/web/ConnectionIndicator.js
  14. 11
    3
      react/features/connection-indicator/components/web/ConnectionIndicatorContent.js
  15. 44
    1
      react/features/connection-stats/components/ConnectionStatsTable.js
  16. 16
    0
      react/features/filmstrip/actions.web.js
  17. 157
    0
      react/features/filmstrip/components/web/FakeScreenShareParticipant.js
  18. 23
    1
      react/features/filmstrip/components/web/Filmstrip.js
  19. 46
    4
      react/features/filmstrip/components/web/Thumbnail.js
  20. 15
    7
      react/features/filmstrip/components/web/ThumbnailBottomIndicators.js
  21. 23
    1
      react/features/filmstrip/components/web/ThumbnailTopIndicators.js
  22. 52
    2
      react/features/filmstrip/components/web/ThumbnailWrapper.js
  23. 50
    11
      react/features/filmstrip/functions.any.js
  24. 10
    1
      react/features/filmstrip/functions.web.js
  25. 4
    0
      react/features/filmstrip/middleware.web.js
  26. 2
    1
      react/features/filmstrip/subscriber.web.js
  27. 10
    0
      react/features/video-layout/actionTypes.js
  28. 17
    0
      react/features/video-layout/actions.js
  29. 2
    0
      react/features/video-layout/reducer.js
  30. 35
    2
      react/features/video-layout/subscriber.js
  31. 16
    10
      react/features/video-quality/subscriber.js

+ 2
- 1
css/filmstrip/_horizontal_filmstrip.scss Näytä tiedosto

@@ -48,7 +48,8 @@
48 48
         /**
49 49
          * The local video identifier.
50 50
          */
51
-        &#filmstripLocalVideo {
51
+        &#filmstripLocalVideo,
52
+        &#filmstripLocalScreenShare {
52 53
             align-self: flex-end;
53 54
             display: block;
54 55
             margin-bottom: 8px;

+ 2
- 1
css/filmstrip/_tile_view_overrides.scss Näytä tiedosto

@@ -2,7 +2,8 @@
2 2
  * Various overrides outside of the filmstrip to style the app to support a
3 3
  * tiled thumbnail experience.
4 4
  */
5
-.tile-view, .stage-filmstrip {
5
+.tile-view,
6
+.stage-filmstrip {
6 7
     /**
7 8
      * Let the avatar grow with the tile.
8 9
      */

+ 20
- 1
css/filmstrip/_vertical_filmstrip.scss Näytä tiedosto

@@ -87,9 +87,27 @@
87 87
             .videocontainer {
88 88
                 height: 0px;
89 89
                 width: 100%;
90
-    }
90
+            }
91 91
         }
92
+    }
93
+
94
+    #filmstripLocalScreenShare {
95
+        align-self: initial;
96
+        margin-bottom: 5px;
97
+        display: flex;
98
+        flex-direction: column-reverse;
99
+        height: auto;
100
+        justify-content: flex-start;
101
+        width: 100%;
92 102
 
103
+        #filmstripLocalScreenShareThumbnail {
104
+            width: calc(100% - 15px);
105
+
106
+            .videocontainer {
107
+                height: 0px;
108
+                width: 100%;
109
+            }
110
+        }
93 111
     }
94 112
 
95 113
     /**
@@ -97,6 +115,7 @@
97 115
      * filmstrip from overlapping the left edge of the screen.
98 116
      */
99 117
     #filmstripLocalVideo,
118
+    #filmstripLocalScreenShare,
100 119
     .remote-videos {
101 120
         padding: 0;
102 121
     }

+ 20
- 13
modules/UI/videolayout/LargeVideoManager.js Näytä tiedosto

@@ -11,12 +11,14 @@ import { Avatar } from '../../../react/features/base/avatar';
11 11
 import theme from '../../../react/features/base/components/themes/participantsPaneTheme.json';
12 12
 import { getSourceNameSignalingFeatureFlag } from '../../../react/features/base/config';
13 13
 import { i18next } from '../../../react/features/base/i18n';
14
-import { MEDIA_TYPE, VIDEO_TYPE } from '../../../react/features/base/media';
14
+import { VIDEO_TYPE } from '../../../react/features/base/media';
15 15
 import {
16 16
     getParticipantById,
17 17
     getParticipantDisplayName
18 18
 } from '../../../react/features/base/participants';
19
-import { getTrackByMediaTypeAndParticipant } from '../../../react/features/base/tracks';
19
+import {
20
+    getVideoTrackByParticipant
21
+} from '../../../react/features/base/tracks';
20 22
 import { CHAT_SIZE } from '../../../react/features/chat';
21 23
 import {
22 24
     isParticipantConnectionStatusActive,
@@ -237,11 +239,14 @@ export default class LargeVideoManager {
237 239
             let isVideoRenderable;
238 240
 
239 241
             if (getSourceNameSignalingFeatureFlag(state)) {
240
-                const videoTrack = getTrackByMediaTypeAndParticipant(
241
-                    state['features/base/tracks'], MEDIA_TYPE.VIDEO, id);
242
-
243
-                isVideoRenderable = !isVideoMuted
244
-                    && (APP.conference.isLocalId(id) || isTrackStreamingStatusActive(videoTrack));
242
+                const tracks = state['features/base/tracks'];
243
+                const videoTrack = getVideoTrackByParticipant(tracks, participant);
244
+
245
+                isVideoRenderable = !isVideoMuted && (
246
+                    APP.conference.isLocalId(id)
247
+                    || participant?.isLocalScreenShare
248
+                    || isTrackStreamingStatusActive(videoTrack)
249
+                );
245 250
             } else {
246 251
                 isVideoRenderable = !isVideoMuted
247 252
                     && (APP.conference.isLocalId(id) || isParticipantConnectionStatusActive(participant));
@@ -268,8 +273,10 @@ export default class LargeVideoManager {
268 273
 
269 274
                         && participant && !participant.local && !participant.isFakeParticipant) {
270 275
                     // remote participant only
271
-                    const track = getTrackByMediaTypeAndParticipant(
272
-                        state['features/base/tracks'], MEDIA_TYPE.VIDEO, id);
276
+
277
+                    const tracks = state['features/base/tracks'];
278
+                    const track = getVideoTrackByParticipant(tracks, participant);
279
+
273 280
                     const isScreenSharing = track?.videoType === 'desktop';
274 281
 
275 282
                     if (isScreenSharing) {
@@ -300,8 +307,8 @@ export default class LargeVideoManager {
300 307
             let messageKey;
301 308
 
302 309
             if (getSourceNameSignalingFeatureFlag(state)) {
303
-                const videoTrack = getTrackByMediaTypeAndParticipant(
304
-                    state['features/base/tracks'], MEDIA_TYPE.VIDEO, id);
310
+                const tracks = state['features/base/tracks'];
311
+                const videoTrack = getVideoTrackByParticipant(tracks, participant);
305 312
 
306 313
                 messageKey = isTrackStreamingStatusInactive(videoTrack) ? 'connection.LOW_BANDWIDTH' : null;
307 314
             } else {
@@ -541,8 +548,8 @@ export default class LargeVideoManager {
541 548
             const state = APP.store.getState();
542 549
 
543 550
             if (getSourceNameSignalingFeatureFlag(state)) {
544
-                const videoTrack = getTrackByMediaTypeAndParticipant(
545
-                    state['features/base/tracks'], MEDIA_TYPE.VIDEO, this.id);
551
+                const tracks = state['features/base/tracks'];
552
+                const videoTrack = getVideoTrackByParticipant(tracks, participant);
546 553
 
547 554
                 // eslint-disable-next-line no-param-reassign
548 555
                 show = !APP.conference.isLocalId(this.id)

+ 20
- 2
modules/UI/videolayout/VideoLayout.js Näytä tiedosto

@@ -2,12 +2,16 @@
2 2
 
3 3
 import Logger from '@jitsi/logger';
4 4
 
5
+import { getSourceNameSignalingFeatureFlag } from '../../../react/features/base/config';
5 6
 import { MEDIA_TYPE, VIDEO_TYPE } from '../../../react/features/base/media';
6 7
 import {
7 8
     getPinnedParticipant,
8 9
     getParticipantById
9 10
 } from '../../../react/features/base/participants';
10
-import { getTrackByMediaTypeAndParticipant } from '../../../react/features/base/tracks';
11
+import {
12
+    getTrackByMediaTypeAndParticipant,
13
+    getFakeScreenshareParticipantTrack
14
+} from '../../../react/features/base/tracks';
11 15
 
12 16
 import LargeVideoManager from './LargeVideoManager';
13 17
 import { VIDEO_CONTAINER_TYPE } from './VideoContainer';
@@ -91,6 +95,10 @@ const VideoLayout = {
91 95
             return VIDEO_TYPE.CAMERA;
92 96
         }
93 97
 
98
+        if (getSourceNameSignalingFeatureFlag(state) && participant?.isFakeScreenShareParticipant) {
99
+            return VIDEO_TYPE.DESKTOP;
100
+        }
101
+
94 102
         const videoTrack = getTrackByMediaTypeAndParticipant(state['features/base/tracks'], MEDIA_TYPE.VIDEO, id);
95 103
 
96 104
         return videoTrack?.videoType;
@@ -177,7 +185,17 @@ const VideoLayout = {
177 185
         const currentContainerType = largeVideo.getCurrentContainerType();
178 186
         const isOnLarge = this.isCurrentlyOnLarge(id);
179 187
         const state = APP.store.getState();
180
-        const videoTrack = getTrackByMediaTypeAndParticipant(state['features/base/tracks'], MEDIA_TYPE.VIDEO, id);
188
+        const participant = getParticipantById(state, id);
189
+        const tracks = state['features/base/tracks'];
190
+
191
+        let videoTrack;
192
+
193
+        if (getSourceNameSignalingFeatureFlag(state) && participant?.isFakeScreenShareParticipant) {
194
+            videoTrack = getFakeScreenshareParticipantTrack(tracks, id);
195
+        } else {
196
+            videoTrack = getTrackByMediaTypeAndParticipant(tracks, MEDIA_TYPE.VIDEO, id);
197
+        }
198
+
181 199
         const videoStream = videoTrack?.jitsiTrack;
182 200
 
183 201
         if (isOnLarge && !forceUpdate

+ 6
- 1
react/features/base/lastn/middleware.js Näytä tiedosto

@@ -5,7 +5,11 @@ import debounce from 'lodash/debounce';
5 5
 import { SET_FILMSTRIP_ENABLED } from '../../filmstrip/actionTypes';
6 6
 import { SELECT_LARGE_VIDEO_PARTICIPANT } from '../../large-video/actionTypes';
7 7
 import { APP_STATE_CHANGED } from '../../mobile/background/actionTypes';
8
-import { SCREEN_SHARE_REMOTE_PARTICIPANTS_UPDATED, SET_TILE_VIEW } from '../../video-layout/actionTypes';
8
+import {
9
+    FAKE_SCREEN_SHARE_REMOTE_PARTICIPANTS_UPDATED,
10
+    SCREEN_SHARE_REMOTE_PARTICIPANTS_UPDATED,
11
+    SET_TILE_VIEW
12
+} from '../../video-layout/actionTypes';
9 13
 import { SET_AUDIO_ONLY } from '../audio-only/actionTypes';
10 14
 import { CONFERENCE_JOINED } from '../conference/actionTypes';
11 15
 import {
@@ -92,6 +96,7 @@ MiddlewareRegistry.register(store => next => action => {
92 96
     switch (action.type) {
93 97
     case APP_STATE_CHANGED:
94 98
     case CONFERENCE_JOINED:
99
+    case FAKE_SCREEN_SHARE_REMOTE_PARTICIPANTS_UPDATED:
95 100
     case PARTICIPANT_JOINED:
96 101
     case PARTICIPANT_KICKED:
97 102
     case PARTICIPANT_LEFT:

+ 38
- 6
react/features/base/participants/functions.js Näytä tiedosto

@@ -5,6 +5,7 @@ import type { Store } from 'redux';
5 5
 
6 6
 import { isStageFilmstripEnabled } from '../../filmstrip/functions';
7 7
 import { GRAVATAR_BASE_URL, isCORSAvatarURL } from '../avatar';
8
+import { getSourceNameSignalingFeatureFlag } from '../config';
8 9
 import { JitsiParticipantConnectionStatus } from '../lib-jitsi-meet';
9 10
 import { MEDIA_TYPE, shouldRenderVideoTrack } from '../media';
10 11
 import { toState } from '../redux';
@@ -119,9 +120,11 @@ export function getNormalizedDisplayName(name: string) {
119 120
 export function getParticipantById(
120 121
         stateful: Object | Function, id: string): ?Object {
121 122
     const state = toState(stateful)['features/base/participants'];
122
-    const { local, remote } = state;
123
+    const { local, localScreenShare, remote } = state;
123 124
 
124
-    return remote.get(id) || (local?.id === id ? local : undefined);
125
+    return remote.get(id)
126
+        || (local?.id === id ? local : undefined)
127
+        || (localScreenShare?.id === id ? localScreenShare : undefined);
125 128
 }
126 129
 
127 130
 /**
@@ -148,10 +151,31 @@ export function getParticipantByIdOrUndefined(stateful: Object | Function, parti
148 151
  * @returns {number}
149 152
  */
150 153
 export function getParticipantCount(stateful: Object | Function) {
151
-    const state = toState(stateful)['features/base/participants'];
152
-    const { local, remote, fakeParticipants } = state;
154
+    const state = toState(stateful);
155
+    const {
156
+        local,
157
+        remote,
158
+        fakeParticipants,
159
+        sortedRemoteFakeScreenShareParticipants
160
+    } = state['features/base/participants'];
161
+
162
+    if (getSourceNameSignalingFeatureFlag(state)) {
163
+        return remote.size - fakeParticipants.size - sortedRemoteFakeScreenShareParticipants.size + (local ? 1 : 0);
164
+    }
153 165
 
154 166
     return remote.size - fakeParticipants.size + (local ? 1 : 0);
167
+
168
+}
169
+
170
+/**
171
+ * Returns participant ID of the owner of a fake screenshare participant.
172
+ *
173
+ * @param {string} id - The ID of the fake screenshare participant.
174
+ * @private
175
+ * @returns {(string|undefined)}
176
+ */
177
+export function getFakeScreenShareParticipantOwnerId(id: string) {
178
+    return id.split('-')[0];
155 179
 }
156 180
 
157 181
 /**
@@ -177,6 +201,10 @@ export function getFakeParticipants(stateful: Object | Function) {
177 201
 export function getRemoteParticipantCount(stateful: Object | Function) {
178 202
     const state = toState(stateful)['features/base/participants'];
179 203
 
204
+    if (getSourceNameSignalingFeatureFlag(state)) {
205
+        return state.remote.size - state.sortedRemoteFakeScreenShareParticipants.size;
206
+    }
207
+
180 208
     return state.remote.size;
181 209
 }
182 210
 
@@ -190,8 +218,12 @@ export function getRemoteParticipantCount(stateful: Object | Function) {
190 218
  * @returns {number}
191 219
  */
192 220
 export function getParticipantCountWithFake(stateful: Object | Function) {
193
-    const state = toState(stateful)['features/base/participants'];
194
-    const { local, remote } = state;
221
+    const state = toState(stateful);
222
+    const { local, localScreenShare, remote } = state['features/base/participants'];
223
+
224
+    if (getSourceNameSignalingFeatureFlag(state)) {
225
+        return remote.size + (local ? 1 : 0) + (localScreenShare ? 1 : 0);
226
+    }
195 227
 
196 228
     return remote.size + (local ? 1 : 0);
197 229
 }

+ 48
- 4
react/features/base/participants/reducer.js Näytä tiedosto

@@ -1,6 +1,8 @@
1 1
 // @flow
2 2
 
3
-import { SCREEN_SHARE_REMOTE_PARTICIPANTS_UPDATED } from '../../video-layout/actionTypes';
3
+import {
4
+    SCREEN_SHARE_REMOTE_PARTICIPANTS_UPDATED
5
+} from '../../video-layout/actionTypes';
4 6
 import { ReducerRegistry, set } from '../redux';
5 7
 
6 8
 import {
@@ -59,9 +61,11 @@ const DEFAULT_STATE = {
59 61
     fakeParticipants: new Map(),
60 62
     haveParticipantWithScreenSharingFeature: false,
61 63
     local: undefined,
64
+    localScreenShare: undefined,
62 65
     pinnedParticipant: undefined,
63 66
     raisedHandsQueue: [],
64 67
     remote: new Map(),
68
+    sortedRemoteFakeScreenShareParticipants: new Map(),
65 69
     sortedRemoteParticipants: new Map(),
66 70
     sortedRemoteScreenshares: new Map(),
67 71
     speakersList: new Map()
@@ -207,7 +211,7 @@ ReducerRegistry.register('features/base/participants', (state = DEFAULT_STATE, a
207 211
     }
208 212
     case PARTICIPANT_JOINED: {
209 213
         const participant = _participantJoined(action);
210
-        const { id, isFakeParticipant, name, pinned } = participant;
214
+        const { id, isFakeParticipant, isFakeScreenShareParticipant, isLocalScreenShare, name, pinned } = participant;
211 215
         const { pinnedParticipant, dominantSpeaker } = state;
212 216
 
213 217
         if (pinned) {
@@ -241,6 +245,13 @@ ReducerRegistry.register('features/base/participants', (state = DEFAULT_STATE, a
241 245
             };
242 246
         }
243 247
 
248
+        if (isLocalScreenShare) {
249
+            return {
250
+                ...state,
251
+                localScreenShare: participant
252
+            };
253
+        }
254
+
244 255
         state.remote.set(id, participant);
245 256
 
246 257
         // Insert the new participant.
@@ -253,6 +264,14 @@ ReducerRegistry.register('features/base/participants', (state = DEFAULT_STATE, a
253 264
         // The sort order of participants is preserved since Map remembers the original insertion order of the keys.
254 265
         state.sortedRemoteParticipants = new Map(sortedRemoteParticipants);
255 266
 
267
+        if (isFakeScreenShareParticipant) {
268
+            const sortedRemoteFakeScreenShareParticipants = [ ...state.sortedRemoteFakeScreenShareParticipants ];
269
+
270
+            sortedRemoteFakeScreenShareParticipants.push([ id, name ]);
271
+            sortedRemoteFakeScreenShareParticipants.sort((a, b) => a[1].localeCompare(b[1]));
272
+
273
+            state.sortedRemoteFakeScreenShareParticipants = new Map(sortedRemoteFakeScreenShareParticipants);
274
+        }
256 275
         if (isFakeParticipant) {
257 276
             state.fakeParticipants.set(id, participant);
258 277
         }
@@ -267,7 +286,15 @@ ReducerRegistry.register('features/base/participants', (state = DEFAULT_STATE, a
267 286
         // (and the fact that the local participant "joins" at the beginning of
268 287
         // the app and "leaves" at the end of the app).
269 288
         const { conference, id } = action.participant;
270
-        const { fakeParticipants, remote, local, dominantSpeaker, pinnedParticipant } = state;
289
+        const {
290
+            fakeParticipants,
291
+            sortedRemoteFakeScreenShareParticipants,
292
+            remote,
293
+            local,
294
+            localScreenShare,
295
+            dominantSpeaker,
296
+            pinnedParticipant
297
+        } = state;
271 298
         let oldParticipant = remote.get(id);
272 299
 
273 300
         if (oldParticipant && oldParticipant.conference === conference) {
@@ -275,6 +302,9 @@ ReducerRegistry.register('features/base/participants', (state = DEFAULT_STATE, a
275 302
         } else if (local?.id === id) {
276 303
             oldParticipant = state.local;
277 304
             delete state.local;
305
+        } else if (localScreenShare?.id === id) {
306
+            oldParticipant = state.local;
307
+            delete state.localScreenShare;
278 308
         } else {
279 309
             // no participant found
280 310
             return state;
@@ -324,6 +354,11 @@ ReducerRegistry.register('features/base/participants', (state = DEFAULT_STATE, a
324 354
             fakeParticipants.delete(id);
325 355
         }
326 356
 
357
+        if (sortedRemoteFakeScreenShareParticipants.has(id)) {
358
+            sortedRemoteFakeScreenShareParticipants.delete(id);
359
+            state.sortedRemoteFakeScreenShareParticipants = new Map(sortedRemoteFakeScreenShareParticipants);
360
+        }
361
+
327 362
         return { ...state };
328 363
     }
329 364
     case RAISE_HAND_UPDATED: {
@@ -447,6 +482,8 @@ function _participantJoined({ participant }) {
447 482
         dominantSpeaker,
448 483
         email,
449 484
         isFakeParticipant,
485
+        isFakeScreenShareParticipant,
486
+        isLocalScreenShare,
450 487
         isReplacing,
451 488
         isJigasi,
452 489
         loadableAvatarUrl,
@@ -479,6 +516,8 @@ function _participantJoined({ participant }) {
479 516
         email,
480 517
         id,
481 518
         isFakeParticipant,
519
+        isFakeScreenShareParticipant,
520
+        isLocalScreenShare,
482 521
         isReplacing,
483 522
         isJigasi,
484 523
         loadableAvatarUrl,
@@ -500,7 +539,7 @@ function _participantJoined({ participant }) {
500 539
  * @returns {boolean} - True if a participant was updated and false otherwise.
501 540
  */
502 541
 function _updateParticipantProperty(state, id, property, value) {
503
-    const { remote, local } = state;
542
+    const { remote, local, localScreenShare } = state;
504 543
 
505 544
     if (remote.has(id)) {
506 545
         remote.set(id, set(remote.get(id), property, value));
@@ -511,6 +550,11 @@ function _updateParticipantProperty(state, id, property, value) {
511 550
         // not in a conference.
512 551
         state.local = set(local, property, value);
513 552
 
553
+        return true;
554
+
555
+    } else if (localScreenShare?.id === id) {
556
+        state.localScreenShare = set(localScreenShare, property, value);
557
+
514 558
         return true;
515 559
     }
516 560
 

+ 12
- 0
react/features/base/tracks/actionTypes.js Näytä tiedosto

@@ -107,6 +107,18 @@ export const TRACK_STOPPED = 'TRACK_STOPPED';
107 107
  */
108 108
 export const TRACK_UPDATED = 'TRACK_UPDATED';
109 109
 
110
+/**
111
+ * The type of redux action dispatched when a screenshare track's muted property were updated.
112
+ *
113
+ * {
114
+ *     type: SCREENSHARE_TRACK_MUTED_UPDATED,
115
+ *     track: Track,
116
+ *     muted: Boolean
117
+ *  
118
+ * }
119
+ */
120
+ export const SCREENSHARE_TRACK_MUTED_UPDATED = 'SCREENSHARE_TRACK_MUTED_UPDATED';
121
+
110 122
 /**
111 123
  * The type of redux action dispatched when a local track starts being created
112 124
  * via a WebRTC {@code getUserMedia} call. The action's payload includes an

+ 26
- 2
react/features/base/tracks/actions.js Näytä tiedosto

@@ -6,7 +6,7 @@ import {
6 6
 } from '../../analytics';
7 7
 import { NOTIFICATION_TIMEOUT_TYPE, showErrorNotification, showNotification } from '../../notifications';
8 8
 import { getCurrentConference } from '../conference';
9
-import { getMultipleVideoSupportFeatureFlag } from '../config';
9
+import { getMultipleVideoSupportFeatureFlag, getSourceNameSignalingFeatureFlag } from '../config';
10 10
 import { JitsiTrackErrors, JitsiTrackEvents, createLocalTrack } from '../lib-jitsi-meet';
11 11
 import {
12 12
     CAMERA_FACING_MODE,
@@ -21,6 +21,7 @@ import { getLocalParticipant } from '../participants';
21 21
 import { updateSettings } from '../settings';
22 22
 
23 23
 import {
24
+    SCREENSHARE_TRACK_MUTED_UPDATED,
24 25
     SET_NO_SRC_DATA_NOTIFICATION_UID,
25 26
     TOGGLE_SCREENSHARING,
26 27
     TRACK_ADDED,
@@ -395,7 +396,12 @@ export function trackAdded(track) {
395 396
     return async (dispatch, getState) => {
396 397
         track.on(
397 398
             JitsiTrackEvents.TRACK_MUTE_CHANGED,
398
-            () => dispatch(trackMutedChanged(track)));
399
+        () => {
400
+            if (getSourceNameSignalingFeatureFlag(getState()) && track.getVideoType() === VIDEO_TYPE.DESKTOP) {
401
+                dispatch(screenshareTrackMutedChanged(track));
402
+            }
403
+            dispatch(trackMutedChanged(track));
404
+        });
399 405
         track.on(
400 406
             JitsiTrackEvents.TRACK_VIDEOTYPE_CHANGED,
401 407
             type => dispatch(trackVideoTypeChanged(track, type)));
@@ -491,6 +497,24 @@ export function trackMutedChanged(track) {
491 497
     };
492 498
 }
493 499
 
500
+/**
501
+ * Create an action for when a screenshare track's muted state has been signaled to be changed.
502
+ *
503
+ * @param {(JitsiLocalTrack|JitsiRemoteTrack)} track - JitsiTrack instance.
504
+ * @returns {{
505
+ *     type: TRACK_UPDATED,
506
+ *     track: Track,
507
+ *     muted: boolean
508
+ * }}
509
+ */
510
+export function screenshareTrackMutedChanged(track) {
511
+    return {
512
+        type: SCREENSHARE_TRACK_MUTED_UPDATED,
513
+        track: { jitsiTrack: track },
514
+        muted: track.isMuted()
515
+    };
516
+}
517
+
494 518
 /**
495 519
  * Create an action for when a track's muted state change action has failed. This could happen because of
496 520
  * {@code getUserMedia} errors during unmute or replace track errors at the peerconnection level.

+ 43
- 0
react/features/base/tracks/functions.js Näytä tiedosto

@@ -4,6 +4,7 @@ import { getMultipleVideoSupportFeatureFlag } from '../config/functions.any';
4 4
 import { isMobileBrowser } from '../environment/utils';
5 5
 import JitsiMeetJS, { JitsiTrackErrors, browser } from '../lib-jitsi-meet';
6 6
 import { MEDIA_TYPE, VIDEO_TYPE, setAudioMuted } from '../media';
7
+import { getFakeScreenShareParticipantOwnerId } from '../participants';
7 8
 import { toState } from '../redux';
8 9
 import {
9 10
     getUserSelectedCameraDeviceId,
@@ -410,6 +411,35 @@ export function getLocalJitsiAudioTrack(state) {
410 411
     return track?.jitsiTrack;
411 412
 }
412 413
 
414
+/**
415
+ * Returns track of specified media type for specified participant.
416
+ *
417
+ * @param {Track[]} tracks - List of all tracks.
418
+ * @param {Object} participant - Participant Object.
419
+ * @returns {(Track|undefined)}
420
+ */
421
+export function getVideoTrackByParticipant(
422
+        tracks,
423
+        participant) {
424
+
425
+    if (!participant) {
426
+        return;
427
+    }
428
+
429
+    let participantId;
430
+    let mediaType;
431
+
432
+    if (participant?.isFakeScreenShareParticipant) {
433
+        participantId = getFakeScreenShareParticipantOwnerId(participant.id);
434
+        mediaType = MEDIA_TYPE.SCREENSHARE;
435
+    } else {
436
+        participantId = participant.id;
437
+        mediaType = MEDIA_TYPE.VIDEO;
438
+    }
439
+
440
+    return getTrackByMediaTypeAndParticipant(tracks, mediaType, participantId);
441
+}
442
+
413 443
 /**
414 444
  * Returns track of specified media type for specified participant id.
415 445
  *
@@ -427,6 +457,19 @@ export function getTrackByMediaTypeAndParticipant(
427 457
     );
428 458
 }
429 459
 
460
+/**
461
+ * Returns track of given fakeScreenshareParticipantId.
462
+ *
463
+ * @param {Track[]} tracks - List of all tracks.
464
+ * @param {string} fakeScreenshareParticipantId - Fake Screenshare Participant ID.
465
+ * @returns {(Track|undefined)}
466
+ */
467
+export function getFakeScreenshareParticipantTrack(tracks, fakeScreenshareParticipantId) {
468
+    const participantId = getFakeScreenShareParticipantOwnerId(fakeScreenshareParticipantId);
469
+
470
+    return getTrackByMediaTypeAndParticipant(tracks, MEDIA_TYPE.SCREENSHARE, participantId);
471
+}
472
+
430 473
 /**
431 474
  * Returns track source name of specified media type for specified participant id.
432 475
  *

+ 70
- 1
react/features/base/tracks/middleware.js Näytä tiedosto

@@ -8,7 +8,7 @@ import { shouldShowModeratedNotification } from '../../av-moderation/functions';
8 8
 import { hideNotification, isModerationNotificationDisplayed } from '../../notifications';
9 9
 import { isPrejoinPageVisible } from '../../prejoin/functions';
10 10
 import { getCurrentConference } from '../conference/functions';
11
-import { getMultipleVideoSupportFeatureFlag } from '../config';
11
+import { getMultipleVideoSupportFeatureFlag, getSourceNameSignalingFeatureFlag } from '../config';
12 12
 import { getAvailableDevices } from '../devices/actions';
13 13
 import {
14 14
     CAMERA_FACING_MODE,
@@ -24,9 +24,11 @@ import {
24 24
     setScreenshareMuted,
25 25
     SCREENSHARE_MUTISM_AUTHORITY
26 26
 } from '../media';
27
+import { participantLeft, participantJoined, getParticipantById } from '../participants';
27 28
 import { MiddlewareRegistry, StateListenerRegistry } from '../redux';
28 29
 
29 30
 import {
31
+    SCREENSHARE_TRACK_MUTED_UPDATED,
30 32
     TOGGLE_SCREENSHARING,
31 33
     TRACK_ADDED,
32 34
     TRACK_MUTE_UNMUTE_FAILED,
@@ -50,6 +52,7 @@ import {
50 52
     isUserInteractionRequiredForUnmute,
51 53
     setTrackMuted
52 54
 } from './functions';
55
+import logger from './logger';
53 56
 
54 57
 import './subscriber';
55 58
 
@@ -72,6 +75,13 @@ MiddlewareRegistry.register(store => next => action => {
72 75
             store.dispatch(getAvailableDevices());
73 76
         }
74 77
 
78
+        if (getSourceNameSignalingFeatureFlag(store.getState())
79
+            && action.track.jitsiTrack.videoType === VIDEO_TYPE.DESKTOP
80
+            && !action.track.jitsiTrack.isMuted()
81
+        ) {
82
+            createFakeScreenShareParticipant(store, action);
83
+        }
84
+
75 85
         break;
76 86
     }
77 87
     case TRACK_NO_DATA_FROM_SOURCE: {
@@ -81,7 +91,40 @@ MiddlewareRegistry.register(store => next => action => {
81 91
 
82 92
         return result;
83 93
     }
94
+
95
+    case SCREENSHARE_TRACK_MUTED_UPDATED: {
96
+        const state = store.getState();
97
+
98
+        if (!getSourceNameSignalingFeatureFlag(state)) {
99
+            return;
100
+        }
101
+
102
+        const { track, muted } = action;
103
+
104
+        if (muted) {
105
+            const conference = getCurrentConference(state);
106
+            const participantId = track?.jitsiTrack.getSourceName();
107
+
108
+            store.dispatch(participantLeft(participantId, conference));
109
+        }
110
+
111
+        if (!muted) {
112
+            createFakeScreenShareParticipant(store, action);
113
+        }
114
+
115
+        break;
116
+    }
117
+
84 118
     case TRACK_REMOVED: {
119
+        const state = store.getState();
120
+
121
+        if (getSourceNameSignalingFeatureFlag(state) && action.track.jitsiTrack.videoType === VIDEO_TYPE.DESKTOP) {
122
+            const conference = getCurrentConference(state);
123
+            const participantId = action.track.jitsiTrack.getSourceName();
124
+
125
+            store.dispatch(participantLeft(participantId, conference));
126
+        }
127
+
85 128
         _removeNoDataFromSourceNotification(store, action.track);
86 129
         break;
87 130
     }
@@ -326,6 +369,32 @@ function _handleNoDataFromSourceErrors(store, action) {
326 369
     }
327 370
 }
328 371
 
372
+/**
373
+ * Creates a fake participant for screen share using the track's source name as the participant id.
374
+ *
375
+ * @param {Store} store - The redux store in which the specified action is dispatched.
376
+ * @param {Action} action - The redux action dispatched in the specified store.
377
+ * @private
378
+ * @returns {void}
379
+ */
380
+function createFakeScreenShareParticipant({ dispatch, getState }, { track }) {
381
+    const state = getState();
382
+    const participantId = track.jitsiTrack?.getParticipantId?.();
383
+    const participant = getParticipantById(state, participantId);
384
+
385
+    if (participant.name) {
386
+        dispatch(participantJoined({
387
+            conference: state['features/base/conference'].conference,
388
+            id: track.jitsiTrack.getSourceName(),
389
+            isFakeScreenShareParticipant: true,
390
+            isLocalScreenShare: track?.jitsiTrack.isLocal(),
391
+            name: `${participant.name}'s screen`
392
+        }));
393
+    } else {
394
+        logger.error(`Failed to create a screenshare participant for participantId: ${participantId}`);
395
+    }
396
+}
397
+
329 398
 /**
330 399
  * Gets the local track associated with a specific {@code MEDIA_TYPE} in a
331 400
  * specific redux store.

+ 12
- 4
react/features/connection-indicator/components/web/ConnectionIndicator.js Näytä tiedosto

@@ -11,7 +11,9 @@ import { MEDIA_TYPE } from '../../../base/media';
11 11
 import { getLocalParticipant, getParticipantById } from '../../../base/participants';
12 12
 import { Popover } from '../../../base/popover';
13 13
 import { connect } from '../../../base/redux';
14
-import { getTrackByMediaTypeAndParticipant } from '../../../base/tracks';
14
+import {
15
+    getFakeScreenshareParticipantTrack,
16
+    getTrackByMediaTypeAndParticipant } from '../../../base/tracks';
15 17
 import {
16 18
     isParticipantConnectionStatusInactive,
17 19
     isParticipantConnectionStatusInterrupted,
@@ -366,12 +368,18 @@ class ConnectionIndicator extends AbstractConnectionIndicator<Props, State> {
366 368
  */
367 369
 export function _mapStateToProps(state: Object, ownProps: Props) {
368 370
     const { participantId } = ownProps;
371
+    const tracks = state['features/base/tracks'];
369 372
     const sourceNameSignalingEnabled = getSourceNameSignalingFeatureFlag(state);
370
-    const firstVideoTrack = getTrackByMediaTypeAndParticipant(
371
-        state['features/base/tracks'], MEDIA_TYPE.VIDEO, participantId);
372
-
373 373
     const participant = participantId ? getParticipantById(state, participantId) : getLocalParticipant(state);
374 374
 
375
+    let firstVideoTrack;
376
+
377
+    if (sourceNameSignalingEnabled && participant?.isFakeScreenShareParticipant) {
378
+        firstVideoTrack = getFakeScreenshareParticipantTrack(tracks, participantId);
379
+    } else {
380
+        firstVideoTrack = getTrackByMediaTypeAndParticipant(tracks, MEDIA_TYPE.VIDEO, participantId);
381
+    }
382
+
375 383
     const _isConnectionStatusInactive = sourceNameSignalingEnabled
376 384
         ? isTrackStreamingStatusInactive(firstVideoTrack)
377 385
         : isParticipantConnectionStatusInactive(participant);

+ 11
- 3
react/features/connection-indicator/components/web/ConnectionIndicatorContent.js Näytä tiedosto

@@ -87,6 +87,12 @@ type Props = AbstractProps & {
87 87
      */
88 88
     _enableSaveLogs: boolean,
89 89
 
90
+    /**
91
+     * Whether or not the displays stats are for screen share. This prop is behind the sourceNameSignaling feature
92
+     * flag.
93
+     */
94
+    _isFakeScreenShareParticipant: Boolean,
95
+
90 96
     /**
91 97
      * Whether or not the displays stats are for local video.
92 98
      */
@@ -199,6 +205,7 @@ class ConnectionIndicatorContent extends AbstractConnectionIndicator<Props, Stat
199 205
                 e2eRtt = { e2eRtt }
200 206
                 enableSaveLogs = { this.props._enableSaveLogs }
201 207
                 framerate = { framerate }
208
+                isFakeScreenShareParticipant = { this.props._isFakeScreenShareParticipant }
202 209
                 isLocalVideo = { this.props._isLocalVideo }
203 210
                 maxEnabledResolution = { maxEnabledResolution }
204 211
                 onSaveLogs = { this.props._onSaveLogs }
@@ -334,10 +341,11 @@ export function _mapStateToProps(state: Object, ownProps: Props) {
334 341
         _connectionStatus: participant?.connectionStatus,
335 342
         _enableSaveLogs: state['features/base/config'].enableSaveLogs,
336 343
         _disableShowMoreStats: state['features/base/config'].disableShowMoreStats,
337
-        _isLocalVideo: participant?.local,
338
-        _region: participant?.region,
339 344
         _isConnectionStatusInactive,
340
-        _isConnectionStatusInterrupted
345
+        _isConnectionStatusInterrupted,
346
+        _isFakeScreenShareParticipant: sourceNameSignalingEnabled && participant?.isFakeScreenShareParticipant,
347
+        _isLocalVideo: participant?.local,
348
+        _region: participant?.region
341 349
     };
342 350
 
343 351
     if (conference) {

+ 44
- 1
react/features/connection-stats/components/ConnectionStatsTable.js Näytä tiedosto

@@ -91,6 +91,11 @@ type Props = {
91 91
      */
92 92
     isLocalVideo: boolean,
93 93
 
94
+    /**
95
+     * Whether or not the statistics are for screen share.
96
+     */
97
+    isFakeScreenShareParticipant: boolean,
98
+
94 99
     /**
95 100
      * The send-side max enabled resolution (aka the highest layer that is not
96 101
      * suspended on the send-side).
@@ -231,9 +236,19 @@ class ConnectionStatsTable extends Component<Props> {
231 236
      * @returns {ReactElement}
232 237
      */
233 238
     render() {
234
-        const { isLocalVideo, enableSaveLogs, disableShowMoreStats, classes } = this.props;
239
+        const {
240
+            classes,
241
+            disableShowMoreStats,
242
+            enableSaveLogs,
243
+            isFakeScreenShareParticipant,
244
+            isLocalVideo
245
+        } = this.props;
235 246
         const className = clsx(classes.connectionStatsTable, { [classes.mobile]: isMobileBrowser() });
236 247
 
248
+        if (isFakeScreenShareParticipant) {
249
+            return this._renderScreenShareStatus();
250
+        }
251
+
237 252
         return (
238 253
             <ContextMenu
239 254
                 className = { classes.contextMenu }
@@ -253,6 +268,34 @@ class ConnectionStatsTable extends Component<Props> {
253 268
         );
254 269
     }
255 270
 
271
+    /**
272
+     * Creates a ReactElement that will display connection statistics for a screen share thumbnail.
273
+     *
274
+     * @private
275
+     * @returns {ReactElement}
276
+     */
277
+    _renderScreenShareStatus() {
278
+        const { classes } = this.props;
279
+        const className = isMobileBrowser() ? 'connection-info connection-info__mobile' : 'connection-info';
280
+
281
+        return (<ContextMenu
282
+            className = { classes.contextMenu }
283
+            hidden = { false }
284
+            inDrawer = { true }>
285
+            <div
286
+                className = { className }
287
+                onClick = { onClick }>
288
+                { <table className = 'connection-info__container'>
289
+                    <tbody>
290
+                        { this._renderResolution() }
291
+                        { this._renderFrameRate() }
292
+                    </tbody>
293
+                </table> }
294
+
295
+            </div>
296
+        </ContextMenu>);
297
+    }
298
+
256 299
     /**
257 300
      * Creates a table as ReactElement that will display additional statistics
258 301
      * related to bandwidth and transport for the local user.

+ 16
- 0
react/features/filmstrip/actions.web.js Näytä tiedosto

@@ -1,6 +1,7 @@
1 1
 // @flow
2 2
 import type { Dispatch } from 'redux';
3 3
 
4
+import { getSourceNameSignalingFeatureFlag } from '../base/config';
4 5
 import {
5 6
     getLocalParticipant,
6 7
     getParticipantById,
@@ -122,6 +123,7 @@ export function setVerticalViewDimensions() {
122 123
         const resizableFilmstrip = isFilmstripResizable(state);
123 124
         const _verticalViewGrid = showGridInVerticalView(state);
124 125
         const numberOfRemoteParticipants = getRemoteParticipantCount(state);
126
+        const { localScreenShare } = state['features/base/participants'];
125 127
 
126 128
         let gridView = {};
127 129
         let thumbnails = {};
@@ -179,6 +181,20 @@ export function setVerticalViewDimensions() {
179 181
                 = thumbnails?.local?.width + TILE_VERTICAL_CONTAINER_HORIZONTAL_MARGIN + SCROLL_SIZE;
180 182
             remoteVideosContainerHeight
181 183
                 = clientHeight - (disableSelfView ? 0 : thumbnails?.local?.height) - VERTICAL_FILMSTRIP_VERTICAL_MARGIN;
184
+
185
+            if (getSourceNameSignalingFeatureFlag(state)) {
186
+                // Account for the height of the local screen share thumbnail when calculating the height of the remote
187
+                // videos container.
188
+                const localCameraThumbnailHeight = thumbnails?.local?.height;
189
+                const localScreenShareThumbnailHeight
190
+                    = localScreenShare && !disableSelfView ? thumbnails?.local?.height : 0;
191
+
192
+                remoteVideosContainerHeight = clientHeight
193
+                    - localCameraThumbnailHeight
194
+                    - localScreenShareThumbnailHeight
195
+                    - VERTICAL_FILMSTRIP_VERTICAL_MARGIN;
196
+            }
197
+
182 198
             hasScroll
183 199
                 = remoteVideosContainerHeight
184 200
                     < (thumbnails?.remote.height + TILE_VERTICAL_MARGIN) * numberOfRemoteParticipants;

+ 157
- 0
react/features/filmstrip/components/web/FakeScreenShareParticipant.js Näytä tiedosto

@@ -0,0 +1,157 @@
1
+// @flow
2
+
3
+import clsx from 'clsx';
4
+import React from 'react';
5
+import { useSelector } from 'react-redux';
6
+
7
+import { VideoTrack } from '../../../base/media';
8
+import { getCurrentLayout, LAYOUTS } from '../../../video-layout';
9
+
10
+import ThumbnailBottomIndicators from './ThumbnailBottomIndicators';
11
+import ThumbnailTopIndicators from './ThumbnailTopIndicators';
12
+
13
+type Props = {
14
+
15
+    /**
16
+     * An object containing the CSS classes.
17
+     */
18
+    classes: Object,
19
+
20
+    /**
21
+     * The class name that will be used for the container.
22
+     */
23
+    containerClassName: string,
24
+
25
+    /**
26
+     * Indicates whether the thumbnail is hovered or not.
27
+     */
28
+    isHovered: boolean,
29
+
30
+    /**
31
+     * Indicates whether we are currently running in a mobile browser.
32
+     */
33
+    isMobile: boolean,
34
+
35
+    /**
36
+     * Click handler.
37
+     */
38
+    onClick: Function,
39
+
40
+    /**
41
+     * Mouse enter handler.
42
+     */
43
+    onMouseEnter: Function,
44
+
45
+    /**
46
+     * Mouse leave handler.
47
+     */
48
+    onMouseLeave: Function,
49
+
50
+     /**
51
+     * Mouse move handler.
52
+     */
53
+    onMouseMove: Function,
54
+
55
+    /**
56
+     * Touch end handler.
57
+     */
58
+    onTouchEnd: Function,
59
+
60
+    /**
61
+     * Touch move handler.
62
+     */
63
+    onTouchMove: Function,
64
+
65
+    /**
66
+     * Touch start handler.
67
+     */
68
+    onTouchStart: Function,
69
+
70
+    /**
71
+     * The ID of the fake screen share participant.
72
+     */
73
+    participantId: string,
74
+
75
+    /**
76
+     * An object with the styles for thumbnail.
77
+     */
78
+    styles: Object,
79
+
80
+    /**
81
+     * JitsiTrack instance.
82
+     */
83
+    videoTrack: Object
84
+}
85
+
86
+const FakeScreenShareParticipant = ({
87
+    classes,
88
+    containerClassName,
89
+    isHovered,
90
+    isMobile,
91
+    onClick,
92
+    onMouseEnter,
93
+    onMouseLeave,
94
+    onMouseMove,
95
+    onTouchEnd,
96
+    onTouchMove,
97
+    onTouchStart,
98
+    participantId,
99
+    styles,
100
+    videoTrack
101
+}: Props) => {
102
+    const currentLayout = useSelector(getCurrentLayout);
103
+    const videoTrackId = videoTrack?.jitsiTrack?.getId();
104
+    const video = videoTrack && <VideoTrack
105
+        id = { `remoteVideo_${videoTrackId || ''}` }
106
+        muted = { true }
107
+        style = { styles.video }
108
+        videoTrack = { videoTrack } />;
109
+
110
+
111
+    return (
112
+        <span
113
+            className = { containerClassName }
114
+            id = { `participant_${participantId}` }
115
+            { ...(isMobile
116
+                ? {
117
+                    onTouchEnd,
118
+                    onTouchMove,
119
+                    onTouchStart
120
+                }
121
+                : {
122
+                    onClick,
123
+                    onMouseEnter,
124
+                    onMouseMove,
125
+                    onMouseLeave
126
+                }
127
+            ) }
128
+            style = { styles.thumbnail }>
129
+            {video}
130
+            <div className = { classes.containerBackground } />
131
+            <div
132
+                className = { clsx(classes.indicatorsContainer,
133
+                        classes.indicatorsTopContainer,
134
+                        currentLayout === LAYOUTS.TILE_VIEW && 'tile-view-mode'
135
+                ) }>
136
+                <ThumbnailTopIndicators
137
+                    currentLayout = { currentLayout }
138
+                    isFakeScreenShareParticipant = { true }
139
+                    isHovered = { isHovered }
140
+                    participantId = { participantId } />
141
+            </div>
142
+            <div
143
+                className = { clsx(classes.indicatorsContainer,
144
+                        classes.indicatorsBottomContainer,
145
+                        currentLayout === LAYOUTS.TILE_VIEW && 'tile-view-mode'
146
+                ) }>
147
+                <ThumbnailBottomIndicators
148
+                    className = { classes.indicatorsBackground }
149
+                    currentLayout = { currentLayout }
150
+                    local = { false }
151
+                    participantId = { participantId }
152
+                    showStatusIndicators = { false } />
153
+            </div>
154
+        </span>);
155
+};
156
+
157
+export default FakeScreenShareParticipant;

+ 23
- 1
react/features/filmstrip/components/web/Filmstrip.js Näytä tiedosto

@@ -12,7 +12,7 @@ import {
12 12
     createToolbarEvent,
13 13
     sendAnalytics
14 14
 } from '../../../analytics';
15
-import { getToolbarButtons } from '../../../base/config';
15
+import { getSourceNameSignalingFeatureFlag, getToolbarButtons } from '../../../base/config';
16 16
 import { isMobileBrowser } from '../../../base/environment/utils';
17 17
 import { translate } from '../../../base/i18n';
18 18
 import { Icon, IconMenuDown, IconMenuUp } from '../../../base/icons';
@@ -107,6 +107,11 @@ type Props = {
107 107
      */
108 108
     _isVerticalFilmstrip: boolean,
109 109
 
110
+    /**
111
+     * The local screen share participant. This prop is behind the sourceNameSignaling feature flag.
112
+     */
113
+     _localScreenShare: Object,
114
+
110 115
     /**
111 116
      * The maximum width of the vertical filmstrip.
112 117
      */
@@ -301,6 +306,7 @@ class Filmstrip extends PureComponent <Props, State> {
301 306
         const {
302 307
             _currentLayout,
303 308
             _disableSelfView,
309
+            _localScreenShare,
304 310
             _resizableFilmstrip,
305 311
             _stageFilmstrip,
306 312
             _visible,
@@ -352,6 +358,20 @@ class Filmstrip extends PureComponent <Props, State> {
352 358
                         }
353 359
                     </div>
354 360
                 )}
361
+                {_localScreenShare && !_disableSelfView && !_verticalViewGrid && (
362
+                    <div
363
+                        className = 'filmstrip__videos'
364
+                        id = 'filmstripLocalScreenShare'>
365
+                        <div id = 'filmstripLocalScreenShareThumbnail'>
366
+                            {
367
+                                !tileViewActive && <Thumbnail
368
+                                    key = 'localScreenShare'
369
+                                    participantID = { _localScreenShare.id } />
370
+
371
+                            }
372
+                        </div>
373
+                    </div>
374
+                )}
355 375
                 {
356 376
                     this._renderRemoteParticipants()
357 377
                 }
@@ -782,6 +802,7 @@ function _mapStateToProps(state, ownProps) {
782 802
     const { testing = {}, iAmRecorder } = state['features/base/config'];
783 803
     const enableThumbnailReordering = testing.enableThumbnailReordering ?? true;
784 804
     const { visible, width: verticalFilmstripWidth } = state['features/filmstrip'];
805
+    const { localScreenShare } = state['features/base/participants'];
785 806
     const reduceHeight = state['features/toolbox'].visible && toolbarButtons.length;
786 807
     const remoteVideosVisible = shouldRemoteVideosBeVisible(state);
787 808
     const { isOpen: shiftRight } = state['features/chat'];
@@ -808,6 +829,7 @@ function _mapStateToProps(state, ownProps) {
808 829
         _isFilmstripButtonEnabled: isButtonEnabled('filmstrip', state),
809 830
         _isToolboxVisible: isToolboxVisible(state),
810 831
         _isVerticalFilmstrip: ownProps._currentLayout === LAYOUTS.VERTICAL_FILMSTRIP_VIEW,
832
+        _localScreenShare: getSourceNameSignalingFeatureFlag(state) && localScreenShare,
811 833
         _maxFilmstripWidth: clientWidth - MIN_STAGE_VIEW_WIDTH,
812 834
         _thumbnailsReordered: enableThumbnailReordering,
813 835
         _verticalFilmstripWidth: verticalFilmstripWidth.current,

+ 46
- 4
react/features/filmstrip/components/web/Thumbnail.js Näytä tiedosto

@@ -7,6 +7,7 @@ import React, { Component } from 'react';
7 7
 
8 8
 import { createScreenSharingIssueEvent, sendAnalytics } from '../../../analytics';
9 9
 import { Avatar } from '../../../base/avatar';
10
+import { getSourceNameSignalingFeatureFlag } from '../../../base/config';
10 11
 import { isMobileBrowser } from '../../../base/environment/utils';
11 12
 import { MEDIA_TYPE, VideoTrack } from '../../../base/media';
12 13
 import {
@@ -21,6 +22,7 @@ import {
21 22
     getLocalAudioTrack,
22 23
     getLocalVideoTrack,
23 24
     getTrackByMediaTypeAndParticipant,
25
+    getFakeScreenshareParticipantTrack,
24 26
     updateLastTrackVideoMediaEvent
25 27
 } from '../../../base/tracks';
26 28
 import { getVideoObjectPosition } from '../../../facial-recognition/functions';
@@ -44,6 +46,7 @@ import {
44 46
 } from '../../functions';
45 47
 import { isStageFilmstripEnabled } from '../../functions.web';
46 48
 
49
+import FakeScreenShareParticipant from './FakeScreenShareParticipant';
47 50
 import ThumbnailAudioIndicator from './ThumbnailAudioIndicator';
48 51
 import ThumbnailBottomIndicators from './ThumbnailBottomIndicators';
49 52
 import ThumbnailTopIndicators from './ThumbnailTopIndicators';
@@ -132,6 +135,12 @@ export type Props = {|
132 135
      */
133 136
     _isCurrentlyOnLargeVideo: boolean,
134 137
 
138
+    /**
139
+     * Indicates whether the participant is a fake screen share participant. This prop is behind the
140
+     * sourceNameSignaling feature flag.
141
+     */
142
+    _isFakeScreenShareParticipant: boolean,
143
+
135 144
     /**
136 145
      * Whether we are currently running in a mobile browser.
137 146
      */
@@ -535,6 +544,7 @@ class Thumbnail extends Component<Props, State> {
535 544
             _currentLayout,
536 545
             _disableTileEnlargement,
537 546
             _height,
547
+            _isFakeScreenShareParticipant,
538 548
             _isHidden,
539 549
             _isScreenSharing,
540 550
             _participant,
@@ -572,7 +582,7 @@ class Thumbnail extends Component<Props, State> {
572 582
             || _disableTileEnlargement
573 583
             || _isScreenSharing;
574 584
 
575
-        if (canPlayEventReceived || _participant.local) {
585
+        if (canPlayEventReceived || _participant.local || _isFakeScreenShareParticipant) {
576 586
             videoStyles = {
577 587
                 objectFit: doNotStretchVideo ? 'contain' : 'cover'
578 588
             };
@@ -1014,7 +1024,7 @@ class Thumbnail extends Component<Props, State> {
1014 1024
      * @returns {ReactElement}
1015 1025
      */
1016 1026
     render() {
1017
-        const { _participant } = this.props;
1027
+        const { _participant, _isFakeScreenShareParticipant } = this.props;
1018 1028
 
1019 1029
         if (!_participant) {
1020 1030
             return null;
@@ -1030,6 +1040,29 @@ class Thumbnail extends Component<Props, State> {
1030 1040
             return this._renderFakeParticipant();
1031 1041
         }
1032 1042
 
1043
+        if (_isFakeScreenShareParticipant) {
1044
+            const { isHovered } = this.state;
1045
+            const { _videoTrack, _isMobile, classes } = this.props;
1046
+
1047
+            return (
1048
+                <FakeScreenShareParticipant
1049
+                    classes = { classes }
1050
+                    containerClassName = { this._getContainerClassName() }
1051
+                    isHovered = { isHovered }
1052
+                    isMobile = { _isMobile }
1053
+                    onClick = { this._onClick }
1054
+                    onMouseEnter = { this._onMouseEnter }
1055
+                    onMouseLeave = { this._onMouseLeave }
1056
+                    onMouseMove = { this._onMouseMove }
1057
+                    onTouchEnd = { this._onTouchEnd }
1058
+                    onTouchMove = { this._onTouchMove }
1059
+                    onTouchStart = { this._onTouchStart }
1060
+                    participantId = { _participant.id }
1061
+                    styles = { this._getStyles() }
1062
+                    videoTrack = { _videoTrack } />
1063
+            );
1064
+        }
1065
+
1033 1066
         return this._renderParticipant();
1034 1067
     }
1035 1068
 }
@@ -1049,8 +1082,16 @@ function _mapStateToProps(state, ownProps): Object {
1049 1082
     const id = participant?.id;
1050 1083
     const isLocal = participant?.local ?? true;
1051 1084
     const tracks = state['features/base/tracks'];
1052
-    const _videoTrack = isLocal
1053
-        ? getLocalVideoTrack(tracks) : getTrackByMediaTypeAndParticipant(tracks, MEDIA_TYPE.VIDEO, participantID);
1085
+    const sourceNameSignalingEnabled = getSourceNameSignalingFeatureFlag(state);
1086
+
1087
+    let _videoTrack;
1088
+
1089
+    if (sourceNameSignalingEnabled && participant?.isFakeScreenShareParticipant) {
1090
+        _videoTrack = getFakeScreenshareParticipantTrack(tracks, id);
1091
+    } else {
1092
+        _videoTrack = isLocal
1093
+            ? getLocalVideoTrack(tracks) : getTrackByMediaTypeAndParticipant(tracks, MEDIA_TYPE.VIDEO, participantID);
1094
+    }
1054 1095
     const _audioTrack = isLocal
1055 1096
         ? getLocalAudioTrack(tracks) : getTrackByMediaTypeAndParticipant(tracks, MEDIA_TYPE.AUDIO, participantID);
1056 1097
     const _currentLayout = stageFilmstrip ? LAYOUTS.TILE_VIEW : getCurrentLayout(state);
@@ -1144,6 +1185,7 @@ function _mapStateToProps(state, ownProps): Object {
1144 1185
         _isAudioOnly: Boolean(state['features/base/audio-only'].enabled),
1145 1186
         _isCurrentlyOnLargeVideo: state['features/large-video']?.participantId === id,
1146 1187
         _isDominantSpeakerDisabled: interfaceConfig.DISABLE_DOMINANT_SPEAKER_INDICATOR,
1188
+        _isFakeScreenShareParticipant: sourceNameSignalingEnabled && participant?.isFakeScreenShareParticipant,
1147 1189
         _isMobile,
1148 1190
         _isMobilePortrait,
1149 1191
         _isScreenSharing: _videoTrack?.videoType === 'desktop',

+ 15
- 7
react/features/filmstrip/components/web/ThumbnailBottomIndicators.js Näytä tiedosto

@@ -32,7 +32,12 @@ type Props = {
32 32
     /**
33 33
      * Id of the participant for which the component is displayed.
34 34
      */
35
-    participantId: string
35
+    participantId: string,
36
+
37
+    /**
38
+     * Whether or not to show the status indicators.
39
+     */
40
+    showStatusIndicators: string
36 41
 }
37 42
 
38 43
 const useStyles = makeStyles(() => {
@@ -58,7 +63,8 @@ const ThumbnailBottomIndicators = ({
58 63
     className,
59 64
     currentLayout,
60 65
     local,
61
-    participantId
66
+    participantId,
67
+    showStatusIndicators = true
62 68
 }: Props) => {
63 69
     const styles = useStyles();
64 70
     const _allowEditing = !useSelector(isNameReadOnly);
@@ -66,11 +72,13 @@ const ThumbnailBottomIndicators = ({
66 72
     const _showDisplayName = useSelector(isDisplayNameVisible);
67 73
 
68 74
     return (<div className = { className }>
69
-        <StatusIndicators
70
-            audio = { true }
71
-            moderator = { true }
72
-            participantID = { participantId }
73
-            screenshare = { currentLayout === LAYOUTS.TILE_VIEW } />
75
+        {
76
+            showStatusIndicators && <StatusIndicators
77
+                audio = { true }
78
+                moderator = { true }
79
+                participantID = { participantId }
80
+                screenshare = { currentLayout === LAYOUTS.TILE_VIEW } />
81
+        }
74 82
         {
75 83
             _showDisplayName && (
76 84
                 <span className = { styles.nameContainer }>

+ 23
- 1
react/features/filmstrip/components/web/ThumbnailTopIndicators.js Näytä tiedosto

@@ -5,6 +5,7 @@ import clsx from 'clsx';
5 5
 import React from 'react';
6 6
 import { useSelector } from 'react-redux';
7 7
 
8
+import { getSourceNameSignalingFeatureFlag } from '../../../base/config';
8 9
 import { isMobileBrowser } from '../../../base/environment/utils';
9 10
 import ConnectionIndicator from '../../../connection-indicator/components/web/ConnectionIndicator';
10 11
 import { LAYOUTS } from '../../../video-layout';
@@ -40,6 +41,11 @@ type Props = {
40 41
      */
41 42
     isHovered: boolean,
42 43
 
44
+    /**
45
+     * Whether or not the thumbnail is a fake screen share participant.
46
+     */
47
+    isFakeScreenShareParticipant: boolean,
48
+
43 49
     /**
44 50
      * Whether or not the indicators are for the local participant.
45 51
      */
@@ -77,6 +83,7 @@ const ThumbnailTopIndicators = ({
77 83
     currentLayout,
78 84
     hidePopover,
79 85
     indicatorsClassName,
86
+    isFakeScreenShareParticipant,
80 87
     isHovered,
81 88
     local,
82 89
     participantId,
@@ -92,9 +99,24 @@ const ThumbnailTopIndicators = ({
92 99
         useSelector(state => state['features/base/config'].connectionIndicators?.autoHide) ?? true);
93 100
     const _connectionIndicatorDisabled = _isMobile
94 101
         || Boolean(useSelector(state => state['features/base/config'].connectionIndicators?.disabled));
95
-
102
+    const sourceNameSignalingEnabled = useSelector(getSourceNameSignalingFeatureFlag);
96 103
     const showConnectionIndicator = isHovered || !_connectionIndicatorAutoHideEnabled;
97 104
 
105
+    if (sourceNameSignalingEnabled && isFakeScreenShareParticipant) {
106
+        return (
107
+            <div className = { styles.container }>
108
+                {!_connectionIndicatorDisabled
109
+                    && <ConnectionIndicator
110
+                        alwaysVisible = { showConnectionIndicator }
111
+                        enableStatsDisplay = { true }
112
+                        iconSize = { _indicatorIconSize }
113
+                        participantId = { participantId }
114
+                        statsPopoverPosition = { STATS_POPOVER_POSITION[currentLayout] } />
115
+                }
116
+            </div>
117
+        );
118
+    }
119
+
98 120
     return (
99 121
         <>
100 122
             <div className = { styles.container }>

+ 52
- 2
react/features/filmstrip/components/web/ThumbnailWrapper.js Näytä tiedosto

@@ -2,6 +2,7 @@
2 2
 import React, { Component } from 'react';
3 3
 import { shouldComponentUpdate } from 'react-window';
4 4
 
5
+import { getSourceNameSignalingFeatureFlag } from '../../../base/config';
5 6
 import { getLocalParticipant } from '../../../base/participants';
6 7
 import { connect } from '../../../base/redux';
7 8
 import { shouldHideSelfView } from '../../../base/settings/functions.any';
@@ -30,6 +31,11 @@ type Props = {
30 31
      */
31 32
     _participantID: ?string,
32 33
 
34
+    /**
35
+     * Whether or not the thumbnail is a local screen share.
36
+     */
37
+    _isLocalScreenShare: boolean,
38
+
33 39
     /**
34 40
      * Whether or not the filmstrip is used a stage filmstrip.
35 41
      */
@@ -84,6 +90,7 @@ class ThumbnailWrapper extends Component<Props> {
84 90
     render() {
85 91
         const {
86 92
             _disableSelfView,
93
+            _isLocalScreenShare = false,
87 94
             _horizontalOffset = 0,
88 95
             _participantID,
89 96
             _stageFilmstrip,
@@ -103,6 +110,15 @@ class ThumbnailWrapper extends Component<Props> {
103 110
                     style = { style } />);
104 111
         }
105 112
 
113
+        if (_isLocalScreenShare) {
114
+            return _disableSelfView ? null : (
115
+                <Thumbnail
116
+                    horizontalOffset = { _horizontalOffset }
117
+                    key = 'localScreenShare'
118
+                    participantID = { _participantID }
119
+                    style = { style } />);
120
+        }
121
+
106 122
         return (
107 123
             <Thumbnail
108 124
                 horizontalOffset = { _horizontalOffset }
@@ -128,6 +144,7 @@ function _mapStateToProps(state, ownProps) {
128 144
     const { testing = {} } = state['features/base/config'];
129 145
     const disableSelfView = shouldHideSelfView(state);
130 146
     const enableThumbnailReordering = testing.enableThumbnailReordering ?? true;
147
+    const sourceNameSignalingEnabled = getSourceNameSignalingFeatureFlag(state);
131 148
     const _verticalViewGrid = showGridInVerticalView(state);
132 149
     const stageFilmstrip = ownProps.data?.stageFilmstrip;
133 150
     const remoteParticipants = stageFilmstrip ? activeParticipants : remote;
@@ -152,9 +169,22 @@ function _mapStateToProps(state, ownProps) {
152 169
         const index = (rowIndex * columns) + columnIndex;
153 170
         let horizontalOffset;
154 171
         const { iAmRecorder } = state['features/base/config'];
155
-        const participantsLength = stageFilmstrip ? remoteParticipantsLength
172
+        let participantsLength = stageFilmstrip ? remoteParticipantsLength
156 173
             : remoteParticipantsLength + (iAmRecorder ? 0 : 1) - (disableSelfView ? 1 : 0);
157 174
 
175
+        const { localScreenShare } = state['features/base/participants'];
176
+        const localParticipantsLength = localScreenShare ? 2 : 1;
177
+
178
+        if (sourceNameSignalingEnabled) {
179
+            participantsLength = remoteParticipantsLength
180
+
181
+            // Add local camera and screen share to total participant count when self view is not disabled.
182
+            + (disableSelfView ? 0 : localParticipantsLength)
183
+
184
+            // Removes iAmRecorder from the total participants count.
185
+            - (iAmRecorder ? 1 : 0);
186
+        }
187
+
158 188
         if (rowIndex === rows - 1) { // center the last row
159 189
             const { width: thumbnailWidth } = thumbnailSize;
160 190
             const partialLastRowParticipantsNumber = participantsLength % columns;
@@ -179,7 +209,18 @@ function _mapStateToProps(state, ownProps) {
179 209
 
180 210
         // When the thumbnails are reordered, local participant is inserted at index 0.
181 211
         const localIndex = enableThumbnailReordering && !disableSelfView ? 0 : remoteParticipantsLength;
182
-        const remoteIndex = enableThumbnailReordering && !iAmRecorder && !disableSelfView ? index - 1 : index;
212
+
213
+        // Local screen share is inserted at index 1 after the local camera.
214
+        const localScreenShareIndex = enableThumbnailReordering && !disableSelfView ? 1 : remoteParticipantsLength;
215
+
216
+        let remoteIndex;
217
+
218
+        if (sourceNameSignalingEnabled) {
219
+            remoteIndex = enableThumbnailReordering && !iAmRecorder && !disableSelfView
220
+                ? index - localParticipantsLength : index;
221
+        } else {
222
+            remoteIndex = enableThumbnailReordering && !iAmRecorder && !disableSelfView ? index - 1 : index;
223
+        }
183 224
 
184 225
         if (!iAmRecorder && index === localIndex) {
185 226
             return {
@@ -189,6 +230,15 @@ function _mapStateToProps(state, ownProps) {
189 230
             };
190 231
         }
191 232
 
233
+        if (sourceNameSignalingEnabled && !iAmRecorder && localScreenShare && index === localScreenShareIndex) {
234
+            return {
235
+                _disableSelfView: disableSelfView,
236
+                _isLocalScreenShare: true,
237
+                _participantID: localScreenShare?.id,
238
+                _horizontalOffset: horizontalOffset
239
+            };
240
+        }
241
+
192 242
         return {
193 243
             _participantID: remoteParticipants[remoteIndex],
194 244
             _horizontalOffset: horizontalOffset

+ 50
- 11
react/features/filmstrip/functions.any.js Näytä tiedosto

@@ -1,5 +1,8 @@
1 1
 // @flow
2 2
 
3
+import { getSourceNameSignalingFeatureFlag } from '../base/config';
4
+import { getFakeScreenShareParticipantOwnerId } from '../base/participants';
5
+
3 6
 import { setRemoteParticipants } from './actions';
4 7
 import { isReorderingEnabled } from './functions';
5 8
 
@@ -15,7 +18,9 @@ export function updateRemoteParticipants(store: Object, participantId: ?number)
15 18
     const state = store.getState();
16 19
     let reorderedParticipants = [];
17 20
 
18
-    if (!isReorderingEnabled(state)) {
21
+    const { sortedRemoteFakeScreenShareParticipants } = state['features/base/participants'];
22
+
23
+    if (!isReorderingEnabled(state) && !sortedRemoteFakeScreenShareParticipants.size) {
19 24
         if (participantId) {
20 25
             const { remoteParticipants } = state['features/filmstrip'];
21 26
 
@@ -34,13 +39,28 @@ export function updateRemoteParticipants(store: Object, participantId: ?number)
34 39
     } = state['features/base/participants'];
35 40
     const remoteParticipants = new Map(sortedRemoteParticipants);
36 41
     const screenShares = new Map(sortedRemoteScreenshares);
42
+    const screenShareParticipants = sortedRemoteFakeScreenShareParticipants
43
+        ? [ ...sortedRemoteFakeScreenShareParticipants.keys() ] : [];
37 44
     const sharedVideos = fakeParticipants ? Array.from(fakeParticipants.keys()) : [];
38 45
     const speakers = new Map(speakersList);
39 46
 
40
-    for (const screenshare of screenShares.keys()) {
41
-        remoteParticipants.delete(screenshare);
42
-        speakers.delete(screenshare);
47
+    if (getSourceNameSignalingFeatureFlag(state)) {
48
+        for (const screenshare of screenShareParticipants) {
49
+            const ownerId = getFakeScreenShareParticipantOwnerId(screenshare);
50
+
51
+            remoteParticipants.delete(ownerId);
52
+            remoteParticipants.delete(screenshare);
53
+
54
+            speakers.delete(ownerId);
55
+            speakers.delete(screenshare);
56
+        }
57
+    } else {
58
+        for (const screenshare of screenShares.keys()) {
59
+            remoteParticipants.delete(screenshare);
60
+            speakers.delete(screenshare);
61
+        }
43 62
     }
63
+
44 64
     for (const sharedVideo of sharedVideos) {
45 65
         remoteParticipants.delete(sharedVideo);
46 66
         speakers.delete(sharedVideo);
@@ -49,13 +69,32 @@ export function updateRemoteParticipants(store: Object, participantId: ?number)
49 69
         remoteParticipants.delete(speaker);
50 70
     }
51 71
 
52
-    // Always update the order of the thumnails.
53
-    reorderedParticipants = [
54
-        ...Array.from(screenShares.keys()),
55
-        ...sharedVideos,
56
-        ...Array.from(speakers.keys()),
57
-        ...Array.from(remoteParticipants.keys())
58
-    ];
72
+    if (getSourceNameSignalingFeatureFlag(state)) {
73
+        // Always update the order of the thumnails.
74
+        const participantsWithScreenShare = screenShareParticipants.reduce((acc, screenshare) => {
75
+            const ownerId = getFakeScreenShareParticipantOwnerId(screenshare);
76
+
77
+            acc.push(ownerId);
78
+            acc.push(screenshare);
79
+
80
+            return acc;
81
+        }, []);
82
+
83
+        reorderedParticipants = [
84
+            ...participantsWithScreenShare,
85
+            ...sharedVideos,
86
+            ...Array.from(speakers.keys()),
87
+            ...Array.from(remoteParticipants.keys())
88
+        ];
89
+    } else {
90
+        // Always update the order of the thumnails.
91
+        reorderedParticipants = [
92
+            ...Array.from(screenShares.keys()),
93
+            ...sharedVideos,
94
+            ...Array.from(speakers.keys()),
95
+            ...Array.from(remoteParticipants.keys())
96
+        ];
97
+    }
59 98
 
60 99
     store.dispatch(setRemoteParticipants(reorderedParticipants));
61 100
 }

+ 10
- 1
react/features/filmstrip/functions.web.js Näytä tiedosto

@@ -229,9 +229,11 @@ export function getTileDefaultAspectRatio(disableResponsiveTiles, disableTileEnl
229 229
 export function getNumberOfPartipantsForTileView(state) {
230 230
     const { iAmRecorder } = state['features/base/config'];
231 231
     const disableSelfView = shouldHideSelfView(state);
232
+    const { localScreenShare } = state['features/base/participants'];
233
+    const localParticipantsCount = getSourceNameSignalingFeatureFlag(state) && localScreenShare ? 2 : 1;
232 234
     const numberOfParticipants = getParticipantCountWithFake(state)
233 235
         - (iAmRecorder ? 1 : 0)
234
-        - (disableSelfView ? 1 : 0);
236
+        - (disableSelfView ? localParticipantsCount : 0);
235 237
 
236 238
     return numberOfParticipants;
237 239
 }
@@ -492,6 +494,7 @@ export function computeDisplayModeFromInput(input: Object) {
492 494
         isActiveParticipant,
493 495
         isAudioOnly,
494 496
         isCurrentlyOnLargeVideo,
497
+        isFakeScreenShareParticipant,
495 498
         isScreenSharing,
496 499
         canPlayEventReceived,
497 500
         isRemoteParticipant,
@@ -499,6 +502,10 @@ export function computeDisplayModeFromInput(input: Object) {
499 502
     } = input;
500 503
     const adjustedIsVideoPlayable = input.isVideoPlayable && (!isRemoteParticipant || canPlayEventReceived);
501 504
 
505
+    if (isFakeScreenShareParticipant) {
506
+        return DISPLAY_VIDEO;
507
+    }
508
+
502 509
     if (!tileViewActive && ((isScreenSharing && isRemoteParticipant) || isActiveParticipant)) {
503 510
         return DISPLAY_AVATAR;
504 511
     } else if (isCurrentlyOnLargeVideo && !tileViewActive) {
@@ -526,6 +533,7 @@ export function getDisplayModeInput(props: Object, state: Object) {
526 533
         _isActiveParticipant,
527 534
         _isAudioOnly,
528 535
         _isCurrentlyOnLargeVideo,
536
+        _isFakeScreenShareParticipant,
529 537
         _isScreenSharing,
530 538
         _isVideoPlayable,
531 539
         _participant,
@@ -545,6 +553,7 @@ export function getDisplayModeInput(props: Object, state: Object) {
545 553
         videoStream: Boolean(_videoTrack),
546 554
         isRemoteParticipant: !_participant?.isFakeParticipant && !_participant?.local,
547 555
         isScreenSharing: _isScreenSharing,
556
+        isFakeScreenShareParticipant: _isFakeScreenShareParticipant,
548 557
         videoStreamMuted: _videoTrack ? _videoTrack.muted : 'no stream'
549 558
     };
550 559
 }

+ 4
- 0
react/features/filmstrip/middleware.web.js Näytä tiedosto

@@ -83,6 +83,10 @@ MiddlewareRegistry.register(store => next => action => {
83 83
     }
84 84
     case PARTICIPANT_JOINED: {
85 85
         result = next(action);
86
+        if (action.participant?.isLocalScreenShare) {
87
+            break;
88
+        }
89
+
86 90
         updateRemoteParticipants(store, action.participant?.id);
87 91
         break;
88 92
     }

+ 2
- 1
react/features/filmstrip/subscriber.web.js Näytä tiedosto

@@ -38,7 +38,8 @@ StateListenerRegistry.register(
38 38
     /* selector */ state => {
39 39
         return {
40 40
             numberOfParticipants: getParticipantCountWithFake(state),
41
-            disableSelfView: shouldHideSelfView(state)
41
+            disableSelfView: shouldHideSelfView(state),
42
+            localScreenShare: state['features/base/participants'].localScreenShare
42 43
         };
43 44
     },
44 45
     /* listener */ (currentState, store) => {

+ 10
- 0
react/features/video-layout/actionTypes.js Näytä tiedosto

@@ -10,6 +10,16 @@
10 10
 export const SCREEN_SHARE_REMOTE_PARTICIPANTS_UPDATED
11 11
     = 'SCREEN_SHARE_REMOTE_PARTICIPANTS_UPDATED';
12 12
 
13
+/**
14
+ * The type of the action which sets the list of known remote fake screen share participant IDs.
15
+ *
16
+ * @returns {{
17
+ *     type: FAKE_SCREEN_SHARE_REMOTE_PARTICIPANTS_UPDATED,
18
+ *     participantIds: Array<string>
19
+ * }}
20
+ */
21
+export const FAKE_SCREEN_SHARE_REMOTE_PARTICIPANTS_UPDATED = 'FAKE_SCREEN_SHARE_REMOTE_PARTICIPANTS_UPDATED';
22
+
13 23
 /**
14 24
  * The type of the action which enables or disables the feature for showing
15 25
  * video thumbnails in a two-axis tile view.

+ 17
- 0
react/features/video-layout/actions.js Näytä tiedosto

@@ -3,6 +3,7 @@
3 3
 import type { Dispatch } from 'redux';
4 4
 
5 5
 import {
6
+    FAKE_SCREEN_SHARE_REMOTE_PARTICIPANTS_UPDATED,
6 7
     SCREEN_SHARE_REMOTE_PARTICIPANTS_UPDATED,
7 8
     SET_TILE_VIEW
8 9
 } from './actionTypes';
@@ -26,6 +27,22 @@ export function setRemoteParticipantsWithScreenShare(participantIds: Array<strin
26 27
     };
27 28
 }
28 29
 
30
+/**
31
+ * Creates a (redux) action which signals that the list of known remote fake screen share participant ids has changed.
32
+ *
33
+ * @param {string} participantIds - The remote fake screen share participants.
34
+ * @returns {{
35
+ *     type: FAKE_SCREEN_SHARE_REMOTE_PARTICIPANTS_UPDATED,
36
+ *     participantIds: Array<string>
37
+ * }}
38
+ */
39
+export function fakeScreenshareParticipantsUpdated(participantIds: Array<string>) {
40
+    return {
41
+        type: FAKE_SCREEN_SHARE_REMOTE_PARTICIPANTS_UPDATED,
42
+        participantIds
43
+    };
44
+}
45
+
29 46
 /**
30 47
  * Creates a (redux) action which signals to set the UI layout to be tiled view
31 48
  * or not.

+ 2
- 0
react/features/video-layout/reducer.js Näytä tiedosto

@@ -3,6 +3,7 @@
3 3
 import { ReducerRegistry } from '../base/redux';
4 4
 
5 5
 import {
6
+    FAKE_SCREEN_SHARE_REMOTE_PARTICIPANTS_UPDATED,
6 7
     SCREEN_SHARE_REMOTE_PARTICIPANTS_UPDATED,
7 8
     SET_TILE_VIEW
8 9
 } from './actionTypes';
@@ -27,6 +28,7 @@ const STORE_NAME = 'features/video-layout';
27 28
 
28 29
 ReducerRegistry.register(STORE_NAME, (state = DEFAULT_STATE, action) => {
29 30
     switch (action.type) {
31
+    case FAKE_SCREEN_SHARE_REMOTE_PARTICIPANTS_UPDATED:
30 32
     case SCREEN_SHARE_REMOTE_PARTICIPANTS_UPDATED: {
31 33
         return {
32 34
             ...state,

+ 35
- 2
react/features/video-layout/subscriber.js Näytä tiedosto

@@ -2,12 +2,45 @@
2 2
 
3 3
 import debounce from 'lodash/debounce';
4 4
 
5
+import { getSourceNameSignalingFeatureFlag } from '../base/config';
5 6
 import { StateListenerRegistry, equals } from '../base/redux';
6 7
 import { isFollowMeActive } from '../follow-me';
7 8
 
8
-import { setRemoteParticipantsWithScreenShare } from './actions';
9
+import { setRemoteParticipantsWithScreenShare, fakeScreenshareParticipantsUpdated } from './actions';
9 10
 import { getAutoPinSetting, updateAutoPinnedParticipant } from './functions';
10 11
 
12
+StateListenerRegistry.register(
13
+    /* selector */ state => state['features/base/participants'].sortedRemoteFakeScreenShareParticipants,
14
+    /* listener */ (sortedRemoteFakeScreenShareParticipants, store) => {
15
+        if (!getAutoPinSetting() || isFollowMeActive(store) || !getSourceNameSignalingFeatureFlag(store.getState())) {
16
+            return;
17
+        }
18
+
19
+        const oldScreenSharesOrder = store.getState()['features/video-layout'].remoteScreenShares || [];
20
+        const knownSharingParticipantIds = [ ...sortedRemoteFakeScreenShareParticipants.keys() ];
21
+
22
+        // Filter out any participants which are no longer screen sharing
23
+        // by looping through the known sharing participants and removing any
24
+        // participant IDs which are no longer sharing.
25
+        const newScreenSharesOrder = oldScreenSharesOrder.filter(
26
+            participantId => knownSharingParticipantIds.includes(participantId));
27
+
28
+        // Make sure all new sharing participant get added to the end of the
29
+        // known screen shares.
30
+        knownSharingParticipantIds.forEach(participantId => {
31
+            if (!newScreenSharesOrder.includes(participantId)) {
32
+                newScreenSharesOrder.push(participantId);
33
+            }
34
+        });
35
+
36
+        if (!equals(oldScreenSharesOrder, newScreenSharesOrder)) {
37
+            store.dispatch(fakeScreenshareParticipantsUpdated(newScreenSharesOrder));
38
+
39
+            updateAutoPinnedParticipant(oldScreenSharesOrder, store);
40
+        }
41
+    });
42
+
43
+
11 44
 /**
12 45
  * For auto-pin mode, listen for changes to the known media tracks and look
13 46
  * for updates to screen shares. The listener is debounced to avoid state
@@ -20,7 +53,7 @@ StateListenerRegistry.register(
20 53
         // possible to have screen sharing participant that has already left in the remoteScreenShares array.
21 54
         // This can lead to rendering a thumbnails for already left participants since the remoteScreenShares
22 55
         // array is used for building the ordered list of remote participants.
23
-        if (!getAutoPinSetting() || isFollowMeActive(store)) {
56
+        if (!getAutoPinSetting() || isFollowMeActive(store) || getSourceNameSignalingFeatureFlag(store.getState())) {
24 57
             return;
25 58
         }
26 59
 

+ 16
- 10
react/features/video-quality/subscriber.js Näytä tiedosto

@@ -250,7 +250,13 @@ function _updateReceiverVideoConstraints({ getState }) {
250 250
 
251 251
         if (visibleRemoteParticipants?.size) {
252 252
             visibleRemoteParticipants.forEach(participantId => {
253
-                const sourceName = getTrackSourceNameByMediaTypeAndParticipant(tracks, MEDIA_TYPE.VIDEO, participantId);
253
+                let sourceName;
254
+
255
+                if (remoteScreenShares.includes(participantId)) {
256
+                    sourceName = participantId;
257
+                } else {
258
+                    sourceName = getTrackSourceNameByMediaTypeAndParticipant(tracks, MEDIA_TYPE.VIDEO, participantId);
259
+                }
254 260
 
255 261
                 if (sourceName) {
256 262
                     visibleRemoteTrackSourceNames.push(sourceName);
@@ -262,10 +268,14 @@ function _updateReceiverVideoConstraints({ getState }) {
262 268
         }
263 269
 
264 270
         if (localParticipantId !== largeVideoParticipantId) {
265
-            largeVideoSourceName = getTrackSourceNameByMediaTypeAndParticipant(
266
-                tracks, MEDIA_TYPE.VIDEO,
267
-                largeVideoParticipantId
268
-            );
271
+            if (remoteScreenShares.includes(largeVideoParticipantId)) {
272
+                largeVideoSourceName = largeVideoParticipantId;
273
+            } else {
274
+                largeVideoSourceName = getTrackSourceNameByMediaTypeAndParticipant(
275
+                    tracks, MEDIA_TYPE.VIDEO,
276
+                    largeVideoParticipantId
277
+                );
278
+            }
269 279
         }
270 280
 
271 281
         // Tile view.
@@ -280,11 +290,7 @@ function _updateReceiverVideoConstraints({ getState }) {
280 290
 
281 291
             // Prioritize screenshare in tile view.
282 292
             if (remoteScreenShares?.length) {
283
-                const remoteScreenShareSourceNames = remoteScreenShares.map(remoteScreenShare =>
284
-                    getTrackSourceNameByMediaTypeAndParticipant(tracks, MEDIA_TYPE.VIDEO, remoteScreenShare)
285
-                );
286
-
287
-                receiverConstraints.selectedSources = remoteScreenShareSourceNames;
293
+                receiverConstraints.selectedSources = remoteScreenShares;
288 294
             }
289 295
 
290 296
         // Stage view.

Loading…
Peruuta
Tallenna