Bläddra i källkod

Merge branch 'master' into jingle-protocol-changes

j8
Philipp Hancke 11 år sedan
förälder
incheckning
8c3b533bb7
72 ändrade filer med 18770 tillägg och 507 borttagningar
  1. 23
    0
      INSTALL.md
  2. 368
    141
      app.js
  3. 217
    0
      audio_levels.js
  4. 32
    0
      bottom_toolbar.js
  5. 109
    0
      canvas_util.js
  6. 49
    6
      chat.js
  7. 15
    7
      config.js
  8. 235
    0
      contact_list.js
  9. 35
    0
      css/contact_list.css
  10. 21
    1
      css/font.css
  11. 240
    14
      css/main.css
  12. 1
    1
      css/modaldialog.css
  13. 53
    7
      css/videolayout_default.css
  14. 62
    20
      data_channels.js
  15. 6
    0
      debian/changelog
  16. 1
    0
      debian/compat
  17. 33
    0
      debian/control
  18. 7
    0
      debian/jitsi-meet-prosody.README.Debian
  19. 31
    0
      debian/jitsi-meet-prosody.copyright
  20. 1
    0
      debian/jitsi-meet-prosody.docs
  21. 1
    0
      debian/jitsi-meet-prosody.install
  22. 59
    0
      debian/jitsi-meet-prosody.postinst
  23. 48
    0
      debian/jitsi-meet-prosody.postrm
  24. 35
    0
      debian/jitsi-meet-prosody.preinst
  25. 36
    0
      debian/jitsi-meet-prosody.prerm
  26. 1
    0
      debian/jitsi-meet-prosody.substvars
  27. 8
    0
      debian/jitsi-meet.README.Debian
  28. 6
    0
      debian/jitsi-meet.README.source
  29. 31
    0
      debian/jitsi-meet.copyright
  30. 3
    0
      debian/jitsi-meet.docs
  31. 2
    0
      debian/jitsi-meet.install
  32. 64
    0
      debian/jitsi-meet.postinst
  33. 48
    0
      debian/jitsi-meet.postrm
  34. 35
    0
      debian/jitsi-meet.preinst
  35. 36
    0
      debian/jitsi-meet.prerm
  36. 16
    0
      debian/rules
  37. 1
    0
      debian/source/format
  38. 19
    0
      debian/source/include-binaries
  39. 1
    0
      debian/usr/share/doc/jitsi-meet-prosody/README
  40. Binär
      debian/usr/share/doc/jitsi-meet-prosody/changelog.Debian.gz
  41. 31
    0
      debian/usr/share/doc/jitsi-meet-prosody/copyright
  42. Binär
      debian/usr/share/doc/jitsi-meet-prosody/prosody.cfg.lua-jvb.example.gz
  43. 13
    0
      debian/usr/share/doc/jitsi-meet/README
  44. Binär
      debian/usr/share/doc/jitsi-meet/changelog.Debian.gz
  45. 31
    0
      debian/usr/share/doc/jitsi-meet/copyright
  46. 36
    0
      debian/usr/share/doc/jitsi-meet/jitsi-meet.example
  47. 103
    0
      doc/quick-install.md
  48. Binär
      favicon.ico
  49. Binär
      fonts/jitsi.eot
  50. 9
    3
      fonts/jitsi.svg
  51. Binär
      fonts/jitsi.ttf
  52. Binär
      fonts/jitsi.woff
  53. Binär
      images/avatar2.png
  54. Binär
      images/welcome_page/bubble.png
  55. Binär
      images/welcome_page/jitsi-logo.png
  56. Binär
      images/welcome_page/pattern-body.png
  57. Binär
      images/welcome_page/pattern-header.png
  58. 208
    83
      index.html
  59. 356
    99
      libs/colibri/colibri.focus.js
  60. 15008
    0
      libs/jquery-ui.js
  61. 6
    0
      libs/jquery.min.js
  62. 94
    0
      libs/rayo.js
  63. 39
    12
      libs/strophe/strophe.jingle.adapter.js
  64. 15
    1
      libs/strophe/strophe.jingle.sdp.util.js
  65. 4
    1
      libs/strophe/strophe.jingle.session.js
  66. 131
    0
      local_stats.js
  67. 30
    0
      media_stream.js
  68. 25
    0
      muc.js
  69. 2
    2
      rtp_stats.js
  70. 111
    24
      toolbar.js
  71. 3
    1
      util.js
  72. 526
    84
      videolayout.js

+ 23
- 0
INSTALL.md Visa fil

230
 
230
 
231
 # Hold your first conference
231
 # Hold your first conference
232
 You are now all set and ready to have your first meet by going to http://jitsi.example.com
232
 You are now all set and ready to have your first meet by going to http://jitsi.example.com
233
+
234
+
235
+## Enabling recording
236
+Currently recording is only supported for linux-64 and macos. To enable it, add
237
+the following properties to sip-communicator.properties:
238
+```
239
+org.jitsi.videobridge.ENABLE_MEDIA_RECORDING=true
240
+org.jitsi.videobridge.MEDIA_RECORDING_PATH=/path/to/recordings/dir
241
+org.jitsi.videobridge.MEDIA_RECORDING_TOKEN=secret
242
+```
243
+
244
+where /path/to/recordings/dir is the path to a pre-existing directory where recordings
245
+will be stored (needs to be writeable by the user running jitsi-videobridge),
246
+and "secret" is a string which will be used for authentication.
247
+
248
+Then, edit the Jitsi-Meet config.js file and set:
249
+```
250
+enableRecording: true
251
+```
252
+
253
+Restart jitsi-videobridge and start a new conference (making sure that the page
254
+is reloaded with the new config.js) -- the organizer of the conference should
255
+now have a "recoriding" button in the floating menu, near the "mute" button.

+ 368
- 141
app.js Visa fil

1
 /* jshint -W117 */
1
 /* jshint -W117 */
2
 /* application specific logic */
2
 /* application specific logic */
3
 var connection = null;
3
 var connection = null;
4
+var authenticatedUser = false;
4
 var focus = null;
5
 var focus = null;
5
 var activecall = null;
6
 var activecall = null;
6
 var RTC = null;
7
 var RTC = null;
7
 var nickname = null;
8
 var nickname = null;
8
 var sharedKey = '';
9
 var sharedKey = '';
10
+var recordingToken ='';
9
 var roomUrl = null;
11
 var roomUrl = null;
12
+var roomName = null;
10
 var ssrc2jid = {};
13
 var ssrc2jid = {};
14
+var mediaStreams = [];
15
+
11
 /**
16
 /**
12
  * 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.
13
  * @type {StatsCollector}
18
  * @type {StatsCollector}
14
  */
19
  */
15
 var statsCollector = null;
20
 var statsCollector = null;
16
 
21
 
22
+/**
23
+ * The stats collector for the local stream.
24
+ * @type {LocalStatsCollector}
25
+ */
26
+var localStatsCollector = null;
27
+
17
 /**
28
 /**
18
  * Indicates whether ssrc is camera video or desktop stream.
29
  * Indicates whether ssrc is camera video or desktop stream.
19
  * FIXME: remove those maps
30
  * FIXME: remove those maps
58
         return;
69
         return;
59
     }
70
     }
60
 
71
 
72
+    var jid = document.getElementById('jid').value || config.hosts.anonymousdomain || config.hosts.domain || window.location.hostname;
73
+    connect(jid);
74
+}
75
+
76
+function connect(jid, password) {
61
     connection = new Strophe.Connection(document.getElementById('boshURL').value || config.bosh || '/http-bind');
77
     connection = new Strophe.Connection(document.getElementById('boshURL').value || config.bosh || '/http-bind');
62
 
78
 
63
     if (nickname) {
79
     if (nickname) {
74
         connection.jingle.pc_constraints.optional.push({googIPv6: true});
90
         connection.jingle.pc_constraints.optional.push({googIPv6: true});
75
     }
91
     }
76
 
92
 
77
-    var jid = document.getElementById('jid').value || config.hosts.domain || window.location.hostname;
93
+    if(!password)
94
+        password = document.getElementById('password').value;
78
 
95
 
79
-    connection.connect(jid, document.getElementById('password').value, function (status) {
96
+    var anonymousConnectionFailed = false;
97
+    connection.connect(jid, password, function (status, msg) {
80
         if (status === Strophe.Status.CONNECTED) {
98
         if (status === Strophe.Status.CONNECTED) {
81
             console.log('connected');
99
             console.log('connected');
82
             if (config.useStunTurn) {
100
             if (config.useStunTurn) {
90
             });
108
             });
91
 
109
 
92
             document.getElementById('connect').disabled = true;
110
             document.getElementById('connect').disabled = true;
111
+
112
+            if(password)
113
+                authenticatedUser = true;
114
+        } else if (status === Strophe.Status.CONNFAIL) {
115
+            if(msg === 'x-strophe-bad-non-anon-jid') {
116
+                anonymousConnectionFailed = true;
117
+            }
118
+            console.log('status', status);
119
+        } else if (status === Strophe.Status.DISCONNECTED) {
120
+            if(anonymousConnectionFailed) {
121
+                // prompt user for username and password
122
+                $(document).trigger('passwordrequired.main');
123
+            }
124
+        } else if (status === Strophe.Status.AUTHFAIL) {
125
+            // wrong password or username, prompt user
126
+            $(document).trigger('passwordrequired.main');
127
+
93
         } else {
128
         } else {
94
             console.log('status', status);
129
             console.log('status', status);
95
         }
130
         }
123
 
158
 
124
     VideoLayout.changeLocalAudio(stream);
159
     VideoLayout.changeLocalAudio(stream);
125
 
160
 
161
+    startLocalRtpStatsCollector(stream);
162
+
126
     if (RTC.browser !== 'firefox') {
163
     if (RTC.browser !== 'firefox') {
127
         getUserMediaWithConstraints(['video'],
164
         getUserMediaWithConstraints(['video'],
128
                                     videoStreamReady,
165
                                     videoStreamReady,
173
         }
210
         }
174
     }
211
     }
175
 
212
 
176
-    roomjid = roomnode + '@' + config.hosts.muc;
213
+    roomName = roomnode + '@' + config.hosts.muc;
214
+
215
+    roomjid = roomName;
177
 
216
 
178
     if (config.useNicks) {
217
     if (config.useNicks) {
179
         var nick = window.prompt('Your nickname (optional)');
218
         var nick = window.prompt('Your nickname (optional)');
183
             roomjid += '/' + Strophe.getNodeFromJid(connection.jid);
222
             roomjid += '/' + Strophe.getNodeFromJid(connection.jid);
184
         }
223
         }
185
     } else {
224
     } else {
186
-        roomjid += '/' + Strophe.getNodeFromJid(connection.jid).substr(0, 8);
225
+
226
+        var tmpJid = Strophe.getNodeFromJid(connection.jid);
227
+
228
+        if(!authenticatedUser)
229
+            tmpJid = tmpJid.substr(0, 8);
230
+
231
+        roomjid += '/' + tmpJid;
187
     }
232
     }
188
     connection.emuc.doJoin(roomjid);
233
     connection.emuc.doJoin(roomjid);
189
 }
234
 }
190
 
235
 
191
-$(document).bind('remotestreamadded.jingle', function (event, data, sid) {
192
-    function waitForRemoteVideo(selector, sid, ssrc) {
193
-        if (selector.removed) {
194
-            console.warn("media removed before had started", selector);
195
-            return;
196
-        }
197
-        var sess = connection.jingle.sessions[sid];
198
-        if (data.stream.id === 'mixedmslabel') return;
199
-        var videoTracks = data.stream.getVideoTracks();
200
-//        console.log("waiting..", videoTracks, selector[0]);
201
-
202
-        if (videoTracks.length === 0 || selector[0].currentTime > 0) {
203
-            RTC.attachMediaStream(selector, data.stream); // FIXME: why do i have to do this for FF?
204
-
205
-            // FIXME: add a class that will associate peer Jid, video.src, it's ssrc and video type
206
-            //        in order to get rid of too many maps
207
-            if (ssrc) {
208
-                videoSrcToSsrc[sel.attr('src')] = ssrc;
209
-            } else {
210
-                console.warn("No ssrc given for video", sel);
211
-            }
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
+    }
212
 
241
 
213
-            $(document).trigger('callactive.jingle', [selector, sid]);
214
-            console.log('waitForremotevideo', sess.peerconnection.iceConnectionState, sess.peerconnection.signalingState);
242
+    if (stream.id === 'mixedmslabel') return;
243
+
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;
215
         } else {
251
         } else {
216
-            setTimeout(function () { waitForRemoteVideo(selector, sid, ssrc); }, 250);
252
+            console.warn("No ssrc given for video", selector);
217
         }
253
         }
254
+
255
+        $(document).trigger('videoactive.jingle', [selector]);
256
+    } else {
257
+        setTimeout(function () {
258
+            waitForRemoteVideo(selector, ssrc, stream);
259
+            }, 250);
218
     }
260
     }
261
+}
262
+
263
+$(document).bind('remotestreamadded.jingle', function (event, data, sid) {
219
     var sess = connection.jingle.sessions[sid];
264
     var sess = connection.jingle.sessions[sid];
220
 
265
 
221
     var thessrc;
266
     var thessrc;
222
     // look up an associated JID for a stream id
267
     // look up an associated JID for a stream id
223
     if (data.stream.id.indexOf('mixedmslabel') === -1) {
268
     if (data.stream.id.indexOf('mixedmslabel') === -1) {
224
-        var ssrclines = SDPUtil.find_lines(sess.peerconnection.remoteDescription.sdp, 'a=ssrc');
269
+        var ssrclines
270
+            = SDPUtil.find_lines(sess.peerconnection.remoteDescription.sdp, 'a=ssrc');
225
         ssrclines = ssrclines.filter(function (line) {
271
         ssrclines = ssrclines.filter(function (line) {
226
             return line.indexOf('mslabel:' + data.stream.label) !== -1;
272
             return line.indexOf('mslabel:' + data.stream.label) !== -1;
227
         });
273
         });
235
         }
281
         }
236
     }
282
     }
237
 
283
 
284
+    mediaStreams.push(new MediaStream(data, sid, thessrc));
285
+
238
     var container;
286
     var container;
239
     var remotes = document.getElementById('remoteVideos');
287
     var remotes = document.getElementById('remoteVideos');
240
 
288
 
241
     if (data.peerjid) {
289
     if (data.peerjid) {
242
         VideoLayout.ensurePeerContainerExists(data.peerjid);
290
         VideoLayout.ensurePeerContainerExists(data.peerjid);
291
+
243
         container  = document.getElementById(
292
         container  = document.getElementById(
244
                 'participant_' + Strophe.getResourceFromJid(data.peerjid));
293
                 'participant_' + Strophe.getResourceFromJid(data.peerjid));
245
     } else {
294
     } else {
252
         }
301
         }
253
         // FIXME: for the mixed ms we dont need a video -- currently
302
         // FIXME: for the mixed ms we dont need a video -- currently
254
         container = document.createElement('span');
303
         container = document.createElement('span');
304
+        container.id = 'mixedstream';
255
         container.className = 'videocontainer';
305
         container.className = 'videocontainer';
256
         remotes.appendChild(container);
306
         remotes.appendChild(container);
257
         Util.playSoundNotification('userJoined');
307
         Util.playSoundNotification('userJoined');
258
     }
308
     }
259
 
309
 
260
     var isVideo = data.stream.getVideoTracks().length > 0;
310
     var isVideo = data.stream.getVideoTracks().length > 0;
261
-    var vid = isVideo ? document.createElement('video') : document.createElement('audio');
262
-    var id = (isVideo ? 'remoteVideo_' : 'remoteAudio_') + sid + '_' + data.stream.id;
263
-
264
-    vid.id = id;
265
-    vid.autoplay = true;
266
-    vid.oncontextmenu = function () { return false; };
267
-
268
-    container.appendChild(vid);
269
-
270
-    // TODO: make mixedstream display:none via css?
271
-    if (id.indexOf('mixedmslabel') !== -1) {
272
-        container.id = 'mixedstream';
273
-        $(container).hide();
274
-    }
275
-
276
-    var sel = $('#' + id);
277
-    sel.hide();
278
-    RTC.attachMediaStream(sel, data.stream);
279
 
311
 
280
-    if (isVideo) {
281
-        waitForRemoteVideo(sel, sid, thessrc);
312
+    if (container) {
313
+        VideoLayout.addRemoteStreamElement( container,
314
+                                            sid,
315
+                                            data.stream,
316
+                                            data.peerjid,
317
+                                            thessrc);
282
     }
318
     }
283
 
319
 
284
-    data.stream.onended = function () {
285
-        console.log('stream ended', this.id);
286
-
287
-        // Mark video as removed to cancel waiting loop(if video is removed before has started)
288
-        sel.removed = true;
289
-        sel.remove();
290
-
291
-        var audioCount = $('#' + container.id + '>audio').length;
292
-        var videoCount = $('#' + container.id + '>video').length;
293
-        if (!audioCount && !videoCount) {
294
-            console.log("Remove whole user", container.id);
295
-            // Remove whole container
296
-            container.remove();
297
-            Util.playSoundNotification('userLeft');
298
-            VideoLayout.resizeThumbnails();
299
-        }
300
-
301
-        VideoLayout.checkChangeLargeVideo(vid.src);
302
-    };
303
-
304
-    // Add click handler
305
-    sel.click(function () {
306
-        VideoLayout.handleVideoThumbClicked(vid.src);
307
-    });
308
-    // Add hover handler
309
-    $(container).hover(
310
-        function() {
311
-            VideoLayout.showDisplayName(container.id, true);
312
-        },
313
-        function() {
314
-            var videoSrc = null;
315
-            if ($('#' + container.id + '>video')
316
-                    && $('#' + container.id + '>video').length > 0) {
317
-                videoSrc = $('#' + container.id + '>video').get(0).src;
318
-            }
319
-
320
-            // If the video has been "pinned" by the user we want to keep the
321
-            // display name on place.
322
-            if (focusedVideoSrc !== videoSrc)
323
-                VideoLayout.showDisplayName(container.id, false);
324
-        }
325
-    );
326
-
327
     // 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
328
     if (isVideo &&
321
     if (isVideo &&
329
         data.peerjid && sess.peerjid === data.peerjid &&
322
         data.peerjid && sess.peerjid === data.peerjid &&
423
 }
416
 }
424
 
417
 
425
 /**
418
 /**
426
- * Callback called by {@link StatsCollector} in intervals supplied to it's
427
- * constructor.
428
- * @param statsCollector {@link StatsCollector} source of the event.
419
+ * Callback for audio levels changed.
420
+ * @param jid JID of the user
421
+ * @param audioLevel the audio level value
429
  */
422
  */
430
-function statsUpdated(statsCollector)
423
+function audioLevelUpdated(jid, audioLevel)
431
 {
424
 {
432
-    Object.keys(statsCollector.jid2stats).forEach(function (jid)
425
+    var resourceJid;
426
+    if(jid === LocalStatsCollector.LOCAL_JID)
433
     {
427
     {
434
-        var peerStats = statsCollector.jid2stats[jid];
435
-        Object.keys(peerStats.ssrc2AudioLevel).forEach(function (ssrc)
436
-        {
437
-            console.info(jid +  " audio level: " +
438
-                peerStats.ssrc2AudioLevel[ssrc] + " of ssrc: " + ssrc);
439
-        });
440
-    });
428
+        resourceJid = AudioLevels.LOCAL_LEVEL;
429
+        if(isAudioMuted())
430
+            return;
431
+    }
432
+    else
433
+    {
434
+        resourceJid = Strophe.getResourceFromJid(jid);
435
+    }
436
+
437
+    AudioLevels.updateAudioLevel(resourceJid, audioLevel);
441
 }
438
 }
442
 
439
 
443
 /**
440
 /**
445
  */
442
  */
446
 function startRtpStatsCollector()
443
 function startRtpStatsCollector()
447
 {
444
 {
445
+    stopRTPStatsCollector();
448
     if (config.enableRtpStats)
446
     if (config.enableRtpStats)
449
     {
447
     {
450
         statsCollector = new StatsCollector(
448
         statsCollector = new StatsCollector(
451
-            getConferenceHandler().peerconnection, 200, statsUpdated);
452
-
449
+            getConferenceHandler().peerconnection, 200, audioLevelUpdated);
453
         statsCollector.start();
450
         statsCollector.start();
454
     }
451
     }
455
 }
452
 }
456
 
453
 
454
+/**
455
+ * Stops the {@link StatsCollector}.
456
+ */
457
+function stopRTPStatsCollector()
458
+{
459
+    if (statsCollector)
460
+    {
461
+        statsCollector.stop();
462
+        statsCollector = null;
463
+    }
464
+}
465
+
466
+/**
467
+ * Starts the {@link LocalStatsCollector} if the feature is enabled in config.js
468
+ * @param stream the stream that will be used for collecting statistics.
469
+ */
470
+function startLocalRtpStatsCollector(stream)
471
+{
472
+    if(config.enableRtpStats)
473
+    {
474
+        localStatsCollector = new LocalStatsCollector(stream, 100, audioLevelUpdated);
475
+        localStatsCollector.start();
476
+    }
477
+}
478
+
479
+/**
480
+ * Stops the {@link LocalStatsCollector}.
481
+ */
482
+function stopLocalRtpStatsCollector()
483
+{
484
+    if(localStatsCollector)
485
+    {
486
+        localStatsCollector.stop();
487
+        localStatsCollector = null;
488
+    }
489
+}
490
+
457
 $(document).bind('callincoming.jingle', function (event, sid) {
491
 $(document).bind('callincoming.jingle', function (event, sid) {
458
     var sess = connection.jingle.sessions[sid];
492
     var sess = connection.jingle.sessions[sid];
459
 
493
 
490
     }
524
     }
491
 });
525
 });
492
 
526
 
493
-$(document).bind('callactive.jingle', function (event, videoelem, sid) {
494
-    if (videoelem.attr('id').indexOf('mixedmslabel') === -1) {
495
-        // ignore mixedmslabela0 and v0
496
-        videoelem.show();
497
-        VideoLayout.resizeThumbnails();
498
-
499
-        if (!focusedVideoSrc)
500
-            VideoLayout.updateLargeVideo(videoelem.attr('src'), 1);
501
-
502
-        VideoLayout.showFocusIndicator();
503
-    }
504
-});
505
-
506
 $(document).bind('callterminated.jingle', function (event, sid, jid, reason) {
527
 $(document).bind('callterminated.jingle', function (event, sid, jid, reason) {
507
     // Leave the room if my call has been remotely terminated.
528
     // Leave the room if my call has been remotely terminated.
508
     if (connection.emuc.joined && focus == null && reason === 'kick') {
529
     if (connection.emuc.joined && focus == null && reason === 'kick') {
530
             directions[type] = (
551
             directions[type] = (
531
                 SDPUtil.find_line(media, 'a=sendrecv') ||
552
                 SDPUtil.find_line(media, 'a=sendrecv') ||
532
                 SDPUtil.find_line(media, 'a=recvonly') ||
553
                 SDPUtil.find_line(media, 'a=recvonly') ||
533
-                SDPUtil.find_line('a=sendonly') ||
534
-                SDPUtil.find_line('a=inactive') ||
554
+                SDPUtil.find_line(media, 'a=sendonly') ||
555
+                SDPUtil.find_line(media, 'a=inactive') ||
535
                 'a=sendrecv').substr(2);
556
                 'a=sendrecv').substr(2);
536
         }
557
         }
537
     });
558
     });
562
 
583
 
563
     if (Object.keys(connection.emuc.members).length < 1) {
584
     if (Object.keys(connection.emuc.members).length < 1) {
564
         focus = new ColibriFocus(connection, config.hosts.bridge);
585
         focus = new ColibriFocus(connection, config.hosts.bridge);
586
+        if (nickname !== null) {
587
+            focus.setEndpointDisplayName(connection.emuc.myroomjid,
588
+                                         nickname);
589
+        }
590
+        Toolbar.showSipCallButton(true);
591
+        Toolbar.showRecordingButton(false);
592
+    }
593
+
594
+    if (!focus)
595
+    {
596
+        Toolbar.showSipCallButton(false);
565
     }
597
     }
566
 
598
 
567
     if (focus && config.etherpad_base) {
599
     if (focus && config.etherpad_base) {
570
 
602
 
571
     VideoLayout.showFocusIndicator();
603
     VideoLayout.showFocusIndicator();
572
 
604
 
605
+    // Add myself to the contact list.
606
+    ContactList.addContact(jid);
607
+
573
     // Once we've joined the muc show the toolbar
608
     // Once we've joined the muc show the toolbar
574
     Toolbar.showToolbar();
609
     Toolbar.showToolbar();
575
 
610
 
576
     var displayName = '';
611
     var displayName = '';
577
     if (info.displayName)
612
     if (info.displayName)
578
         displayName = info.displayName + ' (me)';
613
         displayName = info.displayName + ' (me)';
614
+    else
615
+        displayName = "Me";
579
 
616
 
580
-    VideoLayout.setDisplayName('localVideoContainer', displayName);
617
+    $(document).trigger('displaynamechanged',
618
+                        ['localVideoContainer', displayName]);
581
 });
619
 });
582
 
620
 
583
 $(document).bind('entered.muc', function (event, jid, info, pres) {
621
 $(document).bind('entered.muc', function (event, jid, info, pres) {
584
     console.log('entered', jid, info);
622
     console.log('entered', jid, info);
623
+
585
     console.log('is focus?' + focus ? 'true' : 'false');
624
     console.log('is focus?' + focus ? 'true' : 'false');
586
 
625
 
587
     // Add Peer's container
626
     // Add Peer's container
592
         if (focus.confid === null) {
631
         if (focus.confid === null) {
593
             console.log('make new conference with', jid);
632
             console.log('make new conference with', jid);
594
             focus.makeConference(Object.keys(connection.emuc.members));
633
             focus.makeConference(Object.keys(connection.emuc.members));
634
+            Toolbar.showRecordingButton(true);
595
         } else {
635
         } else {
596
             console.log('invite', jid, 'into conference');
636
             console.log('invite', jid, 'into conference');
597
             focus.addNewParticipant(jid);
637
             focus.addNewParticipant(jid);
637
             && !sessionTerminated) {
677
             && !sessionTerminated) {
638
         console.log('welcome to our new focus... myself');
678
         console.log('welcome to our new focus... myself');
639
         focus = new ColibriFocus(connection, config.hosts.bridge);
679
         focus = new ColibriFocus(connection, config.hosts.bridge);
680
+        if (nickname !== null) {
681
+            focus.setEndpointDisplayName(connection.emuc.myroomjid,
682
+                                         nickname);
683
+        }
684
+
685
+        Toolbar.showSipCallButton(true);
686
+
640
         if (Object.keys(connection.emuc.members).length > 0) {
687
         if (Object.keys(connection.emuc.members).length > 0) {
641
             focus.makeConference(Object.keys(connection.emuc.members));
688
             focus.makeConference(Object.keys(connection.emuc.members));
689
+            Toolbar.showRecordingButton(true);
642
         }
690
         }
643
         $(document).trigger('focusechanged.muc', [focus]);
691
         $(document).trigger('focusechanged.muc', [focus]);
644
     }
692
     }
645
     else if (focus && Object.keys(connection.emuc.members).length === 0) {
693
     else if (focus && Object.keys(connection.emuc.members).length === 0) {
646
         console.log('everyone left');
694
         console.log('everyone left');
647
         // FIXME: closing the connection is a hack to avoid some
695
         // FIXME: closing the connection is a hack to avoid some
648
-        // problemswith reinit
696
+        // problems with reinit
649
         disposeConference();
697
         disposeConference();
650
         focus = new ColibriFocus(connection, config.hosts.bridge);
698
         focus = new ColibriFocus(connection, config.hosts.bridge);
699
+        if (nickname !== null) {
700
+            focus.setEndpointDisplayName(connection.emuc.myroomjid,
701
+                                         nickname);
702
+        }
703
+        Toolbar.showSipCallButton(true);
704
+        Toolbar.showRecordingButton(false);
651
     }
705
     }
652
     if (connection.emuc.getPrezi(jid)) {
706
     if (connection.emuc.getPrezi(jid)) {
653
         $(document).trigger('presentationremoved.muc',
707
         $(document).trigger('presentationremoved.muc',
662
         if (ssrc2jid[ssrc] == jid) {
716
         if (ssrc2jid[ssrc] == jid) {
663
             delete ssrc2jid[ssrc];
717
             delete ssrc2jid[ssrc];
664
         }
718
         }
665
-        if (ssrc2videoType == jid) {
719
+        if (ssrc2videoType[ssrc] == jid) {
666
             delete ssrc2videoType[ssrc];
720
             delete ssrc2videoType[ssrc];
667
         }
721
         }
668
     });
722
     });
685
             case 'recvonly':
739
             case 'recvonly':
686
                 el.hide();
740
                 el.hide();
687
                 // FIXME: Check if we have to change large video
741
                 // FIXME: Check if we have to change large video
688
-                //VideoLayout.checkChangeLargeVideo(el);
742
+                //VideoLayout.updateLargeVideo(el);
689
                 break;
743
                 break;
690
             }
744
             }
691
         }
745
         }
692
     });
746
     });
693
 
747
 
694
-    if (info.displayName) {
695
-        if (jid === connection.emuc.myroomjid) {
696
-            VideoLayout.setDisplayName('localVideoContainer',
697
-                                        info.displayName + ' (me)');
698
-        } else {
699
-            VideoLayout.ensurePeerContainerExists(jid);
700
-            VideoLayout.setDisplayName(
701
-                    'participant_' + Strophe.getResourceFromJid(jid),
702
-                    info.displayName);
703
-        }
748
+    if (info.displayName && info.displayName.length > 0)
749
+        $(document).trigger('displaynamechanged',
750
+                            [jid, info.displayName]);
751
+
752
+    if (focus !== null && info.displayName !== null) {
753
+        focus.setEndpointDisplayName(jid, info.displayName);
704
     }
754
     }
705
 });
755
 });
706
 
756
 
757
+$(document).bind('presence.status.muc', function (event, jid, info, pres) {
758
+
759
+    VideoLayout.setPresenceStatus(
760
+        'participant_' + Strophe.getResourceFromJid(jid), info.status);
761
+
762
+});
763
+
707
 $(document).bind('passwordrequired.muc', function (event, jid) {
764
 $(document).bind('passwordrequired.muc', function (event, jid) {
708
     console.log('on password required', jid);
765
     console.log('on password required', jid);
709
 
766
 
728
     });
785
     });
729
 });
786
 });
730
 
787
 
