Browse Source

feat(VideoLayout): add remote connection problems UI

Grey filter will be applied to the remote video/avatar displayed on
"large" and a message indicating remote connectivity issues will be
shown on top of that.
j8
paweldomas 8 years ago
parent
commit
661ea2cf45

+ 27
- 0
css/_videolayout_default.scss View File

@@ -450,6 +450,11 @@
450 450
     filter: grayscale(.5) opacity(0.8);
451 451
 }
452 452
 
453
+.remoteVideoProblemFilter {
454
+    -webkit-filter: grayscale(100%);
455
+    filter: grayscale(100%);
456
+}
457
+
453 458
 .videoProblemFilter {
454 459
     -webkit-filter: blur(10px) grayscale(.5) opacity(0.8);
455 460
     filter: blur(10px) grayscale(.5) opacity(0.8);
@@ -460,6 +465,28 @@
460 465
     filter: grayscale(100%);
461 466
 }
462 467
 
468
+#remoteConnectionMessage {
469
+    display: none;
470
+    position: absolute;
471
+    width: auto;
472
+    z-index: 1011;
473
+    font-weight: 600;
474
+    font-size: 14px;
475
+    text-align: center;
476
+    color: #FFF;
477
+    opacity: .80;
478
+    text-shadow:    0px 0px 1px rgba(0,0,0,0.3),
479
+                    0px 1px 1px rgba(0,0,0,0.3),
480
+                    1px 0px 1px rgba(0,0,0,0.3),
481
+                    0px 0px 1px rgba(0,0,0,0.3);
482
+
483
+    background: rgba(0,0,0,.5);
484
+    border-radius: 5px;
485
+    padding: 5px;
486
+    padding-left: 10px;
487
+    padding-right: 10px;
488
+}
489
+
463 490
 #videoConnectionMessage {
464 491
     display: none;
465 492
     position: absolute;

+ 1
- 0
index.html View File

@@ -228,6 +228,7 @@
228 228
                     <img id="dominantSpeakerAvatar" src=""/>
229 229
                     <canvas id="dominantSpeakerAudioLevel"></canvas>
230 230
                 </div>
231
+                <span id="remoteConnectionMessage"></span>
231 232
                 <div id="largeVideoWrapper">
232 233
                     <video id="largeVideo" muted="true" autoplay></video>
233 234
                 </div>

+ 2
- 1
lang/main.json View File

@@ -324,7 +324,8 @@
324 324
         "ATTACHED": "Attached",
325 325
         "FETCH_SESSION_ID": "Obtaining session-id...",
326 326
         "GOT_SESSION_ID": "Obtaining session-id... Done",
327
-        "GET_SESSION_ID_ERROR": "Get session-id error: "
327
+        "GET_SESSION_ID_ERROR": "Get session-id error: ",
328
+        "USER_CONNECTION_INTERRUPTED": "__displayName__ is having connectivity issues..."
328 329
     },
329 330
     "recording":
