Browse Source

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

master
hristoterezov 7 years ago
parent
commit
5ffcaca649

+ 37
- 4
conference.js View File

78
 import { getLocationContextRoot } from './react/features/base/util';
78
 import { getLocationContextRoot } from './react/features/base/util';
79
 import { statsEmitter } from './react/features/connection-indicator';
79
 import { statsEmitter } from './react/features/connection-indicator';
80
 import { showDesktopPicker } from './react/features/desktop-picker';
80
 import { showDesktopPicker } from './react/features/desktop-picker';
81
+import { appendSuffix } from './react/features/display-name';
81
 import { maybeOpenFeedbackDialog } from './react/features/feedback';
82
 import { maybeOpenFeedbackDialog } from './react/features/feedback';
82
 import {
83
 import {
83
     mediaPermissionPromptVisibilityChanged,
84
     mediaPermissionPromptVisibilityChanged,
1726
             if (user.isHidden()) {
1727
             if (user.isHidden()) {
1727
                 return;
1728
                 return;
1728
             }
1729
             }
1730
+            const displayName = user.getDisplayName();
1729
 
1731
 
1730
             APP.store.dispatch(participantJoined({
1732
             APP.store.dispatch(participantJoined({
1731
                 id,
1733
                 id,
1732
-                name: user.getDisplayName(),
1734
+                name: displayName,
1733
                 role: user.getRole()
1735
                 role: user.getRole()
1734
             }));
1736
             }));
1735
 
1737
 
1736
             logger.log('USER %s connnected', id, user);
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
             APP.UI.addUser(user);
1744
             APP.UI.addUser(user);
1739
 
1745
 
1740
             // check the roles for the new user and reflect them
1746
             // check the roles for the new user and reflect them
1892
             }
1898
             }
1893
 
1899
 
1894
             APP.UI.addListener(UIEvents.SELECTED_ENDPOINT, id => {
1900
             APP.UI.addListener(UIEvents.SELECTED_ENDPOINT, id => {
1901
+                APP.API.notifyOnStageParticipantChanged(id);
1895
                 try {
1902
                 try {
1896
                     // do not try to select participant if there is none (we
1903
                     // do not try to select participant if there is none (we
1897
                     // are alone in the room), otherwise an error will be
1904
                     // are alone in the room), otherwise an error will be
1938
                     id,
1945
                     id,
1939
                     name: formattedDisplayName
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
                 APP.UI.changeDisplayName(id, formattedDisplayName);
1955
                 APP.UI.changeDisplayName(id, formattedDisplayName);
1943
             }
1956
             }
1944
         );
1957
         );
2377
         APP.store.dispatch(conferenceJoined(room));
2390
         APP.store.dispatch(conferenceJoined(room));
2378
 
2391
 
2379
         APP.UI.mucJoined();
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
         APP.UI.markVideoInterrupted(false);
2406
         APP.UI.markVideoInterrupted(false);
2382
     },
2407
     },
2383
 
2408
 
2748
         }));
2773
         }));
2749
 
2774
 
