Pārlūkot izejas kodu

feat(aot): Handle video not available use cases (#2242)

j8
hristoterezov 7 gadus atpakaļ
vecāks
revīzija
5ffcaca649

+ 37
- 4
conference.js Parādīt failu

@@ -78,6 +78,7 @@ import {
78 78
 import { getLocationContextRoot } from './react/features/base/util';
79 79
 import { statsEmitter } from './react/features/connection-indicator';
80 80
 import { showDesktopPicker } from './react/features/desktop-picker';
81
+import { appendSuffix } from './react/features/display-name';
81 82
 import { maybeOpenFeedbackDialog } from './react/features/feedback';
82 83
 import {
83 84
     mediaPermissionPromptVisibilityChanged,
@@ -1726,15 +1727,20 @@ export default {
1726 1727
             if (user.isHidden()) {
1727 1728
                 return;
1728 1729
             }
1730
+            const displayName = user.getDisplayName();
1729 1731
 
1730 1732
             APP.store.dispatch(participantJoined({
1731 1733
                 id,
1732
-                name: user.getDisplayName(),
1734
+                name: displayName,
1733 1735
                 role: user.getRole()
1734 1736
             }));
1735 1737
 
1736 1738
             logger.log('USER %s connnected', id, user);
1737
-            APP.API.notifyUserJoined(id);
1739
+            APP.API.notifyUserJoined(id, {
1740
+                displayName,
1741
+                formattedDisplayName: appendSuffix(
1742
+                    displayName || interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME)
1743
+            });
1738 1744
             APP.UI.addUser(user);
1739 1745
 
1740 1746
             // check the roles for the new user and reflect them
@@ -1892,6 +1898,7 @@ export default {
1892 1898
             }
1893 1899
 
1894 1900
             APP.UI.addListener(UIEvents.SELECTED_ENDPOINT, id => {
1901
+                APP.API.notifyOnStageParticipantChanged(id);
1895 1902
                 try {
1896 1903
                     // do not try to select participant if there is none (we
1897 1904
                     // are alone in the room), otherwise an error will be
@@ -1938,7 +1945,13 @@ export default {
1938 1945
                     id,
1939 1946
                     name: formattedDisplayName
1940 1947
                 }));
1941
-                APP.API.notifyDisplayNameChanged(id, formattedDisplayName);
1948
+                APP.API.notifyDisplayNameChanged(id, {
1949
+                    displayName: formattedDisplayName,
1950
+                    formattedDisplayName:
1951
+                        appendSuffix(
1952
+                            formattedDisplayName
1953
+                                || interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME)
1954
+                });
1942 1955
                 APP.UI.changeDisplayName(id, formattedDisplayName);
1943 1956
             }
1944 1957
         );
@@ -2377,7 +2390,19 @@ export default {
2377 2390
         APP.store.dispatch(conferenceJoined(room));
2378 2391
 
2379 2392
         APP.UI.mucJoined();
2380
-        APP.API.notifyConferenceJoined(APP.conference.roomName);
2393
+        const displayName = APP.settings.getDisplayName();
2394
+
2395
+        APP.API.notifyConferenceJoined(
2396
+            this.roomName,
2397
+            this._room.myUserId(),
2398
+            {
2399
+                displayName,
2400
+                formattedDisplayName: appendSuffix(
2401
+                    displayName,
2402
+                    interfaceConfig.DEFAULT_LOCAL_DISPLAY_NAME),
2403
+                avatarURL: APP.UI.getAvatarUrl()
2404
+            }
2405
+        );
2381 2406
         APP.UI.markVideoInterrupted(false);
2382 2407
     },
2383 2408
 
@@ -2748,6 +2773,14 @@ export default {
2748 2773
         }));
2749 2774
 
2750 2775
         APP.settings.setDisplayName(formattedNickname);
2776
+        APP.API.notifyDisplayNameChanged(id, {
2777
+            displayName: formattedNickname,
2778
+            formattedDisplayName:
2779
+                appendSuffix(
2780
+                    formattedNickname,
2781
+                    interfaceConfig.DEFAULT_LOCAL_DISPLAY_NAME)
2782
+        });
2783
+
2751 2784
         if (room) {
2752 2785
             room.setDisplayName(formattedNickname);
2753 2786
             APP.UI.changeDisplayName(id, formattedNickname);

+ 24
- 0
css/_videolayout_default.scss Parādīt failu

@@ -250,6 +250,7 @@
250 250
 /**
251 251
  * Positions video thumbnail display name and editor.
252 252
  */
253
+#alwaysOnTop .displayname,
253 254
 .videocontainer .displayname,
254 255
 .videocontainer .editdisplayname {
255 256
     display: inline-block;
@@ -269,6 +270,15 @@
269 270
     z-index: $zindex2;
270 271
 }
271 272
 
273
+#alwaysOnTop .displayname {
274
+    font-size: 15px;
275
+    position: inherit;
276
+    width: 100%;
277
+    left: 0px;
278
+    top: 0px;
279
+    margin-top: 10px;
280
+}
281
+
272 282
 /**
273 283
  * Positions video thumbnail display name editor.
274 284
  */
@@ -507,6 +517,20 @@
507 517
     width: auto;
508 518
 }
