瀏覽代碼

feat(VideoLayout): add ninja icon

Add ninja icon which wil be displayed when user's connection status is
inactive.

Apply grey filter only for interrupted state.

Do not use isLastN directly, but check ParticipantConnectionStatus.
j8
paweldomas 8 年之前
父節點
當前提交
12d7e61362

+ 0
- 12
conference.js 查看文件

@@ -2125,18 +2125,6 @@ export default {
2125 2125
         eventEmitter.removeListener(eventName, listener);
2126 2126
     },
2127 2127
 
2128
-    /**
2129
-     * Checks if the participant given by participantId is currently in the
2130
-     * last N set if there's one supported.
2131
-     *
2132
-     * @param participantId the identifier of the participant
2133
-     * @returns {boolean} {true} if the participant given by the participantId
2134
-     * is currently in the last N set or if there's no last N set at this point
2135
-     * and {false} otherwise
2136
-     */
2137
-    isInLastN(participantId) {
2138
-        return room.isInLastN(participantId);
2139
-    },
2140 2128
     /**
2141 2129
      * Changes the display name for the local user
2142 2130
      * @param nickname {string} the new display name

+ 5
- 0
css/_videolayout_default.scss 查看文件

@@ -97,6 +97,11 @@
97 97
                     color: #FFFFFF;/*#15A1ED*/
98 98
                     overflow: hidden;
99 99
                 }
100
+
101
+                &_ninja
102
+                {
103
+                    font-size: 1.5em;
104
+                }
100 105
             }
101 106
 
102 107
             .icon-connection,

+ 28
- 15
modules/UI/videolayout/ConnectionIndicator.js 查看文件

@@ -1,9 +1,12 @@
1
-/* global $, APP, interfaceConfig */
1
+/* global $, APP, interfaceConfig, JitsiMeetJS */
2 2
 /* jshint -W101 */
3 3
 
4 4
 import JitsiPopover from "../util/JitsiPopover";
5 5
 import UIUtil from "../util/UIUtil";
6 6
 
7
+const ParticipantConnectionStatus
8
+    = JitsiMeetJS.constants.participantConnectionStatus;
9
+
7 10
 /**
8 11
  * Maps a connection quality value (in percent) to the width of the "full" icon.
9 12
  */
@@ -334,8 +337,11 @@ ConnectionIndicator.prototype.create = function () {
334 337
         createIcon(["connection_full"], "icon-connection"));
335 338
     this.interruptedIndicator = connectionIconContainer.appendChild(
336 339
         createIcon(["connection_lost"],"icon-connection-lost"));
340
+    this.ninjaIndicator = connectionIconContainer.appendChild(
341
+        createIcon(["connection_ninja"],"icon-ninja"));
337 342
 
338 343
     $(this.interruptedIndicator).hide();
344
+    $(this.ninjaIndicator).hide();
339 345
     this.connectionIndicatorContainer.appendChild(connectionIconContainer);
340 346
 };
341 347
 
@@ -351,23 +357,30 @@ ConnectionIndicator.prototype.remove = function() {
351 357
 };
352 358
 
353 359
 /**
354
- * Updates the UI which displays warning about user's connectivity problems.
360
+ * Updates the UI which displays or not a warning about user's connectivity
361
+ * problems.
355 362
  *
356
- * @param {boolean} isActive true if the connection is working fine or false if
357
- * the user is having connectivity issues.
363
+ * @param {ParticipantConnectionStatus} connectionStatus
358 364
  */
359 365
 ConnectionIndicator.prototype.updateConnectionStatusIndicator
