浏览代码

Merge pull request #1071 from jitsi/ongoing-work-video-thumbnails

Ongoing work video thumbnails
j8
Paweł Domas 8 年前
父节点
当前提交
2fe69d409b

+ 0
- 4
conference.js 查看文件

1279
             APP.UI.updateRecordingState(status);
1279
             APP.UI.updateRecordingState(status);
1280
         });
1280
         });
1281
 
1281
 
1282
-        room.on(ConferenceEvents.USER_STATUS_CHANGED, function (id, status) {
1283
-            APP.UI.updateUserStatus(id, status);
1284
-        });
1285
-
1286
         room.on(ConferenceEvents.KICKED, () => {
1282
         room.on(ConferenceEvents.KICKED, () => {
1287
             APP.UI.hideStats();
1283
             APP.UI.hideStats();
1288
             APP.UI.notifyKicked();
1284
             APP.UI.notifyKicked();

+ 9
- 0
css/_mixins.scss 查看文件

50
   border-radius: 50%;
50
   border-radius: 50%;
51
 }
51
 }
52
 
52
 
53
+/**
54
+* Absolute position the element at the top left corner
55
+**/
56
+@mixin topLeft() {
57
+    position: absolute;
58
+    top: 0;
59
+    left: 0;
60
+}
61
+
53
 @mixin absoluteAligning($sizeX, $sizeY) {
62
 @mixin absoluteAligning($sizeX, $sizeY) {
54
   top: 50%;
63
   top: 50%;
55
   left: 50%;
64
   left: 50%;

+ 3
- 2
css/_variables.scss 查看文件

11
 $defaultToolbarSize: 50px;
11
 $defaultToolbarSize: 50px;
12
 
12
 
13
 // Video layout.
13
 // Video layout.
14
-$thumbnailIndicatorSize: 23px;
14
+$thumbnailToolbarHeight: 22px;
15
 $thumbnailIndicatorBorder: 0px;
15
 $thumbnailIndicatorBorder: 0px;
16
+$thumbnailIndicatorSize: $thumbnailToolbarHeight;
16
 $thumbnailVideoMargin: 2px;
17
 $thumbnailVideoMargin: 2px;
17
-$thumbnailToolbarHeight: 25px;
18
 
18
 
19
 /**
19
 /**
20
  * Color variables.
20
  * Color variables.
46
 $dominantSpeakerBg: #165ecc;
46
 $dominantSpeakerBg: #165ecc;
47
 $raiseHandBg: #D6D61E;
47
 $raiseHandBg: #D6D61E;
48
 $audioLevelBg: #44A5FF;
48
 $audioLevelBg: #44A5FF;
49
+$connectionIndicatorBg: #165ecc;
49
 $audioLevelShadow: rgba(9, 36, 77, 0.9);
50
 $audioLevelShadow: rgba(9, 36, 77, 0.9);
50
 $videoStateIndicatorColor: $defaultColor;
51
 $videoStateIndicatorColor: $defaultColor;
51
 $videoStateIndicatorBackground: $toolbarBackground;
52
 $videoStateIndicatorBackground: $toolbarBackground;

+ 107
- 88
css/_videolayout_default.scss 查看文件

58
 /**
58
 /**
59
  * The toolbar of the video thumbnail.
59
  * The toolbar of the video thumbnail.
60
  */
60
  */
61
-.videocontainer__toolbar {
61
+.videocontainer__toolbar,
62
+.videocontainer__toptoolbar {
62
     position: absolute;
63
     position: absolute;
63
-    bottom: 0;
64
     left: 0;
64
     left: 0;
65
-    z-index: 1;
65
+    z-index: 3;
66
     width: 100%;
66
     width: 100%;
67
     box-sizing: border-box; // Includes the padding in the 100% width.
67
     box-sizing: border-box; // Includes the padding in the 100% width.
68
-    height: $thumbnailToolbarHeight;
69
-    max-height: 100%;
70
-    background-color: rgba(0, 0, 0, 0.5);
68
+}
69
+
70
+.videocontainer__toolbar {
71
+    bottom: 0;
71
     padding: 0 5px 0 5px;
72
     padding: 0 5px 0 5px;
73
+    height: $thumbnailToolbarHeight;
74
+}
75
+
76
+.videocontainer__toptoolbar {
77
+    $toolbarPadding: 5px;
78
+    top: 0;
79
+    padding: $toolbarPadding;
80
+    padding-bottom: 0;
81
+    height: $thumbnailIndicatorSize + $toolbarPadding;
82
+}
83
+
84
+.videocontainer__hoverOverlay {
85
+    position: relative;
86
+    width: 100%;
87
+    height: 100%;
88
+    visibility: hidden;
89
+    background: rgba(0,0,0,.6);
90
+    z-index: 2;
72
 }
91
 }
73
 
92
 
74
 #remoteVideos .videocontainer.videoContainerFocused,
93
 #remoteVideos .videocontainer.videoContainerFocused,
176
 .videocontainer .editdisplayname {
195
 .videocontainer .editdisplayname {
177
     display: inline-block;
196
     display: inline-block;
178
     position: absolute;
197
     position: absolute;
179
-    left: 30%;
180
-    width: 40%;
198
+    left: 10%;
199
+    width: 80%;
200
+    top: 50%;
201
+    @include transform(translateY(-40%));
181
     color: $participantNameColor;
202
     color: $participantNameColor;
182
     text-align: center;
203
     text-align: center;
183
     text-overflow: ellipsis;
204
     text-overflow: ellipsis;
200
     padding: 0;
221
     padding: 0;
201
 }
222
 }
202
 
223
 
203
-.videocontainer>span.status {
204
-    display: inline-block;
205
-    position: absolute;
206
-    color: #FFFFFF;
207
-    background: rgba(0,0,0,.7);
208
-    text-align: center;
209
-    text-overflow: ellipsis;
210
-    width: 70%;
211
-    height: 15%;
212
-    left: 15%;
213
-    bottom: 2%;
214
-    padding: 5px;
215
-    font-size: 10pt;
216
-    overflow: hidden;
217
-    white-space: nowrap;
218
-    z-index: 2;
219
-    border-radius:3px;
220
-}
221
-
222
-.connectionindicator
223
-{
224
-    display: inline-block;
225
-    position: absolute;
226
-    top: 7px;
227
-    right: 0;
228
-    padding: 0px 5px;
229
-    z-index: 3;
230
-    width: 18px;
231
-    height: 13px;
232
-}
233
-
234
-.connection.connection_empty
235
-{
236
-    color: #8B8B8B;/*#FFFFFF*/
237
-    overflow: hidden;
238
-}
239
-
240
-.connection.connection_lost
241
-{
242
-    color: #8B8B8B;
243
-    overflow: visible;
244
-}
245
-
246
-.connection.connection_full
247
-{
248
-    color: #FFFFFF;/*#15A1ED*/
249
-    overflow: hidden;
250
-}
251
-
252
-.connection
253
-{
254
-    position: absolute;
255
-    left: 0px;
256
-    top: 0px;
257
-    font-size: 8pt;
258
-    border: 0px;
259
-    width: 18px;
260
-    height: 13px;
261
-}
262
-
263
-#localVideoContainer>span.status:hover,
264
 #localVideoContainer .displayname:hover {
224
 #localVideoContainer .displayname:hover {
265
     cursor: text;
225
     cursor: text;
266
 }
226
 }
267
 
227
 
268
-.videocontainer>span.status,
269
 .videocontainer .displayname {
228
 .videocontainer .displayname {
270
     pointer-events: none;
229
     pointer-events: none;
271
 }
230
 }
278
     pointer-events: auto !important;
237
     pointer-events: auto !important;
279
 }
238
 }