509 519
 
520
+#videoNotAvailableScreen {
521
+    text-align: center;
522
+    #avatarContainer {
523
+        height: 50vh;
524
+        display:inline-block;
525
+        margin-top: 25vh;
526
+
527
+        #avatar {
528
+            border-radius: 50%;
529
+            height: 100%;
530
+        }
531
+    }
532
+}
533
+
510 534
 .sharedVideoAvatar {
511 535
     height: 100%;
512 536
     width: 100%;

+ 36
- 14
doc/api.md Parādīt failu

@@ -140,18 +140,26 @@ The `event` parameter is a String object with the name of the event.
140 140
 The `listener` parameter is a Function object with one argument that will be notified when the event occurs with data related to the event.
141 141
 
142 142
 The following events are currently supported:
143
+* **avatarChanged** - event notifications about avatar
144
+changes. The listener will receive an object with the following structure:
145
+```javascript
146
+{
147
+"id": id, // the id of the participant that changed his avatar.
148
+"avatarURL": avatarURL // the new avatar URL.
149
+}
150
+```
143 151
 
144 152
 * **audioAvailabilityChanged** - event notifications about audio availability status changes. The listener will receive an object with the following structure:
145 153
 ```javascript
146 154
 {
147
-"available": available   // new available status - boolean
155
+"available": available // new available status - boolean
148 156
 }
149 157
 ```
150 158
 
151 159
 * **audioMuteStatusChanged** - event notifications about audio mute status changes. The listener will receive an object with the following structure:
152 160
 ```javascript
153 161
 {
154
-"muted": muted   // new muted status - boolean
162
+"muted": muted // new muted status - boolean
155 163
 }
156 164
 ```
157 165
 
@@ -159,9 +167,9 @@ The following events are currently supported:
159 167
 messages. The listener will receive an object with the following structure:
160 168
 ```javascript
161 169
 {
162
-"from": from,    // JID of the user that sent the message
163
-"nick": nick,    // the nickname of the user that sent the message
164
-"message": txt   // the text of the message
170
+"from": from, // The id of the user that sent the message
171
+"nick": nick, // the nickname of the user that sent the message
172
+"message": txt // the text of the message
165 173
 }
166 174
 ```
167 175
 
@@ -169,7 +177,7 @@ messages. The listener will receive an object with the following structure:
169 177
 messages. The listener will receive an object with the following structure:
170 178
 ```javascript
171 179
 {
172
-"message": txt   // the text of the message
180
+"message": txt // the text of the message
173 181
 }
174 182
 ```
175 183
 
@@ -177,50 +185,54 @@ messages. The listener will receive an object with the following structure:
177 185
 changes. The listener will receive an object with the following structure:
178 186
 ```javascript
179 187
 {
180
-"jid": jid,                 // the JID of the participant that changed his display name
181
-"displayname": displayName  // the new display name
188
+"id": id, // the id of the participant that changed his display name
189
+"displayname": displayName // the new display name
182 190
 }
183 191
 ```
184 192
 
185 193
 * **participantJoined** - event notifications about new participants who join the room. The listener will receive an object with the following structure:
186 194
 ```javascript
187 195
 {
188
-"jid": jid   // the JID of the participant
196
+"id": id, // the id of the participant
197
+"displayName": displayName // the display name of the participant
189 198
 }
190 199
 ```
191 200
 
192 201
 * **participantLeft** - event notifications about participants that leave the room. The listener will receive an object with the following structure:
193 202
 ```javascript
194 203
 {
195
-"jid": jid   // the JID of the participant
204
+"id": id // the id of the participant
196 205
 }
197 206
 ```
198 207
 
199 208
 * **videoConferenceJoined** - event notifications fired when the local user has joined the video conference. The listener will receive an object with the following structure:
200 209
 ```javascript
201 210
 {
202
-"roomName": room   // the room name of the conference
211
+"roomName": room, // the room name of the conference
212
+"id": id, // the id of the local participant
213
+"displayName": displayName, // the display name of the local participant
214
+"avatarURL": avatarURL // the avatar URL of the local participant
203 215
 }
204 216
 ```
205 217
 
206 218
 * **videoConferenceLeft** - event notifications fired when the local user has left the video conference. The listener will receive an object with the following structure:
207 219
 ```javascript
208 220
 {
209
-"roomName": room   // the room name of the conference
221
+"roomName": room // the room name of the conference
210 222
 }
211 223
 ```
212 224
 
213 225
 * **videoAvailabilityChanged** - event notifications about video availability status changes. The listener will receive an object with the following structure:
214 226
 ```javascript
215 227
 {
216
-"available": available   // new available status - boolean
228
+"available": available // new available status - boolean
217 229
 }
218 230
 ```
219 231
 
220 232
 * **videoMuteStatusChanged** - event notifications about video mute status changes. The listener will receive an object with the following structure:
221 233
 ```javascript
222 234
 {
223
-"muted": muted   // new muted status - boolean
235
+"muted": muted // new muted status - boolean
224 236
 }
225 237
 ```
226 238
 
@@ -264,6 +276,16 @@ You can get the number of participants in the conference with the following API
264 276
 var numberOfParticipants = api.getNumberOfParticipants();
265 277
 ```
266 278
 
279
+You can get the avatar URL of a participant in the conference with the following API function:
280
+```javascript
281
+var avatarURL = api.getAvatarURL(participantId);
282
+```
283
+
284
+You can get the display name of a participant in the conference with the following API function:
285
+```javascript
286
+var displayName = api.getDisplayName(participantId);
287
+```
288
+
267 289
 You can get the iframe HTML element where Jitsi Meet is loaded with the following API function:
268 290
 ```javascript
269 291
 var iframe = api.getIFrame();

+ 63
- 6
modules/API/API.js Parādīt failu

@@ -184,6 +184,21 @@ class API {
184 184
         initCommands();
185 185
     }
186 186
 
187
+    /**
188
+     * Notify external application (if API is enabled) that the large video
189
+     * visibility changed.
190
+     *
191
+     * @param {boolean} isHidden - True if the large video is hidden and false
192
+     * otherwise.
193
+     * @returns {void}
194
+     */
195
+    notifyLargeVideoVisibilityChanged(isHidden: boolean) {
196
+        this._sendEvent({
197
+            name: 'large-video-visibility-changed',
198
+            isVisible: !isHidden
199
+        });
200
+    }
201
+
187 202
     /**
188 203
      * Sends event to the external application.
189 204
      *
@@ -238,12 +253,14 @@ class API {
238 253
      * conference.
239 254
      *
240 255
      * @param {string} id - User id.
256
+     * @param {Object} props - The display name of the user.
241 257
      * @returns {void}
242 258
      */
243
-    notifyUserJoined(id: string) {
259
+    notifyUserJoined(id: string, props: Object) {
244 260
         this._sendEvent({
245 261
             name: 'participant-joined',
246
-            id
262
+            id,
263
+            ...props
247 264
         });
248 265
     }
249 266
 
@@ -261,18 +278,39 @@ class API {
261 278
         });
262 279
     }
