ソースを参照

Adds a last-n user interface. Some code restructuring related to last-n and stream reception/deletion. Adds a contact list user interface.

j8
yanas 10年前
コミット
e89c7ea85c
21個のファイルの変更865行の追加200行の削除
  1. 70
    128
      app.js
  2. 32
    0
      bottom_toolbar.js
  3. 8
    1
      chat.js
  4. 1
    1
      config.js
  5. 235
    0
      contact_list.js
  6. 35
    0
      css/contact_list.css
  7. 6
    0
      css/font.css
  8. 51
    5
      css/main.css
  9. 5
    1
      css/videolayout_default.css
  10. 8
    2
      data_channels.js
  11. バイナリ
      fonts/jitsi.eot
  12. 2
    0
      fonts/jitsi.svg
  13. バイナリ
      fonts/jitsi.ttf
  14. バイナリ
      fonts/jitsi.woff
  15. バイナリ
      images/avatar2.png
  16. 36
    12
      index.html
  17. 30
    0
      media_stream.js
  18. 8
    0
      muc.js
  19. 11
    10
      toolbar.js
  20. 3
    1
      util.js
  21. 324
    39
      videolayout.js

+ 70
- 128
app.js ファイルの表示

11
 var roomUrl = null;
11
 var roomUrl = null;
12
 var roomName = null;
12
 var roomName = null;
13
 var ssrc2jid = {};
13
 var ssrc2jid = {};
14
+var mediaStreams = [];
15
+
14
 /**
16
 /**
15
  * The stats collector that process stats data and triggers updates to app.js.
17
  * The stats collector that process stats data and triggers updates to app.js.
16
  * @type {StatsCollector}
18
  * @type {StatsCollector}
231
     connection.emuc.doJoin(roomjid);
233
     connection.emuc.doJoin(roomjid);
232
 }
234
 }
233
 
235
 
234
-$(document).bind('remotestreamadded.jingle', function (event, data, sid) {
235
-    function waitForRemoteVideo(selector, sid, ssrc) {
236
-        if (selector.removed) {
237
-            console.warn("media removed before had started", selector);
238
-            return;
239
-        }
240
-        var sess = connection.jingle.sessions[sid];
241
-        if (data.stream.id === 'mixedmslabel') return;
242
-        var videoTracks = data.stream.getVideoTracks();
243
-//        console.log("waiting..", videoTracks, selector[0]);
244
-
245
-        if (videoTracks.length === 0 || selector[0].currentTime > 0) {
246
-            RTC.attachMediaStream(selector, data.stream); // FIXME: why do i have to do this for FF?
247
-
248
-            // FIXME: add a class that will associate peer Jid, video.src, it's ssrc and video type
249
-            //        in order to get rid of too many maps
250
-            if (ssrc) {
251
-                videoSrcToSsrc[sel.attr('src')] = ssrc;
252
-            } else {
253
-                console.warn("No ssrc given for video", sel);
254
-            }
236
+function waitForRemoteVideo(selector, ssrc, stream) {
237
+    if (selector.removed || !selector.parent().is(":visible")) {
238
+        console.warn("Media removed before had started", selector);
239
+        return;
240
+    }
241
+
242
+    if (stream.id === 'mixedmslabel') return;
255
 
243
 
256
-            $(document).trigger('callactive.jingle', [selector, sid]);
257
-            console.log('waitForremotevideo', sess.peerconnection.iceConnectionState, sess.peerconnection.signalingState);
244
+    if (selector[0].currentTime > 0) {
245
+        RTC.attachMediaStream(selector, stream); // FIXME: why do i have to do this for FF?
246
+
247
+        // FIXME: add a class that will associate peer Jid, video.src, it's ssrc and video type
248
+        //        in order to get rid of too many maps
249
+        if (ssrc && selector.attr('src')) {
250
+            videoSrcToSsrc[selector.attr('src')] = ssrc;
258
         } else {
251
         } else {
259
-            setTimeout(function () { waitForRemoteVideo(selector, sid, ssrc); }, 250);
252
+            console.warn("No ssrc given for video", selector);
260
         }
253
         }
254
+
255
+        $(document).trigger('videoactive.jingle', [selector]);
256
+    } else {
257
+        setTimeout(function () {
258
+            waitForRemoteVideo(selector, ssrc, stream);
259
+            }, 250);
261
     }
260
     }
261
+}
262
+
263
+$(document).bind('remotestreamadded.jingle', function (event, data, sid) {
262
     var sess = connection.jingle.sessions[sid];
264
     var sess = connection.jingle.sessions[sid];
263
 
265
 
264
     var thessrc;
266
     var thessrc;
265
     // look up an associated JID for a stream id
267
     // look up an associated JID for a stream id
266
     if (data.stream.id.indexOf('mixedmslabel') === -1) {
268
     if (data.stream.id.indexOf('mixedmslabel') === -1) {
267
-        var ssrclines = SDPUtil.find_lines(sess.peerconnection.remoteDescription.sdp, 'a=ssrc');
269
+        var ssrclines
270
+            = SDPUtil.find_lines(sess.peerconnection.remoteDescription.sdp, 'a=ssrc');
268
         ssrclines = ssrclines.filter(function (line) {
271
         ssrclines = ssrclines.filter(function (line) {
269
             return line.indexOf('mslabel:' + data.stream.label) !== -1;
272
             return line.indexOf('mslabel:' + data.stream.label) !== -1;
270
         });
273
         });
278
         }
281
         }
279
     }
282
     }
280
 
283
 
284
+    mediaStreams.push(new MediaStream(data, sid, thessrc));
285
+
281
     var container;
286
     var container;
282
     var remotes = document.getElementById('remoteVideos');
287
     var remotes = document.getElementById('remoteVideos');
283
 
288
 
284
     if (data.peerjid) {
289
     if (data.peerjid) {
285
         VideoLayout.ensurePeerContainerExists(data.peerjid);
290
         VideoLayout.ensurePeerContainerExists(data.peerjid);
291
+
286
         container  = document.getElementById(
292
         container  = document.getElementById(
287
                 'participant_' + Strophe.getResourceFromJid(data.peerjid));
293
                 'participant_' + Strophe.getResourceFromJid(data.peerjid));
288
     } else {
294
     } else {
295
         }
301
         }
296
         // FIXME: for the mixed ms we dont need a video -- currently
302
         // FIXME: for the mixed ms we dont need a video -- currently
297
         container = document.createElement('span');
303
         container = document.createElement('span');
304
+        container.id = 'mixedstream';
298
         container.className = 'videocontainer';
305
         container.className = 'videocontainer';
299
         remotes.appendChild(container);
306
         remotes.appendChild(container);
300
         Util.playSoundNotification('userJoined');
307
         Util.playSoundNotification('userJoined');
301
     }
308
     }
302
 
309
 
303
     var isVideo = data.stream.getVideoTracks().length > 0;
310
     var isVideo = data.stream.getVideoTracks().length > 0;
304
-    var vid = isVideo ? document.createElement('video') : document.createElement('audio');
305
-    var id = (isVideo ? 'remoteVideo_' : 'remoteAudio_') + sid + '_' + data.stream.id;
306
-
307
-    vid.id = id;
308
-    vid.autoplay = true;
309
-    vid.oncontextmenu = function () { return false; };
310
-
311
-    container.appendChild(vid);
312
 
311
 
313
-    // TODO: make mixedstream display:none via css?
314
-    if (id.indexOf('mixedmslabel') !== -1) {
315
-        container.id = 'mixedstream';
316
-        $(container).hide();
312
+    if (container) {
313
+        VideoLayout.addRemoteStreamElement( container,
314
+                                            sid,
315
+                                            data.stream,
316
+                                            data.peerjid,
317
+                                            thessrc);
317
     }
318
     }
318
 
319
 
319
-    var sel = $('#' + id);
320
-    sel.hide();
321
-    RTC.attachMediaStream(sel, data.stream);
322
-
323
-    if (isVideo) {
324
-        waitForRemoteVideo(sel, sid, thessrc);
325
-    }
326
-
327
-    data.stream.onended = function () {
328
-        console.log('stream ended', this.id);
329
-
330
-        // Mark video as removed to cancel waiting loop(if video is removed
331
-        // before has started)
332
-        sel.removed = true;
333
-        sel.remove();
334
-
335
-        var audioCount = $('#' + container.id + '>audio').length;
336
-        var videoCount = $('#' + container.id + '>video').length;
337
-        if (!audioCount && !videoCount) {
338
-            console.log("Remove whole user", container.id);
339
-            // Remove whole container
340
-            container.remove();
341
-            Util.playSoundNotification('userLeft');
342
-            VideoLayout.resizeThumbnails();
343
-        }
344
-
345
-        VideoLayout.checkChangeLargeVideo(vid.src);
346
-    };
347
-
348
-    // Add click handler.
349
-    container.onclick = function (event) {
350
-        /*
351
-         * FIXME It turns out that videoThumb may not exist (if there is no
352
-         * actual video).
353
-         */
354
-        var videoThumb = $('#' + container.id + '>video').get(0);
355
-
356
-        if (videoThumb)
357
-            VideoLayout.handleVideoThumbClicked(videoThumb.src);
358
-
359
-        event.preventDefault();
360
-        return false;
361
-    };
362
-
363
-    // Add hover handler
364
-    $(container).hover(
365
-        function() {
366
-            VideoLayout.showDisplayName(container.id, true);
367
-        },
368
-        function() {
369
-            var videoSrc = null;
370
-            if ($('#' + container.id + '>video')
371
-                    && $('#' + container.id + '>video').length > 0) {
372
-                videoSrc = $('#' + container.id + '>video').get(0).src;
373
-            }
374
-
375
-            // If the video has been "pinned" by the user we want to keep the
376
-            // display name on place.
377
-            if (!VideoLayout.isLargeVideoVisible()
378
-                    || videoSrc !== $('#largeVideo').attr('src'))
379
-                VideoLayout.showDisplayName(container.id, false);
380
-        }
381
-    );
382
-
383
     // an attempt to work around https://github.com/jitsi/jitmeet/issues/32
320
     // an attempt to work around https://github.com/jitsi/jitmeet/issues/32
384
     if (isVideo &&
321
     if (isVideo &&
385
         data.peerjid && sess.peerjid === data.peerjid &&
322
         data.peerjid && sess.peerjid === data.peerjid &&
587
     }
524
     }
588
 });
525
 });
589
 
526
 