788
+$(document).bind('passwordrequired.main', function (event) {
789
+    console.log('password is required');
790
+
791
+    $.prompt('<h2>Password required</h2>' +
792
+        '<input id="passwordrequired.username" type="text" placeholder="user@domain.net" autofocus>' +
793
+        '<input id="passwordrequired.password" type="password" placeholder="user password">', {
794
+        persistent: true,
795
+        buttons: { "Ok": true, "Cancel": false},
796
+        defaultButton: 1,
797
+        loaded: function (event) {
798
+            document.getElementById('passwordrequired.username').focus();
799
+        },
800
+        submit: function (e, v, m, f) {
801
+            if (v) {
802
+                var username = document.getElementById('passwordrequired.username');
803
+                var password = document.getElementById('passwordrequired.password');
804
+
805
+                if (username.value !== null && password.value != null) {
806
+                    connect(username.value, password.value);
807
+                }
808
+            }
809
+        }
810
+    });
811
+});
812
+
731
 /**
813
 /**
732
  * Checks if video identified by given src is desktop stream.
814
  * Checks if video identified by given src is desktop stream.
733
  * @param videoSrc eg.
815
  * @param videoSrc eg.
818
     buttonClick("#mute", "icon-microphone icon-mic-disabled");
900
     buttonClick("#mute", "icon-microphone icon-mic-disabled");
819
 }
901
 }
820
 
902
 
903
+/**
904
+ * Checks whether the audio is muted or not.
905
+ * @returns {boolean} true if audio is muted and false if not.
906
+ */
907
+function isAudioMuted()
908
+{
909
+    var localAudio = connection.jingle.localAudio;
910
+    for (var idx = 0; idx < localAudio.getAudioTracks().length; idx++) {
911
+        if(localAudio.getAudioTracks()[idx].enabled === true)
912
+            return false;
913
+    }
914
+    return true;
915
+}
916
+
917
+// Starts or stops the recording for the conference.
918
+function toggleRecording() {
919
+    if (focus === null || focus.confid === null) {
920
+        console.log('non-focus, or conference not yet organized: not enabling recording');
921
+        return;
922
+    }
923
+
924
+    if (!recordingToken)
925
+    {
926
+        $.prompt('<h2>Enter recording token</h2>' +
927
+                '<input id="recordingToken" type="text" placeholder="token" autofocus>',
928
+            {
929
+                persistent: false,
930
+                buttons: { "Save": true, "Cancel": false},
931
+                defaultButton: 1,
932
+                loaded: function (event) {
933
+                    document.getElementById('recordingToken').focus();
934
+                },
935
+                submit: function (e, v, m, f) {
936
+                    if (v) {
937
+                        var token = document.getElementById('recordingToken');
938
+
939
+                        if (token.value) {
940
+                            setRecordingToken(Util.escapeHtml(token.value));
941
+                            toggleRecording();
942
+                        }
943
+                    }
944
+                }
945
+            }
946
+        );
947
+
948
+        return;
949
+    }
950
+
951
+    var oldState = focus.recordingEnabled;
952
+    Toolbar.toggleRecordingButtonState();
953
+    focus.setRecording(!oldState,
954
+                        recordingToken,
955
+                        function (state) {
956
+                            console.log("New recording state: ", state);
957
+                            if (state == oldState) //failed to change, reset the token because it might have been wrong
958
+                            {
959
+                                Toolbar.toggleRecordingButtonState();
960
+                                setRecordingToken(null);
961
+                            }
962
+                        }
963
+    );
964
+
965
+
966
+}
967
+
821
 /**
968
 /**
822
  * Returns an array of the video horizontal and vertical indents,
969
  * Returns an array of the video horizontal and vertical indents,
823
  * so that if fits its parent.
970
  * so that if fits its parent.
897
 }
1044
 }
898
 
1045
 
899
 $(document).ready(function () {
1046
 $(document).ready(function () {
1047
+
1048
+    if(config.enableWelcomePage && window.location.pathname == "/")
1049
+    {
1050
+        $("#videoconference_page").hide();
1051
+        $("#enter_room_button").click(function()
1052
+        {
1053
+            var val = Util.escapeHtml($("#enter_room_field").val());
1054
+            window.location.pathname = "/" + val;
1055
+        });
1056
+
1057
+        $("#enter_room_field").keydown(function (event) {
1058
+            if (event.keyCode === 13) {
1059
+                var val = Util.escapeHtml(this.value);
1060
+                window.location.pathname = "/" + val;
1061
+            }
1062
+        });
1063
+
1064
+        if(!config.isBrand)
1065
+        {
1066
+            $("#brand_logo").hide();
1067
+            $("#brand_header").hide();
1068
+            $("#header_text").hide();
1069
+        }
1070
+        return;
1071
+    }
1072
+
1073
+    $("#welcome_page").hide();
900
     Chat.init();
1074
     Chat.init();
901
 
1075
 
902
     $('body').popover({ selector: '[data-toggle=popover]',
1076
     $('body').popover({ selector: '[data-toggle=popover]',
960
             }
1134
             }
961
         });
1135
         });
962
     }
1136
     }
963
-    disposeConference();
1137
+    disposeConference(true);
964
 });
1138
 });
965
 
1139
 
966
-function disposeConference() {
1140
+function disposeConference(onUnload) {
967
     var handler = getConferenceHandler();
1141
     var handler = getConferenceHandler();
968
     if (handler && handler.peerconnection) {
1142
     if (handler && handler.peerconnection) {
969
         // FIXME: probably removing streams is not required and close() should be enough
1143
         // FIXME: probably removing streams is not required and close() should be enough
975
         }
1149
         }
976
         handler.peerconnection.close();
1150
         handler.peerconnection.close();
977
     }
1151
     }
978
-    if (statsCollector)
979
-    {
980
-        statsCollector.stop();
981
-        statsCollector = null;
1152
+    stopRTPStatsCollector();
1153
+    if(onUnload) {
1154
+        stopLocalRtpStatsCollector();
982
     }
1155
     }
983
     focus = null;
1156
     focus = null;
984
     activecall = null;
1157
     activecall = null;
1055
     sharedKey = sKey;
1228
     sharedKey = sKey;
1056
 }
1229
 }
1057
 
1230
 
1231
+function setRecordingToken(token) {
1232
+    recordingToken = token;
1233
+}
1234
+
1058
 /**
1235
 /**
1059
  * Updates the room invite url.
1236
  * Updates the room invite url.
1060
  */
1237
  */
1115
 //    }
1292
 //    }
1116
 }
1293
 }
1117
 
1294
 
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
+}
1316
+
1118
 $(document).bind('fatalError.jingle',
1317
 $(document).bind('fatalError.jingle',
1119
     function (event, session, error)
1318
     function (event, session, error)
1120
     {
1319
     {
1124
             "Your browser version is too old. Please update and try again...");
1323
             "Your browser version is too old. Please update and try again...");
1125
     }
1324
     }
1126
 );
1325
 );
1326
+
1327
+function callSipButtonClicked()
1328
+{
1329
+    $.prompt('<h2>Enter SIP number</h2>' +
1330
+        '<input id="sipNumber" type="text" value="" autofocus>',
1331
+        {
1332
+            persistent: false,
1333
+            buttons: { "Dial": true, "Cancel": false},
1334
+            defaultButton: 2,
1335
+            loaded: function (event)
1336
+            {
1337
+                document.getElementById('sipNumber').focus();
1338
+            },
1339
+            submit: function (e, v, m, f)
1340
+            {
1341
+                if (v)
1342
+                {
1343
+                    var numberInput = document.getElementById('sipNumber');
1344
+                    if (numberInput.value && numberInput.value.length)
1345
+                    {
1346
+                        connection.rayo.dial(
1347
+                            numberInput.value, 'fromnumber', roomName);
1348
+                    }
1349
+                }
1350
+            }
1351
+        }
1352
+    );
1353
+}

+ 217
- 0
audio_levels.js Visa fil

1
+/**
2
+ * The audio Levels plugin.
3
+ */
4
+var AudioLevels = (function(my) {
5
+    var CANVAS_EXTRA = 104;
6
+    var CANVAS_RADIUS = 7;
7
+    var SHADOW_COLOR = '#00ccff';
8
+    var audioLevelCanvasCache = {};
9
+
10
+    my.LOCAL_LEVEL = 'local';
11
+
12
+    /**
13
+     * Updates the audio level canvas for the given peerJid. If the canvas
14
+     * didn't exist we create it.
15
+     */
16
+    my.updateAudioLevelCanvas = function (peerJid) {
17
+        var resourceJid = null;
18
+        var videoSpanId = null;
19
+        if (!peerJid)
20
+            videoSpanId = 'localVideoContainer';
21
+        else {
22
+            resourceJid = Strophe.getResourceFromJid(peerJid);
23
+
24
+            videoSpanId = 'participant_' + resourceJid;
25
+        }
26
+
27
+        videoSpan = document.getElementById(videoSpanId);
28
+
29
+        if (!videoSpan) {
30
+            if (resourceJid)
31
+                console.error("No video element for jid", resourceJid);
32
+            else
33
+                console.error("No video element for local video.");
34
+
35
+            return;
36
+        }
37
+
38
+        var audioLevelCanvas = $('#' + videoSpanId + '>canvas');
39
+
40
+        var videoSpaceWidth = $('#remoteVideos').width();
41
+        var thumbnailSize
42
+            = VideoLayout.calculateThumbnailSize(videoSpaceWidth);
43
+        var thumbnailWidth = thumbnailSize[0];
44
+        var thumbnailHeight = thumbnailSize[1];
45
+
46
+        if (!audioLevelCanvas || audioLevelCanvas.length === 0) {
47
+
48
+            audioLevelCanvas = document.createElement('canvas');
49
+            audioLevelCanvas.className = "audiolevel";
50
+            audioLevelCanvas.style.bottom = "-" + CANVAS_EXTRA/2 + "px";
51
+            audioLevelCanvas.style.left = "-" + CANVAS_EXTRA/2 + "px";
52
+            resizeAudioLevelCanvas( audioLevelCanvas,
53
+                    thumbnailWidth,
54
+                    thumbnailHeight);
55
+
56
+            videoSpan.appendChild(audioLevelCanvas);
57
+        } else {
58
+            audioLevelCanvas = audioLevelCanvas.get(0);
59
+
60
+            resizeAudioLevelCanvas( audioLevelCanvas,
61
+                    thumbnailWidth,
62
+                    thumbnailHeight);
63
+        }
64
+    };
65
+
66
+    /**
67
+     * Updates the audio level UI for the given resourceJid.
68
+     *
69
+     * @param resourceJid the resource jid indicating the video element for
70
+     * which we draw the audio level
71
+     * @param audioLevel the newAudio level to render
72
+     */
73
+    my.updateAudioLevel = function (resourceJid, audioLevel) {
74
+        drawAudioLevelCanvas(resourceJid, audioLevel);
75
+
76
+        var videoSpanId = getVideoSpanId(resourceJid);
77
+
78
+        var audioLevelCanvas = $('#' + videoSpanId + '>canvas').get(0);
79
+
80
+        if (!audioLevelCanvas)
81
+            return;
82
+
83
+        var drawContext = audioLevelCanvas.getContext('2d');
84
+
85
+        var canvasCache = audioLevelCanvasCache[resourceJid];
86
+
87
+        drawContext.clearRect (0, 0,
88
+                audioLevelCanvas.width, audioLevelCanvas.height);
89
+        drawContext.drawImage(canvasCache, 0, 0);
90
+    };
91
+
92
+    /**
93
+     * Resizes the given audio level canvas to match the given thumbnail size.
94
+     */
95
+    function resizeAudioLevelCanvas(audioLevelCanvas,
96
+                                    thumbnailWidth,
97
+                                    thumbnailHeight) {
98
+        audioLevelCanvas.width = thumbnailWidth + CANVAS_EXTRA;
99
+        audioLevelCanvas.height = thumbnailHeight + CANVAS_EXTRA;
100
+    };
101
+
102
+    /**
103
+     * Draws the audio level canvas into the cached canvas object.
104
+     *
105
+     * @param resourceJid the resource jid indicating the video element for
106
+     * which we draw the audio level
107
+     * @param audioLevel the newAudio level to render
108
+     */
109
+    function drawAudioLevelCanvas(resourceJid, audioLevel) {
110
+        if (!audioLevelCanvasCache[resourceJid]) {
111
+
112
+            var videoSpanId = getVideoSpanId(resourceJid);
113
+
114
+            var audioLevelCanvasOrig = $('#' + videoSpanId + '>canvas').get(0);
115
+
116
+            /*
117
+             * FIXME Testing has shown that audioLevelCanvasOrig may not exist.
118
+             * In such a case, the method CanvasUtil.cloneCanvas may throw an
119
+             * error. Since audio levels are frequently updated, the errors have
120
+             * been observed to pile into the console, strain the CPU.
121
+             */
122
+            if (audioLevelCanvasOrig)
123
+            {
124
+                audioLevelCanvasCache[resourceJid]
125
+                    = CanvasUtil.cloneCanvas(audioLevelCanvasOrig);
126
+            }
127
+        }
128
+
129
+        var canvas = audioLevelCanvasCache[resourceJid];
130
+
131
+        if (!canvas)
132
+            return;
133
+
134
+        var drawContext = canvas.getContext('2d');
135
+
136
+        drawContext.clearRect(0, 0, canvas.width, canvas.height);
137
+
138
+        var shadowLevel = getShadowLevel(audioLevel);
139
+
140
+        if (shadowLevel > 0)
141
+            // drawContext, x, y, w, h, r, shadowColor, shadowLevel
142
+            CanvasUtil.drawRoundRectGlow(   drawContext,
143
+                                            CANVAS_EXTRA/2, CANVAS_EXTRA/2,
144
+                                            canvas.width - CANVAS_EXTRA,
145
+                                            canvas.height - CANVAS_EXTRA,
146
+                                            CANVAS_RADIUS,
147
+                                            SHADOW_COLOR,
148
+                                            shadowLevel);
149
+    };
150
+
151
+    /**
152
+     * Returns the shadow/glow level for the given audio level.
153
+     *
154
+     * @param audioLevel the audio level from which we determine the shadow
155
+     * level
156
+     */
157
+    function getShadowLevel (audioLevel) {
158
+        var shadowLevel = 0;
159
+
160
+        if (audioLevel <= 0.3) {
161
+            shadowLevel = Math.round(CANVAS_EXTRA/2*(audioLevel/0.3));
162
+        }
163
+        else if (audioLevel <= 0.6) {
164
+            shadowLevel = Math.round(CANVAS_EXTRA/2*((audioLevel - 0.3) / 0.3));
165
+        }
166
+        else {
167
+            shadowLevel = Math.round(CANVAS_EXTRA/2*((audioLevel - 0.6) / 0.4));
168
+        }
169
+        return shadowLevel;
170
+    };
171
+
172
+    /**
173
+     * Returns the video span id corresponding to the given resourceJid or local
174
+     * user.
175
+     */
176
+    function getVideoSpanId(resourceJid) {
177
+        var videoSpanId = null;
178
+        if (resourceJid === AudioLevels.LOCAL_LEVEL
179
+                || (connection.emuc.myroomjid && resourceJid
180
+                    === Strophe.getResourceFromJid(connection.emuc.myroomjid)))
181
+            videoSpanId = 'localVideoContainer';
182
+        else
183
+            videoSpanId = 'participant_' + resourceJid;
184
+
185
+        return videoSpanId;
186
+    };
187
+
188
+    /**
189
+     * Indicates that the remote video has been resized.
190
+     */
191
+    $(document).bind('remotevideo.resized', function (event, width, height) {
192
+        var resized = false;
193
+        $('#remoteVideos>span>canvas').each(function() {
194
+            var canvas = $(this).get(0);
195
+            if (canvas.width !== width + CANVAS_EXTRA) {
196
+                canvas.width = width + CANVAS_EXTRA;
197
+                resized = true;
198
+            }
199
+
200
+            if (canvas.heigh !== height + CANVAS_EXTRA) {
201
+                canvas.height = height + CANVAS_EXTRA;
202
+                resized = true;
203
+            }
204
+        });
205
+
206
+        if (resized)
207
+            Object.keys(audioLevelCanvasCache).forEach(function (resourceJid) {
208
+                audioLevelCanvasCache[resourceJid].width
209
+                    = width + CANVAS_EXTRA;
210
+                audioLevelCanvasCache[resourceJid].height
211
+                    = height + CANVAS_EXTRA;
212
+            });
213
+    });
214
+
215
+    return my;
216
+
217
+})(AudioLevels || {});

+ 32
- 0
bottom_toolbar.js Visa fil

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 || {}));

+ 109
- 0
canvas_util.js Visa fil

1
+/**
2
+ * Utility class for drawing canvas shapes.
3
+ */
4
+var CanvasUtil = (function(my) {
5
+
6
+    /**
7
+     * Draws a round rectangle with a glow. The glowWidth indicates the depth
8
+     * of the glow.
9
+     *
10
+     * @param drawContext the context of the canvas to draw to
11
+     * @param x the x coordinate of the round rectangle
12
+     * @param y the y coordinate of the round rectangle
13
+     * @param w the width of the round rectangle
14
+     * @param h the height of the round rectangle
15
+     * @param glowColor the color of the glow
16
+     * @param glowWidth the width of the glow
17
+     */
18
+    my.drawRoundRectGlow
19
+        = function(drawContext, x, y, w, h, r, glowColor, glowWidth) {
20
+
21
+        // Save the previous state of the context.
22
+        drawContext.save();
23
+
24
+        if (w < 2 * r) r = w / 2;
25
+        if (h < 2 * r) r = h / 2;
26
+
27
+        // Draw a round rectangle.
28
+        drawContext.beginPath();
29
+        drawContext.moveTo(x+r, y);
30
+        drawContext.arcTo(x+w, y,   x+w, y+h, r);
31
+        drawContext.arcTo(x+w, y+h, x,   y+h, r);
32
+        drawContext.arcTo(x,   y+h, x,   y,   r);
33
+        drawContext.arcTo(x,   y,   x+w, y,   r);
34
+        drawContext.closePath();
35
+
36
+        // Add a shadow around the rectangle
37
+        drawContext.shadowColor = glowColor;
38
+        drawContext.shadowBlur = glowWidth;
39
+        drawContext.shadowOffsetX = 0;
40
+        drawContext.shadowOffsetY = 0;
41
+
42
+        // Fill the shape.
43
+        drawContext.fill();
44
+
45
+        drawContext.save();
46
+
47
+        drawContext.restore();
48
+
49
+//      1) Uncomment this line to use Composite Operation, which is doing the
50
+//      same as the clip function below and is also antialiasing the round
51
+//      border, but is said to be less fast performance wise.
52
+
53
+//      drawContext.globalCompositeOperation='destination-out';
54
+
55
+        drawContext.beginPath();
56
+        drawContext.moveTo(x+r, y);
57
+        drawContext.arcTo(x+w, y,   x+w, y+h, r);
58
+        drawContext.arcTo(x+w, y+h, x,   y+h, r);
59
+        drawContext.arcTo(x,   y+h, x,   y,   r);
60
+        drawContext.arcTo(x,   y,   x+w, y,   r);
61
+        drawContext.closePath();
62
+
63
+//      2) Uncomment this line to use Composite Operation, which is doing the
64
+//      same as the clip function below and is also antialiasing the round
65
+//      border, but is said to be less fast performance wise.
66
+
67
+//      drawContext.fill();
68
+
69
+        // Comment these two lines if choosing to do the same with composite
70
+        // operation above 1 and 2.
71
+        drawContext.clip();
72
+        drawContext.clearRect(0, 0, 277, 200);
73
+
74
+        // Restore the previous context state.
75
+        drawContext.restore();
76
+    };
77
+
78
+    /**
79
+     * Clones the given canvas.
80
+     *
81
+     * @return the new cloned canvas.
82
+     */
83
+    my.cloneCanvas = function (oldCanvas) {
84
+        /*
85
+         * FIXME Testing has shown that oldCanvas may not exist. In such a case,
86
+         * the method CanvasUtil.cloneCanvas may throw an error. Since audio
87
+         * levels are frequently updated, the errors have been observed to pile
88
+         * into the console, strain the CPU.
89
+         */
90
+        if (!oldCanvas)
91
+            return oldCanvas;
92
+
93
+        //create a new canvas
94
+        var newCanvas = document.createElement('canvas');
95
+        var context = newCanvas.getContext('2d');
96
+
97
+        //set dimensions
98
+        newCanvas.width = oldCanvas.width;
99
+        newCanvas.height = oldCanvas.height;
100
+
101
+        //apply the old canvas to the new one
102
+        context.drawImage(oldCanvas, 0, 0);
103
+
104
+        //return the new canvas
105
+        return newCanvas;
106
+    };
107
+
108
+    return my;
109
+})(CanvasUtil || {});

+ 49
- 6
chat.js Visa fil

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);
115
             +  '</div>');
115
             +  '</div>');
116
         $('#chatconversation').animate(
116
         $('#chatconversation').animate(
117
             { scrollTop: $('#chatconversation')[0].scrollHeight}, 1000);
117
             { scrollTop: $('#chatconversation')[0].scrollHeight}, 1000);
118
-
119
-    }
118
+    };
120
 
119
 
121
     /**
120
     /**
122
      * Sets the subject to the UI
121
      * Sets the subject to the UI
135
         {
134
         {
136
             $("#subject").css({display: "block"});
135
             $("#subject").css({display: "block"});
137
         }
136
         }
138
-    }
139
-
137
+    };
140
 
138
 
141
     /**
139
     /**
142
      * Opens / closes the chat area.
140
      * Opens / closes the chat area.
159
         var horizontalIndent = videoPosition[0];
157
         var horizontalIndent = videoPosition[0];
160
         var verticalIndent = videoPosition[1];
158
         var verticalIndent = videoPosition[1];
161
 
159
 
160
+        var thumbnailSize = VideoLayout.calculateThumbnailSize(videospaceWidth);
161
+        var thumbnailsWidth = thumbnailSize[0];
162
+        var thumbnailsHeight = thumbnailSize[1];
163
+
162
         if (chatspace.is(":visible")) {
164
         if (chatspace.is(":visible")) {
163
             videospace.animate({right: chatSize[0],
165
             videospace.animate({right: chatSize[0],
164
                                 width: videospaceWidth,
166
                                 width: videospaceWidth,
166
                                 {queue: false,
168
                                 {queue: false,
167
                                 duration: 500});
169
                                 duration: 500});
168
 
170
 
171
+            $('#remoteVideos').animate({height: thumbnailsHeight},
172
+                                        {queue: false,
173
+                                        duration: 500});
174
+
175
+            $('#remoteVideos>span').animate({height: thumbnailsHeight,
176
+                                            width: thumbnailsWidth},
177
+                                            {queue: false,
178
+                                            duration: 500,
179
+                                            complete: function() {
180
+                                                $(document).trigger(
181
+                                                        "remotevideo.resized",
182
+                                                        [thumbnailsWidth,
183
+                                                         thumbnailsHeight]);
184
+                                            }});
185
+
169
             $('#largeVideoContainer').animate({ width: videospaceWidth,
186
             $('#largeVideoContainer').animate({ width: videospaceWidth,
170
                                                 height: videospaceHeight},
187
                                                 height: videospaceHeight},
171
                                                 {queue: false,
188
                                                 {queue: false,
187
                                             duration: 500});
204
                                             duration: 500});
188
         }
205
         }
189
         else {
206
         else {
207
+            // Undock the toolbar when the chat is shown and if we're in a 
208
+            // video mode.
209
+            if (VideoLayout.isLargeVideoVisible())
210
+                Toolbar.dockToolbar(false);
211
+
190
             videospace.animate({right: chatSize[0],
212
             videospace.animate({right: chatSize[0],
191
                                 width: videospaceWidth,
213
                                 width: videospaceWidth,
192
                                 height: videospaceHeight},
214
                                 height: videospaceHeight},
198
                                 }
220
                                 }
199
                                });
221
                                });
200
 
222
 
223
+            $('#remoteVideos').animate({height: thumbnailsHeight},
224
+                    {queue: false,
225
+                    duration: 500});
226
+
227
+            $('#remoteVideos>span').animate({height: thumbnailsHeight,
228
+                        width: thumbnailsWidth},
229
+                        {queue: false,
230
+                        duration: 500,
231
+                        complete: function() {
232
+                            $(document).trigger(
233
+                                    "remotevideo.resized",
234
+                                    [thumbnailsWidth, thumbnailsHeight]);
235
+                        }});
236
+
201
             $('#largeVideoContainer').animate({ width: videospaceWidth,
237
             $('#largeVideoContainer').animate({ width: videospaceWidth,
202
                                                 height: videospaceHeight},
238
                                                 height: videospaceHeight},
203
                                                 {queue: false,
239
                                                 {queue: false,
265
         return [chatWidth, availableHeight];
301
         return [chatWidth, availableHeight];
266
     };
302
     };
267
 
303
 
304
+    /**
305
+     * Indicates if the chat is currently visible.
306
+     */
307
+    my.isVisible = function () {
308
+        return $('#chatspace').is(":visible");
309
+    };
310
+
268
     /**
311
     /**
269
      * Resizes the chat conversation.
312
      * Resizes the chat conversation.
270
      */
313
      */
290
         if (unreadMessages) {
333
         if (unreadMessages) {
291
             unreadMsgElement.innerHTML = unreadMessages.toString();
334
             unreadMsgElement.innerHTML = unreadMessages.toString();
292
 
335
 
293
-            Toolbar.showToolbar();
336
+            Toolbar.dockToolbar(true);
294
 
337
 
295
             var chatButtonElement
338
             var chatButtonElement
296
                 = document.getElementById('chatButton').parentNode;
339
                 = document.getElementById('chatButton').parentNode;

+ 15
- 7
config.js Visa fil

1
 var config = {
1
 var config = {
2
     hosts: {
2
     hosts: {
3
-        domain: 'guest.jit.si',
4
-        muc: 'meet.jit.si', // FIXME: use XEP-0030
5
-        bridge: 'jitsi-videobridge.lambada.jitsi.net' // FIXME: use XEP-0030
3
+        domain: 'jitsi-meet.example.com',
4
+        //anonymousdomain: 'guest.example.com',
5
+        muc: 'conference.jitsi-meet.example.com', // FIXME: use XEP-0030
6
+        bridge: 'jitsi-videobridge.jitsi-meet.example.com', // FIXME: use XEP-0030
7
+        //call_control: 'callcontrol.jitsi-meet.example.com'
6
     },
8
     },
7
 //  getroomnode: function (path) { return 'someprefixpossiblybasedonpath'; },
9
 //  getroomnode: function (path) { return 'someprefixpossiblybasedonpath'; },
8
 //  useStunTurn: true, // use XEP-0215 to fetch STUN and TURN server
10
 //  useStunTurn: true, // use XEP-0215 to fetch STUN and TURN server
9
 //  useIPv6: true, // ipv6 support. use at your own risk
11
 //  useIPv6: true, // ipv6 support. use at your own risk
10
     useNicks: false,
12
     useNicks: false,
11
-    bosh: '//lambada.jitsi.net/http-bind', // FIXME: use xep-0156 for that
13
+    bosh: '//jitsi-meet.example.com/http-bind', // FIXME: use xep-0156 for that
12
     desktopSharing: 'ext', // Desktop sharing method. Can be set to 'ext', 'webrtc' or false to disable.
14
     desktopSharing: 'ext', // Desktop sharing method. Can be set to 'ext', 'webrtc' or false to disable.
13
     chromeExtensionId: 'diibjkoicjeejcmhdnailmkgecihlobk', // Id of desktop streamer Chrome extension
15
     chromeExtensionId: 'diibjkoicjeejcmhdnailmkgecihlobk', // Id of desktop streamer Chrome extension
14
     minChromeExtVersion: '0.1', // Required version of Chrome extension
16
     minChromeExtVersion: '0.1', // Required version of Chrome extension
15
-    enableRtpStats: false, // Enables RTP stats processing
16
-    openSctp: true //Toggle to enable/disable SCTP channels
17
-};
17
+    enableRtpStats: true, // Enables RTP stats processing
18
+    openSctp: true, // Toggle to enable/disable SCTP channels
19
+    channelLastN: -1, // The default value of the channel attribute last-n.
20
+//    useRtcpMux: true,
21
+//    useBundle: true,
22
+    enableRecording: false,
23
+    enableWelcomePage: false,
24
+    isBrand: false
25
+};

+ 235
- 0
contact_list.js Visa fil

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 Visa fil

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
+}

+ 21
- 1
css/font.css Visa fil

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
-
26
+.icon-contactList:before {
27
+    content: "\e615";
28
+}
29
+.icon-avatar:before {
30
+    content: "\e616";
31
+}
32
+.icon-callRetro:before {
33
+    content: "\e611";
34
+}
35
+.icon-callModern:before {
36
+    content: "\e612";
37
+}
38
+.icon-recDisable:before {
39
+    content: "\e613";
40
+}
41
+.icon-recEnable:before {
42
+    content: "\e614";
43
+}
27
 .icon-kick1:before {
44
 .icon-kick1:before {
28
     content: "\e60f";
45
     content: "\e60f";
29
 }
46
 }
60
 .icon-share-doc:before {
77
 .icon-share-doc:before {
61
 	content: "\e605";
78
 	content: "\e605";
62
 }
79
 }
80
+.icon-telephone:before {
81
+    content: "\e611";
82
+}
63
 .icon-security-locked:before {
83
 .icon-security-locked:before {
64
 	content: "\e607";
84
 	content: "\e607";
65
 }
85
 }

+ 240
- 14
css/main.css Visa fil

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;
131
 }
134
 }
132
 
135
 
133
 #chatButton {
136
 #chatButton {
134
-    -webkit-transition: all .5s ease-in-out;;
135
-       -moz-transition: all .5s ease-in-out;;
136
-            transition: all .5s ease-in-out;;
137
+    -webkit-transition: all .5s ease-in-out;
138
+       -moz-transition: all .5s ease-in-out;
139
+            transition: all .5s ease-in-out;
137
 }
140
 }
138
-
141
+/*#ffde00*/
139
 #chatButton.active {
142
 #chatButton.active {
140
-    -webkit-text-shadow: 0 0 10px #ffffff;
141
-    -moz-text-shadow: 0 0 10px #ffffff;
142
-    text-shadow: 0 0 10px #ffffff;
143
+    -webkit-text-shadow:    -1px 0 10px #00ccff,
144
+                            0 1px 10px #00ccff,
145
+                            1px 0 10px #00ccff,
146
+                            0 -1px 10px #00ccff;
147
+    -moz-text-shadow:   1px 0 10px #00ccff,
148
+                        0 1px 10px #00ccff,
149
+                        1px 0 10px #00ccff,
150
+                        0 -1px 10px #00ccff;
151
+    text-shadow:    -1px 0 10px #00ccff,
152
+                    0 1px 10px #00ccff,
153
+                    1px 0 10px #00ccff,
154
+                    0 -1px 10px #00ccff;
143
 }
155
 }
144
 
156
 
145
-a.button:hover {
157
+#recordButton {
158
+    -webkit-transition: all .5s ease-in-out;
159
+    -moz-transition: all .5s ease-in-out;
160
+    transition: all .5s ease-in-out;
161
+}
162
+/*#ffde00*/
163
+#recordButton.active {
164
+    -webkit-text-shadow:    -1px 0 10px #00ccff,
165
+    0 1px 10px #00ccff,
166
+    1px 0 10px #00ccff,
167
+    0 -1px 10px #00ccff;
168
+    -moz-text-shadow:   1px 0 10px #00ccff,
169
+    0 1px 10px #00ccff,
170
+    1px 0 10px #00ccff,
171
+    0 -1px 10px #00ccff;
172
+    text-shadow:    -1px 0 10px #00ccff,
173
+    0 1px 10px #00ccff,
174
+    1px 0 10px #00ccff,
175
+    0 -1px 10px #00ccff;
176
+}
177
+
178
+a.button:hover,
179
+a.bottomToolbarButton:hover {
146
     top: 0;
180
     top: 0;
147
     cursor: pointer;
181
     cursor: pointer;
148
     background: rgba(0, 0, 0, 0.3);
182
     background: rgba(0, 0, 0, 0.3);
165
     background: #676767;
199
     background: #676767;
166
 }
200
 }
167
 
201
 
168
-input[type='text'], textarea {
202
+input[type='text'], input[type='password'], textarea {
169
     display: inline-block;
203
     display: inline-block;
170
     font-size: 14px;
204
     font-size: 14px;
171
     padding: 5px;
205
     padding: 5px;
181
     resize: none; /* prevents the user-resizing, adjust to taste */
215
     resize: none; /* prevents the user-resizing, adjust to taste */
182
 }
216
 }
183
 
217
 
184
-input[type='text'], textarea:focus {
218
+input[type='text'], input[type='password'], textarea:focus {
185
     box-shadow: inset 0 0 3px 2px #ACD8F0; /* provides a more style-able
219
     box-shadow: inset 0 0 3px 2px #ACD8F0; /* provides a more style-able
186
                                          replacement to the outline */
220
                                          replacement to the outline */
187
 }
221
 }
229
     overflow: visible;
263
     overflow: visible;
230
     z-index: 100;
264
     z-index: 100;
231
 }
265
 }
266
+
267
+#enter_room_field {
268
+    border-radius: 10px;
269
+    font-size: 16px;
270
+    padding: 15px 55px 10px 30px;
271
+    border: none;
272
+    -moz-border-radius: 10px;
273
+    -webkit-border-radius: 10px;
274
+    -webkit-appearance: none;
275
+    width: 318px;
276
+    height: 55px;
277
+    position:absolute;
278
+    font-weight: 500;
279
+    font-family: Helvetica;
280
+    box-shadow: none;
281
+    z-index: 2;
282
+ }
283
+
284
+#enter_room_button {
285
+    width: 73px;
286
+    height: 45px;
287
+    background-color: #16a8fe;
288
+    moz-border-radius: 15px;
289
+    -webkit-border-radius: 15px;
290
+    color: #ffffff;
291
+    font-weight: 600;
292
+    border: none;
293
+    position:absolute;
294
+    margin-left: 240px;
295
+    margin-top: 5px;
296
+    font-size: 19px;
297
+    font-family: Helvetica;
298
+    padding-top: 6px;
299
+    z-index: 2;
300
+    outline: none;
301
+}
302
+
303
+#enter_room {
304
+    margin: 70px auto 0px auto;
305
+    width:318px;
306
+}
307
+
308
+#welcome_page_header
309
+{
310
+    background-image: url(../images/welcome_page/pattern-header.png);
311
+    height: 290px;
312
+    width: 100%;
313
+    position: absolute;
314
+}
315
+
316
+#welcome_page_main
317
+{
318
+    background-image:url(../images/welcome_page/pattern-body.png);
319
+    width: 100%;
320
+    position: absolute;
321
+    margin-top: 290px;
322
+}
323
+
324
+#jitsi_logo
325
+{
326
+    background-image:url(../images/welcome_page/jitsi-logo.png);
327
+    width: 186px;
328
+    height: 74px;
329
+    position: absolute;
330
+    top: 15px;
331
+    left: 30px;
332
+}
333
+
334
+#brand_logo
335
+{
336
+    background-image:url(../images/welcome_page/brand-logo.png);
337
+    width: 215px;
338
+    height: 55px;
339
+    position: absolute;
340
+    top: 15px;
341
+    right: 30px;
342
+
343
+}
344
+
345
+#brand_header
346
+{
347
+    background-image:url(../images/welcome_page/header-big.png);
348
+    position:absolute;
349
+    width: 583px;
350
+    height: 274px;
351
+    left: 340px;
352
+    top:15px;
353
+}
354
+
355
+#header_text
356
+{
357
+    position: absolute;
358
+    left: 200px;
359
+    top: 150px;
360
+    width: 885px;
361
+    height: 100px;
362
+    color: #ffffff;
363
+    font-family: Helvetica;
364
+    font-size: 24px;
365
+    text-align: center;
366
+}
367
+
368
+#features
369
+{
370
+    margin-top: 30px;
371
+}
372
+
373
+.feature_row
374
+{
375
+    width: 100%;
376
+    left: 115px;
377
+    position: relative;
378
+    float: left;
379
+    margin-bottom: 30px;
380
+}
381
+
382
+.feature_holder
383
+{
384
+    float:left;
385
+    width: 169px;
386
+    padding-left: 75px;
387
+}
388
+
389
+.feature_icon
390
+{
391
+    background-image:url(../images/welcome_page/bubble.png);
392
+    background-repeat: no-repeat;
393
+    width: 169px;
394
+    height: 169px;
395
+    font-family: Helvetica;
396
+    color: #ffffff;
397
+    font-size: 22px;
398
+    /*font-weight: bold;*/
399
+    text-align: center;
400
+    display: table-cell;
401
+    padding: 50px 29px 0px 17px;
402
+}
403
+
404
+.feature_description
405
+{
406
+    width: 169px;
407
+    font-family: Helvetica;
408
+    color: #ffffff;
409
+    font-size: 16px;
410
+    padding-top: 30px;
411
+    line-height: 22px;
412
+    font-weight: 200;
413
+}
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
+}
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
+}