263 280
 
281
+    /**
282
+     * Notify external application (if API is enabled) that user changed their
283
+     * avatar.
284
+     *
285
+     * @param {string} id - User id.
286
+     * @param {string} avatarURL - The new avatar URL of the participant.
287
+     * @returns {void}
288
+     */
289
+    notifyAvatarChanged(id: string, avatarURL: string) {
290
+        this._sendEvent({
291
+            name: 'avatar-changed',
292
+            avatarURL,
293
+            id
294
+        });
295
+    }
296
+
264 297
     /**
265 298
      * Notify external application (if API is enabled) that user changed their
266 299
      * nickname.
267 300
      *
268 301
      * @param {string} id - User id.
269 302
      * @param {string} displayname - User nickname.
303
+     * @param {string} formattedDisplayName - The display name shown in Jitsi
304
+     * meet's UI for the user.
270 305
      * @returns {void}
271 306
      */
272
-    notifyDisplayNameChanged(id: string, displayname: string) {
307
+    notifyDisplayNameChanged(
308
+            id: string,
309
+            { displayName, formattedDisplayName }: Object) {
273 310
         this._sendEvent({
274 311
             name: 'display-name-change',
275
-            displayname,
312
+            displayname: displayName,
313
+            formattedDisplayName,
276 314
             id
277 315
         });
278 316
     }
@@ -282,12 +320,17 @@ class API {
282 320
      * been joined.
283 321
      *
284 322
      * @param {string} roomName - The room name.
323
+     * @param {string} id - The id of the local user.
324
+     * @param {Object} props - The display name and avatar URL of the local
325
+     * user.
285 326
      * @returns {void}
286 327
      */
287
-    notifyConferenceJoined(roomName: string) {
328
+    notifyConferenceJoined(roomName: string, id: string, props: Object) {
288 329
         this._sendEvent({
289 330
             name: 'video-conference-joined',
290
-            roomName
331
+            roomName,
332
+            id,
333
+            ...props
291 334
         });
292 335
     }
293 336
 
@@ -373,6 +416,20 @@ class API {
373 416
         });
374 417
     }
375 418
 
419
+    /**
420
+     * Notify external application (if API is enabled) that the on stage
421
+     * participant has changed.
422
+     *
423
+     * @param {string} id - User id of the new on stage participant.
424
+     * @returns {void}
425
+     */
426
+    notifyOnStageParticipantChanged(id: string) {
427
+        this._sendEvent({
428
+            name: 'on-stage-participant-changed',
429
+            id
430
+        });
431
+    }
432
+
376 433
 
377 434
     /**
378 435
      * Disposes the allocated resources.

+ 120
- 4
modules/API/external/external_api.js Parādīt failu

@@ -34,6 +34,7 @@ const commands = {
34 34
  * events expected by jitsi-meet
35 35
  */
36 36
 const events = {
37
+    'avatar-changed': 'avatarChanged',
37 38
     'audio-availability-changed': 'audioAvailabilityChanged',
38 39
     'audio-mute-status-changed': 'audioMuteStatusChanged',
39 40
     'display-name-change': 'displayNameChange',
@@ -224,7 +225,11 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
224 225
                 }
225 226
             })