590
-$(document).bind('callactive.jingle', function (event, videoelem, sid) {
591
-    if (videoelem.attr('id').indexOf('mixedmslabel') === -1) {
592
-        // ignore mixedmslabela0 and v0
593
-        videoelem.show();
594
-        VideoLayout.resizeThumbnails();
595
-
596
-        // Update the large video to the last added video only if there's no
597
-        // current active or focused speaker.
598
-        if (!focusedVideoSrc && !VideoLayout.getDominantSpeakerResourceJid())
599
-            VideoLayout.updateLargeVideo(videoelem.attr('src'), 1);
600
-
601
-        VideoLayout.showFocusIndicator();
602
-    }
603
-});
604
-
605
 $(document).bind('callterminated.jingle', function (event, sid, jid, reason) {
527
 $(document).bind('callterminated.jingle', function (event, sid, jid, reason) {
606
     // Leave the room if my call has been remotely terminated.
528
     // Leave the room if my call has been remotely terminated.
607
     if (connection.emuc.joined && focus == null && reason === 'kick') {
529
     if (connection.emuc.joined && focus == null && reason === 'kick') {
680
 
602
 
681
     VideoLayout.showFocusIndicator();
603
     VideoLayout.showFocusIndicator();
682
 
604
 
605
+    // Add myself to the contact list.
606
+    ContactList.addContact(jid);
607
+
683
     // Once we've joined the muc show the toolbar
608
     // Once we've joined the muc show the toolbar
684
     Toolbar.showToolbar();
609
     Toolbar.showToolbar();
685
 
610
 
686
     var displayName = '';
611
     var displayName = '';
687
     if (info.displayName)
612
     if (info.displayName)
688
         displayName = info.displayName + ' (me)';
613
         displayName = info.displayName + ' (me)';
614
+    else
615
+        displayName = "Me";
689
 
616
 
690
-    VideoLayout.setDisplayName('localVideoContainer', displayName);
617
+    $(document).trigger('displaynamechanged',
618
+                        ['localVideoContainer', displayName]);
691
 });
619
 });
692
 
620
 
693
 $(document).bind('entered.muc', function (event, jid, info, pres) {
621
 $(document).bind('entered.muc', function (event, jid, info, pres) {
811
             case 'recvonly':
739
             case 'recvonly':
812
                 el.hide();
740
                 el.hide();
813
                 // FIXME: Check if we have to change large video
741
                 // FIXME: Check if we have to change large video
814
-                //VideoLayout.checkChangeLargeVideo(el);
742
+                //VideoLayout.updateLargeVideo(el);
815
                 break;
743
                 break;
816
             }
744
             }
817
         }
745
         }
818
     });
746
     });
819
 
747
 
820
-    if (jid === connection.emuc.myroomjid) {
821
-        VideoLayout.setDisplayName('localVideoContainer',
822
-                                    info.displayName);
823
-    } else {
824
-        VideoLayout.ensurePeerContainerExists(jid);
825
-        VideoLayout.setDisplayName(
826
-                'participant_' + Strophe.getResourceFromJid(jid),
827
-                info.displayName);
828
-    }
748
+    if (info.displayName && info.displayName.length > 0)
749
+        $(document).trigger('displaynamechanged',
750
+                            [jid, info.displayName]);
829
 
751
 
830
     if (focus !== null && info.displayName !== null) {
752
     if (focus !== null && info.displayName !== null) {
831
         focus.setEndpointDisplayName(jid, info.displayName);
753
         focus.setEndpointDisplayName(jid, info.displayName);
1370
 //    }
1292
 //    }
1371
 }
1293
 }
1372
 
1294
 
1373
-
1295
+function hangUp() {
1296
+    if (connection && connection.connected) {
1297
+        // ensure signout
1298
+        $.ajax({
1299
+            type: 'POST',
1300
+            url: config.bosh,
1301
+            async: false,
1302
+            cache: false,
1303
+            contentType: 'application/xml',
1304
+            data: "<body rid='" + (connection.rid || connection._proto.rid) + "' xmlns='http://jabber.org/protocol/httpbind' sid='" + (connection.sid || connection._proto.sid) + "' type='terminate'><presence xmlns='jabber:client' type='unavailable'/></body>",
1305
+            success: function (data) {
1306
+                console.log('signed out');
1307
+                console.log(data);
1308
+            },
1309
+            error: function (XMLHttpRequest, textStatus, errorThrown) {
1310
+                console.log('signout error', textStatus + ' (' + errorThrown + ')');
1311
+            }
1312
+        });
1313
+    }
1314
+    disposeConference(true);
1315
+}
1374
 
1316
 
1375
 $(document).bind('fatalError.jingle',
1317
 $(document).bind('fatalError.jingle',
1376
     function (event, session, error)
1318
     function (event, session, error)

+ 32
- 0
bottom_toolbar.js ファイルの表示

1
+var BottomToolbar = (function (my) {
2
+    my.toggleChat = function() {
3
+        if (ContactList.isVisible()) {
4
+            buttonClick("#contactListButton", "active");
5
+            ContactList.toggleContactList();
6
+        }
7
+
8
+        buttonClick("#chatBottomButton", "active");
9
+
10
+        Chat.toggleChat();
11
+    };
12
+
13
+    my.toggleContactList = function() {
14
+        if (Chat.isVisible()) {
15
+            buttonClick("#chatBottomButton", "active");
16
+            Chat.toggleChat();
17
+        }
18
+
19
+        buttonClick("#contactListButton", "active");
20
+
21
+        ContactList.toggleContactList();
22
+    };
23
+
24
+
25
+    $(document).bind("remotevideo.resized", function (event, width, height) {
26
+        var bottom = (height - $('#bottomToolbar').outerHeight())/2 + 18;
27
+
28
+        $('#bottomToolbar').css({bottom: bottom + 'px'});
29
+    });
30
+
31
+    return my;
32
+}(BottomToolbar || {}));

+ 8
- 1
chat.js ファイルの表示

80
         else {
80
         else {
81
             divClassName = "remoteuser";
81
             divClassName = "remoteuser";
82
 
82
 
83
-            if (!$('#chatspace').is(":visible")) {
83
+            if (!Chat.isVisible()) {
84
                 unreadMessages++;
84
                 unreadMessages++;
85
                 Util.playSoundNotification('chatNotification');
85
                 Util.playSoundNotification('chatNotification');
86
                 setVisualNotification(true);
86
                 setVisualNotification(true);
301
         return [chatWidth, availableHeight];
301
         return [chatWidth, availableHeight];
302
     };
302
     };
303
 
303
 
304
+    /**
305
+     * Indicates if the chat is currently visible.
306
+     */
307
+    my.isVisible = function () {
308
+        return $('#chatspace').is(":visible");
309
+    };
310
+
304
     /**
311
     /**
305
      * Resizes the chat conversation.
312
      * Resizes the chat conversation.
306
      */
313
      */

+ 1
- 1
config.js ファイルの表示

16
     minChromeExtVersion: '0.1', // Required version of Chrome extension
16
     minChromeExtVersion: '0.1', // Required version of Chrome extension
17
     enableRtpStats: true, // Enables RTP stats processing
17
     enableRtpStats: true, // Enables RTP stats processing
18
     openSctp: true, // Toggle to enable/disable SCTP channels
18
     openSctp: true, // Toggle to enable/disable SCTP channels
19
-//    channelLastN: -1, // The default value of the channel attribute last-n.
19
+    channelLastN: -1, // The default value of the channel attribute last-n.
20
 //    useRtcpMux: true,
20
 //    useRtcpMux: true,
21
 //    useBundle: true,
21
 //    useBundle: true,
22
     enableRecording: false,
22
     enableRecording: false,

+ 235
- 0
contact_list.js ファイルの表示

1
+/**
2
+ * Contact list.
3
+ */
4
+var ContactList = (function (my) {
5
+    /**
6
+     * Indicates if the chat is currently visible.
7
+     *
8
+     * @return <tt>true</tt> if the chat is currently visible, <tt>false</tt> -
9
+     * otherwise
10
+     */
11
+    my.isVisible = function () {
12
+        return $('#contactlist').is(":visible");
13
+    };
14
+
15
+    /**
16
+     * Adds a contact for the given peerJid if such doesn't yet exist.
17
+     *
18
+     * @param peerJid the peerJid corresponding to the contact
19
+     */
20
+    my.ensureAddContact = function(peerJid) {
21
+        var resourceJid = Strophe.getResourceFromJid(peerJid);
22
+
23
+        var contact = $('#contactlist>ul>li[id="' + resourceJid + '"]');
24
+
25
+        if (!contact || contact.length <= 0)
26
+            ContactList.addContact(peerJid);
27
+    };
28
+
29
+    /**
30
+     * Adds a contact for the given peer jid.
31
+     *
32
+     * @param peerJid the jid of the contact to add
33
+     */
34
+    my.addContact = function(peerJid) {
35
+        var resourceJid = Strophe.getResourceFromJid(peerJid);
36
+
37
+        var contactlist = $('#contactlist>ul');
38
+
39
+        var newContact = document.createElement('li');
40
+        newContact.id = resourceJid;
41
+
42
+        newContact.appendChild(createAvatar());
43
+        newContact.appendChild(createDisplayNameParagraph("Participant"));
44
+
45
+        var clElement = contactlist.get(0);
46
+
47
+        if (resourceJid === Strophe.getResourceFromJid(connection.emuc.myroomjid)
48
+            && $('#contactlist>ul .title')[0].nextSibling.nextSibling)
49
+        {
50
+            clElement.insertBefore(newContact,
51
+                    $('#contactlist>ul .title')[0].nextSibling.nextSibling);
52
+        }
53
+        else {
54
+            clElement.appendChild(newContact);
55
+        }
56
+    };
57
+
58
+    /**
59
+     * Removes a contact for the given peer jid.
60
+     *
61
+     * @param peerJid the peerJid corresponding to the contact to remove
62
+     */
63
+    my.removeContact = function(peerJid) {
64
+        var resourceJid = Strophe.getResourceFromJid(peerJid);
65
+
66
+        var contact = $('#contactlist>ul>li[id="' + resourceJid + '"]');
67
+
68
+        if (contact && contact.length > 0) {
69
+            var contactlist = $('#contactlist>ul');
70
+
71
+            contactlist.get(0).removeChild(contact.get(0));
72
+        }
73
+    };
74
+
75
+    /**
76
+     * Opens / closes the contact list area.
77
+     */
78
+    my.toggleContactList = function () {
79
+        var contactlist = $('#contactlist');
80
+        var videospace = $('#videospace');
81
+
82
+        var chatSize = (ContactList.isVisible()) ? [0, 0] : Chat.getChatSize();
83
+        var videospaceWidth = window.innerWidth - chatSize[0];
84
+        var videospaceHeight = window.innerHeight;
85
+        var videoSize
86
+            = getVideoSize(null, null, videospaceWidth, videospaceHeight);
87
+        var videoWidth = videoSize[0];
88
+        var videoHeight = videoSize[1];
89
+        var videoPosition = getVideoPosition(videoWidth,
90
+                                             videoHeight,
91
+                                             videospaceWidth,
92
+                                             videospaceHeight);
93
+        var horizontalIndent = videoPosition[0];
94
+        var verticalIndent = videoPosition[1];
95
+
96
+        var thumbnailSize = VideoLayout.calculateThumbnailSize(videospaceWidth);
97
+        var thumbnailsWidth = thumbnailSize[0];
98
+        var thumbnailsHeight = thumbnailSize[1];
99
+
100
+        if (ContactList.isVisible()) {
101
+            videospace.animate({right: chatSize[0],
102
+                                width: videospaceWidth,
103
+                                height: videospaceHeight},
104
+                                {queue: false,
105
+                                duration: 500});
106
+
107
+            $('#remoteVideos').animate({height: thumbnailsHeight},
108
+                                        {queue: false,
109
+                                        duration: 500});
110
+
111
+            $('#remoteVideos>span').animate({height: thumbnailsHeight,
112
+                                            width: thumbnailsWidth},
113
+                                            {queue: false,
114
+                                            duration: 500,
115
+                                            complete: function() {
116
+                                                $(document).trigger(
117
+                                                        "remotevideo.resized",
118
+                                                        [thumbnailsWidth,
119
+                                                         thumbnailsHeight]);
120
+                                            }});
121
+
122
+            $('#largeVideoContainer').animate({ width: videospaceWidth,
123
+                                                height: videospaceHeight},
124
+                                                {queue: false,
125
+                                                 duration: 500
126
+                                                });
127
+
128
+            $('#largeVideo').animate({  width: videoWidth,
129
+                                        height: videoHeight,
130
+                                        top: verticalIndent,
131
+                                        bottom: verticalIndent,
132
+                                        left: horizontalIndent,
133
+                                        right: horizontalIndent},
134
+                                        {   queue: false,
135
+                                            duration: 500
136
+                                        });
137
+
138
+            $('#contactlist').hide("slide", { direction: "right",
139
+                                            queue: false,
140
+                                            duration: 500});
141
+        }
142
+        else {
143
+            // Undock the toolbar when the chat is shown and if we're in a 
144
+            // video mode.
145
+            if (VideoLayout.isLargeVideoVisible())
146
+                Toolbar.dockToolbar(false);
147
+
148
+            videospace.animate({right: chatSize[0],
149
+                                width: videospaceWidth,
150
+                                height: videospaceHeight},
151
+                               {queue: false,
152
+                                duration: 500,
153
+                                complete: function () {
154
+                                    contactlist.trigger('shown');
155
+                                }
156
+                               });
157
+
158
+            $('#remoteVideos').animate({height: thumbnailsHeight},
159
+                    {queue: false,
160
+                    duration: 500});
161
+
162
+            $('#remoteVideos>span').animate({height: thumbnailsHeight,
163
+                        width: thumbnailsWidth},
164
+                        {queue: false,
165
+                        duration: 500,
166
+                        complete: function() {
167
+                            $(document).trigger(
168
+                                    "remotevideo.resized",
169
+                                    [thumbnailsWidth, thumbnailsHeight]);
170
+                        }});
171
+
172
+            $('#largeVideoContainer').animate({ width: videospaceWidth,
173
+                                                height: videospaceHeight},
174
+                                                {queue: false,
175
+                                                 duration: 500
176
+                                                });
177
+
178
+            $('#largeVideo').animate({  width: videoWidth,
179
+                                        height: videoHeight,
180
+                                        top: verticalIndent,
181
+                                        bottom: verticalIndent,
182
+                                        left: horizontalIndent,
183
+                                        right: horizontalIndent},
184
+                                        {queue: false,
185
+                                         duration: 500
186
+                                        });
187
+
188
+            $('#contactlist').show("slide", { direction: "right",
189
+                                            queue: false,
190
+                                            duration: 500});
191
+        }
192
+    };
193
+
194
+    /**
195
+     * Creates the avatar element.
196
+     * 
197
+     * @return the newly created avatar element
198
+     */
199
+    function createAvatar() {
200
+        var avatar = document.createElement('i');
201
+        avatar.className = "icon-avatar avatar";
202
+
203
+        return avatar;
204
+    };
205
+
206
+    /**
207
+     * Creates the display name paragraph.
208
+     *
209
+     * @param displayName the display name to set
210
+     */
211
+    function createDisplayNameParagraph(displayName) {
212
+        var p = document.createElement('p');
213
+        p.innerHTML = displayName;
214
+
215
+        return p;
216
+    };
217
+
218
+    /**
219
+     * Indicates that the display name has changed.
220
+     */
221
+    $(document).bind(   'displaynamechanged',
222
+                        function (event, peerJid, displayName) {
223
+        if (peerJid === 'localVideoContainer')
224
+            peerJid = connection.emuc.myroomjid;
225
+
226
+        var resourceJid = Strophe.getResourceFromJid(peerJid);
227
+
228
+        var contactName = $('#contactlist #' + resourceJid + '>p');
229
+
230
+        if (contactName && displayName && displayName.length > 0)
231
+            contactName.html(displayName);
232
+    });
233
+
234
+    return my;
235
+}(ContactList || {}));

+ 35
- 0
css/contact_list.css ファイルの表示

1
+#contactlist {
2
+    background-color:rgba(0,0,0,.65);
3
+}
4
+
5
+#contactlist>ul {
6
+    margin: 0px;
7
+    padding: 0px;
8
+}
9
+
10
+#contactlist>ul>li {
11
+    list-style-type: none;
12
+    text-align: left;
13
+    color: #FFF;
14
+    font-size: 10pt;
15
+    padding: 8px 10px;
16
+}
17
+
18
+#contactlist>ul>li>p {
19
+    display: inline-block;
20
+    vertical-align: middle;
21
+    margin: 0px;
22
+}
23
+
24
+#contactlist>ul>li.title {
25
+    color: #00ccff;
26
+    font-size: 11pt;
27
+    border-bottom: 1px solid #676767;
28
+}
29
+
30
+.avatar {
31
+    padding: 0px;
32
+    margin-right: 10px;
33
+    vertical-align: middle;
34
+    font-size: 22pt;
35
+}