280
 
239
 
281
-.videocontainer>a.status,
282
 .videocontainer>a.displayname {
240
 .videocontainer>a.displayname {
283
     display: inline-block;
241
     display: inline-block;
284
     position: absolute;
242
     position: absolute;
323
   margin: 0px 0px 0px 5px;
281
   margin: 0px 0px 0px 5px;
324
 }
282
 }
325
 
283
 
326
-.videocontainer>span.indicator {
327
-    position: absolute;
328
-    top: 0px;
329
-    left: 0px;
284
+#raisehandindicator {
285
+  background: $raiseHandBg;
286
+}
287
+
288
+#connectionindicator {
289
+  background: $connectionIndicatorBg;
290
+}
291
+
292
+.videocontainer__toptoolbar span.indicator {
293
+    position: relative;
294
+    font-size: 8pt;
295
+    text-align: center;
296
+    line-height: $thumbnailToolbarHeight;
297
+    display: none;
298
+    padding: 0;
299
+    margin-right: 5px;
300
+    float: left;
330
     @include circle($thumbnailIndicatorSize);
301
     @include circle($thumbnailIndicatorSize);
331
     box-sizing: border-box;
302
     box-sizing: border-box;
332
-    line-height: $thumbnailIndicatorSize - 2*$thumbnailIndicatorBorder;
333
     z-index: 3;
303
     z-index: 3;
334
-    text-align: center;
335
     background: $dominantSpeakerBg;
304
     background: $dominantSpeakerBg;
336
-    margin: 7px;
337
-    display: inline-block;
338
     color: $thumbnailPictogramColor;
305
     color: $thumbnailPictogramColor;
339
-    font-size: 8pt;
340
     border: $thumbnailIndicatorBorder solid $thumbnailPictogramColor;
306
     border: $thumbnailIndicatorBorder solid $thumbnailPictogramColor;
307
+
308
+    .indicatoricon {
309
+        position: absolute;
310
+        top: 50%;
311
+        left: 0;
312
+        @include transform(translateY(-50%));
313
+        width: $thumbnailIndicatorSize - 2 * $thumbnailIndicatorBorder;
314
+        height: $thumbnailIndicatorSize - 2 * $thumbnailIndicatorBorder;
315
+        line-height: $thumbnailIndicatorSize - 2 * $thumbnailIndicatorBorder;
316
+    }
317
+
318
+    .connection {
319
+        position: relative;
320
+        margin: 0 auto;
321
+        width: 12px;
322
+        height: 8px;
323
+
324
+        &_empty
325
+        {
326
+            @include topLeft();
327
+            max-width: 12px;
328
+            width: 12px;
329
+            color: #8B8B8B;/*#FFFFFF*/
330
+            overflow: hidden;
331
+        }
332
+
333
+        &_lost
334
+        {
335
+            @include topLeft();
336
+            max-width: 12px;
337
+            width: 12px;
338
+            color: #8B8B8B;
339
+            overflow: visible;
340
+        }
341
+
342
+        &_full
343
+        {
344
+            @include topLeft();
345
+            max-width: 12px;
346
+            width: 12px;
347
+            color: #FFFFFF;/*#15A1ED*/
348
+            overflow: hidden;
349
+        }
350
+    }
351
+
352
+    .icon-connection,
353
+    .icon-connection-lost {
354
+        font-size: 6pt;
355
+    }
341
 }
356
 }
342
 
357
 
343
-.videocontainer>#raisehandindicator {
344
-    background: $raiseHandBg;
358
+.remotevideomenu
359
+{
360
+    display: inline-block;
361
+    position: absolute;
362
+    top: 0px;
363
+    right: 0;
364
+    margin: 7px;
365
+    z-index: 3;
366
+    width: 18px;
367
+    height: 13px;
368
+    color: #FFF;
369
+    font-size: 8pt;
345
 }
370
 }
346
 
371
 