226 227
         });
227
-        this._numberOfParticipants = 1;
228
+        this._isLargeVideoVisible = true;
229
+        this._numberOfParticipants = 0;
230
+        this._participants = {};
231
+        this._myUserID = undefined;
232
+        this._onStageParticipant = undefined;
228 233
         this._setupListeners();
229 234
         id++;
230 235
     }
@@ -278,6 +283,34 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
278 283
         );
279 284
     }
280 285
 
286
+    /**
287
+     * Returns the id of the on stage participant.
288
+     *
289
+     * @returns {string} - The id of the on stage participant.
290
+     */
291
+    _getOnStageParticipant() {
292
+        return this._onStageParticipant;
293
+    }
294
+
295
+
296
+    /**
297
+     * Getter for the large video element in Jitsi Meet.
298
+     *
299
+     * @returns {HTMLElement|undefined} - The large video.
300
+     */
301
+    _getLargeVideo() {
302
+        const iframe = this.getIFrame();
303
+
304
+        if (!this._isLargeVideoVisible
305
+                || !iframe
306
+                || !iframe.contentWindow
307
+                || !iframe.contentWindow.document) {
308
+            return;
309
+        }
310
+
311
+        return iframe.contentWindow.document.getElementById('largeVideo');
312
+    }
313
+
281 314
     /**
282 315
      * Sets the size of the iframe element.
283 316
      *
@@ -308,12 +341,58 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
308 341
      * @private
309 342
      */