+ 6
- 0
css/font.css ファイルの表示

23
     -webkit-font-smoothing: antialiased;
23
     -webkit-font-smoothing: antialiased;
24
     -moz-osx-font-smoothing: grayscale;
24
     -moz-osx-font-smoothing: grayscale;
25
 }
25
 }
26
+.icon-contactList:before {
27
+    content: "\e615";
28
+}
29
+.icon-avatar:before {
30
+    content: "\e616";
31
+}
26
 .icon-callRetro:before {
32
 .icon-callRetro:before {
27
     content: "\e611";
33
     content: "\e611";
28
 }
34
 }

+ 51
- 5
css/main.css ファイルの表示

8
     overflow-x: hidden;
8
     overflow-x: hidden;
9
 }
9
 }
10
 
10
 
11
-#chatspace {
11
+#chatspace,
12
+#contactlist {
12
     display:none;
13
     display:none;
13
     position:absolute;
14
     position:absolute;
14
     float: right;
15
     float: right;
18
     width: 20%;
19
     width: 20%;
19
     max-width: 200px;
20
     max-width: 200px;
20
     overflow: hidden;
21
     overflow: hidden;
21
-    /* background-color:#dfebf1;*/
22
-    background-color:#FFFFFF;
23
-    border-left:1px solid #424242;
24
     z-index: 5;
22
     z-index: 5;
25
 }
23
 }
26
 
24
 
25
+#chatspace {
26
+    background-color:#FFF;
27
+    border-left:1px solid #424242;
28
+}
29
+
27
 #chatconversation {
30
 #chatconversation {
28
     visibility: hidden;
31
     visibility: hidden;
29
     position: relative;
32
     position: relative;
172
     0 -1px 10px #00ccff;
175
     0 -1px 10px #00ccff;
173
 }
176
 }
174
 
177
 
175
-a.button:hover {
178
+a.button:hover,
179
+a.bottomToolbarButton:hover {
176
     top: 0;
180
     top: 0;
177
     cursor: pointer;
181
     cursor: pointer;
178
     background: rgba(0, 0, 0, 0.3);
182
     background: rgba(0, 0, 0, 0.3);
408
     font-weight: 200;
412
     font-weight: 200;
409
 }
413
 }
410
 
414
 
415
+#bottomToolbar {
416
+    display:block;
417
+    position: absolute;
418
+    right: -1;
419
+    bottom: 40px;
420
+    width: 29px;
421
+    border-top-left-radius: 10px;
422
+    border-bottom-left-radius: 10px;
423
+    color: #FFF;
424
+    border: 1px solid #000;
425
+    background: rgba(50,50,50,.65);
426
+    padding-top: 5px;
427
+    padding-bottom: 5px;
428
+    z-index: 6; /*+1 from #remoteVideos*/
429
+}
430
+
431
+.bottomToolbarButton {
432
+    display: inline-block;
433
+    position: relative;
434
+    color: #FFFFFF;
435
+    top: 0;
436
+    padding-top: 3px;
437
+    width: 29px;
438
+    height: 20px;
439
+    cursor: pointer;
440
+    font-size: 10pt;
441
+    text-align: center;
442
+    text-shadow: 0px 1px 0px rgba(255,255,255,.3), 0px -1px 0px rgba(0,0,0,.7);
443
+    z-index: 1;
444
+}
411
 
445
 
446
+.active {
447
+    color: #00ccff;
448
+}
449
+
450
+.bottomToolbar_span>span {
451
+    display: inline-block;
452
+    position: absolute;
453
+    font-size: 7pt;
454
+    color: #ffffff;
455
+    text-align:center;
456
+    cursor: pointer;
457
+}

+ 5
- 1
css/videolayout_default.css ファイルの表示

15
     padding: 18px;
15
     padding: 18px;
16
     bottom: 0;
16
     bottom: 0;
17
     left: 0;
17
     left: 0;
18
-    right: 0;
18
+    right: 20px;
19
     width:auto;
19
     width:auto;
20
     border:1px solid transparent;
20
     border:1px solid transparent;
21
     z-index: 5;
21
     z-index: 5;
334
     z-index: 0;
334
     z-index: 0;
335
     border-radius:10px;
335
     border-radius:10px;
336
 }
336
 }
337
+
338
+#mixedstream {
339
+    display:none !important;
340
+}

+ 8
- 2
data_channels.js ファイルの表示

74
                  */
74
                  */
75
                 var endpointsEnteringLastN = obj.endpointsEnteringLastN;
75
                 var endpointsEnteringLastN = obj.endpointsEnteringLastN;
76
 
76
 
77
-                console.debug(
77
+                var stream = obj.stream;
78
+
79
+                console.log(
78
                     "Data channel new last-n event: ",
80
                     "Data channel new last-n event: ",
79
-                    lastNEndpoints);
81
+                    lastNEndpoints, endpointsEnteringLastN, obj);
82
+
83
+                $(document).trigger(
84
+                        'lastnchanged',
85
+                        [lastNEndpoints, endpointsEnteringLastN, stream]);
80
             }
86
             }
81
             else
87
             else