360
-    = function (isActive) {
361
-        this.isConnectionActive = isActive;
362
-        if (this.isConnectionActive) {
363
-            $(this.interruptedIndicator).hide();
364
-            $(this.emptyIcon).show();
365
-            $(this.fullIcon).show();
366
-        } else {
367
-            $(this.interruptedIndicator).show();
368
-            $(this.emptyIcon).hide();
369
-            $(this.fullIcon).hide();
370
-        }
366
+= function (connectionStatus) {
367
+    this.connectionStatus = connectionStatus;
368
+    if (connectionStatus === ParticipantConnectionStatus.INTERRUPTED) {
369
+        $(this.interruptedIndicator).show();
370
+        $(this.emptyIcon).hide();
371
+        $(this.fullIcon).hide();
372
+        $(this.ninjaIndicator).hide();
373
+    } else if (connectionStatus === ParticipantConnectionStatus.INACTIVE) {
374
+        $(this.interruptedIndicator).hide();
375
+        $(this.emptyIcon).hide();
376
+        $(this.fullIcon).hide();
377
+        $(this.ninjaIndicator).show();
378
+    } else {
379
+        $(this.interruptedIndicator).hide();
380
+        $(this.emptyIcon).show();
381
+        $(this.fullIcon).show();
382
+        $(this.ninjaIndicator).hide();
383
+    }
371 384
 };
372 385
 
373 386
 /**

+ 72
- 52
modules/UI/videolayout/LargeVideoManager.js 查看文件

@@ -26,6 +26,18 @@ const VIDEO_RESOLUTION_POLL_INTERVAL = 2000;
26 26
  * Manager for all Large containers.
27 27
  */
28 28
 export default class LargeVideoManager {
29
+    /**
30
+     * Checks whether given container is a {@link VIDEO_CONTAINER_TYPE}.
31
+     * FIXME currently this is a workaround for the problem where video type is
32
+     * mixed up with container type.
33
+     * @param {string} containerType
34
+     * @return {boolean}
35
+     */
36
+    static isVideoContainer(containerType) {
37
+        return containerType === VIDEO_CONTAINER_TYPE
38
+            || containerType === DESKTOP_CONTAINER_TYPE;
39
+    }
40
+
29 41
     constructor (emitter) {
30 42
         /**
31 43
          * The map of <tt>LargeContainer</tt>s where the key is the video
@@ -116,7 +128,8 @@ export default class LargeVideoManager {
116 128
         this.enableLocalConnectionProblemFilter(true);
117 129
         this._setLocalConnectionMessage("connection.RECONNECTING");
118 130
         // Show the message only if the video is currently being displayed
119
-        this.showLocalConnectionMessage(this.state === VIDEO_CONTAINER_TYPE);
131
+        this.showLocalConnectionMessage(
132
+            LargeVideoManager.isVideoContainer(this.state));
120 133
     }
121 134
 
122 135
     /**
@@ -146,7 +159,12 @@ export default class LargeVideoManager {
146 159
 
147 160
         preUpdate.then(() => {
148 161
             const { id, stream, videoType, resolve } = this.newStreamData;
149
-            const isVideoFromCamera = videoType === VIDEO_CONTAINER_TYPE;
162
+
163
+            // FIXME this does not really make sense, because the videoType
164
+            // (camera or desktop) is a completely different thing than
165
+            // the video container type (Etherpad, SharedVideo, VideoContainer).
166
+            const isVideoContainer
167
+                = LargeVideoManager.isVideoContainer(videoType);
150 168
 
151 169
             this.newStreamData = null;
152 170
 
@@ -158,34 +176,26 @@ export default class LargeVideoManager {
158 176
             // change the avatar url on large
159 177
             this.updateAvatar(Avatar.getAvatarUrl(id));
160 178
 
161
-            // FIXME that does not really make sense, because the videoType
162
-            // (camera or desktop) is a completely different thing than
163
-            // the video container type (Etherpad, SharedVideo, VideoContainer).
164
-            // ----------------------------------------------------------------
165
-            // If the container is VIDEO_CONTAINER_TYPE, we need to check
166
-            // its stream whether exist and is muted to set isVideoMuted
167
-            // in rest of the cases it is false
168
-            let showAvatar = isVideoFromCamera && (!stream || stream.isMuted());
169
-
170 179
             // If the user's connection is disrupted then the avatar will be
171 180
             // displayed in case we have no video image cached. That is if
172
-            // there was a user switch(image is lost on stream detach) or if
181
+            // there was a user switch (image is lost on stream detach) or if
173 182
             // the video was not rendered, before the connection has failed.
174
-            const isConnectionActive = this._isConnectionActive(id);
175
-
176
-            if (isVideoFromCamera
177
-                    && !isConnectionActive
178
-                    && (isUserSwitch || !container.wasVideoRendered)) {
179
-                showAvatar = true;
180
-            }
181
-
182
-            // If audio only mode is enabled, always show the avatar for
183
-            // videos from another participant.
184
-            if (APP.conference.isAudioOnly()
185
-                && (isVideoFromCamera
186
-                    || videoType === DESKTOP_CONTAINER_TYPE)) {
187
-                showAvatar = true;
188
-            }
183
+            const wasUsersImageCached
184
+                = !isUserSwitch && container.wasVideoRendered;
185
+            const isVideoMuted = !stream || stream.isMuted();
186
+
187
+            const connectionStatus
188
+                = APP.conference.getParticipantConnectionStatus(id);
189
+            const isVideoRenderable
190
+                = !isVideoMuted
191
+                    && (APP.conference.isLocalId(id)
192
+                        || connectionStatus
193
+                                === ParticipantConnectionStatus.ACTIVE
194
+                        || wasUsersImageCached);
195
+
196
+            let showAvatar
197
+                = isVideoContainer
198
+                    && (APP.conference.isAudioOnly() || !isVideoRenderable);
189 199
 
190 200
             let promise;
191 201
 
@@ -208,28 +218,31 @@ export default class LargeVideoManager {
208 218
                 this.updateLargeVideoAudioLevel(0);
209 219
             }
210 220
 
221
+            const isConnectionInterrupted
222
+                = APP.conference.getParticipantConnectionStatus(id)
223
+                    === ParticipantConnectionStatus.INTERRUPTED;
224
+            let messageKey = null;
225
+
226
+            if (isConnectionInterrupted) {
227
+                messageKey = "connection.USER_CONNECTION_INTERRUPTED";
228
+            } else if (connectionStatus
229
+                    === ParticipantConnectionStatus.INACTIVE) {
230
+                messageKey = "connection.LOW_BANDWIDTH";
231
+            }
232
+
211 233
             // Make sure no notification about remote failure is shown as
212 234
             // its UI conflicts with the one for local connection interrupted.
213 235
             // For the purposes of UI indicators, audio only is considered as
214 236
             // an "active" connection.
215
-            const isConnected
237
+            const overrideAndHide
216 238
                 = APP.conference.isAudioOnly()
217
-                    || APP.conference.isConnectionInterrupted()
218
-                    || isConnectionActive;
219
-
220
-            // when isHavingConnectivityIssues, state can be inactive,
221
-            // interrupted or restoring. We show different message for
222
-            // interrupted and the rest.
223
-            const isConnectionInterrupted =
224
-                APP.conference.getParticipantConnectionStatus(id)
225
-                    === ParticipantConnectionStatus.INTERRUPTED;
239
+                    || APP.conference.isConnectionInterrupted();
226 240
 
227 241
             this.updateParticipantConnStatusIndication(
228 242
                     id,
229
-                    isConnected,
230
-                    (isConnectionInterrupted)
231
-                        ? "connection.USER_CONNECTION_INTERRUPTED"
232
-                        : "connection.LOW_BANDWIDTH");
243
+                    !overrideAndHide && isConnectionInterrupted,
244
+                    !overrideAndHide && messageKey !== null,
245
+                    messageKey);
233 246
 
234 247
             // resolve updateLargeVideo promise after everything is done
235 248
             promise.then(resolve);
@@ -265,18 +278,20 @@ export default class LargeVideoManager {
265 278
      * shown on the large video area.
266 279
      *
267 280
      * @param {string} id the id of remote participant(MUC nickname)
268
-     * @param {boolean} isConnected true if the connection is active or false
269
-     * when the user is having connectivity issues.
281
+     * @param {boolean} showProblemsIndication
282
+     * @param {boolean} showMessage
270 283
      * @param {string} messageKey the i18n key of the message
271 284
      *
272 285
      * @private
273 286
      */
274
-    updateParticipantConnStatusIndication (id, isConnected, messageKey) {
287
+    updateParticipantConnStatusIndication (
288
+        id, showProblemsIndication, showMessage, messageKey) {
275 289
 
276 290
         // Apply grey filter on the large video
277
-        this.videoContainer.showRemoteConnectionProblemIndicator(!isConnected);
291
+        this.videoContainer.showRemoteConnectionProblemIndicator(
292
+            showProblemsIndication);
278 293
 
279
-        if (isConnected) {
294
+        if (!showMessage) {
280 295
             // Hide the message
281 296
             this.showRemoteConnectionMessage(false);
282 297
         } else {
@@ -289,7 +304,7 @@ export default class LargeVideoManager {
289 304
 
290 305
             // Show it now only if the VideoContainer is on top
291 306
             this.showRemoteConnectionMessage(
292
-                this.state === VIDEO_CONTAINER_TYPE);
307
+                LargeVideoManager.isVideoContainer(this.state));
293 308
         }
294 309
     }
295 310
 
@@ -412,15 +427,20 @@ export default class LargeVideoManager {
412 427
      * Shows hides the "avatar" message which is to be displayed either in
413 428
      * the middle of the screen or below the avatar image.
414 429
      *
415
-     * @param {null|boolean} show (optional) <tt>true</tt> to show the avatar
430
+     * @param {null|boolean} [show=null] <tt>true</tt> to show the avatar
416 431
      * message or <tt>false</tt> to hide it. If not provided then the connection
417 432
      * status of the user currently on the large video will be obtained form
418 433
      * "APP.conference" and the message will be displayed if the user's
419
-     * connection is interrupted.
434
+     * connection is either interrupted or inactive.
420 435
      */
421 436
     showRemoteConnectionMessage (show) {
422 437
         if (typeof show !== 'boolean') {
423
-            show = !this._isConnectionActive(this.id);
438
+            const connStatus
439
+                = APP.conference.getParticipantConnectionStatus(this.id);
440
+
441
+            show = !APP.conference.isLocalId(this.id)
442
+                && (connStatus === ParticipantConnectionStatus.INTERRUPTED
443
+                    || connStatus === ParticipantConnectionStatus.INACTIVE);
424 444
         }
425 445
 
426 446
         if (show) {
@@ -526,7 +546,7 @@ export default class LargeVideoManager {
526 546
         // FIXME when video is being replaced with other content we need to hide
527 547
         // companion icons/messages. It would be best if the container would
528 548
         // be taking care of it by itself, but that is a bigger refactoring
529
-        if (this.state === VIDEO_CONTAINER_TYPE) {
549
+        if (LargeVideoManager.isVideoContainer(this.state)) {
530 550
             this.showWatermark(false);
531 551
             this.showLocalConnectionMessage(false);
532 552
             this.showRemoteConnectionMessage(false);
@@ -537,7 +557,7 @@ export default class LargeVideoManager {
537 557
         let container = this.getContainer(type);
538 558
 
539 559
         return container.show().then(() => {
540
-            if (type === VIDEO_CONTAINER_TYPE) {
560
+            if (LargeVideoManager.isVideoContainer(type)) {
541 561
                 // FIXME when video appears on top of other content we need to
542 562
                 // show companion icons/messages. It would be best if
543 563
                 // the container would be taking care of it by itself, but that

+ 23
- 26
modules/UI/videolayout/RemoteVideo.js 查看文件

@@ -448,29 +448,27 @@ RemoteVideo.prototype.updateRemoteVideoMenu = function (isMuted, force) {
448 448
 
449 449
 /**
450 450
  * @inheritDoc
451
+ * @override
451 452
  */
452
-RemoteVideo.prototype.setMutedView = function(isMuted) {
453
-    SmallVideo.prototype.setMutedView.call(this, isMuted);
453
+RemoteVideo.prototype.setVideoMutedView = function(isMuted) {
454
+    SmallVideo.prototype.setVideoMutedView.call(this, isMuted);
454 455
     // Update 'mutedWhileDisconnected' flag
455
-    this._figureOutMutedWhileDisconnected(this.isConnectionInterrupted());
456
+    this._figureOutMutedWhileDisconnected();
456 457
 };
457 458
 
458 459
 /**
459 460
  * Figures out the value of {@link #mutedWhileDisconnected} flag by taking into
460 461
  * account remote participant's network connectivity and video muted status.
461 462
  *
462
- * @param {boolean} isDisconnected <tt>true</tt> if the remote participant is
463
- * currently having connectivity issues or <tt>false</tt> otherwise.
464
- *
465 463
  * @private
466 464
  */
467
-RemoteVideo.prototype._figureOutMutedWhileDisconnected
468
-    = function(isDisconnected) {
469
-        if (isDisconnected && this.isVideoMuted) {
470
-            this.mutedWhileDisconnected = true;
471
-        } else if (!isDisconnected && !this.isVideoMuted) {
472
-            this.mutedWhileDisconnected = false;
473
-        }
465
+RemoteVideo.prototype._figureOutMutedWhileDisconnected = function() {
466
+    const isActive = this.isConnectionActive();
467
+    if (!isActive && this.isVideoMuted) {
468
+        this.mutedWhileDisconnected = true;
469
+    } else if (isActive && !this.isVideoMuted) {
470
+        this.mutedWhileDisconnected = false;
471
+    }
474 472
 };
475 473
 
476 474
 /**
@@ -572,26 +570,25 @@ RemoteVideo.prototype.updateView = function () {
572 570
  * Updates the UI to reflect user's connectivity status.
573 571
  */
574 572
 RemoteVideo.prototype.updateConnectionStatusIndicator = function () {
575
-    const isActive = this.isConnectionActive();
573
+    const connectionStatus = this.user.getConnectionStatus();
576 574
 
577
-    if (isActive === null) {
578
-        // Cancel processing at this point - no update
579
-        return;
580
-    }
581
-
582
-    logger.debug(this.id + " thumbnail is connection active ? " + isActive);
575
+    logger.debug(`${this.id} thumbnail connection status: ${connectionStatus}`);
583 576
 
577
+    // FIXME rename 'mutedWhileDisconnected' to 'mutedWhileNotRendering'
584 578
     // Update 'mutedWhileDisconnected' flag
585
-    this._figureOutMutedWhileDisconnected(!isActive);
586
-
587
-    if(this.connectionIndicator)
588
-        this.connectionIndicator.updateConnectionStatusIndicator(isActive);
579
+    this._figureOutMutedWhileDisconnected();
580
+    if(this.connectionIndicator) {
581
+        this.connectionIndicator.updateConnectionStatusIndicator(
582
+            connectionStatus);
583
+    }
589 584
 
585
+    const isInterrupted
586
+        = connectionStatus === ParticipantConnectionStatus.INTERRUPTED;
590 587
     // Toggle thumbnail video problem filter
591 588
     this.selectVideoElement().toggleClass(
592
-        "videoThumbnailProblemFilter", !isActive);
589
+        "videoThumbnailProblemFilter", isInterrupted);
593 590
     this.$avatar().toggleClass(
594
-        "videoThumbnailProblemFilter", !isActive);
591
+        "videoThumbnailProblemFilter", isInterrupted);
595 592
 };
596 593
 
597 594
 /**

+ 7
- 1
modules/UI/videolayout/SmallVideo.js 查看文件

@@ -6,6 +6,8 @@ import UIUtil from "../util/UIUtil";
6 6
 import UIEvents from "../../../service/UI/UIEvents";
7 7
 import AudioLevels from "../audio_levels/AudioLevels";
8 8
 
9
+const ParticipantConnectionStatus
10
+    = JitsiMeetJS.constants.participantConnectionStatus;
9 11
 const RTCUIHelper = JitsiMeetJS.util.RTCUIHelper;
10 12
 
11 13
 /**
@@ -444,9 +446,13 @@ SmallVideo.prototype.isCurrentlyOnLargeVideo = function () {
444 446
  * or <tt>false</tt> otherwise.
445 447
  */
446 448
 SmallVideo.prototype.isVideoPlayable = function() {
449
+    const connectionState
450
+        = APP.conference.getParticipantConnectionStatus(this.id);
451
+
447 452
     return this.videoStream // Is there anything to display ?
448 453
         && !this.isVideoMuted && !this.videoStream.isMuted() // Muted ?
449
-        && (this.isLocal || APP.conference.isInLastN(this.id));
454
+        && (this.isLocal
455
+                || connectionState === ParticipantConnectionStatus.ACTIVE);
450 456
 };
451 457
 
452 458
 /**

+ 14
- 3
modules/UI/videolayout/VideoLayout.js 查看文件

@@ -1,4 +1,4 @@
1
-/* global APP, $, interfaceConfig */
1
+/* global APP, $, interfaceConfig, JitsiMeetJS  */
2 2
 const logger = require("jitsi-meet-logger").getLogger(__filename);
3 3
 
4 4
 import Filmstrip from "./Filmstrip";
@@ -10,6 +10,9 @@ import LargeVideoManager  from "./LargeVideoManager";
10 10
 import {VIDEO_CONTAINER_TYPE} from "./VideoContainer";
11 11
 import LocalVideo from "./LocalVideo";
12 12
 
13
+const ParticipantConnectionStatus
14
+    = JitsiMeetJS.constants.participantConnectionStatus;
15
+
13 16
 var remoteVideos = {};
14 17
 var localVideoThumbnail = null;
15 18
 
@@ -559,8 +562,16 @@ var VideoLayout = {
559 562
      * is fine.
560 563
      */
561 564
     showLocalConnectionInterrupted (isInterrupted) {
562
-        localVideoThumbnail.connectionIndicator
563
-            .updateConnectionStatusIndicator(!isInterrupted);
565
+        // Currently local video thumbnail displays only "active" or
566
+        // "interrupted" despite the fact that ConnectionIndicator supports more
567
+        // states.
568
+        const status
569
+            = isInterrupted
570
+                ? ParticipantConnectionStatus.INTERRUPTED
571
+                : ParticipantConnectionStatus.ACTIVE;
572
+
573
+        localVideoThumbnail
574
+            .connectionIndicator.updateConnectionStatusIndicator(status);
564 575
     },
565 576
 
566 577
     /**

Loading…
取消
儲存