+ 1
- 1
css/modaldialog.css Visa fil

15
     margin: 10px 0;
15
     margin: 10px 0;
16
 }
16
 }
17
 
17
 
18
-.jqistates input[type="text"] {
18
+.jqistates input[type='text'], input[type='password'] {
19
     width: 100%;
19
     width: 100%;
20
 }
20
 }
21
 
21
 

+ 53
- 7
css/videolayout_default.css Visa fil

4
     top: 0px;
4
     top: 0px;
5
     left: 0px;
5
     left: 0px;
6
     right: 0px;
6
     right: 0px;
7
+    overflow: hidden;
7
 }
8
 }
8
 
9
 
9
 #remoteVideos {
10
 #remoteVideos {
14
     padding: 18px;
15
     padding: 18px;
15
     bottom: 0;
16
     bottom: 0;
16
     left: 0;
17
     left: 0;
17
-    right: 0;
18
+    right: 20px;
18
     width:auto;
19
     width:auto;
19
     border:1px solid transparent;
20
     border:1px solid transparent;
20
     z-index: 5;
21
     z-index: 5;
32
     background-size: contain;
33
     background-size: contain;
33
     border-radius:8px;
34
     border-radius:8px;
34
     border: 2px solid #212425;
35
     border: 2px solid #212425;
36
+    margin-right: 3px;
35
 }
37
 }
36
 
38
 
37
 #remoteVideos .videocontainer:hover,
39
 #remoteVideos .videocontainer:hover,
48
     -webkit-animation-name: greyPulse;
50
     -webkit-animation-name: greyPulse;
49
     -webkit-animation-duration: 2s;
51
     -webkit-animation-duration: 2s;
50
     -webkit-animation-iteration-count: 1;
52
     -webkit-animation-iteration-count: 1;
51
-    -webkit-box-shadow: 0 0 18px #388396;
52
-    border: 2px solid #388396;
53
-    z-index: 3;
53
+}
54
+
55
+#remoteVideos .videocontainer:hover {
56
+    -webkit-box-shadow: inset 0 0 10px #FFFFFF, 0 0 10px #FFFFFF;
57
+    border: 2px solid #FFFFFF;
58
+}
59
+
60
+#remoteVideos .videocontainer.videoContainerFocused {
61
+    -webkit-box-shadow: inset 0 0 28px #006d91;
62
+    border: 2px solid #006d91;
63
+}
64
+
65
+#remoteVideos .videocontainer.videoContainerFocused:hover {
66
+    -webkit-box-shadow: inset 0 0 5px #FFFFFF, 0 0 10px #FFFFFF, inset 0 0 60px #006d91;
67
+    border: 2px solid #FFFFFF;
54
 }
68
 }
55
 
69
 
56
 #localVideoWrapper {
70
 #localVideoWrapper {
94
     height: 100%;
108
     height: 100%;
95
 }
109
 }
96
 
110
 
97
-.activespeaker {
98
-    -webkit-filter: grayscale(1);
99
-    filter: grayscale(1);
111
+.dominantspeaker {
112
+    background: #000 !important;
100
 }
113
 }
101
 
114
 
102
 #etherpad,
115
 #etherpad,
158
     border-radius:20px;
171
     border-radius:20px;
159
 }
172
 }
160
 
173
 
174
+.videocontainer>span.status {
175
+    display: inline-block;
176
+    position: absolute;
177
+    color: #FFFFFF;
178
+    background: rgba(0,0,0,.7);
179
+    text-align: center;
180
+    text-overflow: ellipsis;
181
+    width: 70%;
182
+    height: 15%;
183
+    left: 15%;
184
+    bottom: 2%;
185
+    padding: 5px;
186
+    font-size: 10pt;
187
+    overflow: hidden;
188
+    white-space: nowrap;
189
+    z-index: 2;
190
+    border-radius:20px;
191
+}
192
+
193
+#localVideoContainer>span.status:hover,
161
 #localVideoContainer>span.displayname:hover {
194
 #localVideoContainer>span.displayname:hover {
162
     cursor: text;
195
     cursor: text;
163
 }
196
 }
164
 
197
 
198
+.videocontainer>span.status,
165
 .videocontainer>span.displayname {
199
 .videocontainer>span.displayname {
166
     pointer-events: none;
200
     pointer-events: none;
167
 }
201
 }
174
     pointer-events: auto !important;
208
     pointer-events: auto !important;
175
 }
209
 }
176
 
210
 
211
+.videocontainer>a.status,
177
 .videocontainer>a.displayname {
212
 .videocontainer>a.displayname {
178
     display: inline-block;
213
     display: inline-block;
179
     position: absolute;
214
     position: absolute;
292
     background-image:url(../images/rightwatermark.png);
327
     background-image:url(../images/rightwatermark.png);
293
     background-position: center right;
328
     background-position: center right;
294
 }
329
 }
330
+
331
+.audiolevel {
332
+    display: inline-block;
333
+    position: absolute;
334
+    z-index: 0;
335
+    border-radius:10px;
336
+}
337
+
338
+#mixedstream {
339
+    display:none !important;
340
+}

+ 62
- 20
data_channels.js Visa fil

1
 /* global connection, Strophe, updateLargeVideo, focusedVideoSrc*/
1
 /* global connection, Strophe, updateLargeVideo, focusedVideoSrc*/
2
+
3
+// cache datachannels to avoid garbage collection
4
+// https://code.google.com/p/chromium/issues/detail?id=405545
5
+var _dataChannels = [];
6
+
2
 /**
7
 /**
3
  * Callback triggered by PeerConnection when new data channel is opened
8
  * Callback triggered by PeerConnection when new data channel is opened
4
  * on the bridge.
9
  * on the bridge.
5
  * @param event the event info object.
10
  * @param event the event info object.
6
  */
11
  */
12
+
7
 function onDataChannel(event)
13
 function onDataChannel(event)
8
 {
14
 {
9
     var dataChannel = event.channel;
15
     var dataChannel = event.channel;
10
 
16
 
11
     dataChannel.onopen = function ()
17
     dataChannel.onopen = function ()
12
     {
18
     {
13
-        console.info("Data channel opened by the bridge !!!", dataChannel);
19
+        console.info("Data channel opened by the Videobridge!", dataChannel);
14
 
20
 
15
         // Code sample for sending string and/or binary data
21
         // Code sample for sending string and/or binary data
16
         // Sends String message to the bridge
22
         // Sends String message to the bridge
26
 
32
 
27
     dataChannel.onmessage = function (event)
33
     dataChannel.onmessage = function (event)
28
     {
34
     {
29
-        var msgData = event.data;
30
-        console.info("Got Data Channel Message:", msgData, dataChannel);
35
+        var data = event.data;
36
+        // JSON
37
+        var obj;
31
 
38
 
32
-        // Active speaker event
33
-        if (msgData.indexOf('activeSpeaker') === 0 && !focusedVideoSrc)
39
+        try
40
+        {
41
+            obj = JSON.parse(data);
42
+        }
43
+        catch (e)
44
+        {
45
+            console.error(
46
+                "Failed to parse data channel message as JSON: ",
47
+                data,
48
+                dataChannel);
49
+        }
50
+        if (('undefined' !== typeof(obj)) && (null !== obj))
34
         {
51
         {
35
-            // Endpoint ID from the bridge
36
-            var endpointId = msgData.split(":")[1];
37
-            console.info("New active speaker: " + endpointId);
52
+            var colibriClass = obj.colibriClass;
38
 
53
 
39
-            var container  = document.getElementById(
40
-                'participant_' + endpointId);
54
+            if ("DominantSpeakerEndpointChangeEvent" === colibriClass)
55
+            {
56
+                // Endpoint ID from the Videobridge.
57
+                var dominantSpeakerEndpoint = obj.dominantSpeakerEndpoint;
58
+
59
+                console.info(
60
+                    "Data channel new dominant speaker event: ",
61
+                    dominantSpeakerEndpoint);
62
+                $(document).trigger(
63
+                    'dominantspeakerchanged',
64
+                    [dominantSpeakerEndpoint]);
65
+            }
66
+            else if ("LastNEndpointsChangeEvent" === colibriClass)
67
+            {
68
+                // The new/latest list of last-n endpoint IDs.
69
+                var lastNEndpoints = obj.lastNEndpoints;
70
+                /*
71
+                 * The list of endpoint IDs which are entering the list of
72
+                 * last-n at this time i.e. were not in the old list of last-n
73
+                 * endpoint IDs.
74
+                 */
75
+                var endpointsEnteringLastN = obj.endpointsEnteringLastN;
41
 
76
 
42
-            // Local video will not have container found, but that's ok
43
-            // since we don't want to switch to local video
77
+                var stream = obj.stream;
44
 
78
 
45
-            if (container)
79
+                console.log(
80
+                    "Data channel new last-n event: ",
81
+                    lastNEndpoints, endpointsEnteringLastN, obj);
82
+
83
+                $(document).trigger(
84
+                        'lastnchanged',
85
+                        [lastNEndpoints, endpointsEnteringLastN, stream]);
86
+            }
87
+            else
46
             {
88
             {
47
-                var video = container.getElementsByTagName("video");
48
-                if (video.length)
49
-                {
50
-                    VideoLayout.updateLargeVideo(video[0].src);
51
-                    VideoLayout.enableActiveSpeaker(endpointId, true);
52
-                }
89
+                console.debug("Data channel JSON-formatted message: ", obj);
53
             }
90
             }
54
         }
91
         }
55
     };
92
     };
57
     dataChannel.onclose = function ()
94
     dataChannel.onclose = function ()
58
     {
95
     {
59
         console.info("The Data Channel closed", dataChannel);
96
         console.info("The Data Channel closed", dataChannel);
97
+        var idx = _dataChannels.indexOf(dataChannel);
98
+        if (idx > -1) 
99
+            _dataChannels = _dataChannels.splice(idx, 1);
60
     };
100
     };
101
+    _dataChannels.push(dataChannel);
61
 }
102
 }
62
 
103
 
63
 /**
104
 /**
90
         var msgData = event.data;
131
         var msgData = event.data;
91
         console.info("Got My Data Channel Message:", msgData, dataChannel);
132
         console.info("Got My Data Channel Message:", msgData, dataChannel);
92
     };*/
133
     };*/
93
-}
134
+}
135
+

+ 6
- 0
debian/changelog Visa fil

1
+jitsi-meet (1.0.1-1) unstable; urgency=low
2
+
3
+  * Initial release
4
+  * Jitsi Meet github snapshot from 2014-07-01
5
+
6
+ -- Yasen Pramatarov <yasen@bluejimp.com>  Tue, 01 Jul 2014 16:31:41 +0300

+ 1
- 0
debian/compat Visa fil

1
+8

+ 33
- 0
debian/control Visa fil

1
+Source: jitsi-meet
2
+Section: net
3
+Priority: extra
4
+Maintainer: Jitsi Team <dev@jitsi.org>
5
+Uploaders: Emil Ivov <emcho@jitsi.org>, Damian Minkov <damencho@jitsi.org>
6
+Build-Depends: debhelper (>= 8.0.0)
7
+Standards-Version: 3.9.3
8
+Homepage: https://jitsi.org/meet
9
+
10
+Package: jitsi-meet
11
+Architecture: all
12
+Pre-Depends: adduser, openssl, jitsi-videobridge
13
+Depends: ${misc:Depends}, nginx, jitsi-meet-prosody
14
+Description: WebRTC JavaScript video conferences
15
+ Jitsi Meet is a WebRTC JavaScript application that uses Jitsi
16
+ Videobridge to provide high quality, scalable video conferences.
17
+ .
18
+ It is a web interface to Jitsi Videobridge for audio and video
19
+ forwarding and relaying, configured to work with nginx
20
+
21
+Package: jitsi-meet-prosody
22
+Architecture: all
23
+Pre-Depends: adduser, openssl, prosody-trunk, jitsi-videobridge
24
+Depends: ${misc:Depends}, nginx, prosody-modules-otalk, lua-sec
25
+Description: Prosody configuration for Jitsi Meet
26
+ Jitsi Meet is a WebRTC JavaScript application that uses Jitsi
27
+ Videobridge to provide high quality, scalable video conferences.
28
+ .
29
+ It is a web interface to Jitsi Videobridge for audio and video
30
+ forwarding and relaying, configured to work with nginx
31
+ .
32
+ This package contains configuration for Prosody to be used with
33
+ Jitsi Meet.

+ 7
- 0
debian/jitsi-meet-prosody.README.Debian Visa fil

1
+Prosody configuration for Jitsi Meet for Debian
2
+----------------------------
3
+
4
+Jitsi Meet is a WebRTC video conferencing application. This package contains 
5
+configuration of prosody which are needed for Jitsi Meet to work.
6
+
7
+ -- Yasen Pramatarov <yasen@bluejimp.com>  Mon, 30 Jun 2014 23:05:18 +0100

+ 31
- 0
debian/jitsi-meet-prosody.copyright Visa fil

1
+Format: http://dep.debian.net/deps/dep5
2
+Upstream-Name: Jitsi Meet
3
+Upstream-Contact: Emil Ivov <emcho@jitsi.org>
4
+Source: https://github.com/jitsi/jitsi-meet
5
+
6
+Files: *
7
+Copyright: 2013-2014 Jitsi
8
+License: MIT
9
+
10
+License: MIT
11
+ The MIT License (MIT)
12
+ .
13
+ Copyright (c) 2013 ESTOS GmbH
14
+ Copyright (c) 2013 BlueJimp SARL
15
+ .
16
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
17
+ this software and associated documentation files (the "Software"), to deal in
18
+ the Software without restriction, including without limitation the rights to
19
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
20
+ the Software, and to permit persons to whom the Software is furnished to do so,
21
+ subject to the following conditions:
22
+ .
23
+ The above copyright notice and this permission notice shall be included in all
24
+ copies or substantial portions of the Software.
25
+ .
26
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
27
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
28
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
29
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
30
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
31
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

+ 1
- 0
debian/jitsi-meet-prosody.docs Visa fil

1
+debian/usr/share/doc/jitsi-meet-prosody/prosody.cfg.lua-jvb.example.gz

+ 1
- 0
debian/jitsi-meet-prosody.install Visa fil