347
 /**
372
 /**
384
     }
409
     }
385
 }
410
 }
386
 
411
 
387
-#indicatoricon {
388
-    width: $thumbnailIndicatorSize - 2*$thumbnailIndicatorBorder;
389
-    height: $thumbnailIndicatorSize - 2*$thumbnailIndicatorBorder;
390
-    line-height: $thumbnailIndicatorSize - 2*$thumbnailIndicatorBorder;
391
-}
392
-
393
 #reloadPresentation {
412
 #reloadPresentation {
394
     display: none;
413
     display: none;
395
     position: absolute;
414
     position: absolute;

+ 2
- 0
index.html 查看文件

254
                     </span>
254
                     </span>
255
                     <audio id="localAudio" autoplay muted></audio>
255
                     <audio id="localAudio" autoplay muted></audio>
256
                     <div class="videocontainer__toolbar"></div>
256
                     <div class="videocontainer__toolbar"></div>
257
+                    <div class="videocontainer__toptoolbar"></div>
258
+                    <div class="videocontainer__hoverOverlay"></div>
257
                 </span>
259
                 </span>
258
                 <audio id="userJoined" src="sounds/joined.wav" preload="auto"></audio>
260
                 <audio id="userJoined" src="sounds/joined.wav" preload="auto"></audio>
259
                 <audio id="userLeft" src="sounds/left.wav" preload="auto"></audio>
261
                 <audio id="userLeft" src="sounds/left.wav" preload="auto"></audio>

+ 0
- 4
modules/UI/UI.js 查看文件

609
     VideoLayout.removeParticipantContainer(id);
609
     VideoLayout.removeParticipantContainer(id);
610
 };
610
 };
611
 
611
 
612
-UI.updateUserStatus = function (id, status) {
613
-    VideoLayout.setPresenceStatus(id, status);
614
-};
615
-
616
 /**
612
 /**
617
  * Update videotype for specified user.
613
  * Update videotype for specified user.
618
  * @param {string} id user id
614
  * @param {string} id user id

+ 1
- 1
modules/UI/shared_video/SharedVideo.js 查看文件

625
     this.videoSpanId = "sharedVideoContainer";
625
     this.videoSpanId = "sharedVideoContainer";
626
     this.container = this.createContainer(this.videoSpanId);
626
     this.container = this.createContainer(this.videoSpanId);
627
     this.container.onclick = this.videoClick.bind(this);
627
     this.container.onclick = this.videoClick.bind(this);
628
-
628
+    this.bindHoverHandler();
629
     SmallVideo.call(this, VideoLayout);
629
     SmallVideo.call(this, VideoLayout);
630
     this.isVideoMuted = true;
630
     this.isVideoMuted = true;
631
 }
631
 }

+ 13
- 0
modules/UI/util/JitsiPopover.js 查看文件

98
         var self = this;
98
         var self = this;
99
         $(".jitsipopover").on("mouseenter", function () {
99
         $(".jitsipopover").on("mouseenter", function () {
100
             self.popoverIsHovered = true;
100
             self.popoverIsHovered = true;
101
+            if(typeof self.onHoverPopover === "function") {
102
+                self.onHoverPopover(self.popoverIsHovered);
103
+            }
101
         }).on("mouseleave", function () {
104
         }).on("mouseleave", function () {
102
             self.popoverIsHovered = false;
105
             self.popoverIsHovered = false;
103
             self.hide();
106
             self.hide();
107
+            if(typeof self.onHoverPopover === "function") {
108
+                self.onHoverPopover(self.popoverIsHovered);
109
+            }
104
         });
110
         });
105
 
111
 
106
         this.refreshPosition();
112
         this.refreshPosition();
107
     };
113
     };
108
 
114
 
115
+    /**
116
+     * Adds a hover listener to the popover.
117
+     */
118
+    JitsiPopover.prototype.addOnHoverPopover = function (listener) {
119
+        this.onHoverPopover = listener;
120
+    };
121
+
109
     /**
122
     /**
110
      * Refreshes the position of the popover.
123
      * Refreshes the position of the popover.
111
      */
124
      */

+ 56
- 1
modules/UI/util/UIUtil.js 查看文件

136
             element.setAttribute('data-i18n', '[content]' + key);
136
             element.setAttribute('data-i18n', '[content]' + key);
137
 
137
 
138
             APP.translation.translateElement($(element));
138
             APP.translation.translateElement($(element));
139
-        }      
139
+        }
140
     },
140
     },
141
 
141
 
142
     /**
142
     /**
237
         $("#"+id).addClass("hide");
237
         $("#"+id).addClass("hide");
238
     },
238
     },
239
 
239
 
240
+    /**
241
+     * Shows / hides the element with the given jQuery selector.
242
+     *
243
+     * @param {jQuery} selector the jQuery selector of the element to show/hide
244
+     * @param {boolean} isVisible
245
+     */
246
+    setVisibility(selector, isVisible) {
247
+        if (selector && selector.length > 0) {
248
+            selector.css("visibility", isVisible ? "visible" : "hidden");
249
+        }
250
+    },
251
+
240
     hideDisabledButtons: function (mappings) {
252
     hideDisabledButtons: function (mappings) {
241
         var selector = Object.keys(mappings)
253
         var selector = Object.keys(mappings)
242
           .map(function (buttonName) {
254
           .map(function (buttonName) {
376
                 "cursor": "default"
388
                 "cursor": "default"
377
             });
389
             });
378
         }
390
         }