310 343
     _setupListeners() {
311
-
312 344
         this._transport.on('event', ({ name, ...data }) => {
313
-            if (name === 'participant-joined') {
345
+            const userID = data.id;
346
+
347
+            switch (name) {
348
+            case 'video-conference-joined':
349
+                this._myUserID = userID;
350
+                this._participants[userID] = {
351
+                    avatarURL: data.avatarURL
352
+                };
353
+
354
+            // eslint-disable-next-line no-fallthrough
355
+            case 'participant-joined': {
356
+                this._participants[userID] = this._participants[userID] || {};
357
+                this._participants[userID].displayName = data.displayName;
358
+                this._participants[userID].formattedDisplayName
359
+                    = data.formattedDisplayName;
314 360
                 changeParticipantNumber(this, 1);
315
-            } else if (name === 'participant-left') {
361
+                break;
362
+            }
363
+            case 'participant-left':
316 364
                 changeParticipantNumber(this, -1);
365
+                delete this._participants[userID];
366
+                break;
367
+            case 'display-name-change': {
368
+                const user = this._participants[userID];
369
+
370
+                if (user) {
371
+                    user.displayName = data.displayname;
372
+                    user.formattedDisplayName = data.formattedDisplayName;
373
+                }
374
+                break;
375
+            }
376
+            case 'avatar-changed': {
377
+                const user = this._participants[userID];
378
+
379
+                if (user) {
380
+                    user.avatarURL = data.avatarURL;
381
+                }
382
+                break;
383
+            }
384
+            case 'on-stage-participant-changed':
385
+                this._onStageParticipant = userID;
386
+                this.emit('largeVideoChanged');
387
+                break;
388
+            case 'large-video-visibility-changed':
389
+                this._isLargeVideoVisible = data.isVisible;
390
+                this.emit('largeVideoChanged');
391
+                break;
392
+            case 'video-conference-left':
393
+                changeParticipantNumber(this, -1);
394
+                delete this._participants[this._myUserID];
395
+                break;
317 396
             }
318 397
 
319 398
             const eventName = events[name];
@@ -487,6 +566,43 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
487 566
         });
488 567
     }
489 568
 
569
+    /**
570
+     * Returns the avatar URL of a participant.
571
+     *
572
+     * @param {string} participantId - The id of the participant.
573
+     * @returns {string} The avatar URL.
574
+     */
575
+    getAvatarURL(participantId) {
576
+        const { avatarURL } = this._participants[participantId] || {};
577
+
578
+        return avatarURL;
579
+    }
580
+
581
+    /**
582
+     * Returns the display name of a participant.
583
+     *
584
+     * @param {string} participantId - The id of the participant.
585
+     * @returns {string} The display name.
586
+     */
587
+    getDisplayName(participantId) {
588
+        const { displayName } = this._participants[participantId] || {};
589
+
590
+        return displayName;
591
+    }
592
+
593
+    /**
594
+     * Returns the formatted display name of a participant.
595
+     *
596
+     * @param {string} participantId - The id of the participant.
597
+     * @returns {string} The formatted display name.
598
+     */
599
+    _getFormattedDisplayName(participantId) {
600
+        const { formattedDisplayName }
601
+            = this._participants[participantId] || {};
602
+
603
+        return formattedDisplayName;
604
+    }
605
+
490 606
     /**
491 607
      * Returns the iframe that loads Jitsi Meet.
492 608
      *

+ 10
- 0
modules/UI/UI.js Parādīt failu

@@ -801,6 +801,16 @@ function changeAvatar(id, avatarUrl) {
801 801
     }
802 802
 }
803 803
 
804
+/**
805
+ * Returns the avatar URL for a given user.
806
+ *
807
+ * @param {string} id - The id of the user.
808
+ * @returns {string} The avatar URL.
809
+ */
810
+UI.getAvatarUrl = function(id) {
811
+    return Avatar.getAvatarUrl(id);
812
+};
813
+
804 814
 /**
805 815
  * Update user email.
806 816
  * @param {string} id user id

+ 4
- 0
modules/UI/avatar/Avatar.js Parādīt failu

@@ -48,6 +48,10 @@ export default {
48 48
             users[id] = {};
49 49
         }
50 50
         users[id][prop] = val;
51
+        APP.API.notifyAvatarChanged(
52
+            id === 'local' ? APP.conference.getMyUserId() : id,
53
+            this.getAvatarUrl(id)
54
+        );
51 55
     },
52 56
 
53 57
     /**

+ 2
- 1
modules/UI/videolayout/VideoContainer.js Parādīt failu

@@ -1,4 +1,4 @@
1
-/* global $, interfaceConfig */
1
+/* global $, APP, interfaceConfig */
2 2
 
3 3
 import Filmstrip from './Filmstrip';
4 4
 import LargeContainer from './LargeContainer';
@@ -545,6 +545,7 @@ export class VideoContainer extends LargeContainer {
545 545
         this.avatarDisplayed = show;
546 546
 
547 547
         this.emitter.emit(UIEvents.LARGE_VIDEO_AVATAR_VISIBLE, show);
548
+        APP.API.notifyLargeVideoVisibilityChanged(show);
548 549
     }
549 550
 
550 551
     /**

+ 161
- 43
react/features/always-on-top/AlwaysOnTop.js Parādīt failu

@@ -61,6 +61,9 @@ const TOOLBAR_TIMEOUT = 4000;
61 61
 type State = {
62 62
     audioAvailable: boolean,
63 63
     audioMuted: boolean,
64
+    avatarURL: string,
65
+    displayName: string,
66
+    isVideoDisplayed: boolean,
64 67
     videoAvailable: boolean,
65 68
     videoMuted: boolean,
66 69
     visible: boolean
@@ -89,13 +92,21 @@ export default class AlwaysOnTop extends Component<*, State> {
89 92
             audioMuted: false,
90 93
             videoMuted: false,
91 94
             audioAvailable: false,
92
-            videoAvailable: false
95
+            videoAvailable: false,
96
+            displayName: '',
97
+            isVideoDisplayed: true,
98
+            avatarURL: ''
93 99
         };
94 100
 
95 101
         // Bind event handlers so they are only bound once per instance.
96 102
         this._audioAvailabilityListener
97 103
             = this._audioAvailabilityListener.bind(this);
98 104
         this._audioMutedListener = this._audioMutedListener.bind(this);
105
+        this._avatarChangedListener = this._avatarChangedListener.bind(this);
106
+        this._largeVideoChangedListener
107
+            = this._largeVideoChangedListener.bind(this);
108
+        this._displayNameChangedListener
109
+            = this._displayNameChangedListener.bind(this);
99 110
         this._mouseMove = this._mouseMove.bind(this);
100 111
         this._onMouseOut = this._onMouseOut.bind(this);
101 112
         this._onMouseOver = this._onMouseOver.bind(this);
@@ -128,6 +139,40 @@ export default class AlwaysOnTop extends Component<*, State> {
128 139
         this.setState({ audioMuted: muted });
129 140
     }
130 141
 
142
+    _avatarChangedListener: () => void;
143
+
144
+    /**
145
+     * Handles avatar changed api events.
146
+     *
147
+     * @returns {void}
148
+     */
149
+    _avatarChangedListener({ avatarURL, id }) {
150
+        if (api._getOnStageParticipant() !== id) {
151
+            return;
152
+        }
153
+
154
+        if (avatarURL !== this.state.avatarURL) {
155
+            this.setState({ avatarURL });
156
+        }
157
+    }
158
+
159
+    _displayNameChangedListener: () => void;
160
+
161
+    /**
162
+     * Handles display name changed api events.
163
+     *
164
+     * @returns {void}
165
+     */
166
+    _displayNameChangedListener({ formattedDisplayName, id }) {
167
+        if (api._getOnStageParticipant() !== id) {
168
+            return;
169
+        }
170
+
171
+        if (formattedDisplayName !== this.state.displayName) {
172
+            this.setState({ displayName: formattedDisplayName });
173
+        }
174
+    }
175
+
131 176
     /**
132 177
      * Hides the toolbar after a timeout.
133 178
      *
@@ -144,6 +189,26 @@ export default class AlwaysOnTop extends Component<*, State> {
144 189
         }, TOOLBAR_TIMEOUT);
145 190
     }
146 191
 
192
+    _largeVideoChangedListener: () => void;
193
+
194
+    /**
195
+     * Handles large video changed api events.
196
+     *
197
+     * @returns {void}
198
+     */
199
+    _largeVideoChangedListener() {
200
+        const userID = api._getOnStageParticipant();
201
+        const displayName = api._getFormattedDisplayName(userID);
202
+        const avatarURL = api.getAvatarURL(userID);
203
+        const isVideoDisplayed = Boolean(api._getLargeVideo());
204
+
205
+        this.setState({
206
+            avatarURL,
207
+            displayName,
208
+            isVideoDisplayed
209
+        });
210
+    }
211
+
147 212
     _mouseMove: () => void;
148 213
 
149 214
     /**
@@ -181,6 +246,38 @@ export default class AlwaysOnTop extends Component<*, State> {
181 246
 
182 247
     _videoAvailabilityListener: ({ available: boolean }) => void;
183 248
 
249
+    /**
250
+     * Renders display name and avatar for the on stage participant.
251
+     *
252
+     * @returns {ReactElement}
253
+     */
254
+    _renderVideoNotAvailableScreen() {
255
+        const { avatarURL, displayName, isVideoDisplayed } = this.state;
256
+
257
+        if (isVideoDisplayed) {
258
+            return null;
259
+        }
260
+
261
+        return (
262
+            <div id = 'videoNotAvailableScreen'>
263
+                {
264
+                    avatarURL
265
+                        ? <div id = 'avatarContainer'>
266
+                            <img
267
+                                id = 'avatar'
268
+                                src = { avatarURL } />
269
+                        </div>
270
+                        : null
271
+                }
272
+                <div
273
+                    className = 'displayname'
274
+                    id = 'displayname'>
275
+                    { displayName }
276
+                </div>
277
+            </div>
278
+        );
279
+    }
280
+
184 281
     /**
185 282
      * Handles audio available api events.
186 283
      *
@@ -214,6 +311,11 @@ export default class AlwaysOnTop extends Component<*, State> {
214 311
         api.on('videoMuteStatusChanged', this._videoMutedListener);
215 312
         api.on('audioAvailabilityChanged', this._audioAvailabilityListener);
216 313
         api.on('videoAvailabilityChanged', this._videoAvailabilityListener);
314
+        api.on('largeVideoChanged', this._largeVideoChangedListener);
315
+        api.on('displayNameChange', this._displayNameChangedListener);
316
+        api.on('avatarChanged', this._avatarChangedListener);
317
+
318
+        this._largeVideoChangedListener();
217 319
 
218 320
         Promise.all([
219 321
             api.isAudioMuted(),
@@ -256,6 +358,11 @@ export default class AlwaysOnTop extends Component<*, State> {
256 358
             this._audioAvailabilityListener);
257 359
         api.removeListener('videoAvailabilityChanged',
258 360
             this._videoAvailabilityListener);
361
+        api.removeListener('largeVideoChanged',
362
+            this._largeVideoChangedListener);
363
+        api.removeListener('displayNameChange',
364
+            this._displayNameChangedListener);
365
+        api.removeListener('avatarChanged', this._avatarChangedListener);
259 366
         window.removeEventListener('mousemove', this._mouseMove);
260 367
     }
261 368
 
@@ -283,50 +390,61 @@ export default class AlwaysOnTop extends Component<*, State> {
283 390
                 this.state.visible ? 'fadeIn' : 'fadeOut'}`;
284 391
 
285 392
         return (
286
-            <StatelessToolbar
287
-                className = { className }
288
-                onMouseOut = { this._onMouseOut }
289
-                onMouseOver = { this._onMouseOver }>
393
+            <div id = 'alwaysOnTop'>
394
+                <StatelessToolbar
395
+                    className = { className }
396
+                    onMouseOut = { this._onMouseOut }
397
+                    onMouseOver = { this._onMouseOver }>
398
+                    {
399
+                        Object.entries(TOOLBAR_BUTTONS).map(
400
+                            ([ key, button ]) => {
401
+                                // XXX The following silences a couple of flow
402
+                                // errors:
403
+                                if (button === null
404
+                                        || typeof button !== 'object') {
405
+                                    return null;
406
+                                }
407
+
408
+                                const { onClick } = button;
409
+                                let enabled = false;
410
+                                let toggled = false;
411
+
412
+                                switch (key) {
413
+                                case 'microphone':
414
+                                    enabled = this.state.audioAvailable;
415
+                                    toggled = enabled
416
+                                        ? this.state.audioMuted : true;
417
+                                    break;
418
+                                case 'camera':
419
+                                    enabled = this.state.videoAvailable;
420
+                                    toggled = enabled
421
+                                        ? this.state.videoMuted : true;
422
+                                    break;
423
+                                default: // hangup button
424
+                                    toggled = false;
425
+                                    enabled = true;
426
+                                }
427
+
428
+                                const updatedButton = {
429
+                                    ...button,
430
+                                    enabled,
431
+                                    toggled
432
+                                };
433
+
434
+                                return (
435
+                                    <StatelessToolbarButton
436
+                                        button = { updatedButton }
437
+                                        key = { key }
438
+                                        onClick = { onClick } />
439
+                                );
440
+                            }
441
+                        )
442
+                    }
443
+                </StatelessToolbar>
290 444
                 {
291
-                    Object.entries(TOOLBAR_BUTTONS).map(([ key, button ]) => {
292
-                        // XXX The following silences a couple of flow errors:
293
-                        if (button === null || typeof button !== 'object') {
294
-                            return null;
295
-                        }
296
-
297
-                        const { onClick } = button;
298
-                        let enabled = false;
299
-                        let toggled = false;
300
-
301
-                        switch (key) {
302
-                        case 'microphone':
303
-                            enabled = this.state.audioAvailable;
304
-                            toggled = enabled ? this.state.audioMuted : true;
305
-                            break;
306
-                        case 'camera':
307
-                            enabled = this.state.videoAvailable;
308
-                            toggled = enabled ? this.state.videoMuted : true;
309
-                            break;
310
-                        default: // hangup button
311
-                            toggled = false;
312
-                            enabled = true;
313
-                        }
314
-
315
-                        const updatedButton = {
316
-                            ...button,
317
-                            enabled,
318
-                            toggled
319
-                        };
320
-
321
-                        return (
322
-                            <StatelessToolbarButton
323
-                                button = { updatedButton }
324
-                                key = { key }
325
-                                onClick = { onClick } />
326
-                        );
327
-                    })
445
+                    this._renderVideoNotAvailableScreen()
328 446
                 }
329
-            </StatelessToolbar>
447
+            </div>
330 448
         );
331 449
     }
332 450
 }

+ 3
- 4
react/features/display-name/components/DisplayName.web.js Parādīt failu

@@ -2,6 +2,8 @@ import PropTypes from 'prop-types';
2 2
 import React, { Component } from 'react';
3 3
 import { connect } from 'react-redux';
4 4
 
5
+import { appendSuffix } from '../functions';
6
+
5 7
 import { translate } from '../../base/i18n';
6 8
 import { participantDisplayNameChanged } from '../../base/participants';
7 9
 
@@ -144,15 +146,12 @@ class DisplayName extends Component {
144 146
             );
145 147
         }
146 148
 
147
-        const suffix
148
-            = displayName && displayNameSuffix ? ` (${displayNameSuffix})` : '';
149
-
150 149
         return (
151 150
             <span
152 151
                 className = 'displayname'
153 152
                 id = { elementID }
154 153
                 onClick = { this._onStartEditing }>
155
-                { `${displayName || displayNameSuffix || ''}${suffix}` }
154
+                { `${appendSuffix(displayName, displayNameSuffix)}` }
156 155
             </span>
157 156
         );
158 157
     }

+ 11
- 0
react/features/display-name/functions.js Parādīt failu

@@ -0,0 +1,11 @@
1
+/**
2
+ * Appends a suffix to the display name.
3
+ *
4
+ * @param {string} displayName - The display name.
5
+ * @param {string} suffix - Suffix that will be appended.
6
+ * @returns {string} The formatted display name.
7
+ */
8
+export function appendSuffix(displayName, suffix) {
9
+    return `${displayName || suffix || ''}${
10
+        displayName && suffix ? ` (${suffix})` : ''}`;
11
+}

+ 1
- 0
react/features/display-name/index.js Parādīt failu

@@ -1,2 +1,3 @@
1 1
 export * from './actions';
2 2
 export * from './components';
3
+export * from './functions';

Notiek ielāde…
Atcelt
Saglabāt