Browse Source

Part 2 of previous commit. Makes the toolbar more visible. Moves toolbar and videolayout related code in separate classes.

j8
yanas 11 years ago
parent
commit
a5e15e80fc
2 changed files with 1091 additions and 0 deletions
  1. 232
    0
      toolbar.js
  2. 859
    0
      videolayout.js

+ 232
- 0
toolbar.js View File

@@ -0,0 +1,232 @@
1
+var Toolbar = (function (my) {
2
+    var INITIAL_TOOLBAR_TIMEOUT = 20000;
3
+    var TOOLBAR_TIMEOUT = INITIAL_TOOLBAR_TIMEOUT;
4
+
5
+    /**
6
+     * Opens the lock room dialog.
7
+     */
8
+    my.openLockDialog = function() {
9
+        // Only the focus is able to set a shared key.
10
+        if (focus === null) {
11
+            if (sharedKey)
12
+                $.prompt("This conversation is currently protected by"
13
+                        + " a shared secret key.",
14
+                    {
15
+                        title: "Secrect key",
16
+                        persistent: false
17
+                    }
18
+                );
19
+            else
20
+                $.prompt("This conversation isn't currently protected by"
21
+                        + " a secret key. Only the owner of the conference" +
22
+                        + " could set a shared key.",
23
+                    {
24
+                        title: "Secrect key",
25
+                        persistent: false
26
+                    }
27
+                );
28
+        } else {
29
+            if (sharedKey) {
30
+                $.prompt("Are you sure you would like to remove your secret key?",
31
+                    {
32
+                        title: "Remove secrect key",
33
+                        persistent: false,
34
+                        buttons: { "Remove": true, "Cancel": false},
35
+                        defaultButton: 1,
36
+                        submit: function (e, v, m, f) {
37
+                            if (v) {
38
+                                setSharedKey('');
39
+                                lockRoom(false);
40
+                            }
41
+                        }
42
+                    }
43
+                );
44
+            } else {
45
+                $.prompt('<h2>Set a secrect key to lock your room</h2>' +
46
+                         '<input id="lockKey" type="text" placeholder="your shared key" autofocus>',
47
+                    {
48
+                        persistent: false,
49
+                        buttons: { "Save": true, "Cancel": false},
50
+                        defaultButton: 1,
51
+                        loaded: function (event) {
52
+                            document.getElementById('lockKey').focus();
53
+                        },
54
+                        submit: function (e, v, m, f) {
55
+                            if (v) {
56
+                                var lockKey = document.getElementById('lockKey');
57
+
58
+                                if (lockKey.value) {
59
+                                    setSharedKey(Util.escapeHtml(lockKey.value));
60
+                                    lockRoom(true);
61
+                                }
62
+                            }
63
+                        }
64
+                    }
65
+                );
66
+            }
67
+        }
68
+    };
69
+
70
+    /**
71
+     * Opens the invite link dialog.
72
+     */
73
+    my.openLinkDialog = function() {
74
+        $.prompt('<input id="inviteLinkRef" type="text" value="' +
75
+            encodeURI(roomUrl) + '" onclick="this.select();" readonly>',
76
+            {
77
+                title: "Share this link with everyone you want to invite",
78
+                persistent: false,
79
+                buttons: { "Cancel": false},
80
+                loaded: function (event) {
81
+                    document.getElementById('inviteLinkRef').select();
82
+                }
83
+            }
84
+        );
85
+    };
86
+
87
+    /**
88
+     * Opens the settings dialog.
89
+     */
90
+    my.openSettingsDialog = function() {
91
+        $.prompt('<h2>Configure your conference</h2>' +
92
+            '<input type="checkbox" id="initMuted"> Participants join muted<br/>' +
93
+            '<input type="checkbox" id="requireNicknames"> Require nicknames<br/><br/>' +
94
+            'Set a secrect key to lock your room: <input id="lockKey" type="text" placeholder="your shared key" autofocus>',
95
+            {
96
+                persistent: false,
97
+                buttons: { "Save": true, "Cancel": false},
98
+                defaultButton: 1,
99
+                loaded: function (event) {
100
+                    document.getElementById('lockKey').focus();
101
+                },
102
+                submit: function (e, v, m, f) {
103
+                    if (v) {
104
+                        if ($('#initMuted').is(":checked")) {
105
+                            // it is checked
106
+                        }
107
+
108
+                        if ($('#requireNicknames').is(":checked")) {
109
+                            // it is checked
110
+                        }
111
+                        /*
112
+                        var lockKey = document.getElementById('lockKey');
113
+
114
+                        if (lockKey.value)
115
+                        {
116
+                            setSharedKey(lockKey.value);
117
+                            lockRoom(true);
118
+                        }
119
+                        */
120
+                    }
121
+                }
122
+            }
123
+        );
124
+    };
125
+
126
+    /**
127
+     * Toggles the application in and out of full screen mode
128
+     * (a.k.a. presentation mode in Chrome).
129
+     */
130
+    my.toggleFullScreen = function() {
131
+        var fsElement = document.documentElement;
132
+
133
+        if (!document.mozFullScreen && !document.webkitIsFullScreen) {
134
+            //Enter Full Screen
135
+            if (fsElement.mozRequestFullScreen) {
136
+                fsElement.mozRequestFullScreen();
137
+            }
138
+            else {
139
+                fsElement.webkitRequestFullScreen(Element.ALLOW_KEYBOARD_INPUT);
140
+            }
141
+        } else {
142
+            //Exit Full Screen
143
+            if (document.mozCancelFullScreen) {
144
+                document.mozCancelFullScreen();
145
+            } else {
146
+                document.webkitCancelFullScreen();
147
+            }
148
+        }
149
+    };
150
+
151
+    /**
152
+     * Shows the main toolbar.
153
+     */
154
+    my.showToolbar = function() {
155
+        if (!$('#header').is(':visible')) {
156
+            $('#header').show("slide", { direction: "up", duration: 300});
157
+
158
+            if (toolbarTimeout) {
159
+                clearTimeout(toolbarTimeout);
160
+                toolbarTimeout = null;
161
+            }
162
+            toolbarTimeout = setTimeout(hideToolbar, TOOLBAR_TIMEOUT);
163
+            TOOLBAR_TIMEOUT = 4000;
164
+        }
165
+
166
+        if (focus != null)
167
+        {
168
+//            TODO: Enable settings functionality. Need to uncomment the settings button in index.html.
169
+//            $('#settingsButton').css({visibility:"visible"});
170
+        }
171
+
172
+        // Show/hide desktop sharing button
173
+        showDesktopSharingButton();
174
+    };
175
+
176
+    /**
177
+     * Docks/undocks the toolbar.
178
+     *
179
+     * @param isDock indicates what operation to perform
180
+     */
181
+    my.dockToolbar = function(isDock) {
182
+        if (isDock) {
183
+            // First make sure the toolbar is shown.
184
+            if (!$('#header').is(':visible')) {
185
+                Toolbar.showToolbar();
186
+            }
187
+            // Then clear the time out, to dock the toolbar.
188
+            clearTimeout(toolbarTimeout);
189
+            toolbarTimeout = null;
190
+        }
191
+        else {
192
+            if (!$('#header').is(':visible')) {
193
+                Toolbar.showToolbar();
194
+            }
195
+            else {
196
+                toolbarTimeout = setTimeout(hideToolbar, TOOLBAR_TIMEOUT);
197
+            }
198
+        }
199
+    };
200
+
201
+    /**
202
+     * Updates the lock button state.
203
+     */
204
+    my.updateLockButton = function() {
205
+        buttonClick("#lockIcon", "icon-security icon-security-locked");
206
+    };
207
+
208
+    /**
209
+     * Hides the toolbar.
210
+     */
211
+    var hideToolbar = function () {
212
+        var isToolbarHover = false;
213
+        $('#header').find('*').each(function () {
214
+            var id = $(this).attr('id');
215
+            if ($("#" + id + ":hover").length > 0) {
216
+                isToolbarHover = true;
217
+            }
218
+        });
219
+
220
+        clearTimeout(toolbarTimeout);
221
+        toolbarTimeout = null;
222
+
223
+        if (!isToolbarHover) {
224
+            $('#header').hide("slide", { direction: "up", duration: 300});
225
+        }
226
+        else {
227
+            toolbarTimeout = setTimeout(hideToolbar, TOOLBAR_TIMEOUT);
228
+        }
229
+    };
230
+
231
+    return my;
232
+}(Toolbar || {}));