82
             {
88
             {

バイナリ
fonts/jitsi.eot ファイルの表示


+ 2
- 0
fonts/jitsi.svg ファイルの表示

28
 <glyph unicode="&#xe612;" d="M155.131 15.215c0-26.065-21.103-47.215-47.2-47.215h-60.703c-26.098 0-47.229 21.15-47.229 47.215v417.835c0 26.079 21.133 47.229 47.229 47.229h60.701c26.097 0 47.2-21.15 47.2-47.229v-417.835zM538.559 480.28h-280.993c-36.459 0-66.165-30.337-66.165-67.626v-377.058c0-37.259 29.706-67.596 66.165-67.596h280.993c36.49 0 66.197 30.337 66.197 67.596v377.058c0 37.29-29.707 67.626-66.197 67.626zM264.915 413.453h266.327l0.031-71.649h-266.358v71.649zM321.627 25.814h-56.776v56.776h56.776v-56.776zM321.627 128.374h-56.776v56.777h56.776v-56.777zM321.691 231.878h-56.776v56.777h56.776v-56.777zM426.45 25.814h-56.776v56.776h56.776v-56.776zM426.45 128.374h-56.776v56.777h56.776v-56.777zM426.514 231.878h-56.778v56.777h56.778v-56.777zM531.274 25.814h-56.778v56.776h56.778v-56.776zM531.274 128.374h-56.778v56.777h56.778v-56.777zM531.335 231.878h-56.777v56.777h56.777v-56.777z" horiz-adv-x="605" />
28
 <glyph unicode="&#xe612;" d="M155.131 15.215c0-26.065-21.103-47.215-47.2-47.215h-60.703c-26.098 0-47.229 21.15-47.229 47.215v417.835c0 26.079 21.133 47.229 47.229 47.229h60.701c26.097 0 47.2-21.15 47.2-47.229v-417.835zM538.559 480.28h-280.993c-36.459 0-66.165-30.337-66.165-67.626v-377.058c0-37.259 29.706-67.596 66.165-67.596h280.993c36.49 0 66.197 30.337 66.197 67.596v377.058c0 37.29-29.707 67.626-66.197 67.626zM264.915 413.453h266.327l0.031-71.649h-266.358v71.649zM321.627 25.814h-56.776v56.776h56.776v-56.776zM321.627 128.374h-56.776v56.777h56.776v-56.777zM321.691 231.878h-56.776v56.777h56.776v-56.777zM426.45 25.814h-56.776v56.776h56.776v-56.776zM426.45 128.374h-56.776v56.777h56.776v-56.777zM426.514 231.878h-56.778v56.777h56.778v-56.777zM531.274 25.814h-56.778v56.776h56.778v-56.776zM531.274 128.374h-56.778v56.777h56.778v-56.777zM531.335 231.878h-56.777v56.777h56.777v-56.777z" horiz-adv-x="605" />
29
 <glyph unicode="&#xe613;" d="M561.722 469.507c-11.797 13.24-32.066 14.495-45.37 2.697l-504.135-446.718c-13.305-11.734-14.495-32.065-2.73-45.338 6.337-7.153 15.154-10.825 24.063-10.825 7.562 0 15.186 2.7 21.272 8.098l65.023 57.61c45.371-40.922 105.284-66.082 171.237-66.050 141.408 0.031 255.457 113.985 255.644 255.486 0.063 54.967-17.341 105.683-46.75 147.36l59.044 52.313c13.241 11.763 14.499 32.065 2.702 45.368zM472.211 224.909c0.064-100.461-80.948-181.601-181.255-181.476-43.78 0.062-83.786 15.575-115.031 41.284l165.638 146.755v-36.588c0.536-30.497 16.348-46.090 47.472-46.846 30.998 0.756 46.843 16.382 47.565 46.878v20.548h-36.113v-23.75c0.125-2.321-0.282-5.303-1.255-8.974-0.625-1.63-1.724-3.010-3.262-4.046-1.599-1.286-3.923-1.914-6.934-1.914-5.272 0.155-8.566 2.134-9.913 5.961-0.534 1.756-0.974 3.452-1.254 5.082-0.127 1.443-0.188 2.766-0.188 3.893v71.755l21.238 18.817c0.108-0.216 0.226-0.425 0.315-0.651 0.974-3.233 1.381-6.399 1.255-9.538v-18.386h36.113v15.060c-0.123 15.623-4.543 27.35-13.181 35.224l20.356 18.034c17.894-28.028 28.401-61.293 28.433-97.122zM119.897 165.735c-6.306 18.512-9.913 38.279-9.913 58.957-0.095 100.118 80.792 181.005 180.973 181.067 28.426 0 55.156-6.651 79.067-18.199l58.923 52.211c-39.722 25.476-86.879 40.409-137.646 40.474-141.689 0.091-255.677-113.865-255.894-255.838-0.063-39.783 9.318-77.34 25.569-110.941l58.924 52.269zM194.288 313.010h-48.757v-124.529l36.115 32.035v0.344h0.407l58.86 52.209c0 0.282 0.061 0.47 0.061 0.755 0.376 26.949-15.184 40.034-46.687 39.185zM202.98 280.759c0.971-1.384 1.537-3.235 1.661-5.556 0.156-2.226 0.219-4.8 0.219-7.623 0.125-5.458-0.345-9.915-1.475-13.493-1.476-3.797-5.492-5.678-12.079-5.678h-9.662v37.022h7.655c3.921 0 6.933-0.341 9.036-1.095 2.198-0.787 3.734-1.976 4.645-3.577z" horiz-adv-x="570" />
29
 <glyph unicode="&#xe613;" d="M561.722 469.507c-11.797 13.24-32.066 14.495-45.37 2.697l-504.135-446.718c-13.305-11.734-14.495-32.065-2.73-45.338 6.337-7.153 15.154-10.825 24.063-10.825 7.562 0 15.186 2.7 21.272 8.098l65.023 57.61c45.371-40.922 105.284-66.082 171.237-66.050 141.408 0.031 255.457 113.985 255.644 255.486 0.063 54.967-17.341 105.683-46.75 147.36l59.044 52.313c13.241 11.763 14.499 32.065 2.702 45.368zM472.211 224.909c0.064-100.461-80.948-181.601-181.255-181.476-43.78 0.062-83.786 15.575-115.031 41.284l165.638 146.755v-36.588c0.536-30.497 16.348-46.090 47.472-46.846 30.998 0.756 46.843 16.382 47.565 46.878v20.548h-36.113v-23.75c0.125-2.321-0.282-5.303-1.255-8.974-0.625-1.63-1.724-3.010-3.262-4.046-1.599-1.286-3.923-1.914-6.934-1.914-5.272 0.155-8.566 2.134-9.913 5.961-0.534 1.756-0.974 3.452-1.254 5.082-0.127 1.443-0.188 2.766-0.188 3.893v71.755l21.238 18.817c0.108-0.216 0.226-0.425 0.315-0.651 0.974-3.233 1.381-6.399 1.255-9.538v-18.386h36.113v15.060c-0.123 15.623-4.543 27.35-13.181 35.224l20.356 18.034c17.894-28.028 28.401-61.293 28.433-97.122zM119.897 165.735c-6.306 18.512-9.913 38.279-9.913 58.957-0.095 100.118 80.792 181.005 180.973 181.067 28.426 0 55.156-6.651 79.067-18.199l58.923 52.211c-39.722 25.476-86.879 40.409-137.646 40.474-141.689 0.091-255.677-113.865-255.894-255.838-0.063-39.783 9.318-77.34 25.569-110.941l58.924 52.269zM194.288 313.010h-48.757v-124.529l36.115 32.035v0.344h0.407l58.86 52.209c0 0.282 0.061 0.47 0.061 0.755 0.376 26.949-15.184 40.034-46.687 39.185zM202.98 280.759c0.971-1.384 1.537-3.235 1.661-5.556 0.156-2.226 0.219-4.8 0.219-7.623 0.125-5.458-0.345-9.915-1.475-13.493-1.476-3.797-5.492-5.678-12.079-5.678h-9.662v37.022h7.655c3.921 0 6.933-0.341 9.036-1.095 2.198-0.787 3.734-1.976 4.645-3.577z" horiz-adv-x="570" />
30
 <glyph unicode="&#xe614;" d="M290.639 480.854c142.428-0.095 257.404-115.258 257.213-257.498-0.189-142.524-115.036-257.277-257.435-257.308-142.27-0.031-257.656 115.259-257.466 257.211 0.219 142.968 115.005 257.719 257.688 257.595zM290.289 405.878c-100.882-0.061-182.333-81.516-182.239-182.332 0-101.009 81.262-182.368 182.239-182.492 101.009-0.158 182.587 81.515 182.524 182.712-0.126 100.884-81.578 182.175-182.524 182.112zM143.849 312.453h49.098c31.721 0.884 47.392-12.259 47.013-39.431 0.127-9.541-1.106-17.441-3.728-23.761-3.002-6.254-9.353-10.994-19.083-14.090v-0.41c14.186-3.13 21.516-11.787 21.99-25.973v-28.844c0-5.623 0.127-11.406 0.379-17.378 0.41-6.002 1.517-10.49 3.348-13.522h-35.923c-1.863 3.032-3.064 7.519-3.57 13.522-0.506 5.971-0.727 11.755-0.569 17.378v26.161c0 4.801-1.107 8.276-3.286 10.49-2.338 2.053-6.351 3.095-12.006 3.095h-7.299v-70.645h-36.365v163.41zM180.214 247.43h9.732c6.636 0 10.679 1.897 12.166 5.688 1.138 3.602 1.611 8.152 1.484 13.585 0 2.908-0.063 5.434-0.221 7.709-0.126 2.337-0.696 4.202-1.676 5.623-0.916 1.579-2.463 2.781-4.676 3.57-2.117 0.727-5.149 1.106-9.1 1.106h-7.709v-37.282zM249.186 312.453h81.041v-31.343h-44.675v-32.794h39.051v-31.343h-39.051v-36.555h46.411v-31.375h-82.779v163.409zM341.253 268c0.158 15.891 4.708 27.771 13.712 35.703 8.72 7.645 20.093 11.468 34.091 11.468 14.123 0 25.56-3.823 34.312-11.5 8.91-7.899 13.46-19.811 13.586-35.704v-15.166h-36.365v18.516c0.127 3.096-0.285 6.319-1.264 9.604-0.632 1.579-1.738 2.969-3.286 4.107-1.611 0.757-3.949 1.202-6.982 1.202-5.308-0.158-8.625-1.928-9.983-5.309-1.106-3.286-1.611-6.508-1.454-9.604v-80.978c0-1.137 0.063-2.433 0.19-3.886 0.284-1.705 0.727-3.412 1.264-5.116 1.358-3.887 4.676-5.878 9.983-6.003 3.034 0 5.372 0.63 6.982 1.896 1.549 1.075 2.654 2.433 3.286 4.108 0.98 3.665 1.391 6.665 1.264 9.003v23.918h36.365v-20.664c-0.726-30.774-16.682-46.507-47.897-47.235-31.342 0.727-47.265 16.461-47.803 47.171v74.469z" horiz-adv-x="571" />
30
 <glyph unicode="&#xe614;" d="M290.639 480.854c142.428-0.095 257.404-115.258 257.213-257.498-0.189-142.524-115.036-257.277-257.435-257.308-142.27-0.031-257.656 115.259-257.466 257.211 0.219 142.968 115.005 257.719 257.688 257.595zM290.289 405.878c-100.882-0.061-182.333-81.516-182.239-182.332 0-101.009 81.262-182.368 182.239-182.492 101.009-0.158 182.587 81.515 182.524 182.712-0.126 100.884-81.578 182.175-182.524 182.112zM143.849 312.453h49.098c31.721 0.884 47.392-12.259 47.013-39.431 0.127-9.541-1.106-17.441-3.728-23.761-3.002-6.254-9.353-10.994-19.083-14.090v-0.41c14.186-3.13 21.516-11.787 21.99-25.973v-28.844c0-5.623 0.127-11.406 0.379-17.378 0.41-6.002 1.517-10.49 3.348-13.522h-35.923c-1.863 3.032-3.064 7.519-3.57 13.522-0.506 5.971-0.727 11.755-0.569 17.378v26.161c0 4.801-1.107 8.276-3.286 10.49-2.338 2.053-6.351 3.095-12.006 3.095h-7.299v-70.645h-36.365v163.41zM180.214 247.43h9.732c6.636 0 10.679 1.897 12.166 5.688 1.138 3.602 1.611 8.152 1.484 13.585 0 2.908-0.063 5.434-0.221 7.709-0.126 2.337-0.696 4.202-1.676 5.623-0.916 1.579-2.463 2.781-4.676 3.57-2.117 0.727-5.149 1.106-9.1 1.106h-7.709v-37.282zM249.186 312.453h81.041v-31.343h-44.675v-32.794h39.051v-31.343h-39.051v-36.555h46.411v-31.375h-82.779v163.409zM341.253 268c0.158 15.891 4.708 27.771 13.712 35.703 8.72 7.645 20.093 11.468 34.091 11.468 14.123 0 25.56-3.823 34.312-11.5 8.91-7.899 13.46-19.811 13.586-35.704v-15.166h-36.365v18.516c0.127 3.096-0.285 6.319-1.264 9.604-0.632 1.579-1.738 2.969-3.286 4.107-1.611 0.757-3.949 1.202-6.982 1.202-5.308-0.158-8.625-1.928-9.983-5.309-1.106-3.286-1.611-6.508-1.454-9.604v-80.978c0-1.137 0.063-2.433 0.19-3.886 0.284-1.705 0.727-3.412 1.264-5.116 1.358-3.887 4.676-5.878 9.983-6.003 3.034 0 5.372 0.63 6.982 1.896 1.549 1.075 2.654 2.433 3.286 4.108 0.98 3.665 1.391 6.665 1.264 9.003v23.918h36.365v-20.664c-0.726-30.774-16.682-46.507-47.897-47.235-31.342 0.727-47.265 16.461-47.803 47.171v74.469z" horiz-adv-x="571" />
31
+<glyph unicode="&#xe615;" d="M508.412 2.883c-1.026 7.687-2.666 15.269-3.93 22.923-4.167 25.229-16.503 43.252-41.031 53.961-39.187 17.099-77.551 36.060-116.055 54.697-27.843 13.512-26.204 44.26-17.048 57.207 5.945 8.44 11.172 17.286 11.788 28.426 0.222 4.113 4.151 9.495 7.909 11.647 13.035 7.518 19.081 19.782 25.010 32.491 1.555 3.348 3.69 6.594 6.133 9.361 4.236 4.834 6.132 9.618 3.039 15.921-0.717 1.485 0.666 4.167 1.4 6.183 2.152 6.013 5.142 11.838 6.56 18.022 1.778 7.669 2.699 15.612 3.126 23.487 0.187 3.262-3.022 6.764-2.681 9.975 1.741 15.956-7.279 28.101-12.37 41.988-6.233 17.099-18.464 27.81-29.26 40.553-2.033 2.392-2.613 6.526-2.786 9.943-0.36 7.294-3.366 10.898-11.002 9.906-3.055-0.394-6.386-1.248-9.205-0.496-2.478 0.667-6.203 3.144-6.338 5.056-0.769 9.668-4.132 11.258-14.008 9.618-6.182-1.025-14.228 4.577-20.292 8.78-5.072 3.521-9.445 5.023-15.341 3.588-2.457-0.598-5.772-0.495-7.858 0.717-2.221 1.332-4.387 2.119-6.559 2.562v0.374c-0.478-0.016-0.991-0.102-1.469-0.154-0.477 0.051-0.956 0.137-1.434 0.154v-0.375c-2.185-0.444-4.375-1.231-6.578-2.562-2.066-1.213-5.381-1.316-7.84-0.718-5.911 1.434-10.285-0.068-15.342-3.588-6.079-4.202-14.108-9.805-20.292-8.781-9.873 1.641-13.255 0.052-14.024-9.618-0.154-1.912-3.843-4.389-6.338-5.056-2.834-0.752-6.149 0.102-9.223 0.495-7.618 0.992-10.625-2.613-10.985-9.906-0.169-3.416-0.751-7.551-2.784-9.943-10.794-12.743-23.025-23.454-29.278-40.553-5.058-13.886-14.094-26.031-12.335-41.987 0.343-3.211-2.872-6.714-2.7-9.975 0.445-7.875 1.368-15.818 3.127-23.487 1.418-6.184 4.407-12.010 6.576-18.022 0.719-2.016 2.121-4.698 1.384-6.183-3.091-6.303-1.179-11.087 3.058-15.921 2.427-2.767 4.56-6.013 6.115-9.361 5.929-12.709 11.974-24.974 25.007-32.491 3.76-2.152 7.689-7.534 7.929-11.647 0.596-11.14 5.825-19.986 11.785-28.426 9.141-12.947 10.573-43.369-17.081-57.207-38.228-19.132-76.871-37.6-116.021-54.697-24.564-10.709-36.863-28.731-41.032-53.961-1.263-7.656-2.939-15.238-3.929-22.923-1.505-11.464-3.912-34.883-3.912-34.883h512.306c-0.001 0-2.39 23.419-3.894 34.883z" horiz-adv-x="513" />
32
+<glyph unicode="&#xe616;" d="M513.087 224.534c0-141.673-114.855-256.526-256.554-256.526-141.674 0-256.534 114.851-256.534 256.526 0 141.692 114.861 256.553 256.534 256.553 141.7 0 256.554-114.861 256.554-256.553zM256.534-31.993c67.863 0 129.556 26.356 175.437 69.37-4.858 5.825-11.276 10.557-19.557 14.171-29.467 12.873-58.313 27.128-87.267 41.128-20.935 10.161-19.702 33.293-12.82 43.029 4.471 6.346 8.402 12.999 8.864 21.373 0.166 3.084 3.12 7.142 5.945 8.761 9.802 5.652 14.349 14.873 18.802 24.43 1.17 2.515 2.777 4.945 4.615 7.038 3.185 3.622 4.612 7.218 2.286 11.971-0.543 1.104 0.502 3.12 1.053 4.637 1.619 4.534 3.866 8.901 4.93 13.558 1.335 5.774 2.029 11.74 2.351 17.661 0.14 2.451-2.272 5.092-2.017 7.493 1.31 12.011-5.471 21.136-9.299 31.579-4.688 12.857-13.885 20.91-22.002 30.485-1.529 1.812-1.964 4.919-2.094 7.476-0.269 5.49-2.53 8.207-8.272 7.462-2.299-0.3-4.805-0.943-6.921-0.378-1.864 0.494-4.663 2.362-4.767 3.802-0.577 7.269-3.106 8.465-10.533 7.238-4.648-0.772-10.697 3.429-15.257 6.601-3.816 2.646-7.104 3.777-11.534 2.69-1.849-0.45-4.341-0.373-5.908 0.547-1.671 0.988-3.303 1.592-4.933 1.919v0.276c-0.36-0.007-0.745-0.065-1.104-0.108-0.361 0.044-0.72 0.103-1.078 0.108v-0.276c-1.645-0.327-3.287-0.931-4.945-1.919-1.556-0.918-4.046-0.996-5.899-0.547-4.443 1.087-7.724-0.044-11.532-2.69-4.578-3.173-10.611-7.373-15.259-6.601-7.431 1.226-9.97 0.031-10.547-7.238-0.109-1.439-2.897-3.308-4.758-3.802-2.139-0.565-4.624 0.077-6.944 0.378-5.728 0.745-7.994-1.971-8.258-7.462-0.131-2.555-0.565-5.665-2.095-7.476-8.111-9.575-17.308-17.629-22.009-30.485-3.814-10.443-10.602-19.568-9.285-31.579 0.256-2.401-2.152-5.042-2.023-7.493 0.327-5.923 1.020-11.888 2.351-17.661 1.065-4.656 3.313-9.024 4.945-13.558 0.547-1.516 1.587-3.531 1.041-4.637-2.325-4.754-0.894-8.351 2.291-11.971 1.837-2.094 3.437-4.523 4.612-7.038 4.45-9.555 8.996-18.779 18.798-24.43 2.827-1.619 5.78-5.676 5.952-8.761 0.457-8.374 4.387-15.027 8.869-21.373 6.873-9.735 7.951-32.623-12.837-43.029-28.76-14.386-57.8-28.255-87.251-41.128-8.285-3.615-14.704-8.347-19.561-14.169 45.88-43.015 107.569-69.372 175.422-69.372z" horiz-adv-x="513" />
31
 </font></defs></svg>
33
 </font></defs></svg>

バイナリ
fonts/jitsi.ttf ファイルの表示


バイナリ
fonts/jitsi.woff ファイルの表示


バイナリ
images/avatar2.png ファイルの表示


+ 36
- 12
index.html ファイルの表示

23
     <script src="libs/rayo.js?v=1"></script>
23
     <script src="libs/rayo.js?v=1"></script>
24
     <script src="libs/tooltip.js?v=1"></script><!-- bootstrap tooltip lib -->
24
     <script src="libs/tooltip.js?v=1"></script><!-- bootstrap tooltip lib -->
25
     <script src="libs/popover.js?v=1"></script><!-- bootstrap tooltip lib -->
25
     <script src="libs/popover.js?v=1"></script><!-- bootstrap tooltip lib -->
26
-    <script src="config.js?v=3"></script><!-- adapt to your needs, i.e. set hosts and bosh path -->
27
-    <script src="muc.js?v=12"></script><!-- simple MUC library -->
26
+    <script src="config.js?v=4"></script><!-- adapt to your needs, i.e. set hosts and bosh path -->
27
+    <script src="muc.js?v=13"></script><!-- simple MUC library -->
28
     <script src="estos_log.js?v=2"></script><!-- simple stanza logger -->
28
     <script src="estos_log.js?v=2"></script><!-- simple stanza logger -->
29
     <script src="desktopsharing.js?v=2"></script><!-- desktop sharing -->
29
     <script src="desktopsharing.js?v=2"></script><!-- desktop sharing -->
30
-    <script src="data_channels.js?v=2"></script><!-- data channels -->
31
-    <script src="app.js?v=4"></script><!-- application logic -->
30
+    <script src="data_channels.js?v=3"></script><!-- data channels -->
31
+    <script src="app.js?v=5"></script><!-- application logic -->
32
     <script src="commands.js?v=1"></script><!-- application logic -->
32
     <script src="commands.js?v=1"></script><!-- application logic -->
33
-    <script src="chat.js?v=8"></script><!-- chat logic -->
34
-    <script src="util.js?v=5"></script><!-- utility functions -->
33
+    <script src="chat.js?v=9"></script><!-- chat logic -->
34
+    <script src="contact_list.js?v=1"></script><!-- contact list logic -->
35
+    <script src="util.js?v=6"></script><!-- utility functions -->
35
     <script src="etherpad.js?v=8"></script><!-- etherpad plugin -->
36
     <script src="etherpad.js?v=8"></script><!-- etherpad plugin -->
36
     <script src="prezi.js?v=4"></script><!-- prezi plugin -->
37
     <script src="prezi.js?v=4"></script><!-- prezi plugin -->
37
     <script src="smileys.js?v=2"></script><!-- smiley images -->
38
     <script src="smileys.js?v=2"></script><!-- smiley images -->
40
     <script src="analytics.js?v=1"></script><!-- google analytics plugin -->
41
     <script src="analytics.js?v=1"></script><!-- google analytics plugin -->
41
     <script src="rtp_stats.js?v=1"></script><!-- RTP stats processing -->
42
     <script src="rtp_stats.js?v=1"></script><!-- RTP stats processing -->
42
     <script src="local_stats.js?v=1"></script><!-- Local stats processing -->
43
     <script src="local_stats.js?v=1"></script><!-- Local stats processing -->
43
-    <script src="videolayout.js?v=7"></script><!-- video ui -->
44
-    <script src="toolbar.js?v=3"></script><!-- toolbar ui -->
44
+    <script src="videolayout.js?v=8"></script><!-- video ui -->
45
+    <script src="toolbar.js?v=4"></script><!-- toolbar ui -->
45
     <script src="canvas_util.js?v=1"></script><!-- canvas drawing utils -->
46
     <script src="canvas_util.js?v=1"></script><!-- canvas drawing utils -->
46
     <script src="audio_levels.js?v=1"></script><!-- audio levels plugin -->
47
     <script src="audio_levels.js?v=1"></script><!-- audio levels plugin -->
48
+    <script src="media_stream.js?v=1"></script><!-- media stream -->
49
+    <script src="bottom_toolbar.js?v=1"></script><!-- media stream -->
47
     <link href="//netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.css" rel="stylesheet">
50
     <link href="//netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.css" rel="stylesheet">
48
-    <link rel="stylesheet" href="css/font.css"/>
49
-    <link rel="stylesheet" type="text/css" media="screen" href="css/main.css?v=22"/>
50
-    <link rel="stylesheet" type="text/css" media="screen" href="css/videolayout_default.css?v=8" id="videolayout_default"/>
51
+    <link rel="stylesheet" href="css/font.css?v=2"/>
52
+    <link rel="stylesheet" type="text/css" media="screen" href="css/main.css?v=23"/>
53
+    <link rel="stylesheet" type="text/css" media="screen" href="css/videolayout_default.css?v=9" id="videolayout_default"/>
51
     <link rel="stylesheet" href="css/jquery-impromptu.css?v=4">
54
     <link rel="stylesheet" href="css/jquery-impromptu.css?v=4">
52
     <link rel="stylesheet" href="css/modaldialog.css?v=3">
55
     <link rel="stylesheet" href="css/modaldialog.css?v=3">
53
     <link rel="stylesheet" href="css/popup_menu.css?v=2">
56
     <link rel="stylesheet" href="css/popup_menu.css?v=2">
54
     <link rel="stylesheet" href="css/popover.css?v=1">
57
     <link rel="stylesheet" href="css/popover.css?v=1">
58
+    <link rel="stylesheet" href="css/contact_list.css?v=1">
55
     <!--
59
     <!--
56
         Link used for inline installation of chrome desktop streaming extension,
60
         Link used for inline installation of chrome desktop streaming extension,
57
         is updated automatically from the code with the value defined in config.js -->
61
         is updated automatically from the code with the value defined in config.js -->
158
                     </a>
162
                     </a>
159
                     <div class="header_button_separator"></div>
163
                     <div class="header_button_separator"></div>
160
                     <span class="toolbar_span">
164
                     <span class="toolbar_span">
161
-                        <a class="button" data-toggle="popover" data-placement="bottom" data-content="Open / close chat" onclick='Chat.toggleChat();'>
165
+                        <a class="button" data-toggle="popover" data-placement="bottom" data-content="Open / close chat" onclick='BottomToolbar.toggleChat();'>
162
                             <i id="chatButton" class="icon-chat"></i>
166
                             <i id="chatButton" class="icon-chat"></i>
163
                         </a>
167
                         </a>
164
                         <span id="unreadMessages"></span>
168
                         <span id="unreadMessages"></span>
222
                 <audio id="userJoined" src="sounds/joined.wav" preload="auto"></audio>
226
                 <audio id="userJoined" src="sounds/joined.wav" preload="auto"></audio>
223
                 <audio id="userLeft" src="sounds/left.wav" preload="auto"></audio>
227
                 <audio id="userLeft" src="sounds/left.wav" preload="auto"></audio>
224
             </div>
228
             </div>
229
+            <span id="bottomToolbar">
230
+                <span class="bottomToolbar_span">
231
+                    <a class="bottomToolbarButton" data-toggle="popover" data-placement="top" data-content="Open / close chat" onclick='BottomToolbar.toggleChat();'>
232
+                        <i id="chatBottomButton" class="icon-chat-simple"></i>
233
+                    </a>
234
+                    <span id="unreadMessages"></span>
235
+                </span>
236
+                <span class="bottomToolbar_span">
237
+                    <a class="bottomToolbarButton" data-toggle="popover" data-placement="top" data-content="Open / close contact list" onclick='BottomToolbar.toggleContactList();'>
238
+                        <i id="contactListButton" class="icon-contactList"></i>
239
+                    </a>
240
+                    <span id="unreadMessages"></span>
241
+                </span>
242
+            </span>
225
         </div>
243
         </div>
226
         <div id="chatspace">
244
         <div id="chatspace">
227
             <div id="nickname">
245
             <div id="nickname">
238
         </div>
256
         </div>
239
         <a id="downloadlog" onclick='dump(event.target);' data-toggle="popover" data-placement="right" data-content="Download logs" ><i class="fa fa-cloud-download"></i></a>
257
         <a id="downloadlog" onclick='dump(event.target);' data-toggle="popover" data-placement="right" data-content="Download logs" ><i class="fa fa-cloud-download"></i></a>
240
     </div>
258
     </div>
259
+    <div id="contactlist">
260
+        <ul>
261
+            <li class="title"><i class="icon-contact-list"></i> CONTACT LIST</li>
262
+        </ul>
263
+    </div>
264
+    <a id="downloadlog" onclick='dump(event.target);' data-toggle="popover" data-placement="right" data-content="Download logs" ><i class="fa fa-cloud-download"></i></a>
241
   </body>
265
   </body>
242
 </html>
266
 </html>

+ 30
- 0
media_stream.js ファイルの表示

1
+/**
2
+ * Provides a wrapper class for the MediaStream.
3
+ * 
4
+ * TODO : Add here the src from the video element and other related properties
5
+ * and get rid of some of the mappings that we use throughout the UI.
6
+ */
7
+var MediaStream = (function() {
8
+    /**
9
+     * Creates a MediaStream object for the given data, session id and ssrc.
10
+     *
11
+     * @param data the data object from which we obtain the stream,
12
+     * the peerjid, etc.
13
+     * @param sid the session id
14
+     * @param ssrc the ssrc corresponding to this MediaStream
15
+     *
16
+     * @constructor
17
+     */
18
+    function MediaStreamProto(data, sid, ssrc) {
19
+        this.VIDEO_TYPE = "Video";
20
+        this.AUDIO_TYPE = "Audio";
21
+        this.stream = data.stream;
22
+        this.peerjid = data.peerjid;
23
+        this.ssrc = ssrc;
24
+        this.session = connection.jingle.sessions[sid];
25
+        this.type = (this.stream.getVideoTracks().length > 0)
26
+                    ? this.VIDEO_TYPE : this.AUDIO_TYPE;
27
+    }
28
+
29
+    return MediaStreamProto;
30
+})();

+ 8
- 0
muc.js ファイルの表示

373
     addVideoInfoToPresence: function(isMuted) {
373
     addVideoInfoToPresence: function(isMuted) {
374
         this.presMap['videons'] = 'http://jitsi.org/jitmeet/video';
374
         this.presMap['videons'] = 'http://jitsi.org/jitmeet/video';
375
         this.presMap['videomuted'] = isMuted.toString();
375
         this.presMap['videomuted'] = isMuted.toString();
376
+    },
377
+    findJidFromResource: function(resourceJid) {
378
+        var peerJid = null;
379
+        Object.keys(this.members).some(function (jid) {
380
+            peerJid = jid;
381
+            return Strophe.getResourceFromJid(jid) === resourceJid;
382
+        });
383
+        return peerJid;
376
     }
384
     }
377
 });
