Bläddra i källkod

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 år sedan
förälder
incheckning
70090fd716
Inget konto är kopplat till bidragsgivarens mejladress
31 ändrade filer med 852 tillägg och 80 borttagningar
  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 Visa fil

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

+ 2
- 1
css/filmstrip/_tile_view_overrides.scss Visa fil

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

+ 20
- 1
css/filmstrip/_vertical_filmstrip.scss Visa fil

87
             .videocontainer {
87
             .videocontainer {
88
                 height: 0px;
88
                 height: 0px;
89
                 width: 100%;
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
      * filmstrip from overlapping the left edge of the screen.
115
      * filmstrip from overlapping the left edge of the screen.
98
      */
116
      */
99
     #filmstripLocalVideo,
117
     #filmstripLocalVideo,
118
+    #filmstripLocalScreenShare,
100
     .remote-videos {
119
     .remote-videos {
101
         padding: 0;
120
         padding: 0;
102
     }
121
     }

+ 20
- 13
modules/UI/videolayout/LargeVideoManager.js Visa fil

11
 import theme from '../../../react/features/base/components/themes/participantsPaneTheme.json';
11
 import theme from '../../../react/features/base/components/themes/participantsPaneTheme.json';
12
 import { getSourceNameSignalingFeatureFlag } from '../../../react/features/base/config';
12
 import { getSourceNameSignalingFeatureFlag } from '../../../react/features/base/config';
13
 import { i18next } from '../../../react/features/base/i18n';
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
 import {
15
 import {
16
     getParticipantById,
16
     getParticipantById,
17
     getParticipantDisplayName
17
     getParticipantDisplayName
18
 } from '../../../react/features/base/participants';
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
 import { CHAT_SIZE } from '../../../react/features/chat';
22
 import { CHAT_SIZE } from '../../../react/features/chat';
21
 import {
23
 import {
22
     isParticipantConnectionStatusActive,
24
     isParticipantConnectionStatusActive,
237
             let isVideoRenderable;
239
             let isVideoRenderable;
238
 
240
 
239
             if (getSourceNameSignalingFeatureFlag(state)) {
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
             } else {
250
             } else {
246
                 isVideoRenderable = !isVideoMuted
251
                 isVideoRenderable = !isVideoMuted
247
                     && (APP.conference.isLocalId(id) || isParticipantConnectionStatusActive(participant));
252
                     && (APP.conference.isLocalId(id) || isParticipantConnectionStatusActive(participant));
268
 
273
 
269
                         && participant && !participant.local && !participant.isFakeParticipant) {
274
                         && participant && !participant.local && !participant.isFakeParticipant) {
270
                     // remote participant only
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
                     const isScreenSharing = track?.videoType === 'desktop';
280
                     const isScreenSharing = track?.videoType === 'desktop';
274
 
281
 
275
                     if (isScreenSharing) {
282
                     if (isScreenSharing) {
300
             let messageKey;
307
             let messageKey;
301
 
308
 
302
             if (getSourceNameSignalingFeatureFlag(state)) {
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
                 messageKey = isTrackStreamingStatusInactive(videoTrack) ? 'connection.LOW_BANDWIDTH' : null;
313
                 messageKey = isTrackStreamingStatusInactive(videoTrack) ? 'connection.LOW_BANDWIDTH' : null;
307
             } else {
314
             } else {
541
             const state = APP.store.getState();
548
             const state = APP.store.getState();
542
 
549
 
543
             if (getSourceNameSignalingFeatureFlag(state)) {
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
                 // eslint-disable-next-line no-param-reassign
554
                 // eslint-disable-next-line no-param-reassign
548
                 show = !APP.conference.isLocalId(this.id)
555
                 show = !APP.conference.isLocalId(this.id)

+ 20
- 2
modules/UI/videolayout/VideoLayout.js Visa fil

2
 
2
 
3
 import Logger from '@jitsi/logger';
3
 import Logger from '@jitsi/logger';
4
 
4
 
5
+import { getSourceNameSignalingFeatureFlag } from '../../../react/features/base/config';
5
 import { MEDIA_TYPE, VIDEO_TYPE } from '../../../react/features/base/media';
6
 import { MEDIA_TYPE, VIDEO_TYPE } from '../../../react/features/base/media';
6
 import {
7
 import {
7
     getPinnedParticipant,
8
     getPinnedParticipant,
8
     getParticipantById
9
     getParticipantById
9
 } from '../../../react/features/base/participants';
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
 import LargeVideoManager from './LargeVideoManager';
16
 import LargeVideoManager from './LargeVideoManager';
13
 import { VIDEO_CONTAINER_TYPE } from './VideoContainer';
17
 import { VIDEO_CONTAINER_TYPE } from './VideoContainer';
91
             return VIDEO_TYPE.CAMERA;
95
             return VIDEO_TYPE.CAMERA;
92
         }
96
         }
93
 
97
 
98
+        if (getSourceNameSignalingFeatureFlag(state) && participant?.isFakeScreenShareParticipant) {
99
+            return VIDEO_TYPE.DESKTOP;
100
+        }
101
+
94
         const videoTrack = getTrackByMediaTypeAndParticipant(state['features/base/tracks'], MEDIA_TYPE.VIDEO, id);
102
         const videoTrack = getTrackByMediaTypeAndParticipant(state['features/base/tracks'], MEDIA_TYPE.VIDEO, id);
95
 
103
 
96
         return videoTrack?.videoType;
104
         return videoTrack?.videoType;
177
         const currentContainerType = largeVideo.getCurrentContainerType();
185
         const currentContainerType = largeVideo.getCurrentContainerType();
178
         const isOnLarge = this.isCurrentlyOnLarge(id);
186
         const isOnLarge = this.isCurrentlyOnLarge(id);
179
         const state = APP.store.getState();
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
         const videoStream = videoTrack?.jitsiTrack;
199
         const videoStream = videoTrack?.jitsiTrack;
182
 
200
 
183
         if (isOnLarge && !forceUpdate
201
         if (isOnLarge && !forceUpdate

+ 6
- 1
react/features/base/lastn/middleware.js Visa fil

5
 import { SET_FILMSTRIP_ENABLED } from '../../filmstrip/actionTypes';
5
 import { SET_FILMSTRIP_ENABLED } from '../../filmstrip/actionTypes';
6
 import { SELECT_LARGE_VIDEO_PARTICIPANT } from '../../large-video/actionTypes';
6
 import { SELECT_LARGE_VIDEO_PARTICIPANT } from '../../large-video/actionTypes';
7
 import { APP_STATE_CHANGED } from '../../mobile/background/actionTypes';
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
 import { SET_AUDIO_ONLY } from '../audio-only/actionTypes';
13
 import { SET_AUDIO_ONLY } from '../audio-only/actionTypes';
10
 import { CONFERENCE_JOINED } from '../conference/actionTypes';
14
 import { CONFERENCE_JOINED } from '../conference/actionTypes';
11
 import {
15
 import {
92
     switch (action.type) {
96
     switch (action.type) {
93
     case APP_STATE_CHANGED:
97
     case APP_STATE_CHANGED:
94
     case CONFERENCE_JOINED:
98
     case CONFERENCE_JOINED:
99
+    case FAKE_SCREEN_SHARE_REMOTE_PARTICIPANTS_UPDATED:
95
     case PARTICIPANT_JOINED:
100
     case PARTICIPANT_JOINED:
96
     case PARTICIPANT_KICKED:
101
     case PARTICIPANT_KICKED:
97
     case PARTICIPANT_LEFT:
102
     case PARTICIPANT_LEFT:

+ 38
- 6
react/features/base/participants/functions.js Visa fil

5
 
5
 
6
 import { isStageFilmstripEnabled } from '../../filmstrip/functions';
6
 import { isStageFilmstripEnabled } from '../../filmstrip/functions';
7
 import { GRAVATAR_BASE_URL, isCORSAvatarURL } from '../avatar';
7
 import { GRAVATAR_BASE_URL, isCORSAvatarURL } from '../avatar';
8
+import { getSourceNameSignalingFeatureFlag } from '../config';
8
 import { JitsiParticipantConnectionStatus } from '../lib-jitsi-meet';
9
 import { JitsiParticipantConnectionStatus } from '../lib-jitsi-meet';
9
 import { MEDIA_TYPE, shouldRenderVideoTrack } from '../media';
10
 import { MEDIA_TYPE, shouldRenderVideoTrack } from '../media';
10
 import { toState } from '../redux';
11
 import { toState } from '../redux';
119
 export function getParticipantById(
120
 export function getParticipantById(
120
         stateful: Object | Function, id: string): ?Object {
121
         stateful: Object | Function, id: string): ?Object {
121
     const state = toState(stateful)['features/base/participants'];
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
  * @returns {number}
151
  * @returns {number}
149
  */
152
  */
150
 export function getParticipantCount(stateful: Object | Function) {
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
     return remote.size - fakeParticipants.size + (local ? 1 : 0);
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
 export function getRemoteParticipantCount(stateful: Object | Function) {
201
 export function getRemoteParticipantCount(stateful: Object | Function) {
178
     const state = toState(stateful)['features/base/participants'];
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
     return state.remote.size;
208
     return state.remote.size;
181
 }
209
 }
182
 
210
 
190
  * @returns {number}
218
  * @returns {number}
191
  */
219
  */
192
 export function getParticipantCountWithFake(stateful: Object | Function) {
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
     return remote.size + (local ? 1 : 0);
228
     return remote.size + (local ? 1 : 0);
197
 }
229
 }

+ 48
- 4
react/features/base/participants/reducer.js Visa fil

1
 // @flow
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
 import { ReducerRegistry, set } from '../redux';
6
 import { ReducerRegistry, set } from '../redux';
5
 
7
 
6
 import {
8
 import {
59
     fakeParticipants: new Map(),
61
     fakeParticipants: new Map(),
60
     haveParticipantWithScreenSharingFeature: false,
62
     haveParticipantWithScreenSharingFeature: false,
61
     local: undefined,
63
     local: undefined,
64
+    localScreenShare: undefined,
62
     pinnedParticipant: undefined,
65
     pinnedParticipant: undefined,
63
     raisedHandsQueue: [],
66
     raisedHandsQueue: [],
64
     remote: new Map(),
67
     remote: new Map(),
68
+    sortedRemoteFakeScreenShareParticipants: new Map(),
65
     sortedRemoteParticipants: new Map(),
69
     sortedRemoteParticipants: new Map(),
66
     sortedRemoteScreenshares: new Map(),
70
     sortedRemoteScreenshares: new Map(),
67
     speakersList: new Map()
71
     speakersList: new Map()
207
     }
211
     }
208
     case PARTICIPANT_JOINED: {
212
     case PARTICIPANT_JOINED: {
209
         const participant = _participantJoined(action);
213
         const participant = _participantJoined(action);
210
-        const { id, isFakeParticipant, name, pinned } = participant;
214
+        const { id, isFakeParticipant, isFakeScreenShareParticipant, isLocalScreenShare, name, pinned } = participant;
211
         const { pinnedParticipant, dominantSpeaker } = state;
215
         const { pinnedParticipant, dominantSpeaker } = state;
212
 
216
 
213
         if (pinned) {
217
         if (pinned) {
241
             };
245
             };
242
         }
246
         }
243
 
247
 
248
+        if (isLocalScreenShare) {
249
+            return {
250
+                ...state,
251
+                localScreenShare: participant
252
+            };
253
+        }
254
+
244
         state.remote.set(id, participant);
255
         state.remote.set(id, participant);
245
 
256
 
246
         // Insert the new participant.
257
         // Insert the new participant.
253
         // The sort order of participants is preserved since Map remembers the original insertion order of the keys.
264
         // The sort order of participants is preserved since Map remembers the original insertion order of the keys.
254
         state.sortedRemoteParticipants = new Map(sortedRemoteParticipants);
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
         if (isFakeParticipant) {
275
         if (isFakeParticipant) {
257
             state.fakeParticipants.set(id, participant);
276
             state.fakeParticipants.set(id, participant);
258
         }
277
         }
267
         // (and the fact that the local participant "joins" at the beginning of
286
         // (and the fact that the local participant "joins" at the beginning of
268
         // the app and "leaves" at the end of the app).
287
         // the app and "leaves" at the end of the app).
269
         const { conference, id } = action.participant;
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
         let oldParticipant = remote.get(id);
298
         let oldParticipant = remote.get(id);
272
 
299
 
273
         if (oldParticipant && oldParticipant.conference === conference) {
300
         if (oldParticipant && oldParticipant.conference === conference) {
275
         } else if (local?.id === id) {
302
         } else if (local?.id === id) {
276
             oldParticipant = state.local;
303
             oldParticipant = state.local;
277
             delete state.local;
304
             delete state.local;
305
+        } else if (localScreenShare?.id === id) {
306
+            oldParticipant = state.local;
307
+            delete state.localScreenShare;
278
         } else {
308
         } else {
279
             // no participant found
309
             // no participant found
280
             return state;
310
             return state;
324
             fakeParticipants.delete(id);
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
         return { ...state };
362
         return { ...state };
328
     }
363
     }
329
     case RAISE_HAND_UPDATED: {
364
     case RAISE_HAND_UPDATED: {
447
         dominantSpeaker,
482
         dominantSpeaker,
448
         email,
483
         email,
449
         isFakeParticipant,
484
         isFakeParticipant,
485
+        isFakeScreenShareParticipant,
486
+        isLocalScreenShare,
450
         isReplacing,
487
         isReplacing,
451
         isJigasi,
488
         isJigasi,
452
         loadableAvatarUrl,
489
         loadableAvatarUrl,
479
         email,
516
         email,
480
         id,
517
         id,
481
         isFakeParticipant,
518
         isFakeParticipant,
519
+        isFakeScreenShareParticipant,
520
+        isLocalScreenShare,
482
         isReplacing,
521
         isReplacing,
483
         isJigasi,
522
         isJigasi,
484
         loadableAvatarUrl,
523
         loadableAvatarUrl,
500
  * @returns {boolean} - True if a participant was updated and false otherwise.
539
  * @returns {boolean} - True if a participant was updated and false otherwise.
501
  */
540
  */
502
 function _updateParticipantProperty(state, id, property, value) {
541
 function _updateParticipantProperty(state, id, property, value) {
503
-    const { remote, local } = state;
542
+    const { remote, local, localScreenShare } = state;
504
 
543
 
505
     if (remote.has(id)) {
544
     if (remote.has(id)) {
506
         remote.set(id, set(remote.get(id), property, value));
545
         remote.set(id, set(remote.get(id), property, value));
511
         // not in a conference.
550
         // not in a conference.
512
         state.local = set(local, property, value);
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
         return true;
558
         return true;
515
     }
559
     }
516
 
560
 

+ 12
- 0
react/features/base/tracks/actionTypes.js Visa fil

107
  */
107
  */
108
 export const TRACK_UPDATED = 'TRACK_UPDATED';
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
  * The type of redux action dispatched when a local track starts being created
123
  * The type of redux action dispatched when a local track starts being created
112
  * via a WebRTC {@code getUserMedia} call. The action's payload includes an
124
  * via a WebRTC {@code getUserMedia} call. The action's payload includes an

+ 26
- 2
react/features/base/tracks/actions.js Visa fil

6
 } from '../../analytics';
6
 } from '../../analytics';
7
 import { NOTIFICATION_TIMEOUT_TYPE, showErrorNotification, showNotification } from '../../notifications';
7
 import { NOTIFICATION_TIMEOUT_TYPE, showErrorNotification, showNotification } from '../../notifications';
8
 import { getCurrentConference } from '../conference';
8
 import { getCurrentConference } from '../conference';
9
-import { getMultipleVideoSupportFeatureFlag } from '../config';
9
+import { getMultipleVideoSupportFeatureFlag, getSourceNameSignalingFeatureFlag } from '../config';
10
 import { JitsiTrackErrors, JitsiTrackEvents, createLocalTrack } from '../lib-jitsi-meet';
10
 import { JitsiTrackErrors, JitsiTrackEvents, createLocalTrack } from '../lib-jitsi-meet';
11
 import {
11
 import {
12
     CAMERA_FACING_MODE,
12
     CAMERA_FACING_MODE,
21
 import { updateSettings } from '../settings';
21
 import { updateSettings } from '../settings';
22
 
22
 
23
 import {
23
 import {
24
+    SCREENSHARE_TRACK_MUTED_UPDATED,
24
     SET_NO_SRC_DATA_NOTIFICATION_UID,
25
     SET_NO_SRC_DATA_NOTIFICATION_UID,
25
     TOGGLE_SCREENSHARING,
26
     TOGGLE_SCREENSHARING,
26
     TRACK_ADDED,
27
     TRACK_ADDED,
395
     return async (dispatch, getState) => {
396
     return async (dispatch, getState) => {
396
         track.on(
397
         track.on(
397
             JitsiTrackEvents.TRACK_MUTE_CHANGED,
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
         track.on(
405
         track.on(
400
             JitsiTrackEvents.TRACK_VIDEOTYPE_CHANGED,
406
             JitsiTrackEvents.TRACK_VIDEOTYPE_CHANGED,
401
             type => dispatch(trackVideoTypeChanged(track, type)));
407
             type => dispatch(trackVideoTypeChanged(track, type)));
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
  * Create an action for when a track's muted state change action has failed. This could happen because of
519
  * Create an action for when a track's muted state change action has failed. This could happen because of
496
  * {@code getUserMedia} errors during unmute or replace track errors at the peerconnection level.
520
  * {@code getUserMedia} errors during unmute or replace track errors at the peerconnection level.

+ 43
- 0
react/features/base/tracks/functions.js Visa fil

4
 import { isMobileBrowser } from '../environment/utils';
4
 import { isMobileBrowser } from '../environment/utils';
5
 import JitsiMeetJS, { JitsiTrackErrors, browser } from '../lib-jitsi-meet';
5
 import JitsiMeetJS, { JitsiTrackErrors, browser } from '../lib-jitsi-meet';
6
 import { MEDIA_TYPE, VIDEO_TYPE, setAudioMuted } from '../media';
6
 import { MEDIA_TYPE, VIDEO_TYPE, setAudioMuted } from '../media';
7
+import { getFakeScreenShareParticipantOwnerId } from '../participants';
7
 import { toState } from '../redux';
8
 import { toState } from '../redux';
8
 import {
9
 import {
9
     getUserSelectedCameraDeviceId,
10
     getUserSelectedCameraDeviceId,
410
     return track?.jitsiTrack;
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
  * Returns track of specified media type for specified participant id.
444
  * Returns track of specified media type for specified participant id.
415
  *
445
  *
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
  * Returns track source name of specified media type for specified participant id.
474
  * Returns track source name of specified media type for specified participant id.
432
  *
475
  *

+ 70
- 1
react/features/base/tracks/middleware.js Visa fil

8
 import { hideNotification, isModerationNotificationDisplayed } from '../../notifications';
8
 import { hideNotification, isModerationNotificationDisplayed } from '../../notifications';
9
 import { isPrejoinPageVisible } from '../../prejoin/functions';
9
 import { isPrejoinPageVisible } from '../../prejoin/functions';
10
 import { getCurrentConference } from '../conference/functions';
10
 import { getCurrentConference } from '../conference/functions';
11
-import { getMultipleVideoSupportFeatureFlag } from '../config';
11
+import { getMultipleVideoSupportFeatureFlag, getSourceNameSignalingFeatureFlag } from '../config';
12
 import { getAvailableDevices } from '../devices/actions';
12
 import { getAvailableDevices } from '../devices/actions';
13
 import {
13
 import {
14
     CAMERA_FACING_MODE,
14
     CAMERA_FACING_MODE,
24
     setScreenshareMuted,
24
     setScreenshareMuted,
25
     SCREENSHARE_MUTISM_AUTHORITY
25
     SCREENSHARE_MUTISM_AUTHORITY
26
 } from '../media';
26
 } from '../media';
27
+import { participantLeft, participantJoined, getParticipantById } from '../participants';
27
 import { MiddlewareRegistry, StateListenerRegistry } from '../redux';
28
 import { MiddlewareRegistry, StateListenerRegistry } from '../redux';
28
 
29
 
29
 import {
30
 import {
31
+    SCREENSHARE_TRACK_MUTED_UPDATED,
30
     TOGGLE_SCREENSHARING,
32
     TOGGLE_SCREENSHARING,
31
     TRACK_ADDED,
33
     TRACK_ADDED,
32
     TRACK_MUTE_UNMUTE_FAILED,
34
     TRACK_MUTE_UNMUTE_FAILED,
50
     isUserInteractionRequiredForUnmute,
52
     isUserInteractionRequiredForUnmute,
51
     setTrackMuted
53
     setTrackMuted
52
 } from './functions';
54
 } from './functions';
55
+import logger from './logger';
53
 
56
 
54
 import './subscriber';
57
 import './subscriber';
55
 
58
 
72
             store.dispatch(getAvailableDevices());
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
         break;
85
         break;
76
     }
86
     }
77
     case TRACK_NO_DATA_FROM_SOURCE: {
87
     case TRACK_NO_DATA_FROM_SOURCE: {
81
 
91
 
82
         return result;
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
     case TRACK_REMOVED: {
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
         _removeNoDataFromSourceNotification(store, action.track);
128
         _removeNoDataFromSourceNotification(store, action.track);
86
         break;
129
         break;
87
     }
130
     }
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
  * Gets the local track associated with a specific {@code MEDIA_TYPE} in a
399
  * Gets the local track associated with a specific {@code MEDIA_TYPE} in a
331
  * specific redux store.
400
  * specific redux store.

+ 12
- 4
react/features/connection-indicator/components/web/ConnectionIndicator.js Visa fil

11
 import { getLocalParticipant, getParticipantById } from '../../../base/participants';
11
 import { getLocalParticipant, getParticipantById } from '../../../base/participants';
12
 import { Popover } from '../../../base/popover';
12
 import { Popover } from '../../../base/popover';
13
 import { connect } from '../../../base/redux';
13
 import { connect } from '../../../base/redux';
14
-import { getTrackByMediaTypeAndParticipant } from '../../../base/tracks';
14
+import {
15
+    getFakeScreenshareParticipantTrack,
16
+    getTrackByMediaTypeAndParticipant } from '../../../base/tracks';
15
 import {
17
 import {
16
     isParticipantConnectionStatusInactive,
18
     isParticipantConnectionStatusInactive,
17
     isParticipantConnectionStatusInterrupted,
19
     isParticipantConnectionStatusInterrupted,
366
  */
368
  */
367
 export function _mapStateToProps(state: Object, ownProps: Props) {
369
 export function _mapStateToProps(state: Object, ownProps: Props) {
368
     const { participantId } = ownProps;
370
     const { participantId } = ownProps;
371
+    const tracks = state['features/base/tracks'];
369
     const sourceNameSignalingEnabled = getSourceNameSignalingFeatureFlag(state);
372
     const sourceNameSignalingEnabled = getSourceNameSignalingFeatureFlag(state);
370
-    const firstVideoTrack = getTrackByMediaTypeAndParticipant(
371
-        state['features/base/tracks'], MEDIA_TYPE.VIDEO, participantId);
372
-
373
     const participant = participantId ? getParticipantById(state, participantId) : getLocalParticipant(state);
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
     const _isConnectionStatusInactive = sourceNameSignalingEnabled
383
     const _isConnectionStatusInactive = sourceNameSignalingEnabled
376
         ? isTrackStreamingStatusInactive(firstVideoTrack)
384
         ? isTrackStreamingStatusInactive(firstVideoTrack)
377
         : isParticipantConnectionStatusInactive(participant);
385
         : isParticipantConnectionStatusInactive(participant);

+ 11
- 3
react/features/connection-indicator/components/web/ConnectionIndicatorContent.js Visa fil

87
      */
87
      */
88
     _enableSaveLogs: boolean,
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
      * Whether or not the displays stats are for local video.
97
      * Whether or not the displays stats are for local video.
92
      */
98
      */
199
                 e2eRtt = { e2eRtt }
205
                 e2eRtt = { e2eRtt }
200
                 enableSaveLogs = { this.props._enableSaveLogs }
206
                 enableSaveLogs = { this.props._enableSaveLogs }
201
                 framerate = { framerate }
207
                 framerate = { framerate }
208
+                isFakeScreenShareParticipant = { this.props._isFakeScreenShareParticipant }
202
                 isLocalVideo = { this.props._isLocalVideo }
209
                 isLocalVideo = { this.props._isLocalVideo }
203
                 maxEnabledResolution = { maxEnabledResolution }
210
                 maxEnabledResolution = { maxEnabledResolution }
204
                 onSaveLogs = { this.props._onSaveLogs }
211
                 onSaveLogs = { this.props._onSaveLogs }
334
         _connectionStatus: participant?.connectionStatus,
341
         _connectionStatus: participant?.connectionStatus,
335
         _enableSaveLogs: state['features/base/config'].enableSaveLogs,
342
         _enableSaveLogs: state['features/base/config'].enableSaveLogs,
336
         _disableShowMoreStats: state['features/base/config'].disableShowMoreStats,
343
         _disableShowMoreStats: state['features/base/config'].disableShowMoreStats,
337
-        _isLocalVideo: participant?.local,
338
-        _region: participant?.region,
339
         _isConnectionStatusInactive,
344
         _isConnectionStatusInactive,
340
-        _isConnectionStatusInterrupted
345
+        _isConnectionStatusInterrupted,
346
+        _isFakeScreenShareParticipant: sourceNameSignalingEnabled && participant?.isFakeScreenShareParticipant,
347
+        _isLocalVideo: participant?.local,
348
+        _region: participant?.region
341
     };
349
     };
342
 
350
 
343
     if (conference) {
351
     if (conference) {

+ 44
- 1
react/features/connection-stats/components/ConnectionStatsTable.js Visa fil

91
      */
91
      */
92
     isLocalVideo: boolean,
92
     isLocalVideo: boolean,
93
 
93
 
94
+    /**
95
+     * Whether or not the statistics are for screen share.
96
+     */
97
+    isFakeScreenShareParticipant: boolean,
98
+
94
     /**
99
     /**
95
      * The send-side max enabled resolution (aka the highest layer that is not
100
      * The send-side max enabled resolution (aka the highest layer that is not
96
      * suspended on the send-side).
101
      * suspended on the send-side).
231
      * @returns {ReactElement}
236
      * @returns {ReactElement}
232
      */
237
      */
233
     render() {
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
         const className = clsx(classes.connectionStatsTable, { [classes.mobile]: isMobileBrowser() });
246
         const className = clsx(classes.connectionStatsTable, { [classes.mobile]: isMobileBrowser() });
236
 
247
 
248
+        if (isFakeScreenShareParticipant) {
249
+            return this._renderScreenShareStatus();
250
+        }
251
+
237
         return (
252
         return (
238
             <ContextMenu
253
             <ContextMenu
239
                 className = { classes.contextMenu }
254
                 className = { classes.contextMenu }
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
      * Creates a table as ReactElement that will display additional statistics
300
      * Creates a table as ReactElement that will display additional statistics
258
      * related to bandwidth and transport for the local user.
301
      * related to bandwidth and transport for the local user.

+ 16
- 0
react/features/filmstrip/actions.web.js Visa fil

1
 // @flow
1
 // @flow
2
 import type { Dispatch } from 'redux';
2
 import type { Dispatch } from 'redux';
3
 
3
 
4
+import { getSourceNameSignalingFeatureFlag } from '../base/config';
4
 import {
5
 import {
5
     getLocalParticipant,
6
     getLocalParticipant,
6
     getParticipantById,
7
     getParticipantById,
122
         const resizableFilmstrip = isFilmstripResizable(state);
123
         const resizableFilmstrip = isFilmstripResizable(state);
123
         const _verticalViewGrid = showGridInVerticalView(state);
124
         const _verticalViewGrid = showGridInVerticalView(state);
124
         const numberOfRemoteParticipants = getRemoteParticipantCount(state);
125
         const numberOfRemoteParticipants = getRemoteParticipantCount(state);
126
+        const { localScreenShare } = state['features/base/participants'];
125
 
127
 
126
         let gridView = {};
128
         let gridView = {};
127
         let thumbnails = {};
129
         let thumbnails = {};
179
                 = thumbnails?.local?.width + TILE_VERTICAL_CONTAINER_HORIZONTAL_MARGIN + SCROLL_SIZE;
181
                 = thumbnails?.local?.width + TILE_VERTICAL_CONTAINER_HORIZONTAL_MARGIN + SCROLL_SIZE;
180
             remoteVideosContainerHeight
182
             remoteVideosContainerHeight
181
                 = clientHeight - (disableSelfView ? 0 : thumbnails?.local?.height) - VERTICAL_FILMSTRIP_VERTICAL_MARGIN;
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
             hasScroll
198
             hasScroll
183
                 = remoteVideosContainerHeight
199
                 = remoteVideosContainerHeight
184
                     < (thumbnails?.remote.height + TILE_VERTICAL_MARGIN) * numberOfRemoteParticipants;
200
                     < (thumbnails?.remote.height + TILE_VERTICAL_MARGIN) * numberOfRemoteParticipants;

+ 157
- 0
react/features/filmstrip/components/web/FakeScreenShareParticipant.js Visa fil

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 Visa fil

12
     createToolbarEvent,
12
     createToolbarEvent,
13
     sendAnalytics
13
     sendAnalytics
14
 } from '../../../analytics';
14
 } from '../../../analytics';
15
-import { getToolbarButtons } from '../../../base/config';
15
+import { getSourceNameSignalingFeatureFlag, getToolbarButtons } from '../../../base/config';
16
 import { isMobileBrowser } from '../../../base/environment/utils';
16
 import { isMobileBrowser } from '../../../base/environment/utils';
17
 import { translate } from '../../../base/i18n';
17
 import { translate } from '../../../base/i18n';
18
 import { Icon, IconMenuDown, IconMenuUp } from '../../../base/icons';
18
 import { Icon, IconMenuDown, IconMenuUp } from '../../../base/icons';
107
      */
107
      */
108
     _isVerticalFilmstrip: boolean,
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
      * The maximum width of the vertical filmstrip.
116
      * The maximum width of the vertical filmstrip.
112
      */
117
      */
301
         const {
306
         const {
302
             _currentLayout,
307
             _currentLayout,
303
             _disableSelfView,
308
             _disableSelfView,
309
+            _localScreenShare,
304
             _resizableFilmstrip,
310
             _resizableFilmstrip,
305
             _stageFilmstrip,
311
             _stageFilmstrip,
306
             _visible,
312
             _visible,
352
                         }
358
                         }
353
                     </div>
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
                     this._renderRemoteParticipants()
376
                     this._renderRemoteParticipants()
357
                 }
377
                 }
782
     const { testing = {}, iAmRecorder } = state['features/base/config'];
802
     const { testing = {}, iAmRecorder } = state['features/base/config'];
783
     const enableThumbnailReordering = testing.enableThumbnailReordering ?? true;
803
     const enableThumbnailReordering = testing.enableThumbnailReordering ?? true;
784
     const { visible, width: verticalFilmstripWidth } = state['features/filmstrip'];
804
     const { visible, width: verticalFilmstripWidth } = state['features/filmstrip'];
805
+    const { localScreenShare } = state['features/base/participants'];
785
     const reduceHeight = state['features/toolbox'].visible && toolbarButtons.length;
806
     const reduceHeight = state['features/toolbox'].visible && toolbarButtons.length;
786
     const remoteVideosVisible = shouldRemoteVideosBeVisible(state);
807
     const remoteVideosVisible = shouldRemoteVideosBeVisible(state);
787
     const { isOpen: shiftRight } = state['features/chat'];
808
     const { isOpen: shiftRight } = state['features/chat'];
808
         _isFilmstripButtonEnabled: isButtonEnabled('filmstrip', state),
829
         _isFilmstripButtonEnabled: isButtonEnabled('filmstrip', state),
809
         _isToolboxVisible: isToolboxVisible(state),
830
         _isToolboxVisible: isToolboxVisible(state),
810
         _isVerticalFilmstrip: ownProps._currentLayout === LAYOUTS.VERTICAL_FILMSTRIP_VIEW,
831
         _isVerticalFilmstrip: ownProps._currentLayout === LAYOUTS.VERTICAL_FILMSTRIP_VIEW,
832
+        _localScreenShare: getSourceNameSignalingFeatureFlag(state) && localScreenShare,
811
         _maxFilmstripWidth: clientWidth - MIN_STAGE_VIEW_WIDTH,
833
         _maxFilmstripWidth: clientWidth - MIN_STAGE_VIEW_WIDTH,
812
         _thumbnailsReordered: enableThumbnailReordering,
834
         _thumbnailsReordered: enableThumbnailReordering,
813
         _verticalFilmstripWidth: verticalFilmstripWidth.current,
835
         _verticalFilmstripWidth: verticalFilmstripWidth.current,

+ 46
- 4
react/features/filmstrip/components/web/Thumbnail.js Visa fil

7
 
7
 
8
 import { createScreenSharingIssueEvent, sendAnalytics } from '../../../analytics';
8
 import { createScreenSharingIssueEvent, sendAnalytics } from '../../../analytics';
9
 import { Avatar } from '../../../base/avatar';
9
 import { Avatar } from '../../../base/avatar';
10
+import { getSourceNameSignalingFeatureFlag } from '../../../base/config';
10
 import { isMobileBrowser } from '../../../base/environment/utils';
11
 import { isMobileBrowser } from '../../../base/environment/utils';
11
 import { MEDIA_TYPE, VideoTrack } from '../../../base/media';
12
 import { MEDIA_TYPE, VideoTrack } from '../../../base/media';
12
 import {
13
 import {
21
     getLocalAudioTrack,
22
     getLocalAudioTrack,
22
     getLocalVideoTrack,
23
     getLocalVideoTrack,
23
     getTrackByMediaTypeAndParticipant,
24
     getTrackByMediaTypeAndParticipant,
25
+    getFakeScreenshareParticipantTrack,
24
     updateLastTrackVideoMediaEvent
26
     updateLastTrackVideoMediaEvent
25
 } from '../../../base/tracks';
27
 } from '../../../base/tracks';
26
 import { getVideoObjectPosition } from '../../../facial-recognition/functions';
28
 import { getVideoObjectPosition } from '../../../facial-recognition/functions';
44
 } from '../../functions';
46
 } from '../../functions';
45
 import { isStageFilmstripEnabled } from '../../functions.web';
47
 import { isStageFilmstripEnabled } from '../../functions.web';
46
 
48
 
49
+import FakeScreenShareParticipant from './FakeScreenShareParticipant';
47
 import ThumbnailAudioIndicator from './ThumbnailAudioIndicator';
50
 import ThumbnailAudioIndicator from './ThumbnailAudioIndicator';
48
 import ThumbnailBottomIndicators from './ThumbnailBottomIndicators';
51
 import ThumbnailBottomIndicators from './ThumbnailBottomIndicators';
49
 import ThumbnailTopIndicators from './ThumbnailTopIndicators';
52
 import ThumbnailTopIndicators from './ThumbnailTopIndicators';
132
      */
135
      */
133
     _isCurrentlyOnLargeVideo: boolean,
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
      * Whether we are currently running in a mobile browser.
145
      * Whether we are currently running in a mobile browser.
137
      */
146
      */
535
             _currentLayout,
544
             _currentLayout,
536
             _disableTileEnlargement,
545
             _disableTileEnlargement,
537
             _height,
546
             _height,
547
+            _isFakeScreenShareParticipant,
538
             _isHidden,
548
             _isHidden,
539
             _isScreenSharing,
549
             _isScreenSharing,
540
             _participant,
550
             _participant,
572
             || _disableTileEnlargement
582
             || _disableTileEnlargement
573
             || _isScreenSharing;
583
             || _isScreenSharing;
574
 
584
 
575
-        if (canPlayEventReceived || _participant.local) {
585
+        if (canPlayEventReceived || _participant.local || _isFakeScreenShareParticipant) {
576
             videoStyles = {
586
             videoStyles = {
577
                 objectFit: doNotStretchVideo ? 'contain' : 'cover'
587
                 objectFit: doNotStretchVideo ? 'contain' : 'cover'
578
             };
588
             };
1014
      * @returns {ReactElement}
1024
      * @returns {ReactElement}
1015
      */
1025
      */
1016
     render() {
1026
     render() {
1017
-        const { _participant } = this.props;
1027
+        const { _participant, _isFakeScreenShareParticipant } = this.props;
1018
 
1028
 
1019
         if (!_participant) {
1029
         if (!_participant) {
1020
             return null;
1030
             return null;
1030
             return this._renderFakeParticipant();
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
         return this._renderParticipant();
1066
         return this._renderParticipant();
1034
     }
1067
     }
1035
 }
1068
 }
1049
     const id = participant?.id;
1082
     const id = participant?.id;
1050
     const isLocal = participant?.local ?? true;
1083
     const isLocal = participant?.local ?? true;
1051
     const tracks = state['features/base/tracks'];
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
     const _audioTrack = isLocal
1095
     const _audioTrack = isLocal
1055
         ? getLocalAudioTrack(tracks) : getTrackByMediaTypeAndParticipant(tracks, MEDIA_TYPE.AUDIO, participantID);
1096
         ? getLocalAudioTrack(tracks) : getTrackByMediaTypeAndParticipant(tracks, MEDIA_TYPE.AUDIO, participantID);
1056
     const _currentLayout = stageFilmstrip ? LAYOUTS.TILE_VIEW : getCurrentLayout(state);
1097
     const _currentLayout = stageFilmstrip ? LAYOUTS.TILE_VIEW : getCurrentLayout(state);
1144
         _isAudioOnly: Boolean(state['features/base/audio-only'].enabled),
1185
         _isAudioOnly: Boolean(state['features/base/audio-only'].enabled),
1145
         _isCurrentlyOnLargeVideo: state['features/large-video']?.participantId === id,
1186
         _isCurrentlyOnLargeVideo: state['features/large-video']?.participantId === id,
1146
         _isDominantSpeakerDisabled: interfaceConfig.DISABLE_DOMINANT_SPEAKER_INDICATOR,
1187
         _isDominantSpeakerDisabled: interfaceConfig.DISABLE_DOMINANT_SPEAKER_INDICATOR,
1188
+        _isFakeScreenShareParticipant: sourceNameSignalingEnabled && participant?.isFakeScreenShareParticipant,
1147
         _isMobile,
1189
         _isMobile,
1148
         _isMobilePortrait,
1190
         _isMobilePortrait,
1149
         _isScreenSharing: _videoTrack?.videoType === 'desktop',
1191
         _isScreenSharing: _videoTrack?.videoType === 'desktop',

+ 15
- 7
react/features/filmstrip/components/web/ThumbnailBottomIndicators.js Visa fil

32
     /**
32
     /**
33
      * Id of the participant for which the component is displayed.
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
 const useStyles = makeStyles(() => {
43
 const useStyles = makeStyles(() => {
58
     className,
63
     className,
59
     currentLayout,
64
     currentLayout,
60
     local,
65
     local,
61
-    participantId
66
+    participantId,
67
+    showStatusIndicators = true
62
 }: Props) => {
68
 }: Props) => {
63
     const styles = useStyles();
69
     const styles = useStyles();
64
     const _allowEditing = !useSelector(isNameReadOnly);
70
     const _allowEditing = !useSelector(isNameReadOnly);
66
     const _showDisplayName = useSelector(isDisplayNameVisible);
72
     const _showDisplayName = useSelector(isDisplayNameVisible);
67
 
73
 
68
     return (<div className = { className }>
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
             _showDisplayName && (
83
             _showDisplayName && (
76
                 <span className = { styles.nameContainer }>
84
                 <span className = { styles.nameContainer }>

+ 23
- 1
react/features/filmstrip/components/web/ThumbnailTopIndicators.js Visa fil

5
 import React from 'react';
5
 import React from 'react';
6
 import { useSelector } from 'react-redux';
6
 import { useSelector } from 'react-redux';
7
 
7
 
8
+import { getSourceNameSignalingFeatureFlag } from '../../../base/config';
8
 import { isMobileBrowser } from '../../../base/environment/utils';
9
 import { isMobileBrowser } from '../../../base/environment/utils';
9
 import ConnectionIndicator from '../../../connection-indicator/components/web/ConnectionIndicator';
10
 import ConnectionIndicator from '../../../connection-indicator/components/web/ConnectionIndicator';
10
 import { LAYOUTS } from '../../../video-layout';
11
 import { LAYOUTS } from '../../../video-layout';
40
      */
41
      */
41
     isHovered: boolean,
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
      * Whether or not the indicators are for the local participant.
50
      * Whether or not the indicators are for the local participant.
45
      */
51
      */
77
     currentLayout,
83
     currentLayout,
78
     hidePopover,
84
     hidePopover,
79
     indicatorsClassName,
85
     indicatorsClassName,
86
+    isFakeScreenShareParticipant,
80
     isHovered,
87
     isHovered,
81
     local,
88
     local,
82
     participantId,
89
     participantId,
92
         useSelector(state => state['features/base/config'].connectionIndicators?.autoHide) ?? true);
99
         useSelector(state => state['features/base/config'].connectionIndicators?.autoHide) ?? true);
93
     const _connectionIndicatorDisabled = _isMobile
100
     const _connectionIndicatorDisabled = _isMobile
94
         || Boolean(useSelector(state => state['features/base/config'].connectionIndicators?.disabled));
101
         || Boolean(useSelector(state => state['features/base/config'].connectionIndicators?.disabled));
95
-
102
+    const sourceNameSignalingEnabled = useSelector(getSourceNameSignalingFeatureFlag);
96
     const showConnectionIndicator = isHovered || !_connectionIndicatorAutoHideEnabled;
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
     return (
120
     return (
99
         <>
121
         <>
100
             <div className = { styles.container }>
122
             <div className = { styles.container }>

+ 52
- 2
react/features/filmstrip/components/web/ThumbnailWrapper.js Visa fil

2
 import React, { Component } from 'react';
2
 import React, { Component } from 'react';
3
 import { shouldComponentUpdate } from 'react-window';
3
 import { shouldComponentUpdate } from 'react-window';
4
 
4
 
5
+import { getSourceNameSignalingFeatureFlag } from '../../../base/config';
5
 import { getLocalParticipant } from '../../../base/participants';
6
 import { getLocalParticipant } from '../../../base/participants';
6
 import { connect } from '../../../base/redux';
7
 import { connect } from '../../../base/redux';
7
 import { shouldHideSelfView } from '../../../base/settings/functions.any';
8
 import { shouldHideSelfView } from '../../../base/settings/functions.any';
30
      */
31
      */
31
     _participantID: ?string,
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
      * Whether or not the filmstrip is used a stage filmstrip.
40
      * Whether or not the filmstrip is used a stage filmstrip.
35
      */
41
      */
84
     render() {
90
     render() {
85
         const {
91
         const {
86
             _disableSelfView,
92
             _disableSelfView,
93
+            _isLocalScreenShare = false,
87
             _horizontalOffset = 0,
94
             _horizontalOffset = 0,
88
             _participantID,
95
             _participantID,
89
             _stageFilmstrip,
96
             _stageFilmstrip,
103
                     style = { style } />);
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
         return (
122
         return (
107
             <Thumbnail
123
             <Thumbnail
108
                 horizontalOffset = { _horizontalOffset }
124
                 horizontalOffset = { _horizontalOffset }
128
     const { testing = {} } = state['features/base/config'];
144
     const { testing = {} } = state['features/base/config'];
129
     const disableSelfView = shouldHideSelfView(state);
145
     const disableSelfView = shouldHideSelfView(state);
130
     const enableThumbnailReordering = testing.enableThumbnailReordering ?? true;
146
     const enableThumbnailReordering = testing.enableThumbnailReordering ?? true;
147
+    const sourceNameSignalingEnabled = getSourceNameSignalingFeatureFlag(state);
131
     const _verticalViewGrid = showGridInVerticalView(state);
148
     const _verticalViewGrid = showGridInVerticalView(state);
132
     const stageFilmstrip = ownProps.data?.stageFilmstrip;
149
     const stageFilmstrip = ownProps.data?.stageFilmstrip;
133
     const remoteParticipants = stageFilmstrip ? activeParticipants : remote;
150
     const remoteParticipants = stageFilmstrip ? activeParticipants : remote;
152
         const index = (rowIndex * columns) + columnIndex;
169
         const index = (rowIndex * columns) + columnIndex;
153
         let horizontalOffset;
170
         let horizontalOffset;
154
         const { iAmRecorder } = state['features/base/config'];
171
         const { iAmRecorder } = state['features/base/config'];
155
-        const participantsLength = stageFilmstrip ? remoteParticipantsLength
172
+        let participantsLength = stageFilmstrip ? remoteParticipantsLength
156
             : remoteParticipantsLength + (iAmRecorder ? 0 : 1) - (disableSelfView ? 1 : 0);
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
         if (rowIndex === rows - 1) { // center the last row
188
         if (rowIndex === rows - 1) { // center the last row
159
             const { width: thumbnailWidth } = thumbnailSize;
189
             const { width: thumbnailWidth } = thumbnailSize;
160
             const partialLastRowParticipantsNumber = participantsLength % columns;
190
             const partialLastRowParticipantsNumber = participantsLength % columns;
179
 
209
 
180
         // When the thumbnails are reordered, local participant is inserted at index 0.
210
         // When the thumbnails are reordered, local participant is inserted at index 0.
181
         const localIndex = enableThumbnailReordering && !disableSelfView ? 0 : remoteParticipantsLength;
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
         if (!iAmRecorder && index === localIndex) {
225
         if (!iAmRecorder && index === localIndex) {
185
             return {
226
             return {
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
         return {
242
         return {
193
             _participantID: remoteParticipants[remoteIndex],
243
             _participantID: remoteParticipants[remoteIndex],
194
             _horizontalOffset: horizontalOffset
244
             _horizontalOffset: horizontalOffset

+ 50
- 11
react/features/filmstrip/functions.any.js Visa fil

1
 // @flow
1
 // @flow
2
 
2
 
3
+import { getSourceNameSignalingFeatureFlag } from '../base/config';
4
+import { getFakeScreenShareParticipantOwnerId } from '../base/participants';
5
+
3
 import { setRemoteParticipants } from './actions';
6
 import { setRemoteParticipants } from './actions';
4
 import { isReorderingEnabled } from './functions';
7
 import { isReorderingEnabled } from './functions';
5
 
8
 
15
     const state = store.getState();
18
     const state = store.getState();
16
     let reorderedParticipants = [];
19
     let reorderedParticipants = [];
17
 
20
 
18
-    if (!isReorderingEnabled(state)) {
21
+    const { sortedRemoteFakeScreenShareParticipants } = state['features/base/participants'];
22
+
23
+    if (!isReorderingEnabled(state) && !sortedRemoteFakeScreenShareParticipants.size) {
19
         if (participantId) {
24
         if (participantId) {
20
             const { remoteParticipants } = state['features/filmstrip'];
25
             const { remoteParticipants } = state['features/filmstrip'];
21
 
26
 
34
     } = state['features/base/participants'];
39
     } = state['features/base/participants'];
35
     const remoteParticipants = new Map(sortedRemoteParticipants);
40
     const remoteParticipants = new Map(sortedRemoteParticipants);
36
     const screenShares = new Map(sortedRemoteScreenshares);
41
     const screenShares = new Map(sortedRemoteScreenshares);
42
+    const screenShareParticipants = sortedRemoteFakeScreenShareParticipants
43
+        ? [ ...sortedRemoteFakeScreenShareParticipants.keys() ] : [];
37
     const sharedVideos = fakeParticipants ? Array.from(fakeParticipants.keys()) : [];
44
     const sharedVideos = fakeParticipants ? Array.from(fakeParticipants.keys()) : [];
38
     const speakers = new Map(speakersList);
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
     for (const sharedVideo of sharedVideos) {
64
     for (const sharedVideo of sharedVideos) {
45
         remoteParticipants.delete(sharedVideo);
65
         remoteParticipants.delete(sharedVideo);
46
         speakers.delete(sharedVideo);
66
         speakers.delete(sharedVideo);
49
         remoteParticipants.delete(speaker);
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
     store.dispatch(setRemoteParticipants(reorderedParticipants));
99
     store.dispatch(setRemoteParticipants(reorderedParticipants));
61
 }
100
 }

+ 10
- 1
react/features/filmstrip/functions.web.js Visa fil

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

+ 4
- 0
react/features/filmstrip/middleware.web.js Visa fil

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

+ 2
- 1
react/features/filmstrip/subscriber.web.js Visa fil

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

+ 10
- 0
react/features/video-layout/actionTypes.js Visa fil

10
 export const SCREEN_SHARE_REMOTE_PARTICIPANTS_UPDATED
10
 export const SCREEN_SHARE_REMOTE_PARTICIPANTS_UPDATED
11
     = 'SCREEN_SHARE_REMOTE_PARTICIPANTS_UPDATED';
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
  * The type of the action which enables or disables the feature for showing
24
  * The type of the action which enables or disables the feature for showing
15
  * video thumbnails in a two-axis tile view.
25
  * video thumbnails in a two-axis tile view.

+ 17
- 0
react/features/video-layout/actions.js Visa fil

3
 import type { Dispatch } from 'redux';
3
 import type { Dispatch } from 'redux';
4
 
4
 
5
 import {
5
 import {
6
+    FAKE_SCREEN_SHARE_REMOTE_PARTICIPANTS_UPDATED,
6
     SCREEN_SHARE_REMOTE_PARTICIPANTS_UPDATED,
7
     SCREEN_SHARE_REMOTE_PARTICIPANTS_UPDATED,
7
     SET_TILE_VIEW
8
     SET_TILE_VIEW
8
 } from './actionTypes';
9
 } from './actionTypes';
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
  * Creates a (redux) action which signals to set the UI layout to be tiled view
47
  * Creates a (redux) action which signals to set the UI layout to be tiled view
31
  * or not.
48
  * or not.

+ 2
- 0
react/features/video-layout/reducer.js Visa fil

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

+ 35
- 2
react/features/video-layout/subscriber.js Visa fil

2
 
2
 
3
 import debounce from 'lodash/debounce';
3
 import debounce from 'lodash/debounce';
4
 
4
 
5
+import { getSourceNameSignalingFeatureFlag } from '../base/config';
5
 import { StateListenerRegistry, equals } from '../base/redux';
6
 import { StateListenerRegistry, equals } from '../base/redux';
6
 import { isFollowMeActive } from '../follow-me';
7
 import { isFollowMeActive } from '../follow-me';
7
 
8
 
8
-import { setRemoteParticipantsWithScreenShare } from './actions';
9
+import { setRemoteParticipantsWithScreenShare, fakeScreenshareParticipantsUpdated } from './actions';
9
 import { getAutoPinSetting, updateAutoPinnedParticipant } from './functions';
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
  * For auto-pin mode, listen for changes to the known media tracks and look
45
  * For auto-pin mode, listen for changes to the known media tracks and look
13
  * for updates to screen shares. The listener is debounced to avoid state
46
  * for updates to screen shares. The listener is debounced to avoid state
20
         // possible to have screen sharing participant that has already left in the remoteScreenShares array.
53
         // possible to have screen sharing participant that has already left in the remoteScreenShares array.
21
         // This can lead to rendering a thumbnails for already left participants since the remoteScreenShares
54
         // This can lead to rendering a thumbnails for already left participants since the remoteScreenShares
22
         // array is used for building the ordered list of remote participants.
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
             return;
57
             return;
25
         }
58
         }
26
 
59
 

+ 16
- 10
react/features/video-quality/subscriber.js Visa fil

250
 
250
 
251
         if (visibleRemoteParticipants?.size) {
251
         if (visibleRemoteParticipants?.size) {
252
             visibleRemoteParticipants.forEach(participantId => {
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
                 if (sourceName) {
261
                 if (sourceName) {
256
                     visibleRemoteTrackSourceNames.push(sourceName);
262
                     visibleRemoteTrackSourceNames.push(sourceName);
262
         }
268
         }
263
 
269
 
264
         if (localParticipantId !== largeVideoParticipantId) {
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
         // Tile view.
281
         // Tile view.
280
 
290
 
281
             // Prioritize screenshare in tile view.
291
             // Prioritize screenshare in tile view.
282
             if (remoteScreenShares?.length) {
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
         // Stage view.
296
         // Stage view.

Laddar…
Avbryt
Spara