1
+debian/usr/share/*		usr/share/

+ 59
- 0
debian/jitsi-meet-prosody.postinst Visa fil

1
+#!/bin/sh
2
+# postinst script for jitsi-meet-prosody
3
+#
4
+# see: dh_installdeb(1)
5
+
6
+set -e
7
+
8
+# summary of how this script can be called:
9
+#        * <postinst> `configure' <most-recently-configured-version>
10
+#        * <old-postinst> `abort-upgrade' <new version>
11
+#        * <conflictor's-postinst> `abort-remove' `in-favour' <package>
12
+#          <new-version>
13
+#        * <postinst> `abort-remove'
14
+#        * <deconfigured's-postinst> `abort-deconfigure' `in-favour'
15
+#          <failed-install-package> <version> `removing'
16
+#          <conflicting-package> <version>
17
+# for details, see http://www.debian.org/doc/debian-policy/ or
18
+# the debian-policy package
19
+
20
+
21
+case "$1" in
22
+    configure)
23
+
24
+        . /etc/default/jitsi-videobridge
25
+
26
+        if [ -x /etc/prosody/prosody.cfg.lua ]; then
27
+            mv /etc/prosody/prosody.cfg.lua /etc/prosody/prosody.cfg.lua.orig
28
+        fi
29
+        gunzip -c /usr/share/doc/jitsi-meet-prosody/prosody.cfg.lua-jvb.example.gz > /etc/prosody/prosody.cfg.lua
30
+        sed -i "s/jitmeet.example.com/$JVB_HOSTNAME/g" /etc/prosody/prosody.cfg.lua
31
+        sed -i "s/jitmeetSecret/$JVB_SECRET/g" /etc/prosody/prosody.cfg.lua
32
+        if [ ! -f /var/lib/prosody/$JVB_HOSTNAME.crt ]; then
33
+            HOST="$( (hostname -s; echo localhost) | head -n 1)"
34
+            DOMAIN="$( (hostname -d; echo localdomain) | head -n 1)"
35
+            openssl req -new -newkey rsa:4096 -days 365 -nodes -x509 -subj \
36
+                "/O=$DOMAIN/OU=$HOST/CN=$JVB_HOSTNAME/emailAddress=webmaster@$HOST.$DOMAIN" \
37
+                -keyout /var/lib/prosody/$JVB_HOSTNAME.key \
38
+                -out /var/lib/prosody/$JVB_HOSTNAME.crt
39
+        fi
40
+        ln -sf /var/lib/prosody/$JVB_HOSTNAME.key /etc/prosody/certs/$JVB_HOSTNAME.key
41
+        ln -sf /var/lib/prosody/$JVB_HOSTNAME.crt /etc/prosody/certs/$JVB_HOSTNAME.crt
42
+        invoke-rc.d prosody restart
43
+    ;;
44
+
45
+    abort-upgrade|abort-remove|abort-deconfigure)
46
+    ;;
47
+
48
+    *)
49
+        echo "postinst called with unknown argument \`$1'" >&2
50
+        exit 1
51
+    ;;
52
+esac
53
+
54
+# dh_installdeb will replace this with shell code automatically
55
+# generated by other debhelper scripts.
56
+
57
+#DEBHELPER#
58
+
59
+exit 0

+ 48
- 0
debian/jitsi-meet-prosody.postrm Visa fil

1
+#!/bin/sh
2
+# postrm script for jitsi-meet-prosody
3
+#
4
+# see: dh_installdeb(1)
5
+
6
+set -e
7
+
8
+# summary of how this script can be called:
9
+#        * <postrm> `remove'
10
+#        * <postrm> `purge'
11
+#        * <old-postrm> `upgrade' <new-version>
12
+#        * <new-postrm> `failed-upgrade' <old-version>
13
+#        * <new-postrm> `abort-install'
14
+#        * <new-postrm> `abort-install' <old-version>
15
+#        * <new-postrm> `abort-upgrade' <old-version>
16
+#        * <disappearer's-postrm> `disappear' <overwriter>
17
+#          <overwriter-version>
18
+# for details, see http://www.debian.org/doc/debian-policy/ or
19
+# the debian-policy package
20
+
21
+# Load debconf
22
+. /usr/share/debconf/confmodule
23
+
24
+
25
+case "$1" in
26
+    purge|remove)
27
+        if [ -x "/etc/init.d/prosody" ]; then
28
+            invoke-rc.d nginx reload
29
+        fi
30
+    ;;
31
+
32
+    upgrade|failed-upgrade|abort-install|abort-upgrade|disappear)
33
+    ;;
34
+
35
+    *)
36
+        echo "postrm called with unknown argument \`$1'" >&2
37
+        exit 1
38
+    ;;
39
+esac
40
+
41
+# dh_installdeb will replace this with shell code automatically
42
+# generated by other debhelper scripts.
43
+
44
+#DEBHELPER#
45
+
46
+db_stop
47
+
48
+exit 0

+ 35
- 0
debian/jitsi-meet-prosody.preinst Visa fil

1
+#!/bin/sh
2
+# preinst script for jitsi-meet-prosody
3
+#
4
+# see: dh_installdeb(1)
5
+
6
+set -e
7
+
8
+# summary of how this script can be called:
9
+#        * <new-preinst> `install'
10
+#        * <new-preinst> `install' <old-version>
11
+#        * <new-preinst> `upgrade' <old-version>
12
+#        * <old-preinst> `abort-upgrade' <new-version>
13
+# for details, see http://www.debian.org/doc/debian-policy/ or
14
+# the debian-policy package
15
+
16
+
17
+case "$1" in
18
+    install|upgrade)
19
+    ;;
20
+
21
+    abort-upgrade)
22
+    ;;
23
+
24
+    *)
25
+        echo "preinst called with unknown argument \`$1'" >&2
26
+        exit 1
27
+    ;;
28
+esac
29
+
30
+# dh_installdeb will replace this with shell code automatically
31
+# generated by other debhelper scripts.
32
+
33
+#DEBHELPER#
34
+
35
+exit 0

+ 36
- 0
debian/jitsi-meet-prosody.prerm Visa fil

1
+#!/bin/sh
2
+# prerm script for jitsi-meet-prosody
3
+#
4
+# see: dh_installdeb(1)
5
+
6
+set -e
7
+
8
+# summary of how this script can be called:
9
+#        * <prerm> `remove'
10
+#        * <old-prerm> `upgrade' <new-version>
11
+#        * <new-prerm> `failed-upgrade' <old-version>
12
+#        * <conflictor's-prerm> `remove' `in-favour' <package> <new-version>
13
+#        * <deconfigured's-prerm> `deconfigure' `in-favour'
14
+#          <package-being-installed> <version> `removing'
15
+#          <conflicting-package> <version>
16
+# for details, see http://www.debian.org/doc/debian-policy/ or
17
+# the debian-policy package
18
+
19
+
20
+case "$1" in
21
+    remove|purge)
22
+    ;;
23
+
24
+    upgrade|deconfigure)
25
+    ;;
26
+
27
+    failed-upgrade)
28
+    ;;
29
+
30
+    *)
31
+        echo "prerm called with unknown argument \`$1'" >&2
32
+        exit 1
33
+    ;;
34
+esac
35
+
36
+exit 0

+ 1
- 0
debian/jitsi-meet-prosody.substvars Visa fil

1
+misc:Depends=

+ 8
- 0
debian/jitsi-meet.README.Debian Visa fil

1
+Jitsi Meet for Debian
2
+----------------------------
3
+
4
+This is a WebRTC frontend of the video conferencing tool Jitsi Meet. It depends on the
5
+jitsi-videobridge package, which is a SFU (Selective Forwarding Unit) and both packages
6
+are designed to work together.
7
+
8
+ -- Yasen Pramatarov <yasen@bluejimp.com>  Mon, 30 Jun 2014 23:05:18 +0100

+ 6
- 0
debian/jitsi-meet.README.source Visa fil

1
+jitsi-meet for Debian
2
+---------------------
3
+
4
+The jitsi-meet package is built from the sources of Jitsi Meet.
5
+
6
+Jitsi Meet is downloaded from https://github.com/jitsi/jitsi-meet and the git files are removed. you can recreate the source with 'git clone https://github.com/jitsi/jitsi-meet.git'.

+ 31
- 0
debian/jitsi-meet.copyright Visa fil

1
+Format: http://dep.debian.net/deps/dep5
2
+Upstream-Name: Jitsi Meet
3
+Upstream-Contact: Emil Ivov <emcho@jitsi.org>
4
+Source: https://github.com/jitsi/jitsi-meet
5
+
6
+Files: *
7
+Copyright: 2013-2014 Jitsi
8
+License: MIT
9
+
10
+License: MIT
11
+ The MIT License (MIT)
12
+ .
13
+ Copyright (c) 2013 ESTOS GmbH
14
+ Copyright (c) 2013 BlueJimp SARL
15
+ .
16
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
17
+ this software and associated documentation files (the "Software"), to deal in
18
+ the Software without restriction, including without limitation the rights to
19
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
20
+ the Software, and to permit persons to whom the Software is furnished to do so,
21
+ subject to the following conditions:
22
+ .
23
+ The above copyright notice and this permission notice shall be included in all
24
+ copies or substantial portions of the Software.
25
+ .
26
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
27
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
28
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
29
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
30
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
31
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

+ 3
- 0
debian/jitsi-meet.docs Visa fil

1
+README.md
2
+debian/usr/share/doc/jitsi-meet/README
3
+debian/usr/share/doc/jitsi-meet/jitsi-meet.example

+ 2
- 0
debian/jitsi-meet.install Visa fil

1
+*				        usr/share/jitsi-meet/
2
+debian/usr/share/*		usr/share/

+ 64
- 0
debian/jitsi-meet.postinst Visa fil

1
+#!/bin/sh
2
+# postinst script for jitsi-meet
3
+#
4
+# see: dh_installdeb(1)
5
+
6
+set -e
7
+
8
+# summary of how this script can be called:
9
+#        * <postinst> `configure' <most-recently-configured-version>
10
+#        * <old-postinst> `abort-upgrade' <new version>
11
+#        * <conflictor's-postinst> `abort-remove' `in-favour' <package>
12
+#          <new-version>
13
+#        * <postinst> `abort-remove'
14
+#        * <deconfigured's-postinst> `abort-deconfigure' `in-favour'
15
+#          <failed-install-package> <version> `removing'
16
+#          <conflicting-package> <version>
17
+# for details, see http://www.debian.org/doc/debian-policy/ or
18
+# the debian-policy package
19
+
20
+
21
+case "$1" in
22
+    configure)
23
+
24
+        # nginx conf
25
+        . /etc/default/jitsi-videobridge
26
+        cp /usr/share/doc/jitsi-meet/jitsi-meet.example /etc/nginx/sites-available/$JVB_HOSTNAME.conf
27
+        if [ ! -f /etc/nginx/sites-enabled/$JVB_HOSTNAME.conf ]; then
28
+            ln -s /etc/nginx/sites-available/$JVB_HOSTNAME.conf /etc/nginx/sites-enabled/$JVB_HOSTNAME.conf
29
+        fi
30
+        sed -i "s/jitsi-meet.example.com/$JVB_HOSTNAME/g" /etc/nginx/sites-available/$JVB_HOSTNAME.conf
31
+        # FIXME do we need the default?
32
+        if [ ! -f /etc/nginx/sites-enabled/default ]; then
33
+            ln -s /etc/nginx/sites-available/default /etc/nginx/sites-enabled/default
34
+        fi
35
+        if grep "# server_names_hash_bucket_size 64" /etc/nginx/nginx.conf > /dev/null; then
36
+            sed -i "s/#\ server_names_hash_bucket_size\ 64/\ server_names_hash_bucket_size\ 64/" /etc/nginx/nginx.conf
37
+        fi
38
+
39
+        # jitsi meet
40
+        chown -R www-data:www-data /usr/share/jitsi-meet/
41
+        sed -i "s/jitsi-meet.example.com/$JVB_HOSTNAME/g" /usr/share/jitsi-meet/config.js
42
+        # enable turn
43
+        if grep "//  useStunTurn: true," /usr/share/jitsi-meet/config.js > /dev/null; then
44
+            sed -i "s/\/\/\ \ useStunTurn:\ true,/\ \ \ \ useStunTurn:\ true,/" /usr/share/jitsi-meet/config.js
45
+        fi
46
+        invoke-rc.d nginx restart
47
+
48
+    ;;
49
+
50
+    abort-upgrade|abort-remove|abort-deconfigure)
51
+    ;;
52
+
53
+    *)
54
+        echo "postinst called with unknown argument \`$1'" >&2
55
+        exit 1
56
+    ;;
57
+esac
58
+
59
+# dh_installdeb will replace this with shell code automatically
60
+# generated by other debhelper scripts.
61
+
62
+#DEBHELPER#
63
+
64
+exit 0

+ 48
- 0
debian/jitsi-meet.postrm Visa fil

1
+#!/bin/sh
2
+# postrm script for jitsi-meet
3
+#
4
+# see: dh_installdeb(1)
5
+
6
+set -e
7
+
8
+# summary of how this script can be called:
9
+#        * <postrm> `remove'
10
+#        * <postrm> `purge'
11
+#        * <old-postrm> `upgrade' <new-version>
12
+#        * <new-postrm> `failed-upgrade' <old-version>
13
+#        * <new-postrm> `abort-install'
14
+#        * <new-postrm> `abort-install' <old-version>
15
+#        * <new-postrm> `abort-upgrade' <old-version>
16
+#        * <disappearer's-postrm> `disappear' <overwriter>
17
+#          <overwriter-version>
18
+# for details, see http://www.debian.org/doc/debian-policy/ or
19
+# the debian-policy package
20
+
21
+# Load debconf
22
+. /usr/share/debconf/confmodule
23
+
24
+
25
+case "$1" in
26
+    purge|remove)
27
+        if [ -x "/etc/init.d/nginx" ]; then
28
+            invoke-rc.d nginx reload
29
+        fi
30
+    ;;
31
+
32
+    upgrade|failed-upgrade|abort-install|abort-upgrade|disappear)
33
+    ;;
34
+
35
+    *)
36
+        echo "postrm called with unknown argument \`$1'" >&2
37
+        exit 1
38
+    ;;
39
+esac
40
+
41
+# dh_installdeb will replace this with shell code automatically
42
+# generated by other debhelper scripts.
43
+
44
+#DEBHELPER#
45
+
46
+db_stop
47
+
48
+exit 0

+ 35
- 0
debian/jitsi-meet.preinst Visa fil

1
+#!/bin/sh
2
+# preinst script for jitsi-meet
3
+#
4
+# see: dh_installdeb(1)
5
+
6
+set -e
7
+
8
+# summary of how this script can be called:
9
+#        * <new-preinst> `install'
10
+#        * <new-preinst> `install' <old-version>
11
+#        * <new-preinst> `upgrade' <old-version>
12
+#        * <old-preinst> `abort-upgrade' <new-version>
13
+# for details, see http://www.debian.org/doc/debian-policy/ or
14
+# the debian-policy package
15
+
16
+
17
+case "$1" in
18
+    install|upgrade)
19
+    ;;
20
+
21
+    abort-upgrade)
22
+    ;;
23
+
24
+    *)
25
+        echo "preinst called with unknown argument \`$1'" >&2
26
+        exit 1
27
+    ;;
28
+esac
29
+
30
+# dh_installdeb will replace this with shell code automatically
31
+# generated by other debhelper scripts.
32
+
33
+#DEBHELPER#
34
+
35
+exit 0

+ 36
- 0
debian/jitsi-meet.prerm Visa fil

1
+#!/bin/sh
2
+# prerm script for jitsi-meet
3
+#
4
+# see: dh_installdeb(1)
5
+
6
+set -e
7
+
8
+# summary of how this script can be called:
9
+#        * <prerm> `remove'
10
+#        * <old-prerm> `upgrade' <new-version>
11
+#        * <new-prerm> `failed-upgrade' <old-version>
12
+#        * <conflictor's-prerm> `remove' `in-favour' <package> <new-version>
13
+#        * <deconfigured's-prerm> `deconfigure' `in-favour'
14
+#          <package-being-installed> <version> `removing'
15
+#          <conflicting-package> <version>
16
+# for details, see http://www.debian.org/doc/debian-policy/ or
17
+# the debian-policy package
18
+
19
+
20
+case "$1" in
21
+    remove|purge)
22
+    ;;
23
+
24
+    upgrade|deconfigure)
25
+    ;;
26
+
27
+    failed-upgrade)
28
+    ;;
29
+
30
+    *)
31
+        echo "prerm called with unknown argument \`$1'" >&2
32
+        exit 1
33
+    ;;
34
+esac
35
+
36
+exit 0

+ 16
- 0
debian/rules Visa fil

1
+#!/usr/bin/make -f
2
+# -*- makefile -*-
3
+# Sample debian/rules that uses debhelper.
4
+# This file was originally written by Joey Hess and Craig Small.
5
+# As a special exception, when this file is copied by dh-make into a
6
+# dh-make output file, you may use that output file without restriction.
7
+# This special exception was added by Craig Small in version 0.37 of dh-make.
8
+
9
+# Uncomment this to turn on verbose mode.
10
+#export DH_VERBOSE=1
11
+
12
+%:
13
+	dh $@
14
+
15
+override_dh_install-indep:
16
+	dh_install -Xdebian -Xdoc -XINSTALL.md -XLICENSE -XREADME.md usr/share/jitsi-meet/

+ 1
- 0
debian/source/format Visa fil

1
+3.0 (quilt)

+ 19
- 0
debian/source/include-binaries Visa fil

1
+debian/usr/share/jitsi-meet/favicon.ico
2
+debian/usr/share/jitsi-meet/fonts/jitsi.eot
3
+debian/usr/share/jitsi-meet/fonts/jitsi.woff
4
+debian/usr/share/jitsi-meet/fonts/jitsi.ttf
5
+debian/usr/share/jitsi-meet/sounds/left.wav
6
+debian/usr/share/jitsi-meet/sounds/incomingMessage.wav
7
+debian/usr/share/jitsi-meet/sounds/joined.wav
8
+debian/usr/share/jitsi-meet/images/estoslogo.png
9
+debian/usr/share/jitsi-meet/images/chromelogo.png
10
+debian/usr/share/jitsi-meet/images/jitsilogo.png
11
+debian/usr/share/jitsi-meet/images/watermark.png
12
+debian/usr/share/jitsi-meet/images/avatarprezi.png
13
+debian/usr/share/jitsi-meet/images/chromepointer.png
14
+debian/usr/share/jitsi-meet/images/avatar1.png
15
+debian/usr/share/jitsi-meet/images/popupPointer.png
16
+debian/usr/share/jitsi-meet/images/favicon.ico
17
+debian/usr/share/doc/jitsi-meet/changelog.Debian.gz
18
+debian/usr/share/doc/jitsi-meet-prosody/changelog.Debian.gz
19
+debian/usr/share/doc/jitsi-meet-prosody/prosody.cfg.lua-jvb.example.gz

+ 1
- 0
debian/usr/share/doc/jitsi-meet-prosody/README Visa fil

1
+Prosody configuration for Jitsi Meet

Binär
debian/usr/share/doc/jitsi-meet-prosody/changelog.Debian.gz Visa fil


+ 31
- 0
debian/usr/share/doc/jitsi-meet-prosody/copyright Visa fil

1
+Format: http://dep.debian.net/deps/dep5
2
+Upstream-Name: Jitsi Meet
3
+Upstream-Contact: Emil Ivov <emcho@jitsi.org>
4
+Source: https://github.com/jitsi/jitsi-meet
5
+
6
+Files: *
7
+Copyright: 2013-2014 Jitsi
8
+License: MIT
9
+
10
+License: MIT
11
+ The MIT License (MIT)
12
+ .
13
+ Copyright (c) 2013 ESTOS GmbH
14
+ Copyright (c) 2013 BlueJimp SARL
15
+ .
16
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
17
+ this software and associated documentation files (the "Software"), to deal in
18
+ the Software without restriction, including without limitation the rights to
19
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
20
+ the Software, and to permit persons to whom the Software is furnished to do so,
21
+ subject to the following conditions:
22
+ .
23
+ The above copyright notice and this permission notice shall be included in all
24
+ copies or substantial portions of the Software.
25
+ .
26
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
27
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
28
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
29
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
30
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
31
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Binär
debian/usr/share/doc/jitsi-meet-prosody/prosody.cfg.lua-jvb.example.gz Visa fil


+ 13
- 0
debian/usr/share/doc/jitsi-meet/README Visa fil

1
+Jitsi Meet
2
+
3
+====
4
+
5
+A WebRTC-powered multi-user videochat. For a live demo, check out either
6
+https://meet.estos.de/ or https://meet.jit.si/.
7
+
8
+Built using colibri.js[0] and strophe.jingle[1], powered by the jitsi-videobridge[2]
9
+
10
+
11
+[0] https://github.com/ESTOS/colibri.js
12
+[1] https://github.com/ESTOS/strophe.jingle
13
+[3] https://github.com/jitsi/jitsi-videobridge

Binär
debian/usr/share/doc/jitsi-meet/changelog.Debian.gz Visa fil


+ 31
- 0
debian/usr/share/doc/jitsi-meet/copyright Visa fil

1
+Format: http://dep.debian.net/deps/dep5
2
+Upstream-Name: Jitsi Meet
3
+Upstream-Contact: Emil Ivov <emcho@jitsi.org>
4
+Source: https://github.com/jitsi/jitsi-meet
5
+
6
+Files: *
7
+Copyright: 2013-2014 Jitsi
8
+License: MIT
9
+
10
+License: MIT
11
+ The MIT License (MIT)
12
+ .
13
+ Copyright (c) 2013 ESTOS GmbH
14
+ Copyright (c) 2013 BlueJimp SARL
15
+ .
16
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
17
+ this software and associated documentation files (the "Software"), to deal in
18
+ the Software without restriction, including without limitation the rights to
19
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
20
+ the Software, and to permit persons to whom the Software is furnished to do so,
21
+ subject to the following conditions:
22
+ .
23
+ The above copyright notice and this permission notice shall be included in all
24
+ copies or substantial portions of the Software.
25
+ .
26
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
27
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
28
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
29
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
30
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
31
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

+ 36
- 0
debian/usr/share/doc/jitsi-meet/jitsi-meet.example Visa fil

1
+server {
2
+    listen 80;
3
+    server_name jitsi-meet.example.com;
4
+    return 301 https://$host$request_uri;
5
+}
6
+server {
7
+    listen 443 ssl;
8
+    server_name jitsi-meet.example.com;
9
+
10
+    ssl_certificate /var/lib/prosody/jitsi-meet.example.com.crt;
11
+    ssl_certificate_key /var/lib/prosody/jitsi-meet.example.com.key;
12
+
13
+    root /usr/share/jitsi-meet;
14
+    index index.html index.htm;
15
+
16
+    location ~ ^/([a-zA-Z0-9]+)$ {
17
+        rewrite ^/(.*)$ / break;
18
+    }
19
+
20
+    # BOSH
21
+    location /http-bind {
22
+        proxy_pass      http://localhost:5280/http-bind;
23
+        proxy_set_header X-Forwarded-For $remote_addr;
24
+        proxy_set_header Host $http_host;
25
+    }
26
+
27
+    # xmpp websockets
28
+    location /xmpp-websocket {
29
+        proxy_pass http://localhost:5280;
30
+        proxy_http_version 1.1;
31
+        proxy_set_header Upgrade $http_upgrade;
32
+        proxy_set_header Connection "upgrade";
33
+        proxy_set_header Host $host;
34
+        tcp_nodelay on;
35
+    }
36
+}

+ 103
- 0
doc/quick-install.md Visa fil

1
+# Jitsi Meet quick install
2
+
3
+This documents decribes the needed steps for quick Jitsi Meet installation on a Debian based GNU/Linux system.
4
+
5
+N.B.: All commands are supposed to be run by root. If you are logged in as a regular user with sudo rights, please prepend ___sudo___ to each of the commands.
6
+
7
+## Basic Jitsi Meet install
8
+
9
+### Add the repository
10
+
11
+```sh
12
+add-apt-repository 'deb http://download.jitsi.org/nightly/deb unstable/'
13
+wget -qO - https://download.jitsi.org/nightly/deb/unstable/archive.key | apt-key add -
14
+```
15
+
16
+add-apt-repository is in the default Ubuntu install and is available for both Ubuntu and Debian, but if it's not present, either install it with
17
+
18
+```sh
19
+apt-get -y install software-properties-common
20
+add-apt-repository 'deb http://download.jitsi.org/nightly/deb unstable/'
21
+wget -qO - https://download.jitsi.org/nightly/deb/unstable/archive.key | apt-key add -
22
+```
23
+
24
+or add the repository by hand with
25
+
26
+```sh
27
+echo 'deb http://download.jitsi.org/nightly/deb unstable/' >> /etc/apt/sources.list
28
+wget -qO - https://download.jitsi.org/nightly/deb/unstable/archive.key | apt-key add -
29
+```
30
+
31
+### Update the package lists
32
+
33
+```sh
34
+apt-get update
35
+```
36
+
37
+### Install Jitsi Meet
38
+
39
+```sh
40
+apt-get -y install jitsi-meet
41
+```
42
+
43
+During the installation you'll be asked to enter the hostname of the Jitsi Meet instance. If you have a FQDN hostname for the instance already set ip in DNS, enter it there. If you don't have a resolvable hostname, you can enter the IP address of the machine (if it is static or doesn't change).
44
+
45
+This hostname (or IP address) will be used for virtualhost configuration inside the Jitsi Meet and also you and your correspondents will be using it to access the web conferences.
46
+
47
+### Open a conference
48
+
49
+Launch a web broswer (Chrome, Chromium or latest Opera) and enter in the URL bar the hostname (or IP address) you used in the previous step.
50
+
51
+Confirm that you trust the self-signed certificate of the newly installed Jitsi Meet.
52
+
53
+Enjoy!
54
+
55
+## Adding sip-gateway to Jitsi Meet
56
+
57
+### Install Jigasi
58
+
59
+```sh
60
+apt-get -o Dpkg::Options::="--force-overwrite" -y install jigasi
61
+```
62
+
63
+or
64
+
65
+```sh
66
+wget https://download.jitsi.org/jigasi_1.0-1_amd64.deb
67
+dpkg -i --force-overwrite jigasi_1.0-1_amd64.deb
68
+```
69
+
70
+You need to pass "--force-overwrite" option to dpkg, because the jigasi package patches some of the files in the jitsi-meet package in order to enable the SIP support in Jitsi Meet.
71
+
72
+During the installation you'll be asked to enter your SIP account and password. This account will be used to invite the other SIP participants.
73
+
74
+### Reload Jitsi Meet
75
+
76
+Launch again a browser with the Jitsi Meet URL and you'll see a telephone icon on the right end of the toolbar. Use it to invite SIP accounts to join the current conference.
77
+
78
+Enjoy!
79
+
80
+## Troubleshoot
81
+
82
+If the SIP gateway doesn't work on first try, restart it.
83
+
84
+```sh
85
+/etc/init.d/jigasi restart
86
+```
87
+
88
+## Deinstall
89
+
90
+```sh
91
+apt-get purge jigasi jitsi-meet jitsi-videobridge
92
+```
93
+
94
+Somethimes the following packages will fail to uninstall properly:
95
+
96
+- jigasi
97
+- jitsi-videobridge
98
+
99
+When this happens, just run the deinstall command a second time and it should be ok.
100
+
101
+The reason for failure is that not allways the daemons are stopped right away, there is a timeout before the actual stop. And if the unistall script goes on before the services' stop, there is an error.
102
+
103
+The second run of the deinstall command fixes this, as by then the jigasi or jvb daemons are already stopped.

Binär
favicon.ico Visa fil


Binär
fonts/jitsi.eot Visa fil


+ 9
- 3
fonts/jitsi.svg Visa fil

15
 <glyph unicode="&#xe605;" d="M0.759 320.807h138.767v159.899c0 0-39.017-4.051-88.090-55.817-49.069-51.764-50.676-104.082-50.676-104.082zM341.64 480.706h-169.842v-192.298l-171.040 0.125-0.757 1.734v-255.251c0-36.923 30.7-66.99 68.424-66.99h273.217c37.757 0 68.456 30.068 68.456 66.99v378.702c-0.002 36.921-30.699 66.988-68.457 66.988zM345.927 72.582h-286.424v46.394h286.423v-46.394zM345.927 169.401h-286.424v46.392h286.423v-46.392z" horiz-adv-x="410" />
15
 <glyph unicode="&#xe605;" d="M0.759 320.807h138.767v159.899c0 0-39.017-4.051-88.090-55.817-49.069-51.764-50.676-104.082-50.676-104.082zM341.64 480.706h-169.842v-192.298l-171.040 0.125-0.757 1.734v-255.251c0-36.923 30.7-66.99 68.424-66.99h273.217c37.757 0 68.456 30.068 68.456 66.99v378.702c-0.002 36.921-30.699 66.988-68.457 66.988zM345.927 72.582h-286.424v46.394h286.423v-46.394zM345.927 169.401h-286.424v46.392h286.423v-46.392z" horiz-adv-x="410" />
16
 <glyph unicode="&#xe606;" d="M476.95 481.193h-409.887c-36.483 0-66.209-30.356-66.209-67.672v-270.084c0-37.284 29.727-67.639 66.209-67.639h17.912v-106.445l172.483 106.445h219.493c36.482 0 66.208 30.355 66.208 67.639v270.084c0.001 37.316-29.725 67.672-66.207 67.672zM247.214 146.677l-97.885-62v62h-79.092v263.626h403.539l0.062-263.626h-226.625z" horiz-adv-x="545" />
16
 <glyph unicode="&#xe606;" d="M476.95 481.193h-409.887c-36.483 0-66.209-30.356-66.209-67.672v-270.084c0-37.284 29.727-67.639 66.209-67.639h17.912v-106.445l172.483 106.445h219.493c36.482 0 66.208 30.355 66.208 67.639v270.084c0.001 37.316-29.725 67.672-66.207 67.672zM247.214 146.677l-97.885-62v62h-79.092v263.626h403.539l0.062-263.626h-226.625z" horiz-adv-x="545" />
17
 <glyph unicode="&#xe607;" d="M354.757 310.047v22.227c0 81.545-66.331 147.875-147.875 147.875-81.546 0-147.876-66.329-147.876-147.875v-22.227c-33.113-3.697-59.007-32.458-59.007-67.304v-205.811c0-37.315 29.741-67.683 66.236-67.683h281.291c36.529 0 66.267 30.368 66.267 67.683v205.811c0 34.848-25.896 63.609-59.037 67.304zM206.882 415.769c46.022 0 83.493-37.472 83.493-83.494v-21.816h-166.989v21.816c0 46.022 37.441 83.494 83.495 83.494z" horiz-adv-x="414" />
17
 <glyph unicode="&#xe607;" d="M354.757 310.047v22.227c0 81.545-66.331 147.875-147.875 147.875-81.546 0-147.876-66.329-147.876-147.875v-22.227c-33.113-3.697-59.007-32.458-59.007-67.304v-205.811c0-37.315 29.741-67.683 66.236-67.683h281.291c36.529 0 66.267 30.368 66.267 67.683v205.811c0 34.848-25.896 63.609-59.037 67.304zM206.882 415.769c46.022 0 83.493-37.472 83.493-83.494v-21.816h-166.989v21.816c0 46.022 37.441 83.494 83.495 83.494z" horiz-adv-x="414" />
18
-<glyph unicode="&#xe608;" d="M613.039 358.427l-90.297-88.124v109.103c0 37.441-29.829 67.911-66.474 67.911h-318.101c-36.644 0-66.469-30.47-66.469-67.911v-305.329c0-37.425 29.826-67.894 66.469-67.894h318.101c36.645 0 66.474 30.469 66.474 67.894v86.562l96.954-77.070c24.451-17.791 48.463 2.608 48.463 20.292v242.327c0.001 17.682-28.015 35.754-55.122 12.24zM301.315 99.154c-70.723 0-128.065 57.342-128.065 128.066s57.341 128.065 128.065 128.065c70.724 0 128.067-57.341 128.067-128.065 0-70.725-57.344-128.066-128.067-128.066zM296.894 299.788c-40.335 0-73.037-32.704-73.037-73.036 0-40.335 32.703-73.040 73.037-73.040 40.331 0 73.036 32.707 73.036 73.040 0 40.332-32.705 73.036-73.036 73.036zM296.894 260.049c-14.916 0-27.014-12.116-27.014-27.013 0-4.423-3.594-8.004-8.005-8.004-4.44 0-8.002 3.58-8.002 8.004 0 23.716 19.291 43.024 43.022 43.024 4.409 0 8.002-3.58 8.002-8.005-0.002-4.426-3.596-8.005-8.002-8.005z" horiz-adv-x="667" />
18
+<glyph unicode="&#xe608;" d="M613.039 358.427l-90.297-88.124v109.103c0 37.441-29.829 67.911-66.474 67.911h-318.101c-36.644 0-66.469-30.47-66.469-67.911v-305.329c0-37.425 29.826-67.894 66.469-67.894h318.101c36.645 0 66.474 30.469 66.474 67.894v86.562l96.954-77.070c24.451-17.791 48.463 2.608 48.463 20.292v242.327c0.001 17.682-28.015 35.754-55.122 12.24zM301.315 99.154c-70.723 0-128.065 57.342-128.065 128.066s57.341 128.065 128.065 128.065c70.724 0 128.067-57.341 128.067-128.065s-57.344-128.066-128.067-128.066zM296.894 299.788c-40.335 0-73.037-32.704-73.037-73.036 0-40.335 32.703-73.040 73.037-73.040 40.331 0 73.036 32.707 73.036 73.040 0 40.332-32.705 73.036-73.036 73.036zM296.894 260.049c-14.916 0-27.014-12.116-27.014-27.013 0-4.423-3.594-8.004-8.005-8.004-4.44 0-8.002 3.58-8.002 8.004 0 23.716 19.291 43.024 43.022 43.024 4.409 0 8.002-3.58 8.002-8.005-0.002-4.426-3.596-8.005-8.002-8.005z" horiz-adv-x="667" />
19
 <glyph unicode="&#xe609;" d="M611.967 358.573l-90.149-87.978v108.924c0 3.831-0.333 7.574-0.951 11.216l36.847 32.673c13.174 11.705 14.42 31.9 2.684 45.12-11.737 13.203-31.902 14.436-45.136 2.7l-504.51-447.356c-13.204-11.705-14.421-31.903-2.699-45.104 6.303-7.118 15.091-10.769 23.925-10.769 7.538 0 15.107 2.652 21.195 8.050l47.92 42.49c10.498-7.313 23.13-11.616 36.796-11.616h317.596c36.55 0 66.33 30.404 66.33 67.769v86.434l96.83-76.978c24.408-17.73 48.383 2.624 48.383 20.292v241.914c0.001 17.653-27.966 35.693-55.062 12.22zM300.757 99.724c-29.405 0-56.283 10.108-77.763 26.899l41.465 36.767c10.431-5.832 22.425-9.193 35.235-9.193 40.267 0 72.916 32.649 72.916 72.918 0 9.71-1.948 18.928-5.428 27.357l43.423 38.501c11.424-19.13 18.006-41.484 18.006-65.391 0-70.607-57.246-127.855-127.853-127.855zM172.899 227.58c0 70.608 57.248 127.857 127.858 127.857 10.75 0 21.038-1.717 30.993-4.214l108.379 96.096h-302.237c-36.569 0-66.349-30.419-66.349-67.799v-259.037l102.947 91.272c-0.654 5.243-1.592 10.426-1.592 15.826z" horiz-adv-x="667" />
19
 <glyph unicode="&#xe609;" d="M611.967 358.573l-90.149-87.978v108.924c0 3.831-0.333 7.574-0.951 11.216l36.847 32.673c13.174 11.705 14.42 31.9 2.684 45.12-11.737 13.203-31.902 14.436-45.136 2.7l-504.51-447.356c-13.204-11.705-14.421-31.903-2.699-45.104 6.303-7.118 15.091-10.769 23.925-10.769 7.538 0 15.107 2.652 21.195 8.050l47.92 42.49c10.498-7.313 23.13-11.616 36.796-11.616h317.596c36.55 0 66.33 30.404 66.33 67.769v86.434l96.83-76.978c24.408-17.73 48.383 2.624 48.383 20.292v241.914c0.001 17.653-27.966 35.693-55.062 12.22zM300.757 99.724c-29.405 0-56.283 10.108-77.763 26.899l41.465 36.767c10.431-5.832 22.425-9.193 35.235-9.193 40.267 0 72.916 32.649 72.916 72.918 0 9.71-1.948 18.928-5.428 27.357l43.423 38.501c11.424-19.13 18.006-41.484 18.006-65.391 0-70.607-57.246-127.855-127.853-127.855zM172.899 227.58c0 70.608 57.248 127.857 127.858 127.857 10.75 0 21.038-1.717 30.993-4.214l108.379 96.096h-302.237c-36.569 0-66.349-30.419-66.349-67.799v-259.037l102.947 91.272c-0.654 5.243-1.592 10.426-1.592 15.826z" horiz-adv-x="667" />
20
 <glyph unicode="&#xe60a;" d="M560.562 469.433c-11.74 13.207-31.942 14.425-45.148 2.686l-504.653-447.452c-13.207-11.709-14.426-31.908-2.717-45.116 6.306-7.122 15.112-10.774 23.947-10.774 7.525 0 15.112 2.654 21.201 8.054l128.536 113.967c16.613-11.432 34.994-19.839 54.305-24.856-35.096-17.827-59.386-53.858-59.386-95.947h215.936c0 42.868-25.164 79.558-61.382 97.039 27.475 7.245 52.921 19.983 73.748 38.748 25.944 23.356 56.856 65.757 56.856 135.165v65.162c0 9.406-3.962 17.883-10.293 23.899l106.332 94.279c13.21 11.738 14.424 31.908 2.717 45.147zM395.767 240.946c0-78.993-58.825-114.961-113.495-114.961-17.607 0-34.329 3.608-49.142 10.393l27.868 24.708c7.366-2.295 15.178-3.566 23.305-3.566 44.678 0 80.992 36.344 80.992 80.99v15.050l30.474 27.021v-39.635zM365.295 396.933c0 44.649-36.313 80.992-80.992 80.992-44.649 0-80.992-36.344-80.992-80.992v-158.425c0-0.125 0-0.249 0-0.374l161.984 143.625v15.174zM175.398 213.345c-1.623 8.741-2.559 17.891-2.559 27.601v65.161c0 18.203-14.8 33.002-33.003 33.002-18.233 0-33.002-14.798-33.002-33.002v-65.161c0-28.599 5.558-53.513 14.549-75.466l54.015 47.865z" horiz-adv-x="569" />
20
 <glyph unicode="&#xe60a;" d="M560.562 469.433c-11.74 13.207-31.942 14.425-45.148 2.686l-504.653-447.452c-13.207-11.709-14.426-31.908-2.717-45.116 6.306-7.122 15.112-10.774 23.947-10.774 7.525 0 15.112 2.654 21.201 8.054l128.536 113.967c16.613-11.432 34.994-19.839 54.305-24.856-35.096-17.827-59.386-53.858-59.386-95.947h215.936c0 42.868-25.164 79.558-61.382 97.039 27.475 7.245 52.921 19.983 73.748 38.748 25.944 23.356 56.856 65.757 56.856 135.165v65.162c0 9.406-3.962 17.883-10.293 23.899l106.332 94.279c13.21 11.738 14.424 31.908 2.717 45.147zM395.767 240.946c0-78.993-58.825-114.961-113.495-114.961-17.607 0-34.329 3.608-49.142 10.393l27.868 24.708c7.366-2.295 15.178-3.566 23.305-3.566 44.678 0 80.992 36.344 80.992 80.99v15.050l30.474 27.021v-39.635zM365.295 396.933c0 44.649-36.313 80.992-80.992 80.992-44.649 0-80.992-36.344-80.992-80.992v-158.425c0-0.125 0-0.249 0-0.374l161.984 143.625v15.174zM175.398 213.345c-1.623 8.741-2.559 17.891-2.559 27.601v65.161c0 18.203-14.8 33.002-33.003 33.002-18.233 0-33.002-14.798-33.002-33.002v-65.161c0-28.599 5.558-53.513 14.549-75.466l54.015 47.865z" horiz-adv-x="569" />
21
-<glyph unicode="&#xe60b;" d="M429.207 339.972c-18.298 0-33.123-14.826-33.123-33.091v-65.362c0-79.211-58.991-115.298-113.817-115.298-29.337 0-56.309 9.935-75.93 27.98-22.115 20.409-33.848 50.601-33.848 87.32v65.363c0 18.265-14.827 33.091-33.091 33.091-18.265 0-33.091-14.826-33.091-33.091v-65.363c0-97.917 59.747-157.382 129.589-175.52-35.204-17.855-59.588-54.007-59.588-96.216h216.559c0 42.996-25.204 79.81-61.514 97.286 27.539 7.32 53.060 20.063 73.943 38.895 26.025 23.438 57.004 65.963 57.004 135.553v65.363c0 18.265-14.795 33.091-33.092 33.091zM284.286 157.86c-44.794 0-81.23 36.466-81.23 81.26v158.832c0 44.795 36.435 81.23 81.23 81.23 44.796 0 81.262-36.435 81.262-81.23v-158.832c0.002-44.796-36.464-81.26-81.262-81.26z" horiz-adv-x="569" />
22
-<glyph unicode="&#xe60c;" d="M256.178 480c-141.228 0-256.178-114.919-256.178-256.239 0-141.195 114.95-256.113 256.178-256.113 141.257 0 256.207 114.919 256.207 256.113 0 141.32-114.95 256.239-256.207 256.239zM256.178 7.428c-119.272 0-216.335 97.063-216.335 216.333 0 119.398 97.063 216.429 216.335 216.429 119.3 0 216.428-97.031 216.428-216.429 0-119.27-97.127-216.333-216.428-216.333zM256.272 427.481c-112.377 0-203.754-91.375-203.754-203.657 0-112.281 91.375-203.657 203.754-203.657 112.219 0 203.594 91.377 203.594 203.658-0.002 112.283-91.375 203.658-203.594 203.658zM256.272 63.661c-88.358 0-160.226 71.902-160.226 160.162 0 88.262 71.868 160.162 160.226 160.162 88.262 0 160.098-71.901 160.098-160.162 0-88.26-71.837-160.162-160.098-160.162zM141.925 281.394l-0.477-0.699v-117.207l0.477-0.699c7.879-11.53 18.237-22.271 30.85-31.899l4.481-3.401v189.171l-4.481-3.368c-12.55-9.595-22.907-20.272-30.85-31.899zM207.819 332.865l-1.81-0.667v-220.18l1.81-0.699c9.341-3.527 19.444-5.97 30.883-7.466l3.112-0.381v237.207l-3.082-0.381c-11.119-1.398-21.508-3.876-30.913-7.435zM273.683 340.299l-3.082 0.381v-237.208l3.082 0.381c11.151 1.397 21.538 3.906 30.882 7.432l1.842 0.7v220.244l-1.842 0.667c-9.406 3.526-19.762 6.005-30.882 7.403zM370.49 281.394c-7.846 11.501-18.236 22.24-30.849 31.899l-4.447 3.43v-189.234l4.447 3.401c12.675 9.69 23.066 20.431 30.849 31.93l0.445 0.699v117.176l-0.445 0.7z" horiz-adv-x="513" />
21
+<glyph unicode="&#xe60b;" d="M429.207 339.972c-18.298 0-33.123-14.826-33.123-33.091v-65.362c0-79.211-58.991-115.298-113.817-115.298-29.337 0-56.309 9.935-75.93 27.98-22.115 20.409-33.848 50.601-33.848 87.32v65.363c0 18.265-14.827 33.091-33.091 33.091s-33.091-14.826-33.091-33.091v-65.363c0-97.917 59.747-157.382 129.589-175.52-35.204-17.855-59.588-54.007-59.588-96.216h216.559c0 42.996-25.204 79.81-61.514 97.286 27.539 7.32 53.060 20.063 73.943 38.895 26.025 23.438 57.004 65.963 57.004 135.553v65.363c0 18.265-14.795 33.091-33.092 33.091zM284.286 157.86c-44.794 0-81.23 36.466-81.23 81.26v158.832c0 44.795 36.435 81.23 81.23 81.23 44.796 0 81.262-36.435 81.262-81.23v-158.832c0.002-44.796-36.464-81.26-81.262-81.26z" horiz-adv-x="569" />
22
+<glyph unicode="&#xe60c;" d="M256.178 480c-141.228 0-256.178-114.919-256.178-256.239 0-141.195 114.95-256.113 256.178-256.113 141.257 0 256.207 114.919 256.207 256.113 0 141.32-114.95 256.239-256.207 256.239zM256.178 7.428c-119.272 0-216.335 97.063-216.335 216.333 0 119.398 97.063 216.429 216.335 216.429 119.3 0 216.428-97.031 216.428-216.429 0-119.27-97.127-216.333-216.428-216.333zM256.272 427.481c-112.377 0-203.754-91.375-203.754-203.657s91.375-203.657 203.754-203.657c112.219 0 203.594 91.377 203.594 203.658-0.002 112.283-91.375 203.658-203.594 203.658zM256.272 63.661c-88.358 0-160.226 71.902-160.226 160.162 0 88.262 71.868 160.162 160.226 160.162 88.262 0 160.098-71.901 160.098-160.162 0-88.26-71.837-160.162-160.098-160.162zM141.925 281.394l-0.477-0.699v-117.207l0.477-0.699c7.879-11.53 18.237-22.271 30.85-31.899l4.481-3.401v189.171l-4.481-3.368c-12.55-9.595-22.907-20.272-30.85-31.899zM207.819 332.865l-1.81-0.667v-220.18l1.81-0.699c9.341-3.527 19.444-5.97 30.883-7.466l3.112-0.381v237.207l-3.082-0.381c-11.119-1.398-21.508-3.876-30.913-7.435zM273.683 340.299l-3.082 0.381v-237.208l3.082 0.381c11.151 1.397 21.538 3.906 30.882 7.432l1.842 0.7v220.244l-1.842 0.667c-9.406 3.526-19.762 6.005-30.882 7.403zM370.49 281.394c-7.846 11.501-18.236 22.24-30.849 31.899l-4.447 3.43v-189.234l4.447 3.401c12.675 9.69 23.066 20.431 30.849 31.93l0.445 0.699v117.176l-0.445 0.7z" horiz-adv-x="513" />
23
 <glyph unicode="&#xe60d;" d="M476.183 480.067h-410.238c-36.514 0-66.266-30.38-66.266-67.728v-376.179c0-37.33 29.752-67.712 66.266-67.712h410.24c36.545 0 66.298 30.383 66.298 67.712v376.179c-0.001 37.347-29.754 67.728-66.299 67.728zM473.067 39.401h-403.947v369.731h403.917l0.029-369.731zM284.871 255.938l45.886 48.433-38.652 38.654 158.197 42.52-42.49-158.195-37.678 37.647-45.917-48.433zM257.382 192.281l-45.883-48.433 38.65-38.652-158.194-42.522 42.489 158.194 37.678-37.645 45.917 48.435z" horiz-adv-x="545" />
23
 <glyph unicode="&#xe60d;" d="M476.183 480.067h-410.238c-36.514 0-66.266-30.38-66.266-67.728v-376.179c0-37.33 29.752-67.712 66.266-67.712h410.24c36.545 0 66.298 30.383 66.298 67.712v376.179c-0.001 37.347-29.754 67.728-66.299 67.728zM473.067 39.401h-403.947v369.731h403.917l0.029-369.731zM284.871 255.938l45.886 48.433-38.652 38.654 158.197 42.52-42.49-158.195-37.678 37.647-45.917-48.433zM257.382 192.281l-45.883-48.433 38.65-38.652-158.194-42.522 42.489 158.194 37.678-37.645 45.917 48.435z" horiz-adv-x="545" />
24
 <glyph unicode="&#xe60e;" d="M476.613 479.59h-410.332c-36.523 0-66.281-30.388-66.281-67.744v-376.262c0-37.324 29.759-67.71 66.281-67.71h410.33c36.553 0 66.312 30.388 66.312 67.711v376.262c0.001 37.356-29.758 67.744-66.311 67.744zM473.497 38.824h-404.039v369.798h404.009l0.031-369.798zM457.769 353.35l-45.897-48.445 38.663-38.661-158.232-42.515 42.5 158.232 37.687-37.67 45.926 48.445zM85.313 94.111l45.897 48.442-38.661 38.663 158.232 42.514-42.499-158.23-37.686 37.671-45.928-48.445z" horiz-adv-x="545" />
24
 <glyph unicode="&#xe60e;" d="M476.613 479.59h-410.332c-36.523 0-66.281-30.388-66.281-67.744v-376.262c0-37.324 29.759-67.71 66.281-67.71h410.33c36.553 0 66.312 30.388 66.312 67.711v376.262c0.001 37.356-29.758 67.744-66.311 67.744zM473.497 38.824h-404.039v369.798h404.009l0.031-369.798zM457.769 353.35l-45.897-48.445 38.663-38.661-158.232-42.515 42.5 158.232 37.687-37.67 45.926 48.445zM85.313 94.111l45.897 48.442-38.661 38.663 158.232 42.514-42.499-158.23-37.686 37.671-45.928-48.445z" horiz-adv-x="545" />
25
 <glyph unicode="&#xe60f;" d="M256.518 480c141.785-0.094 256.207-114.737 256.018-256.332-0.188-141.878-114.483-256.114-256.271-256.144-141.595-0.034-256.456 114.737-256.267 256.050 0.187 142.319 114.483 256.551 256.518 256.426zM256.142 405.365c-100.395-0.063-181.478-81.146-181.416-181.507 0-100.553 80.894-181.541 181.416-181.667 100.582-0.156 181.791 81.147 181.728 181.886-0.125 100.426-81.209 181.351-181.727 181.287zM298.696 223.794h-0.252l-0.063 0.063h0.315l57.808 57.933c0 0-39.123 39.252-41.077 41.077l-58.877-58.091-59.507 59.098-41.012-41.139 44.407-44.409 13.337-14.531h0.251l0.126-0.127h-0.378l-57.744-57.903c0 0 39.125-39.282 41.012-41.106l58.88 58.123 59.504-59.13 41.077 41.203-44.408 44.41-13.4 14.531z" horiz-adv-x="513" />
25
 <glyph unicode="&#xe60f;" d="M256.518 480c141.785-0.094 256.207-114.737 256.018-256.332-0.188-141.878-114.483-256.114-256.271-256.144-141.595-0.034-256.456 114.737-256.267 256.050 0.187 142.319 114.483 256.551 256.518 256.426zM256.142 405.365c-100.395-0.063-181.478-81.146-181.416-181.507 0-100.553 80.894-181.541 181.416-181.667 100.582-0.156 181.791 81.147 181.728 181.886-0.125 100.426-81.209 181.351-181.727 181.287zM298.696 223.794h-0.252l-0.063 0.063h0.315l57.808 57.933c0 0-39.123 39.252-41.077 41.077l-58.877-58.091-59.507 59.098-41.012-41.139 44.407-44.409 13.337-14.531h0.251l0.126-0.127h-0.378l-57.744-57.903c0 0 39.125-39.282 41.012-41.106l58.88 58.123 59.504-59.13 41.077 41.203-44.408 44.41-13.4 14.531z" horiz-adv-x="513" />
26
 <glyph unicode="&#xe610;" d="M33.245-31.998h513.97zM599.298 243.993c-67.851 67.947-135.707 135.83-203.683 203.62-3.122 3.045-6.934 5.857-10.933 7.327-15.618 5.7-31.74-6.404-31.864-23.837-0.127-33.832-0.063-67.666-0.063-101.483 0-2.14 0-4.31 0-6.982-3.062 0-5.435 0-7.81 0-53.123 0-106.167 0.031-159.243 0-17.589-0.016-27.43-9.855-27.43-27.461-0.030-46.389-0.030-92.75 0-139.173 0-17.9 9.841-27.74 27.805-27.74 52.887-0.031 105.712 0 158.555 0 2.438 0 4.811 0 8.123 0 0-2.688 0-4.687 0-6.654 0-33.397 0.125-66.851 0-100.248 0-11.529 4.623-20.12 15.121-24.774 10.558-4.685 19.68-1.405 27.615 6.469 67.978 68.039 136.016 136.017 203.994 204.087 11.745 11.716 11.62 25.056-0.187 36.847zM266.298 22.365c-1.313 9.81-11.028 16.058-13.964 17.713-4.405 2.593-9.185 2.906-12.841 2.906l-3.185-0.063-113.963 0.094c-28.021 0.062-49.234 21.087-49.296 48.858-0.094 88.563-0.094 177.161 0.032 265.755 0.029 26.991 21.335 48.265 48.484 48.406l117.961 0.031c16.713 0.015 25.647 8.060 27.24 24.618 0.5 6.482 0.405 13.026 0.219 19.586-0.564 19.869-9.685 28.74-29.553 28.756l-108.933-0.060c-7.747 0-15.463-0.281-23.18-1.172-57.76-6.577-103.778-56.825-104.841-114.399-0.656-31.944-0.498-63.902-0.342-95.859l0.093-30.101h-0.189c0 0-0.060-113.977 0.033-144.906 0.124-67.851 50.764-120.398 117.772-122.24 16.588-0.438 33.209-0.594 49.827-0.594h0.032l74.006 0.377c14.684 0 23.741 8.685 24.865 23.772 0.877 11.029 0.814 20.087-0.279 28.522z" horiz-adv-x="608" />
26
 <glyph unicode="&#xe610;" d="M33.245-31.998h513.97zM599.298 243.993c-67.851 67.947-135.707 135.83-203.683 203.62-3.122 3.045-6.934 5.857-10.933 7.327-15.618 5.7-31.74-6.404-31.864-23.837-0.127-33.832-0.063-67.666-0.063-101.483 0-2.14 0-4.31 0-6.982-3.062 0-5.435 0-7.81 0-53.123 0-106.167 0.031-159.243 0-17.589-0.016-27.43-9.855-27.43-27.461-0.030-46.389-0.030-92.75 0-139.173 0-17.9 9.841-27.74 27.805-27.74 52.887-0.031 105.712 0 158.555 0 2.438 0 4.811 0 8.123 0 0-2.688 0-4.687 0-6.654 0-33.397 0.125-66.851 0-100.248 0-11.529 4.623-20.12 15.121-24.774 10.558-4.685 19.68-1.405 27.615 6.469 67.978 68.039 136.016 136.017 203.994 204.087 11.745 11.716 11.62 25.056-0.187 36.847zM266.298 22.365c-1.313 9.81-11.028 16.058-13.964 17.713-4.405 2.593-9.185 2.906-12.841 2.906l-3.185-0.063-113.963 0.094c-28.021 0.062-49.234 21.087-49.296 48.858-0.094 88.563-0.094 177.161 0.032 265.755 0.029 26.991 21.335 48.265 48.484 48.406l117.961 0.031c16.713 0.015 25.647 8.060 27.24 24.618 0.5 6.482 0.405 13.026 0.219 19.586-0.564 19.869-9.685 28.74-29.553 28.756l-108.933-0.060c-7.747 0-15.463-0.281-23.18-1.172-57.76-6.577-103.778-56.825-104.841-114.399-0.656-31.944-0.498-63.902-0.342-95.859l0.093-30.101h-0.189c0 0-0.060-113.977 0.033-144.906 0.124-67.851 50.764-120.398 117.772-122.24 16.588-0.438 33.209-0.594 49.827-0.594h0.032l74.006 0.377c14.684 0 23.741 8.685 24.865 23.772 0.877 11.029 0.814 20.087-0.279 28.522z" horiz-adv-x="608" />
27
+<glyph unicode="&#xe611;" d="M419.667 193.243c0-39.599-32.128-71.731-71.743-71.731-39.587 0-71.716 32.131-71.716 71.731 0 39.613 32.129 71.716 71.716 71.716 39.615-0.002 71.743-32.102 71.743-71.716zM686.385 353.243c-3.298 19.729-14.748 32.084-35.303 34.638-11.894 1.459-19.128 7.819-22.363 19.52-4.853 17.76-16.839 29.496-33.905 35.38-12.404 4.298-25.15 8.231-38.093 10.245-34.827 5.455-70.255 7.962-104.763 14.971-34.765 7.089-69.526 11.671-104.447 12.036-34.923-0.365-69.685-4.947-104.447-12.036-34.511-7.008-69.938-9.514-104.763-14.97-12.942-2.014-25.692-5.948-38.094-10.245-17.065-5.884-29.052-17.62-33.907-35.38-3.234-11.702-10.467-18.061-22.361-19.52-20.552-2.554-32.003-14.91-35.3-34.639-3.394-20.205-5.869-40.601-8.405-60.942-1.364-11.055 3.203-16.288 15.193-16.224 60.419 0.349 120.846 0.349 181.298 0.048 12.023-0.064 16.558 4.694 16.716 16.669 0.381 28.685-2.315 55.947-23.568 78.309-3.52 3.695-5.424 12.64-3.363 16.923 2.031 4.25 20.428 8.358 22.996 8.358 21.664-0.095 21.569-0.111 24.709-21.712 0.602-4.14 1.269-9.23 3.901-11.926 15.16-15.431 10.626-33.353 3.617-48.817-15.195-33.57-30.768-67.413-50.434-98.435-36.729-57.916-52.205-80.372-99.34-130.422-29.337-31.147-34.286-50.844-34.286-68.733 0-35.495 20.519-48.372 68.574-48.372 90.807 0 130.454 0.158 221.264 0.158 90.807 0 130.452-0.158 221.264-0.158 48.051 0 68.571 12.876 68.571 48.372 0 17.889-4.949 37.587-34.287 68.733-47.132 50.050-62.611 72.506-99.339 130.422-19.666 31.021-35.238 64.865-50.43 98.435-7.010 15.464-11.546 33.385 3.617 48.817 2.632 2.697 3.298 7.787 3.901 11.926 3.14 21.601 3.045 21.617 24.706 21.712 2.57 0 20.966-4.108 22.996-8.358 2.063-4.283 0.159-13.227-3.361-16.923-21.251-22.362-23.949-49.625-23.568-78.309 0.158-11.974 4.694-16.733 16.715-16.669 60.454 0.302 120.877 0.302 181.3-0.048 11.987-0.063 16.554 5.171 15.192 16.224-2.538 20.343-5.009 40.737-8.403 60.942zM479.995 191.752c0-73.157-59.312-132.468-132.485-132.468s-132.487 59.31-132.487 132.468c0 73.159 59.312 132.468 132.487 132.468 73.171 0 132.485-59.31 132.485-132.468z" horiz-adv-x="695" />
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" />
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" />
27
 </font></defs></svg>
33
 </font></defs></svg>

Binär
fonts/jitsi.ttf Visa fil


Binär
fonts/jitsi.woff Visa fil


Binär
images/avatar2.png Visa fil


Binär
images/welcome_page/bubble.png Visa fil


Binär
images/welcome_page/jitsi-logo.png Visa fil


Binär
images/welcome_page/pattern-body.png Visa fil


Binär
images/welcome_page/pattern-header.png Visa fil


+ 208
- 83
index.html Visa fil

1
-<html itemscope itemtype="http://schema.org/Product" prefix="og: http://ogp.me/ns#">
1
+<html itemscope itemtype="http://schema.org/Product" prefix="og: http://ogp.me/ns#" xmlns="http://www.w3.org/1999/html">
2
   <head>
2
   <head>
3
     <title>Jitsi Videobridge meets WebRTC</title>
3
     <title>Jitsi Videobridge meets WebRTC</title>
4
     <link rel="icon" type="image/png" href="/images/favicon.ico"/>
4
     <link rel="icon" type="image/png" href="/images/favicon.ico"/>
9
     <meta itemprop="name" content="Jitsi Meet"/>
9
     <meta itemprop="name" content="Jitsi Meet"/>
10
     <meta itemprop="description" content="Join a WebRTC video conference powered by the Jitsi Videobridge"/>
10
     <meta itemprop="description" content="Join a WebRTC video conference powered by the Jitsi Videobridge"/>
11
     <meta itemprop="image" content="/images/jitsilogo.png"/>
11
     <meta itemprop="image" content="/images/jitsilogo.png"/>
12
-    <script src="//ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
12
+    <script src="libs/jquery.min.js"></script>
13
     <script src="libs/strophe/strophe.jingle.adapter.js?v=1"></script><!-- strophe.jingle bundles -->
13
     <script src="libs/strophe/strophe.jingle.adapter.js?v=1"></script><!-- strophe.jingle bundles -->
14
     <script src="libs/strophe/strophe.jingle.bundle.js?v=8"></script>
14
     <script src="libs/strophe/strophe.jingle.bundle.js?v=8"></script>
15
     <script src="libs/strophe/strophe.jingle.js?v=1"></script>
15
     <script src="libs/strophe/strophe.jingle.js?v=1"></script>
19
     <script src="libs/strophe/strophe.jingle.session.js?v=1"></script>
19
     <script src="libs/strophe/strophe.jingle.session.js?v=1"></script>
20
     <script src="libs/colibri/colibri.focus.js?v=8"></script><!-- colibri focus implementation -->
20
     <script src="libs/colibri/colibri.focus.js?v=8"></script><!-- colibri focus implementation -->
21
     <script src="libs/colibri/colibri.session.js?v=1"></script>
21
     <script src="libs/colibri/colibri.session.js?v=1"></script>
22
-    <script src="//code.jquery.com/ui/1.10.4/jquery-ui.js"></script>
22
+    <script src="libs/jquery-ui.js"></script>
23
+    <script src="libs/rayo.js?v=1"></script>
23
     <script src="libs/tooltip.js?v=1"></script><!-- bootstrap tooltip lib -->
24
     <script src="libs/tooltip.js?v=1"></script><!-- bootstrap tooltip lib -->
24
     <script src="libs/popover.js?v=1"></script><!-- bootstrap tooltip lib -->
25
     <script src="libs/popover.js?v=1"></script><!-- bootstrap tooltip lib -->
25
-    <script src="config.js?v=2"></script><!-- adapt to your needs, i.e. set hosts and bosh path -->
26
-    <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 -->
27
     <script src="estos_log.js?v=2"></script><!-- simple stanza logger -->
28
     <script src="estos_log.js?v=2"></script><!-- simple stanza logger -->
28
     <script src="desktopsharing.js?v=2"></script><!-- desktop sharing -->
29
     <script src="desktopsharing.js?v=2"></script><!-- desktop sharing -->
29
-    <script src="data_channels.js?v=1"></script><!-- data channels -->
30
-    <script src="app.js?v=28"></script><!-- application logic -->
30
+    <script src="data_channels.js?v=3"></script><!-- data channels -->
31
+    <script src="app.js?v=5"></script><!-- application logic -->
31
     <script src="commands.js?v=1"></script><!-- application logic -->
32
     <script src="commands.js?v=1"></script><!-- application logic -->
32
-    <script src="chat.js?v=6"></script><!-- chat logic -->
33
-    <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 -->
34
     <script src="etherpad.js?v=8"></script><!-- etherpad plugin -->
36
     <script src="etherpad.js?v=8"></script><!-- etherpad plugin -->
35
     <script src="prezi.js?v=4"></script><!-- prezi plugin -->
37
     <script src="prezi.js?v=4"></script><!-- prezi plugin -->
36
     <script src="smileys.js?v=2"></script><!-- smiley images -->
38
     <script src="smileys.js?v=2"></script><!-- smiley images -->
38
     <script src="moderatemuc.js?v=3"></script><!-- moderator plugin -->
40
     <script src="moderatemuc.js?v=3"></script><!-- moderator plugin -->
39
     <script src="analytics.js?v=1"></script><!-- google analytics plugin -->
41
     <script src="analytics.js?v=1"></script><!-- google analytics plugin -->
40
     <script src="rtp_stats.js?v=1"></script><!-- RTP stats processing -->
42
     <script src="rtp_stats.js?v=1"></script><!-- RTP stats processing -->
41
-    <script src="videolayout.js?v=3"></script><!-- video ui -->
42
-    <script src="toolbar.js?v=2"></script><!-- toolbar ui -->
43
+    <script src="local_stats.js?v=1"></script><!-- Local stats processing -->
44
+    <script src="videolayout.js?v=8"></script><!-- video ui -->
45
+    <script src="toolbar.js?v=4"></script><!-- toolbar ui -->
46
+    <script src="canvas_util.js?v=1"></script><!-- canvas drawing utils -->
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 -->
43
     <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">
44
-    <link rel="stylesheet" href="css/font.css"/>
45
-    <link rel="stylesheet" type="text/css" media="screen" href="css/main.css?v=21"/>
46
-    <link rel="stylesheet" type="text/css" media="screen" href="css/videolayout_default.css?v=7" id="videolayout_default"/>
51
+    <link rel="stylesheet" href="css/font.css?v=3"/>
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"/>
47
     <link rel="stylesheet" href="css/jquery-impromptu.css?v=4">
54
     <link rel="stylesheet" href="css/jquery-impromptu.css?v=4">
48
     <link rel="stylesheet" href="css/modaldialog.css?v=3">
55
     <link rel="stylesheet" href="css/modaldialog.css?v=3">
49
     <link rel="stylesheet" href="css/popup_menu.css?v=2">
56
     <link rel="stylesheet" href="css/popup_menu.css?v=2">
50
     <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">
51
     <!--
59
     <!--
52
         Link used for inline installation of chrome desktop streaming extension,
60
         Link used for inline installation of chrome desktop streaming extension,
53
         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 -->
57
     <script src="libs/prezi_player.js?v=2"></script>
65
     <script src="libs/prezi_player.js?v=2"></script>
58
   </head>
66
   </head>
59
   <body>
67
   <body>
60
-    <div style="position: relative;" id="header_container">
61
-        <div id="header">
62
-            <span id="toolbar">
63
-                <a class="button" data-toggle="popover" data-placement="bottom" data-content="Mute / Unmute" onclick='toggleAudio();'>
64
-                    <i id="mute" class="icon-microphone"></i></a>
65
-                <div class="header_button_separator"></div>
66
-                <a class="button" data-toggle="popover" data-placement="bottom" data-content="Start / stop camera" onclick='buttonClick("#video", "icon-camera icon-camera-disabled");toggleVideo();'>
67
-                    <i id="video" class="icon-camera"></i></a>
68
-                <div class="header_button_separator"></div>
69
-                <a class="button" data-toggle="popover" data-placement="bottom" data-content="Lock / unlock room" onclick="Toolbar.openLockDialog();">
70
-                    <i id="lockIcon" class="icon-security"></i></a>
71
-                <div class="header_button_separator"></div>
72
-                <a class="button" data-toggle="popover" data-placement="bottom" data-content="Invite others" onclick="Toolbar.openLinkDialog();"><i class="icon-link"></i></a>
73
-                <div class="header_button_separator"></div>
74
-                <span class="toolbar_span">
75
-                    <a class="button" data-toggle="popover" data-placement="bottom" data-content="Open / close chat" onclick='Chat.toggleChat();'><i id="chatButton" class="icon-chat"></i></a>
76
-                    <span id="unreadMessages"></span>
77
-                </span>
78
-                <div class="header_button_separator"></div>
79
-                <a class="button" data-toggle="popover" data-placement="bottom" data-content="Share Prezi" onclick='Prezi.openPreziDialog();'><i class="icon-prezi"></i></a>
80
-                <span id="etherpadButton">
68
+    <div id="welcome_page">
69
+        <div id="welcome_page_header">
70
+            <a href="http://jitsi.org" target="_new">
71
+                <div id="jitsi_logo"></div>
72
+            </a>
73
+
74
+            <div id="enter_room">
75
+                <input id="enter_room_field" type="text" placeholder="Enter room name" />
76
+                <input id="enter_room_button" type="button" value="GO" />
77
+            </div>
78
+            <div id="brand_header"></div>
79
+            <div id="header_text"></div>
80
+        </div>
81
+        <div id="welcome_page_main">
82
+            <div id="features">
83
+                <div class="feature_row">
84
+                    <div class="feature_holder">
85
+                        <div class="feature_icon">Simple to use</div>
86
+                        <div class="feature_description">
87
+                            No downloads required. uTalk works directly within your browser. Simply share your conference URL with others to get started.
88
+                        </div>
89
+                    </div>
90
+                    <div class="feature_holder">
91
+                        <div class="feature_icon">Low bandwidth</div>
92
+                        <div class="feature_description">
93
+                            Multi-party video conferences work with as little as 128Kbps. Screen-sharing and audio-only conferences are possible with far less.
94
+                        </div>
95
+                    </div>
96
+                    <div class="feature_holder">
97
+                        <div class="feature_icon">Open source</div>
98
+                        <div class="feature_description">
99
+                            uTalk is licensed under the &lt;GPL/LGPL/WHATEVER&gt;. You can download, use, modify, and share the software without any restrictions.
100
+                        </div>
101
+                    </div>
102
+                    <div class="feature_holder">
103
+                        <div class="feature_icon">Unlimited users</div>
104
+                        <div class="feature_description">
105
+                            There are no artificial restrictions on the number of users or conference participants. Server power and bandwidth are the only limiting factors.
106
+                        </div>
107
+                    </div>
108
+                </div>
109
+                <div class="feature_row">
110
+                    <div class="feature_holder">
111
+                        <div class="feature_icon">Screen sharing</div>
112
+                        <div class="feature_description">
113
+                            It's easy to share your screen with others. uTalk is ideal for on-line presentations, lectures, and tech support sessions.
114
+                        </div>
115
+                    </div>
116
+                    <div class="feature_holder">
117
+                        <div class="feature_icon">Secure rooms</div>
118
+                        <div class="feature_description">
119
+                            Need some privacy? uTalk conference rooms can be secured with a password in order to exclude unwanted guests and prevent interruptions.
120
+                        </div>
121
+                    </div>
122
+                    <div class="feature_holder">
123
+                        <div class="feature_icon">Shared notes</div>
124
+                        <div class="feature_description">
125
+                            uTalk features Etherpad, a real-time collaborative text editor that's great for meeting minutes, writing articles, and more.
126
+                        </div>
127
+                    </div>
128
+                    <div class="feature_holder">
129
+                        <div class="feature_icon">Usage statistics</div>
130
+                        <div class="feature_description">
131
+                            Learn about your users through easy integration with Piwik, Google Analytics, and other usage monitoring and statistics systems.
132
+                        </div>
133
+                    </div>
134
+                </div>
135
+            </div>
136
+        </div>
137
+    </div>
138
+    <div id="videoconference_page">
139
+        <div style="position: relative;" id="header_container">
140
+            <div id="header">
141
+                <span id="toolbar">
142
+                    <a class="button" data-toggle="popover" data-placement="bottom" data-content="Mute / Unmute" onclick='toggleAudio();'>
143
+                        <i id="mute" class="icon-microphone"></i>
144
+                    </a>
81
                     <div class="header_button_separator"></div>
145
                     <div class="header_button_separator"></div>
82
-                    <a class="button" data-toggle="popover" data-placement="bottom" data-content="Shared document" onclick='Etherpad.toggleEtherpad(0);'><i class="icon-share-doc"></i></a>
83
-                </span>
84
-                <div class="header_button_separator"></div>
85
-                <span id="desktopsharing" style="display: none">
86
-                    <a class="button" data-toggle="popover" data-placement="bottom" data-content="Share screen" onclick="toggleScreenSharing();"><i class="icon-share-desktop"></i></a>
146
+                    <a class="button" data-toggle="popover" data-placement="bottom" data-content="Start / stop camera" onclick='buttonClick("#video", "icon-camera icon-camera-disabled");toggleVideo();'>
147
+                        <i id="video" class="icon-camera"></i>
148
+                    </a>
149
+                    <span id="recording" style="display: none">
150
+                        <div class="header_button_separator"></div>
151
+                        <a class="button" data-toggle="popover" data-placement="bottom" data-content="Record" onclick='toggleRecording();'>
152
+                            <i id="recordButton" class="icon-recEnable"></i>
153
+                        </a>
154
+                    </span>
155
+                    <div class="header_button_separator"></div>
156
+                    <a class="button" data-toggle="popover" data-placement="bottom" data-content="Lock / unlock room" onclick="Toolbar.openLockDialog();">
157
+                        <i id="lockIcon" class="icon-security"></i>
158
+                    </a>
159
+                    <div class="header_button_separator"></div>
160
+                    <a class="button" data-toggle="popover" data-placement="bottom" data-content="Invite others" onclick="Toolbar.openLinkDialog();">
161
+                        <i class="icon-link"></i>
162
+                    </a>
163
+                    <div class="header_button_separator"></div>
164
+                    <span class="toolbar_span">
165
+                        <a class="button" data-toggle="popover" data-placement="bottom" data-content="Open / close chat" onclick='BottomToolbar.toggleChat();'>
166
+                            <i id="chatButton" class="icon-chat"></i>
167
+                        </a>
168
+                        <span id="unreadMessages"></span>
169
+                    </span>
170
+                    <div class="header_button_separator"></div>
171
+                    <a class="button" data-toggle="popover" data-placement="bottom" data-content="Share Prezi" onclick='Prezi.openPreziDialog();'>
172
+                        <i class="icon-prezi"></i>
173
+                    </a>
174
+                    <span id="etherpadButton">
175
+                        <div class="header_button_separator"></div>
176
+                        <a class="button" data-toggle="popover" data-placement="bottom" data-content="Shared document" onclick='Etherpad.toggleEtherpad(0);'>
177
+                            <i class="icon-share-doc"></i>
178
+                        </a>
179
+                    </span>
180
+                    <div class="header_button_separator"></div>
181
+                    <span id="desktopsharing" style="display: none">
182
+                        <a class="button" data-toggle="popover" data-placement="bottom" data-content="Share screen" onclick="toggleScreenSharing();">
183
+                            <i class="icon-share-desktop"></i>
184
+                        </a>
185
+                    </span>
87
                     <div class="header_button_separator"></div>
186
                     <div class="header_button_separator"></div>
187
+                    <a class="button" data-toggle="popover" data-placement="bottom" data-content="Enter / Exit Full Screen" onclick='buttonClick("#fullScreen", "icon-full-screen icon-exit-full-screen");Toolbar.toggleFullScreen();'>
188
+                        <i id="fullScreen" class="icon-full-screen"></i>
189
+                    </a>
190
+                    <span id="sipCallButton">
191
+                        <div class="header_button_separator"></div>
192
+                        <a class="button" data-toggle="popover" data-placement="bottom" data-content="Call SIP number" onclick='callSipButtonClicked();'>
193
+                            <i class="icon-telephone"></i></a>
194
+                    </span>
88
                 </span>
195
                 </span>
89
-                <a class="button" data-toggle="popover" data-placement="bottom" data-content="Enter / Exit Full Screen" onclick='buttonClick("#fullScreen", "icon-full-screen icon-exit-full-screen");Toolbar.toggleFullScreen();'>
90
-                    <i id="fullScreen" class="icon-full-screen"></i></a>
91
-            </span>
196
+            </div>
197
+            <div id="subject"></div>
92
         </div>
198
         </div>
93
-        <div id="subject"></div>
94
-    </div>
95
-    <div id="settings">
96
-      <h1>Connection Settings</h1>
97
-      <form id="loginInfo">
98
-        <label>JID: <input id="jid" type="text" name="jid" placeholder="me@example.com"/></label>
99
-        <label>Password: <input id="password" type="password" name="password" placeholder="secret"/></label>
100
-        <label>BOSH URL: <input id="boshURL" type="text" name="boshURL" placeholder="/http-bind"/></label>
101
-        <input id="connect" type="submit" value="Connect" />
102
-      </form>
103
-    </div>
104
-    <div id="reloadPresentation"><a onclick='Prezi.reloadPresentation();'><i title="Reload Prezi" class="fa fa-repeat fa-lg"></i></a></div>
105
-    <div id="videospace" onmousemove="Toolbar.showToolbar();">
106
-        <div id="largeVideoContainer" class="videocontainer">
107
-            <div id="presentation"></div>
108
-            <div id="etherpad"></div>
109
-            <a href="http://jitsi.org" target="_new"><div class="watermark" id="leftwatermark"></div></a>
110
-            <!-- a href="http://jitsi.org" target="_new"><div class="watermark" id="rightwatermark"></div></a -->
111
-            <video id="largeVideo" autoplay oncontextmenu="return false;"></video>
199
+        <div id="settings">
200
+          <h1>Connection Settings</h1>
201
+          <form id="loginInfo">
202
+            <label>JID: <input id="jid" type="text" name="jid" placeholder="me@example.com"/></label>
203
+            <label>Password: <input id="password" type="password" name="password" placeholder="secret"/></label>
204
+            <label>BOSH URL: <input id="boshURL" type="text" name="boshURL" placeholder="/http-bind"/></label>
205
+            <input id="connect" type="submit" value="Connect" />
206
+          </form>
112
         </div>
207
         </div>
113
-        <div id="remoteVideos">
114
-            <span id="localVideoContainer" class="videocontainer">
115
-                <span id="localNick" class="nick"></span>
116
-                <span id="localVideoWrapper">
117
-                    <!--<video id="localVideo" autoplay oncontextmenu="return false;" muted></video> - is now per stream generated -->
208
+        <div id="reloadPresentation"><a onclick='Prezi.reloadPresentation();'><i title="Reload Prezi" class="fa fa-repeat fa-lg"></i></a></div>
209
+        <div id="videospace" onmousemove="Toolbar.showToolbar();">
210
+            <div id="largeVideoContainer" class="videocontainer">
211
+                <div id="presentation"></div>
212
+                <div id="etherpad"></div>
213
+                <a href="http://jitsi.org" target="_new"><div class="watermark" id="leftwatermark"></div></a>
214
+                <!-- a href="http://jitsi.org" target="_new"><div class="watermark" id="rightwatermark"></div></a -->
215
+                <video id="largeVideo" autoplay oncontextmenu="return false;"></video>
216
+            </div>
217
+            <div id="remoteVideos">
218
+                <span id="localVideoContainer" class="videocontainer">
219
+                    <span id="localNick" class="nick"></span>
220
+                    <span id="localVideoWrapper">
221
+                        <!--<video id="localVideo" autoplay oncontextmenu="return false;" muted></video> - is now per stream generated -->
222
+                    </span>
223
+                    <audio id="localAudio" autoplay oncontextmenu="return false;" muted></audio>
224
+                    <span class="focusindicator"></span>
225
+                </span>
226
+                <audio id="userJoined" src="sounds/joined.wav" preload="auto"></audio>
227
+                <audio id="userLeft" src="sounds/left.wav" preload="auto"></audio>
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>
118
                 </span>
241
                 </span>
119
-                <audio id="localAudio" autoplay oncontextmenu="return false;" muted></audio>
120
-                <span class="focusindicator" data-content="The owner of&#10;this conference" data-toggle="popover" data-placement="top"></span>
121
             </span>
242
             </span>
122
-            <audio id="userJoined" src="sounds/joined.wav" preload="auto"></audio>
123
-            <audio id="userLeft" src="sounds/left.wav" preload="auto"></audio>
124
-        </div>
125
-    </div>
126
-    <div id="chatspace">
127
-        <div id="nickname">
128
-            Enter a nickname in the box below
129
-            <form>
130
-                <input type='text' id="nickinput" placeholder='Choose a nickname' autofocus>
131
-            </form>
132
         </div>
243
         </div>
244
+        <div id="chatspace">
245
+            <div id="nickname">
246
+                Enter a nickname in the box below
247
+                <form>
248
+                    <input type='text' id="nickinput" placeholder='Choose a nickname' autofocus>
249
+                </form>
250
+            </div>
133
 
251
 
134
-        <!--div><i class="fa fa-comments">&nbsp;</i><span class='nick'></span>:&nbsp;<span class='chattext'></span></div-->
135
-        <div id="chatconversation"></div>
136
-        <audio id="chatNotification" src="sounds/incomingMessage.wav" preload="auto"></audio>
137
-        <textarea id="usermsg" placeholder='Enter text...' autofocus></textarea>
252
+            <!--div><i class="fa fa-comments">&nbsp;</i><span class='nick'></span>:&nbsp;<span class='chattext'></span></div-->
253
+            <div id="chatconversation"></div>
254
+            <audio id="chatNotification" src="sounds/incomingMessage.wav" preload="auto"></audio>
255
+            <textarea id="usermsg" placeholder='Enter text...' autofocus></textarea>
256
+        </div>
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>
258
+    </div>
259
+    <div id="contactlist">
260
+        <ul>
261
+            <li class="title"><i class="icon-contact-list"></i> CONTACT LIST</li>
262
+        </ul>
138
     </div>
263
     </div>
139
     <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>
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>
140
   </body>
265
   </body>

+ 356
- 99
libs/colibri/colibri.focus.js Visa fil

54
      * Default channel expire value in seconds.
54
      * Default channel expire value in seconds.
55
      * @type {number}
55
      * @type {number}
56
      */