2750
         APP.settings.setDisplayName(formattedNickname);
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
         if (room) {
2784
         if (room) {
2752
             room.setDisplayName(formattedNickname);
2785
             room.setDisplayName(formattedNickname);
2753
             APP.UI.changeDisplayName(id, formattedNickname);
2786
             APP.UI.changeDisplayName(id, formattedNickname);

+ 24
- 0
css/_videolayout_default.scss View File

250
 /**
250
 /**
251
  * Positions video thumbnail display name and editor.
251
  * Positions video thumbnail display name and editor.
252
  */
252
  */
253
+#alwaysOnTop .displayname,
253
 .videocontainer .displayname,
254
 .videocontainer .displayname,
254
 .videocontainer .editdisplayname {
255
 .videocontainer .editdisplayname {
255
     display: inline-block;
256
     display: inline-block;
269
     z-index: $zindex2;
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
  * Positions video thumbnail display name editor.
283
  * Positions video thumbnail display name editor.
274
  */
284
  */
507
     width: auto;
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
 .sharedVideoAvatar {
534
 .sharedVideoAvatar {
511
     height: 100%;
535
     height: 100%;
512
     width: 100%;
536
     width: 100%;

+ 36
- 14
doc/api.md View File

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.
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
 The following events are currently supported:
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
 * **audioAvailabilityChanged** - event notifications about audio availability status changes. The listener will receive an object with the following structure:
152
 * **audioAvailabilityChanged** - event notifications about audio availability status changes. The listener will receive an object with the following structure:
145
 ```javascript
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
 * **audioMuteStatusChanged** - event notifications about audio mute status changes. The listener will receive an object with the following structure:
159
 * **audioMuteStatusChanged** - event notifications about audio mute status changes. The listener will receive an object with the following structure:
152
 ```javascript
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
 messages. The listener will receive an object with the following structure:
167
 messages. The listener will receive an object with the following structure:
160
 ```javascript
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
 messages. The listener will receive an object with the following structure:
177
 messages. The listener will receive an object with the following structure:
170
 ```javascript
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
 changes. The listener will receive an object with the following structure:
185
 changes. The listener will receive an object with the following structure:
178
 ```javascript
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
 * **participantJoined** - event notifications about new participants who join the room. The listener will receive an object with the following structure:
193
 * **participantJoined** - event notifications about new participants who join the room. The listener will receive an object with the following structure:
186
 ```javascript
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
 * **participantLeft** - event notifications about participants that leave the room. The listener will receive an object with the following structure:
201
 * **participantLeft** - event notifications about participants that leave the room. The listener will receive an object with the following structure:
193
 ```javascript
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
 * **videoConferenceJoined** - event notifications fired when the local user has joined the video conference. The listener will receive an object with the following structure:
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
 ```javascript
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
 * **videoConferenceLeft** - event notifications fired when the local user has left the video conference. The listener will receive an object with the following structure:
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
 ```javascript
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
 * **videoAvailabilityChanged** - event notifications about video availability status changes. The listener will receive an object with the following structure:
225
 * **videoAvailabilityChanged** - event notifications about video availability status changes. The listener will receive an object with the following structure:
214
 ```javascript
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
 * **videoMuteStatusChanged** - event notifications about video mute status changes. The listener will receive an object with the following structure:
232
 * **videoMuteStatusChanged** - event notifications about video mute status changes. The listener will receive an object with the following structure:
221
 ```javascript
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
 var numberOfParticipants = api.getNumberOfParticipants();
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
 You can get the iframe HTML element where Jitsi Meet is loaded with the following API function:
289
 You can get the iframe HTML element where Jitsi Meet is loaded with the following API function:
268
 ```javascript
290
 ```javascript
269
 var iframe = api.getIFrame();
291
 var iframe = api.getIFrame();

+ 63
- 6
modules/API/API.js View File

184
         initCommands();
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
      * Sends event to the external application.
203
      * Sends event to the external application.
189
      *
204
      *
238
      * conference.
253
      * conference.
239
      *
254
      *
240
      * @param {string} id - User id.
255
      * @param {string} id - User id.
256
+     * @param {Object} props - The display name of the user.
241
      * @returns {void}
257
      * @returns {void}
242
      */
258
      */
243
-    notifyUserJoined(id: string) {
259
+    notifyUserJoined(id: string, props: Object) {
244
         this._sendEvent({
260
         this._sendEvent({
245
             name: 'participant-joined',
261
             name: 'participant-joined',
246
-            id
262
+            id,
263
+            ...props
247
         });
264
         });
248
     }
265
     }
249
 
266
 
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
      * Notify external application (if API is enabled) that user changed their
298
      * Notify external application (if API is enabled) that user changed their
266
      * nickname.
299
      * nickname.
267
      *
300
      *
268
      * @param {string} id - User id.
301
      * @param {string} id - User id.
269
      * @param {string} displayname - User nickname.
302
      * @param {string} displayname - User nickname.
303
+     * @param {string} formattedDisplayName - The display name shown in Jitsi
304
+     * meet's UI for the user.
270
      * @returns {void}
305
      * @returns {void}
271
      */
306
      */
272
-    notifyDisplayNameChanged(id: string, displayname: string) {
307
+    notifyDisplayNameChanged(
308
+            id: string,
309
+            { displayName, formattedDisplayName }: Object) {
273
         this._sendEvent({
310
         this._sendEvent({
274
             name: 'display-name-change',
311
             name: 'display-name-change',
275
-            displayname,
312
+            displayname: displayName,
313
+            formattedDisplayName,
276
             id
314
             id
277
         });
315
         });
278
     }
316
     }
282
      * been joined.
320
      * been joined.
283
      *
321
      *
284
      * @param {string} roomName - The room name.
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
      * @returns {void}
326
      * @returns {void}
286
      */
327
      */
287
-    notifyConferenceJoined(roomName: string) {
328
+    notifyConferenceJoined(roomName: string, id: string, props: Object) {
288
         this._sendEvent({
329
         this._sendEvent({
289
             name: 'video-conference-joined',
330
             name: 'video-conference-joined',
290
-            roomName
331
+            roomName,
332
+            id,
333
+            ...props
291
         });
334
         });
292
     }
335
     }
293
 
336
 
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
      * Disposes the allocated resources.
435
      * Disposes the allocated resources.

+ 120
- 4
modules/API/external/external_api.js View File

34
  * events expected by jitsi-meet
34
  * events expected by jitsi-meet
35
  */
35
  */
36
 const events = {
36
 const events = {
37
+    'avatar-changed': 'avatarChanged',
37
     'audio-availability-changed': 'audioAvailabilityChanged',
38
     'audio-availability-changed': 'audioAvailabilityChanged',
38
     'audio-mute-status-changed': 'audioMuteStatusChanged',
39
     'audio-mute-status-changed': 'audioMuteStatusChanged',
39
     'display-name-change': 'displayNameChange',
40
     'display-name-change': 'displayNameChange',
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
         this._setupListeners();
233
         this._setupListeners();
229
         id++;
234
         id++;
230
     }
235
     }
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
      * Sets the size of the iframe element.
315
      * Sets the size of the iframe element.
283
      *
316
      *
308
      * @private
341
      * @private
309
      */
342
      */
310
     _setupListeners() {
343
     _setupListeners() {
311
-
312
         this._transport.on('event', ({ name, ...data }) => {
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
                 changeParticipantNumber(this, 1);
360
                 changeParticipantNumber(this, 1);
315
-            } else if (name === 'participant-left') {
361
+                break;
362
+            }
363
+            case 'participant-left':
316
                 changeParticipantNumber(this, -1);
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
             const eventName = events[name];
398
             const eventName = events[name];
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
      * Returns the iframe that loads Jitsi Meet.
607
      * Returns the iframe that loads Jitsi Meet.
492
      *
608
      *

+ 10
- 0
modules/UI/UI.js View File

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
  * Update user email.
815
  * Update user email.
806
  * @param {string} id user id
816
  * @param {string} id user id

+ 4
- 0
modules/UI/avatar/Avatar.js View File

48
             users[id] = {};
48
             users[id] = {};
49
         }
49
         }
50
         users[id][prop] = val;
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 View File

1
-/* global $, interfaceConfig */
1
+/* global $, APP, interfaceConfig */
2
 
2
 
3
 import Filmstrip from './Filmstrip';
3
 import Filmstrip from './Filmstrip';
4
 import LargeContainer from './LargeContainer';
4
 import LargeContainer from './LargeContainer';
545
         this.avatarDisplayed = show;
545
         this.avatarDisplayed = show;
546
 
546
 
547
         this.emitter.emit(UIEvents.LARGE_VIDEO_AVATAR_VISIBLE, show);
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 View File

61
 type State = {
61
 type State = {
62
     audioAvailable: boolean,
62
     audioAvailable: boolean,
63
     audioMuted: boolean,
63
     audioMuted: boolean,
64
+    avatarURL: string,
65
+    displayName: string,
66
+    isVideoDisplayed: boolean,
64
     videoAvailable: boolean,
67
     videoAvailable: boolean,
65
     videoMuted: boolean,
68
     videoMuted: boolean,
66
     visible: boolean
69
     visible: boolean
89
             audioMuted: false,
92
             audioMuted: false,
90
             videoMuted: false,
93
             videoMuted: false,
91
             audioAvailable: false,
94
             audioAvailable: false,
92
-            videoAvailable: false
95
+            videoAvailable: false,
96
+            displayName: '',
97
+            isVideoDisplayed: true,
98
+            avatarURL: ''
93
         };
99
         };
94
 
100
 
95
         // Bind event handlers so they are only bound once per instance.
101
         // Bind event handlers so they are only bound once per instance.
96
         this._audioAvailabilityListener
102
         this._audioAvailabilityListener
97
             = this._audioAvailabilityListener.bind(this);
103
             = this._audioAvailabilityListener.bind(this);
98
         this._audioMutedListener = this._audioMutedListener.bind(this);
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
         this._mouseMove = this._mouseMove.bind(this);
110
         this._mouseMove = this._mouseMove.bind(this);
100
         this._onMouseOut = this._onMouseOut.bind(this);
111
         this._onMouseOut = this._onMouseOut.bind(this);
101
         this._onMouseOver = this._onMouseOver.bind(this);
112
         this._onMouseOver = this._onMouseOver.bind(this);
128
         this.setState({ audioMuted: muted });
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
      * Hides the toolbar after a timeout.
177
      * Hides the toolbar after a timeout.
133
      *
178
      *
144
         }, TOOLBAR_TIMEOUT);
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
     _mouseMove: () => void;
212
     _mouseMove: () => void;
148
 
213
 
149
     /**
214
     /**
181
 
246
 
182
     _videoAvailabilityListener: ({ available: boolean }) => void;
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
      * Handles audio available api events.
282
      * Handles audio available api events.
186
      *
283
      *
214
         api.on('videoMuteStatusChanged', this._videoMutedListener);
311
         api.on('videoMuteStatusChanged', this._videoMutedListener);
215
         api.on('audioAvailabilityChanged', this._audioAvailabilityListener);
312
         api.on('audioAvailabilityChanged', this._audioAvailabilityListener);
216
         api.on('videoAvailabilityChanged', this._videoAvailabilityListener);
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
         Promise.all([
320
         Promise.all([
219
             api.isAudioMuted(),
321
             api.isAudioMuted(),
256
             this._audioAvailabilityListener);
358
             this._audioAvailabilityListener);
257
         api.removeListener('videoAvailabilityChanged',
359
         api.removeListener('videoAvailabilityChanged',
258
             this._videoAvailabilityListener);
360
             this._videoAvailabilityListener);
361
+        api.removeListener('largeVideoChanged',
362
+            this._largeVideoChangedListener);
363
+        api.removeListener('displayNameChange',
364
+            this._displayNameChangedListener);
365
+        api.removeListener('avatarChanged', this._avatarChangedListener);
259
         window.removeEventListener('mousemove', this._mouseMove);
366
         window.removeEventListener('mousemove', this._mouseMove);
260
     }
367
     }
261
 
368
 
283
                 this.state.visible ? 'fadeIn' : 'fadeOut'}`;
390
                 this.state.visible ? 'fadeIn' : 'fadeOut'}`;
284
 
391
 
285
         return (
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 View File

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

+ 11
- 0
react/features/display-name/functions.js View File

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 View File

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

Loading…
Cancel
Save