+ 859
- 0
videolayout.js View File

@@ -0,0 +1,859 @@
1
+var VideoLayout = (function (my) {
2
+    var preMuted = false;
3
+    var currentActiveSpeaker = null;
4
+
5
+    my.changeLocalAudio = function(stream) {
6
+        connection.jingle.localAudio = stream;
7
+
8
+        RTC.attachMediaStream($('#localAudio'), stream);
9
+        document.getElementById('localAudio').autoplay = true;
10
+        document.getElementById('localAudio').volume = 0;
11
+        if (preMuted) {
12
+            toggleAudio();
13
+            preMuted = false;
14
+        }
15
+    };
16
+
17
+    my.changeLocalVideo = function(stream, flipX) {
18
+        connection.jingle.localVideo = stream;
19
+
20
+        var localVideo = document.createElement('video');
21
+        localVideo.id = 'localVideo_' + stream.id;
22
+        localVideo.autoplay = true;
23
+        localVideo.volume = 0; // is it required if audio is separated ?
24
+        localVideo.oncontextmenu = function () { return false; };
25
+
26
+        var localVideoContainer = document.getElementById('localVideoWrapper');
27
+        localVideoContainer.appendChild(localVideo);
28
+
29
+        var localVideoSelector = $('#' + localVideo.id);
30
+        // Add click handler
31
+        localVideoSelector.click(function () {
32
+            VideoLayout.handleVideoThumbClicked(localVideo.src);
33
+        });
34
+        // Add hover handler
35
+        $('#localVideoContainer').hover(
36
+            function() {
37
+                VideoLayout.showDisplayName('localVideoContainer', true);
38
+            },
39
+            function() {
40
+                if (focusedVideoSrc !== localVideo.src)
41
+                    VideoLayout.showDisplayName('localVideoContainer', false);
42
+            }
43
+        );
44
+        // Add stream ended handler
45
+        stream.onended = function () {
46
+            localVideoContainer.removeChild(localVideo);
47
+            VideoLayout.checkChangeLargeVideo(localVideo.src);
48
+        };
49
+        // Flip video x axis if needed
50
+        flipXLocalVideo = flipX;
51
+        if (flipX) {
52
+            localVideoSelector.addClass("flipVideoX");
53
+        }
54
+        // Attach WebRTC stream
55
+        RTC.attachMediaStream(localVideoSelector, stream);
56
+
57
+        localVideoSrc = localVideo.src;
58
+        VideoLayout.updateLargeVideo(localVideoSrc, 0);
59
+    };
60
+
61
+    /**
62
+     * Checks if removed video is currently displayed and tries to display another one instead.
63
+     * @param removedVideoSrc src stream identifier of the video.
64
+     */
65
+    my.checkChangeLargeVideo = function(removedVideoSrc) {
66
+        if (removedVideoSrc === $('#largeVideo').attr('src')) {
67
+            // this is currently displayed as large
68
+            // pick the last visible video in the row
69
+            // if nobody else is left, this picks the local video
70
+            var pick = $('#remoteVideos>span[id!="mixedstream"]:visible:last>video').get(0);
71
+
72
+            if (!pick) {
73
+                console.info("Last visible video no longer exists");
74
+                pick = $('#remoteVideos>span[id!="mixedstream"]>video').get(0);
75
+                if (!pick) {
76
+                    // Try local video
77
+                    console.info("Fallback to local video...");
78
+                    pick = $('#remoteVideos>span>span>video').get(0);
79
+                }
80
+            }
81
+
82
+            // mute if localvideo
83
+            if (pick) {
84
+                VideoLayout.updateLargeVideo(pick.src, pick.volume);
85
+            } else {
86
+                console.warn("Failed to elect large video");
87
+            }
88
+        }
89
+    };
90
+
91
+
92
+    /**
93
+     * Updates the large video with the given new video source.
94
+     */
95
+    my.updateLargeVideo = function(newSrc, vol) {
96
+        console.log('hover in', newSrc);
97
+
98
+        if ($('#largeVideo').attr('src') != newSrc) {
99
+
100
+            var isVisible = $('#largeVideo').is(':visible');
101
+
102
+            $('#largeVideo').fadeOut(300, function () {
103
+                $(this).attr('src', newSrc);
104
+
105
+                // Screen stream is already rotated
106
+                var flipX = (newSrc === localVideoSrc) && flipXLocalVideo;
107
+
108
+                var videoTransform = document.getElementById('largeVideo')
109
+                                        .style.webkitTransform;
110
+
111
+                if (flipX && videoTransform !== 'scaleX(-1)') {
112
+                    document.getElementById('largeVideo').style.webkitTransform
113
+                        = "scaleX(-1)";
114
+                }
115
+                else if (!flipX && videoTransform === 'scaleX(-1)') {
116
+                    document.getElementById('largeVideo').style.webkitTransform
117
+                        = "none";
118
+                }
119
+
120
+                // Change the way we'll be measuring and positioning large video
121
+                var isDesktop = isVideoSrcDesktop(newSrc);
122
+                getVideoSize = isDesktop
123
+                                ? getDesktopVideoSize
124
+                                : getCameraVideoSize;
125
+                getVideoPosition = isDesktop
126
+                                    ? getDesktopVideoPosition
127
+                                    : getCameraVideoPosition;
128
+
129
+                if (isVisible)
130
+                    $(this).fadeIn(300);
131
+            });
132
+        }
133
+    };
134
+
135
+    my.handleVideoThumbClicked = function(videoSrc) {
136
+        // Restore style for previously focused video
137
+        var focusJid = getJidFromVideoSrc(focusedVideoSrc);
138
+        var oldContainer =
139
+            getParticipantContainer(focusJid);
140
+
141
+        if (oldContainer) {
142
+            oldContainer.removeClass("videoContainerFocused");
143
+            VideoLayout.enableActiveSpeaker(
144
+                    Strophe.getResourceFromJid(focusJid), false);
145
+        }
146
+
147
+        // Unlock
148
+        if (focusedVideoSrc === videoSrc)
149
+        {
150
+            focusedVideoSrc = null;
151
+            return;
152
+        }
153
+
154
+        // Lock new video
155
+        focusedVideoSrc = videoSrc;
156
+
157
+        var userJid = getJidFromVideoSrc(videoSrc);
158
+        if (userJid)
159
+        {
160
+            var container = getParticipantContainer(userJid);
161
+            container.addClass("videoContainerFocused");
162
+
163
+            var resourceJid = Strophe.getResourceFromJid(userJid);
164
+            VideoLayout.enableActiveSpeaker(resourceJid, true);
165
+        }
166
+
167
+        $(document).trigger("video.selected", [false]);
168
+
169
+        VideoLayout.updateLargeVideo(videoSrc, 1);
170
+
171
+        $('audio').each(function (idx, el) {
172
+            if (el.id.indexOf('mixedmslabel') !== -1) {
173
+                el.volume = 0;
174
+                el.volume = 1;
175
+            }
176
+        });
177
+    };
178
+
179
+    /**
180
+     * Positions the large video.
181
+     *
182
+     * @param videoWidth the stream video width
183
+     * @param videoHeight the stream video height
184
+     */
185
+    my.positionLarge = function (videoWidth, videoHeight) {
186
+        var videoSpaceWidth = $('#videospace').width();
187
+        var videoSpaceHeight = window.innerHeight;
188
+
189
+        var videoSize = getVideoSize(videoWidth,
190
+                                     videoHeight,
191
+                                     videoSpaceWidth,
192
+                                     videoSpaceHeight);
193
+
194
+        var largeVideoWidth = videoSize[0];
195
+        var largeVideoHeight = videoSize[1];
196
+
197
+        var videoPosition = getVideoPosition(largeVideoWidth,
198
+                                             largeVideoHeight,
199
+                                             videoSpaceWidth,
200
+                                             videoSpaceHeight);
201
+
202
+        var horizontalIndent = videoPosition[0];
203
+        var verticalIndent = videoPosition[1];
204
+
205
+        positionVideo($('#largeVideo'),
206
+                      largeVideoWidth,
207
+                      largeVideoHeight,
208
+                      horizontalIndent, verticalIndent);
209
+    };
210
+
211
+    /**
212
+     * Shows/hides the large video.
213
+     */
214
+    my.setLargeVideoVisible = function(isVisible) {
215
+        if (isVisible) {
216
+            $('#largeVideo').css({visibility: 'visible'});
217
+            $('.watermark').css({visibility: 'visible'});
218
+        }
219
+        else {
220
+            $('#largeVideo').css({visibility: 'hidden'});
221
+            $('.watermark').css({visibility: 'hidden'});
222
+        }
223
+    };
224
+
225
+
226
+    /**
227
+     * Checks if container for participant identified by given peerJid exists
228
+     * in the document and creates it eventually.
229
+     * 
230
+     * @param peerJid peer Jid to check.
231
+     */
232
+    my.ensurePeerContainerExists = function(peerJid) {
233
+        var peerResource = Strophe.getResourceFromJid(peerJid);
234
+        var videoSpanId = 'participant_' + peerResource;
235
+
236
+        if ($('#' + videoSpanId).length > 0) {
237
+            // If there's been a focus change, make sure we add focus related
238
+            // interface!!
239
+            if (focus && $('#remote_popupmenu_' + peerResource).length <= 0)
240
+                addRemoteVideoMenu( peerJid,
241
+                                    document.getElementById(videoSpanId));
242
+            return;
243
+        }
244
+
245
+        var container
246
+            = VideoLayout.addRemoteVideoContainer(peerJid, videoSpanId);
247
+
248
+        var nickfield = document.createElement('span');
249
+        nickfield.className = "nick";
250
+        nickfield.appendChild(document.createTextNode(peerResource));
251
+        container.appendChild(nickfield);
252
+        VideoLayout.resizeThumbnails();
253
+    };
254
+
255
+    my.addRemoteVideoContainer = function(peerJid, spanId) {
256
+        var container = document.createElement('span');
257
+        container.id = spanId;
258
+        container.className = 'videocontainer';
259
+        var remotes = document.getElementById('remoteVideos');
260
+
261
+        // If the peerJid is null then this video span couldn't be directly
262
+        // associated with a participant (this could happen in the case of prezi).
263
+        if (focus && peerJid != null)
264
+            addRemoteVideoMenu(peerJid, container);
265
+
266
+        remotes.appendChild(container);
267
+        return container;
268
+    };
269
+
270
+    /**
271
+     * Shows the display name for the given video.
272
+     */
273
+    my.setDisplayName = function(videoSpanId, displayName) {
274
+        var nameSpan = $('#' + videoSpanId + '>span.displayname');
275
+
276
+        // If we already have a display name for this video.
277
+        if (nameSpan.length > 0) {
278
+            var nameSpanElement = nameSpan.get(0);
279
+
280
+            if (nameSpanElement.id === 'localDisplayName' &&
281
+                $('#localDisplayName').text() !== displayName) {
282
+                $('#localDisplayName').text(displayName);
283
+            } else {
284
+                $('#' + videoSpanId + '_name').text(displayName);
285
+            }
286
+        } else {
287
+            var editButton = null;
288
+
289
+            if (videoSpanId === 'localVideoContainer') {
290
+                editButton = createEditDisplayNameButton();
291
+            }
292
+            if (displayName.length) {
293
+                nameSpan = document.createElement('span');
294
+                nameSpan.className = 'displayname';
295
+                nameSpan.innerText = displayName;
296
+                $('#' + videoSpanId)[0].appendChild(nameSpan);
297
+            }
298
+
299
+            if (!editButton) {
300
+                nameSpan.id = videoSpanId + '_name';
301
+            } else {
302
+                nameSpan.id = 'localDisplayName';
303
+                $('#' + videoSpanId)[0].appendChild(editButton);
304
+
305
+                var editableText = document.createElement('input');
306
+                editableText.className = 'displayname';
307
+                editableText.id = 'editDisplayName';
308
+
309
+                if (displayName.length) {
310
+                    editableText.value
311
+                        = displayName.substring(0, displayName.indexOf(' (me)'));
312
+                }
313
+
314
+                editableText.setAttribute('style', 'display:none;');
315
+                editableText.setAttribute('placeholder', 'ex. Jane Pink');
316
+                $('#' + videoSpanId)[0].appendChild(editableText);
317
+
318
+                $('#localVideoContainer .displayname').bind("click", function (e) {
319
+                    e.preventDefault();
320
+                    $('#localDisplayName').hide();
321
+                    $('#editDisplayName').show();
322
+                    $('#editDisplayName').focus();
323
+                    $('#editDisplayName').select();
324
+
325
+                    var inputDisplayNameHandler = function (name) {
326
+                        if (nickname !== name) {
327
+                            nickname = name;
328
+                            window.localStorage.displayname = nickname;
329
+                            connection.emuc.addDisplayNameToPresence(nickname);
330
+                            connection.emuc.sendPresence();
331
+
332
+                            Chat.setChatConversationMode(true);
333
+                        }
334
+
335
+                        if (!$('#localDisplayName').is(":visible")) {
336
+                            if (nickname) {
337
+                                $('#localDisplayName').text(nickname + " (me)");
338
+                                $('#localDisplayName').show();
339
+                            }
340
+                            else {
341
+                                $('#localDisplayName').text(nickname);
342
+                            }
343
+
344
+                            $('#editDisplayName').hide();
345
+                        }
346
+                    };
347
+
348
+                    $('#editDisplayName').one("focusout", function (e) {
349
+                        inputDisplayNameHandler(this.value);
350
+                    });
351
+
352
+                    $('#editDisplayName').on('keydown', function (e) {
353
+                        if (e.keyCode === 13) {
354
+                            e.preventDefault();
355
+                            inputDisplayNameHandler(this.value);
356
+                        }
357
+                    });
358
+                });
359
+            }
360
+        }
361
+    };
362
+
363
+    /**
364
+     * Shows/hides the display name on the remote video.
365
+     * @param videoSpanId the identifier of the video span element
366
+     * @param isShow indicates if the display name should be shown or hidden
367
+     */
368
+    my.showDisplayName = function(videoSpanId, isShow) {
369
+        var nameSpan = $('#' + videoSpanId + '>span.displayname').get(0);
370
+
371
+        if (isShow) {
372
+            if (nameSpan && nameSpan.innerHTML && nameSpan.innerHTML.length) 
373
+                nameSpan.setAttribute("style", "display:inline-block;");
374
+        }
375
+        else {
376
+            if (nameSpan)
377
+                nameSpan.setAttribute("style", "display:none;");
378
+        }
379
+    };
380
+
381
+    /**
382
+     * Shows a visual indicator for the focus of the conference.
383
+     * Currently if we're not the owner of the conference we obtain the focus
384
+     * from the connection.jingle.sessions.
385
+     */
386
+    my.showFocusIndicator = function() {
387
+        if (focus !== null) {
388
+            var indicatorSpan = $('#localVideoContainer .focusindicator');
389
+
390
+            if (indicatorSpan.children().length === 0)
391
+            {
392
+                createFocusIndicatorElement(indicatorSpan[0]);
393
+            }
394
+        }
395
+        else if (Object.keys(connection.jingle.sessions).length > 0) {
396
+            // If we're only a participant the focus will be the only session we have.
397
+            var session
398
+                = connection.jingle.sessions
399
+                    [Object.keys(connection.jingle.sessions)[0]];
400
+            var focusId
401
+                = 'participant_' + Strophe.getResourceFromJid(session.peerjid);
402
+
403
+            var focusContainer = document.getElementById(focusId);
404
+            if (!focusContainer) {
405
+                console.error("No focus container!");
406
+                return;
407
+            }
408
+            var indicatorSpan = $('#' + focusId + ' .focusindicator');
409
+
410
+            if (!indicatorSpan || indicatorSpan.length === 0) {
411
+                indicatorSpan = document.createElement('span');
412
+                indicatorSpan.className = 'focusindicator';
413
+                focusContainer.appendChild(indicatorSpan);
414
+
415
+                createFocusIndicatorElement(indicatorSpan);
416
+            }
417
+        }
418
+    };
419
+
420
+    /**
421
+     * Shows video muted indicator over small videos.
422
+     */
423
+    my.showVideoIndicator = function(videoSpanId, isMuted) {
424
+        var videoMutedSpan = $('#' + videoSpanId + '>span.videoMuted');
425
+
426
+        if (isMuted === 'false') {
427
+            if (videoMutedSpan.length > 0) {
428
+                videoMutedSpan.remove();
429
+            }
430
+        }
431
+        else {
432
+            var audioMutedSpan = $('#' + videoSpanId + '>span.audioMuted');
433
+
434
+            videoMutedSpan = document.createElement('span');
435
+            videoMutedSpan.className = 'videoMuted';
436
+            if (audioMutedSpan) {
437
+                videoMutedSpan.right = '30px';
438
+            }
439
+            $('#' + videoSpanId)[0].appendChild(videoMutedSpan);
440
+
441
+            var mutedIndicator = document.createElement('i');
442
+            mutedIndicator.className = 'icon-camera-disabled';
443
+            mutedIndicator.title = "Participant has stopped the camera.";
444
+            videoMutedSpan.appendChild(mutedIndicator);
445
+        }
446
+    };
447
+
448
+    /**
449
+     * Shows audio muted indicator over small videos.
450
+     */
451
+    my.showAudioIndicator = function(videoSpanId, isMuted) {
452
+        var audioMutedSpan = $('#' + videoSpanId + '>span.audioMuted');
453
+
454
+        if (isMuted === 'false') {
455
+            if (audioMutedSpan.length > 0) {
456
+                audioMutedSpan.remove();
457
+            }
458
+        }
459
+        else {
460
+            var videoMutedSpan = $('#' + videoSpanId + '>span.videoMuted');
461
+
462
+            audioMutedSpan = document.createElement('span');
463
+            audioMutedSpan.className = 'audioMuted';
464
+            if (videoMutedSpan) {
465
+                audioMutedSpan.right = '30px';
466
+            }
467
+            $('#' + videoSpanId)[0].appendChild(audioMutedSpan);
468
+
469
+            var mutedIndicator = document.createElement('i');
470
+            mutedIndicator.className = 'icon-mic-disabled';
471
+            mutedIndicator.title = "Participant is muted";
472
+            audioMutedSpan.appendChild(mutedIndicator);
473
+        }
474
+    };
475
+
476
+    /**
477
+     * Resizes the large video container.
478
+     */
479
+    my.resizeLargeVideoContainer = function () {
480
+        Chat.resizeChat();
481
+        var availableHeight = window.innerHeight;
482
+        var availableWidth = Util.getAvailableVideoWidth();
483
+
484
+        if (availableWidth < 0 || availableHeight < 0) return;
485
+
486
+        $('#videospace').width(availableWidth);
487
+        $('#videospace').height(availableHeight);
488
+        $('#largeVideoContainer').width(availableWidth);
489
+        $('#largeVideoContainer').height(availableHeight);
490
+
491
+        VideoLayout.resizeThumbnails();
492
+    };
493
+
494
+    /**
495
+     * Resizes thumbnails.
496
+     */
497
+    my.resizeThumbnails = function() {
498
+        var thumbnailSize = calculateThumbnailSize();
499
+        var width = thumbnailSize[0];
500
+        var height = thumbnailSize[1];
501
+
502
+        // size videos so that while keeping AR and max height, we have a
503
+        // nice fit
504
+        $('#remoteVideos').height(height);
505
+        $('#remoteVideos>span').width(width);
506
+        $('#remoteVideos>span').height(height);
507
+    };
508
+
509
+    /**
510
+     * Enables the active speaker UI.
511
+     *
512
+     * @param resourceJid the jid indicating the video element to
513
+     * activate/deactivate
514
+     * @param isEnable indicates if the active speaker should be enabled or
515
+     * disabled
516
+     */
517
+    my.enableActiveSpeaker = function(resourceJid, isEnable) {
518
+        var videoSpanId = null;
519
+        if (resourceJid
520
+                === Strophe.getResourceFromJid(connection.emuc.myroomjid))
521
+            videoSpanId = 'localVideoWrapper';
522
+        else
523
+            videoSpanId = 'participant_' + resourceJid;
524
+
525
+        videoSpan = document.getElementById(videoSpanId);
526
+
527
+        if (!videoSpan) {
528
+            console.error("No video element for jid", resourceJid);
529
+            return;
530
+        }
531
+
532
+        // If there's an active speaker (automatically) selected we have to
533
+        // disable this state and update the current active speaker.
534
+        if (isEnable) {
535
+            if (currentActiveSpeaker)
536
+                VideoLayout.enableActiveSpeaker(currentActiveSpeaker, false);
537
+            else
538
+                currentActiveSpeaker = resourceJid;
539
+        }
540
+        else if (resourceJid === currentActiveSpeaker)
541
+            currentActiveSpeaker = null;
542
+
543
+        var activeSpeakerCanvas = $('#' + videoSpanId + '>canvas');
544
+        var videoElement = $('#' + videoSpanId + '>video');
545
+        var canvasSize = calculateThumbnailSize();
546
+
547
+        if (isEnable && (!activeSpeakerCanvas
548
+                    || activeSpeakerCanvas.length === 0)) {
549
+
550
+              activeSpeakerCanvas = document.createElement('canvas');
551
+              activeSpeakerCanvas.width = canvasSize[0];
552
+              activeSpeakerCanvas.height = canvasSize[1];
553
+
554
+              // We flip the canvas image if this is the local video.
555
+              if (videoSpanId === 'localVideoWrapper')
556
+                  activeSpeakerCanvas.className += " flipVideoX";
557
+
558
+              videoSpan.appendChild(activeSpeakerCanvas);
559
+              activeSpeakerCanvas.addEventListener(
560
+                      'click',
561
+                      function() {
562
+                          VideoLayout.handleVideoThumbClicked(
563
+                                  videoElement.get(0).src);
564
+                      }, false);
565
+        }
566
+        else {
567
+            activeSpeakerCanvas = activeSpeakerCanvas.get(0);
568
+        } 
569
+
570
+        if (videoElement && videoElement.length > 0) {
571
+            var video = document.getElementById(videoElement.get(0).id);
572
+            if (isEnable) {
573
+                var context = activeSpeakerCanvas.getContext('2d');
574
+
575
+                context.fillRect(0, 0, canvasSize[0], canvasSize[1]);
576
+                context.drawImage(video, 0, 0, canvasSize[0], canvasSize[1]);
577
+                Util.imageToGrayScale(activeSpeakerCanvas);
578
+
579
+                VideoLayout
580
+                    .showDisplayName(videoSpanId, true);
581
+                activeSpeakerCanvas
582
+                    .setAttribute('style', 'display:block !important;');
583
+                video.setAttribute('style', 'display:none !important;');
584
+            }
585
+            else {
586
+                VideoLayout
587
+                    .showDisplayName(videoSpanId, false);
588
+                video.setAttribute('style', 'display:block !important;');
589
+                activeSpeakerCanvas
590
+                    .setAttribute('style', 'display:none !important;');
591
+            }
592
+        }
593
+    };
594
+
595
+    /**
596
+     * Gets the selector of video thumbnail container for the user identified by
597
+     * given <tt>userJid</tt>
598
+     * @param userJid user's Jid for whom we want to get the video container.
599
+     */
600
+    function getParticipantContainer(userJid)
601
+    {
602
+        if (!userJid)
603
+            return null;
604
+
605
+        if (userJid === connection.emuc.myroomjid)
606
+            return $("#localVideoContainer");
607
+        else
608
+            return $("#participant_" + Strophe.getResourceFromJid(userJid));
609
+    }
610
+
611
+    /**
612
+     * Sets the size and position of the given video element.
613
+     *
614
+     * @param video the video element to position
615
+     * @param width the desired video width
616
+     * @param height the desired video height
617
+     * @param horizontalIndent the left and right indent
618
+     * @param verticalIndent the top and bottom indent
619
+     */
620
+    function positionVideo(video,
621
+                           width,
622
+                           height,
623
+                           horizontalIndent,
624
+                           verticalIndent) {
625
+        video.width(width);
626
+        video.height(height);
627
+        video.css({  top: verticalIndent + 'px',
628
+                     bottom: verticalIndent + 'px',
629
+                     left: horizontalIndent + 'px',
630
+                     right: horizontalIndent + 'px'});
631
+    }
632
+
633
+    /**
634
+     * Calculates the thumbnail size.
635
+     */
636
+    var calculateThumbnailSize = function () {
637
+        // Calculate the available height, which is the inner window height minus
638
+       // 39px for the header minus 2px for the delimiter lines on the top and
639
+       // bottom of the large video, minus the 36px space inside the remoteVideos
640
+       // container used for highlighting shadow.
641
+       var availableHeight = 100;
642
+
643
+       var numvids = $('#remoteVideos>span:visible').length;
644
+
645
+       // Remove the 1px borders arround videos and the chat width.
646
+       var availableWinWidth = $('#remoteVideos').width() - 2 * numvids - 50;
647
+       var availableWidth = availableWinWidth / numvids;
648
+       var aspectRatio = 16.0 / 9.0;
649
+       var maxHeight = Math.min(160, availableHeight);
650
+       availableHeight = Math.min(maxHeight, availableWidth / aspectRatio);
651
+       if (availableHeight < availableWidth / aspectRatio) {
652
+           availableWidth = Math.floor(availableHeight * aspectRatio);
653
+       }
654
+
655
+       return [availableWidth, availableHeight];
656
+   };
657
+
658
+   /**
659
+    * Returns an array of the video dimensions, so that it keeps it's aspect
660
+    * ratio and fits available area with it's larger dimension. This method
661
+    * ensures that whole video will be visible and can leave empty areas.
662
+    *
663
+    * @return an array with 2 elements, the video width and the video height
664
+    */
665
+   function getDesktopVideoSize(videoWidth,
666
+                                videoHeight,
667
+                                videoSpaceWidth,
668
+                                videoSpaceHeight) {
669
+       if (!videoWidth)
670
+           videoWidth = currentVideoWidth;
671
+       if (!videoHeight)
672
+           videoHeight = currentVideoHeight;
673
+
674
+       var aspectRatio = videoWidth / videoHeight;
675
+
676
+       var availableWidth = Math.max(videoWidth, videoSpaceWidth);
677
+       var availableHeight = Math.max(videoHeight, videoSpaceHeight);
678
+
679
+       videoSpaceHeight -= $('#remoteVideos').outerHeight();
680
+
681
+       if (availableWidth / aspectRatio >= videoSpaceHeight)
682
+       {
683
+           availableHeight = videoSpaceHeight;
684
+           availableWidth = availableHeight * aspectRatio;
685
+       }
686
+
687
+       if (availableHeight * aspectRatio >= videoSpaceWidth)
688
+       {
689
+           availableWidth = videoSpaceWidth;
690
+           availableHeight = availableWidth / aspectRatio;
691
+       }
692
+
693
+       return [availableWidth, availableHeight];
694
+   }
695
+
696
+   /**
697
+    * Creates the edit display name button.
698
+    * 
699
+    * @returns the edit button
700
+    */
701
+    function createEditDisplayNameButton() {
702
+        var editButton = document.createElement('a');
703
+        editButton.className = 'displayname';
704
+        editButton.innerHTML = '<i class="fa fa-pencil"></i>';
705
+
706
+        return editButton;
707
+    }
708
+
709
+    /**
710
+     * Creates the element indicating the focus of the conference.
711
+     *
712
+     * @param parentElement the parent element where the focus indicator will
713
+     * be added
714
+     */
715
+    function createFocusIndicatorElement(parentElement) {
716
+        var focusIndicator = document.createElement('i');
717
+        focusIndicator.className = 'fa fa-star';
718
+        focusIndicator.title = "The owner of this conference";
719
+        parentElement.appendChild(focusIndicator);
720
+    }
721
+
722
+    /**
723
+     * Updates the remote video menu.
724
+     *
725
+     * @param jid the jid indicating the video for which we're adding a menu.
726
+     * @param isMuted indicates the current mute state
727
+     */
728
+    my.updateRemoteVideoMenu = function(jid, isMuted) {
729
+        var muteMenuItem
730
+            = $('#remote_popupmenu_'
731
+                    + Strophe.getResourceFromJid(jid)
732
+                    + '>li>a.mutelink');
733
+
734
+        var mutedIndicator = "<i class='icon-mic-disabled'></i>";
735
+
736
+        if (muteMenuItem.length) {
737
+            var muteLink = muteMenuItem.get(0);
738
+
739
+            if (isMuted === 'true') {
740
+                muteLink.innerHTML = mutedIndicator + ' Muted';
741
+                muteLink.className = 'mutelink disabled';
742
+            }
743
+            else {
744
+                muteLink.innerHTML = mutedIndicator + ' Mute';
745
+                muteLink.className = 'mutelink';
746
+            }
747
+        }
748
+    };
749
+
750
+    /**
751
+     * Adds the remote video menu element for the given <tt>jid</tt> in the
752
+     * given <tt>parentElement</tt>.
753
+     *
754
+     * @param jid the jid indicating the video for which we're adding a menu.
755
+     * @param parentElement the parent element where this menu will be added
756
+     */
757
+    function addRemoteVideoMenu(jid, parentElement) {
758
+        var spanElement = document.createElement('span');
759
+        spanElement.className = 'remotevideomenu';
760
+
761
+        parentElement.appendChild(spanElement);
762
+
763
+        var menuElement = document.createElement('i');
764
+        menuElement.className = 'fa fa-angle-down';
765
+        menuElement.title = 'Remote user controls';
766
+        spanElement.appendChild(menuElement);
767
+
768
+//        <ul class="popupmenu">
769
+//        <li><a href="#">Mute</a></li>
770
+//        <li><a href="#">Eject</a></li>
771
+//        </ul>
772
+        var popupmenuElement = document.createElement('ul');
773
+        popupmenuElement.className = 'popupmenu';
774
+        popupmenuElement.id
775
+            = 'remote_popupmenu_' + Strophe.getResourceFromJid(jid);
776
+        spanElement.appendChild(popupmenuElement);
777
+
778
+        var muteMenuItem = document.createElement('li');
779
+        var muteLinkItem = document.createElement('a');
780
+
781
+        var mutedIndicator = "<i class='icon-mic-disabled'></i>";
782
+
783
+        if (!mutedAudios[jid]) {
784
+            muteLinkItem.innerHTML = mutedIndicator + 'Mute';
785
+            muteLinkItem.className = 'mutelink';
786
+        }
787
+        else {
788
+            muteLinkItem.innerHTML = mutedIndicator + ' Muted';
789
+            muteLinkItem.className = 'mutelink disabled';
790
+        }
791
+
792
+        muteLinkItem.onclick = function(){
793
+            if ($(this).attr('disabled') != undefined) {
794
+                event.preventDefault();
795
+            }
796
+            var isMute = !mutedAudios[jid];
797
+            connection.moderate.setMute(jid, isMute);
798
+            popupmenuElement.setAttribute('style', 'display:none;');
799
+
800
+            if (isMute) {
801
+                this.innerHTML = mutedIndicator + ' Muted';
802
+                this.className = 'mutelink disabled';
803
+            }
804
+            else {
805
+                this.innerHTML = mutedIndicator + ' Mute';
806
+                this.className = 'mutelink';
807
+            }
808
+        };
809
+
810
+        muteMenuItem.appendChild(muteLinkItem);
811
+        popupmenuElement.appendChild(muteMenuItem);
812
+
813
+        var ejectIndicator = "<i class='fa fa-eject'></i>";
814
+
815
+        var ejectMenuItem = document.createElement('li');
816
+        var ejectLinkItem = document.createElement('a');
817
+        ejectLinkItem.innerHTML = ejectIndicator + ' Kick out';
818
+        ejectLinkItem.onclick = function(){
819
+            connection.moderate.eject(jid);
820
+            popupmenuElement.setAttribute('style', 'display:none;');
821
+        };
822
+
823
+        ejectMenuItem.appendChild(ejectLinkItem);
824
+        popupmenuElement.appendChild(ejectMenuItem);
825
+    }
826
+
827
+    $(document).bind('audiomuted.muc', function (event, jid, isMuted) {
828
+        var videoSpanId = null;
829
+        if (jid === connection.emuc.myroomjid) {
830
+            videoSpanId = 'localVideoContainer';
831
+        } else {
832
+            VideoLayout.ensurePeerContainerExists(jid);
833
+            videoSpanId = 'participant_' + Strophe.getResourceFromJid(jid);
834
+        }
835
+
836
+        if (focus) {
837
+            mutedAudios[jid] = isMuted;
838
+            VideoLayout.updateRemoteVideoMenu(jid, isMuted);
839
+        }
840
+
841
+        if (videoSpanId)
842
+            VideoLayout.showAudioIndicator(videoSpanId, isMuted);
843
+    });
844
+
845
+    $(document).bind('videomuted.muc', function (event, jid, isMuted) {
846
+        var videoSpanId = null;
847
+        if (jid === connection.emuc.myroomjid) {
848
+            videoSpanId = 'localVideoContainer';
849
+        } else {
850
+            VideoLayout.ensurePeerContainerExists(jid);
851
+            videoSpanId = 'participant_' + Strophe.getResourceFromJid(jid);
852
+        }
853
+
854
+        if (videoSpanId)
855
+            VideoLayout.showVideoIndicator(videoSpanId, isMuted);
856
+    });
857
+
858
+    return my;
859
+}(VideoLayout || {}));

Loading…
Cancel
Save