56
      */
57
-    this.channelExpire = 60;
57
+    this.channelExpire
58
+        = ('number' === typeof(config.channelExpire))
59
+            ? config.channelExpire
60
+            : 15;
61
+    /**
62
+     * Default channel last-n value.
63
+     * @type {number}
64
+     */
65
+    this.channelLastN
66
+        = ('number' === typeof(config.channelLastN)) ? config.channelLastN : -1;
58
 
67
 
59
     // media types of the conference
68
     // media types of the conference
60
     if (config.openSctp)
69
     if (config.openSctp)
61
-    {
62
         this.media = ['audio', 'video', 'data'];
70
         this.media = ['audio', 'video', 'data'];
63
-    }
64
     else
71
     else
65
-    {
66
         this.media = ['audio', 'video'];
72
         this.media = ['audio', 'video'];
67
-    }
68
 
73
 
69
     this.connection.jingle.sessions[this.sid] = this;
74
     this.connection.jingle.sessions[this.sid] = this;
75
+    this.bundledTransports = {};
70
     this.mychannel = [];
76
     this.mychannel = [];
71
     this.channels = [];
77
     this.channels = [];
72
     this.remotessrc = {};
78
     this.remotessrc = {};
77
 
83
 
78
     // silly wait flag
84
     // silly wait flag
79
     this.wait = true;
85
     this.wait = true;
86
+
87
+    this.recordingEnabled = false;
88
+
89
+    // stores information about the endpoints (i.e. display names) to
90
+    // be sent to the videobridge.
91
+    this.endpointsInfo = null;
80
 }
92
 }
81
 
93
 
82
 // creates a conferences with an initial set of peers
94
 // creates a conferences with an initial set of peers
164
     */