385
 });

+ 11
- 10
toolbar.js ファイルの表示

118
 
118
 
119
         var conferenceName = roomUrl.substring(roomUrl.lastIndexOf('/') + 1);
119
         var conferenceName = roomUrl.substring(roomUrl.lastIndexOf('/') + 1);
120
         var subject = "Invitation to a Jitsi Meet (" + conferenceName + ")";
120
         var subject = "Invitation to a Jitsi Meet (" + conferenceName + ")";
121
-        var body = "Hey there, I%27d like to invite you to a Jitsi Meet"
122
-                    + " conference I%27ve just set up.%0D%0A%0D%0A"
123
-                    + "Please click on the following link in order"
124
-                    + " to join the conference.%0D%0A%0D%0A"
125
-                    + roomUrl + "%0D%0A%0D%0A"
126
-                    + sharedKeyText
127
-                    + "Note that Jitsi Meet is currently only supported by Chromium,"
128
-                    + " Google Chrome and Opera, so you need"
129
-                    + " to be using one of these browsers.%0D%0A%0D%0A"
130
-                    + "Talk to you in a sec!";
121
+        var body = "Hey there, I%27d like to invite you to a Jitsi Meet" +
122
+                    " conference I%27ve just set up.%0D%0A%0D%0A" +
123
+                    "Please click on the following link in order" +
124
+                    " to join the conference.%0D%0A%0D%0A" +
125
+                    roomUrl +
126
+                    "%0D%0A%0D%0A" +
127
+                    sharedKeyText +
128
+                    "Note that Jitsi Meet is currently only supported by Chromium," +
129
+                    " Google Chrome and Opera, so you need" +
130
+                    " to be using one of these browsers.%0D%0A%0D%0A" +
131
+                    "Talk to you in a sec!";
131
 