330 331
     {

+ 115
- 8
modules/UI/videolayout/LargeVideoManager.js View File

@@ -121,7 +121,8 @@ export default class LargeVideoManager {
121 121
 
122 122
         // Include hide()/fadeOut only if we're switching between users
123 123
         let preUpdate;
124
-        if (this.newStreamData.id != this.id) {
124
+        let isUserSwitch = this.newStreamData.id != this.id;
125
+        if (isUserSwitch) {
125 126
             preUpdate = container.hide();
126 127
         } else {
127 128
             preUpdate = Promise.resolve();
@@ -146,25 +147,46 @@ export default class LargeVideoManager {
146 147
             // If we the continer is VIDEO_CONTAINER_TYPE, we need to check
147 148
             // its stream whether exist and is muted to set isVideoMuted
148 149
             // in rest of the cases it is false
149
-            let isVideoMuted = false;
150
+            let showAvatar = false;
150 151
             if (videoType == VIDEO_CONTAINER_TYPE)
151
-                isVideoMuted = stream ? stream.isMuted() : true;
152
-
153
-            // show the avatar on large if needed
154
-            container.showAvatar(isVideoMuted);
152
+                showAvatar = stream ? stream.isMuted() : true;
153
+
154
+            // If the user's connection is disrupted then the avatar will be
155
+            // displayed in case we have no video image cached. That is if
156
+            // there was a user switch(image is lost on stream detach) or if
157
+            // the video was not rendered, before the connection has failed.
158
+            let isHavingConnectivityIssues
159
+                = APP.conference.isParticipantConnectionActive(id) === false;
160
+            if (isHavingConnectivityIssues
161
+                    && (isUserSwitch | !container.wasVideoRendered)) {
162
+                showAvatar = true;
163
+            }
155 164
 
156 165
             let promise;
157 166
 
158 167
             // do not show stream if video is muted
159 168
             // but we still should show watermark
160
-            if (isVideoMuted) {
169
+            if (showAvatar) {
161 170
                 this.showWatermark(true);
162
-                // If the avatar is to be displayed the video should be hidden
171
+                // If the intention of this switch is to show the avatar
172
+                // we need to make sure that the video is hidden
163 173
                 promise = container.hide();
164 174
             } else {
165 175
                 promise = container.show();
166 176
             }
167 177
 
178
+            // show the avatar on large if needed
179
+            container.showAvatar(showAvatar);
180
+
181
+            // Make sure no notification about remote failure is shown as
182
+            // it's UI conflicts with the one for local connection interrupted.
183
+            if (APP.conference.isConnectionInterrupted()) {
184
+                this.updateParticipantConnStatusIndication(id, true);
185
+            } else {
186
+                this.updateParticipantConnStatusIndication(
187
+                    id, !isHavingConnectivityIssues);
188
+            }
189
+
168 190
             // resolve updateLargeVideo promise after everything is done
169 191
             promise.then(resolve);
170 192
 
@@ -177,6 +199,38 @@ export default class LargeVideoManager {
177 199
         });
178 200
     }
179 201
 
202
+    /**
203
+     * Shows/hides notification about participant's connectivity issues to be
204
+     * shown on the large video area.
205
+     *
206
+     * @param {string} id the id of remote participant(MUC nickname)
207
+     * @param {boolean} isConnected true if the connection is active or false
208
+     * when the user is having connectivity issues.
209
+     *
210
+     * @private
211
+     */
212
+    updateParticipantConnStatusIndication (id, isConnected) {
213
+
214
+        // Apply grey filter on the large video
215
+        this.videoContainer.showRemoteConnectionProblemIndicator(!isConnected);
216
+
217
+        if (isConnected) {
218
+            // Hide the message
219
+            this.showRemoteConnectionMessage(false);
220
+        } else {
221
+            // Get user's display name
222
+            let displayName
223
+                = APP.conference.getParticipantDisplayName(id);
224
+            this._setRemoteConnectionMessage(
225
+                "connection.USER_CONNECTION_INTERRUPTED",
226
+                { displayName: displayName });
227
+
228
+            // Show it now only if the VideoContainer is on top
229
+            this.showRemoteConnectionMessage(
230
+                this.state === VIDEO_CONTAINER_TYPE);
231
+        }
232
+    }
233
+
180 234
     /**
181 235
      * Update large video.
182 236
      * Switches to large video even if previously other container was visible.
@@ -274,11 +328,59 @@ export default class LargeVideoManager {
274 328
 
275 329
         if (show) {
276 330
             $('#videoConnectionMessage').css({display: "block"});
331
+            // Avatar message conflicts with 'videoConnectionMessage',
332
+            // so it must be hidden
333
+            this.showRemoteConnectionMessage(false);
277 334
         } else {
278 335
             $('#videoConnectionMessage').css({display: "none"});
279 336
         }
280 337
     }
281 338
 
339
+    /**
340
+     * Shows hides the "avatar" message which is to be displayed either in
341
+     * the middle of the screen or below the avatar image.
342
+     *
343
+     * @param {null|boolean} show (optional) <tt>true</tt> to show the avatar
344
+     * message or <tt>false</tt> to hide it. If not provided then the connection
345
+     * status of the user currently on the large video will be obtained form
346
+     * "APP.conference" and the message will be displayed if the user's
347
+     * connection is interrupted.
348
+     */
349
+    showRemoteConnectionMessage (show) {
350
+        if (typeof show !== 'boolean') {
351
+            show = APP.conference.isParticipantConnectionActive(this.id);
352
+        }
353
+
354
+        if (show) {
355
+            $('#remoteConnectionMessage').css({display: "block"});
356
+            // 'videoConnectionMessage' message conflicts with 'avatarMessage',
357
+            // so it must be hidden
358
+            this.showVideoConnectionMessage(false);
359
+        } else {
360
+            $('#remoteConnectionMessage').hide();
361
+        }
362
+    }
363
+
364
+    /**
365
+     * Updates the text which describes that the remote user is having
366
+     * connectivity issues.
367
+     *
368
+     * @param {string} msgKey the translation key which will be used to get
369
+     * the message text.
370
+     * @param {object} msgOptions translation options object.
371
+     *
372
+     * @private
373
+     */
374
+    _setRemoteConnectionMessage (msgKey, msgOptions) {
375
+        if (msgKey) {
376
+            let text = APP.translation.translateString(msgKey, msgOptions);
377
+            $('#remoteConnectionMessage')
378
+                .attr("data-i18n", msgKey).text(text);
379
+        }
380
+
381
+        this.videoContainer.positionRemoteConnectionMessage();
382
+    }
383
+
282 384
     /**
283 385
      * Updated the text which is to be shown on the top of large video.
284 386
      *
@@ -353,6 +455,7 @@ export default class LargeVideoManager {
353 455
         if (this.state === VIDEO_CONTAINER_TYPE) {
354 456
             this.showWatermark(false);
355 457
             this.showVideoConnectionMessage(false);
458
+            this.showRemoteConnectionMessage(false);
356 459
         }
357 460
         oldContainer.hide();
358 461
 
@@ -366,6 +469,10 @@ export default class LargeVideoManager {
366 469
                 // the container would be taking care of it by itself, but that
367 470
                 // is a bigger refactoring
368 471
                 this.showWatermark(true);
472
+                // "avatar" and "video connection" can not be displayed both
473
+                // at the same time, but the latter is of higher priority and it
474
+                // will hide the avatar one if will be displayed.
475
+                this.showRemoteConnectionMessage(/* fet the current state */);
369 476
                 this.showVideoConnectionMessage(/* fetch the current state */);
370 477
             }
371 478
         });