176
     */
165
 };
177
 };
166
 
178
 
179
+// Sends a COLIBRI message which enables or disables (according to 'state') the
180
+// recording on the bridge. Waits for the result IQ and calls 'callback' with
181
+// the new recording state, according to the IQ.
182
+ColibriFocus.prototype.setRecording = function(state, token, callback) {
183
+    var self = this;
184
+    var elem = $iq({to: this.bridgejid, type: 'set'});
185
+    elem.c('conference', {
186
+        xmlns: 'http://jitsi.org/protocol/colibri',
187
+        id: this.confid
188
+    });
189
+    elem.c('recording', {state: state, token: token});
190
+    elem.up();
191
+
192
+    this.connection.sendIQ(elem,
193
+        function (result) {
194
+            console.log('Set recording "', state, '". Result:', result);
195
+            var recordingElem = $(result).find('>conference>recording');
196
+            var newState = ('true' === recordingElem.attr('state'));
197
+
198
+            self.recordingEnabled = newState;
199
+            callback(newState);
200
+        },
201
+        function (error) {
202
+            console.warn(error);
203
+        }
204
+    );
205
+};
206
+
207
+/*
208
+ * Updates the display name for an endpoint with a specific jid.
209
+ * jid: the jid associated with the endpoint.
210
+ * displayName: the new display name for the endpoint.
211
+ */
212
+ColibriFocus.prototype.setEndpointDisplayName = function(jid, displayName) {
213
+    var endpointId = jid.substr(1 + jid.lastIndexOf('/'));
214
+    var update = false;
215
+
216
+    if (this.endpointsInfo === null) {
217
+       this.endpointsInfo = {};
218
+    }
219
+
220
+    var endpointInfo = this.endpointsInfo[endpointId];
221
+    if ('undefined' === typeof endpointInfo) {
222
+        endpointInfo = this.endpointsInfo[endpointId] = {};
223
+    }
224
+
225
+    if (endpointInfo['displayname'] !== displayName) {
226
+        endpointInfo['displayname'] = displayName;
227
+        update = true;
228
+    }
229
+
230
+    if (update) {
231
+        this.updateEndpoints();
232
+    }
233
+};
234
+
235
+/*
236
+ * Sends a colibri message to the bridge that contains the
237
+ * current endpoints and their display names.
238
+ */
239
+ColibriFocus.prototype.updateEndpoints = function() {
240
+    if (this.confid === null
241
+        || this.endpointsInfo === null) {
242
+        return;
243
+    }
244
+
245
+    if (this.confid === 0) {
246
+        // the colibri conference is currently initiating
247
+        var self = this;
248
+        window.setTimeout(function() { self.updateEndpoints()}, 1000);
249
+        return;
250
+    }
251
+
252
+    var elem = $iq({to: this.bridgejid, type: 'set'});
253
+    elem.c('conference', {
254
+        xmlns: 'http://jitsi.org/protocol/colibri',
255
+        id: this.confid
256
+    });
257
+
258
+    for (var id in this.endpointsInfo) {
259
+        elem.c('endpoint');
260
+        elem.attrs({ id: id,
261
+                     displayname: this.endpointsInfo[id]['displayname']
262
+        });
263
+        elem.up();
264
+    }
265
+
266
+    //elem.up(); //conference
267
+
268
+    this.connection.sendIQ(
269
+        elem,
270
+        function (result) {},
271
+        function (error) { console.warn(error); }
272
+    );
273
+};
274
+
167
 ColibriFocus.prototype._makeConference = function () {
275
 ColibriFocus.prototype._makeConference = function () {
168
     var self = this;
276
     var self = this;
169
-    var elem = $iq({to: this.bridgejid, type: 'get'});
170
-    elem.c('conference', {xmlns: 'http://jitsi.org/protocol/colibri'});
277
+    var elem = $iq({ to: this.bridgejid, type: 'get' });
278
+    elem.c('conference', { xmlns: 'http://jitsi.org/protocol/colibri' });
171
 
279
 
172
     this.media.forEach(function (name) {
280
     this.media.forEach(function (name) {
173
-        var isData = name === 'data';
174
-        var channel = isData ? 'sctpconnection' : 'channel';
281
+        var elemName;
282
+        var elemAttrs = { initiator: 'true', expire: self.channelExpire };
175
 
283
 
176
-        elem.c('content', {name: name});
284
+        if ('data' === name)
285
+        {
286
+            elemName = 'sctpconnection';
287
+            elemAttrs['port'] = 5000;
288
+        }
289
+        else
290
+        {
291
+            elemName = 'channel';
292
+            if (('video' === name) && (self.channelLastN >= 0))
293
+                elemAttrs['last-n'] = self.channelLastN;
294
+        }
177
 
295
 
178
-        elem.c(channel, {
179
-            initiator: 'true',
180
-            expire: '15',
181
-            endpoint: self.myMucResource
182
-        });
183
-        if (isData)
184
-            elem.attrs({port:  5000});
185
-        elem.up();// end of channel
296
+        elem.c('content', { name: name });
297
+
298
+        elem.c(elemName, elemAttrs);
299
+        elem.attrs({ endpoint: self.myMucResource });
300
+        if (config.useBundle) {
301
+            elem.attrs({ 'channel-bundle-id': self.myMucResource });
302
+        }
303
+        elem.up();// end of channel/sctpconnection
186
 
304
 
187
         for (var j = 0; j < self.peers.length; j++) {
305
         for (var j = 0; j < self.peers.length; j++) {
188
-            elem.c(channel, {
189
-                initiator: 'true',
190
-                expire: '15',
191
-                endpoint: self.peers[j].substr(1 + self.peers[j].lastIndexOf('/'))
192
-            });
193
-            if (isData)
194
-                elem.attrs({port: 5000});
195
-            elem.up(); // end of channel
306
+            var peer = self.peers[j];
307
+            var peerEndpoint = peer.substr(1 + peer.lastIndexOf('/'));
308
+
309
+            elem.c(elemName, elemAttrs);
310
+            elem.attrs({ endpoint: peerEndpoint });
311
+            if (config.useBundle) {
312
+                elem.attrs({ 'channel-bundle-id': peerEndpoint });
313
+            }
314
+            elem.up(); // end of channel/sctpconnection
196
         }
315
         }
197
         elem.up(); // end of content
316
         elem.up(); // end of content
198
     });
317
     });
318
+
319
+    if (this.endpointsInfo !== null) {
320
+        for (var id in this.endpointsInfo) {
321
+            elem.c('endpoint');
322
+            elem.attrs({ id: id,
323
+                         displayname: this.endpointsInfo[id]['displayname']
324
+            });
325
+            elem.up();
326
+        }
327
+    }
328
+
199
     /*
329
     /*
200
     var localSDP = new SDP(this.peerconnection.localDescription.sdp);
330
     var localSDP = new SDP(this.peerconnection.localDescription.sdp);
201
     localSDP.media.forEach(function (media, channel) {
331
     localSDP.media.forEach(function (media, channel) {
202
         var name = SDPUtil.parse_mline(media.split('\r\n')[0]).media;
332
         var name = SDPUtil.parse_mline(media.split('\r\n')[0]).media;
203
         elem.c('content', {name: name});
333
         elem.c('content', {name: name});
204
-        elem.c('channel', {initiator: 'false', expire: '15'});
334
+        elem.c('channel', {initiator: 'false', expire: self.channelExpire});
205
 
335
 
206
         // FIXME: should reuse code from .toJingle
336
         // FIXME: should reuse code from .toJingle
207
         var mline = SDPUtil.parse_mline(media.split('\r\n')[0]);
337
         var mline = SDPUtil.parse_mline(media.split('\r\n')[0]);
215
 
345
 
216
         elem.up(); // end of channel
346
         elem.up(); // end of channel
217
         for (j = 0; j < self.peers.length; j++) {
347
         for (j = 0; j < self.peers.length; j++) {
218
-            elem.c('channel', {initiator: 'true', expire:'15' }).up();
348
+            elem.c('channel', {initiator: 'true', expire: self.channelExpire }).up();
219
         }
349
         }
220
         elem.up(); // end of content
350
         elem.up(); // end of content
221
     });
351
     });
231
     );
361
     );
232
 };
362
 };
233
 
363
 
234
-// callback when a conference was created
364
+// callback when a colibri conference was created
235
 ColibriFocus.prototype.createdConference = function (result) {
365
 ColibriFocus.prototype.createdConference = function (result) {
236
     console.log('created a conference on the bridge');
366
     console.log('created a conference on the bridge');
237
     var self = this;
367
     var self = this;
257
         }
387
         }
258
     }
388
     }
259
 
389
 
390
+    // save the 'transport' elements from 'channel-bundle'-s
391
+    var channelBundles = $(result).find('>conference>channel-bundle');
392
+    for (var i = 0; i < channelBundles.length; i++)
393
+    {
394
+        var endpointId = $(channelBundles[i]).attr('id');
395
+        this.bundledTransports[endpointId] = $(channelBundles[i]).find('>transport[xmlns="urn:xmpp:jingle:transports:ice-udp:1"]');
396
+    }
397
+
260
     console.log('remote channels', this.channels);
398
     console.log('remote channels', this.channels);
261
 
399
 
262
     // Notify that the focus has created the conference on the bridge
400
     // Notify that the focus has created the conference on the bridge
268
         's=-\r\n' +
406
         's=-\r\n' +
269
         't=0 0\r\n' +
407
         't=0 0\r\n' +
270
         /* Audio */
408
         /* Audio */
409
+        (config.useBundle
410
+            ? ('a=group:BUNDLE audio video' +
411
+                (config.openSctp ? ' data' : '') +
412
+               '\r\n')
413
+            : '') +
271
         'm=audio 1 RTP/SAVPF 111 103 104 0 8 106 105 13 126\r\n' +
414
         'm=audio 1 RTP/SAVPF 111 103 104 0 8 106 105 13 126\r\n' +
272
         'c=IN IP4 0.0.0.0\r\n' +
415
         'c=IN IP4 0.0.0.0\r\n' +
273
         'a=rtcp:1 IN IP4 0.0.0.0\r\n' +
416
         'a=rtcp:1 IN IP4 0.0.0.0\r\n' +
285
         'a=rtpmap:13 CN/8000\r\n' +
428
         'a=rtpmap:13 CN/8000\r\n' +
286
         'a=rtpmap:126 telephone-event/8000\r\n' +
429
         'a=rtpmap:126 telephone-event/8000\r\n' +
287
         'a=maxptime:60\r\n' +
430
         'a=maxptime:60\r\n' +
431
+        (config.useRtcpMux ? 'a=rtcp-mux\r\n' : '') +
288
         /* Video */
432
         /* Video */
289
         'm=video 1 RTP/SAVPF 100 116 117\r\n' +
433
         'm=video 1 RTP/SAVPF 100 116 117\r\n' +
290
         'c=IN IP4 0.0.0.0\r\n' +
434
         'c=IN IP4 0.0.0.0\r\n' +
299
         'a=rtcp-fb:100 goog-remb\r\n' +
443
         'a=rtcp-fb:100 goog-remb\r\n' +
300
         'a=rtpmap:116 red/90000\r\n' +
444
         'a=rtpmap:116 red/90000\r\n' +
301
         'a=rtpmap:117 ulpfec/90000\r\n' +
445
         'a=rtpmap:117 ulpfec/90000\r\n' +
446
+        (config.useRtcpMux ? 'a=rtcp-mux\r\n' : '') +
302
         /* Data SCTP */
447
         /* Data SCTP */
303
         (config.openSctp ?
448
         (config.openSctp ?
304
             'm=application 1 DTLS/SCTP 5000\r\n' +
449
             'm=application 1 DTLS/SCTP 5000\r\n' +
347
         tmp = $(this.mychannel[channel]).find('>source[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]');
492
         tmp = $(this.mychannel[channel]).find('>source[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]');
348
         // FIXME: check rtp-level-relay-type
493
         // FIXME: check rtp-level-relay-type
349
 
494
 
350
-        var isData = bridgeSDP.media[channel].indexOf('application') !== -1;
351
-        if (!isData && tmp.length)
352
-        {
353
-            bridgeSDP.media[channel] += 'a=ssrc:' + tmp.attr('ssrc') + ' ' + 'cname:mixed' + '\r\n';
354
-            bridgeSDP.media[channel] += 'a=ssrc:' + tmp.attr('ssrc') + ' ' + 'label:mixedlabela0' + '\r\n';
355
-            bridgeSDP.media[channel] += 'a=ssrc:' + tmp.attr('ssrc') + ' ' + 'msid:mixedmslabel mixedlabela0' + '\r\n';
356
-            bridgeSDP.media[channel] += 'a=ssrc:' + tmp.attr('ssrc') + ' ' + 'mslabel:mixedmslabel' + '\r\n';
357
-        }
358
-        else if (!isData)
359
-        {
495
+        var name = bridgeSDP.media[channel].split(" ")[0].substr(2); // 'm=audio ...'
496
+        if (name === 'audio' || name === 'video') {
360
             // make chrome happy... '3735928559' == 0xDEADBEEF
497
             // make chrome happy... '3735928559' == 0xDEADBEEF
361
-            // FIXME: this currently appears as two streams, should be one
362
-            bridgeSDP.media[channel] += 'a=ssrc:' + '3735928559' + ' ' + 'cname:mixed' + '\r\n';
363
-            bridgeSDP.media[channel] += 'a=ssrc:' + '3735928559' + ' ' + 'label:mixedlabelv0' + '\r\n';
364
-            bridgeSDP.media[channel] += 'a=ssrc:' + '3735928559' + ' ' + 'msid:mixedmslabel mixedlabelv0' + '\r\n';
365
-            bridgeSDP.media[channel] += 'a=ssrc:' + '3735928559' + ' ' + 'mslabel:mixedmslabel' + '\r\n';
498
+            var ssrc = tmp.length ? tmp.attr('ssrc') : '3735928559';
499
+
500
+            bridgeSDP.media[channel] += 'a=ssrc:' + ssrc + ' cname:mixed\r\n';
501
+            bridgeSDP.media[channel] += 'a=ssrc:' + ssrc + ' label:mixedlabel' + name + '0\r\n';
502
+            bridgeSDP.media[channel] += 'a=ssrc:' + ssrc + ' msid:mixedmslabel mixedlabel' + name + '0\r\n';
503
+            bridgeSDP.media[channel] += 'a=ssrc:' + ssrc + ' mslabel:mixedmslabel\r\n';
366
         }
504
         }
367
 
505
 
368
         // FIXME: should take code from .fromJingle
506
         // FIXME: should take code from .fromJingle
369
-        tmp = $(this.mychannel[channel]).find('>transport[xmlns="urn:xmpp:jingle:transports:ice-udp:1"]');
507
+        var channelBundleId = $(this.mychannel[channel]).attr('channel-bundle-id');
508
+        if (typeof channelBundleId != 'undefined') {
509
+            tmp = this.bundledTransports[channelBundleId];
510
+        } else {
511
+            tmp = $(this.mychannel[channel]).find('>transport[xmlns="urn:xmpp:jingle:transports:ice-udp:1"]');
512
+        }
513
+
370
         if (tmp.length) {
514
         if (tmp.length) {
371
             bridgeSDP.media[channel] += 'a=ice-ufrag:' + tmp.attr('ufrag') + '\r\n';
515
             bridgeSDP.media[channel] += 'a=ice-ufrag:' + tmp.attr('ufrag') + '\r\n';
372
             bridgeSDP.media[channel] += 'a=ice-pwd:' + tmp.attr('pwd') + '\r\n';
516
             bridgeSDP.media[channel] += 'a=ice-pwd:' + tmp.attr('pwd') + '\r\n';
390
                 function (answer) {
534
                 function (answer) {
391
                     self.peerconnection.setLocalDescription(answer,
535
                     self.peerconnection.setLocalDescription(answer,
392
                         function () {
536
                         function () {
393
-                            console.log('setLocalDescription succeded.');
537
+                            console.log('setLocalDescription succeeded.');
394
                             // make sure our presence is updated
538
                             // make sure our presence is updated
395
                             $(document).trigger('setLocalDescription.jingle', [self.sid]);
539
                             $(document).trigger('setLocalDescription.jingle', [self.sid]);
396
                             var elem = $iq({to: self.bridgejid, type: 'get'});
540
                             var elem = $iq({to: self.bridgejid, type: 'get'});
428
                                         {
572
                                         {
429
                                             initiator: 'true',
573
                                             initiator: 'true',
430
                                             expire: self.channelExpire,
574
                                             expire: self.channelExpire,
575
+                                            id: self.mychannel[channel].attr('id'),
431
                                             endpoint: self.myMucResource,
576
                                             endpoint: self.myMucResource,
432
                                             port: sctpPort
577
                                             port: sctpPort
433
                                         }
578
                                         }
446
                                 },
591
                                 },
447
                                 function (error) {
592
                                 function (error) {
448
                                     console.error(
593
                                     console.error(
449
-                                        "ERROR setLocalDescription succeded",
594
+                                        "ERROR sending colibri message",
450
                                         error, elem);
595
                                         error, elem);
451
                                 }
596
                                 }
452
                             );
597
                             );
492
         var localSDP = new SDP(this.peerconnection.localDescription.sdp);
637
         var localSDP = new SDP(this.peerconnection.localDescription.sdp);
493
         // throw away stuff we don't want
638
         // throw away stuff we don't want
494
         // not needed with static offer
639
         // not needed with static offer
495
-        sdp.removeSessionLines('a=group:');
640
+        if (!config.useBundle) {
641
+            sdp.removeSessionLines('a=group:');
642
+        }
496
         sdp.removeSessionLines('a=msid-semantic:'); // FIXME: not mapped over jingle anyway...
643
         sdp.removeSessionLines('a=msid-semantic:'); // FIXME: not mapped over jingle anyway...
497
         for (var i = 0; i < sdp.media.length; i++) {
644
         for (var i = 0; i < sdp.media.length; i++) {
498
-            sdp.removeMediaLines(i, 'a=rtcp-mux');
645
+            if (!config.useRtcpMux){
646
+                sdp.removeMediaLines(i, 'a=rtcp-mux');
647
+            }
499
             sdp.removeMediaLines(i, 'a=ssrc:');
648
             sdp.removeMediaLines(i, 'a=ssrc:');
500
             sdp.removeMediaLines(i, 'a=crypto:');
649
             sdp.removeMediaLines(i, 'a=crypto:');
501
             sdp.removeMediaLines(i, 'a=candidate:');
650
             sdp.removeMediaLines(i, 'a=candidate:');
508
             if (1) { //i > 0) { // not for audio FIXME: does not work as intended
657
             if (1) { //i > 0) { // not for audio FIXME: does not work as intended
509
                 // re-add all remote a=ssrcs
658
                 // re-add all remote a=ssrcs
510
                 for (var jid in this.remotessrc) {
659
                 for (var jid in this.remotessrc) {
511
-                    if (jid == peer) continue;
660
+                    if (jid == peer || !this.remotessrc[jid][i])
661
+                        continue;
512
                     sdp.media[i] += this.remotessrc[jid][i];
662
                     sdp.media[i] += this.remotessrc[jid][i];
513
                 }
663
                 }
514
                 // and local a=ssrc lines
664
                 // and local a=ssrc lines
527
         console.log('channel id', chan.attr('id'));
677
         console.log('channel id', chan.attr('id'));
528
 
678
 
529
         tmp = chan.find('>source[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]');
679
         tmp = chan.find('>source[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]');
530
-        if (tmp.length) {
531
-            sdp.media[j] += 'a=ssrc:' + tmp.attr('ssrc') + ' ' + 'cname:mixed' + '\r\n';
532
-            sdp.media[j] += 'a=ssrc:' + tmp.attr('ssrc') + ' ' + 'label:mixedlabela0' + '\r\n';
533
-            sdp.media[j] += 'a=ssrc:' + tmp.attr('ssrc') + ' ' + 'msid:mixedmslabel mixedlabela0' + '\r\n';
534
-            sdp.media[j] += 'a=ssrc:' + tmp.attr('ssrc') + ' ' + 'mslabel:mixedmslabel' + '\r\n';
535
-        }
536
-        // No SSRCs for 'data', comes when j == 2
537
-        else if (j < 2)
538
-        {
680
+
681
+        var name = sdp.media[j].split(" ")[0].substr(2); // 'm=audio ...'
682
+        if (name === 'audio' || name === 'video') {
539
             // make chrome happy... '3735928559' == 0xDEADBEEF
683
             // make chrome happy... '3735928559' == 0xDEADBEEF
540
-            sdp.media[j] += 'a=ssrc:' + '3735928559' + ' ' + 'cname:mixed' + '\r\n';
541
-            sdp.media[j] += 'a=ssrc:' + '3735928559' + ' ' + 'label:mixedlabelv0' + '\r\n';
542
-            sdp.media[j] += 'a=ssrc:' + '3735928559' + ' ' + 'msid:mixedmslabel mixedlabelv0' + '\r\n';
543
-            sdp.media[j] += 'a=ssrc:' + '3735928559' + ' ' + 'mslabel:mixedmslabel' + '\r\n';
684
+            var ssrc = tmp.length ? tmp.attr('ssrc') : '3735928559';
685
+
686
+            sdp.media[j] += 'a=ssrc:' + ssrc + ' cname:mixed\r\n';
687
+            sdp.media[j] += 'a=ssrc:' + ssrc + ' label:mixedlabel' + name + '0\r\n';
688
+            sdp.media[j] += 'a=ssrc:' + ssrc + ' msid:mixedmslabel mixedlabel' + name + '0\r\n';
689
+            sdp.media[j] += 'a=ssrc:' + ssrc + ' mslabel:mixedmslabel\r\n';
690
+        }
691
+
692
+        // In the case of bundle, we add each candidate to all m= lines/jingle contents,
693
+        // just as chrome does
694
+        if (config.useBundle){
695
+            tmp = this.bundledTransports[chan.attr('channel-bundle-id')];
696
+        } else {
697
+            tmp = chan.find('>transport[xmlns="urn:xmpp:jingle:transports:ice-udp:1"]');
544
         }
698
         }
545
 
699
 
546
-        tmp = chan.find('>transport[xmlns="urn:xmpp:jingle:transports:ice-udp:1"]');
547
         if (tmp.length) {
700
         if (tmp.length) {
548
             if (tmp.attr('ufrag'))
701
             if (tmp.attr('ufrag'))
549
                 sdp.media[j] += 'a=ice-ufrag:' + tmp.attr('ufrag') + '\r\n';
702
                 sdp.media[j] += 'a=ice-ufrag:' + tmp.attr('ufrag') + '\r\n';
615
         {
768
         {
616
             console.error('local description not ready yet, postponing', peer);
769
             console.error('local description not ready yet, postponing', peer);
617
         }
770
         }
618
-        window.setTimeout(function () {
619
-            self.addNewParticipant(peer);
620
-        }, 250);
771
+        window.setTimeout(function () { self.addNewParticipant(peer); }, 250);
621
         return;
772
         return;
622
     }
773
     }
623
     var index = this.channels.length;
774
     var index = this.channels.length;
625
     this.peers.push(peer);
776
     this.peers.push(peer);
626
 
777
 
627
     var elem = $iq({to: this.bridgejid, type: 'get'});
778
     var elem = $iq({to: this.bridgejid, type: 'get'});
628
-    elem.c('conference', {xmlns: 'http://jitsi.org/protocol/colibri', id: this.confid});
779
+    elem.c(
780
+        'conference',
781
+        { xmlns: 'http://jitsi.org/protocol/colibri', id: this.confid });
629
     var localSDP = new SDP(this.peerconnection.localDescription.sdp);
782
     var localSDP = new SDP(this.peerconnection.localDescription.sdp);
630
     localSDP.media.forEach(function (media, channel) {
783
     localSDP.media.forEach(function (media, channel) {
631
         var name = SDPUtil.parse_mid(SDPUtil.find_line(media, 'a=mid:'));
784
         var name = SDPUtil.parse_mid(SDPUtil.find_line(media, 'a=mid:'));
632
-        elem.c('content', {name: name});
633
-        if (name !== 'data')
634
-        {
635
-            elem.c('channel', {
785
+        var elemName;
786
+        var endpointId = peer.substr(1 + peer.lastIndexOf('/'));
787
+        var elemAttrs
788
+            = {
636
                 initiator: 'true',
789
                 initiator: 'true',
637
                 expire: self.channelExpire,
790
                 expire: self.channelExpire,
638
-                endpoint: peer.substr(1 + peer.lastIndexOf('/'))
639
-            });
791
+                endpoint: endpointId
792
+            };
793
+        if (config.useBundle) {
794
+            elemAttrs['channel-bundle-id'] = endpointId;
795
+        }
796
+
797
+
798
+        if ('data' == name)
799
+        {
800
+            elemName = 'sctpconnection';
801
+            elemAttrs['port'] = 5000;
640
         }
802
         }
641
         else
803
         else
642
         {
804
         {
643
-            elem.c('sctpconnection', {
644
-                endpoint: peer.substr(1 + peer.lastIndexOf('/')),
645
-                initiator: 'true',
646
-                expire: self.channelExpire,
647
-                port: 5000
648
-            });
805
+            elemName = 'channel';
806
+            if (('video' === name) && (self.channelLastN >= 0))
807
+                elemAttrs['last-n'] = self.channelLastN;
649
         }
808
         }
809
+
810
+        elem.c('content', { name: name });
811
+        elem.c(elemName, elemAttrs);
650
         elem.up(); // end of channel/sctpconnection
812
         elem.up(); // end of channel/sctpconnection
651
         elem.up(); // end of content
813
         elem.up(); // end of content
652
     });
814
     });
654
     this.connection.sendIQ(elem,
816
     this.connection.sendIQ(elem,
655
         function (result) {
817
         function (result) {
656
             var contents = $(result).find('>conference>content').get();
818
             var contents = $(result).find('>conference>content').get();
657
-            for (var i = 0; i < contents.length; i++) {
819
+            var i;
820
+            for (i = 0; i < contents.length; i++) {
658
                 var channelXml = $(contents[i]).find('>channel');
821
                 var channelXml = $(contents[i]).find('>channel');
659
                 if (channelXml.length)
822
                 if (channelXml.length)
660
                 {
823
                 {
666
                 }
829
                 }
667
                 self.channels[index][i] = tmp[0];
830
                 self.channels[index][i] = tmp[0];
668
             }
831
             }
832
+            var channelBundles = $(result).find('>conference>channel-bundle');
833
+            for (i = 0; i < channelBundles.length; i++)
834
+            {
835
+                var endpointId = $(channelBundles[i]).attr('id');
836
+                self.bundledTransports[endpointId] = $(channelBundles[i]).find('>transport[xmlns="urn:xmpp:jingle:transports:ice-udp:1"]');
837
+            }
669
             self.initiate(peer, true);
838
             self.initiate(peer, true);
670
         },
839
         },
671
         function (error) {
840
         function (error) {
682
     change.c('conference', {xmlns: 'http://jitsi.org/protocol/colibri', id: this.confid});
851
     change.c('conference', {xmlns: 'http://jitsi.org/protocol/colibri', id: this.confid});
683
     for (channel = 0; channel < this.channels[participant].length; channel++)
852
     for (channel = 0; channel < this.channels[participant].length; channel++)
684
     {
853
     {
854
+        if (!remoteSDP.media[channel])
855
+            continue;
856
+
685
         var name = SDPUtil.parse_mid(SDPUtil.find_line(remoteSDP.media[channel], 'a=mid:'));
857
         var name = SDPUtil.parse_mid(SDPUtil.find_line(remoteSDP.media[channel], 'a=mid:'));
686
         change.c('content', {name: name});
858
         change.c('content', {name: name});
687
         if (name !== 'data')
859
         if (name !== 'data')
714
         {
886
         {
715
             var sctpmap = SDPUtil.find_line(remoteSDP.media[channel], 'a=sctpmap:');
887
             var sctpmap = SDPUtil.find_line(remoteSDP.media[channel], 'a=sctpmap:');
716
             change.c('sctpconnection', {
888
             change.c('sctpconnection', {
889
+                id: $(this.channels[participant][channel]).attr('id'),
717
                 endpoint: $(this.channels[participant][channel]).attr('endpoint'),
890
                 endpoint: $(this.channels[participant][channel]).attr('endpoint'),
718
                 expire: self.channelExpire,
891
                 expire: self.channelExpire,
719
                 port: SDPUtil.parse_sctpmap(sctpmap)[0]
892
                 port: SDPUtil.parse_sctpmap(sctpmap)[0]
770
     if (!this.peerconnection.localDescription)
943
     if (!this.peerconnection.localDescription)
771
     {
944
     {
772
         console.warn("addSource - localDescription not ready yet")
945
         console.warn("addSource - localDescription not ready yet")
773
-        setTimeout(function()
774
-            {
775
-                self.addSource(elem, fromJid);
776
-            },
777
-            200
778
-        );
946
+        setTimeout(function() { self.addSource(elem, fromJid); }, 200);
779
         return;
947
         return;
780
     }
948
     }
781
 
949
 
816
     if (!self.peerconnection.localDescription)
984
     if (!self.peerconnection.localDescription)
817
     {
985
     {
818
         console.warn("removeSource - localDescription not ready yet");
986
         console.warn("removeSource - localDescription not ready yet");
819
-        setTimeout(function()
820
-            {
821
-                self.removeSource(elem, fromJid);
822
-            },
823
-            200
824
-        );
987
+        setTimeout(function() { self.removeSource(elem, fromJid); }, 200);
825
         return;
988
         return;
826
     }
989
     }
827
 
990
 
862
     this.remotessrc[session.peerjid] = [];
1025
     this.remotessrc[session.peerjid] = [];
863
     for (channel = 0; channel < this.channels[participant].length; channel++) {
1026
     for (channel = 0; channel < this.channels[participant].length; channel++) {
864
         //if (channel == 0) continue; FIXME: does not work as intended
1027
         //if (channel == 0) continue; FIXME: does not work as intended
1028
+        if (!remoteSDP.media[channel])
1029
+            continue;
1030
+
865
         if (SDPUtil.find_lines(remoteSDP.media[channel], 'a=ssrc:').length)
1031
         if (SDPUtil.find_lines(remoteSDP.media[channel], 'a=ssrc:').length)
866
         {
1032
         {
867
             this.remotessrc[session.peerjid][channel] =
1033
             this.remotessrc[session.peerjid][channel] =
873
     // ACT 4: add new a=ssrc lines to local remotedescription
1039
     // ACT 4: add new a=ssrc lines to local remotedescription
874
     for (channel = 0; channel < this.channels[participant].length; channel++) {
1040
     for (channel = 0; channel < this.channels[participant].length; channel++) {
875
         //if (channel == 0) continue; FIXME: does not work as intended
1041
         //if (channel == 0) continue; FIXME: does not work as intended
1042
+        if (!remoteSDP.media[channel])
1043
+            continue;
1044
+
876
         if (SDPUtil.find_lines(remoteSDP.media[channel], 'a=ssrc:').length) {
1045
         if (SDPUtil.find_lines(remoteSDP.media[channel], 'a=ssrc:').length) {
877
             this.peerconnection.enqueueAddSsrc(
1046
             this.peerconnection.enqueueAddSsrc(
878
                 channel,
1047
                 channel,
893
     $(elem).each(function () {
1062
     $(elem).each(function () {
894
         var name = $(this).attr('name');
1063
         var name = $(this).attr('name');
895
 
1064
 
1065
+        // If we are using bundle, audio/video/data channel will have the same candidates, so only send them for
1066
+        // the audio channel.
1067
+        if (config.useBundle && name !== 'audio') {
1068
+            return;
1069
+        }
1070
+
896
         var channel = name == 'audio' ? 0 : 1; // FIXME: search mlineindex in localdesc
1071
         var channel = name == 'audio' ? 0 : 1; // FIXME: search mlineindex in localdesc
897
         if (name != 'audio' && name != 'video')
1072
         if (name != 'audio' && name != 'video')
898
             channel = 2; // name == 'data'
1073
             channel = 2; // name == 'data'
909
         else
1084
         else
910
         {
1085
         {
911
             change.c('sctpconnection', {
1086
             change.c('sctpconnection', {
1087
+                id: $(self.channels[participant][channel]).attr('id'),
912
                 endpoint: $(self.channels[participant][channel]).attr('endpoint'),
1088
                 endpoint: $(self.channels[participant][channel]).attr('endpoint'),
913
                 expire: self.channelExpire
1089
                 expire: self.channelExpire
914
             });
1090
             });
919
                 pwd: $(this).attr('pwd'),
1095
                 pwd: $(this).attr('pwd'),
920
                 xmlns: $(this).attr('xmlns')
1096
                 xmlns: $(this).attr('xmlns')
921
             });
1097
             });
1098
+            if (config.useRtcpMux
1099
+                  && 'channel' === change.node.parentNode.nodeName) {
1100
+                change.c('rtcp-mux').up();
1101
+            }
922
 
1102
 
923
             $(this).find('>candidate').each(function () {
1103
             $(this).find('>candidate').each(function () {
924
                 /* not yet
1104
                 /* not yet
956
     }
1136
     }
957
     if (this.drip_container.length === 0) {
1137
     if (this.drip_container.length === 0) {
958
         // start 20ms callout
1138
         // start 20ms callout
959
-        window.setTimeout(function () {
960
-            if (self.drip_container.length === 0) return;
961
-            self.sendIceCandidates(self.drip_container);
962
-            self.drip_container = [];
963
-        }, 20);
1139
+        window.setTimeout(
1140
+            function () {
1141
+                if (self.drip_container.length === 0) return;
1142
+                self.sendIceCandidates(self.drip_container);
1143
+                self.drip_container = [];
1144
+            },
1145
+            20);
964
     }
1146
     }
965
     this.drip_container.push(candidate);
1147
     this.drip_container.push(candidate);
966
 };
1148
 };
990
             else
1172
             else
991
             {
1173
             {
992
                 mycands.c('sctpconnection', {
1174
                 mycands.c('sctpconnection', {
1175
+                    id: $(this.mychannel[cands[0].sdpMLineIndex]).attr('id'),
993
                     endpoint: $(this.mychannel[cands[0].sdpMLineIndex]).attr('endpoint'),
1176
                     endpoint: $(this.mychannel[cands[0].sdpMLineIndex]).attr('endpoint'),
994
                     port: $(this.mychannel[cands[0].sdpMLineIndex]).attr('port'),
1177
                     port: $(this.mychannel[cands[0].sdpMLineIndex]).attr('port'),
995
                     expire: self.channelExpire
1178
                     expire: self.channelExpire
996
                 });
1179
                 });
997
             }
1180
             }
998
             mycands.c('transport', {xmlns: 'urn:xmpp:jingle:transports:ice-udp:1'});
1181
             mycands.c('transport', {xmlns: 'urn:xmpp:jingle:transports:ice-udp:1'});
1182
+            if (config.useRtcpMux && name !== 'data') {
1183
+                mycands.c('rtcp-mux').up();
1184
+            }
999
             for (var i = 0; i < cands.length; i++) {
1185
             for (var i = 0; i < cands.length; i++) {
1000
                 mycands.c('candidate', SDPUtil.candidateToJingle(cands[i].candidate)).up();
1186
                 mycands.c('candidate', SDPUtil.candidateToJingle(cands[i].candidate)).up();
1001
             }
1187
             }
1046
         else
1232
         else
1047
         {
1233
         {
1048
             change.c('sctpconnection', {
1234
             change.c('sctpconnection', {
1235
+                id: $(this.channels[participant][channel]).attr('id'),
1049
                 endpoint: $(this.channels[participant][channel]).attr('endpoint'),
1236
                 endpoint: $(this.channels[participant][channel]).attr('endpoint'),
1050
                 expire: '0'
1237
                 expire: '0'
1051
             });
1238
             });
1120
         window.clearInterval(this.statsinterval);
1307
         window.clearInterval(this.statsinterval);
1121
         this.statsinterval = null;
1308
         this.statsinterval = null;
1122
     }
1309
     }
1123
-};
1310
+};
1311
+
1312
+ColibriFocus.prototype.setRTCPTerminationStrategy = function (strategyFQN) {
1313
+    var self = this;
1314
+    var strategyIQ = $iq({to: this.bridgejid, type: 'set'});
1315
+    strategyIQ.c('conference', {
1316
+	    xmlns: 'http://jitsi.org/protocol/colibri',
1317
+	    id: this.confid,
1318
+    });
1319
+
1320
+    strategyIQ.c('rtcp-termination-strategy', {name: strategyFQN });
1321
+
1322
+    strategyIQ.c('content', {name: "video"});
1323
+    strategyIQ.up(); // end of content
1324
+
1325
+    console.log('setting RTCP termination strategy', strategyFQN);
1326
+    this.connection.sendIQ(strategyIQ,
1327
+        function (res) {
1328
+            console.log('got result');
1329
+        },
1330
+        function (err) {
1331
+            console.error('got error', err);
1332
+        }
1333
+    );
1334
+};
1335
+
1336
+/**
1337
+ * Sets the default value of the channel last-n attribute in this conference and
1338
+ * updates/patches the existing channels.
1339
+ */
1340
+ColibriFocus.prototype.setChannelLastN = function (channelLastN) {
1341
+    if (('number' === typeof(channelLastN))
1342
+            && (this.channelLastN !== channelLastN))
1343
+    {
1344
+        this.channelLastN = channelLastN;
1345
+
1346
+        // Update/patch the existing channels.
1347
+        var patch = $iq({ to: this.bridgejid, type: 'set' });
1348
+
1349
+        patch.c(
1350
+            'conference',
1351
+            { xmlns: 'http://jitsi.org/protocol/colibri', id: this.confid });
1352
+        patch.c('content', { name: 'video' });
1353
+        patch.c(
1354
+            'channel',
1355
+            {
1356
+                id: $(this.mychannel[1 /* video */]).attr('id'),
1357
+                'last-n': this.channelLastN
1358
+            });
1359
+        patch.up(); // end of channel
1360
+        for (var p = 0; p < this.channels.length; p++)
1361
+        {
1362
+            patch.c(
1363
+                'channel',
1364
+                {
1365
+                    id: $(this.channels[p][1 /* video */]).attr('id'),
1366
+                    'last-n': this.channelLastN
1367
+                });
1368
+            patch.up(); // end of channel
1369
+        }
1370
+        this.connection.sendIQ(
1371
+            patch,
1372
+            function (res) {
1373
+                console.info('Set channel last-n succeeded:', res);
1374
+            },
1375
+            function (err) {
1376
+                console.error('Set channel last-n failed:', err);
1377
+            });
1378
+    }
1379
+};
1380
+

+ 15008
- 0
libs/jquery-ui.js
Filskillnaden har hållits tillbaka eftersom den är för stor
Visa fil


+ 6
- 0
libs/jquery.min.js
Filskillnaden har hållits tillbaka eftersom den är för stor
Visa fil


+ 94
- 0
libs/rayo.js Visa fil

1
+/* jshint -W117 */
2
+Strophe.addConnectionPlugin('rayo',
3
+    {
4
+        RAYO_XMLNS: 'urn:xmpp:rayo:1',
5
+        connection: null,
6
+        init: function (conn)
7
+        {
8
+            this.connection = conn;
9
+            if (this.connection.disco)
10
+            {
11
+                this.connection.disco.addFeature('urn:xmpp:rayo:client:1');
12
+            }
13
+
14
+            this.connection.addHandler(
15
+                this.onRayo.bind(this), this.RAYO_XMLNS, 'iq', 'set', null, null);
16
+        },
17
+        onRayo: function (iq)
18
+        {
19
+            console.info("Rayo IQ", iq);
20
+        },
21
+        dial: function (to, from, roomName)
22
+        {
23
+            var self = this;
24
+            var req = $iq(
25
+                {
26
+                    type: 'set',
27
+                    to: config.hosts.call_control
28
+                }
29
+            );
30
+            req.c('dial',
31
+                {
32
+                    xmlns: this.RAYO_XMLNS,
33
+                    to: to,
34
+                    from: from
35
+                });
36
+            req.c('header',
37
+                {
38
+                    name: 'JvbRoomName',
39
+                    value: roomName
40
+                });
41
+
42
+            this.connection.sendIQ(
43
+                req,
44
+                function (result)
45
+                {
46
+                    console.info('Dial result ', result);
47
+
48
+                    var resource = $(result).find('ref').attr('uri');
49
+                    this.call_resource = resource.substr('xmpp:'.length);
50
+                    console.info(
51
+                        "Received call resource: " + this.call_resource);
52
+                },
53
+                function (error)
54
+                {
55
+                    console.info('Dial error ', error);
56
+                }
57
+            );
58
+        },
59
+        hang_up: function ()
60
+        {
61
+            if (!this.call_resource)
62
+            {
63
+                console.warn("No call in progress");
64
+                return;
65
+            }
66
+
67
+            var self = this;
68
+            var req = $iq(
69
+                {
70
+                    type: 'set',
71
+                    to: this.call_resource
72
+                }
73
+            );
74
+            req.c('hangup',
75
+                {
76
+                    xmlns: this.RAYO_XMLNS
77
+                });
78
+
79
+            this.connection.sendIQ(
80
+                req,
81
+                function (result)
82
+                {
83
+                    console.info('Hangup result ', result);
84
+                    self.call_resource = null;
85
+                },
86
+                function (error)
87
+                {
88
+                    console.info('Hangup error ', error);
89
+                    self.call_resource = null;
90
+                }
91
+            );
92
+        }
93
+    }
94
+);

+ 39
- 12
libs/strophe/strophe.jingle.adapter.js Visa fil

510
     var constraints = {audio: false, video: false};
510
     var constraints = {audio: false, video: false};
511
 
511
 
512
     if (um.indexOf('video') >= 0) {
512
     if (um.indexOf('video') >= 0) {
513
-        constraints.video = {mandatory: {}};// same behaviour as true
513
+        constraints.video = { mandatory: {}, optional: [] };// same behaviour as true
514
     }
514
     }
515
     if (um.indexOf('audio') >= 0) {
515
     if (um.indexOf('audio') >= 0) {
516
-        constraints.audio = {};// same behaviour as true
516
+        constraints.audio = { mandatory: {}, optional: []};// same behaviour as true
517
     }
517
     }
518
     if (um.indexOf('screen') >= 0) {
518
     if (um.indexOf('screen') >= 0) {
519
         constraints.video = {
519
         constraints.video = {
520
             mandatory: {
520
             mandatory: {
521
                 chromeMediaSource: "screen",
521
                 chromeMediaSource: "screen",
522
+                googLeakyBucket: true,
522
                 maxWidth: window.screen.width,
523
                 maxWidth: window.screen.width,
523
                 maxHeight: window.screen.height,
524
                 maxHeight: window.screen.height,
524
                 maxFrameRate: 3
525
                 maxFrameRate: 3
525
-            }
526
+            },
527
+            optional: []
526
         };
528
         };
527
     }
529
     }
528
     if (um.indexOf('desktop') >= 0) {
530
     if (um.indexOf('desktop') >= 0) {
530
             mandatory: {
532
             mandatory: {
531
                 chromeMediaSource: "desktop",
533
                 chromeMediaSource: "desktop",
532
                 chromeMediaSourceId: desktopStream,
534
                 chromeMediaSourceId: desktopStream,
535
+                googLeakyBucket: true,
533
                 maxWidth: window.screen.width,
536
                 maxWidth: window.screen.width,
534
                 maxHeight: window.screen.height,
537
                 maxHeight: window.screen.height,
535
                 maxFrameRate: 3
538
                 maxFrameRate: 3
536
-            }
539
+            },
540
+            optional: []
541
+        }
542
+    }
543
+
544
+    if (constraints.audio) {
545
+        // if it is good enough for hangouts...
546
+        constraints.audio.optional.push(
547
+            {googEchoCancellation: true},
548
+            {googAutoGainControl: true},
549
+            {googNoiseSupression: true},
550
+            {googHighpassFilter: true},
551
+            {googNoisesuppression2: true},
552
+            {googEchoCancellation2: true},
553
+            {googAutoGainControl2: true}
554
+        );
555
+    }
556
+    if (constraints.video) {
557
+        constraints.video.optional.push(
558
+            {googNoiseReduction: true}
559
+        );
560
+        if (um.indexOf('video') >= 0) {
561
+            constraints.video.optional.push(
562
+                {googLeakyBucket: true}
563
+            );
537
         }
564
         }
538
     }
565
     }
539
 
566
 
541
     var isAndroid = navigator.userAgent.indexOf('Android') != -1;
568
     var isAndroid = navigator.userAgent.indexOf('Android') != -1;
542
 
569
 
543
     if (resolution && !constraints.video || isAndroid) {
570
     if (resolution && !constraints.video || isAndroid) {
544
-        constraints.video = {mandatory: {}};// same behaviour as true
571
+        constraints.video = { mandatory: {}, optional: [] };// same behaviour as true
545
     }
572
     }
546
     // see https://code.google.com/p/chromium/issues/detail?id=143631#c9 for list of supported resolutions
573
     // see https://code.google.com/p/chromium/issues/detail?id=143631#c9 for list of supported resolutions
547
     switch (resolution) {
574
     switch (resolution) {
550
         case 'fullhd':
577
         case 'fullhd':
551
             constraints.video.mandatory.minWidth = 1920;
578
             constraints.video.mandatory.minWidth = 1920;
552
             constraints.video.mandatory.minHeight = 1080;
579
             constraints.video.mandatory.minHeight = 1080;
553
-            constraints.video.mandatory.minAspectRatio = 1.77;
580
+            constraints.video.optional.push({ minAspectRatio: 1.77 });
554
             break;
581
             break;
555
         case '720':
582
         case '720':
556
         case 'hd':
583
         case 'hd':
557
             constraints.video.mandatory.minWidth = 1280;
584
             constraints.video.mandatory.minWidth = 1280;
558
             constraints.video.mandatory.minHeight = 720;
585
             constraints.video.mandatory.minHeight = 720;
559
-            constraints.video.mandatory.minAspectRatio = 1.77;
586
+            constraints.video.optional.push({ minAspectRatio: 1.77 });
560
             break;
587
             break;
561
         case '360':
588
         case '360':
562
             constraints.video.mandatory.minWidth = 640;
589
             constraints.video.mandatory.minWidth = 640;
563
             constraints.video.mandatory.minHeight = 360;
590
             constraints.video.mandatory.minHeight = 360;
564
-            constraints.video.mandatory.minAspectRatio = 1.77;
591
+            constraints.video.optional.push({ minAspectRatio: 1.77 });
565
             break;
592
             break;
566
         case '180':
593
         case '180':
567
             constraints.video.mandatory.minWidth = 320;
594
             constraints.video.mandatory.minWidth = 320;
568
             constraints.video.mandatory.minHeight = 180;
595
             constraints.video.mandatory.minHeight = 180;
569
-            constraints.video.mandatory.minAspectRatio = 1.77;
596
+            constraints.video.optional.push({ minAspectRatio: 1.77 });
570
             break;
597
             break;
571
         // 4:3
598
         // 4:3
572
         case '960':
599
         case '960':
592
     }
619
     }
593
 
620
 
594
     if (bandwidth) { // doesn't work currently, see webrtc issue 1846
621
     if (bandwidth) { // doesn't work currently, see webrtc issue 1846
595
-        if (!constraints.video) constraints.video = {mandatory: {}};//same behaviour as true
596
-        constraints.video.optional = [{bandwidth: bandwidth}];
622
+        if (!constraints.video) constraints.video = {mandatory: {}, optional: []};//same behaviour as true
623
+        constraints.video.optional.push({bandwidth: bandwidth});
597
     }
624
     }
598
     if (fps) { // for some cameras it might be necessary to request 30fps
625
     if (fps) { // for some cameras it might be necessary to request 30fps
599
         // so they choose 30fps mjpg over 10fps yuy2
626
         // so they choose 30fps mjpg over 10fps yuy2
600
-        if (!constraints.video) constraints.video = {mandatory: {}};// same behaviour as tru;
627
+        if (!constraints.video) constraints.video = {mandatory: {}, optional: []};// same behaviour as true;
601
         constraints.video.mandatory.minFrameRate = fps;
628
         constraints.video.mandatory.minFrameRate = fps;
602
     }
629
     }
603
 
630
 

+ 15
- 1
libs/strophe/strophe.jingle.sdp.util.js Visa fil

174
                 case 'generation':
174
                 case 'generation':
175
                     candidate.generation = elems[i + 1];
175
                     candidate.generation = elems[i + 1];
176
                     break;
176
                     break;
177
+                case 'tcptype':
178
+                    candidate.tcptype = elems[i + 1];
179
+                    break;
177
                 default: // TODO
180
                 default: // TODO
178
                     console.log('parse_icecandidate not translating "' + elems[i] + '" = "' + elems[i + 1] + '"');
181
                     console.log('parse_icecandidate not translating "' + elems[i] + '" = "' + elems[i + 1] + '"');
179
             }
182
             }
201
                 }
204
                 }
202
                 break;
205
                 break;
203
         }
206
         }
207
+        if (cand.hasOwnAttribute('tcptype')) {
208
+            line += 'tcptype';
209
+            line += ' ';
210
+            line += cand.tcptype;
211
+            line += ' ';
212
+        }
204
         line += 'generation';
213
         line += 'generation';
205
         line += ' ';
214
         line += ' ';
206
         line += cand.hasOwnAttribute('generation') ? cand.generation : '0';
215
         line += cand.hasOwnAttribute('generation') ? cand.generation : '0';
283
     candidateToJingle: function (line) {
292
     candidateToJingle: function (line) {
284
         // a=candidate:2979166662 1 udp 2113937151 192.168.2.100 57698 typ host generation 0
293
         // a=candidate:2979166662 1 udp 2113937151 192.168.2.100 57698 typ host generation 0
285
         //      <candidate component=... foundation=... generation=... id=... ip=... network=... port=... priority=... protocol=... type=.../>
294
         //      <candidate component=... foundation=... generation=... id=... ip=... network=... port=... priority=... protocol=... type=.../>
286
-        if (line.substring(0, 12) != 'a=candidate:') {
295
+        if (line.indexOf('candidate:') === 0) {
296
+            line = 'a=' + line;
297
+        } else if (line.substring(0, 12) != 'a=candidate:') {
287
             console.log('parseCandidate called with a line that is not a candidate line');
298
             console.log('parseCandidate called with a line that is not a candidate line');
288
             console.log(line);
299
             console.log(line);
289
             return null;
300
             return null;
319
                 case 'generation':
330
                 case 'generation':
320
                     candidate.generation = elems[i + 1];
331
                     candidate.generation = elems[i + 1];
321
                     break;
332
                     break;
333
+                case 'tcptype':
334
+                    candidate.tcptype = elems[i + 1];
335
+                    break;
322
                 default: // TODO
336
                 default: // TODO
323
                     console.log('not translating "' + elems[i] + '" = "' + elems[i + 1] + '"');
337
                     console.log('not translating "' + elems[i] + '" = "' + elems[i + 1] + '"');
324
             }
338
             }

+ 4
- 1
libs/strophe/strophe.jingle.session.js Visa fil

287
             if (SDPUtil.find_line(this.localSDP.media[mid], 'a=fingerprint:', this.localSDP.session)) {
287
             if (SDPUtil.find_line(this.localSDP.media[mid], 'a=fingerprint:', this.localSDP.session)) {
288
                 var tmp = SDPUtil.parse_fingerprint(SDPUtil.find_line(this.localSDP.media[mid], 'a=fingerprint:', this.localSDP.session));
288
                 var tmp = SDPUtil.parse_fingerprint(SDPUtil.find_line(this.localSDP.media[mid], 'a=fingerprint:', this.localSDP.session));
289
                 tmp.required = true;
289
                 tmp.required = true;
290
-                cand.c('fingerprint').t(tmp.fingerprint);
290
+                cand.c(
291
+                    'fingerprint',
292
+                    {xmlns: 'urn:xmpp:jingle:apps:dtls:0'})
293
+                    .t(tmp.fingerprint);
291
                 delete tmp.fingerprint;
294
                 delete tmp.fingerprint;
292
                 cand.attrs(tmp);
295
                 cand.attrs(tmp);
293
                 cand.up();
296
                 cand.up();

+ 131
- 0
local_stats.js Visa fil

1
+/**
2
+ * Provides statistics for the local stream.
3
+ */
4
+var LocalStatsCollector = (function() {
5
+    /**
6
+     * Size of the webaudio analizer buffer.
7
+     * @type {number}
8
+     */
9
+    var WEBAUDIO_ANALIZER_FFT_SIZE = 2048;
10
+
11
+    /**
12
+     * Value of the webaudio analizer smoothing time parameter.
13
+     * @type {number}
14
+     */
15
+    var WEBAUDIO_ANALIZER_SMOOTING_TIME = 0.8;
16
+
17
+    /**
18
+     * <tt>LocalStatsCollector</tt> calculates statistics for the local stream.
19
+     *
20
+     * @param stream the local stream
21
+     * @param interval stats refresh interval given in ms.
22
+     * @param {function(LocalStatsCollector)} updateCallback the callback called on stats
23
+     *                                   update.
24
+     * @constructor
25
+     */
26
+    function LocalStatsCollectorProto(stream, interval, updateCallback) {
27
+        window.AudioContext = window.AudioContext || window.webkitAudioContext;
28
+        this.stream = stream;
29
+        this.intervalId = null;
30
+        this.intervalMilis = interval;
31
+        this.updateCallback = updateCallback;
32
+        this.audioLevel = 0;
33
+    }
34
+
35
+    /**
36
+     * Starts the collecting the statistics.
37
+     */
38
+    LocalStatsCollectorProto.prototype.start = function () {
39
+        if (!window.AudioContext)
40
+            return;
41
+
42
+        var context = new AudioContext();
43
+        var analyser = context.createAnalyser();
44
+        analyser.smoothingTimeConstant = WEBAUDIO_ANALIZER_SMOOTING_TIME;
45
+        analyser.fftSize = WEBAUDIO_ANALIZER_FFT_SIZE;
46
+
47
+
48
+        var source = context.createMediaStreamSource(this.stream);
49
+        source.connect(analyser);
50
+
51
+
52
+        var self = this;
53
+
54
+        this.intervalId = setInterval(
55
+            function () {
56
+                var array = new Uint8Array(analyser.frequencyBinCount);
57
+                analyser.getByteTimeDomainData(array);
58
+                var audioLevel = TimeDomainDataToAudioLevel(array);
59
+                if(audioLevel != self.audioLevel) {
60
+                    self.audioLevel = animateLevel(audioLevel, self.audioLevel);
61
+                    self.updateCallback(LocalStatsCollectorProto.LOCAL_JID, self.audioLevel);
62
+                }
63
+            },
64
+            this.intervalMilis
65
+        );
66
+
67
+    };
68
+
69
+    /**
70
+     * Stops collecting the statistics.
71
+     */
72
+    LocalStatsCollectorProto.prototype.stop = function () {
73
+        if (this.intervalId) {
74
+            clearInterval(this.intervalId);
75
+            this.intervalId = null;
76
+        }
77
+    };
78
+
79
+    /**
80
+     * Converts time domain data array to audio level.
81
+     * @param array the time domain data array.
82
+     * @returns {number} the audio level
83
+     */
84
+    var TimeDomainDataToAudioLevel = function (samples) {
85
+
86
+        var maxVolume = 0;
87
+
88
+        var length = samples.length;
89
+
90
+        for (var i = 0; i < length; i++) {
91
+            if (maxVolume < samples[i])
92
+                maxVolume = samples[i];
93
+        }
94
+
95
+        return parseFloat(((maxVolume - 127) / 128).toFixed(3));
96
+    };
97
+
98
+    /**
99
+     * Animates audio level change
100
+     * @param newLevel the new audio level
101
+     * @param lastLevel the last audio level
102
+     * @returns {Number} the audio level to be set
103
+     */
104
+    function animateLevel(newLevel, lastLevel)
105
+    {
106
+        var value = 0;
107
+        var diff = lastLevel - newLevel;
108
+        if(diff > 0.2)
109
+        {
110
+            value = lastLevel - 0.2;
111
+        }
112
+        else if(diff < -0.4)
113
+        {
114
+            value = lastLevel + 0.4;
115
+        }
116
+        else
117
+        {
118
+            value = newLevel;
119
+        }
120
+
121
+        return parseFloat(value.toFixed(3));
122
+    }
123
+
124
+    /**
125
+     * Indicates that this audio level is for local jid.
126
+     * @type {string}
127
+     */
128
+    LocalStatsCollectorProto.LOCAL_JID = 'local';
129
+
130
+    return LocalStatsCollectorProto;
131
+})();

+ 30
- 0
media_stream.js Visa fil

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
+})();

+ 25
- 0
muc.js Visa fil

131
         // Always trigger presence to update bindings
131
         // Always trigger presence to update bindings
132
         console.log('presence change from', from);
132
         console.log('presence change from', from);
133
         $(document).trigger('presence.muc', [from, member, pres]);
133
         $(document).trigger('presence.muc', [from, member, pres]);
134
+
135
+        // Trigger status message update
136
+        if (member.status) {
137
+            $(document).trigger('presence.status.muc', [from, member, pres]);
138
+        }
139
+
134
         return true;
140
         return true;
135
     },
141
     },
136
     onPresenceUnavailable: function (pres) {
142
     onPresenceUnavailable: function (pres) {
157
         var from = pres.getAttribute('from');
163
         var from = pres.getAttribute('from');
158
         if ($(pres).find('>error[type="auth"]>not-authorized[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]').length) {
164
         if ($(pres).find('>error[type="auth"]>not-authorized[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]').length) {
159
             $(document).trigger('passwordrequired.muc', [from]);
165
             $(document).trigger('passwordrequired.muc', [from]);
166
+        } else if ($(pres).find(
167
+                '>error[type="cancel"]>not-allowed[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]').length) {
168
+            var toDomain = Strophe.getDomainFromJid(pres.getAttribute('to'));
169
+            if(toDomain === config.hosts.anonymousdomain) {
170
+                // we are connected with anonymous domain and only non anonymous users can create rooms
171
+                // we must authorize the user
172
+                $(document).trigger('passwordrequired.main');
173
+            }
174
+            else
175
+                console.warn('onPresError ', pres);
176
+
160
         } else {
177
         } else {
161
             console.warn('onPresError ', pres);
178
             console.warn('onPresError ', pres);
162
         }
179
         }
356
     addVideoInfoToPresence: function(isMuted) {
373
     addVideoInfoToPresence: function(isMuted) {
357
         this.presMap['videons'] = 'http://jitsi.org/jitmeet/video';
374
         this.presMap['videons'] = 'http://jitsi.org/jitmeet/video';
358
         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;
359
     }
384
     }
360
 });
385
 });

+ 2
- 2
rtp_stats.js Visa fil

213
             // but it seems to vary between 0 and around 32k.
213
             // but it seems to vary between 0 and around 32k.
214
             audioLevel = audioLevel / 32767;
214
             audioLevel = audioLevel / 32767;
215
             jidStats.setSsrcAudioLevel(ssrc, audioLevel);
215
             jidStats.setSsrcAudioLevel(ssrc, audioLevel);
216
+            if(jid != connection.emuc.myroomjid)
217
+                this.updateCallback(jid, audioLevel);
216
         }
218
         }
217
 
219
 
218
         var key = 'packetsReceived';
220
         var key = 'packetsReceived';
281
         // bar indicator
283
         // bar indicator
282
         //console.info("Loss SMA3: " + outputAvg + " Q: " + quality);
284
         //console.info("Loss SMA3: " + outputAvg + " Q: " + quality);
283
     }
285
     }
284
-
285
-    self.updateCallback(self);
286
 };
286
 };
287
 
287
 

+ 111
- 24
toolbar.js Visa fil

5
     /**
5
     /**
6
      * Opens the lock room dialog.
6
      * Opens the lock room dialog.
7
      */
7
      */
8
-    my.openLockDialog = function() {
8
+    my.openLockDialog = function () {
9
         // Only the focus is able to set a shared key.
9
         // Only the focus is able to set a shared key.
10
         if (focus === null) {
10
         if (focus === null) {
11
             if (sharedKey)
11
             if (sharedKey)
12
                 $.prompt("This conversation is currently protected by"
12
                 $.prompt("This conversation is currently protected by"
13
                         + " a shared secret key.",
13
                         + " a shared secret key.",
14
                     {
14
                     {
15
-                        title: "Secrect key",
15
+                        title: "Secret key",
16
                         persistent: false
16
                         persistent: false
17
                     }
17
                     }
18
                 );
18
                 );
19
             else
19
             else
20
                 $.prompt("This conversation isn't currently protected by"
20
                 $.prompt("This conversation isn't currently protected by"
21
-                        + " a secret key. Only the owner of the conference" +
21
+                        + " a secret key. Only the owner of the conference"
22
                         + " could set a shared key.",
22
                         + " could set a shared key.",
23
                     {
23
                     {
24
-                        title: "Secrect key",
24
+                        title: "Secret key",
25
                         persistent: false
25
                         persistent: false
26
                     }
26
                     }
27
                 );
27
                 );
29
             if (sharedKey) {
29
             if (sharedKey) {
30
                 $.prompt("Are you sure you would like to remove your secret key?",
30
                 $.prompt("Are you sure you would like to remove your secret key?",
31
                     {
31
                     {
32
-                        title: "Remove secrect key",
32
+                        title: "Remove secret key",
33
                         persistent: false,
33
                         persistent: false,
34
                         buttons: { "Remove": true, "Cancel": false},
34
                         buttons: { "Remove": true, "Cancel": false},
35
                         defaultButton: 1,
35
                         defaultButton: 1,
42
                     }
42
                     }
43
                 );
43
                 );
44
             } else {
44
             } else {
45
-                $.prompt('<h2>Set a secrect key to lock your room</h2>' +
45
+                $.prompt('<h2>Set a secret key to lock your room</h2>' +
46
                          '<input id="lockKey" type="text" placeholder="your shared key" autofocus>',
46
                          '<input id="lockKey" type="text" placeholder="your shared key" autofocus>',
47
                     {
47
                     {
48
                         persistent: false,
48
                         persistent: false,
54
                         submit: function (e, v, m, f) {
54
                         submit: function (e, v, m, f) {
55
                             if (v) {
55
                             if (v) {
56
                                 var lockKey = document.getElementById('lockKey');
56
                                 var lockKey = document.getElementById('lockKey');
57
-
57
+    
58
                                 if (lockKey.value) {
58
                                 if (lockKey.value) {
59
                                     setSharedKey(Util.escapeHtml(lockKey.value));
59
                                     setSharedKey(Util.escapeHtml(lockKey.value));
60
                                     lockRoom(true);
60
                                     lockRoom(true);
70
     /**
70
     /**
71
      * Opens the invite link dialog.
71
      * Opens the invite link dialog.
72
      */
72
      */
73
-    my.openLinkDialog = function() {
73
+    my.openLinkDialog = function () {
74
+        var inviteLink;
75
+        if (roomUrl == null)
76
+            inviteLink = "Your conference is currently being created...";
77
+        else
78
+            inviteLink = encodeURI(roomUrl);
79
+
74
         $.prompt('<input id="inviteLinkRef" type="text" value="' +
80
         $.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();
81
+                inviteLink + '" onclick="this.select();" readonly>',
82
+                {
83
+                    title: "Share this link with everyone you want to invite",
84
+                    persistent: false,
85
+                    buttons: { "Invite": true, "Cancel": false},
86
+                    defaultButton: 1,
87
+                    loaded: function (event) {
88
+                        if (roomUrl)
89
+                            document.getElementById('inviteLinkRef').select();
90
+                        else
91
+                            document.getElementById('jqi_state0_buttonInvite')
92
+                                .disabled = true;
93
+                    },
94
+                    submit: function (e, v, m, f) {
95
+                        if (v) {
96
+                            if (roomUrl) {
97
+                                inviteParticipants();
98
+                            }
99
+                        }
100
+                    }
82
                 }
101
                 }
83
-            }
84
-        );
102
+            );
85
     };
103
     };
86
 
104
 
105
+    /**
106
+     * Invite participants to conference.
107
+     */
108
+    function inviteParticipants() {
109
+        if (roomUrl == null)
110
+            return;
111
+
112
+        var sharedKeyText = "";
113
+        if (sharedKey && sharedKey.length > 0)
114
+            sharedKeyText
115
+                = "This conference is password protected. Please use the "
116
+                    + "following pin when joining:%0D%0A%0D%0A"
117
+                    + sharedKey + "%0D%0A%0D%0A";
118
+
119
+        var conferenceName = roomUrl.substring(roomUrl.lastIndexOf('/') + 1);
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 +
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!";
132
+
133
+        if (window.localStorage.displayname)
134
+            body += "%0D%0A%0D%0A" + window.localStorage.displayname;
135
+
136
+        window.open("mailto:?subject=" + subject + "&body=" + body, '_blank');
137
+    }
138
+
87
     /**
139
     /**
88
      * Opens the settings dialog.
140
      * Opens the settings dialog.
89
      */
141
      */
90
-    my.openSettingsDialog = function() {
142
+    my.openSettingsDialog = function () {
91
         $.prompt('<h2>Configure your conference</h2>' +
143
         $.prompt('<h2>Configure your conference</h2>' +
92
             '<input type="checkbox" id="initMuted"> Participants join muted<br/>' +
144
             '<input type="checkbox" id="initMuted"> Participants join muted<br/>' +
93
             '<input type="checkbox" id="requireNicknames"> Require nicknames<br/><br/>' +
145
             '<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>',
146
+            'Set a secret key to lock your room: <input id="lockKey" type="text" placeholder="your shared key" autofocus>',
95
             {
147
             {
96
                 persistent: false,
148
                 persistent: false,
97
                 buttons: { "Save": true, "Cancel": false},
149
                 buttons: { "Save": true, "Cancel": false},
104
                         if ($('#initMuted').is(":checked")) {
156
                         if ($('#initMuted').is(":checked")) {
105
                             // it is checked
157
                             // it is checked
106
                         }
158
                         }
107
-
159
+    
108
                         if ($('#requireNicknames').is(":checked")) {
160
                         if ($('#requireNicknames').is(":checked")) {
109
                             // it is checked
161
                             // it is checked
110
                         }
162
                         }
111
                         /*
163
                         /*
112
                         var lockKey = document.getElementById('lockKey');
164
                         var lockKey = document.getElementById('lockKey');
113
-
165
+    
114
                         if (lockKey.value)
166
                         if (lockKey.value)
115
                         {
167
                         {
116
                             setSharedKey(lockKey.value);
168
                             setSharedKey(lockKey.value);
185
             if (!$('#header').is(':visible')) {
237
             if (!$('#header').is(':visible')) {
186
                 Toolbar.showToolbar();
238
                 Toolbar.showToolbar();
187
             }
239
             }
240
+
188
             // Then clear the time out, to dock the toolbar.
241
             // Then clear the time out, to dock the toolbar.
189
-            clearTimeout(toolbarTimeout);
190
-            toolbarTimeout = null;
242
+            if (toolbarTimeout) {
243
+                clearTimeout(toolbarTimeout);
244
+                toolbarTimeout = null;
245
+            }
191
         }
246
         }
192
         else {
247
         else {
193
             if (!$('#header').is(':visible')) {
248
             if (!$('#header').is(':visible')) {
230
         }
285
         }
231
     };
286
     };
232
 
287
 
288
+    // Shows or hides the 'recording' button.
289
+    my.showRecordingButton = function (show) {
290
+        if (!config.enableRecording) {
291
+            return;
292
+        }
293
+
294
+        if (show) {
295
+            $('#recording').css({display: "inline"});
296
+        }
297
+        else {
298
+            $('#recording').css({display: "none"});
299
+        }
300
+    };
301
+
302
+    // Toggle the state of the recording button
303
+    my.toggleRecordingButtonState = function() {
304
+        $('#recordButton').toggleClass('active');
305
+    };
306
+
307
+    // Shows or hides SIP calls button
308
+    my.showSipCallButton = function (show)
309
+    {
310
+        if (config.hosts.call_control && show)
311
+        {
312
+            $('#sipCallButton').css({display: "inline"});
313
+        }
314
+        else
315
+        {
316
+            $('#sipCallButton').css({display: "none"});
317
+        }
318
+    };
319
+
233
     return my;
320
     return my;
234
-}(Toolbar || {}));
321
+}(Toolbar || {}));

+ 3
- 1
util.js Visa fil

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
     };

+ 526
- 84
videolayout.js Visa fil

1
 var VideoLayout = (function (my) {
1
 var VideoLayout = (function (my) {
2
     var preMuted = false;
2
     var preMuted = false;
3
-    var currentActiveSpeaker = 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;
26
         var localVideoContainer = document.getElementById('localVideoWrapper');
28
         var localVideoContainer = document.getElementById('localVideoWrapper');
27
         localVideoContainer.appendChild(localVideo);
29
         localVideoContainer.appendChild(localVideo);
28
 
30
 
31
+        AudioLevels.updateAudioLevelCanvas();
32
+
29
         var localVideoSelector = $('#' + localVideo.id);
33
         var localVideoSelector = $('#' + localVideo.id);
30
-        // Add click handler
34
+        // Add click handler to both video and video wrapper elements in case
35
+        // there's no video.
31
         localVideoSelector.click(function () {
36
         localVideoSelector.click(function () {
32
             VideoLayout.handleVideoThumbClicked(localVideo.src);
37
             VideoLayout.handleVideoThumbClicked(localVideo.src);
33
         });
38
         });
39
+        $('#localVideoContainer').click(function () {
40
+            VideoLayout.handleVideoThumbClicked(localVideo.src);
41
+        });
42
+
34
         // Add hover handler
43
         // Add hover handler
35
         $('#localVideoContainer').hover(
44
         $('#localVideoContainer').hover(
36
             function() {
45
             function() {
37
                 VideoLayout.showDisplayName('localVideoContainer', true);
46
                 VideoLayout.showDisplayName('localVideoContainer', true);
38
             },
47
             },
39
             function() {
48
             function() {
40
-                if (focusedVideoSrc !== localVideo.src)
49
+                if (!VideoLayout.isLargeVideoVisible()
50
+                        || localVideo.src !== $('#largeVideo').attr('src'))
41
                     VideoLayout.showDisplayName('localVideoContainer', false);
51
                     VideoLayout.showDisplayName('localVideoContainer', false);
42
             }
52
             }
43
         );
53
         );
44
         // Add stream ended handler
54
         // Add stream ended handler
45
         stream.onended = function () {
55
         stream.onended = function () {
46
             localVideoContainer.removeChild(localVideo);
56
             localVideoContainer.removeChild(localVideo);
47
-            VideoLayout.checkChangeLargeVideo(localVideo.src);
57
+            VideoLayout.updateRemovedVideo(localVideo.src);
48
         };
58
         };
49
         // Flip video x axis if needed
59
         // Flip video x axis if needed
50
         flipXLocalVideo = flipX;
60
         flipXLocalVideo = flipX;
55
         RTC.attachMediaStream(localVideoSelector, stream);
65
         RTC.attachMediaStream(localVideoSelector, stream);
56
 
66
 
57
         localVideoSrc = localVideo.src;
67
         localVideoSrc = localVideo.src;
68
+
58
         VideoLayout.updateLargeVideo(localVideoSrc, 0);
69
         VideoLayout.updateLargeVideo(localVideoSrc, 0);
59
     };
70
     };
60
 
71
 
63
      * another one instead.
74
      * another one instead.
64
      * @param removedVideoSrc src stream identifier of the video.
75
      * @param removedVideoSrc src stream identifier of the video.
65
      */
76
      */
66
-    my.checkChangeLargeVideo = function(removedVideoSrc) {
77
+    my.updateRemovedVideo = function(removedVideoSrc) {
67
         if (removedVideoSrc === $('#largeVideo').attr('src')) {
78
         if (removedVideoSrc === $('#largeVideo').attr('src')) {
68
             // this is currently displayed as large
79
             // this is currently displayed as large
69
             // pick the last visible video in the row
80
             // pick the last visible video in the row
75
             if (!pick) {
86
             if (!pick) {
76
                 console.info("Last visible video no longer exists");
87
                 console.info("Last visible video no longer exists");
77
                 pick = $('#remoteVideos>span[id!="mixedstream"]>video').get(0);
88
                 pick = $('#remoteVideos>span[id!="mixedstream"]>video').get(0);
78
-                if (!pick) {
89
+
90
+                if (!pick || !pick.src) {
79
                     // Try local video
91
                     // Try local video
80
                     console.info("Fallback to local video...");
92
                     console.info("Fallback to local video...");
81
                     pick = $('#remoteVideos>span>span>video').get(0);
93
                     pick = $('#remoteVideos>span>span>video').get(0);
103
             var isVisible = $('#largeVideo').is(':visible');
115
             var isVisible = $('#largeVideo').is(':visible');
104
 
116
 
105
             $('#largeVideo').fadeOut(300, function () {
117
             $('#largeVideo').fadeOut(300, function () {
118
+                var oldSrc = $(this).attr('src');
119
+
106
                 $(this).attr('src', newSrc);
120
                 $(this).attr('src', newSrc);
107
 
121
 
108
                 // Screen stream is already rotated
122
                 // Screen stream is already rotated
129
                                     ? getDesktopVideoPosition
143
                                     ? getDesktopVideoPosition
130
                                     : getCameraVideoPosition;
144
                                     : getCameraVideoPosition;
131
 
145
 
132
-                if (isVisible)
146
+                if (isVisible) {
147
+                    // Only if the large video is currently visible.
148
+                    // Disable previous dominant speaker video.
149
+                    var oldJid = getJidFromVideoSrc(oldSrc);
150
+                    if (oldJid) {
151
+                        var oldResourceJid = Strophe.getResourceFromJid(oldJid);
152
+                        VideoLayout.enableDominantSpeaker(oldResourceJid, false);
153
+                    }
154
+
155
+                    // Enable new dominant speaker in the remote videos section.
156
+                    var userJid = getJidFromVideoSrc(newSrc);
157
+                    if (userJid)
158
+                    {
159
+                        var resourceJid = Strophe.getResourceFromJid(userJid);
160
+                        VideoLayout.enableDominantSpeaker(resourceJid, true);
161
+                    }
162
+
133
                     $(this).fadeIn(300);
163
                     $(this).fadeIn(300);
164
+                }
134
             });
165
             });
135
         }
166
         }
136
     };
167
     };
138
     my.handleVideoThumbClicked = function(videoSrc) {
169
     my.handleVideoThumbClicked = function(videoSrc) {
139
         // Restore style for previously focused video
170
         // Restore style for previously focused video
140
         var focusJid = getJidFromVideoSrc(focusedVideoSrc);
171
         var focusJid = getJidFromVideoSrc(focusedVideoSrc);
141
-        var oldContainer =
142
-            getParticipantContainer(focusJid);
172
+        var oldContainer = getParticipantContainer(focusJid);
143
 
173
 
144
         if (oldContainer) {
174
         if (oldContainer) {
145
             oldContainer.removeClass("videoContainerFocused");
175
             oldContainer.removeClass("videoContainerFocused");
146
-            VideoLayout.enableActiveSpeaker(
147
-                    Strophe.getResourceFromJid(focusJid), false);
148
         }
176
         }
149
 
177
 
150
-        // Unlock
178
+        // Unlock current focused. 
151
         if (focusedVideoSrc === videoSrc)
179
         if (focusedVideoSrc === videoSrc)
152
         {
180
         {
153
             focusedVideoSrc = null;
181
             focusedVideoSrc = null;
182
+            var dominantSpeakerVideo = null;
183
+            // Enable the currently set dominant speaker.
184
+            if (currentDominantSpeaker) {
185
+                dominantSpeakerVideo
186
+                    = $('#participant_' + currentDominantSpeaker + '>video')
187
+                        .get(0);
188
+
189
+                if (dominantSpeakerVideo) {
190
+                    VideoLayout.updateLargeVideo(dominantSpeakerVideo.src, 1);
191
+                }
192
+            }
193
+
154
             return;
194
             return;
155
         }
195
         }
156
 
196
 
157
         // Lock new video
197
         // Lock new video
158
         focusedVideoSrc = videoSrc;
198
         focusedVideoSrc = videoSrc;
159
 
199
 
200
+        // Update focused/pinned interface.
160
         var userJid = getJidFromVideoSrc(videoSrc);
201
         var userJid = getJidFromVideoSrc(videoSrc);
161
         if (userJid)
202
         if (userJid)
162
         {
203
         {
163
             var container = getParticipantContainer(userJid);
204
             var container = getParticipantContainer(userJid);
164
             container.addClass("videoContainerFocused");
205
             container.addClass("videoContainerFocused");
165
-
166
-            var resourceJid = Strophe.getResourceFromJid(userJid);
167
-            VideoLayout.enableActiveSpeaker(resourceJid, true);
168
         }
206
         }
169
 
207
 
208
+        // Triggers a "video.selected" event. The "false" parameter indicates
209
+        // this isn't a prezi.
170
         $(document).trigger("video.selected", [false]);
210
         $(document).trigger("video.selected", [false]);
171
 
211
 
172
         VideoLayout.updateLargeVideo(videoSrc, 1);
212
         VideoLayout.updateLargeVideo(videoSrc, 1);
215
      * Shows/hides the large video.
255
      * Shows/hides the large video.
216
      */
256
      */
217
     my.setLargeVideoVisible = function(isVisible) {
257
     my.setLargeVideoVisible = function(isVisible) {
258
+        var largeVideoJid = getJidFromVideoSrc($('#largeVideo').attr('src'));
259
+        var resourceJid = Strophe.getResourceFromJid(largeVideoJid);
260
+
218
         if (isVisible) {
261
         if (isVisible) {
219
             $('#largeVideo').css({visibility: 'visible'});
262
             $('#largeVideo').css({visibility: 'visible'});
220
             $('.watermark').css({visibility: 'visible'});
263
             $('.watermark').css({visibility: 'visible'});
264
+            VideoLayout.enableDominantSpeaker(resourceJid, true);
221
         }
265
         }
222
         else {
266
         else {
223
             $('#largeVideo').css({visibility: 'hidden'});
267
             $('#largeVideo').css({visibility: 'hidden'});
224
             $('.watermark').css({visibility: 'hidden'});
268
             $('.watermark').css({visibility: 'hidden'});
269
+            VideoLayout.enableDominantSpeaker(resourceJid, false);
225
         }
270
         }
226
     };
271
     };
227
 
272
 
273
+    /**
274
+     * Indicates if the large video is currently visible.
275
+     *
276
+     * @return <tt>true</tt> if visible, <tt>false</tt> - otherwise
277
+     */
278
+    my.isLargeVideoVisible = function() {
279
+        return $('#largeVideo').is(':visible');
280
+    };
228
 
281
 
229
     /**
282
     /**
230
      * Checks if container for participant identified by given peerJid exists
283
      * Checks if container for participant identified by given peerJid exists
231
      * in the document and creates it eventually.
284
      * in the document and creates it eventually.
232
      * 
285
      * 
233
      * @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
234
      */
290
      */
235
     my.ensurePeerContainerExists = function(peerJid) {
291
     my.ensurePeerContainerExists = function(peerJid) {
236
-        var peerResource = Strophe.getResourceFromJid(peerJid);
237
-        var videoSpanId = 'participant_' + peerResource;
292
+        ContactList.ensureAddContact(peerJid);
293
+
294
+        var resourceJid = Strophe.getResourceFromJid(peerJid);
295
+
296
+        var videoSpanId = 'participant_' + resourceJid;
238
 
297
 
239
         if ($('#' + videoSpanId).length > 0) {
298
         if ($('#' + videoSpanId).length > 0) {
240
             // 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
241
             // interface!!
300
             // interface!!
242
-            if (focus && $('#remote_popupmenu_' + peerResource).length <= 0)
301
+            if (focus && $('#remote_popupmenu_' + resourceJid).length <= 0)
243
                 addRemoteVideoMenu( peerJid,
302
                 addRemoteVideoMenu( peerJid,
244
                                     document.getElementById(videoSpanId));
303
                                     document.getElementById(videoSpanId));
245
-            return;
246
         }
304
         }
247
-
248
-        var container
249
-            = VideoLayout.addRemoteVideoContainer(peerJid, videoSpanId);
250
-
251
-        var nickfield = document.createElement('span');
252
-        nickfield.className = "nick";
253
-        nickfield.appendChild(document.createTextNode(peerResource));
254
-        container.appendChild(nickfield);
255
-        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
+        }
256
     };
323
     };
257
 
324
 
258
     my.addRemoteVideoContainer = function(peerJid, spanId) {
325
     my.addRemoteVideoContainer = function(peerJid, spanId) {
267
             addRemoteVideoMenu(peerJid, container);
334
             addRemoteVideoMenu(peerJid, container);
268
 
335
 
269
         remotes.appendChild(container);
336
         remotes.appendChild(container);
337
+        AudioLevels.updateAudioLevelCanvas(peerJid);
338
+
270
         return container;
339
         return container;
271
     };
340
     };
272
 
341
 
273
     /**
342
     /**
274
-     * 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
275
      */
439
      */
276
-    my.setDisplayName = function(videoSpanId, displayName) {
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.
493
+     */
494
+    function setDisplayName(videoSpanId, displayName) {
277
         var nameSpan = $('#' + videoSpanId + '>span.displayname');
495
         var nameSpan = $('#' + videoSpanId + '>span.displayname');
496
+        var defaultLocalDisplayName = "Me";
497
+        var defaultRemoteDisplayName = "Speaker";
278
 
498
 
279
         // If we already have a display name for this video.
499
         // If we already have a display name for this video.
280
         if (nameSpan.length > 0) {
500
         if (nameSpan.length > 0) {
282
 
502
 
283
             if (nameSpanElement.id === 'localDisplayName' &&
503
             if (nameSpanElement.id === 'localDisplayName' &&
284
                 $('#localDisplayName').text() !== displayName) {
504
                 $('#localDisplayName').text() !== displayName) {
285
-                $('#localDisplayName').text(displayName);
505
+                if (displayName && displayName.length > 0)
506
+                    $('#localDisplayName').text(displayName + ' (me)');
507
+                else
508
+                    $('#localDisplayName').text(defaultLocalDisplayName);
286
             } else {
509
             } else {
287
-                $('#' + videoSpanId + '_name').text(displayName);
510
+                if (displayName && displayName.length > 0)
511
+                    $('#' + videoSpanId + '_name').text(displayName);
512
+                else
513
+                    $('#' + videoSpanId + '_name').text(defaultRemoteDisplayName);
288
             }
514
             }
289
         } else {
515
         } else {
290
             var editButton = null;
516
             var editButton = null;
291
 
517
 
518
+            nameSpan = document.createElement('span');
519
+            nameSpan.className = 'displayname';
520
+            $('#' + videoSpanId)[0].appendChild(nameSpan);
521
+
292
             if (videoSpanId === 'localVideoContainer') {
522
             if (videoSpanId === 'localVideoContainer') {
293
                 editButton = createEditDisplayNameButton();
523
                 editButton = createEditDisplayNameButton();
524
+                nameSpan.innerText = defaultLocalDisplayName;
294
             }
525
             }
295
-            if (displayName.length) {
296
-                nameSpan = document.createElement('span');
297
-                nameSpan.className = 'displayname';
526
+            else {
527
+                nameSpan.innerText = defaultRemoteDisplayName;
528
+            }
529
+
530
+            if (displayName && displayName.length > 0) {
298
                 nameSpan.innerText = displayName;
531
                 nameSpan.innerText = displayName;
299
-                $('#' + videoSpanId)[0].appendChild(nameSpan);
300
             }
532
             }
301
 
533
 
302
             if (!editButton) {
534
             if (!editButton) {
318
                 editableText.setAttribute('placeholder', 'ex. Jane Pink');
550
                 editableText.setAttribute('placeholder', 'ex. Jane Pink');
319
                 $('#' + videoSpanId)[0].appendChild(editableText);
551
                 $('#' + videoSpanId)[0].appendChild(editableText);
320
 
552
 
321
-                $('#localVideoContainer .displayname').bind("click", function (e) {
553
+                $('#localVideoContainer .displayname')
554
+                    .bind("click", function (e) {
555
+
322
                     e.preventDefault();
556
                     e.preventDefault();
323
                     $('#localDisplayName').hide();
557
                     $('#localDisplayName').hide();
324
                     $('#editDisplayName').show();
558
                     $('#editDisplayName').show();
336
                         }
570
                         }
337
 
571
 
338
                         if (!$('#localDisplayName').is(":visible")) {
572
                         if (!$('#localDisplayName').is(":visible")) {
339
-                            if (nickname) {
573
+                            if (nickname)
340
                                 $('#localDisplayName').text(nickname + " (me)");
574
                                 $('#localDisplayName').text(nickname + " (me)");
341
-                                $('#localDisplayName').show();
342
-                            }
343
-                            else {
344
-                                $('#localDisplayName').text(nickname);
345
-                            }
346
-
347
-                            $('#editDisplayName').hide();
575
+                            else
576
+                                $('#localDisplayName')
577
+                                    .text(defaultLocalDisplayName);
578
+                            $('#localDisplayName').show();
348
                         }
579
                         }
580
+
581
+                        $('#editDisplayName').hide();
349
                     };
582
                     };
350
 
583
 
351
                     $('#editDisplayName').one("focusout", function (e) {
584
                     $('#editDisplayName').one("focusout", function (e) {
370
      */
603
      */
371
     my.showDisplayName = function(videoSpanId, isShow) {
604
     my.showDisplayName = function(videoSpanId, isShow) {
372
         var nameSpan = $('#' + videoSpanId + '>span.displayname').get(0);
605
         var nameSpan = $('#' + videoSpanId + '>span.displayname').get(0);
373
-
374
         if (isShow) {
606
         if (isShow) {
375
             if (nameSpan && nameSpan.innerHTML && nameSpan.innerHTML.length) 
607
             if (nameSpan && nameSpan.innerHTML && nameSpan.innerHTML.length) 
376
                 nameSpan.setAttribute("style", "display:inline-block;");
608
                 nameSpan.setAttribute("style", "display:inline-block;");
381
         }
613
         }
382
     };
614
     };
383
 
615
 
616
+    /**
617
+     * Shows the presence status message for the given video.
618
+     */
619
+    my.setPresenceStatus = function (videoSpanId, statusMsg) {
620
+
621
+        if (!$('#' + videoSpanId).length) {
622
+            // No container
623
+            return;
624
+        }
625
+
626
+        var statusSpan = $('#' + videoSpanId + '>span.status');
627
+        if (!statusSpan.length) {
628
+            //Add status span
629
+            statusSpan = document.createElement('span');
630
+            statusSpan.className = 'status';
631
+            statusSpan.id = videoSpanId + '_status';
632
+            $('#' + videoSpanId)[0].appendChild(statusSpan);
633
+
634
+            statusSpan = $('#' + videoSpanId + '>span.status');
635
+        }
636
+
637
+        // Display status
638
+        if (statusMsg && statusMsg.length) {
639
+            $('#' + videoSpanId + '_status').text(statusMsg);
640
+            statusSpan.get(0).setAttribute("style", "display:inline-block;");
641
+        }
642
+        else {
643
+            // Hide
644
+            statusSpan.get(0).setAttribute("style", "display:none;");
645
+        }
646
+    };
647
+
384
     /**
648
     /**
385
      * Shows a visual indicator for the focus of the conference.
649
      * Shows a visual indicator for the focus of the conference.
386
      * Currently if we're not the owner of the conference we obtain the focus
650
      * Currently if we're not the owner of the conference we obtain the focus
413
             if (!indicatorSpan || indicatorSpan.length === 0) {
677
             if (!indicatorSpan || indicatorSpan.length === 0) {
414
                 indicatorSpan = document.createElement('span');
678
                 indicatorSpan = document.createElement('span');
415
                 indicatorSpan.className = 'focusindicator';
679
                 indicatorSpan.className = 'focusindicator';
416
-                Util.setTooltip(indicatorSpan,
417
-                                "The owner of<br/>this conference",
418
-                                "top");
680
+
419
                 focusContainer.appendChild(indicatorSpan);
681
                 focusContainer.appendChild(indicatorSpan);
420
 
682
 
421
                 createFocusIndicatorElement(indicatorSpan);
683
                 createFocusIndicatorElement(indicatorSpan);
506
      * Resizes thumbnails.
768
      * Resizes thumbnails.
507
      */
769
      */
508
     my.resizeThumbnails = function() {
770
     my.resizeThumbnails = function() {
509
-        var thumbnailSize = calculateThumbnailSize();
771
+        var videoSpaceWidth = $('#remoteVideos').width();
772
+
773
+        var thumbnailSize = VideoLayout.calculateThumbnailSize(videoSpaceWidth);
510
         var width = thumbnailSize[0];
774
         var width = thumbnailSize[0];
511
         var height = thumbnailSize[1];
775
         var height = thumbnailSize[1];
512
 
776
 
515
         $('#remoteVideos').height(height);
779
         $('#remoteVideos').height(height);
516
         $('#remoteVideos>span').width(width);
780
         $('#remoteVideos>span').width(width);
517
         $('#remoteVideos>span').height(height);
781
         $('#remoteVideos>span').height(height);
782
+
783
+        $(document).trigger("remotevideo.resized", [width, height]);
518
     };
784
     };
519
 
785
 
520
     /**
786
     /**
521
-     * Enables the active speaker UI.
787
+     * Enables the dominant speaker UI.
522
      *
788
      *
523
      * @param resourceJid the jid indicating the video element to
789
      * @param resourceJid the jid indicating the video element to
524
      * activate/deactivate
790
      * activate/deactivate
525
-     * @param isEnable indicates if the active speaker should be enabled or
791
+     * @param isEnable indicates if the dominant speaker should be enabled or
526
      * disabled
792
      * disabled
527
      */
793
      */
528
-    my.enableActiveSpeaker = function(resourceJid, isEnable) {
529
-        console.log("Enable active speaker", resourceJid, isEnable);
794
+    my.enableDominantSpeaker = function(resourceJid, isEnable) {
795
+        var displayName = resourceJid;
796
+        var nameSpan = $('#participant_' + resourceJid + '>span.displayname');
797
+        if (nameSpan.length > 0)
798
+            displayName = nameSpan.text();
799
+
800
+        console.log("UI enable dominant speaker",
801
+                    displayName,
802
+                    resourceJid,
803
+                    isEnable);
530
 
804
 
531
         var videoSpanId = null;
805
         var videoSpanId = null;
806
+        var videoContainerId = null;
532
         if (resourceJid
807
         if (resourceJid
533
-                === Strophe.getResourceFromJid(connection.emuc.myroomjid))
808
+                === Strophe.getResourceFromJid(connection.emuc.myroomjid)) {
534
             videoSpanId = 'localVideoWrapper';
809
             videoSpanId = 'localVideoWrapper';
535
-        else
810
+            videoContainerId = 'localVideoContainer';
811
+        }
812
+        else {
536
             videoSpanId = 'participant_' + resourceJid;
813
             videoSpanId = 'participant_' + resourceJid;
814
+            videoContainerId = videoSpanId;
815
+        }
537
 
816
 
538
-        videoSpan = document.getElementById(videoSpanId);
817
+        videoSpan = document.getElementById(videoContainerId);
539
 
818
 
540
         if (!videoSpan) {
819
         if (!videoSpan) {
541
             console.error("No video element for jid", resourceJid);
820
             console.error("No video element for jid", resourceJid);
542
             return;
821
             return;
543
         }
822
         }
544
 
823
 
545
-        // If there's an active speaker (automatically) selected we have to
546
-        // disable this state and update the current active speaker.
547
-        if (isEnable) {
548
-            if (currentActiveSpeaker) {
549
-                var oldSpeaker = currentActiveSpeaker;
550
-                setTimeout(function () {
551
-                    VideoLayout.enableActiveSpeaker(oldSpeaker, false);
552
-                    }, 200);
553
-            }
554
-
555
-            currentActiveSpeaker = resourceJid;
556
-        }
557
-        else if (resourceJid === currentActiveSpeaker)
558
-            currentActiveSpeaker = null;
559
-
560
         var video = $('#' + videoSpanId + '>video');
824
         var video = $('#' + videoSpanId + '>video');
561
 
825
 
562
         if (video && video.length > 0) {
826
         if (video && video.length > 0) {
563
-            var videoElement = video.get(0);
564
             if (isEnable) {
827
             if (isEnable) {
565
-                if (!videoElement.classList.contains("activespeaker"))
566
-                    videoElement.classList.add("activespeaker");
828
+                VideoLayout.showDisplayName(videoContainerId, true);
829
+
830
+                if (!videoSpan.classList.contains("dominantspeaker"))
831
+                    videoSpan.classList.add("dominantspeaker");
567
 
832
 
568
-                VideoLayout.showDisplayName(videoSpanId, true);
833
+                video.css({visibility: 'hidden'});
569
             }
834
             }
570
             else {
835
             else {
571
-                VideoLayout.showDisplayName(videoSpanId, false);
836
+                VideoLayout.showDisplayName(videoContainerId, false);
837
+
838
+                if (videoSpan.classList.contains("dominantspeaker"))
839
+                    videoSpan.classList.remove("dominantspeaker");
572
 
840
 
573
-                if (videoElement.classList.contains("activespeaker"))
574
-                    videoElement.classList.remove("activespeaker");
841
+                video.css({visibility: 'visible'});
575
             }
842
             }
576
         }
843
         }
577
     };
844
     };
616
 
883
 
617
     /**
884
     /**
618
      * Calculates the thumbnail size.
885
      * Calculates the thumbnail size.
886
+     *
887
+     * @param videoSpaceWidth the width of the video space
619
      */
888
      */
620
-    var calculateThumbnailSize = function () {
889
+    my.calculateThumbnailSize = function (videoSpaceWidth) {
621
         // Calculate the available height, which is the inner window height minus
890
         // Calculate the available height, which is the inner window height minus
622
        // 39px for the header minus 2px for the delimiter lines on the top and
891
        // 39px for the header minus 2px for the delimiter lines on the top and
623
        // bottom of the large video, minus the 36px space inside the remoteVideos
892
        // bottom of the large video, minus the 36px space inside the remoteVideos
624
        // container used for highlighting shadow.
893
        // container used for highlighting shadow.
625
        var availableHeight = 100;
894
        var availableHeight = 100;
626
 
895
 
627
-       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;
901
+
902
+       // Remove the 3px borders arround videos and border around the remote
903
+       // videos area
904
+       var availableWinWidth = videoSpaceWidth - 2 * 3 * numvids - 70;
628
 
905
 
629
-       // Remove the 1px borders arround videos and the chat width.
630
-       var availableWinWidth = $('#remoteVideos').width() - 2 * numvids - 50;
631
        var availableWidth = availableWinWidth / numvids;
906
        var availableWidth = availableWinWidth / numvids;
632
        var aspectRatio = 16.0 / 9.0;
907
        var aspectRatio = 16.0 / 9.0;
633
        var maxHeight = Math.min(160, availableHeight);
908
        var maxHeight = Math.min(160, availableHeight);
703
         var focusIndicator = document.createElement('i');
978
         var focusIndicator = document.createElement('i');
704
         focusIndicator.className = 'fa fa-star';
979
         focusIndicator.className = 'fa fa-star';
705
         parentElement.appendChild(focusIndicator);
980
         parentElement.appendChild(focusIndicator);
981
+
982
+        Util.setTooltip(parentElement,
983
+                "The owner of<br/>this conference",
984
+                "top");
706
     }
985
     }
707
 
986
 
708
     /**
987
     /**
733
         }
1012
         }
734
     };
1013
     };
735
 
1014
 
1015
+    /**
1016
+     * Returns the current dominant speaker resource jid.
1017
+     */
1018
+    my.getDominantSpeakerResourceJid = function () {
1019
+        return currentDominantSpeaker;
1020
+    };
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
+
736
     /**
1036
     /**
737
      * 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
738
      * given <tt>parentElement</tt>.
1038
      * given <tt>parentElement</tt>.
810
         popupmenuElement.appendChild(ejectMenuItem);
1110
         popupmenuElement.appendChild(ejectMenuItem);
811
     }
1111
     }
812
 
1112
 
1113
+    /**
1114
+     * On audio muted event.
1115
+     */
813
     $(document).bind('audiomuted.muc', function (event, jid, isMuted) {
1116
     $(document).bind('audiomuted.muc', function (event, jid, isMuted) {
814
         var videoSpanId = null;
1117
         var videoSpanId = null;
815
         if (jid === connection.emuc.myroomjid) {
1118
         if (jid === connection.emuc.myroomjid) {
828
             VideoLayout.showAudioIndicator(videoSpanId, isMuted);
1131
             VideoLayout.showAudioIndicator(videoSpanId, isMuted);
829
     });
1132
     });
830
 
1133
 
1134
+    /**
1135
+     * On video muted event.
1136
+     */
831
     $(document).bind('videomuted.muc', function (event, jid, isMuted) {
1137
     $(document).bind('videomuted.muc', function (event, jid, isMuted) {
832
         var videoSpanId = null;
1138
         var videoSpanId = null;
833
         if (jid === connection.emuc.myroomjid) {
1139
         if (jid === connection.emuc.myroomjid) {
841
             VideoLayout.showVideoIndicator(videoSpanId, isMuted);
1147
             VideoLayout.showVideoIndicator(videoSpanId, isMuted);
842
     });
1148
     });
843
 
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
+
1169
+    /**
1170
+     * On dominant speaker changed event.
1171
+     */
1172
+    $(document).bind('dominantspeakerchanged', function (event, resourceJid) {
1173
+        // We ignore local user events.
1174
+        if (resourceJid
1175
+                === Strophe.getResourceFromJid(connection.emuc.myroomjid))
1176
+            return;
1177
+
1178
+        // Update the current dominant speaker.
1179
+        if (resourceJid !== currentDominantSpeaker)
1180
+            currentDominantSpeaker = resourceJid;
1181
+        else
1182
+            return;
1183
+
1184
+        // Obtain container for new dominant speaker.
1185
+        var container  = document.getElementById(
1186
+                'participant_' + resourceJid);
1187
+
1188
+        // Local video will not have container found, but that's ok
1189
+        // since we don't want to switch to local video.
1190
+        if (container && !focusedVideoSrc)
1191
+        {
1192
+            var video = container.getElementsByTagName("video");
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)
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);
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();
1283
+        }
1284
+    });
1285
+
844
     return my;
1286
     return my;
845
-}(VideoLayout || {}));
1287
+}(VideoLayout || {}));

Laddar…
Avbryt
Spara