391
+    },
392
+
393
+    /**
394
+     * Gets an "indicator" span for a video thumbnail.
395
+     * If element doesn't exist then creates it and appends
396
+     * video span container.
397
+     *
398
+     * @param {object} opts
399
+     * @param opts.indicatorId {String} - identificator of indicator
400
+     * @param opts.videoSpanId {String} - identificator of video span
401
+     * @param opts.content {String} HTML content of indicator
402
+     * @param opts.tooltip {String} - tooltip key for translation
403
+     *
404
+     * @returns {HTMLSpanElement} indicatorSpan
405
+     */
406
+    getVideoThumbnailIndicatorSpan(opts = {}) {
407
+        let indicatorId = opts.indicatorId;
408
+        let videoSpanId = opts.videoSpanId;
409
+        let indicators = $(`#${videoSpanId} [id="${indicatorId}"]`);
410
+        let indicatorSpan;
411
+
412
+        if (indicators.length <= 0) {
413
+            indicatorSpan = document.createElement('span');
414
+            indicatorSpan.className = 'indicator';
415
+            indicatorSpan.id = indicatorId;
416
+
417
+            if(opts.content) {
418
+                indicatorSpan.innerHTML = opts.content;
419
+            }
420
+
421
+            if (opts.tooltip) {
422
+                this.setTooltip(indicatorSpan, opts.tooltip, "top");
423
+                APP.translation.translateElement($(indicatorSpan));
424
+            }
425
+
426
+            document.getElementById(videoSpanId)
427
+                .querySelector('.videocontainer__toptoolbar')
428
+                .appendChild(indicatorSpan);
429
+        } else {
430
+            indicatorSpan = indicators[0];
431
+        }
432
+
433
+        return indicatorSpan;
379
     }
434
     }
380
 };
435
 };
381
 
436
 

+ 45
- 27
modules/UI/videolayout/ConnectionIndicator.js 查看文件

2
 /* jshint -W101 */
2
 /* jshint -W101 */
3
 import JitsiPopover from "../util/JitsiPopover";
3
 import JitsiPopover from "../util/JitsiPopover";
4
 import VideoLayout from "./VideoLayout";
4
 import VideoLayout from "./VideoLayout";
5
+import UIUtil from "../util/UIUtil";
5
 
6
 
6
 /**
7
 /**
7
  * Constructs new connection indicator.
8
  * Constructs new connection indicator.
8
  * @param videoContainer the video container associated with the indicator.
9
  * @param videoContainer the video container associated with the indicator.
10
+ * @param videoId the identifier of the video
9
  * @constructor
11
  * @constructor
10
  */
12
  */
11
-function ConnectionIndicator(videoContainer, id) {
13
+function ConnectionIndicator(videoContainer, videoId) {
12
     this.videoContainer = videoContainer;
14
     this.videoContainer = videoContainer;
13
     this.bandwidth = null;
15
     this.bandwidth = null;
14
     this.packetLoss = null;
16
     this.packetLoss = null;
18
     this.isResolutionHD = null;
20
     this.isResolutionHD = null;
19
     this.transport = [];
21
     this.transport = [];
20
     this.popover = null;
22
     this.popover = null;
21
-    this.id = id;
23
+    this.id = videoId;
22
     this.create();
24
     this.create();
23
 }
25
 }
24
 
26
 
32
  *         0: string}}
34
  *         0: string}}
33
  */
35
  */
34
 ConnectionIndicator.connectionQualityValues = {
36
 ConnectionIndicator.connectionQualityValues = {
35
-    98: "18px", //full
36
-    81: "15px",//4 bars
37
-    64: "11px",//3 bars
38
-    47: "7px",//2 bars
39
-    30: "3px",//1 bar
40
-    0: "0px"//empty
37
+    98: "100%", //full
38
+    81: "80%",//4 bars
39
+    64: "55%",//3 bars
40
+    47: "40%",//2 bars
41
+    30: "20%",//1 bar
42
+    0: "0"//empty
41
 };
43
 };
42
 
44
 
43
 ConnectionIndicator.getIP = function(value) {
45
 ConnectionIndicator.getIP = function(value) {
259
  * Creates the indicator
261
  * Creates the indicator
260
  */
262
  */
261
 ConnectionIndicator.prototype.create = function () {
263
 ConnectionIndicator.prototype.create = function () {
262
-    this.connectionIndicatorContainer = document.createElement("div");
263
-    this.connectionIndicatorContainer.className = "connectionindicator";
264
-    this.connectionIndicatorContainer.style.display = "none";
265
-    this.videoContainer.container.appendChild(
266
-        this.connectionIndicatorContainer);
267
-    this.popover = new JitsiPopover(
268
-        $("#" + this.videoContainer.videoSpanId + " > .connectionindicator"), {
269
-            content: "<div class=\"connection-info\" " +
270
-                        "data-i18n='connectionindicator.na'></div>",
271
-            skin: "black",
272
-            onBeforePosition: el => APP.translation.translateElement(el)
273
-        });
264
+    let indicatorId = 'connectionindicator';
265
+    let element = UIUtil.getVideoThumbnailIndicatorSpan({
266
+        videoSpanId: this.videoContainer.videoSpanId,
267
+        indicatorId
268
+    });
269
+    element.classList.add('show');
270
+    this.connectionIndicatorContainer = element;
271
+
272
+    let popoverContent = (
273
+        `<div class="connection-info" data-i18n="${indicatorId}.na"></div>`
274
+    );
275
+    this.popover = new JitsiPopover($(element), {
276
+        content: popoverContent,
277
+        skin: "black",
278
+        onBeforePosition: el => APP.translation.translateElement(el)
279
+    });
274
 
280
 
275
     // override popover show method to make sure we will update the content
281
     // override popover show method to make sure we will update the content
276
     // before showing the popover
282
     // before showing the popover
283
         origShowFunc.call(this.popover);
289
         origShowFunc.call(this.popover);
284
     }.bind(this);
290
     }.bind(this);
285
 
291
 
286
-    this.emptyIcon = this.connectionIndicatorContainer.appendChild(
287
-        createIcon(["connection", "connection_empty"], "icon-connection"));
288
-    this.fullIcon = this.connectionIndicatorContainer.appendChild(
289
-        createIcon(["connection", "connection_full"], "icon-connection"));
290
-    this.interruptedIndicator = this.connectionIndicatorContainer.appendChild(
291
-        createIcon(["connection", "connection_lost"],"icon-connection-lost"));
292
+    let connectionIconContainer = document.createElement('div');
293
+    connectionIconContainer.className = 'connection indicatoricon';
294
+
295
+
296
+    this.emptyIcon = connectionIconContainer.appendChild(
297
+        createIcon(["connection_empty"], "icon-connection"));
298
+    this.fullIcon = connectionIconContainer.appendChild(
299
+        createIcon(["connection_full"], "icon-connection"));
300
+    this.interruptedIndicator = connectionIconContainer.appendChild(
301
+        createIcon(["connection_lost"],"icon-connection-lost"));
302
+
292
     $(this.interruptedIndicator).hide();
303
     $(this.interruptedIndicator).hide();
304
+    this.connectionIndicatorContainer.appendChild(connectionIconContainer);
293
 };
305
 };