+ 50
- 0
modules/UI/videolayout/VideoContainer.js View File

@@ -173,8 +173,19 @@ export class VideoContainer extends LargeContainer {
173 173
 
174 174
         this.isVisible = false;
175 175
 
176
+        /**
177
+         * Flag indicates whether or not the avatar is currently displayed.
178
+         * @type {boolean}
179
+         */
180
+        this.avatarDisplayed = false;
176 181
         this.$avatar = $('#dominantSpeaker');
177 182
 
183
+        /**
184
+         * A jQuery selector of the remote connection message.
185
+         * @type {jQuery|HTMLElement}
186
+         */
187
+        this.$remoteConnectionMessage = $('#remoteConnectionMessage');
188
+
178 189
         /**
179 190
          * Indicates whether or not the video stream attached to the video
180 191
          * element has started(which means that there is any image rendered
@@ -266,6 +277,30 @@ export class VideoContainer extends LargeContainer {
266 277
         }
267 278
     }
268 279
 
280
+    /**
281
+     * Update position of the remote connection message which describes that
282
+     * the remote user is having connectivity issues.
283
+     */
284
+    positionRemoteConnectionMessage () {
285
+
286
+        if (this.avatarDisplayed) {
287
+            let $avatarImage = $("#dominantSpeakerAvatar");
288
+            this.$remoteConnectionMessage.css(
289
+                'top',
290
+                $avatarImage.offset().top + $avatarImage.height() + 10);
291
+        } else {
292
+            let height = this.$remoteConnectionMessage.height();
293
+            let parentHeight = this.$remoteConnectionMessage.parent().height();
294
+            this.$remoteConnectionMessage.css(
295
+                'top', (parentHeight/2) - (height/2));
296
+        }
297
+
298
+        let width = this.$remoteConnectionMessage.width();
299
+        let parentWidth = this.$remoteConnectionMessage.parent().width();
300
+        this.$remoteConnectionMessage.css(
301
+            'left', ((parentWidth/2) - (width/2)));
302
+    }
303
+
269 304
     resize (containerWidth, containerHeight, animate = false) {
270 305
         let [width, height]
271 306
             = this.getVideoSize(containerWidth, containerHeight);
@@ -278,6 +313,8 @@ export class VideoContainer extends LargeContainer {
278 313
 
279 314
         this.$avatar.css('top', top);
280 315
 
316
+        this.positionRemoteConnectionMessage();
317
+
281 318
         this.$wrapper.animate({
282 319
             width: width,
283 320
             height: height,
@@ -362,10 +399,23 @@ export class VideoContainer extends LargeContainer {
362 399
             (show) ? interfaceConfig.DEFAULT_BACKGROUND : "#000");
363 400
 
364 401
         this.$avatar.css("visibility", show ? "visible" : "hidden");
402
+        this.avatarDisplayed = show;
365 403
 
366 404
         this.emitter.emit(UIEvents.LARGE_VIDEO_AVATAR_DISPLAYED, show);
367 405
     }
368 406
 
407
+    /**
408
+     * Indicates that the remote user who is currently displayed by this video
409
+     * container is having connectivity issues.
410
+     *
411
+     * @param {boolean} show <tt>true</tt> to show or <tt>false</tt> to hide
412
+     * the indication.
413
+     */
414
+    showRemoteConnectionProblemIndicator (show) {
415
+        this.$video.toggleClass("remoteVideoProblemFilter", show);
416
+        this.$avatar.toggleClass("remoteVideoProblemFilter", show);
417
+    }
418
+
369 419
     // We are doing fadeOut/fadeIn animations on parent div which wraps
370 420
     // largeVideo, because when Temasys plugin is in use it replaces
371 421
     // <video> elements with plugin <object> tag. In Safari jQuery is

+ 6
- 0
modules/UI/videolayout/VideoLayout.js View File

@@ -650,6 +650,12 @@ var VideoLayout = {
650 650
      * the user is having connectivity issues.
651 651
      */
652 652
     onParticipantConnectionStatusChanged (id, isActive) {
653
+        // Show/hide warning on the large video
654
+        if (this.isCurrentlyOnLarge(id)) {
655
+            if (largeVideo) {
656
+                largeVideo.updateParticipantConnStatusIndication(id, isActive);
657
+            }
658
+        }
653 659
         // Show/hide warning on the thumbnail
654 660
         let remoteVideo = remoteVideos[id];
655 661
         if (remoteVideo) {

Loading…
Cancel
Save