132
 
132
         if (window.localStorage.displayname)
133
         if (window.localStorage.displayname)
133
             body += "%0D%0A%0D%0A" + window.localStorage.displayname;
134
             body += "%0D%0A%0D%0A" + window.localStorage.displayname;

+ 3
- 1
util.js ファイルの表示

52
      */
52
      */
53
     my.getAvailableVideoWidth = function () {
53
     my.getAvailableVideoWidth = function () {
54
         var chatspaceWidth
54
         var chatspaceWidth
55
-            = $('#chatspace').is(":visible") ? $('#chatspace').width() : 0;
55
+            = (Chat.isVisible() || ContactList.isVisible())
56
+                ? $('#chatspace').width()
57
+                : 0;
56
 
58
 
57
         return window.innerWidth - chatspaceWidth;
59
         return window.innerWidth - chatspaceWidth;
58
     };
60
     };

+ 324
- 39
videolayout.js ファイルの表示

1
 var VideoLayout = (function (my) {
1
 var VideoLayout = (function (my) {
2
     var preMuted = false;
2
     var preMuted = false;
3
     var currentDominantSpeaker = null;
3
     var currentDominantSpeaker = null;
4
+    var lastNCount = config.channelLastN;
5
+    var lastNEndpointsCache = [];
4
 
6
 
5
     my.changeLocalAudio = function(stream) {
7
     my.changeLocalAudio = function(stream) {
6
         connection.jingle.localAudio = stream;
8
         connection.jingle.localAudio = stream;
52
         // Add stream ended handler
54
         // Add stream ended handler
53
         stream.onended = function () {
55
         stream.onended = function () {
54
             localVideoContainer.removeChild(localVideo);
56
             localVideoContainer.removeChild(localVideo);
55
-            VideoLayout.checkChangeLargeVideo(localVideo.src);
57
+            VideoLayout.updateRemovedVideo(localVideo.src);
56
         };
58
         };
57
         // Flip video x axis if needed
59
         // Flip video x axis if needed
58
         flipXLocalVideo = flipX;
60
         flipXLocalVideo = flipX;
63
         RTC.attachMediaStream(localVideoSelector, stream);
65
         RTC.attachMediaStream(localVideoSelector, stream);
64
 
66
 
65
         localVideoSrc = localVideo.src;
67
         localVideoSrc = localVideo.src;
68
+
66
         VideoLayout.updateLargeVideo(localVideoSrc, 0);
69
         VideoLayout.updateLargeVideo(localVideoSrc, 0);
67
     };
70
     };
68
 
71
 
71
      * another one instead.
74
      * another one instead.
72
      * @param removedVideoSrc src stream identifier of the video.
75
      * @param removedVideoSrc src stream identifier of the video.
73
      */
76
      */
74
-    my.checkChangeLargeVideo = function(removedVideoSrc) {
77
+    my.updateRemovedVideo = function(removedVideoSrc) {
75
         if (removedVideoSrc === $('#largeVideo').attr('src')) {
78
         if (removedVideoSrc === $('#largeVideo').attr('src')) {
76
             // this is currently displayed as large
79
             // this is currently displayed as large
77
             // pick the last visible video in the row
80
             // pick the last visible video in the row
83
             if (!pick) {
86
             if (!pick) {
84
                 console.info("Last visible video no longer exists");
87
                 console.info("Last visible video no longer exists");
85
                 pick = $('#remoteVideos>span[id!="mixedstream"]>video').get(0);
88
                 pick = $('#remoteVideos>span[id!="mixedstream"]>video').get(0);
86
-                if (!pick) {
89
+
90
+                if (!pick || !pick.src) {
87
                     // Try local video
91
                     // Try local video
88
                     console.info("Fallback to local video...");
92
                     console.info("Fallback to local video...");
89
                     pick = $('#remoteVideos>span>span>video').get(0);
93
                     pick = $('#remoteVideos>span>span>video').get(0);
182
                     = $('#participant_' + currentDominantSpeaker + '>video')
186
                     = $('#participant_' + currentDominantSpeaker + '>video')
183
                         .get(0);
187
                         .get(0);
184
 
188
 
185
-                if (dominantSpeakerVideo)
189
+                if (dominantSpeakerVideo) {
186
                     VideoLayout.updateLargeVideo(dominantSpeakerVideo.src, 1);
190
                     VideoLayout.updateLargeVideo(dominantSpeakerVideo.src, 1);
191
+                }
187
             }
192
             }
188
 
193
 
189
             return;
194
             return;
279
      * in the document and creates it eventually.
284
      * in the document and creates it eventually.
280
      * 
285
      * 
281
      * @param peerJid peer Jid to check.
286
      * @param peerJid peer Jid to check.
287
+     * 
288
+     * @return Returns <tt>true</tt> if the peer container exists,
289
+     * <tt>false</tt> - otherwise
282
      */
290
      */
283
     my.ensurePeerContainerExists = function(peerJid) {
291
     my.ensurePeerContainerExists = function(peerJid) {
284
-        var peerResource = Strophe.getResourceFromJid(peerJid);
285
-        var videoSpanId = 'participant_' + peerResource;
292
+        ContactList.ensureAddContact(peerJid);
293
+
294
+        var resourceJid = Strophe.getResourceFromJid(peerJid);
295
+
296
+        var videoSpanId = 'participant_' + resourceJid;
286
 
297
 
287
         if ($('#' + videoSpanId).length > 0) {
298
         if ($('#' + videoSpanId).length > 0) {
288
             // If there's been a focus change, make sure we add focus related
299
             // If there's been a focus change, make sure we add focus related
289
             // interface!!
300
             // interface!!
290
-            if (focus && $('#remote_popupmenu_' + peerResource).length <= 0)
301
+            if (focus && $('#remote_popupmenu_' + resourceJid).length <= 0)
291
                 addRemoteVideoMenu( peerJid,
302
                 addRemoteVideoMenu( peerJid,
292
                                     document.getElementById(videoSpanId));
303
                                     document.getElementById(videoSpanId));
293
-            return;
294
         }
304
         }
295
-
296
-        var container
297
-            = VideoLayout.addRemoteVideoContainer(peerJid, videoSpanId);
298
-
299
-        var nickfield = document.createElement('span');
300
-        nickfield.className = "nick";
301
-        nickfield.appendChild(document.createTextNode(peerResource));
302
-        container.appendChild(nickfield);
303
-        VideoLayout.resizeThumbnails();
305
+        else {
306
+            var container
307
+                = VideoLayout.addRemoteVideoContainer(peerJid, videoSpanId);
308
+
309
+            var nickfield = document.createElement('span');
310
+            nickfield.className = "nick";
311
+            nickfield.appendChild(document.createTextNode(resourceJid));
312
+            container.appendChild(nickfield);
313
+
314
+            // In case this is not currently in the last n we don't show it.
315
+            if (lastNCount
316
+                && lastNCount > 0
317
+                && $('#remoteVideos>span').length >= lastNCount + 2) {
318
+                showPeerContainer(resourceJid, false);
319
+            }
320
+            else
321
+                VideoLayout.resizeThumbnails();
322
+        }
304
     };
323
     };
305
 
324
 
306
     my.addRemoteVideoContainer = function(peerJid, spanId) {
325
     my.addRemoteVideoContainer = function(peerJid, spanId) {
321
     };
340
     };
322
 
341
 
323
     /**
342
     /**
324
-     * Shows the display name for the given video.
343
+     * Creates an audio or video stream element.
344
+     */
345
+    my.createStreamElement = function (sid, stream) {
346
+        var isVideo = stream.getVideoTracks().length > 0;
347
+
348
+        var element = isVideo
349
+                        ? document.createElement('video')
350
+                        : document.createElement('audio');
351
+        var id = (isVideo ? 'remoteVideo_' : 'remoteAudio_')
352
+                    + sid + '_' + stream.id;
353
+
354
+        element.id = id;
355
+        element.autoplay = true;
356
+        element.oncontextmenu = function () { return false; };
357
+
358
+        return element;
359
+    };
360
+
361
+    my.addRemoteStreamElement
362
+        = function (container, sid, stream, peerJid, thessrc) {
363
+        var newElementId = null;
364
+
365
+        var isVideo = stream.getVideoTracks().length > 0;
366
+
367
+        if (container) {
368
+            var streamElement = VideoLayout.createStreamElement(sid, stream);
369
+            newElementId = streamElement.id;
370
+
371
+            container.appendChild(streamElement);
372
+
373
+            var sel = $('#' + newElementId);
374
+            sel.hide();
375
+
376
+            // If the container is currently visible we attach the stream.
377
+            if (!isVideo
378
+                || (container.offsetParent !== null && isVideo)) {
379
+                RTC.attachMediaStream(sel, stream);
380
+
381
+                if (isVideo)
382
+                    waitForRemoteVideo(sel, thessrc, stream);
383
+            }
384
+
385
+            stream.onended = function () {
386
+                console.log('stream ended', this);
387
+
388
+                VideoLayout.removeRemoteStreamElement(stream, container);
389
+
390
+                if (peerJid)
391
+                    ContactList.removeContact(peerJid);
392
+            };
393
+
394
+            // Add click handler.
395
+            container.onclick = function (event) {
396
+                /*
397
+                 * FIXME It turns out that videoThumb may not exist (if there is
398
+                 * no actual video).
399
+                 */
400
+                var videoThumb = $('#' + container.id + '>video').get(0);
401
+
402
+                if (videoThumb)
403
+                    VideoLayout.handleVideoThumbClicked(videoThumb.src);
404
+
405
+                event.preventDefault();
406
+                return false;
407
+            };
408
+
409
+            // Add hover handler
410
+            $(container).hover(
411
+                function() {
412
+                    VideoLayout.showDisplayName(container.id, true);
413
+                },
414
+                function() {
415
+                    var videoSrc = null;
416
+                    if ($('#' + container.id + '>video')
417
+                            && $('#' + container.id + '>video').length > 0) {
418
+                        videoSrc = $('#' + container.id + '>video').get(0).src;
419
+                    }
420
+
421
+                    // If the video has been "pinned" by the user we want to
422
+                    // keep the display name on place.
423
+                    if (!VideoLayout.isLargeVideoVisible()
424
+                            || videoSrc !== $('#largeVideo').attr('src'))
425
+                        VideoLayout.showDisplayName(container.id, false);
426
+                }
427
+            );
428
+        }
429
+
430
+        return newElementId;
431
+    };
432
+
433
+    /**
434
+     * Removes the remote stream element corresponding to the given stream and
435
+     * parent container.
436
+     * 
437
+     * @param stream the stream
438
+     * @param container
439
+     */
440
+    my.removeRemoteStreamElement = function (stream, container) {
441
+        if (!container)
442
+            return;
443
+
444
+        var select = null;
445
+        var removedVideoSrc = null;
446
+        if (stream.getVideoTracks().length > 0) {
447
+            select = $('#' + container.id + '>video');
448
+            removedVideoSrc = select.get(0).src;
449
+        }
450
+        else
451
+            select = $('#' + container.id + '>audio');
452
+
453
+        // Remove video source from the mapping.
454
+        delete videoSrcToSsrc[removedVideoSrc];
455
+
456
+        // Mark video as removed to cancel waiting loop(if video is removed
457
+        // before has started)
458
+        select.removed = true;
459
+        select.remove();
460
+
461
+        var audioCount = $('#' + container.id + '>audio').length;
462
+        var videoCount = $('#' + container.id + '>video').length;
463
+
464
+        if (!audioCount && !videoCount) {
465
+            console.log("Remove whole user", container.id);
466
+            // Remove whole container
467
+            container.remove();
468
+            Util.playSoundNotification('userLeft');
469
+            VideoLayout.resizeThumbnails();
470
+        }
471
+
472
+        if (removedVideoSrc)
473
+            VideoLayout.updateRemovedVideo(removedVideoSrc);
474
+    };
475
+
476
+    /**
477
+     * Show/hide peer container for the given resourceJid.
478
+     */
479
+    function showPeerContainer(resourceJid, isShow) {
480
+        var peerContainer = $('#participant_' + resourceJid);
481
+
482
+        if (!peerContainer)
483
+            return;
484
+
485
+        if (!peerContainer.is(':visible') && isShow)
486
+            peerContainer.show();
487
+        else if (peerContainer.is(':visible') && !isShow)
488
+            peerContainer.hide();
489
+    };
490
+
491
+    /**
492
+     * Sets the display name for the given video span id.
325
      */
493
      */
326
-    my.setDisplayName = function(videoSpanId, displayName) {
494
+    function setDisplayName(videoSpanId, displayName) {
327
         var nameSpan = $('#' + videoSpanId + '>span.displayname');
495
         var nameSpan = $('#' + videoSpanId + '>span.displayname');
328
         var defaultLocalDisplayName = "Me";
496
         var defaultLocalDisplayName = "Me";
329
         var defaultRemoteDisplayName = "Speaker";
497
         var defaultRemoteDisplayName = "Speaker";
334
 
502
 
335
             if (nameSpanElement.id === 'localDisplayName' &&
503
             if (nameSpanElement.id === 'localDisplayName' &&
336
                 $('#localDisplayName').text() !== displayName) {
504
                 $('#localDisplayName').text() !== displayName) {
337
-                if (displayName)
505
+                if (displayName && displayName.length > 0)
338
                     $('#localDisplayName').text(displayName + ' (me)');
506
                     $('#localDisplayName').text(displayName + ' (me)');
339
                 else
507
                 else
340
                     $('#localDisplayName').text(defaultLocalDisplayName);
508
                     $('#localDisplayName').text(defaultLocalDisplayName);
341
             } else {
509
             } else {
342
-                if (displayName)
510
+                if (displayName && displayName.length > 0)
343
                     $('#' + videoSpanId + '_name').text(displayName);
511
                     $('#' + videoSpanId + '_name').text(displayName);
344
                 else
512
                 else
345
                     $('#' + videoSpanId + '_name').text(defaultRemoteDisplayName);
513
                     $('#' + videoSpanId + '_name').text(defaultRemoteDisplayName);
359
                 nameSpan.innerText = defaultRemoteDisplayName;
527
                 nameSpan.innerText = defaultRemoteDisplayName;
360
             }
528
             }
361
 
529
 
362
-            if (displayName && displayName.length) {
530
+            if (displayName && displayName.length > 0) {
363
                 nameSpan.innerText = displayName;
531
                 nameSpan.innerText = displayName;
364
             }
532
             }
365
 
533
 
434
      * @param isShow indicates if the display name should be shown or hidden
602
      * @param isShow indicates if the display name should be shown or hidden
435
      */
603
      */
436
     my.showDisplayName = function(videoSpanId, isShow) {
604
     my.showDisplayName = function(videoSpanId, isShow) {
437
-        // FIX: need to use noConflict of jquery, because apparently we're
438
-        // using another library that uses $, which conflics with jquery and
439
-        // sometimes objects are null because of that!!!!!!!!!
440
-        // http://api.jquery.com/jQuery.noConflict/
441
-        var nameSpan = jQuery('#' + videoSpanId + '>span.displayname').get(0);
605
+        var nameSpan = $('#' + videoSpanId + '>span.displayname').get(0);
442
         if (isShow) {
606
         if (isShow) {
443
             if (nameSpan && nameSpan.innerHTML && nameSpan.innerHTML.length) 
607
             if (nameSpan && nameSpan.innerHTML && nameSpan.innerHTML.length) 
444
                 nameSpan.setAttribute("style", "display:inline-block;");
608
                 nameSpan.setAttribute("style", "display:inline-block;");
459
             return;
623
             return;
460
         }
624
         }
461
 
625
 
462
-        var nameSpan = $('#' + videoSpanId + '>span.displayname');
463
-
464
         var statusSpan = $('#' + videoSpanId + '>span.status');
626
         var statusSpan = $('#' + videoSpanId + '>span.status');
465
         if (!statusSpan.length) {
627
         if (!statusSpan.length) {
466
             //Add status span
628
             //Add status span
721
 
883
 
722
     /**
884
     /**
723
      * Calculates the thumbnail size.
885
      * Calculates the thumbnail size.
886
+     *
887
+     * @param videoSpaceWidth the width of the video space
724
      */
888
      */
725
     my.calculateThumbnailSize = function (videoSpaceWidth) {
889
     my.calculateThumbnailSize = function (videoSpaceWidth) {
726
         // Calculate the available height, which is the inner window height minus
890
         // Calculate the available height, which is the inner window height minus
729
        // container used for highlighting shadow.
893
        // container used for highlighting shadow.
730
        var availableHeight = 100;
894
        var availableHeight = 100;
731
 
895
 
732
-       var numvids = $('#remoteVideos>span:visible').length;
896
+       var numvids = 0;
897
+       if (lastNCount && lastNCount > 0)
898
+           numvids = lastNCount + 1;
899
+       else
900
+           numvids = $('#remoteVideos>span:visible').length;
733
 
901
 
734
        // Remove the 3px borders arround videos and border around the remote
902
        // Remove the 3px borders arround videos and border around the remote
735
        // videos area
903
        // videos area
736
-       var availableWinWidth = videoSpaceWidth - 2 * 3 * numvids - 50;
904
+       var availableWinWidth = videoSpaceWidth - 2 * 3 * numvids - 70;
737
 
905
 
738
        var availableWidth = availableWinWidth / numvids;
906
        var availableWidth = availableWinWidth / numvids;
739
        var aspectRatio = 16.0 / 9.0;
907
        var aspectRatio = 16.0 / 9.0;
851
         return currentDominantSpeaker;
1019
         return currentDominantSpeaker;
852
     };
1020
     };
853
 
1021
 
1022
+    /**
1023
+     * Returns the corresponding resource jid to the given peer container
1024
+     * DOM element.
1025
+     *
1026
+     * @return the corresponding resource jid to the given peer container
1027
+     * DOM element
1028
+     */
1029
+    my.getPeerContainerResourceJid = function (containerElement) {
1030
+        var i = containerElement.id.indexOf('participant_');
1031
+
1032
+        if (i >= 0)
1033
+            return containerElement.id.substring(i + 12); 
1034
+    };
1035
+
854
     /**
1036
     /**
855
      * Adds the remote video menu element for the given <tt>jid</tt> in the
1037
      * Adds the remote video menu element for the given <tt>jid</tt> in the
856
      * given <tt>parentElement</tt>.
1038
      * given <tt>parentElement</tt>.
965
             VideoLayout.showVideoIndicator(videoSpanId, isMuted);
1147
             VideoLayout.showVideoIndicator(videoSpanId, isMuted);
966
     });
1148
     });
967
 
1149
 
1150
+    /**
1151
+     * Display name changed.
1152
+     */
1153
+    $(document).bind('displaynamechanged',
1154
+                    function (event, jid, displayName, status) {
1155
+        if (jid === 'localVideoContainer'
1156
+            || jid === connection.emuc.myroomjid) {
1157
+            setDisplayName('localVideoContainer',
1158
+                           displayName);
1159
+        } else {
1160
+            VideoLayout.ensurePeerContainerExists(jid);
1161
+
1162
+            setDisplayName(
1163
+                'participant_' + Strophe.getResourceFromJid(jid),
1164
+                displayName,
1165
+                status);
1166
+        }
1167
+    });
1168
+
968
     /**
1169
     /**
969
      * On dominant speaker changed event.
1170
      * On dominant speaker changed event.
970
      */
1171
      */
974
                 === Strophe.getResourceFromJid(connection.emuc.myroomjid))
1175
                 === Strophe.getResourceFromJid(connection.emuc.myroomjid))
975
             return;
1176
             return;
976
 
1177
 
977
-        // Obtain container for new dominant speaker.
978
-        var container  = document.getElementById(
979
-                'participant_' + resourceJid);
980
-
981
         // Update the current dominant speaker.
1178
         // Update the current dominant speaker.
982
         if (resourceJid !== currentDominantSpeaker)
1179
         if (resourceJid !== currentDominantSpeaker)
983
             currentDominantSpeaker = resourceJid;
1180
             currentDominantSpeaker = resourceJid;
984
         else
1181
         else
985
             return;
1182
             return;
986
 
1183
 
1184
+        // Obtain container for new dominant speaker.
1185
+        var container  = document.getElementById(
1186
+                'participant_' + resourceJid);
1187
+
987
         // Local video will not have container found, but that's ok
1188
         // Local video will not have container found, but that's ok
988
         // since we don't want to switch to local video.
1189
         // since we don't want to switch to local video.
989
         if (container && !focusedVideoSrc)
1190
         if (container && !focusedVideoSrc)
990
         {
1191
         {
991
             var video = container.getElementsByTagName("video");
1192
             var video = container.getElementsByTagName("video");
992
-            if (video.length)
993
-            {
1193
+
1194
+            // Update the large video if the video source is already available,
1195
+            // otherwise wait for the "videoactive.jingle" event.
1196
+            if (video.length && video[0].currentTime > 0)
994
                 VideoLayout.updateLargeVideo(video[0].src);
1197
                 VideoLayout.updateLargeVideo(video[0].src);
1198
+        }
1199
+    });
1200
+
1201
+    /**
1202
+     * On last N change event.
1203
+     *
1204
+     * @param event the event that notified us
1205
+     * @param lastNEndpoints the list of last N endpoints
1206
+     * @param endpointsEnteringLastN the list currently entering last N
1207
+     * endpoints
1208
+     */
1209
+    $(document).bind('lastnchanged', function ( event,
1210
+                                                lastNEndpoints,
1211
+                                                endpointsEnteringLastN,
1212
+                                                stream) {
1213
+        if (lastNCount !== lastNEndpoints.length)
1214
+            lastNCount = lastNEndpoints.length;
1215
+
1216
+        lastNEndpointsCache = lastNEndpoints;
1217
+
1218
+        $('#remoteVideos>span').each(function( index, element ) {
1219
+            var resourceJid = VideoLayout.getPeerContainerResourceJid(element);
1220
+
1221
+            if (resourceJid
1222
+                && lastNEndpoints.length > 0
1223
+                && lastNEndpoints.indexOf(resourceJid) < 0) {
1224
+                console.log("Remove from last N", resourceJid);
1225
+                showPeerContainer(resourceJid, false);
995
             }
1226
             }
1227
+        });
1228
+
1229
+        if (!endpointsEnteringLastN || endpointsEnteringLastN.length < 0)
1230
+            endpointsEnteringLastN = lastNEndpoints;
1231
+
1232
+        if (endpointsEnteringLastN && endpointsEnteringLastN.length > 0) {
1233
+            endpointsEnteringLastN.forEach(function (resourceJid) {
1234
+
1235
+                if (!$('#participant_' + resourceJid).is(':visible')) {
1236
+                    console.log("Add to last N", resourceJid);
1237
+                    showPeerContainer(resourceJid, true);
1238
+
1239
+                    mediaStreams.some(function (mediaStream) {
1240
+                        if (mediaStream.peerjid
1241
+                            && Strophe.getResourceFromJid(mediaStream.peerjid)
1242
+                                === resourceJid
1243
+                            && mediaStream.type === mediaStream.VIDEO_TYPE) {
1244
+                            var sel = $('#participant_' + resourceJid + '>video');
1245
+
1246
+                            RTC.attachMediaStream(sel, mediaStream.stream);
1247
+                            waitForRemoteVideo(
1248
+                                    sel,
1249
+                                    mediaStream.ssrc,
1250
+                                    mediaStream.stream);
1251
+                            return true;
1252
+                        }
1253
+                    });
1254
+                }
1255
+            });
1256
+        }
1257
+    });
1258
+
1259
+    $(document).bind('videoactive.jingle', function (event, videoelem) {
1260
+        if (videoelem.attr('id').indexOf('mixedmslabel') === -1) {
1261
+            // ignore mixedmslabela0 and v0
1262
+
1263
+            videoelem.show();
1264
+            VideoLayout.resizeThumbnails();
1265
+
1266
+            var videoParent = videoelem.parent();
1267
+            var parentResourceJid = null;
1268
+            if (videoParent)
1269
+                parentResourceJid
1270
+                    = VideoLayout.getPeerContainerResourceJid(videoParent[0]);
1271
+
1272
+            // Update the large video to the last added video only if there's no
1273
+            // current dominant or focused speaker or update it to the current
1274
+            // dominant speaker.
1275
+            if ((!focusedVideoSrc && !VideoLayout.getDominantSpeakerResourceJid())
1276
+                || (parentResourceJid
1277
+                && VideoLayout.getDominantSpeakerResourceJid()
1278
+                    === parentResourceJid)) {
1279
+                VideoLayout.updateLargeVideo(videoelem.attr('src'), 1);
1280
+            }
1281
+
1282
+            VideoLayout.showFocusIndicator();
996
         }
1283
         }
997
     });
1284
     });
998
 
1285
 
999
     return my;
1286
     return my;
1000
 }(VideoLayout || {}));
1287
 }(VideoLayout || {}));
1001
-
1002
-    

読み込み中…
キャンセル
保存