294
 
306
 
295
 /**
307
 /**
320
         $(this.interruptedIndicator).show();
332
         $(this.interruptedIndicator).show();
321
         $(this.emptyIcon).hide();
333
         $(this.emptyIcon).hide();
322
         $(this.fullIcon).hide();
334
         $(this.fullIcon).hide();
323
-        this.updateConnectionQuality(0 /* zero bars */);
324
     }
335
     }
325
 };
336
 };
326
 
337
 
427
     }
438
     }
428
 };
439
 };
429
 
440
 
441
+/**
442
+ * Adds a hover listener to the popover.
443
+ */
444
+ConnectionIndicator.prototype.addPopoverHoverListener = function (listener) {
445
+    this.popover.addOnHoverPopover(listener);
446
+};
447
+
430
 export default ConnectionIndicator;
448
 export default ConnectionIndicator;

+ 4
- 5
modules/UI/videolayout/LocalVideo.js 查看文件

11
     this.videoSpanId = "localVideoContainer";
11
     this.videoSpanId = "localVideoContainer";
12
     this.container = $("#localVideoContainer").get(0);
12
     this.container = $("#localVideoContainer").get(0);
13
     this.localVideoId = null;
13
     this.localVideoId = null;
14
+    this.createConnectionIndicator();
15
+    this.bindHoverHandler();
14
     if(config.enableLocalVideoFlip)
16
     if(config.enableLocalVideoFlip)
15
         this._buildContextMenu();
17
         this._buildContextMenu();
16
     this.isLocal = true;
18
     this.isLocal = true;
27
     // Set default display name.
29
     // Set default display name.
28
     this.setDisplayName();
30
     this.setDisplayName();
29
 
31
 
30
-    this.createConnectionIndicator();
31
     this.addAudioLevelIndicator();
32
     this.addAudioLevelIndicator();
32
 }
33
 }
33
 
34
 
70
         nameSpan = document.createElement('span');
71
         nameSpan = document.createElement('span');
71
         nameSpan.className = 'displayname';
72
         nameSpan.className = 'displayname';
72
         document.getElementById(this.videoSpanId)
73
         document.getElementById(this.videoSpanId)
73
-            .querySelector('.videocontainer__toolbar')
74
             .appendChild(nameSpan);
74
             .appendChild(nameSpan);
75
 
75
 
76
 
76
 
104
         APP.translation.translateElement($(editableText));
104
         APP.translation.translateElement($(editableText));
105
 
105
 
106
         this.container
106
         this.container
107
-            .querySelector('.videocontainer__toolbar')
108
             .appendChild(editableText);
107
             .appendChild(editableText);
109
 
108
 
110
         var self = this;
109
         var self = this;
115
 
114
 
116
                 e.preventDefault();
115
                 e.preventDefault();
117
                 e.stopPropagation();
116
                 e.stopPropagation();
118
-                $localDisplayName.hide();
117
+                UIUtil.setVisibility($localDisplayName, false);
119
                 $editDisplayName.show();
118
                 $editDisplayName.show();
120
                 $editDisplayName.focus();
119
                 $editDisplayName.focus();
121
                 $editDisplayName.select();
120
                 $editDisplayName.select();
123
                 $editDisplayName.one("focusout", function () {
122
                 $editDisplayName.one("focusout", function () {
124
                     self.emitter.emit(UIEvents.NICKNAME_CHANGED, this.value);
123
                     self.emitter.emit(UIEvents.NICKNAME_CHANGED, this.value);
125
                     $editDisplayName.hide();
124
                     $editDisplayName.hide();
126
-                    $localDisplayName.show();
125
+                    UIUtil.setVisibility($localDisplayName, true);
127
                 });
126
                 });
128
 
127
 
129
                 $editDisplayName.on('keydown', function (e) {
128
                 $editDisplayName.on('keydown', function (e) {

+ 14
- 11
modules/UI/videolayout/RemoteVideo.js 查看文件

31
     this.addRemoteVideoContainer();
31
     this.addRemoteVideoContainer();
32
     this.connectionIndicator = new ConnectionIndicator(this, this.id);
32
     this.connectionIndicator = new ConnectionIndicator(this, this.id);
33
     this.setDisplayName();
33
     this.setDisplayName();
34
+    this.bindHoverHandler();
34
     this.flipX = false;
35
     this.flipX = false;
35
     this.isLocal = false;
36
     this.isLocal = false;
36
     /**
37
     /**
233
     RemoteVideo.prototype.addRemoteVideoMenu = function () {
234
     RemoteVideo.prototype.addRemoteVideoMenu = function () {
234
 
235
 
235
         var spanElement = document.createElement('span');
236
         var spanElement = document.createElement('span');
236
-        spanElement.className = 'remotevideomenu toolbar-icon right';
237
+        spanElement.className = 'remotevideomenu';
237
 
238
 
238
-        this.container
239
-            .querySelector('.videocontainer__toolbar')
240
-            .appendChild(spanElement);
239
+        this.container.appendChild(spanElement);
241
 
240
 
242
         var menuElement = document.createElement('i');
241
         var menuElement = document.createElement('i');
243
         menuElement.className = 'icon-menu-up';
242
         menuElement.className = 'icon-menu-up';
512
 
511
 
513
 /**
512
 /**
514
  * Sets the display name for the given video span id.
513
  * Sets the display name for the given video span id.
514
+ *
515
+ * @param displayName the display name to set
515
  */
516
  */
516
-RemoteVideo.prototype.setDisplayName = function(displayName, key) {
517
-
517
+RemoteVideo.prototype.setDisplayName = function(displayName) {
518
     if (!this.container) {
518
     if (!this.container) {
519
         console.warn( "Unable to set displayName - " + this.videoSpanId +
519
         console.warn( "Unable to set displayName - " + this.videoSpanId +
520
                 " does not exist");
520
                 " does not exist");
530
             if (displaynameSpan.text() !== displayName)
530
             if (displaynameSpan.text() !== displayName)
531
                 displaynameSpan.text(displayName);
531
                 displaynameSpan.text(displayName);
532
         }
532
         }
533
-        else if (key && key.length > 0) {
534
-            var nameHtml = APP.translation.generateTranslationHTML(key);
535
-            $('#' + this.videoSpanId + '_name').html(nameHtml);
536
-        }
537
         else
533
         else
538
             $('#' + this.videoSpanId + '_name').text(
534
             $('#' + this.videoSpanId + '_name').text(
539
                 interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME);
535
                 interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME);
541
         nameSpan = document.createElement('span');
537
         nameSpan = document.createElement('span');
542
         nameSpan.className = 'displayname';
538
         nameSpan.className = 'displayname';
543
         $('#' + this.videoSpanId)[0]
539
         $('#' + this.videoSpanId)[0]
544
-            .querySelector('.videocontainer__toolbar')
545
             .appendChild(nameSpan);
540
             .appendChild(nameSpan);
546
 
541
 
547
         if (displayName && displayName.length > 0) {
542
         if (displayName && displayName.length > 0) {
573
     container.id = spanId;
568
     container.id = spanId;
574
     container.className = 'videocontainer';
569
     container.className = 'videocontainer';
575
 
570
 
571
+    let indicatorBar = document.createElement('div');
572
+    indicatorBar.className = "videocontainer__toptoolbar";
573
+    container.appendChild(indicatorBar);
574
+
576
     let toolbar = document.createElement('div');
575
     let toolbar = document.createElement('div');
577
     toolbar.className = "videocontainer__toolbar";
576
     toolbar.className = "videocontainer__toolbar";
578
     container.appendChild(toolbar);
577
     container.appendChild(toolbar);
579
 
578
 
579
+    let overlay = document.createElement('div');
580
+    overlay.className = "videocontainer__hoverOverlay";
581
+    container.appendChild(overlay);
582
+
580
     var remotes = document.getElementById('remoteVideos');
583
     var remotes = document.getElementById('remoteVideos');
581
     return remotes.appendChild(container);
584
     return remotes.appendChild(container);
582
 };
585
 };

+ 115
- 97
modules/UI/videolayout/SmallVideo.js 查看文件

1
-/* global $, APP, JitsiMeetJS, interfaceConfig */
1
+/* global $, JitsiMeetJS, interfaceConfig */
2
 import Avatar from "../avatar/Avatar";
2
 import Avatar from "../avatar/Avatar";
3
 import UIUtil from "../util/UIUtil";
3
 import UIUtil from "../util/UIUtil";
4
 import UIEvents from "../../../service/UI/UIEvents";
4
 import UIEvents from "../../../service/UI/UIEvents";
21
 const DISPLAY_AVATAR = 1;
21
 const DISPLAY_AVATAR = 1;
22
 /**
22
 /**
23
  * Display mode constant used when neither video nor avatar is being displayed
23
  * Display mode constant used when neither video nor avatar is being displayed
24
- * on the small video.
24
+ * on the small video. And we just show the display name.
25
  * @type {number}
25
  * @type {number}
26
  * @constant
26
  * @constant
27
  */
27
  */
28
-const DISPLAY_BLACKNESS = 2;
28
+const DISPLAY_BLACKNESS_WITH_NAME = 2;
29
+
30
+/**
31
+ * Display mode constant used when video is displayed and display name
32
+ * at the same time.
33
+ * @type {number}
34
+ * @constant
35
+ */
36
+const DISPLAY_VIDEO_WITH_NAME = 3;
37
+
38
+/**
39
+ * Display mode constant used when neither video nor avatar is being displayed
40
+ * on the small video. And we just show the display name.
41
+ * @type {number}
42
+ * @constant
43
+ */
44
+const DISPLAY_AVATAR_WITH_NAME = 4;
29
 
45
 
30
 function SmallVideo(VideoLayout) {
46
 function SmallVideo(VideoLayout) {
31
     this.isAudioMuted = false;
47
     this.isAudioMuted = false;
34
     this.videoStream = null;
50
     this.videoStream = null;
35
     this.audioStream = null;
51
     this.audioStream = null;
36
     this.VideoLayout = VideoLayout;
52
     this.VideoLayout = VideoLayout;
37
-}
38
-
39
-function setVisibility(selector, show) {
40
-    if (selector && selector.length > 0) {
41
-        selector.css("visibility", show ? "visible" : "hidden");
42
-    }
53
+    this.videoIsHovered = false;
43
 }
54
 }
44
 
55
 
45
 /**
56
 /**
60
     return $('#' + this.videoSpanId).is(':visible');
71
     return $('#' + this.videoSpanId).is(':visible');
61
 };
72
 };
62
 
73
 
63
-SmallVideo.prototype.showDisplayName = function(isShow) {
64
-    var nameSpan = $('#' + this.videoSpanId + ' .displayname').get(0);
65
-    if (isShow) {
66
-        if (nameSpan && nameSpan.innerHTML && nameSpan.innerHTML.length)
67
-            nameSpan.setAttribute("style", "display:inline-block;");
68
-    }
69
-    else {
70
-        if (nameSpan)
71
-            nameSpan.setAttribute("style", "display:none;");
72
-    }
73
-};
74
-
75
 /**
74
 /**
76
  * Enables / disables the device availability icons for this small video.
75
  * Enables / disables the device availability icons for this small video.
77
  * @param {enable} set to {true} to enable and {false} to disable
76
  * @param {enable} set to {true} to enable and {false} to disable
132
     return this.videoType;
131
     return this.videoType;
133
 };
132
 };
134
 
133
 
135
-/**
136
- * Shows the presence status message for the given video.
137
- */
138
-SmallVideo.prototype.setPresenceStatus = function (statusMsg) {
139
-    if (!this.container) {
140
-        // No container
141
-        return;
142
-    }
143
-
144
-    var statusSpan = $('#' + this.videoSpanId + '>span.status');
145
-    if (!statusSpan.length) {
146
-        //Add status span
147
-        statusSpan = document.createElement('span');
148
-        statusSpan.className = 'status';
149
-        statusSpan.id = this.videoSpanId + '_status';
150
-        $('#' + this.videoSpanId)[0].appendChild(statusSpan);
151
-
152
-        statusSpan = $('#' + this.videoSpanId + '>span.status');
153
-    }
154
-
155
-    // Display status
156
-    if (statusMsg && statusMsg.length) {
157
-        $('#' + this.videoSpanId + '_status').text(statusMsg);
158
-        statusSpan.get(0).setAttribute("style", "display:inline-block;");
159
-    }
160
-    else {
161
-        // Hide
162
-        statusSpan.get(0).setAttribute("style", "display:none;");
163
-    }
164
-};
165
-
166
 /**
134
 /**
167
  * Creates an audio or video element for a particular MediaStream.
135
  * Creates an audio or video element for a particular MediaStream.
168
  */
136
  */
192
     return (isVideo ? 'remoteVideo_' : 'remoteAudio_') + stream.getId();
160
     return (isVideo ? 'remoteVideo_' : 'remoteAudio_') + stream.getId();
193
 };
161
 };
194
 
162
 
163
+/**
164
+ * Configures hoverIn/hoverOut handlers. Depends on connection indicator.
165
+ */
166
+SmallVideo.prototype.bindHoverHandler = function () {
167
+    // Add hover handler
168
+    $(this.container).hover(
169
+        () => {
170
+            this.videoIsHovered = true;
171
+            this.updateView();
172
+        },
173
+        () => {
174
+            this.videoIsHovered = false;
175
+            this.updateView();
176
+        }
177
+    );
178
+    if (this.connectionIndicator) {
179
+        this.connectionIndicator.addPopoverHoverListener(
180
+            () => {
181
+                this.updateView();
182
+            });
183
+    }
184
+};
185
+
195
 /**
186
 /**
196
  * Updates the data for the indicator
187
  * Updates the data for the indicator
197
  * @param id the id of the indicator
188
  * @param id the id of the indicator
395
     return $('#' + this.videoSpanId + ' .userAvatar');
386
     return $('#' + this.videoSpanId + ' .userAvatar');
396
 };
387
 };
397
 
388
 
389
+/**
390
+ * Returns the display name element, which appears on the video thumbnail.
391
+ *
392
+ * @return {jQuery} a jQuery selector pointing to the display name element of
393
+ * the video thumbnail
394
+ */
395
+SmallVideo.prototype.$displayName = function () {
396
+    return $('#' + this.videoSpanId + ' .displayname');
397
+};
398
+
398
 /**
399
 /**
399
  * Enables / disables the css responsible for focusing/pinning a video
400
  * Enables / disables the css responsible for focusing/pinning a video
400
  * thumbnail.
401
  * thumbnail.
445
  * Determines what should be display on the thumbnail.
446
  * Determines what should be display on the thumbnail.
446
  *
447
  *
447
  * @return {number} one of <tt>DISPLAY_VIDEO</tt>,<tt>DISPLAY_AVATAR</tt>
448
  * @return {number} one of <tt>DISPLAY_VIDEO</tt>,<tt>DISPLAY_AVATAR</tt>
448
- * or <tt>DISPLAY_BLACKNESS</tt>.
449
+ * or <tt>DISPLAY_BLACKNESS_WITH_NAME</tt>.
449
  */
450
  */
450
 SmallVideo.prototype.selectDisplayMode = function() {
451
 SmallVideo.prototype.selectDisplayMode = function() {
451
     // Display name is always and only displayed when user is on the stage
452
     // Display name is always and only displayed when user is on the stage
452
     if (this.isCurrentlyOnLargeVideo()) {
453
     if (this.isCurrentlyOnLargeVideo()) {
453
-        return DISPLAY_BLACKNESS;
454
+        return DISPLAY_BLACKNESS_WITH_NAME;
454
     } else if (this.isVideoPlayable() && this.selectVideoElement().length) {
455
     } else if (this.isVideoPlayable() && this.selectVideoElement().length) {
455
-        return DISPLAY_VIDEO;
456
+        // check hovering and change state to video with name
457
+        return this._isHovered() ?
458
+            DISPLAY_VIDEO_WITH_NAME : DISPLAY_VIDEO;
456
     } else {
459
     } else {
457
-        return DISPLAY_AVATAR;
460
+        // check hovering and change state to avatar with name
461
+        return this._isHovered() ?
462
+            DISPLAY_AVATAR_WITH_NAME : DISPLAY_AVATAR;
458
     }
463
     }
459
 };
464
 };
460
 
465
 
466
+/**
467
+ * Checks whether current video is considered hovered. Currently it is hovered
468
+ * if the mouse is over the video, or if the connection
469
+ * indicator is shown(hovered).
470
+ * @private
471
+ */
472
+SmallVideo.prototype._isHovered = function () {
473
+    return this.videoIsHovered
474
+        || (this.connectionIndicator
475
+            && this.connectionIndicator.popover.popoverIsHovered);
476
+};
477
+
461
 /**
478
 /**
462
  * Hides or shows the user's avatar.
479
  * Hides or shows the user's avatar.
463
  * This update assumes that large video had been updated and we will
480
  * This update assumes that large video had been updated and we will
479
 
496
 
480
     // Determine whether video, avatar or blackness should be displayed
497
     // Determine whether video, avatar or blackness should be displayed
481
     let displayMode = this.selectDisplayMode();
498
     let displayMode = this.selectDisplayMode();
482
-    // Show/hide video
483
-    setVisibility(this.selectVideoElement(), displayMode === DISPLAY_VIDEO);
484
-    // Show/hide the avatar
485
-    setVisibility(this.$avatar(), displayMode === DISPLAY_AVATAR);
499
+    // Show/hide video.
500
+    UIUtil.setVisibility(   this.selectVideoElement(),
501
+                            (displayMode === DISPLAY_VIDEO
502
+                                || displayMode === DISPLAY_VIDEO_WITH_NAME));
503
+    // Show/hide the avatar.
504
+    UIUtil.setVisibility(   this.$avatar(),
505
+                            (displayMode === DISPLAY_AVATAR
506
+                                || displayMode === DISPLAY_AVATAR_WITH_NAME));
507
+    // Show/hide the display name.
508
+    UIUtil.setVisibility(   this.$displayName(),
509
+                            (displayMode === DISPLAY_BLACKNESS_WITH_NAME
510
+                                || displayMode === DISPLAY_VIDEO_WITH_NAME
511
+                                || displayMode === DISPLAY_AVATAR_WITH_NAME));
512
+    // show hide overlay when there is a video or avatar under
513
+    // the display name
514
+    UIUtil.setVisibility(   $('#' + this.videoSpanId
515
+                                + ' .videocontainer__hoverOverlay'),
516
+                            (displayMode === DISPLAY_AVATAR_WITH_NAME
517
+                                || displayMode === DISPLAY_VIDEO_WITH_NAME));
486
 };
518
 };
487
 
519
 
488
 SmallVideo.prototype.avatarChanged = function (avatarUrl) {
520
 SmallVideo.prototype.avatarChanged = function (avatarUrl) {
519
         return;
551
         return;
520
     }
552
     }
521
 
553
 
522
-    var indicatorSpan = this.getIndicatorSpan({
523
-        id: 'dominantspeakerindicator',
524
-        content: '<i id="indicatoricon" class="fa fa-bullhorn"></i>',
554
+    let indicatorSpanId = "dominantspeakerindicator";
555
+    let content = `<i id="indicatoricon"
556
+        '             class="indicatoricon fa fa-bullhorn"></i>`;
557
+    let indicatorSpan = UIUtil.getVideoThumbnailIndicatorSpan({
558
+        videoSpanId: this.videoSpanId,
559
+        indicatorId: indicatorSpanId,
560
+        content,
525
         tooltip: 'speaker'
561
         tooltip: 'speaker'
526
     });
562
     });
527
 
563
 
528
-    indicatorSpan.style.display = show ? "" : "none";
564
+    if (show) {
565
+        indicatorSpan.classList.add('show');
566
+    } else {
567
+        indicatorSpan.classList.remove('show');
568
+    }
529
 };
569
 };
530
 
570
 
531
 /**
571
 /**
539
         return;
579
         return;
540
     }
580
     }
541
 
581
 
542
-    var indicatorSpan = this.getIndicatorSpan({
543
-        id: 'raisehandindicator',
544
-        content: '<i id="indicatoricon" class="icon-raised-hand"></i>',
582
+    let indicatorSpanId = "raisehandindicator";
583
+    let content = `<i id="indicatoricon"
584
+                      class="icon-raised-hand indicatoricon"></i>`;
585
+    let indicatorSpan = UIUtil.getVideoThumbnailIndicatorSpan({
586
+        indicatorId: indicatorSpanId,
587
+        videoSpanId: this.videoSpanId,
588
+        content,
545
         tooltip: 'raisedHand'
589
         tooltip: 'raisedHand'
546
     });
590
     });
547
 
591
 
548
-    indicatorSpan.style.display = show ? "" : "none";
549
-};
550
-
551
-/**
552
- * Gets (creating if necessary) the "indicator" span for this SmallVideo.
553
- *
554
- * @param options.id {String} element ID
555
- * @param options.content {String} HTML content of the indicator
556
- * @param options.tooltip {String} The key that should be passed to tooltip
557
- *
558
- * @returns {HTMLElement} DOM represention of the indicator
559
- */
560
-SmallVideo.prototype.getIndicatorSpan = function(options) {
561
-    var indicator = this.container.querySelector('#' + options.id);
562
-
563
-    if (indicator) {
564
-        return indicator;
592
+    if (show) {
593
+        indicatorSpan.classList.add('show');
594
+    } else {
595
+        indicatorSpan.classList.remove('show');
565
     }
596
     }
566
-
567
-    indicator = document.createElement('span');
568
-    indicator.className = 'indicator';
569
-    indicator.id = options.id;
570
-
571
-    indicator.innerHTML = options.content;
572
-
573
-    UIUtil.setTooltip(indicator, options.tooltip, "top");
574
-    APP.translation.translateElement($(indicator));
575
-
576
-    this.container.appendChild(indicator);
577
-
578
-    return indicator;
579
 };
597
 };
580
 
598
 
581
 /**
599
 /**

+ 0
- 9
modules/UI/videolayout/VideoLayout.js 查看文件

449
         }
449
         }
450
     },
450
     },
451
 
451
 
452
-    /**
453
-     * Shows the presence status message for the given video.
454
-     */
455
-    setPresenceStatus (id, statusMsg) {
456
-        let remoteVideo = remoteVideos[id];
457
-        if (remoteVideo)
458
-            remoteVideo.setPresenceStatus(statusMsg);
459
-    },
460
-
461
     /**
452
     /**
462
      * Shows a visual indicator for the moderator of the conference.
453
      * Shows a visual indicator for the moderator of the conference.
463
      * On local or remote participants.
454
      * On local or remote participants.

正在加载...
取消
保存